diff --git a/pyscf/dft/radi.py b/pyscf/dft/radi.py index d5ed0ee0f9..c5b835a0d2 100644 --- a/pyscf/dft/radi.py +++ b/pyscf/dft/radi.py @@ -66,6 +66,17 @@ def delley(n, *args, **kwargs): return r, dr gauss_legendre = delley +def mura_knowles_inputFar(n, far, charge=None, *args, **kwargs): + '''Mura-Knowles (JCP, 104, 9848) log3 quadrature radial grids''' + far = 50 + r = numpy.empty(n) + dr = numpy.empty(n) + for i in range(n): + x = (i+.5) / n + r[i] = -far * numpy.log(1-x**3) + dr[i] = far * 3*x*x/((1-x**3)*n) + return r, dr + def mura_knowles(n, charge=None, *args, **kwargs): '''Mura-Knowles (JCP, 104, 9848) log3 quadrature radial grids''' r = numpy.empty(n) @@ -84,6 +95,16 @@ def mura_knowles(n, charge=None, *args, **kwargs): # Gauss-Chebyshev of the second kind, and the transformed interval [0,\infty) # Ref Matthias Krack and Andreas M. Koster, J. Chem. Phys. 108 (1998), 3226 def gauss_chebyshev(n, *args, **kwargs): + '''Mura-Knowles (JCP, 104, 9848) log3 quadrature radial grids''' + far = 50 + r = numpy.empty(n) + dr = numpy.empty(n) + for i in range(n): + x = (i+.5) / n + r[i] = -far * numpy.log(1-x**3) + dr[i] = far * 3*x*x/((1-x**3)*n) + return r, dr + '''Gauss-Chebyshev (JCP, 108, 3226) radial grids''' ln2 = 1 / numpy.log(2) fac = 16./3 / (n+1) diff --git a/pyscf/gto/__init__.py b/pyscf/gto/__init__.py index 60be5e5d35..9741f2e061 100644 --- a/pyscf/gto/__init__.py +++ b/pyscf/gto/__init__.py @@ -22,6 +22,7 @@ from pyscf.gto.mole import * from pyscf.gto.moleintor import getints, getints_by_shell from pyscf.gto.eval_gto import eval_gto +from pyscf.gto.write_gto import write_gto from pyscf.gto import ecp parse = basis.parse diff --git a/pyscf/gto/basis/__init__.py b/pyscf/gto/basis/__init__.py index 2d6a2aae42..1d219522e4 100644 --- a/pyscf/gto/basis/__init__.py +++ b/pyscf/gto/basis/__init__.py @@ -152,6 +152,7 @@ 'def2qzvppd' : 'def2-qzvppd.dat', 'def2qzvpp' : 'def2-qzvpp.dat' , 'def2qzvp' : 'def2-qzvp.dat' , + 'def2univjkfit' : 'def2-univ-jkfit.dat' , 'def2svpjfit' : 'def2-svp-jfit.dat' , 'def2svpjkfit' : 'def2-svp-jkfit.dat' , 'def2tzvpjfit' : 'def2-tzvp-jfit.dat' , diff --git a/pyscf/gto/write_gto.py b/pyscf/gto/write_gto.py new file mode 100644 index 0000000000..d491b9d5fd --- /dev/null +++ b/pyscf/gto/write_gto.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# Copyright 2014-2018 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author: Qiming Sun +# + +import warnings +import ctypes +import numpy +from pyscf import lib +from pyscf.gto.moleintor import make_loc + +BLKSIZE = 128 # needs to be the same to lib/gto/grid_ao_drv.c + +libcgto = lib.load_library('libcgto') + +def write_gto(mol): + atm = numpy.asarray(mol._atm, dtype=numpy.int32, order='C') + bas = numpy.asarray(mol._bas, dtype=numpy.int32, order='C') + env = numpy.asarray(mol._env, dtype=numpy.double, order='C') + natm = atm.shape[0] + nbas = bas.shape[0] + + eval_name, comp = _get_intor_and_comp(mol, "GTOval") + ao_loc = make_loc(bas, eval_name) + + shls_slice = (0, nbas) + sh0, sh1 = shls_slice + nao = ao_loc[sh1] - ao_loc[sh0] + + ngrids = 1 + non0tab = numpy.ones(((ngrids+BLKSIZE-1)//BLKSIZE,nbas), + dtype=numpy.int8) + + basisInfo = open("basisInfo.txt", "w") + if ("cart" in eval_name): + basisInfo.write("cart\n") + else: + basisInfo.write("sph\n") + basisInfo.write("%i\n"%(nao)) + basisInfo.write("%i\n%i \n"%(shls_slice[0], shls_slice[1])) + basisInfo.write("%i\n"%(len(ao_loc))) + for a in ao_loc: + basisInfo.write("%i "%(a)) + basisInfo.write("\n%i %i\n"%(atm.shape[0], atm.shape[1])) + for i in range(atm.shape[0]): + for j in range(atm.shape[1]): + basisInfo.write("%i "%(atm[i][j])) + basisInfo.write("\n%i %i\n"%(bas.shape[0], bas.shape[1])) + for i in range(bas.shape[0]): + for j in range(bas.shape[1]): + basisInfo.write("%i "%(bas[i][j])) + basisInfo.write("\n%i\n"%(len(env))) + for e in env: + basisInfo.write("%35.18e\n"%(e)) + + basisInfo.write("%i\n"%(len(non0tab[0]))) + for e in non0tab[0]: + basisInfo.write("%i "%(e)) + + basisInfo.close() + +def _get_intor_and_comp(mol, eval_name, comp=None): + if not ('_sph' in eval_name or '_cart' in eval_name or + '_spinor' in eval_name): + if mol.cart: + eval_name = eval_name + '_cart' + else: + eval_name = eval_name + '_sph' + + if comp is None: + if '_spinor' in eval_name: + fname = eval_name.replace('_spinor', '') + comp = _GTO_EVAL_FUNCTIONS.get(fname, (None,None))[1] + else: + fname = eval_name.replace('_sph', '').replace('_cart', '') + comp = _GTO_EVAL_FUNCTIONS.get(fname, (None,None))[0] + if comp is None: + warnings.warn('Function %s not found. Set its comp to 1' % eval_name) + comp = 1 + return eval_name, comp + +_GTO_EVAL_FUNCTIONS = { +# Functiona name : (comp-for-scalar, comp-for-spinor) + 'GTOval' : (1, 1 ), + 'GTOval_ip' : (3, 3 ), + 'GTOval_ig' : (3, 3 ), + 'GTOval_ipig' : (3, 3 ), + 'GTOval_deriv0' : (1, 1 ), + 'GTOval_deriv1' : (4, 4 ), + 'GTOval_deriv2' : (10,10), + 'GTOval_deriv3' : (20,20), + 'GTOval_deriv4' : (35,35), + 'GTOval_sp' : (4, 1 ), + 'GTOval_ipsp' : (12,3 ), +} diff --git a/pyscf/scf/__init__.py b/pyscf/scf/__init__.py index 5e181692c6..6e48028a04 100644 --- a/pyscf/scf/__init__.py +++ b/pyscf/scf/__init__.py @@ -118,6 +118,7 @@ from pyscf.scf.uhf import spin_square from pyscf.scf.hf import get_init_guess from pyscf.scf.addons import * +from pyscf.scf import aochf def HF(mol, *args): @@ -185,10 +186,13 @@ def DHF(mol, *args): return dhf.UHF(mol, *args) -def X2C(mol, *args): +def X2C(mol, *kwargs): '''X2C UHF (in testing)''' from pyscf.x2c import x2c - return x2c.UHF(mol, *args) + return x2c.UHF(mol, *kwargs) + +def AOCHF(mol, *kwargs): + return aochf.AOCHF(mol, *kwargs) def sfx2c1e(mf): return mf.sfx2c1e() diff --git a/pyscf/scf/aochf.py b/pyscf/scf/aochf.py new file mode 100644 index 0000000000..cc4a581cdd --- /dev/null +++ b/pyscf/scf/aochf.py @@ -0,0 +1,114 @@ +''' +Average of configuration Hartree-Fock +as described in DIRAC by Timo Fleig +current plan is to use this to generate spherical symmetric reference atom. +''' + +from functools import reduce +import numpy +import pyscf.gto +from pyscf import lib +from pyscf.lib import logger +from pyscf.scf import hf, rohf, uhf +import pyscf.scf.chkfile +from pyscf import __config__ + +def get_occ(mf, mo_energy=None, mo_coeff=None, nclose=None, nact=None, nopen=None): + '''Label occupancies for each orbitals + + Kwargs: + mo_energy : 1D ndarray + Obital energies + + mo_coeff : 2D ndarray + Obital coefficients + + nclose : int + Number of core orbitals + + nact : int + Number of active electrons + + nopen : int + Number of active orbitals + ''' + if mo_energy is None: mo_energy = mf.mo_energy + if nact is None : nact = mf.nact + if nopen is None : nopen = mf.nopen // 2 + + nclose = (mf.mol.nelectron - nact) // 2 + if mo_energy is None: mo_energy = mf.mo_energy + e_idx_a = numpy.argsort(mo_energy[0]) + e_idx_b = numpy.argsort(mo_energy[1]) + e_sort_a = mo_energy[0][e_idx_a] + e_sort_b = mo_energy[1][e_idx_b] + nmo = mo_energy[0].size + n_a, n_b = nclose+nopen, nclose+nopen + mo_occ = numpy.zeros_like(mo_energy) + mo_occ[0,e_idx_a[:nclose]] = 1 + mo_occ[1,e_idx_b[:nclose]] = 1 + mo_occ[0,e_idx_a[nclose:nclose+nopen]] = nact/nopen/2. + mo_occ[1,e_idx_b[nclose:nclose+nopen]] = nact/nopen/2. + if mf.verbose >= logger.INFO and n_a < nmo and n_b > 0 and n_b < nmo: + if e_sort_a[n_a-1]+1e-3 > e_sort_a[n_a]: + logger.warn(mf, 'alpha nocc = %d HOMO %.15g >= LUMO %.15g', + n_a, e_sort_a[n_a-1], e_sort_a[n_a]) + else: + logger.info(mf, ' alpha nocc = %d HOMO = %.15g LUMO = %.15g', + n_a, e_sort_a[n_a-1], e_sort_a[n_a]) + + if e_sort_b[n_b-1]+1e-3 > e_sort_b[n_b]: + logger.warn(mf, 'beta nocc = %d HOMO %.15g >= LUMO %.15g', + n_b, e_sort_b[n_b-1], e_sort_b[n_b]) + else: + logger.info(mf, ' beta nocc = %d HOMO = %.15g LUMO = %.15g', + n_b, e_sort_b[n_b-1], e_sort_b[n_b]) + + if e_sort_a[n_a-1]+1e-3 > e_sort_b[n_b]: + logger.warn(mf, 'system HOMO %.15g >= system LUMO %.15g', + e_sort_b[n_a-1], e_sort_b[n_b]) + + numpy.set_printoptions(threshold=nmo) + logger.debug(mf, ' alpha mo_energy =\n%s', mo_energy[0]) + logger.debug(mf, ' beta mo_energy =\n%s', mo_energy[1]) + numpy.set_printoptions(threshold=1000) + + if mo_coeff is not None and mf.verbose >= logger.DEBUG: + ss, s = mf.spin_square((mo_coeff[0][:,mo_occ[0]>0], + mo_coeff[1][:,mo_occ[1]>0]), mf.get_ovlp()) + logger.debug(mf, 'multiplicity = %.8g 2S+1 = %.8g', ss, s) + return mo_occ + +def make_rdm1(mo_coeff, mo_occ, **kwargs): + '''One-particle densit matrix. mo_occ is a 1D array, with occupancy 1 or 2. + ''' + print(mo_occ) + mo_a = mo_coeff[:,mo_occ>0] + mo_b = mo_coeff[:,mo_occ==2] + occ_a = numpy.zeros(mo_occ.size) + occ_a[mo_occ==2] = 1 + occ_a = mo_occ-occ_a + print(occ_a) + dm_a = numpy.dot(mo_a*occ_a[:5],mo_a.conj().T) + #dm_a = reduce(numpy.dot, (mo_a, mo_a.conj().T)) + dm_b = numpy.dot(mo_b, mo_b.conj().T) + #print(dm_a-numpy.dot(mo_a, mo_a.conj().T)) + return numpy.array((dm_a, dm_b)) + +class AOCHF(uhf.UHF): + __doc__ = hf.SCF.__doc__ + nclose = getattr(__config__, 'nclose', 0) + nact = getattr(__config__, 'nact', 0) + nopen = getattr(__config__, 'nopen', 0) + + def __init__(self, mol): + hf.SCF.__init__(self, mol) + self.nelec=None + + get_occ = get_occ + #@lib.with_doc(make_rdm1.__doc__) + #def make_rdm1(self, mo_coeff=None, mo_occ=None, **kwargs): + # if mo_coeff is None: mo_coeff = self.mo_coeff + # if mo_occ is None: mo_occ = self.mo_occ + # return make_rdm1(mo_coeff, mo_occ, **kwargs) + diff --git a/pyscf/scf/dhf.py b/pyscf/scf/dhf.py index 1d3a8a961b..b24a0cea6d 100644 --- a/pyscf/scf/dhf.py +++ b/pyscf/scf/dhf.py @@ -108,6 +108,40 @@ def get_jk_coulomb(mol, dm, hermi=1, coulomb_allow='SSSS', vk[...,n2c:,n2c:] += k1 return vj, vk + +def get_jk_sf_coulomb(mol, dm, hermi=1, coulomb_allow='SSSS', + opt_llll=None, opt_ssll=None, opt_ssss=None, omega=None, verbose=None): + log = logger.new_logger(mol, verbose) + with mol.with_range_coulomb(omega): + if coulomb_allow.upper() == 'LLLL': + log.debug('Coulomb integral: (LL|LL)') + j1, k1 = _call_veff_llll(mol, dm, hermi, opt_llll) + n2c = j1.shape[1] + vj = numpy.zeros_like(dm) + vk = numpy.zeros_like(dm) + vj[...,:n2c,:n2c] = j1 + vk[...,:n2c,:n2c] = k1 + elif coulomb_allow.upper() == 'SSLL' \ + or coulomb_allow.upper() == 'LLSS': + log.debug('Coulomb integral: (LL|LL) + (SS|LL)') + vj, vk = _call_veff_sf_ssll(mol, dm, hermi, opt_ssll) + j1, k1 = _call_veff_llll(mol, dm, hermi, opt_llll) + n2c = j1.shape[1] + vj[...,:n2c,:n2c] += j1 + vk[...,:n2c,:n2c] += k1 + else: # coulomb_allow == 'SSSS' + log.debug('Coulomb integral: (LL|LL) + (SS|LL) + (SS|SS)') + vj, vk = _call_veff_sf_ssll(mol, dm, hermi, opt_ssll) + j1, k1 = _call_veff_llll(mol, dm, hermi, opt_llll) + n2c = j1.shape[1] + vj[...,:n2c,:n2c] += j1 + vk[...,:n2c,:n2c] += k1 + j1, k1 = _call_veff_sf_ssss(mol, dm, hermi, opt_ssss) + vj[...,n2c:,n2c:] += j1 + vk[...,n2c:,n2c:] += k1 + + return vj, vk + get_jk = get_jk_coulomb @@ -376,6 +410,9 @@ class UHF(hf.SCF): with_ssss = getattr(__config__, 'scf_dhf_SCF_with_ssss', True) with_gaunt = getattr(__config__, 'scf_dhf_SCF_with_gaunt', False) with_breit = getattr(__config__, 'scf_dhf_SCF_with_breit', False) + nopen = getattr(__config__, 'scf_dhf_SCF_nopen', 0) + nact = getattr(__config__, 'scf_dhf_SCF_nopen', 0) + nopen = getattr(__config__, 'scf_dhf_SCF_nopen', 0) def __init__(self, mol): hf.SCF.__init__(self, mol) @@ -437,20 +474,31 @@ def build(self, mol=None): def get_occ(self, mo_energy=None, mo_coeff=None): if mo_energy is None: mo_energy = self.mo_energy mol = self.mol + nopen = self.nopen + nact = self.nact + nclose = mol.nelectron - nact c = lib.param.LIGHT_SPEED n4c = len(mo_energy) n2c = n4c // 2 mo_occ = numpy.zeros(n2c * 2) if mo_energy[n2c] > -1.999 * c**2: - mo_occ[n2c:n2c+mol.nelectron] = 1 + if nopen is 0: + mo_occ[n2c:n2c+mol.nelectron] = 1 + else: + mo_occ[n2c:n2c+nclose] = 1 + mo_occ[n2c+nclose:n2c+nclose+nopen] = 1.*nact/nopen else: lumo = mo_energy[mo_energy > -1.999 * c**2][mol.nelectron] mo_occ[mo_energy > -1.999 * c**2] = 1 mo_occ[mo_energy >= lumo] = 0 if self.verbose >= logger.INFO: + if nopen is 0: + homo_ndx = mol.nelectron + else: + homo_ndx = nclose + nopen logger.info(self, 'HOMO %d = %.12g LUMO %d = %.12g', - n2c+mol.nelectron, mo_energy[n2c+mol.nelectron-1], - n2c+mol.nelectron+1, mo_energy[n2c+mol.nelectron]) + n2c+homo_ndx, mo_energy[n2c+homo_ndx-1], + n2c+homo_ndx+1, mo_energy[n2c+homo_ndx]) logger.debug1(self, 'NES mo_energy = %s', mo_energy[:n2c]) logger.debug(self, 'PES mo_energy = %s', mo_energy[n2c:]) return mo_occ @@ -681,6 +729,36 @@ def _call_veff_ssll(mol, dm, hermi=1, mf_opt=None): vk = vk.reshape(vk.shape[1:]) return _jk_triu_(vj, vk, hermi) +def _call_veff_sf_ssll(mol, dm, hermi=1, mf_opt=None): + if isinstance(dm, numpy.ndarray) and dm.ndim == 2: + n_dm = 1 + n2c = dm.shape[0] // 2 + dmll = dm[:n2c,:n2c].copy() + dmsl = dm[n2c:,:n2c].copy() + dmss = dm[n2c:,n2c:].copy() + dms = (dmll, dmss, dmsl) + else: + n_dm = len(dm) + n2c = dm[0].shape[0] // 2 + dms = [dmi[:n2c,:n2c].copy() for dmi in dm] \ + + [dmi[n2c:,n2c:].copy() for dmi in dm] \ + + [dmi[n2c:,:n2c].copy() for dmi in dm] + jks = ('lk->s2ij',) * n_dm \ + + ('ji->s2kl',) * n_dm \ + + ('jk->s1il',) * n_dm + c1 = .5 / lib.param.LIGHT_SPEED + vx = _vhf.rdirect_bindm('int2e_pp1_spinor', 's4', jks, dms, 1, + mol._atm, mol._bas, mol._env, mf_opt) * c1**2 + vj = numpy.zeros((n_dm,n2c*2,n2c*2), dtype=numpy.complex) + vk = numpy.zeros((n_dm,n2c*2,n2c*2), dtype=numpy.complex) + vj[:,n2c:,n2c:] = vx[ :n_dm ,:,:] + vj[:,:n2c,:n2c] = vx[n_dm :n_dm*2,:,:] + vk[:,n2c:,:n2c] = vx[n_dm*2: ,:,:] + if n_dm == 1: + vj = vj.reshape(vj.shape[1:]) + vk = vk.reshape(vk.shape[1:]) + return _jk_triu_(vj, vk, hermi) + def _call_veff_ssss(mol, dm, hermi=1, mf_opt=None): c1 = .5 / lib.param.LIGHT_SPEED if isinstance(dm, numpy.ndarray) and dm.ndim == 2: @@ -696,6 +774,21 @@ def _call_veff_ssss(mol, dm, hermi=1, mf_opt=None): mol._atm, mol._bas, mol._env, mf_opt) * c1**4 return _jk_triu_(vj, vk, hermi) +def _call_veff_sf_ssss(mol, dm, hermi=1, mf_opt=None): + c1 = .5 / lib.param.LIGHT_SPEED + if isinstance(dm, numpy.ndarray) and dm.ndim == 2: + n2c = dm.shape[0] // 2 + dms = dm[n2c:,n2c:].copy() + else: + n2c = dm[0].shape[0] // 2 + dms = [] + for dmi in dm: + dms.append(dmi[n2c:,n2c:].copy()) + vj, vk = _vhf.rdirect_mapdm('int2e_pp1pp2_spinor', 's8', + ('ji->s2kl', 'jk->s1il'), dms, 1, + mol._atm, mol._bas, mol._env, mf_opt) * c1**4 + return _jk_triu_(vj, vk, hermi) + def _call_veff_gaunt_breit(mol, dm, hermi=1, mf_opt=None, with_breit=False): if with_breit: intor_prefix = 'int2e_breit_' diff --git a/pyscf/scf/hf.py b/pyscf/scf/hf.py index fe02c3f92b..c10f9fe369 100644 --- a/pyscf/scf/hf.py +++ b/pyscf/scf/hf.py @@ -35,6 +35,7 @@ from pyscf.scf import chkfile from pyscf.data import nist, elements from pyscf import __config__ +from pyscf import x2c WITH_META_LOWDIN = getattr(__config__, 'scf_analyze_with_meta_lowdin', True) PRE_ORTH_METHOD = getattr(__config__, 'scf_analyze_pre_orth_method', 'ANO') @@ -206,6 +207,7 @@ def kernel(mf, conv_tol=1e-10, conv_tol_grad=None, #fock = mf.get_fock(h1e, s1e, vhf, dm) # = h1e + vhf mo_energy, mo_coeff = mf.eig(fock, s1e) mo_occ = mf.get_occ(mo_energy, mo_coeff) + dm, dm_last = mf.make_rdm1(mo_coeff, mo_occ), dm dm = lib.tag_array(dm, mo_coeff=mo_coeff, mo_occ=mo_occ) vhf = mf.get_veff(mol, dm, dm_last, vhf) diff --git a/pyscf/shciscf/__init__.py b/pyscf/shciscf/__init__.py index 31982cc7a5..47528a364b 100644 --- a/pyscf/shciscf/__init__.py +++ b/pyscf/shciscf/__init__.py @@ -16,3 +16,6 @@ # Author: Sandeep Sharma # James Smith # + +from . import shci +from . import socutils diff --git a/pyscf/shciscf/examples/06_I_soc.py b/pyscf/shciscf/examples/06_I_soc.py new file mode 100644 index 0000000000..6b82508fd7 --- /dev/null +++ b/pyscf/shciscf/examples/06_I_soc.py @@ -0,0 +1,117 @@ +from pyscf import gto, scf, mcscf +from pyscf.shciscf import shci, socutils +import numpy + +# molecule object +# to carry out ecp calculation, spin-orbit pseudo potential has to be provided +# crenbl and crenbs ecp are currently the only available in pyscf, for more ecps +# visit http://www.tc.uni-koeln.de/PP/index.en.html it is only available in molpro format. +# or http://www.cosmologic-services.de/basis-sets/basissets.php the dhf-()-2c basis sets also have spin-orbit ecps. +mol = gto.M( + verbose = 4, + atom = "I 0 0 0", + basis = 'cc-pvdz-pp', + ecp = ''' +I nelec 28 +I ul +2 1.00000000 0.00000000 +I S +2 40.03337600 49.98964900 +2 17.30057600 281.00655600 +2 8.85172000 61.41673900 +I P +2 15.72014100 67.41623900 -134.832478 +2 15.20822200 134.80769600 134.807696 +2 8.29418600 14.56654800 -29.133096 +2 7.75394900 28.96842200 28.968422 +I D +2 13.81775100 35.53875600 -35.538756 +2 13.58780500 53.33975900 35.559839 +2 6.94763000 9.71646600 -9.716466 +2 6.96009900 14.97750000 9.985000 +I F +2 18.52295000 -20.17661800 13.451079 +2 18.25103500 -26.08807700 -13.044039 +2 7.55790100 -0.22043400 0.146956 +2 7.59740400 -0.22164600 -0.110823 + ''', + spin = 1) + +# mean-field object +mf = scf.RHF(mol) +mf.kernel() + +# state-average mcscf calculation with valence active space +mc = mcscf.CASSCF(mf, 4, 7).state_average_(numpy.ones(3)/3.0) +mc.internal_rotation = True +mc.mc1step() + +# writes the SOC integrals, by default picture change effects are +# considered using breit-pauli Hamiltonian +socutils.writeSOCIntegrals(mc) + +# perform a one-step SHCI calculation +mch = shci.SHCISCF(mf, 4, 7).state_average_(numpy.ones(6)/6.0) +mch.fcisolver.DoSOC = True +mch.fcisolver.DoRDM = False +# the first three doublet states are degenerate at non-relativistic calculations +# due to spin-orbit coupling, it splits into a four-fold degenerate state and +# a two-fold degenerate state +mch.fcisolver.nroots = 6 +mch.fcisolver.sweep_iter = [0] +mch.fcisolver.sweep_epsilon = [1e-5] +mch.fcisolver.dets = [[0,1,2,3,4,5,6],[0,1,2,3,4,5,7]] +shci.dryrun(mch, mc.mo_coeff) + +e_ecp = shci.readEnergy(mch.fcisolver) + +# then do a very small all electron calculation for reference +mol_all_elec = gto.M( + verbose = 4, + atom = 'I 0 0 0', + basis = 'ano@6s5p3d1f', + max_memory = 2000, + spin = 1) +mf_nr = scf.RHF(mol_all_elec) +mf_nr.kernel() + +mc_nr = mcscf.CASSCF(mf_nr, 4, 7).state_average_(numpy.ones(3)/3.0) +mc_nr.internal_rotation = True +mc_nr.mc2step() + +socutils.writeSOCIntegrals(mc_nr, pictureChange1e = 'bp', pictureChange2e = 'bp') + +mch_nr = shci.SHCISCF(mf_nr, 4, 7).state_average_(numpy.ones(6)/6.0) +mch_nr.fcisolver.DoSOC = True +mch_nr.fcisolver.DoRDM = False +mch_nr.fcisolver.nroots = 6 +mch_nr.fcisolver.sweep_iter = [0] +mch_nr.fcisolver.sweep_epsilon = [1e-5] +mch_nr.fcisolver.dets = [[0,1,2,3,4,5,6],[0,1,2,3,4,5,7]] +shci.dryrun(mch_nr, mc_nr.mo_coeff) + +e_bp = shci.readEnergy(mch_nr.fcisolver) + +mf_x2c = scf.sfx2c1e(scf.RHF(mol_all_elec)) +mf_x2c.kernel() +mc_x2c = mcscf.CASSCF(mf_x2c, 4, 7).state_average_(numpy.ones(3)/3.0) +mc_x2c.mc2step() + +socutils.writeSOCIntegrals(mc_x2c, pictureChange1e = 'x2c1', pictureChange2e = 'x2c') +mch_x2c = shci.SHCISCF(mf_x2c, 4, 7).state_average_(numpy.ones(6)/6.0) +mch_x2c.fcisolver.DoSOC = True +mch_x2c.fcisolver.DoRDM = False +mch_x2c.fcisolver.nroots = 6 +mch_x2c.fcisolver.sweep_iter = [0] +mch_x2c.fcisolver.sweep_epsilon = [1e-5] +mch_x2c.fcisolver.dets = [[0,1,2,3,4,5,6],[0,1,2,3,4,5,7]] +shci.dryrun(mch_x2c, mc.mo_coeff) + +e_x2c = shci.readEnergy(mch_x2c.fcisolver) + +print( "%11s%20s %12s%11s%12s %11s \n" %("", "SOECP(Har) (cm^-1)", " BP(Har) "," (cm^-1)", " X2C(Har)", "cm^-1")) +for i in range(6): + print("State %i : %10.4f %10.1f %12.4f %10.1f %12.4f %10.1f\n" %(i, + e_ecp[i], (e_ecp[i]-e_ecp[0]) * 219470., + e_bp[i], (e_bp[i] -e_bp[0] ) * 219470., + e_x2c[i], (e_x2c[i]-e_x2c[0]) * 219470.)) diff --git a/pyscf/shciscf/fcidump_rel.py b/pyscf/shciscf/fcidump_rel.py new file mode 100644 index 0000000000..3d9dead947 --- /dev/null +++ b/pyscf/shciscf/fcidump_rel.py @@ -0,0 +1,128 @@ +import pyscf, h5py, numpy +from pyscf import scf, gto, ao2mo, tools +from functools import reduce + +TOL=1e-16 +DEFAULT_FLOAT_FORMAT='(%16.12e, %16.12e)' + +def write_hcore(fout, h, nmo, tol=TOL, float_format=DEFAULT_FLOAT_FORMAT): + h = h.reshape(nmo,nmo) + output_format = float_format + ' %4d %4d 0 0\n' + for i in range(nmo): + for j in range(nmo): + if abs(h[i,j]) > tol: + fout.write(output_format % (h[i,j].real, h[i,j].imag, i+1, j+1)) + +def write_eri(fout, eri, nmo, tol=TOL, float_format=DEFAULT_FLOAT_FORMAT): + npair = nmo*(nmo+1)//2 + output_format = float_format + ' %4d %4d %4d %4d\n' + for i in range(nmo): + for j in range(0, nmo): + ij = i*nmo+j + for k in range(0, nmo): + for l in range(0, nmo): + kl = k*nmo+l + if abs(eri[ij][kl]) > tol: + fout.write(output_format % (eri[ij][kl].real, eri[ij][kl].imag, i+1, j+1, k+1, l+1)) + +def from_integrals(filename, h1e, h2e, nmo, nelec, nuc=0, ms=0, orbsym=None, + tol=TOL, float_format=DEFAULT_FLOAT_FORMAT): + '''Convert the given 1-electron and 2-electron integrals to FCIDUMP format''' + with open(filename, 'w') as fout: + write_head(fout, nmo, nelec, ms, orbsym) + write_eri(fout, h2e, nmo, tol=tol, float_format=float_format) + write_hcore(fout, h1e, nmo, tol=tol, float_format=float_format) + output_format = float_format + ' 0 0 0 0\n' + fout.write(output_format % (nuc, 0.0)) + +def view(h5file, dataname='eri_mo'): + with h5py.File(h5file, 'r') as f5: + print('dataset %s, shape %s' % (str(f5.keys()), str(f5[dataname].shape))) + +def write_head(fout, nmo, nelec, ms, orbsym=None): + if not isinstance(nelec, (int, numpy.number)): + ms = abs(nelec[0] - nelec[1]) + nelec = nelec[0] + nelec[1] + fout.write(' &FCI NORB=%4d,NELEC=%2d,MS2=%d,\n' % (nmo, nelec, ms)) + if orbsym is not None and len(orbsym) > 0: + fout.write(' ORBSYM=%s\n' % ','.join([str(x) for x in orbsym])) + else: + fout.write(' ORBSYM=%s\n' % ('1,' * nmo)) + fout.write(' ISYM=0,\n') + fout.write(' &END\n') + +def from_x2c(mf, ncore, nact, filename = 'FCIDUMP', tol=1e-8, intor='int2e_spinor', h1e = None, approx = '1e'): + ncore = ncore*2 + nact = nact*2 + mo_coeff = mf.mo_coeff[:,ncore:ncore+nact] + mol = mf.mol + + assert mo_coeff.dtype == numpy.complex + + mf.with_x2c.approx = approx + hcore = mf.get_hcore() + core_occ = numpy.zeros(len(mf.mo_energy)) + core_occ[:ncore]=1.0 + core_dm = mf.make_rdm1(mo_occ = core_occ) + corevhf = mf.get_veff(mol, core_dm) + energy_core = mf.energy_nuc() + energy_core += numpy.einsum('ij,ji', core_dm, hcore) + energy_core += numpy.einsum('ij,ji', core_dm, corevhf) * .5 + h1eff = reduce(numpy.dot, (mo_coeff.T.conjugate(), hcore+corevhf, mo_coeff)) + #print(h1eff, energy_core) + #reduce(numpy.dot, (mf.mo_coeff.T, mf.get_hcore(), mf.mo_coeff))[ncore:ncore+nact, ncore:ncore+nact] + + if mf._eri is None: + eri = ao2mo.kernel(mf.mol, mo_coeff, intor=intor) + else: + eri = ao2mo.kernel(mf._eri, mo_coeff, intor=intor) + #core_occ = numpy.zeros(len(mf.mo_energy)) + #core_occ[:ncore] = 1.0 + #dm = mf.make_rdm1(mo_occ = core_occ) + #core_energy = scf.hf.energy_elec(mf, dm=dm) + print(mf.energy_nuc(), energy_core) + from_integrals(filename = filename, h1e=h1eff, h2e=eri, nmo=nact, nelec=sum(mf.mol.nelec)-ncore, nuc=energy_core.real, tol=tol) + +def from_dhf(mf, ncore, nact, filename = 'FCIDUMP', tol=1e-10, intor='int2e_spinor'): + ncore = ncore*2 + nact = nact*2 + n4c, nmo = mf.mo_coeff.shape + n2c = n4c // 2 + nNeg = nmo // 2 + ncore += nNeg + mo_coeff = mf.mo_coeff[n2c:,ncore:ncore+nact] + + assert mo_coeff.dtype == numpy.complex + + h1e = reduce(numpy.dot, (mf.mo_coeff.T, mf.get_hcore(), mf.mo_coeff))[ncore:ncore+nact, ncore:ncore+nact] + if mf._eri is None: + eri = ao2mo.kernel(mf.mol, mo_coeff, intor=intor) + else: + eri = ao2mo.kernel(mf._eri, mo_coeff, intor=intor) + core_occ = numpy.zeros(len(mf.mo_energy)//2) + core_occ[:ncore] = 1.0 + dm = mf.make_rdm1(mo_occ = core_occ) + core_energy = scf.hf.energy_elec(mf, dm=dm) + nuc = mf.energy_nuc() + core_energy + from_integrals(filename = filename, h1e=h1e, h2e=eri, nmo=nact, nelec=sum(mol.nelec)-ncore, nuc=nuc) + return + +if __name__ == '__main__': + mol = gto.M( + atom = + ''' + O 0. 0. 0. + H 0. -0.757 0.587 + H 0. 0.757 0.587 + ''', + basis = 'sto3g', + verbose = 3, + spin = 0) + mf_x2c = scf.X2C(mol) + mf_x2c.kernel() + from_x2c(mf_x2c, 0, 7, filename = 'FCIDUMP_x2c') + + mf_dirac = scf.DHF(mol) + mf_dirac.kernel() + from_dhf(mf_dirac, 0, 7, filename = 'FCIDUMP_dhf') + diff --git a/pyscf/shciscf/shci.py b/pyscf/shciscf/shci.py index 7bb062ae0d..91bb11d2b2 100644 --- a/pyscf/shciscf/shci.py +++ b/pyscf/shciscf/shci.py @@ -24,6 +24,7 @@ import ctypes import os import sys +import re import struct import time import tempfile @@ -32,10 +33,11 @@ from subprocess import CalledProcessError import numpy -import pyscf.tools +import pyscf.tools, pyscf.shciscf import pyscf.lib from pyscf.lib import logger from pyscf.lib import chkfile +from pyscf.shciscf import fcidump_rel from pyscf import mcscf ndpointer = numpy.ctypeslib.ndpointer @@ -46,6 +48,7 @@ from pyscf import __config__ settings = lambda: None settings.SHCIEXE = getattr(__config__, 'shci_SHCIEXE', None) + settings.ZSHCIEXE = getattr(__config__, 'shci_ZSHCIEXE', None) settings.SHCISCRATCHDIR = getattr(__config__, 'shci_SHCISCRATCHDIR', None) settings.SHCIRUNTIMEDIR = getattr(__config__, 'shci_SHCIRUNTIMEDIR', None) settings.MPIPREFIX = getattr(__config__, 'shci_MPIPREFIX', None) @@ -134,6 +137,7 @@ class SHCI(pyscf.lib.StreamObject): nroots: int nPTiter: int DoRDM: bool + DoSOC: bool sweep_iter: [int] sweep_epsilon: [float] initialStates: [[int]] @@ -161,6 +165,7 @@ def __init__(self, mol=None, maxM=None, tol=None, num_thrds=1, self.outputlevel = 2 self.executable = settings.SHCIEXE + self.executableZDice2 = settings.ZSHCIEXE self.scratchDirectory = settings.SHCISCRATCHDIR self.mpiprefix = settings.MPIPREFIX self.memory = memory @@ -176,6 +181,7 @@ def __init__(self, mol=None, maxM=None, tol=None, num_thrds=1, # TODO: Organize into pyscf and SHCI parameters # Standard SHCI Input parameters + self.dets = None self.davidsonTol = 5.e-5 self.epsilon2 = 1.e-7 self.epsilon2Large = 1000. @@ -194,7 +200,10 @@ def __init__(self, mol=None, maxM=None, tol=None, num_thrds=1, self.io = True self.nroots = 1 self.nPTiter = 0 + self.DoSpinRDM = False self.DoRDM = True + self.DoTRDM = False + self.DoSOC = False self.sweep_iter = [] self.sweep_epsilon = [] self.maxIter = 6 @@ -280,11 +289,51 @@ def dump_flags(self, verbose=None): # # --> See various remarks in the pertinent functions below. # ----------------------------------------------------------------------------------------------- - + + def trans_rdm1(self, state_i, state_j, norb, nelec, link_index=None, **kwargs): + if self.DoSOC: + trdm1 = numpy.zeros((norb, norb), dtype=complex) + else: + trdm1 = numpy.zeros((norb, norb)) + # assume Dice prints only i < j transition rdm. + if state_i > state_j: + tmp = state_i + state_i = state_j + state_j = tmp + + filetrdm1 = os.path.join(self.scratchDirectory, "transition1RDM.%d.%d.txt" % (state_i, state_j)) + with open(filetrdm1) as f: + line = f.readline() + file_orb = int(line.split()[0]) + for line in f: + orb1 = int(line.split()[0]) + orb2 = int(line.split()[1]) + if self.DoSOC: + val = re.split("[(,)]", line.split()[2]) + val = complex(float(val[1]), float(val[2])) + else: + val = float(line.split()[2]) + trdm1[orb1][orb2] = val + return trdm1 + def make_rdm1(self, state, norb, nelec, link_index=None, **kwargs): # Avoid calling self.make_rdm12 because it may be overloaded return self.make_rdm12(state, norb, nelec, link_index, **kwargs)[0] + def make_rdm12Frombin(self, state, norb, nelec): + if isinstance(nelec, (int, numpy.integer)): + nelectrons = nelec + else: + nelectrons = nelec[0] + nelec[1] + f = open("%s/Dice_%d_%d.rdm2"%(self.scratchDirectory, state, state), "rb") + import os + f.seek(4, os.SEEK_SET) + twopdm = numpy.fromfile(f, dtype=complex) + #twopdm = numpy.reshape(twopdm, (norb, norb, norb, norb)) + twopdm = numpy.reshape(twopdm, (norb, norb, norb, norb), order='F') + onepdm = numpy.einsum('ijkj->ik', twopdm)/(nelectrons-1) + return onepdm, twopdm + def make_rdm12(self, state, norb, nelec, link_index=None, **kwargs): nelectrons = 0 if isinstance(nelec, (int, numpy.integer)): @@ -321,16 +370,6 @@ def make_rdm12(self, state, norb, nelec, link_index=None, **kwargs): onepdm /= (nelectrons - 1) return onepdm, twopdm - def trans_rdm1(self, - statebra, - stateket, - norb, - nelec, - link_index=None, - **kwargs): - return self.trans_rdm12(statebra, stateket, norb, nelec, link_index, - **kwargs)[0] - def trans_rdm12(self, statebra, stateket, @@ -788,6 +827,11 @@ def kernel(self, h1e, eri, norb, nelec, fciRestart=None, ecore=0, if 'orbsym' in kwargs: self.orbsym = kwargs['orbsym'] + + if (h1e.dtype == numpy.complex and eri.dtype ==numpy.complex): + self.DoRDM = False + self.DoSpinRDM = True + self.extraline += ['readText'] writeIntegralFile(self, h1e, eri, norb, nelec, ecore) writeSHCIConfFile(self, nelec, fciRestart) if self.verbose >= logger.DEBUG1: @@ -795,7 +839,7 @@ def kernel(self, h1e, eri, norb, nelec, fciRestart=None, ecore=0, logger.debug1(self, 'SHCI Input conf') logger.debug1(self, open(inFile, 'r').read()) if self.onlywriteIntegral: - logger.info(self, 'Only write integral') + Logger.info(self, 'Only write integral') try: calc_e = readEnergy(self) except IOError: @@ -806,7 +850,6 @@ def kernel(self, h1e, eri, norb, nelec, fciRestart=None, ecore=0, return calc_e, roots if self.returnInt: return h1e, eri - executeSHCI(self) if self.verbose >= logger.DEBUG1: outFile = os.path.join(self.runtimeDir, self.outputFile) @@ -879,39 +922,36 @@ def cleanup_dice_files(self): os.remove(os.path.join(self.runtimeDir, self.integralFile)) -def print1Int(h1, name): - with open('%s.X' % (name), 'w') as fout: - fout.write('%d\n' % h1[0].shape[0]) - for i in range(h1[0].shape[0]): - for j in range(h1[0].shape[0]): - if (abs(h1[0, i, j]) > 1.e-8): - fout.write( - '%16.10g %4d %4d\n' % (h1[0, i, j], i + 1, j + 1)) - - with open('%s.Y' % (name), 'w') as fout: - fout.write('%d\n' % h1[1].shape[0]) - for i in range(h1[1].shape[0]): - for j in range(h1[1].shape[0]): - if (abs(h1[1, i, j]) > 1.e-8): - fout.write( - '%16.10g %4d %4d\n' % (h1[1, i, j], i + 1, j + 1)) - - with open('%s.Z' % (name), 'w') as fout: - fout.write('%d\n' % h1[2].shape[0]) - for i in range(h1[2].shape[0]): - for j in range(h1[2].shape[0]): - if (abs(h1[2, i, j]) > 1.e-8): - fout.write( - '%16.10g %4d %4d\n' % (h1[2, i, j], i + 1, j + 1)) - - with open('%sZ' % (name), 'w') as fout: - fout.write('%d\n' % h1[2].shape[0]) - for i in range(h1[2].shape[0]): - for j in range(h1[2].shape[0]): - if (abs(h1[2, i, j]) > 1.e-8): - fout.write( - '%16.10g %4d %4d\n' % (h1[2, i, j], i + 1, j + 1)) - +def transition_dipole(mc, state_i, state_j): + t_dm1 = mc.fcisolver.trans_rdm1(state_i, state_j, mc.ncas, mc.nelecas) + ncore = mc.ncore + ncasorb = mc.ncas + mol = mc.mol + mo_cas = mc.mo_coeff[:, ncore:ncore+ncasorb] + t_dm1 = pyscf.lib.einsum('pi, ij, qj->pq', mo_cas, t_dm1, mo_cas) + #print(t_dm1) + charge_center = (numpy.einsum('z,zx->x', mol.atom_charges(), mol.atom_coords()) / mol.atom_charges().sum()) + with mol.with_common_origin(charge_center): + t_dip = numpy.einsum('xij,ji->x', mol.intor('int1e_r'), t_dm1) + return t_dip + +def oscillator_strength(mc, state_i, state_j): + t_dip = transition_dipole(mc, state_i, state_j) + calc_e = readEnergy(mc.fcisolver) + delta_e = abs(calc_e[state_i] - calc_e[state_j]) + return 2./3.*delta_e*sum(abs(t_dip)**2), 2./3.*delta_e*abs(t_dip)**2 + +def phospherescence_lifetime(mc, s0=0, triplet_list=[1,2,3]): + au2wavenumber = 219470. + tau = numpy.zeros(len(triplet_list)) + calc_e = readEnergy(mc.fcisolver) + for state in triplet_list: + delta_e = calc_e[state] - calc_e[s0] + fosc = oscillator_strength(mc, s0, state) + tau[triplet_list.index(state)] = 1.5/(fosc*(delta_e*au2wavenumber)**2) + tau_av = 3./(sum(1/tau)) + return tau_av, tau + def make_sched(SHCI): @@ -941,12 +981,21 @@ def writeSHCIConfFile(SHCI, nelec, Restart): f.write('#system\n') f.write('nocc %i\n' % (nelec[0] + nelec[1])) if SHCI.__class__.__name__ == 'FakeCISolver': - for i in range(nelec[0]): - f.write('%i ' % (2 * i)) - for i in range(nelec[1]): - f.write('%i ' % (2 * i + 1)) + if SHCI.initialStates is not None: + print("write determinants") + for i in range(len(SHCI.initialStates)): + for j in SHCI.initialStates[i]: + f.write('%i ' % (j)) + if (i != len(SHCI.initialStates) - 1): + f.write('\n') + else: + for i in range(nelec[0]): + f.write('%i ' % (2 * i)) + for i in range(nelec[1]): + f.write('%i ' % (2 * i + 1)) else: if SHCI.initialStates is not None: + print("write determinants") for i in range(len(SHCI.initialStates)): for j in SHCI.initialStates[i]: f.write('%i ' % (j)) @@ -1034,16 +1083,26 @@ def writeSHCIConfFile(SHCI, nelec, Restart): # Miscellaneous Keywords f.write('\n#misc\n') - f.write('io \n') + if Restart is True: + f.write('noio \n') + if (SHCI.scratchDirectory != ""): if not os.path.exists(SHCI.scratchDirectory): os.makedirs(SHCI.scratchDirectory) f.write('prefix %s\n' % (SHCI.scratchDirectory)) + if (SHCI.DoSOC): + f.write('DoSOC\n') + SHCI.DoRDM = False if (SHCI.DoRDM): f.write('DoRDM\n') + if (SHCI.DoSpinRDM): + f.write('DoSpinRDM\n') + if (SHCI.nroots > 1 and (SHCI.DoRDM or SHCI.DoTRDM)): + f.write('DoTRDM\n') for line in SHCI.extraline: f.write('%s\n' % line) + f.write('\n') # SHCI requires that there is an extra line. f.close() @@ -1158,6 +1217,12 @@ def DinfhtoD2h(SHCI, norb, nelec): def writeIntegralFile(SHCI, h1eff, eri_cas, norb, nelec, ecore=0): + if (h1eff.dtype == numpy.complex and eri_cas.dtype == numpy.complex): + integralFile = os.path.join(SHCI.runtimeDir, SHCI.integralFile) + fcidump_rel.from_integrals(filename=integralFile, h1e=h1eff, h2e=eri_cas, + nmo=norb, nelec=nelec, ms=nelec, nuc=ecore.real, tol=1e-12) + return + if isinstance(nelec, (int, numpy.integer)): neleca = nelec // 2 + nelec % 2 nelecb = nelec - neleca @@ -1220,10 +1285,15 @@ def executeSHCI(SHCI): inFile = os.path.join(SHCI.runtimeDir, SHCI.configFile) outFile = os.path.join(SHCI.runtimeDir, SHCI.outputFile) try: - cmd = ' '.join((SHCI.mpiprefix, SHCI.executable, inFile)) - cmd = "%s > %s 2>&1" % (cmd, outFile) - check_call(cmd, shell=True) - #save_output(SHCI) + if SHCI.DoSpinRDM: + cmd = ' '.join((SHCI.mpiprefix, SHCI.executableZDice2, inFile)) + cmd = "%s > %s 2>&1" % (cmd, outFile) + check_call(cmd, shell=True) + else: + cmd = ' '.join((SHCI.mpiprefix, SHCI.executable, inFile)) + cmd = "%s > %s 2>&1" % (cmd, outFile) + check_call(cmd, shell=True) + #save_output(SHCI) except CalledProcessError as err: logger.error(SHCI, cmd) raise err @@ -1281,204 +1351,6 @@ def SHCISCF(mf, norb, nelec, maxM=1000, tol=1.e-8, *args, **kwargs): return mc -def get_hso1e(wso, x, rp): - nb = x.shape[0] - hso1e = numpy.zeros((3, nb, nb)) - for ic in range(3): - hso1e[ic] = reduce(numpy.dot, (rp.T, x.T, wso[ic], x, rp)) - return hso1e - - -def get_wso(mol): - nb = mol.nao_nr() - wso = numpy.zeros((3, nb, nb)) - for iatom in range(mol.natm): - zA = mol.atom_charge(iatom) - xyz = mol.atom_coord(iatom) - mol.set_rinv_orig(xyz) - wso += zA * mol.intor('cint1e_prinvxp_sph', - 3) # sign due to integration by part - return wso - - -def get_p(dm, x, rp): - pLL = rp.dot(dm.dot(rp.T)) - pLS = pLL.dot(x.T) - pSS = x.dot(pLL.dot(x.T)) - return pLL, pLS, pSS - - -def get_fso2e_withkint(kint, x, rp, pLL, pLS, pSS): - nb = x.shape[0] - fso2e = numpy.zeros((3, nb, nb)) - for ic in range(3): - gsoLL = -2.0 * numpy.einsum('lmkn,lk->mn', kint[ic], pSS) - gsoLS = -numpy.einsum('mlkn,lk->mn',kint[ic],pLS) \ - -numpy.einsum('lmkn,lk->mn',kint[ic],pLS) - gsoSS = -2.0*numpy.einsum('mnkl,lk',kint[ic],pLL) \ - -2.0*numpy.einsum('mnlk,lk',kint[ic],pLL) \ - +2.0*numpy.einsum('mlnk,lk',kint[ic],pLL) - fso2e[ic] = gsoLL + gsoLS.dot(x) + x.T.dot(-gsoLS.T) \ - + x.T.dot(gsoSS.dot(x)) - fso2e[ic] = reduce(numpy.dot, (rp.T, fso2e[ic], rp)) - return fso2e - - -def get_kint2(mol): - nb = mol.nao_nr() - kint = mol.intor('int2e_spv1spv2_spinor', comp=3) - return kint.reshape(3, nb, nb, nb, nb) - - -def get_fso2e(mol, x, rp, pLL, pLS, pSS): - nb = mol.nao_nr() - np = nb * nb - nq = np * np - ddint = mol.intor('int2e_ip1ip2_sph', 9).reshape(3, 3, nq) - fso2e = numpy.zeros((3, nb, nb)) - - ddint[0, 0] = ddint[1, 2] - ddint[2, 1] - kint = ddint[0, 0].reshape(nb, nb, nb, nb) - gsoLL = -2.0 * numpy.einsum('lmkn,lk->mn', kint, pSS) - gsoLS = -numpy.einsum('mlkn,lk->mn',kint,pLS) \ - -numpy.einsum('lmkn,lk->mn',kint,pLS) - gsoSS = -2.0*numpy.einsum('mnkl,lk',kint,pLL) \ - -2.0*numpy.einsum('mnlk,lk',kint,pLL) \ - +2.0*numpy.einsum('mlnk,lk',kint,pLL) - fso2e[0] = gsoLL + gsoLS.dot(x) + x.T.dot(-gsoLS.T) \ - + x.T.dot(gsoSS.dot(x)) - fso2e[0] = reduce(numpy.dot, (rp.T, fso2e[0], rp)) - - ddint[0, 0] = ddint[2, 0] - ddint[2, 1] - kint = ddint[0, 0].reshape(nb, nb, nb, nb) - gsoLL = -2.0 * numpy.einsum('lmkn,lk->mn', kint, pSS) - gsoLS = -numpy.einsum('mlkn,lk->mn',kint,pLS) \ - -numpy.einsum('lmkn,lk->mn',kint,pLS) - gsoSS = -2.0*numpy.einsum('mnkl,lk',kint,pLL) \ - -2.0*numpy.einsum('mnlk,lk',kint,pLL) \ - +2.0*numpy.einsum('mlnk,lk',kint,pLL) - fso2e[1] = gsoLL + gsoLS.dot(x) + x.T.dot(-gsoLS.T) \ - + x.T.dot(gsoSS.dot(x)) - fso2e[1] = reduce(numpy.dot, (rp.T, fso2e[1], rp)) - - ddint[0, 0] = ddint[0, 1] - ddint[1, 0] - kint = ddint[0, 0].reshape(nb, nb, nb, nb) - gsoLL = -2.0 * numpy.einsum('lmkn,lk->mn', kint, pSS) - gsoLS = -numpy.einsum('mlkn,lk->mn',kint,pLS) \ - -numpy.einsum('lmkn,lk->mn',kint,pLS) - gsoSS = -2.0*numpy.einsum('mnkl,lk',kint,pLL) \ - -2.0*numpy.einsum('mnlk,lk',kint,pLL) \ - +2.0*numpy.einsum('mlnk,lk',kint,pLL) - fso2e[2] = gsoLL + gsoLS.dot(x) + x.T.dot(-gsoLS.T) \ - + x.T.dot(gsoSS.dot(x)) - fso2e[2] = reduce(numpy.dot, (rp.T, fso2e[2], rp)) - return fso2e - - -def get_kint(mol): - nb = mol.nao_nr() - np = nb * nb - nq = np * np - ddint = mol.intor('int2e_ip1ip2_sph', 9).reshape(3, 3, nq) - - kint = numpy.zeros((3, nq)) - kint[0] = ddint[1, 2] - ddint[2, 1] # x = yz - zy - kint[1] = ddint[2, 0] - ddint[0, 2] # y = zx - xz - kint[2] = ddint[0, 1] - ddint[1, 0] # z = xy - yx - return kint.reshape(3, nb, nb, nb, nb) - - -def writeSOCIntegrals(mc, - ncasorbs=None, - rdm1=None, - pictureChange1e="bp", - pictureChange2e="bp", - uncontract=True): - from pyscf.x2c import x2c, sfx2c1e - from pyscf.lib.parameters import LIGHT_SPEED - LIGHT_SPEED = 137.0359895000 - alpha = 1.0 / LIGHT_SPEED - - if (uncontract): - xmol, contr_coeff = x2c.X2C().get_xmol(mc.mol) - else: - xmol, contr_coeff = mc.mol, numpy.eye(mc.mo_coeff.shape[0]) - - rdm1ao = rdm1 - if (rdm1 is None): - rdm1ao = 1. * mc.make_rdm1() - if len(rdm1ao.shape) > 2: rdm1ao = (rdm1ao[0] + rdm1ao[1]) - - if (uncontract): - dm = reduce(numpy.dot, (contr_coeff, rdm1ao, contr_coeff.T)) - else: - dm = 1. * rdm1ao - np, nc = contr_coeff.shape[0], contr_coeff.shape[1] - - hso1e = numpy.zeros((3, np, np)) - h1e_1c, x, rp = sfx2c1e.SpinFreeX2C(mc.mol).get_hxr( - mc.mol, uncontract=uncontract) - - #two electron terms - if (pictureChange2e == "bp"): - h2ao = -(alpha)**2 * 0.5 * xmol.intor( - 'cint2e_p1vxp1_sph', comp=3, aosym='s1') - h2ao = h2ao.reshape(3, np, np, np, np) - hso1e += 1. * (numpy.einsum('ijklm,lm->ijk', h2ao, dm) - 1.5 * - (numpy.einsum('ijklm, kl->ijm', h2ao, dm) + - numpy.einsum('ijklm,mj->ilk', h2ao, dm))) - elif (pictureChange2e == "x2c"): - dm1 = dm / 2. - pLL, pLS, pSS = get_p(dm1, x, rp) - #kint = get_kint(xmol) - #hso1e += -(alpha)**2*0.5*get_fso2e_withkint(kint,x,rp,pLL,pLS,pSS) - hso1e += -(alpha)**2 * 0.5 * get_fso2e(xmol, x, rp, pLL, pLS, pSS) - elif (pictureChange2e == "none"): - hso1e *= 0.0 - else: - print(pictureChane2e, "not a valid option") - exit(0) - - #MF 1 electron term - if (pictureChange1e == "bp"): - hso1e += (alpha)**2 * 0.5 * get_wso(xmol) - elif (pictureChange1e == "x2c1"): - dm /= 2. - pLL, pLS, pSS = get_p(dm, x, rp) - wso = (alpha)**2 * 0.5 * get_wso(xmol) - hso1e += get_hso1e(wso, x, rp) - elif (pictureChange1e == "x2cn"): - h1e_2c = x2c.get_hcore(xmol) - - for i in range(np): - for j in range(np): - if (abs(h1e_2c[2 * i, 2 * j + 1].imag) > 1.e-8): - hso1e[0][i, j] -= h1e_2c[2 * i, 2 * j + 1].imag * 2. - if (abs(h1e_2c[2 * i, 2 * j + 1].real) > 1.e-8): - hso1e[1][i, j] -= h1e_2c[2 * i, 2 * j + 1].real * 2. - if (abs(h1e_2c[2 * i, 2 * j].imag) > 1.e-8): - hso1e[2][i, j] -= h1e_2c[2 * i, 2 * j].imag * 2. - else: - print(pictureChane1e, "not a valid option") - exit(0) - - h1ao = numpy.zeros((3, nc, nc)) - if (uncontract): - for ic in range(3): - h1ao[ic] = reduce(numpy.dot, - (contr_coeff.T, hso1e[ic], contr_coeff)) - else: - h1ao = 1. * hso1e - - ncore, ncas = mc.ncore, mc.ncas - if (ncasorbs is not None): - ncas = ncasorbs - mo_coeff = mc.mo_coeff - h1 = numpy.einsum('xpq,pi,qj->xij', h1ao, mo_coeff, - mo_coeff)[:, ncore:ncore + ncas, ncore:ncore + ncas] - print1Int(h1, 'SOC') - - def dryrun(mc, mo_coeff=None): ''' Generate FCIDUMP and SHCI input.dat file for a give multiconfigurational @@ -1541,41 +1413,6 @@ def runQDPT(mc, gtensor): logger.error(mc.fcisolver, cmd) raise err - else: - writeSHCIConfFile(mc.fcisolver, mc.nelecas, False) - confFile = os.path.join(mc.fcisolver.runtimeDir, - mc.fcisolver.configFile) - outFile = os.path.join(mc.fcisolver.runtimeDir, - mc.fcisolver.outputFile) - if (gtensor): - f = open(confFile, 'a') - f.write("dogtensor\n") - f.close() - try: - cmd = ' '.join((mc.fcisolver.mpiprefix, - mc.fcisolver.QDPTexecutable, confFile)) - cmd = "%s > %s 2>&1" % (cmd, outFile) - check_call(cmd, shell=True) - check_call('cat %s|grep -v "#"' % (outFile), shell=True) - except CalledProcessError as err: - logger.error(mc.fcisolver, cmd) - raise err - - -def doSOC(mc, gtensor=False, pictureChange="bp"): - writeSOCIntegrals(mc, pictureChange=pictureChange) - dryrun(mc) - if (gtensor or True): - ncore, ncas = mc.ncore, mc.ncas - charge_center = numpy.einsum('z,zx->x', mc.mol.atom_charges(), - mc.mol.atom_coords()) - h1ao = mc.mol.intor('cint1e_cg_irxp_sph', comp=3) - h1 = numpy.einsum( - 'xpq,pi,qj->xij', h1ao, mc.mo_coeff, - mc.mo_coeff)[:, ncore:ncore + ncas, ncore:ncore + ncas] - print1Int(h1, 'GTensor') - - runQDPT(mc, gtensor) if __name__ == '__main__': diff --git a/pyscf/shciscf/socutils.py b/pyscf/shciscf/socutils.py new file mode 100644 index 0000000000..20e1ad4840 --- /dev/null +++ b/pyscf/shciscf/socutils.py @@ -0,0 +1,585 @@ +import time +from functools import reduce + +import numpy +import scipy.linalg + +from pyscf import gto, lib +from pyscf.lib.parameters import LIGHT_SPEED +from pyscf.lib import logger +from pyscf.shciscf import shci +from pyscf.x2c import x2c +from pyscf.lib import logger + +alpha = 1./LIGHT_SPEED +c = LIGHT_SPEED +def get_socamf(atoms, basis): + hso1e_atoms = {} + factor = -alpha**2 * 0.25 + for atom in atoms: + from pyscf import gto + atm_id=gto.elements.charge(atom) + spin=atm_id%2 + mol_atom = gto.M(atom=[[atom,[0,0,0]]], basis=basis, spin=spin) + conf = gto.elements.CONFIGURATION[atm_id] + # generate configuration for spherical symmetry atom + if conf[0] % 2==0: + if conf[1]%6 == 0: + if conf[2]%10 == 0: + if conf[3]%14 == 0: + nopen = 0 + nact = 0 + else: + nopen = 7 + nact = conf[3]%14 + else: + nopen = 5 + nact = conf[2]%10 + else: + nopen = 3 + nact = conf[1]%6 + else: + nopen = 1 + nact = 1 + from pyscf import scf + mf_atom = scf.DHF(mol_atom) + mf_atom.nopen = nopen*2 + mf_atom.nact = nact + mf_atom.kernel() + dm = mf_atom.make_rdm1() + from pyscf.scf import dhf + x2c_obj = x2c.X2C(mol_atom) + x2c_obj.xuncontract = False + x = x2c_obj.get_xmat() + s = mol_atom.intor_symmetric('int1e_ovlp_spinor') + t = mol_atom.intor_symmetric('int1e_spsp_spinor')*.5 + s1 = s + reduce(numpy.dot, (x.T.conj(), t, x)) * (.5/c**2) + r = x2c._get_r(s, s1) + vj, vk = dhf.get_jk_coulomb(mol_atom, dm, 1, mf_atom._coulomb_now) + vj_sf, vk_sf = dhf.get_jk_sf_coulomb(mol_atom, dm, 1, mf_atom._coulomb_now) + + veff_sd = (vj-vk)-(vj_sf-vk_sf) + n2c=mol_atom.nao_2c() + g_ll = veff_sd[:n2c, :n2c] + g_ls = veff_sd[:n2c, n2c:] + g_sl = veff_sd[n2c:, :n2c] + g_ss = veff_sd[n2c:, n2c:] + g_so_mf = reduce(numpy.dot,(r.T.conj(), (g_ll+reduce(numpy.dot, (g_ls, x))+reduce(numpy.dot, (x.T.conj(), g_sl))+reduce(numpy.dot, (x.T.conj(), g_ss, x))),r)) + hso1e_atoms[atom]=g_so_mf + return hso1e_atoms + +def print1Int(h1, name): + xyz = ["X", "Y", "Z"] + for k in range(3): + with open('%s.' % (name) + xyz[k], 'w') as fout: + fout.write('%d\n' % h1[k].shape[0]) + for i in range(h1[k].shape[0]): + for j in range(h1[k].shape[0]): + if (abs(h1[k, i, j]) > 1.e-8): + fout.write( + '%16.10g %4d %4d\n' % (h1[k, i, j], i + 1, j + 1)) + + +# by default h is returned in the contracted basis +# x and r in the uncontracted basis +def get_hxr(mc, uncontract=True): + if (uncontract): + xmol, contr_coeff = x2c.X2C(mc.mol).get_xmol() + else: + xmol, contr_coeff = mc.mol, numpy.eye(mc.mo_coeff.shape[0]) + + c = lib.param.LIGHT_SPEED + t = xmol.intor_symmetric('int1e_kin') + v = xmol.intor_symmetric('int1e_nuc') + s = xmol.intor_symmetric('int1e_ovlp') + w = xmol.intor_symmetric('int1e_pnucp') + + h1, x, r = _x2c1e_hxrmat(t, v, w, s, c) + if (uncontract): + h1 = reduce(numpy.dot, (contr_coeff.T, h1, contr_coeff)) + + return h1, x, r + + +def _x2c1e_hxrmat(t, v, w, s, c): + nao = s.shape[0] + n2 = nao * 2 + h = numpy.zeros((n2, n2), dtype=v.dtype) + m = numpy.zeros((n2, n2), dtype=v.dtype) + h[:nao, :nao] = v + h[:nao, nao:] = t + h[nao:, :nao] = t + h[nao:, nao:] = w * (.25 / c**2) - t + m[:nao, :nao] = s + m[nao:, nao:] = t * (.5 / c**2) + + e, a = scipy.linalg.eigh(h, m) + cl = a[:nao, nao:] + cs = a[nao:, nao:] + + b = numpy.dot(cl, cl.T.conj()) + x = reduce(numpy.dot, (cs, cl.T.conj(), numpy.linalg.inv(b))) + + s1 = s + reduce(numpy.dot, (x.T.conj(), t, x)) * (.5 / c**2) + tx = reduce(numpy.dot, (t, x)) + h1 = (h[:nao, :nao] + h[:nao, nao:].dot(x) + x.T.conj().dot(h[nao:, :nao]) + + reduce(numpy.dot, (x.T.conj(), h[nao:, nao:], x))) + + sa = _invsqrt(s) + sb = _invsqrt(reduce(numpy.dot, (sa, s1, sa))) + r = reduce(numpy.dot, (sa, sb, sa, s)) + h1out = reduce(numpy.dot, (r.T.conj(), h1, r)) + return h1out, x, r + + +def _invsqrt(a, tol=1e-14): + e, v = numpy.linalg.eigh(a) + idx = e > tol + return numpy.dot(v[:, idx] / numpy.sqrt(e[idx]), v[:, idx].T.conj()) + + +def get_hso1e(wso, x, rp): + nb = x.shape[0] + hso1e = numpy.zeros((3, nb, nb)) + for ic in range(3): + hso1e[ic] = reduce(numpy.dot, (rp.T, x.T, wso[ic], x, rp)) + return hso1e + + +def get_wso(mol): + nb = mol.nao_nr() + wso = numpy.zeros((3, nb, nb)) + for iatom in range(mol.natm): + zA = mol.atom_charge(iatom) + xyz = mol.atom_coord(iatom) + mol.set_rinv_orig(xyz) + # sign due to integration by part + wso += zA * mol.intor('cint1e_prinvxp_sph', 3) + return wso + + +def get_wso_1c(mol, atomlist): + nb = mol.nao_nr() + wso = numpy.zeros((3, nb, nb)) + aoslice = mol.aoslice_by_atom() + for iatom in atomlist: + zA = mol.atom_charge(iatom) + xyz = mol.atom_coord(iatom) + mol.set_rinv_orig(xyz) + ao_start = aoslice[iatom, 2] + ao_end = aoslice[iatom, 3] + shl_start = aoslice[iatom, 0] + shl_end = aoslice[iatom, 1] + wso[:, ao_start: ao_end, ao_start: ao_end]\ + = zA*mol.intor('cint1e_prinvxp_sph', 3, shls_slice=[shl_start, shl_end, shl_start, shl_end]).reshape(3, ao_end-ao_start, ao_end-ao_start) + return wso + + +def get_p(dm, x, rp): + pLL = rp.dot(dm.dot(rp.T)) + pLS = pLL.dot(x.T) + pSS = x.dot(pLL.dot(x.T)) + return pLL, pLS, pSS + + +def get_fso2e_x2c_original(mol, x, rp, pLL, pLS, pSS): + ''' Function for x2c Hamiltonian without any memory saving ''' + # although this function is not used but just keep here for reference + nb = mol.nao_nr() + np = nb * nb + nq = np * np + + ddint = mol.intor('int2e_ip1ip2_sph', 9).reshape(3, 3, nq) + fso2e = numpy.zeros((3, nb, nb)) + + xyz = [0, 1, 2] + for i_x in xyz: + i_y = xyz[i_x - 2] + i_z = xyz[i_x - 1] + ddint[0, 0] = ddint[i_y, i_z] - ddint[i_z, i_y] # x = yz - zy etc + kint = ddint[0, 0].reshape(nb, nb, nb, nb) + gsoLL = -2.0 * lib.einsum('lmkn,lk->mn', kint, pSS) + gsoLS = -1.0 * lib.einsum('mlkn,lk->mn', kint, pLS) \ + - 1.0 * lib.einsum('lmkn,lk->mn', kint, pLS) + gsoSS = -2.0 * lib.einsum('mnkl,lk', kint, pLL) \ + - 2.0 * lib.einsum('mnlk,lk', kint, pLL) \ + + 2.0 * lib.einsum('mlnk,lk', kint, pLL) + fso2e[i_x] = gsoLL + gsoLS.dot(x) + x.T.dot(-gsoLS.T) \ + + x.T.dot(gsoSS.dot(x)) + fso2e[i_x] = reduce(numpy.dot, (rp.T, fso2e[i_x], rp)) + return fso2e + + +def get_fso2e_x2c(mol, x, rp, pLL, pLS, pSS): + ''' Two-electron x2c operator with memory saving strategy ''' + nb = mol.nao_nr() + + fso2e = numpy.zeros((3, nb, nb)) + gsoLL = numpy.zeros((3, nb, nb)) + gsoLS = numpy.zeros((3, nb, nb)) + gsoSS = numpy.zeros((3, nb, nb)) + from pyscf.gto import moleintor + nbas = mol.nbas + max_double = mol.max_memory / 8.0 * 1.0e6 + max_basis = pow(max_double / 9., 1. / 4.) + ao_loc_orig = moleintor.make_loc(mol._bas, 'int2e_ip1_ip2_sph') + shl_size = [] + shl_slice = [0] + ao_loc = [0] + if nb > max_basis: + for i in range(0, nbas - 1): + if (ao_loc_orig[i + 1] - ao_loc[-1] > max_basis and ao_loc_orig[i] - ao_loc[-1]): + ao_loc.append(ao_loc_orig[i]) + shl_size.append(ao_loc[-1] - ao_loc[-2]) + shl_slice.append(i) + if ao_loc[-1] is not ao_loc_orig[-1]: + ao_loc.append(ao_loc_orig[-1]) + shl_size.append(ao_loc[-1] - ao_loc[-2]) + shl_slice.append(nbas) + nbas = len(shl_size) + logger.info(mol, "Cutting basis functions into %d batches, need to calculate %d integrals batches.", nbas, nbas**4) + + start = time.clock() + for i in range(0, nbas): + for j in range(0, nbas): + for k in range(0, nbas): + for l in range(0, nbas): + start_this = time.clock() + ddint = mol.intor('int2e_ip1ip2_sph', comp=9, shls_slice=[shl_slice[i], shl_slice[i+1], shl_slice[j], shl_slice[j+1], shl_slice[k], shl_slice[k+1], shl_slice[l], shl_slice[l+1]]).reshape(3, 3, -1) + kint = numpy.zeros(3 * shl_size[i] * shl_size[j] * shl_size[k] * shl_size[l]).reshape( + 3, shl_size[i], shl_size[j], shl_size[k], shl_size[l]) + kint[0] = (ddint[1, 2] - ddint[2, 1]).reshape(shl_size[i], shl_size[j], shl_size[k], shl_size[l]) + kint[1] = (ddint[2, 0] - ddint[0, 2]).reshape(shl_size[i], shl_size[j], shl_size[k], shl_size[l]) + kint[2] = (ddint[0, 1] - ddint[1, 0]).reshape(shl_size[i], shl_size[j], shl_size[k], shl_size[l]) + + gsoLL[:, ao_loc[j]:ao_loc[j+1], ao_loc[l]:ao_loc[l+1]] \ + +=-2.0*lib.einsum('ilmkn,lk->imn', kint, \ + pSS[ao_loc[i]:ao_loc[i+1], ao_loc[k]:ao_loc[k+1]]) + gsoLS[:, ao_loc[i]:ao_loc[i+1], ao_loc[l]:ao_loc[l+1]] \ + +=-1.0*lib.einsum('imlkn,lk->imn', kint, \ + pLS[ao_loc[j]:ao_loc[j+1], ao_loc[k]:ao_loc[k+1]]) + gsoLS[:, ao_loc[j]:ao_loc[j+1], ao_loc[l]:ao_loc[l+1]] \ + +=-1.0*lib.einsum('ilmkn,lk->imn', kint, \ + pLS[ao_loc[i]:ao_loc[i+1], ao_loc[k]:ao_loc[k+1]]) + gsoSS[:, ao_loc[i]:ao_loc[i+1], ao_loc[j]:ao_loc[j+1]] \ + +=-2.0*lib.einsum('imnkl,lk->imn', kint, \ + pLL[ao_loc[l]:ao_loc[l+1], ao_loc[k]:ao_loc[k+1]])\ + -2.0*lib.einsum('imnlk,lk->imn', kint, \ + pLL[ao_loc[k]:ao_loc[k+1], ao_loc[l]:ao_loc[l+1]]) + gsoSS[:, ao_loc[i]:ao_loc[i+1], ao_loc[k]:ao_loc[k+1]] \ + += 2.0*lib.einsum('imlnk,lk->imn', kint, \ + pLL[ao_loc[j]:ao_loc[j+1], ao_loc[l]:ao_loc[l+1]]) + + logger.info(mol, "Time elapsed for %dth batch in %d batches is %g, cumulates time is %g.", \ + i*nbas**3+j*nbas**2+k*nbas+l+1, nbas*4, time.clock()-start_this, time.clock()-start) + for comp in range(0, 3): + fso2e[comp] = gsoLL[comp] + gsoLS[comp].dot(x) + x.T.dot(-gsoLS[comp].T) + x.T.dot(gsoSS[comp].dot(x)) + fso2e[comp] = reduce(numpy.dot, (rp.T, fso2e[comp], rp)) + + logger.info(mol, 'Two electron part of SOC integral is done') + return fso2e + + +def get_fso2e_x2c1c(mol, x, rp, pLL, pLS, pSS): + ''' Function for x2c Hamiltonian with one-center approximation ''' + nb = mol.nao_nr() + #np = nb * nb + fso2e = numpy.zeros((3, nb, nb)) + gsoLL = numpy.zeros((3, nb, nb)) + gsoLS = numpy.zeros((3, nb, nb)) + gsoSS = numpy.zeros((3, nb, nb)) + + from pyscf.gto import moleintor + #nbas = mol.nbas + #max_double = mol.max_memory / 8.0 * 1.0e6 + #max_basis = pow(max_double / 9., 1. / 4.) + #ao_loc_orig = moleintor.make_loc(mol._bas, 'int2e_ip1_ip2_sph') + shl_size = [] + shl_slice = [0] + ao_loc = [0] + + ao_slice_by_atom = mol.aoslice_by_atom() + for slice in ao_slice_by_atom: + shl_slice.append(slice[1]) + shl_size.append(slice[3] - slice[2]) + ao_loc.append(slice[3]) + natom = len(ao_slice_by_atom) + + for iatom in range(0, natom): + ibegin = shl_slice[iatom] + iend = shl_slice[iatom + 1] + #start = time.clock() + ddint = mol.intor('int2e_ip1ip2_sph', + comp=9, + shls_slice=[ibegin, iend, ibegin, iend, ibegin, iend, ibegin, iend]).reshape(3, 3, -1) + kint = numpy.zeros(3 * shl_size[iatom] * shl_size[iatom] * shl_size[iatom] * shl_size[iatom]).reshape( + 3, shl_size[iatom], shl_size[iatom], shl_size[iatom], shl_size[iatom]) + kint[0] = (ddint[1, 2] - ddint[2, 1]).reshape(shl_size[iatom], shl_size[iatom], shl_size[iatom], + shl_size[iatom]) + kint[1] = (ddint[2, 0] - ddint[0, 2]).reshape(shl_size[iatom], shl_size[iatom], shl_size[iatom], + shl_size[iatom]) + kint[2] = (ddint[0, 1] - ddint[1, 0]).reshape(shl_size[iatom], shl_size[iatom], shl_size[iatom], + shl_size[iatom]) + #start = time.clock() + gsoLL[:, ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]] \ + += -2.0*lib.einsum('ilmkn,lk->imn', kint, + pSS[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]]) + gsoLS[:, ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]] \ + += -1.0*lib.einsum('imlkn,lk->imn', kint, + pLS[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]]) + gsoLS[:, ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]] \ + += -1.0*lib.einsum('ilmkn,lk->imn', kint, + pLS[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]]) + gsoSS[:, ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]] \ + += -2.0*lib.einsum('imnkl,lk->imn', kint, + pLL[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]])\ + - 2.0*lib.einsum('imnlk,lk->imn', kint, + pLL[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]]) + gsoSS[:, ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]] \ + += 2.0*lib.einsum('imlnk,lk->imn', kint, + pLL[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]]) + #print(" Time elapsed for einsum:", time.clock() - start) + for comp in range(0, 3): + fso2e[comp] = gsoLL[comp] + \ + gsoLS[comp].dot(x) + x.T.dot(-gsoLS[comp].T) + \ + x.T.dot(gsoSS[comp].dot(x)) + fso2e[comp] = reduce(numpy.dot, (rp.T, fso2e[comp], rp)) + + return fso2e + + +def get_fso2e_bp(mol, dm): + ''' Two-electron bp operator ''' + nb = mol.nao_nr() + + hso1e = numpy.zeros(3 * nb * nb).reshape(3, nb, nb) + from pyscf.gto import moleintor + nbas = mol.nbas + max_double = mol.max_memory / 8.0 * 1.0e6 + max_basis = pow(max_double / 3., 1. / 4.) + ao_loc_orig = moleintor.make_loc(mol._bas, 'cint2e_p1vxp1_sph') + shl_size = [] + shl_slice = [0] + ao_loc = [0] + if nb > max_basis: + for i in range(0, nbas - 1): + if (ao_loc_orig[i + 1] - ao_loc[-1] > max_basis and ao_loc_orig[i] - ao_loc[-1]): + ao_loc.append(ao_loc_orig[i]) + shl_size.append(ao_loc[-1] - ao_loc[-2]) + shl_slice.append(i) + if ao_loc[-1] is not ao_loc_orig[-1]: + ao_loc.append(ao_loc_orig[-1]) + shl_size.append(ao_loc[-1] - ao_loc[-2]) + shl_slice.append(nbas) + nbas = len(shl_size) + + for i in range(0, nbas): + for j in range(0, nbas): + for k in range(0, nbas): + for l in range(0, nbas): + h2ao = mol.intor('cint2e_p1vxp1_sph', comp=3, aosym='s1', + shls_slice=[shl_slice[i], shl_slice[i+1], + shl_slice[j], shl_slice[j+1], + shl_slice[k], shl_slice[k+1], + shl_slice[l], shl_slice[l+1]]).reshape( + 3, shl_size[i], shl_size[j], shl_size[k], shl_size[l]) + hso1e[:, ao_loc[i]:ao_loc[i+1], ao_loc[j]:ao_loc[j+1]] \ + += 1.0*lib.einsum('ijklm, lm->ijk', h2ao, + dm[ao_loc[k]:ao_loc[k+1], ao_loc[l]:ao_loc[l+1]]) + hso1e[:, ao_loc[i]:ao_loc[i+1], ao_loc[l]:ao_loc[l+1]] \ + += -1.5*lib.einsum('ijklm, kl->ijm', h2ao, + dm[ao_loc[j]:ao_loc[j+1], ao_loc[k]:ao_loc[k+1]]) + hso1e[:, ao_loc[k]:ao_loc[k+1], ao_loc[j]:ao_loc[j+1]] \ + += -1.5*lib.einsum('ijklm, mj->ilk', h2ao, + dm[ao_loc[l]:ao_loc[l+1], ao_loc[i]:ao_loc[i+1]]) + return hso1e + + +def get_fso2e_bp1c(mol, dm, atomlist): + ''' Two electron bp operator with one-center approximation ''' + nb = mol.nao_nr() + + hso1e = numpy.zeros(3 * nb * nb).reshape(3, nb, nb) + from pyscf.gto import moleintor + max_double = mol.max_memory / 8.0 * 1.0e6 + max_basis = pow(max_double / 3., 1. / 4.) + shl_size = [] + shl_slice = [0] + ao_loc = [0] + ao_slice_by_atom = mol.aoslice_by_atom() + for slice in ao_slice_by_atom: + shl_slice.append(slice[1]) + shl_size.append(slice[3] - slice[2]) + ao_loc.append(slice[3]) + + for iatom in atomlist: + if shl_size[iatom] > max_basis: + print("TODO: split the basis with atom to deal with very heavy element") + else: + ibegin = shl_slice[iatom] + iend = shl_slice[iatom + 1] + #start = time.clock() + h2ao = mol.intor('cint2e_p1vxp1_sph', comp=3, aosym='s1', shls_slice=[ibegin, iend, ibegin, iend, ibegin, iend, ibegin, iend]).reshape( + 3, shl_size[iatom], shl_size[iatom], shl_size[iatom], shl_size[iatom]) + #end = time.clock() + # print("Time elapsed for integral calculation:", + # end - start, iatom, nbas) + hso1e[:, ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]] \ + += 1.0*lib.einsum('ijklm, lm->ijk', h2ao, + dm[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]]) + hso1e[:, ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]] \ + += -1.5*lib.einsum('ijklm, kl->ijm', h2ao, + dm[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]]) + hso1e[:, ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]] \ + += -1.5*lib.einsum('ijklm, mj->ilk', h2ao, + dm[ao_loc[iatom]:ao_loc[iatom+1], ao_loc[iatom]:ao_loc[iatom+1]]) + return hso1e + + +def writeGTensorIntegrals(mc, atomlist=None, origin=None): + mol = mc.mol + ncore, ncas = mc.ncore, mc.ncas + nb = mol.nao_nr() + + if (origin is not None): + mol.set_common_origin(origin) + + if atomlist is None: + h1ao = mol.intor('int1e_cg_irxp_sph', comp=3, hermi=2) + else: + h1ao = numpy.zeros((3, nb, nb)) + aoslice = mc.mol.aoslice_by_atom() + for iatom in atomlist: + for jatom in atomlist: + iao_start = aoslice[iatom, 2] + jao_start = aoslice[jatom, 2] + iao_end = aoslice[iatom, 3] + jao_end = aoslice[jatom, 3] + ishl_start = aoslice[iatom, 0] + jshl_start = aoslice[jatom, 0] + ishl_end = aoslice[iatom, 1] + jshl_end = aoslice[jatom, 1] + + h1ao[:, iao_start: iao_end, jao_start: jao_end]\ + += mol.intor('cint1e_cg_irxp_sph', 3, shls_slice=[ishl_start, ishl_end, jshl_start, jshl_end]).reshape(3, iao_end-iao_start, jao_end-jao_start) + + h1 = lib.einsum('xpq, pi, qj->xij', h1ao, mc.mo_coeff, mc.mo_coeff) + print1Int(h1[:, ncore:ncore + ncas, ncore:ncore + ncas], 'GTensor') + + +def writeSOCIntegrals(mc, + ncasorbs=None, + rdm1=None, + pictureChange1e="bp", + pictureChange2e="bp", + uncontract=True, + atomlist=None): + + from pyscf.lib.parameters import LIGHT_SPEED + alpha = 1.0 / LIGHT_SPEED + + # \alpha^2/4 factor before SOC Hamiltonian in the paper + # not applicable to ECP terms + factor = alpha**2 * 0.25 + + has_ecp = mc.mol.has_ecp() + + if ("bp" in pictureChange1e and "bp" in pictureChange2e): + uncontract = False + + if (uncontract): + xmol, contr_coeff = x2c.X2C(mc.mol).get_xmol() + else: + xmol, contr_coeff = mc.mol, numpy.eye(mc.mo_coeff.shape[0]) + + if (atomlist is None): + atomlist = numpy.linspace(0, xmol.natm - 1, xmol.natm, dtype=int) + + rdm1ao = rdm1 + if (rdm1 is None): + rdm1ao = 1. * mc.make_rdm1() + if len(rdm1ao.shape) > 2: + rdm1ao = (rdm1ao[0] + rdm1ao[1]) + + if (uncontract): + dm = reduce(numpy.dot, (contr_coeff, rdm1ao, contr_coeff.T)) + else: + dm = 1. * rdm1ao + np, nc = contr_coeff.shape[0], contr_coeff.shape[1] + + hso1e = numpy.zeros((3, np, np)) + + if ("x2c" in pictureChange1e or "x2c" in pictureChange2e): + h1e_1c, x, rp = get_hxr(mc, uncontract=uncontract) + if (has_ecp): + print("X2C Hamiltonian shouldn't be used with ECPs, switch to BP.") + exit(0) + + # ECPso terms + if (has_ecp): + hso1e += xmol.intor('ECPso') + + + if (not has_ecp): + # two electron terms + logger.note(xmol, "Start calculating two-electron contribution to SOC integrals, be patient.") + if (pictureChange2e == "bp"): + hso1e += -factor * get_fso2e_bp(xmol, dm) + elif (pictureChange2e == "bp1c"): + hso1e += -factor * get_fso2e_bp1c(xmol, dm, atomlist) + elif (pictureChange2e == "x2c_old"): + pLL, pLS, pSS = get_p(dm / 2.0, x, rp) + hso1e += -factor * get_fso2e_x2c_original(xmol, x, rp, pLL, pLS, pSS) + elif (pictureChange2e == "x2c"): + pLL, pLS, pSS = get_p(dm / 2.0, x, rp) + #hso1e += -factor * get_fso2e_x2c(xmol, x, rp, pLL, pLS, pSS) + hso1e[0] += -factor * get_fso2e_x2c(xmol, x, rp, pLL, pLS, pSS)[0] + hso1e[1] += -factor * get_fso2e_x2c(xmol, x, rp, pLL, pLS, pSS)[1] + hso1e[2] += -factor * get_fso2e_x2c(xmol, x, rp, pLL, pLS, pSS)[2] + elif (pictureChange2e == "x2c1c"): + pLL, pLS, pSS = get_p(dm / 2.0, x, rp) + hso1e += -factor * get_fso2e_x2c1c(xmol, x, rp, pLL, pLS, pSS) + elif (pictureChange2e == "none"): + hso1e += 0.0 + else: + print(pictureChange2e, "not a valid option") + exit(0) + + # MF 1 electron term + if (pictureChange1e == "bp"): + hso1e += factor * get_wso(xmol) + elif (pictureChange1e == "x2c1"): + wso = factor * get_wso(xmol) + hso1e += get_hso1e(wso, x, rp) + elif (pictureChange1e == "none"): + hso1e += 0.0 + else: + print(pictureChange1e, "not a valid option") + exit(0) + + h1ao = numpy.zeros((3, nc, nc)) + if (uncontract): + for ic in range(3): + h1ao[ic] = reduce( + numpy.dot, (contr_coeff.T, hso1e[ic], contr_coeff)) + else: + h1ao = 1. * hso1e + + ncore, ncas = mc.ncore, mc.ncas + if (ncasorbs is not None): + ncas = ncasorbs + mo_coeff = mc.mo_coeff + h1 = lib.einsum('xpq,pi,qj->xij', h1ao, mo_coeff, + mo_coeff)[:, ncore:ncore + ncas, ncore:ncore + ncas] + print1Int(h1, 'SOC') + + +def doSOC(mc, pictureChange1e="bp", pictureChange2e="bp", uncontract=False, atomlist=None): + writeGTensorIntegrals(mc, atomlist=atomlist) + writeSOCIntegrals(mc, pictureChange1e=pictureChange1e, + pictureChange2e=pictureChange2e, uncontract=uncontract, atomlist=atomlist) + mch = shci.SHCISCF(mc.mol, mc.norb, mc.nelec) + mch.fcisolver.DoSOC = True + mch.fcisolver.DoRDM = False + shci.dryrun(mch, mc.mo_coeff) + shci.executeSHCI(mc.fcisolver) diff --git a/pyscf/tools/fcidump.py b/pyscf/tools/fcidump.py index 13f4d7d402..03c79d5417 100644 --- a/pyscf/tools/fcidump.py +++ b/pyscf/tools/fcidump.py @@ -135,6 +135,8 @@ def from_mo(mol, filename, mo_coeff, orbsym=None, orbsym = getattr(mo_coeff, 'orbsym', None) t = mol.intor_symmetric('int1e_kin') v = mol.intor_symmetric('int1e_nuc') + if len(mol._ecpbas) > 0: + v += mol.intor_symmetric('ECPscalar') h1e = reduce(numpy.dot, (mo_coeff.T, t+v, mo_coeff)) eri = ao2mo.full(mol, mo_coeff, verbose=0) nuc = mol.energy_nuc() diff --git a/pyscf/vmcscf/__init__.py b/pyscf/vmcscf/__init__.py new file mode 100644 index 0000000000..31982cc7a5 --- /dev/null +++ b/pyscf/vmcscf/__init__.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# Copyright 2014-2018 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author: Sandeep Sharma +# James Smith +# diff --git a/pyscf/vmcscf/vmc.py b/pyscf/vmcscf/vmc.py new file mode 100644 index 0000000000..dc3c0129d7 --- /dev/null +++ b/pyscf/vmcscf/vmc.py @@ -0,0 +1,620 @@ +#!/usr/bin/env python +# Copyright 2014-2018 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author: Sandeep Sharma +# +''' +VMC solver for CASCI and CASSCF. +''' + +from functools import reduce +import ctypes +import os +import sys +import struct +import time +import tempfile +import warnings +from subprocess import check_call +from subprocess import CalledProcessError + +from pyscf.lo import pipek, boys, edmiston, iao, ibo +from pyscf import ao2mo, tools, scf, mcscf, lo, gto +import numpy +import scipy +import pyscf.tools +import pyscf.lib +from pyscf.lib import logger +from pyscf.lib import chkfile +from pyscf import mcscf +ndpointer = numpy.ctypeslib.ndpointer + +# Settings +try: + from pyscf.vmcscf import settings +except ImportError: + from pyscf import __config__ + settings = lambda: None + settings.VMCEXE = getattr(__config__, 'vmc_VMCEXE', None) + settings.VMCSCRATCHDIR = getattr(__config__, 'vmc_VMCSCRATCHDIR', None) + settings.VMCRUNTIMEDIR = getattr(__config__, 'vmc_VMCRUNTIMEDIR', None) + settings.MPIPREFIX = getattr(__config__, 'vmc_MPIPREFIX', None) + if (settings.VMCEXE is None or settings.VMCSCRATCHDIR is None): + import sys + sys.stderr.write('settings.py not found for module vmcscf. Please create %s\n' + % os.path.join(os.path.dirname(__file__), 'settings.py')) + raise ImportError('settings.py not found') + +# Libraries +from pyscf.lib import load_library +libE3unpack = load_library('libicmpspt') +# TODO: Organize this better. +vmcLib = load_library('libshciscf') + + +writeIntNoSymm = vmcLib.writeIntNoSymm +writeIntNoSymm.argtypes = [ + ctypes.c_int, + ndpointer(ctypes.c_double), + ndpointer(ctypes.c_double), ctypes.c_double, ctypes.c_int, + ndpointer(ctypes.c_int) +] + +fcidumpFromIntegral = vmcLib.fcidumpFromIntegral +fcidumpFromIntegral.restype = None +fcidumpFromIntegral.argtypes = [ + ctypes.c_char_p, + ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), + ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), ctypes.c_size_t, + ctypes.c_size_t, ctypes.c_double, + ndpointer(ctypes.c_int32, flags="C_CONTIGUOUS"), ctypes.c_size_t +] + +r2RDM = vmcLib.r2RDM +r2RDM.restype = None +r2RDM.argtypes = [ + ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), ctypes.c_size_t, + ctypes.c_char_p +] + + +class VMC(pyscf.lib.StreamObject): + r'''VMC program interface and object to hold VMC program input parameters. + + Attributes: + initialStates: [[int]] + groupname : str + groupname, orbsym together can control whether to employ symmetry in + the calculation. "groupname = None and orbsym = []" requires the + VMC program using C1 symmetry. + useExtraSymm : False + if the symmetry of the molecule is Dooh or Cooh, then this keyword uses + complex orbitals to make full use of this symmetry + + Examples: + + ''' + + def __init__(self, mol=None): + self.mol = mol + if mol is None: + self.stdout = sys.stdout + self.verbose = logger.NOTE + else: + self.stdout = mol.stdout + self.verbose = mol.verbose + self.outputlevel = 2 + + self.executable = settings.VMCEXE + self.scratchDirectory = settings.VMCSCRATCHDIR + self.mpiprefix = settings.MPIPREFIX + + self.wavefunction = "jastrowslater" + self.slater = "ghf" + self.maxIter = 100 + self.stochasticIter = 1000 + self.stochasticIterTight = 4*self.stochasticIter + + self.integralFile = "FCIDUMP" + self.configFile = "vmc.dat" + self.rdmconfigFile = "rdmvmc.dat" + self.outputFile = "vmc.out" + self.rdmoutputFile = "rdmvmc.out" + if getattr(settings, 'VMCRUNTIMEDIR', None): + self.runtimeDir = settings.VMCRUNTIMEDIR + else: + self.runtimeDir = '.' + self.extraline = [] + + if mol is None: + self.groupname = None + else: + if mol.symmetry: + self.groupname = mol.groupname + else: + self.groupname = None + + + def dump_flags(self, verbose=None): + if verbose is None: + verbose = self.verbose + log = logger.Logger(self.stdout, verbose) + log.info('') + log.info('******** VMC flags ********') + log.info('executable = %s', self.executable) + log.info('mpiprefix = %s', self.mpiprefix) + log.info('scratchDirectory = %s', self.scratchDirectory) + log.info('integralFile = %s', + os.path.join(self.runtimeDir, self.integralFile)) + log.info('configFile = %s', + os.path.join(self.runtimeDir, self.configFile)) + log.info('outputFile = %s', + os.path.join(self.runtimeDir, self.outputFile)) + log.info('maxIter = %d', self.maxIter) + log.info('') + return self + + # ABOUT RDMs AND INDEXES: ----------------------------------------------------------------------- + # There is two ways to stored an RDM + # (the numbers help keep track of creation/annihilation that go together): + # E3[i1,j2,k3,l3,m2,n1] is the way DICE outputs text and bin files + # E3[i1,j2,k3,l1,m2,n3] is the way the tensors need to be written for SQA and ICPT + # + # --> See various remarks in the pertinent functions below. + # ----------------------------------------------------------------------------------------------- + + def make_rdm12(self, state, norb, nelec, link_index=None, **kwargs): + nelectrons = 0 + if isinstance(nelec, (int, numpy.integer)): + nelectrons = nelec + else: + nelectrons = nelec[0] + nelec[1] + + # The 2RDMs written by "VMCrdm::saveRDM" in DICE + # are written as E2[i1,j2,k1,l2] + # and stored here as E2[i1,k1,j2,l2] (for PySCF purposes) + # This is NOT done with SQA in mind. + twopdm = numpy.zeros((norb, norb, norb, norb)) + file2pdm = "spatialRDM.%d.%d.txt" % (state, state) + # file2pdm = file2pdm.encode() # .encode for python3 compatibility + r2RDM(twopdm, norb, + os.path.join(file2pdm).encode()) + + # (This is coherent with previous statement about indexes) + onepdm = numpy.einsum('ikjj->ki', twopdm) + onepdm /= (nelectrons - 1) + return onepdm, twopdm + + + def make_rdm12_forSQA(self, state, norb, nelec, link_index=None, **kwargs): + nelectrons = 0 + if isinstance(nelec, (int, numpy.integer)): + nelectrons = nelec + else: + nelectrons = nelec[0]+nelec[1] + + # The 2RDMs written by "VMCrdm::saveRDM" in DICE + # are written as E2[i1,j2,k1,l2] + # and stored here as E2[i1,k1,j2,l2] (for PySCF purposes) + # This is NOT done with SQA in mind. + twopdm = numpy.zeros((norb, norb, norb, norb)) + file2pdm = "spatialRDM.%d.%d.txt" % (state,state) + r2RDM(twopdm, norb, + os.path.join(self.scratchDirectory, file2pdm).endcode()) + twopdm=twopdm.transpose(0,2,1,3) + + # (This is coherent with previous statement about indexes) + onepdm = numpy.einsum('ijkj->ki', twopdm) + onepdm /= (nelectrons-1) + return onepdm, twopdm + + + def kernel(self, h1e, eri, norb, nelec, fciRestart=None, ecore=0, + **kwargs): + """ + Approximately solve CI problem for the specified active space. + """ + writeIntegralFile(self, h1e, eri, norb, nelec, ecore) + self.writeConfig() + executeVMC(self) + + #onerdm, twordm = make_rdm12(self, 0, norb, nelec) + + outFile = os.path.join(self.runtimeDir, self.outputFile) + f = open(outFile, 'r') + l = f.readlines() + calc_e = float(l[-1].split()[1]) + roots = 0 + return calc_e, roots + + + def writeConfig(self, restart=True, readBestDeterminant=False): + confFile = os.path.join(self.runtimeDir, self.configFile) + + f = open(confFile, 'w') + f.write("%s\n" %self.wavefunction) + f.write("complex\n") + f.write("%s\n" %self.slater) + f.write("maxiter %d\n" %self.maxIter) + f.write("stochasticIter %d\n" % self.stochasticIter) + #if (restart) : + #f.write("fullrestart\n") + if (readBestDeterminant) : + f.write("determinants bestDet") + f.close() + + + confFile = os.path.join(self.runtimeDir, self.rdmconfigFile) + + f = open(confFile, 'w') + f.write("slatertwordm\n") + f.write("complex\n") + f.write("%s\n" %self.slater) + f.write("maxiter %d\n" %self.maxIter) + f.write("stochasticIter %d\n" % self.stochasticIterTight) + f.close() + + + def spin_square(self, civec, norb, nelec): + if isinstance(nelec, (int, numpy.integer)): + nelecb = nelec // 2 + neleca = nelec - nelecb + else: + neleca, nelecb = nelec + s = (neleca - nelecb) * .5 + ss = s * (s + 1) + if isinstance(civec, int): + return ss, s * 2 + 1 + else: + return [ss] * len(civec), [s * 2 + 1] * len(civec) + + def cleanup_dice_files(self): + """ + Remove the files used for Dice communication. + """ + os.remove("input.dat") + os.remove("output.dat") + os.remove("FCIDUMP") + + +def print1Int(h1, name): + with open('%s.X' % (name), 'w') as fout: + fout.write('%d\n' % h1[0].shape[0]) + for i in range(h1[0].shape[0]): + for j in range(h1[0].shape[0]): + if (abs(h1[0, i, j]) > 1.e-8): + fout.write( + '%16.10g %4d %4d\n' % (h1[0, i, j], i + 1, j + 1)) + + with open('%s.Y' % (name), 'w') as fout: + fout.write('%d\n' % h1[1].shape[0]) + for i in range(h1[1].shape[0]): + for j in range(h1[1].shape[0]): + if (abs(h1[1, i, j]) > 1.e-8): + fout.write( + '%16.10g %4d %4d\n' % (h1[1, i, j], i + 1, j + 1)) + + with open('%s.Z' % (name), 'w') as fout: + fout.write('%d\n' % h1[2].shape[0]) + for i in range(h1[2].shape[0]): + for j in range(h1[2].shape[0]): + if (abs(h1[2, i, j]) > 1.e-8): + fout.write( + '%16.10g %4d %4d\n' % (h1[2, i, j], i + 1, j + 1)) + + with open('%sZ' % (name), 'w') as fout: + fout.write('%d\n' % h1[2].shape[0]) + for i in range(h1[2].shape[0]): + for j in range(h1[2].shape[0]): + if (abs(h1[2, i, j]) > 1.e-8): + fout.write( + '%16.10g %4d %4d\n' % (h1[2, i, j], i + 1, j + 1)) + + + +def writeIntegralFile(VMC, h1eff, eri_cas, norb, nelec, ecore=0): + if isinstance(nelec, (int, numpy.integer)): + neleca = nelec // 2 + nelec % 2 + nelecb = nelec - neleca + else: + neleca, nelecb = nelec + + # The name of the FCIDUMP file, default is "FCIDUMP". + integralFile = os.path.join(VMC.runtimeDir, VMC.integralFile) + + if not os.path.exists(VMC.scratchDirectory): + os.makedirs(VMC.scratchDirectory) + + from pyscf import symm + from pyscf.dmrgscf import dmrg_sym + + if (VMC.groupname == 'Dooh' + or VMC.groupname == 'Coov') and VMC.useExtraSymm: + coeffs, nRows, rowIndex, rowCoeffs, orbsym = D2htoDinfh( + VMC, norb, nelec) + + newintt = numpy.tensordot(coeffs.conj(), h1eff, axes=([1], [0])) + newint1 = numpy.tensordot(newintt, coeffs, axes=([1], [1])) + newint1r = numpy.zeros(shape=(norb, norb), order='C') + for i in range(norb): + for j in range(norb): + newint1r[i, j] = newint1[i, j].real + int2 = pyscf.ao2mo.restore(1, eri_cas, norb) + eri_cas = numpy.zeros_like(int2) + + transformDinfh(norb, numpy.ascontiguousarray(nRows, numpy.int32), + numpy.ascontiguousarray(rowIndex, numpy.int32), + numpy.ascontiguousarray(rowCoeffs, numpy.float64), + numpy.ascontiguousarray(int2, numpy.float64), + numpy.ascontiguousarray(eri_cas, numpy.float64)) + + writeIntNoSymm(norb, numpy.ascontiguousarray(newint1r, numpy.float64), + numpy.ascontiguousarray(eri_cas, numpy.float64), + ecore, neleca + nelecb, + numpy.asarray(orbsym, dtype=numpy.int32)) + + else: + if VMC.groupname is not None and VMC.orbsym is not []: + orbsym = dmrg_sym.convert_orbsym(VMC.groupname, VMC.orbsym) + else: + orbsym = [1] * norb + + eri_cas = pyscf.ao2mo.restore(8, eri_cas, norb) + # Writes the FCIDUMP file using functions in VMC_tools.cpp. + integralFile = integralFile.encode() # .encode for python3 compatibility + fcidumpFromIntegral(integralFile, h1eff, eri_cas, norb, + neleca + nelecb, ecore, + numpy.asarray(orbsym, dtype=numpy.int32), + abs(neleca - nelecb)) + + +def executeVMC(VMC): + inFile = os.path.join(VMC.runtimeDir, VMC.configFile) + outFile = os.path.join(VMC.runtimeDir, VMC.outputFile) + try: + cmd = ' '.join((VMC.mpiprefix, VMC.executable, inFile)) + cmd = "%s > %s 2>&1" % (cmd, outFile) + check_call(cmd, shell=True) + #save_output(VMC) + except CalledProcessError as err: + logger.error(VMC, cmd) + raise err + + inFile = os.path.join(VMC.runtimeDir, VMC.rdmconfigFile) + outFile = os.path.join(VMC.runtimeDir, VMC.rdmoutputFile) + try: + cmd = ' '.join((VMC.mpiprefix, VMC.executable, inFile)) + cmd = "%s > %s 2>&1" % (cmd, outFile) + check_call(cmd, shell=True) + #save_output(VMC) + except CalledProcessError as err: + logger.error(VMC, cmd) + raise err + + +#def save_output(VMC): +# for i in range(50): +# if os.path.exists(os.path.join(VMC.runtimeDir, "output%02d.dat"%(i))): +# continue +# else: +# import shutil +# shutil.copy2(os.path.join(VMC.runtimeDir, "output.dat"),os.path.join(VMC.runtimeDir, "output%02d.dat"%(i))) +# shutil.copy2(os.path.join(VMC.runtimeDir, "%s/vmc.e"%(VMC.scratchDirectory)), os.path.join(VMC.runtimeDir, "vmc%02d.e"%(i))) +# #print('BM copied into "output%02d.dat"'%(i)) +# #print('BM copied into "vmc%02d.e"'%(i)) +# break + + +def readEnergy(VMC): + file1 = open( + os.path.join(VMC.runtimeDir, "%s/vmc.e" % (VMC.scratchDirectory)), + "rb") + format = ['d'] * VMC.nroots + format = ''.join(format) + calc_e = struct.unpack(format, file1.read()) + file1.close() + if VMC.nroots == 1: + return calc_e[0] + else: + return list(calc_e) + +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) + +# can be used for all electron, but not recommended +def bestDetValence(mol, lmo, occ, eri, writeToFile=True): + + # index of the ao contributing the most to an lmo + maxLMOContributers = [ numpy.argmax(numpy.abs(lmo[::,i])) for i in range(lmo.shape[1]) ] + + # end AO index for each atom in ascending order + atomNumAOs = [ i[1][3] - 1 for i in enumerate(mol.aoslice_nr_by_atom()) ] + + lmoSites = [ [] for i in range(mol.natm) ] #lmo's cetered on each atom + for i in enumerate(maxLMOContributers): + lmoSites[numpy.searchsorted(numpy.array(atomNumAOs), i[1])].append(i[0]) + + bestDet = ['0' for i in range(lmo.shape[1])] + def pair(i): + return i*(i+1)//2+i + for i in enumerate(occ): + if eri.ndim == 2: + onSiteIntegrals = [ (j, eri[pair(j),pair(j)]) for (n,j) in enumerate(lmoSites[i[0]]) ] + elif eri.ndim == 1: + onSiteIntegrals = [ (j, eri[pair(pair(j))]) for (n,j) in enumerate(lmoSites[i[0]]) ] + onSiteIntegrals.sort(key = lambda tup : tup[1], reverse=True) + for k in range(i[1][0]): + bestDet[onSiteIntegrals[k][0]] = '2' + for l in range(i[1][1]): + bestDet[onSiteIntegrals[i[1][0] + l][0]] = 'a' + for m in range(i[1][2]): + bestDet[onSiteIntegrals[i[1][0] + i[1][1] + m][0]] = 'b' + + bestDetStr = ' '.join(bestDet) + print('bestDet: ' + bestDetStr) + if writeToFile: + fileh = open("bestDet", 'w') + fileh.write('1. ' + bestDetStr + '\n') + fileh.close() + + return bestDetStr + +def writeMat(mat, fileName, isComplex): + fileh = open(fileName, 'w') + for i in range(mat.shape[0]): + for j in range(mat.shape[1]): + if (isComplex): + fileh.write('(%16.10e, %16.10e) '%(mat[i,j].real, mat[i,j].imag)) + else: + fileh.write('%16.10e '%(mat[i,j])) + fileh.write('\n') + fileh.close() + +def VMCSCF(mf, ncore, nact, occ=None, frozen = None, loc="iao", proc=None,*args, **kwargs): + + mol = mf.mol + mo = mf.mo_coeff + nelec = mol.nelectron - 2 * ncore + mc = mcscf.CASSCF(mf, nact, nelec) + + if loc is not None: + lmo = localizeValence(mf, mo[:, ncore:ncore+nact], loc) + tools.molden.from_mo(mf.mol, 'valenceOrbs.molden', lmo) + + h1cas, energy_core = mcscf.casci.h1e_for_cas(mc, mf.mo_coeff, nact, ncore) + mo_core = mc.mo_coeff[:,:ncore] + core_dm = 2 * mo_core.dot(mo_core.T) + corevhf = mc.get_veff(mol, core_dm) + h1eff = lmo.T.dot(mc.get_hcore() + corevhf).dot(lmo) + eri = ao2mo.kernel(mol, lmo) + else: + lmo = numpy.eye(nact) + h1eff = mf.get_hcore() + eri = mf._eri + energy_core = 0 + if occ is not None: + bestDetValence(mol, lmo, occ, eri, True) + + + #prepare initial guess for the HF orbitals + norb = nact + molA = gto.M() + molA.nelectron = nelec + molA.verbose = 4 + molA.incore_anyway = True + gmf = scf.GHF(molA) + gmf.get_hcore = lambda *args: scipy.linalg.block_diag(h1eff, h1eff) + gmf.get_ovlp = lambda *args: numpy.identity(2*norb) + gmf.energy_nuc = lambda *args: energy_core + gmf._eri = eri + + dm = gmf.get_init_guess() + dm = dm + 2 * numpy.random.rand(2*norb, 2*norb) + gmf.level_shift = 0.1 + gmf.max_cycle = 500 + print(gmf.kernel(dm0 = dm)) + mocoeff = numpy.zeros((2*norb, 2*norb), dtype=complex) + mocoeff = 1.*gmf.mo_coeff + writeMat(gmf.mo_coeff, "hf.txt", True) + + #run an initial very short turn + mc.fcisolver = VMC(mf.mol) + mc.fcisolver.maxIter = 5 + writeIntegralFile(mc.fcisolver, h1eff, eri, nact, nelec, energy_core) + mc.fcisolver.writeConfig(restart=False, readBestDeterminant= (occ is not None)) + mc.fcisolver.mpiprefix = "mpirun" + if (proc is not None): + mc.fcisolver.mpiprefix = ("mpirun -np %d" %(proc)) + executeVMC(mc.fcisolver) + + + mc.fcisolver.maxIter = 100 + mf.mo_coeff[:,ncore:ncore+nact] = lmo + mc.mo_coeff = 1.*mf.mo_coeff + mc.internal_rotation = True + + if frozen is not None: + mc.frozen = frozen + + if mc.chkfile == mc._scf._chkfile.name: + # Do not delete chkfile after mcscf + mc.chkfile = tempfile.mktemp(dir=settings.VMCSCRATCHDIR) + if not os.path.exists(settings.VMCSCRATCHDIR): + os.makedirs(settings.VMCSCRATCHDIR) + return mc + + +if __name__ == '__main__': + from pyscf import gto, scf, mcscf, dmrgscf + from pyscf.vmcscf import vmc + + # Initialize benzene molecule + atomstring = ''' + C 0.000517 0.000000 0.000299 + C 0.000517 0.000000 1.394692 + C 1.208097 0.000000 2.091889 + C 2.415677 0.000000 1.394692 + C 2.415677 0.000000 0.000299 + C 1.208097 0.000000 -0.696898 + H -0.939430 0.000000 -0.542380 + H -0.939430 0.000000 1.937371 + H 1.208097 0.000000 3.177246 + H 3.355625 0.000000 1.937371 + H 3.355625 0.000000 -0.542380 + H 1.208097 0.000000 -1.782255 + ''' + mol = gto.M( + atom = atomstring, + unit = 'angstrom', + basis = 'sto-6g', + verbose = 4, + symmetry= 0, + spin = 0) + + # Create HF molecule + mf = scf.RHF(mol) + mf.conv_tol = 1e-9 + mf.scf() + + + ncore, nact = 6, 30 + occ = [] + configC = [[0,4,0], [0,0,4]] # no double occ, 4 up or 4 dn + configH = [[0,1,0], [0,0,1]] # no double occ, 1 up or 1 dn + for i in range(6): + occ.append(configC[i%2]) + for i in range(6): + occ.append(configH[(i+1)%2]) + + + frozen = list(range(0,ncore)) + list (range(36, mf.mo_coeff.shape[0])) + print (frozen) + # Create VMC molecule for just variational opt. + # Active spaces chosen to reflect valence active space. + mch = vmc.VMCSCF(mf, ncore, nact, occ=occ, loc="ibo",\ + frozen = frozen) + mch.fcisolver.stochasticIter = 400 + mch.fcisolver.maxIter = 50 + e_noPT = mch.mc2step()[0] + diff --git a/pyscf/vmcscf/vmcActiveActive.py b/pyscf/vmcscf/vmcActiveActive.py new file mode 100644 index 0000000000..f36d259925 --- /dev/null +++ b/pyscf/vmcscf/vmcActiveActive.py @@ -0,0 +1,781 @@ +#!/usr/bin/env python +# Copyright 2014-2018 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author: Sandeep Sharma +# +''' +VMC solver for CASCI and CASSCF. +''' + +from functools import reduce +import ctypes +import os +import sys +import struct +import time +import tempfile +import warnings +from subprocess import check_call +from subprocess import CalledProcessError + +from pyscf.lo import pipek, boys, edmiston, iao, ibo +from pyscf import ao2mo, tools, scf, mcscf, lo, gto +from pyscf.shciscf import shci +import numpy +import scipy +from scipy.linalg import fractional_matrix_power +import pyscf.tools +import pyscf.lib +from pyscf import lib +from pyscf.lib import logger +from pyscf.lib import chkfile +from pyscf import mcscf +from scipy.linalg import expm +ndpointer = numpy.ctypeslib.ndpointer + +# Settings +try: + from pyscf.vmcscf import settings +except ImportError: + from pyscf import __config__ + settings = lambda: None + settings.VMCEXE = getattr(__config__, 'vmc_VMCEXE', None) + settings.VMCSCRATCHDIR = getattr(__config__, 'vmc_VMCSCRATCHDIR', None) + settings.VMCRUNTIMEDIR = getattr(__config__, 'vmc_VMCRUNTIMEDIR', None) + settings.MPIPREFIX = getattr(__config__, 'vmc_MPIPREFIX', None) + if (settings.VMCEXE is None or settings.VMCSCRATCHDIR is None): + import sys + sys.stderr.write('settings.py not found for module vmcscf. Please create %s\n' + % os.path.join(os.path.dirname(__file__), 'settings.py')) + raise ImportError('settings.py not found') + +# Libraries +from pyscf.lib import load_library +libE3unpack = load_library('libicmpspt') +# TODO: Organize this better. +vmcLib = load_library('libshciscf') + + +writeIntNoSymm = vmcLib.writeIntNoSymm +writeIntNoSymm.argtypes = [ + ctypes.c_int, + ndpointer(ctypes.c_double), + ndpointer(ctypes.c_double), ctypes.c_double, ctypes.c_int, + ndpointer(ctypes.c_int) +] + +fcidumpFromIntegral = vmcLib.fcidumpFromIntegral +fcidumpFromIntegral.restype = None +fcidumpFromIntegral.argtypes = [ + ctypes.c_char_p, + ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), + ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), ctypes.c_size_t, + ctypes.c_size_t, ctypes.c_double, + ndpointer(ctypes.c_int32, flags="C_CONTIGUOUS"), ctypes.c_size_t +] + +r2RDM = vmcLib.r2RDM +r2RDM.restype = None +r2RDM.argtypes = [ + ndpointer(ctypes.c_double, flags="C_CONTIGUOUS"), ctypes.c_size_t, + ctypes.c_char_p +] + + +class VMC(pyscf.lib.StreamObject): + r'''VMC program interface and object to hold VMC program input parameters. + + Attributes: + initialStates: [[int]] + groupname : str + groupname, orbsym together can control whether to employ symmetry in + the calculation. "groupname = None and orbsym = []" requires the + VMC program using C1 symmetry. + useExtraSymm : False + if the symmetry of the molecule is Dooh or Cooh, then this keyword uses + complex orbitals to make full use of this symmetry + + Examples: + + ''' + + def __init__(self, mol=None): + self.mol = mol + if mol is None: + self.stdout = sys.stdout + self.verbose = logger.NOTE + else: + self.stdout = mol.stdout + self.verbose = mol.verbose + self.outputlevel = 2 + + self.executable = settings.VMCEXE + self.scratchDirectory = settings.VMCSCRATCHDIR + self.mpiprefix = settings.MPIPREFIX + + self.wavefunction = "jastrowslater" + self.slater = "ghf" + self.maxIter = 100 + self.stochasticIter = 1000 + self.stochasticIterTight = 4*self.stochasticIter + + self.integralFile = "FCIDUMP" + self.configFile = "vmc.dat" + self.rdmconfigFile = "rdmvmc.dat" + self.outputFile = "vmc.out" + self.rdmoutputFile = "rdmvmc.out" + if getattr(settings, 'VMCRUNTIMEDIR', None): + self.runtimeDir = settings.VMCRUNTIMEDIR + else: + self.runtimeDir = '.' + self.extraline = [] + + if mol is None: + self.groupname = None + else: + if mol.symmetry: + self.groupname = mol.groupname + else: + self.groupname = None + + + def dump_flags(self, verbose=None): + if verbose is None: + verbose = self.verbose + log = logger.Logger(self.stdout, verbose) + log.info('') + log.info('******** VMC flags ********') + log.info('executable = %s', self.executable) + log.info('mpiprefix = %s', self.mpiprefix) + log.info('scratchDirectory = %s', self.scratchDirectory) + log.info('integralFile = %s', + os.path.join(self.runtimeDir, self.integralFile)) + log.info('configFile = %s', + os.path.join(self.runtimeDir, self.configFile)) + log.info('outputFile = %s', + os.path.join(self.runtimeDir, self.outputFile)) + log.info('maxIter = %d', self.maxIter) + log.info('') + return self + + # ABOUT RDMs AND INDEXES: ----------------------------------------------------------------------- + # There is two ways to stored an RDM + # (the numbers help keep track of creation/annihilation that go together): + # E3[i1,j2,k3,l3,m2,n1] is the way DICE outputs text and bin files + # E3[i1,j2,k3,l1,m2,n3] is the way the tensors need to be written for SQA and ICPT + # + # --> See various remarks in the pertinent functions below. + # ----------------------------------------------------------------------------------------------- + + def make_rdm12(self, state, norb, nelec, link_index=None, **kwargs): + nelectrons = 0 + if isinstance(nelec, (int, numpy.integer)): + nelectrons = nelec + else: + nelectrons = nelec[0] + nelec[1] + + # The 2RDMs written by "VMCrdm::saveRDM" in DICE + # are written as E2[i1,j2,k1,l2] + # and stored here as E2[i1,k1,j2,l2] (for PySCF purposes) + # This is NOT done with SQA in mind. + twopdm = numpy.zeros((norb, norb, norb, norb)) + file2pdm = "spatialRDM.%d.%d.txt" % (state, state) + # file2pdm = file2pdm.encode() # .encode for python3 compatibility + r2RDM(twopdm, norb, + os.path.join(file2pdm).encode()) + + # (This is coherent with previous statement about indexes) + onepdm = lib.einsum('ikjj->ki', twopdm) + onepdm /= (nelectrons - 1) + return onepdm, twopdm + + + def make_rdm12_forSQA(self, state, norb, nelec, link_index=None, **kwargs): + nelectrons = 0 + if isinstance(nelec, (int, numpy.integer)): + nelectrons = nelec + else: + nelectrons = nelec[0]+nelec[1] + + # The 2RDMs written by "VMCrdm::saveRDM" in DICE + # are written as E2[i1,j2,k1,l2] + # and stored here as E2[i1,k1,j2,l2] (for PySCF purposes) + # This is NOT done with SQA in mind. + twopdm = numpy.zeros((norb, norb, norb, norb)) + file2pdm = "spatialRDM.%d.%d.txt" % (state,state) + r2RDM(twopdm, norb, + os.path.join(self.scratchDirectory, file2pdm).endcode()) + twopdm=twopdm.transpose(0,2,1,3) + + # (This is coherent with previous statement about indexes) + onepdm = numpy.einsum('ijkj->ki', twopdm) + onepdm /= (nelectrons-1) + return onepdm, twopdm + + + def kernel(self, h1e, eri, norb, nelec, fciRestart=None, ecore=0, + **kwargs): + """ + Approximately solve CI problem for the specified active space. + """ + writeIntegralFile(self, h1e, eri, norb, nelec, ecore) + self.writeConfig() + executeVMC(self) + + #onerdm, twordm = make_rdm12(self, 0, norb, nelec) + + outFile = os.path.join(self.runtimeDir, self.outputFile) + f = open(outFile, 'r') + l = f.readlines() + calc_e = float(l[-1].split()[1]) + roots = 0 + return calc_e, roots + + + def writeConfig(self, restart=True, readBestDeterminant=False): + confFile = os.path.join(self.runtimeDir, self.configFile) + + f = open(confFile, 'w') + f.write("%s\n" %self.wavefunction) + f.write("complex\n") + f.write("%s\n" %self.slater) + f.write("maxiter %d\n" %self.maxIter) + f.write("stochasticIter %d\n" % self.stochasticIter) + #f.write("deterministic\n") + if (restart) : + f.write("fullrestart\n") + if (readBestDeterminant) : + f.write("determinants bestDet") + f.close() + + + confFile = os.path.join(self.runtimeDir, self.rdmconfigFile) + + f = open(confFile, 'w') + f.write("slatertwordm\n") + f.write("complex\n") + f.write("%s\n" %self.slater) + f.write("maxiter %d\n" %self.maxIter) + f.write("stochasticIter %d\n" % self.stochasticIterTight) + #f.write("deterministic\n") + f.close() + + + +def writeIntegralFile(VMC, h1eff, eri_cas, norb, nelec, ecore=0): + if isinstance(nelec, (int, numpy.integer)): + neleca = nelec // 2 + nelec % 2 + nelecb = nelec - neleca + else: + neleca, nelecb = nelec + + # The name of the FCIDUMP file, default is "FCIDUMP". + integralFile = os.path.join(VMC.runtimeDir, VMC.integralFile) + + if not os.path.exists(VMC.scratchDirectory): + os.makedirs(VMC.scratchDirectory) + + from pyscf import symm + from pyscf.dmrgscf import dmrg_sym + + if (VMC.groupname == 'Dooh' + or VMC.groupname == 'Coov') and VMC.useExtraSymm: + coeffs, nRows, rowIndex, rowCoeffs, orbsym = D2htoDinfh( + VMC, norb, nelec) + + newintt = numpy.tensordot(coeffs.conj(), h1eff, axes=([1], [0])) + newint1 = numpy.tensordot(newintt, coeffs, axes=([1], [1])) + newint1r = numpy.zeros(shape=(norb, norb), order='C') + for i in range(norb): + for j in range(norb): + newint1r[i, j] = newint1[i, j].real + int2 = pyscf.ao2mo.restore(1, eri_cas, norb) + eri_cas = numpy.zeros_like(int2) + + transformDinfh(norb, numpy.ascontiguousarray(nRows, numpy.int32), + numpy.ascontiguousarray(rowIndex, numpy.int32), + numpy.ascontiguousarray(rowCoeffs, numpy.float64), + numpy.ascontiguousarray(int2, numpy.float64), + numpy.ascontiguousarray(eri_cas, numpy.float64)) + + writeIntNoSymm(norb, numpy.ascontiguousarray(newint1r, numpy.float64), + numpy.ascontiguousarray(eri_cas, numpy.float64), + ecore, neleca + nelecb, + numpy.asarray(orbsym, dtype=numpy.int32)) + + else: + if VMC.groupname is not None and VMC.orbsym is not []: + orbsym = dmrg_sym.convert_orbsym(VMC.groupname, VMC.orbsym) + else: + orbsym = [1] * norb + + eri_cas = pyscf.ao2mo.restore(8, eri_cas, norb) + # Writes the FCIDUMP file using functions in VMC_tools.cpp. + integralFile = integralFile.encode() # .encode for python3 compatibility + fcidumpFromIntegral(integralFile, h1eff, eri_cas, norb, + neleca + nelecb, ecore, + numpy.asarray(orbsym, dtype=numpy.int32), + abs(neleca - nelecb)) + + +def executeVMC(VMC): + inFile = os.path.join(VMC.runtimeDir, VMC.configFile) + outFile = os.path.join(VMC.runtimeDir, VMC.outputFile) + try: + cmd = ' '.join((VMC.mpiprefix, VMC.executable, inFile)) + cmd = "%s > %s 2>&1" % (cmd, outFile) + check_call(cmd, shell=True) + #save_output(VMC) + except CalledProcessError as err: + logger.error(VMC, cmd) + raise err + + inFile = os.path.join(VMC.runtimeDir, VMC.rdmconfigFile) + outFile = os.path.join(VMC.runtimeDir, VMC.rdmoutputFile) + try: + cmd = ' '.join((VMC.mpiprefix, VMC.executable, inFile)) + cmd = "%s > %s 2>&1" % (cmd, outFile) + check_call(cmd, shell=True) + #save_output(VMC) + except CalledProcessError as err: + logger.error(VMC, cmd) + raise err + + +#def save_output(VMC): +# for i in range(50): +# if os.path.exists(os.path.join(VMC.runtimeDir, "output%02d.dat"%(i))): +# continue +# else: +# import shutil +# shutil.copy2(os.path.join(VMC.runtimeDir, "output.dat"),os.path.join(VMC.runtimeDir, "output%02d.dat"%(i))) +# shutil.copy2(os.path.join(VMC.runtimeDir, "%s/vmc.e"%(VMC.scratchDirectory)), os.path.join(VMC.runtimeDir, "vmc%02d.e"%(i))) +# #print('BM copied into "output%02d.dat"'%(i)) +# #print('BM copied into "vmc%02d.e"'%(i)) +# break + + +def localizeValence(mf, mo_coeff, method="iao"): + if (method == "iao"): + return iao.iao(mf.mol, mo_coeff) + elif (method == "ibo"): + iaos = lo.iao.iao(mf.mol, mo_coeff) + return lo.ibo.ibo(mf.mol, mo_coeff, locmethod='PM', iaos=iaos).kernel() + #return ibo.PM(mf.mol, mo_coeff).kernel() + + #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 == "lowdin"): + return fractional_matrix_power(mf.get_ovlp(mf.mol), -0.5).T + elif (method == "pipek"): + return pipek.PM(mf.mol).kernel(mo_coeff) + elif (method == "boys"): + return boys.Boys(mf.mol).kernel(mo_coeff) + elif (method == "er"): + return edmiston.ER(mf.mol).kernel(mo_coeff) + + + +# can be used for all electron, but not recommended +def bestDetValence(mol, lmo, occ, eri, writeToFile=True): + + # index of the ao contributing the most to an lmo + maxLMOContributers = [ numpy.argmax(numpy.abs(lmo[::,i])) for i in range(lmo.shape[1]) ] + + # end AO index for each atom in ascending order + atomNumAOs = [ i[1][3] - 1 for i in enumerate(mol.aoslice_nr_by_atom()) ] + + lmoSites = [ [] for i in range(mol.natm) ] #lmo's cetered on each atom + for i in enumerate(maxLMOContributers): + lmoSites[numpy.searchsorted(numpy.array(atomNumAOs), i[1])].append(i[0]) + + bestDet = ['0' for i in range(lmo.shape[1])] + def pair(i): + return i*(i+1)//2+i + for i in enumerate(occ): + if eri.ndim == 2: + onSiteIntegrals = [ (j, eri[pair(j),pair(j)]) for (n,j) in enumerate(lmoSites[i[0]]) ] + elif eri.ndim == 1: + onSiteIntegrals = [ (j, eri[pair(pair(j))]) for (n,j) in enumerate(lmoSites[i[0]]) ] + onSiteIntegrals.sort(key = lambda tup : tup[1], reverse=True) + for k in range(i[1][0]): + bestDet[onSiteIntegrals[k][0]] = '2' + for l in range(i[1][1]): + bestDet[onSiteIntegrals[i[1][0] + l][0]] = 'a' + for m in range(i[1][2]): + bestDet[onSiteIntegrals[i[1][0] + i[1][1] + m][0]] = 'b' + + bestDetStr = ' '.join(bestDet) + + if writeToFile: + fileh = open("bestDet", 'w') + fileh.write('1. ' + bestDetStr + '\n') + fileh.close() + + return bestDetStr + +def getGradient(t, E1, V, E2): + nact = E1.shape[0] + gradient =-1. * lib.einsum('Pa,Qa->PQ', t, E1) + gradient +=1. * lib.einsum('Qa,Pa->PQ', t, E1) + gradient +=-1. * lib.einsum('aP,aQ->PQ', t, E1) + gradient +=1. * lib.einsum('aQ,aP->PQ', t, E1) + gradient +=-0.5 * lib.einsum('Pabc,Qabc->PQ', V, E2) + gradient +=-0.5 * lib.einsum('Pabc,abcQ->PQ', V, E2) + gradient +=0.5 * lib.einsum('Qabc,Pabc->PQ', V, E2) + gradient +=0.5 * lib.einsum('Qabc,abcP->PQ', V, E2) + gradient +=-0.5 * lib.einsum('aPbc,Qacb->PQ', V, E2) + gradient +=-0.5 * lib.einsum('aPbc,acbQ->PQ', V, E2) + gradient +=0.5 * lib.einsum('aQbc,Pacb->PQ', V, E2) + gradient +=0.5 * lib.einsum('aQbc,acbP->PQ', V, E2) + ''' + gradient = ( -1.00000) *lib.einsum('Pa,Qa->PQ', h1eff, E1) + gradient += ( 1.00000) *lib.einsum('Qa,Pa->PQ', h1eff, E1) + gradient += ( -1.00000) *lib.einsum('aP,aQ->PQ', h1eff, E1) + gradient += ( 1.00000) *lib.einsum('aQ,aP->PQ', h1eff, E1) + gradient += ( -1.00000) *lib.einsum('Pabc,Qabc->PQ', eri, E2) + gradient += ( -1.00000) *lib.einsum('Pabc,abcQ->PQ', eri, E2) + gradient += ( 1.00000) *lib.einsum('Qabc,Pabc->PQ', eri, E2) + gradient += ( 1.00000) *lib.einsum('Qabc,abcP->PQ', eri, E2) + gradient += ( -1.00000) *lib.einsum('aPbc,Qacb->PQ', eri, E2) + gradient += ( -1.00000) *lib.einsum('aPbc,acbQ->PQ', eri, E2) + gradient += ( 1.00000) *lib.einsum('aQbc,Pacb->PQ', eri, E2) + gradient += ( 1.00000) *lib.einsum('aQbc,acbP->PQ', eri, E2) + ''' + return gradient + +def lowerTriangle(gradient): + nact = gradient.shape[0] + Grad = numpy.zeros(((nact*(nact-1))//2,)) + index = 0 + for p in range(nact): + for q in range(p): + Grad[index] = gradient[p, q] + index+=1 + return Grad + +def fullTriangle(gradient, nact): + Grad = numpy.zeros((nact,nact)) + index = 0 + for p in range(nact): + for q in range(p): + Grad[p,q] = gradient[index] + Grad[q,p] = -gradient[index] + index+=1 + return Grad + +def getHessian(h1eff, E1, eri, E2): + nact = E1.shape[0] + norb = nact + Hessian = 1. * lib.einsum('PR,QS->PQRS', h1eff, E1) + Hessian +=-1. * lib.einsum('PS,QR->PQRS', h1eff, E1) + Hessian +=-1. * lib.einsum('QR,PS->PQRS', h1eff, E1) + Hessian +=1. * lib.einsum('QS,PR->PQRS', h1eff, E1) + Hessian +=1. * lib.einsum('RP,SQ->PQRS', h1eff, E1) + Hessian +=-1. * lib.einsum('RQ,SP->PQRS', h1eff, E1) + Hessian +=-1. * lib.einsum('SP,RQ->PQRS', h1eff, E1) + Hessian +=1. * lib.einsum('SQ,RP->PQRS', h1eff, E1) + Hessian +=-1. * lib.einsum('PR,Qa,Sa->PQRS', numpy.eye(norb), h1eff, E1) + Hessian +=-1. * lib.einsum('PR,aQ,aS->PQRS', numpy.eye(norb), h1eff, E1) + Hessian +=1. * lib.einsum('PS,Qa,Ra->PQRS', numpy.eye(norb), h1eff, E1) + Hessian +=1. * lib.einsum('PS,aQ,aR->PQRS', numpy.eye(norb), h1eff, E1) + Hessian +=1. * lib.einsum('QR,Pa,Sa->PQRS', numpy.eye(norb), h1eff, E1) + Hessian +=1. * lib.einsum('QR,aP,aS->PQRS', numpy.eye(norb), h1eff, E1) + Hessian +=-1. * lib.einsum('QS,Pa,Ra->PQRS', numpy.eye(norb), h1eff, E1) + Hessian +=-1. * lib.einsum('QS,aP,aR->PQRS', numpy.eye(norb), h1eff, E1) + Hessian +=0.5 * lib.einsum('PRab,QSab->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('PRab,QbaS->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('PRab,SabQ->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('PRab,abQS->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('PSab,QRab->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('PSab,QbaR->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('PSab,RabQ->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('PSab,abQR->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('PaRb,QaSb->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('PaRb,SaQb->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('PaSb,QaRb->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('PaSb,RaQb->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('QRab,PSab->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('QRab,PbaS->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('QRab,SabP->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('QRab,abPS->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('QSab,PRab->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('QSab,PbaR->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('QSab,RabP->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('QSab,abPR->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('QaRb,PaSb->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('QaRb,SaPb->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('QaSb,PaRb->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('QaSb,RaPb->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('RPab,QSba->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('RPab,QabS->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('RPab,SbaQ->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('RPab,abSQ->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('RQab,PSba->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('RQab,PabS->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('RQab,SbaP->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('RQab,abSP->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('SPab,QRba->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('SPab,QabR->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('SPab,RbaQ->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('SPab,abRQ->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('SQab,PRba->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('SQab,PabR->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('SQab,RbaP->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('SQab,abRP->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('aPbR,QaSb->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('aPbR,SaQb->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('aPbS,QaRb->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('aPbS,RaQb->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('aQbR,PaSb->PQRS', eri, E2) + Hessian +=-0.5 * lib.einsum('aQbR,SaPb->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('aQbS,PaRb->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('aQbS,RaPb->PQRS', eri, E2) + Hessian +=0.5 * lib.einsum('Pabc,QR,Sabc->PQRS', eri, numpy.eye(norb), E2) + Hessian +=0.5 * lib.einsum('Pabc,QR,abcS->PQRS', eri, numpy.eye(norb), E2) + Hessian +=-0.5 * lib.einsum('Pabc,QS,Rabc->PQRS', eri, numpy.eye(norb), E2) + Hessian +=-0.5 * lib.einsum('Pabc,QS,abcR->PQRS', eri, numpy.eye(norb), E2) + Hessian +=-0.5 * lib.einsum('Qabc,PR,Sabc->PQRS', eri, numpy.eye(norb), E2) + Hessian +=-0.5 * lib.einsum('Qabc,PR,abcS->PQRS', eri, numpy.eye(norb), E2) + Hessian +=0.5 * lib.einsum('Qabc,PS,Rabc->PQRS', eri, numpy.eye(norb), E2) + Hessian +=0.5 * lib.einsum('Qabc,PS,abcR->PQRS', eri, numpy.eye(norb), E2) + Hessian +=0.5 * lib.einsum('aPbc,QR,Sacb->PQRS', eri, numpy.eye(norb), E2) + Hessian +=0.5 * lib.einsum('aPbc,QR,acbS->PQRS', eri, numpy.eye(norb), E2) + Hessian +=-0.5 * lib.einsum('aPbc,QS,Racb->PQRS', eri, numpy.eye(norb), E2) + Hessian +=-0.5 * lib.einsum('aPbc,QS,acbR->PQRS', eri, numpy.eye(norb), E2) + Hessian +=-0.5 * lib.einsum('aQbc,PR,Sacb->PQRS', eri, numpy.eye(norb), E2) + Hessian +=-0.5 * lib.einsum('aQbc,PR,acbS->PQRS', eri, numpy.eye(norb), E2) + Hessian +=0.5 * lib.einsum('aQbc,PS,Racb->PQRS', eri, numpy.eye(norb), E2) + Hessian +=0.5 * lib.einsum('aQbc,PS,acbR->PQRS', eri, numpy.eye(norb), E2) + + Hess = 0.5*(Hessian + lib.einsum('pqrs->rspq', Hessian)) + + npair = (nact*(nact-1))//2 + Hessian = numpy.zeros( (npair,npair)) + for p in range(nact): + for q in range(p): + for r in range(nact): + for s in range(r): + Hessian[ (p*(p-1))//2 + q , (r*(r-1))//2+s] = Hess[p,q,r,s] + return Hessian + +def writeMat(mat, fileName, isComplex): + fileh = open(fileName, 'w') + for i in range(mat.shape[0]): + for j in range(mat.shape[1]): + if (isComplex): + fileh.write('(%16.10e, %16.10e) '%(mat[i,j].real, mat[i,j].imag)) + else: + fileh.write('%16.10e '%(mat[i,j])) + fileh.write('\n') + fileh.close() + +def randomHermitian(nact): + b = numpy.zeros((nact, nact), dtype=complex) + for i in range(nact): + for j in range(nact): + b[i,j] = numpy.random.random() + numpy.random.random()*1j + antiHermi = (b - b.T.conjugate())/2 + return expm(0.01*antiHermi) + +def VMCSCF(mf, ncore, nact, occ=None, frozen = None, loc="iao", proc=None, + stochasticIter = 1000, maxIter = 100, *args, **kwargs): + + mol = mf.mol + mo = mf.mo_coeff + nelec = mol.nelectron - 2 * ncore + mc = mcscf.CASSCF(mf, nact, nelec) + + if loc is not None: + lmo = localizeValence(mf, mo[:, ncore:ncore+nact], loc) + tools.molden.from_mo(mf.mol, 'valenceOrbs.molden', lmo) + + #performing unitary transformation to get rid of symmetries + b = numpy.random.rand(nact,nact) + antiHermi = (b - b.T)/2 + U = expm(0.01*antiHermi) + lmo = lib.einsum('mi, ia->ma', lmo, U) + + h1cas, energy_core = mcscf.casci.h1e_for_cas(mc, mf.mo_coeff, nact, ncore) + mo_core = mc.mo_coeff[:,:ncore] + core_dm = 2 * mo_core.dot(mo_core.T) + corevhf = mc.get_veff(mol, core_dm) + h1eff = lmo.T.dot(mc.get_hcore() + corevhf).dot(lmo) + eri = ao2mo.kernel(mol, lmo) + else: + lmo = numpy.eye(nact) + + b = numpy.random.rand(nact,nact) + antiHermi = (b - b.T)/2 + U = expm(0.01*antiHermi) + lmo = lib.einsum('mi, ia->ma', lmo, U) + + h1eff = mf.get_hcore() + corevhf = 0.*h1eff + eri = mf._eri + energy_core = 0 + if occ is not None: + bestDetValence(mol, lmo, occ, eri, True) + + + #prepare initial guess for the HF orbitals + norb = nact + molA = gto.M() + molA.nelectron = nelec + molA.verbose = 4 + molA.incore_anyway = True + gmf = scf.GHF(molA) + gmf.get_hcore = lambda *args: scipy.linalg.block_diag(h1eff, h1eff) + gmf.get_ovlp = lambda *args: numpy.identity(2*norb) + gmf.energy_nuc = lambda *args: energy_core + gmf._eri = eri + + dm = gmf.get_init_guess() + dm = dm + 2 * numpy.random.rand(2*norb, 2*norb) + gmf.level_shift = 0.1 + gmf.max_cycle = 500 + print(gmf.kernel(dm0 = dm)) + mocoeff = numpy.zeros((2*norb, 2*norb), dtype=complex) + mocoeff = (1.+0j)*gmf.mo_coeff + + #performing unitary transformation to get rid of symmetries + U = randomHermitian(nact) + mocoeff[:,:nact] = numpy.dot(mocoeff[:,:nact], U) + + writeMat(mocoeff, "hf.txt", True) + + + #now start running the orbital optimization + mc.fcisolver = VMC(mf.mol) + mc.fcisolver.stochasticIter = stochasticIter + mc.fcisolver.maxIter = maxIter + + maxMicro, maxMacro = 100, 5 + for i in range(maxMacro): + print ("\nMacroIter: %4d"%i) + #execute VMC + writeIntegralFile(mc.fcisolver, h1eff, eri, nact, nelec, energy_core) + mc.fcisolver.writeConfig(restart= i is not 0, readBestDeterminant= (occ is not None and i is 0)) + + mc.fcisolver.mpiprefix = "mpirun" + if (proc is not None): + mc.fcisolver.mpiprefix = ("mpirun -np %d" %(proc)) + executeVMC(mc.fcisolver) + + #read RDM + E1, E2 = mc.fcisolver.make_rdm12(0, nact, nelec) + E2 = lib.einsum('abcd->acbd', E2) + + #optimize the orbitals and generate new integrals + for j in range(maxMicro): + eri = ao2mo.restore(1, eri, nact) + eri.shape= (nact, nact, nact, nact) + eri = lib.einsum('abcd->acbd', eri) + + gradient = getGradient(h1eff, E1, eri, E2) + Grad = -1.*lowerTriangle(gradient) + + print ("MicorIter: %4d %18.10f %18.10f"% (j, lib.einsum('ij,ij',h1eff, E1) + 0.5*lib.einsum('ijkl,ijkl', eri, E2)+energy_core, numpy.linalg.norm(Grad)), flush=True) + + + Hess = getHessian(h1eff, E1, eri, E2) + Hess += 1.e-2 * numpy.eye(Hess.shape[0]) + step = numpy.linalg.solve(Hess, Grad) + K = fullTriangle(step, nact) + U = expm(-K) + + lmo = lib.einsum('mi, ia->ma', lmo, U) + h1eff = lmo.T.dot(mc.get_hcore() + corevhf).dot(lmo) + + if (loc is not None) : + eri = ao2mo.kernel(mol, lmo) + else: #Hubbard model + eri = ao2mo.incore.full(eri, lmo) + if (numpy.linalg.norm(Grad) < 1.e-4): + break + + + + exit(0) + mc.fcisolver.maxIter = 100 + mf.mo_coeff[:,ncore:ncore+nact] = lmo + mc.mo_coeff = 1.*mf.mo_coeff + mc.internal_rotation = True + + if frozen is not None: + mc.frozen = frozen + + if mc.chkfile == mc._scf._chkfile.name: + # Do not delete chkfile after mcscf + mc.chkfile = tempfile.mktemp(dir=settings.VMCSCRATCHDIR) + if not os.path.exists(settings.VMCSCRATCHDIR): + os.makedirs(settings.VMCSCRATCHDIR) + return mc + + +if __name__ == '__main__': + from pyscf import gto, scf, mcscf, dmrgscf + from pyscf.vmcscf import vmc + + # Initialize benzene molecule + atomstring = ''' + C 0.000517 0.000000 0.000299 + C 0.000517 0.000000 1.394692 + C 1.208097 0.000000 2.091889 + C 2.415677 0.000000 1.394692 + C 2.415677 0.000000 0.000299 + C 1.208097 0.000000 -0.696898 + H -0.939430 0.000000 -0.542380 + H -0.939430 0.000000 1.937371 + H 1.208097 0.000000 3.177246 + H 3.355625 0.000000 1.937371 + H 3.355625 0.000000 -0.542380 + H 1.208097 0.000000 -1.782255 + ''' + mol = gto.M( + atom = atomstring, + unit = 'angstrom', + basis = 'sto-6g', + verbose = 4, + symmetry= 0, + spin = 0) + + # Create HF molecule + mf = scf.RHF(mol) + mf.conv_tol = 1e-9 + mf.scf() + + + ncore, nact = 6, 30 + occ = [] + configC = [[0,4,0], [0,0,4]] # no double occ, 4 up or 4 dn + configH = [[0,1,0], [0,0,1]] # no double occ, 1 up or 1 dn + for i in range(6): + occ.append(configC[i%2]) + for i in range(6): + occ.append(configH[(i+1)%2]) + + + frozen = list(range(0,ncore)) + list (range(36, mf.mo_coeff.shape[0])) + print (frozen) + # Create VMC molecule for just variational opt. + # Active spaces chosen to reflect valence active space. + mch = vmc.VMCSCF(mf, ncore, nact, occ=occ, loc="ibo",\ + frozen = frozen) + mch.fcisolver.stochasticIter = 400 + mch.fcisolver.maxIter = 50 + e_noPT = mch.mc2step()[0] + diff --git a/pyscf/x2c/sfx2c1e.py b/pyscf/x2c/sfx2c1e.py index 0dae9b0173..b84c2f7bf8 100644 --- a/pyscf/x2c/sfx2c1e.py +++ b/pyscf/x2c/sfx2c1e.py @@ -228,6 +228,27 @@ def picture_change(self, even_operator=(None, None), odd_operator=None): else: return lib.einsum('pi,xpq,qj->xij', c, pc_mat, c) + #by default h is returned in the contracted basis + #x and r in the uncontracted basis + def get_hxr(self, mol=None, uncontract = True): + if mol is None: mol = self.mol + if (uncontract): + xmol, contr_coeff = self.get_xmol(mol) + else: + xmol, contr_coeff = mol, None + + c = lib.param.LIGHT_SPEED + assert('1E' in self.approx.upper()) + t = xmol.intor_symmetric('int1e_kin') + v = xmol.intor_symmetric('int1e_nuc') + s = xmol.intor_symmetric('int1e_ovlp') + w = xmol.intor_symmetric('int1e_pnucp') + + h1, x, r = x2c._x2c1e_hxrmat(t, v, w, s, c) + if (uncontract): + h1 = reduce(numpy.dot, (contr_coeff.T, h1, contr_coeff)) + return h1, x, r + def get_xmat(self, mol=None): if mol is None: xmol = self.get_xmol(mol)[0] diff --git a/pyscf/x2c/x2c.py b/pyscf/x2c/x2c.py index 9ff1198e98..4cdcf2a877 100644 --- a/pyscf/x2c/x2c.py +++ b/pyscf/x2c/x2c.py @@ -26,6 +26,7 @@ from pyscf.scf import dhf from pyscf.scf import _vhf from pyscf import __config__ +from pyscf.shciscf import socutils LINEAR_DEP_THRESHOLD = 1e-9 @@ -80,6 +81,42 @@ def get_hcore(self, mol=None): t = xmol.intor_symmetric('int1e_spsp_spinor') * .5 v = xmol.intor_symmetric('int1e_nuc_spinor') w = xmol.intor_symmetric('int1e_spnucsp_spinor') + + # an approximate one-electron spin-orbit integral to account for two-electron effect + # by adding an scaling factor + # Q = [0,2,10,28,60,110] for [s,p,d,f,g,h] seperately + # + if 'BOETTGER' in self.approx.upper(): + # first construct the array sqrt(Q(l)/Z) + fudge_factor = numpy.zeros(xmol.nao_2c()) + spinor_labels = xmol.spinor_labels(0) + atom_slices = xmol.offset_2c_by_atom() + for ia in range(xmol.natm): + Z_ia = xmol.atom_charge(ia) + #print(Z_ia) + for iorb in range(atom_slices[ia][2], atom_slices[ia][3]): + label = spinor_labels[iorb] + if 'S' in label[2].upper(): + fudge_factor[iorb] = 0./Z_ia + elif 'P' in label[2].upper(): + #if 2 < Z_ia: + fudge_factor[iorb] = 2./Z_ia + elif 'D' in label[2].upper(): + #if 10 < Z_ia: + fudge_factor[iorb] = 10./Z_ia + elif 'F' in label[2].upper(): + #if 28 < Z_ia: + fudge_factor[iorb] = 28./Z_ia + elif 'G' in label[2].upper(): + #if 60 < Z_ia: + fudge_factor[iorb] = 60./Z_ia + elif 'H' in label[2].upper: + #if 110 < Z_ia: + fudge_factor[iorb] = 110./Z_ia + for iorb in range(xmol.nao_2c()): + for jorb in range(xmol.nao_2c()): + w[iorb, jorb]*=(1.-numpy.sqrt(fudge_factor[iorb]*fudge_factor[jorb])) + if 'ATOM' in self.approx.upper(): atom_slices = xmol.offset_2c_by_atom() n2c = xmol.nao_2c() @@ -109,7 +146,16 @@ def get_hcore(self, mol=None): contr_coeff[0::2,0::2] = contr_coeff_nr contr_coeff[1::2,1::2] = contr_coeff_nr h1 = reduce(numpy.dot, (contr_coeff.T.conj(), h1, contr_coeff)) - return h1 + hso1e = numpy.zeros((nc*2, nc*2), dtype=complex) + if 'AMF' in self.approx.upper(): + # incorporate X2CAMF approximation. + # since 1e SOC terms are already included. + hso1e_atoms = socutils.get_socamf(list(set(xmol.elements)), xmol.basis) + atom_slices = self.mol.aoslice_2c_by_atom() + for ia in range(xmol.natm): + ish0, ish1, c0, c1 = atom_slices[ia] + hso1e[c0:c1,c0:c1] += hso1e_atoms[self.mol.elements[ia]] + return h1+hso1e def _picture_change(self, xmol, even_operator=(None, None), odd_operator=None): '''Picture change for even_operator + odd_operator @@ -320,12 +366,17 @@ def get_init_guess(mol, key='minao'): class X2C_UHF(hf.SCF): + nopen = getattr(__config__, 'scf_dhf_SCF_nopen', 0) + nact = getattr(__config__, 'scf_dhf_SCF_nact', 0) + nclose = getattr(__config__, 'scf_dhf_SCF_nclose', 0) + h1_somf = getattr(__config__, 'scf_dhf_SCF_h1_somf', None) + def __init__(self, mol): hf.SCF.__init__(self, mol) self.with_x2c = X2C(mol) #self.with_x2c.xuncontract = False self._keys = self._keys.union(['with_x2c']) - + def build(self, mol=None): if self.verbose >= logger.WARN: self.check_sanity() @@ -360,6 +411,7 @@ def _eigh(self, h, s): def get_hcore(self, mol=None): if mol is None: mol = self.mol return self.with_x2c.get_hcore(mol) + def get_ovlp(self, mol=None): if mol is None: mol = self.mol @@ -367,10 +419,19 @@ def get_ovlp(self, mol=None): def get_occ(self, mo_energy=None, mo_coeff=None): if mo_energy is None: mo_energy = self.mo_energy + nact = self.nact + nopen = self.nopen mol = self.mol + nclose = mol.nelectron-nact + assert(mol.nelectron==nclose+nact) mo_occ = numpy.zeros_like(mo_energy) - mo_occ[:mol.nelectron] = 1 - if mol.nelectron < len(mo_energy): + if nopen is 0: + mo_occ[:mol.nelectron] = 1 + else: + mo_occ[:nclose]=1 + average_occ=nact/nopen + mo_occ[nclose:nclose+nopen]=average_occ + if nopen+nclose < len(mo_energy): logger.info(self, 'nocc = %d HOMO = %.12g LUMO = %.12g', \ mol.nelectron, mo_energy[mol.nelectron-1], mo_energy[mol.nelectron]) @@ -650,6 +711,16 @@ def _x2c1e_xmatrix(t, v, w, s, c): x = cs.dot(cl.conj().T).dot(m) return x +def _x2cmf_xmatrix(h, s, c): + nao = s.shape[0] // 2 + n2 = nao * 2 + m = s + e, a = scipy.linalg.eigh(h, m) + cl = a[:nao,nao:] + cs = a[nao:,nao:] + x = numpy.linalg.solve(cl.T, cs.T).T # B = XA + return x + def _x2c1e_get_hcore(t, v, w, s, c): nao = s.shape[0] n2 = nao * 2 diff --git a/pyscf/zmcscf/__init__.py b/pyscf/zmcscf/__init__.py new file mode 100644 index 0000000000..34c291e954 --- /dev/null +++ b/pyscf/zmcscf/__init__.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# Copyright 2014-2018 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pyscf.zmcscf import zmc2step +from pyscf.zmcscf import gzcasci +from pyscf.mcscf import casci +from pyscf.mcscf import addons + + +def ZCASSCF(mf_or_mol, ncas, nelecas, ncore=None, frozen=None): + from pyscf import gto + from pyscf import scf + if isinstance(mf_or_mol, gto.Mole): + mf = scf.RHF(mf_or_mol) + else: + mf = mf_or_mol + + if isinstance(mf, scf.uhf.UHF): + mf = scf.addons.convert_to_rhf(mf) +# if getattr(mf, 'with_df', None): +# return DFCASSCF(mf, ncas, nelecas, ncore, frozen) + +# if mf.mol.symmetry: +# mc = mc1step_symm.CASSCF(mf, ncas, nelecas, ncore, frozen) +# else: + mc = zmc2step.ZCASSCF(mf, ncas, nelecas, ncore, frozen) + return mc + + diff --git a/pyscf/zmcscf/gzcasci.py b/pyscf/zmcscf/gzcasci.py new file mode 100644 index 0000000000..8d3cf41eba --- /dev/null +++ b/pyscf/zmcscf/gzcasci.py @@ -0,0 +1,1005 @@ +#!/usr/bin/env python +# Copyright 2014-2020 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author: Qiming Sun +# + +import sys +import time +from functools import reduce +import numpy +from pyscf.lib import logger +from pyscf import gto, scf, ao2mo, fci, x2c, symm, __config__, lib +from pyscf.mcscf import addons +import h5py + +WITH_META_LOWDIN = getattr(__config__, 'mcscf_analyze_with_meta_lowdin', True) +LARGE_CI_TOL = getattr(__config__, 'mcscf_analyze_large_ci_tol', 0.1) +PENALTY = getattr(__config__, 'mcscf_casci_CASCI_fix_spin_shift', 0.2) + +if sys.version_info < (3,): + RANGE_TYPE = list +else: + RANGE_TYPE = range + + +def h1e_for_cas(casci, mo_coeff=None, ncas=None, ncore=None): + '''CAS sapce one-electron hamiltonian + + Args: + casci : a CASSCF/CASCI object or RHF object + + Returns: + A tuple, the first is the effective one-electron hamiltonian defined in CAS space, + the second is the electronic energy from core. + ''' + if mo_coeff is None: mo_coeff = casci.mo_coeff + if ncas is None: ncas = casci.ncas + if ncore is None: ncore = casci.ncore + mo_core = mo_coeff[:,:ncore] + mo_cas = mo_coeff[:,ncore:ncore+ncas] + + hcore = casci.get_hcore() + energy_core = casci.energy_nuc() + if mo_core.size == 0: + corevhf = 0 + else: + core_dm = numpy.dot(mo_core, mo_core.T.conj()) + corevhf = casci.get_veff(casci.mol, core_dm) + + j,k = casci._scf.get_jk(casci.mol, core_dm) + #print (energy_core, numpy.einsum('ij,ji', core_dm, hcore), numpy.einsum('ij,ji', j, corevhf), numpy.einsum('ij,ji', k, corevhf) ) + energy_core += numpy.einsum('ij,ji', core_dm, hcore) + energy_core += numpy.einsum('ij,ji', core_dm, corevhf) * .5 + h1eff = reduce(numpy.dot, (mo_cas.T.conj(), hcore+corevhf, mo_cas)) + return h1eff, energy_core + +def analyze(casscf, mo_coeff=None, ci=None, verbose=None, + large_ci_tol=LARGE_CI_TOL, with_meta_lowdin=WITH_META_LOWDIN, + **kwargs): + from pyscf.lo import orth + from pyscf.tools import dump_mat + from pyscf.mcscf import addons + log = logger.new_logger(casscf, verbose) + + if mo_coeff is None: mo_coeff = casscf.mo_coeff + if ci is None: ci = casscf.ci + nelecas = casscf.nelecas + ncas = casscf.ncas + ncore = casscf.ncore + nocc = ncore + ncas + mocore = mo_coeff[:,:ncore] + mocas = mo_coeff[:,ncore:nocc] + + label = casscf.mol.ao_labels() + if (isinstance(ci, (list, tuple, RANGE_TYPE)) and + not isinstance(casscf.fcisolver, addons.StateAverageFCISolver)): + log.warn('Mulitple states found in CASCI/CASSCF solver. Density ' + 'matrix of the first state is generated in .analyze() function.') + civec = ci[0] + else: + civec = ci + if getattr(casscf.fcisolver, 'make_rdm1s', None): + casdm1a, casdm1b = casscf.fcisolver.make_rdm1s(civec, ncas, nelecas) + casdm1 = casdm1a + casdm1b + dm1b = numpy.dot(mocore, mocore.T) + dm1a = dm1b + reduce(numpy.dot, (mocas, casdm1a, mocas.T)) + dm1b += reduce(numpy.dot, (mocas, casdm1b, mocas.T)) + dm1 = dm1a + dm1b + if log.verbose >= logger.DEBUG2: + log.info('alpha density matrix (on AO)') + dump_mat.dump_tri(log.stdout, dm1a, label, **kwargs) + log.info('beta density matrix (on AO)') + dump_mat.dump_tri(log.stdout, dm1b, label, **kwargs) + else: + casdm1 = casscf.fcisolver.make_rdm1(civec, ncas, nelecas) + dm1a =(numpy.dot(mocore, mocore.T) * 2 + + reduce(numpy.dot, (mocas, casdm1, mocas.T))) + dm1b = None + dm1 = dm1a + + if log.verbose >= logger.INFO: + ovlp_ao = casscf._scf.get_ovlp() + # note the last two args of ._eig for mc1step_symm + occ, ucas = casscf._eig(-casdm1, ncore, nocc) + log.info('Natural occ %s', str(-occ)) + mocas = numpy.dot(mocas, ucas) + if with_meta_lowdin: + log.info('Natural orbital (expansion on meta-Lowdin AOs) in CAS space') + orth_coeff = orth.orth_ao(casscf.mol, 'meta_lowdin', s=ovlp_ao) + mocas = reduce(numpy.dot, (orth_coeff.T, ovlp_ao, mocas)) + else: + log.info('Natural orbital (expansion on AOs) in CAS space') + dump_mat.dump_rec(log.stdout, mocas, label, start=1, **kwargs) + if log.verbose >= logger.DEBUG2: + if not casscf.natorb: + log.debug2('NOTE: mc.mo_coeff in active space is different to ' + 'the natural orbital coefficients printed in above.') + if with_meta_lowdin: + c = reduce(numpy.dot, (orth_coeff.T, ovlp_ao, mo_coeff)) + log.debug2('MCSCF orbital (expansion on meta-Lowdin AOs)') + else: + c = mo_coeff + log.debug2('MCSCF orbital (expansion on AOs)') + dump_mat.dump_rec(log.stdout, c, label, start=1, **kwargs) + + if casscf._scf.mo_coeff is not None: + addons.map2hf(casscf, casscf._scf.mo_coeff) + + if (ci is not None and + (getattr(casscf.fcisolver, 'large_ci', None) or + getattr(casscf.fcisolver, 'states_large_ci', None))): + log.info('** Largest CI components **') + if isinstance(ci, (list, tuple, RANGE_TYPE)): + if hasattr(casscf.fcisolver, 'states_large_ci'): + # defined in state_average_mix_ mcscf object + res = casscf.fcisolver.states_large_ci(ci, casscf.ncas, casscf.nelecas, + large_ci_tol, return_strs=False) + else: + res = [casscf.fcisolver.large_ci(civec, casscf.ncas, casscf.nelecas, + large_ci_tol, return_strs=False) + for civec in ci] + for i, civec in enumerate(ci): + log.info(' [alpha occ-orbitals] [beta occ-orbitals] state %-3d CI coefficient', i) + for c,ia,ib in res[i]: + log.info(' %-20s %-30s %.12f', ia, ib, c) + else: + log.info(' [alpha occ-orbitals] [beta occ-orbitals] CI coefficient') + res = casscf.fcisolver.large_ci(ci, casscf.ncas, casscf.nelecas, + large_ci_tol, return_strs=False) + for c,ia,ib in res: + log.info(' %-20s %-30s %.12f', ia, ib, c) + + if with_meta_lowdin: + casscf._scf.mulliken_meta(casscf.mol, dm1, s=ovlp_ao, verbose=log) + else: + casscf._scf.mulliken_pop(casscf.mol, dm1, s=ovlp_ao, verbose=log) + return dm1a, dm1b + +def get_fock(mc, mo_coeff=None, ci=None, eris=None, casdm1=None, verbose=None): + r''' + Effective one-electron Fock matrix in AO representation + f = \sum_{pq} E_{pq} F_{pq} + F_{pq} = h_{pq} + \sum_{rs} [(pq|rs)-(ps|rq)] DM_{sr} + + Ref. + Theor. Chim. Acta., 91, 31 + Chem. Phys. 48, 157 + + For state-average CASCI/CASSCF object, the effective fock matrix is based + on the state-average density matrix. To obtain Fock matrix of a specific + state in the state-average calculations, you can pass "casdm1" of the + specific state to this function. + + Args: + mc: a CASSCF/CASCI object or RHF object + + Kwargs: + mo_coeff (ndarray): orbitals that span the core, active and external + space. + ci (ndarray): CI coefficients (or objects to represent the CI + wavefunctions in DMRG/QMC-MCSCF calculations). + eris: Integrals for the MCSCF object. Input this object to reduce the + overhead of computing integrals. It can be generated by + :func:`mc.ao2mo` method. + casdm1 (ndarray): 1-particle density matrix in active space. Without + input casdm1, the density matrix is computed with the input ci + coefficients/object. If neither ci nor casdm1 were given, density + matrix is computed by :func:`mc.fcisolver.make_rdm1` method. For + state-average CASCI/CASCF calculation, this results in the + effective Fock matrix based on the state-average density matrix. + To obtain the effective Fock matrix for one particular state, you + can assign the density matrix of that state to the kwarg casdm1. + + Returns: + Fock matrix + ''' + + if ci is None: ci = mc.ci + if mo_coeff is None: mo_coeff = mc.mo_coeff + nmo = mo_coeff.shape[1] + ncore = mc.ncore + ncas = mc.ncas + nocc = ncore + ncas + nelecas = mc.nelecas + + if casdm1 is None: + casdm1 = mc.fcisolver.make_rdm1(ci, ncas, nelecas) + if getattr(eris, 'ppaa', None) is not None: + vj = numpy.empty((nmo,nmo)) + vk = numpy.empty((nmo,nmo)) + for i in range(nmo): + vj[i] = numpy.einsum('ij,qij->q', casdm1, eris.ppaa[i]) + vk[i] = numpy.einsum('ij,iqj->q', casdm1, eris.papa[i]) + mo_inv = numpy.dot(mo_coeff.T, mc._scf.get_ovlp()) + fock =(mc.get_hcore() + + reduce(numpy.dot, (mo_inv.T, eris.vhf_c+vj-vk*.5, mo_inv))) + else: + dm_core = numpy.dot(mo_coeff[:,:ncore]*2, mo_coeff[:,:ncore].T) + mocas = mo_coeff[:,ncore:nocc] + dm = dm_core + reduce(numpy.dot, (mocas, casdm1, mocas.T)) + vj, vk = mc._scf.get_jk(mc.mol, dm) + fock = mc.get_hcore() + vj-vk*.5 + return fock + +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 + 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) + + 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) + + 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].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(' %d %d %12.8f', + ncore+i+1, j+1, s[i,j]) + return mo_coeff1, fcivec, mo_occ + +def canonicalize(mc, mo_coeff=None, ci=None, eris=None, sort=False, + cas_natorb=False, casdm1=None, verbose=logger.NOTE, + with_meta_lowdin=WITH_META_LOWDIN): + '''Canonicalized CASCI/CASSCF orbitals of effecitive Fock matrix and + update CI coefficients accordingly. + + Effective Fock matrix is built with one-particle density matrix (see + also :func:`mcscf.casci.get_fock`). For state-average CASCI/CASSCF object, + the canonicalized orbitals are based on the state-average density matrix. + To obtain canonicalized orbitals for an individual state, you need to pass + "casdm1" of the specific state to this function. + + Args: + mc: a CASSCF/CASCI object or RHF object + + Kwargs: + mo_coeff (ndarray): orbitals that span the core, active and external + space. + ci (ndarray): CI coefficients (or objects to represent the CI + wavefunctions in DMRG/QMC-MCSCF calculations). + eris: Integrals for the MCSCF object. Input this object to reduce the + overhead of computing integrals. It can be generated by + :func:`mc.ao2mo` method. + sort (bool): Whether the canonicalized orbitals are sorted based on + the orbital energy (diagonal part of the effective Fock matrix) + within each subspace (core, active, external). If point group + symmetry is not available in the system, orbitals are always + sorted. When point group symmetry is available, sort=False will + preserve the symmetry label of input orbitals and only sort the + orbitals in each symmetry sector. sort=True will reorder all + orbitals over all symmetry sectors in each subspace and the + symmetry labels may be changed. + cas_natorb (bool): Whether to transform active orbitals to natual + orbitals. If enabled, the output orbitals in active space are + transformed to natural orbitals and CI coefficients are updated + accordingly. + casdm1 (ndarray): 1-particle density matrix in active space. This + density matrix is used to build effective fock matrix. Without + input casdm1, the density matrix is computed with the input ci + coefficients/object. If neither ci nor casdm1 were given, density + matrix is computed by :func:`mc.fcisolver.make_rdm1` method. For + state-average CASCI/CASCF calculation, this results in a set of + canonicalized orbitals of state-average effective Fock matrix. + To canonicalize the orbitals for one particular state, you can + assign the density matrix of that state to the kwarg casdm1. + + Returns: + A tuple, (natural orbitals, CI coefficients, orbital energies) + The orbital energies are the diagonal terms of effective Fock matrix. + ''' + from pyscf.lo import orth + from pyscf.tools import dump_mat + from pyscf.mcscf import addons + log = logger.new_logger(mc, verbose) + + if mo_coeff is None: mo_coeff = mc.mo_coeff + if ci is None: ci = mc.ci + if casdm1 is None: + if (isinstance(ci, (list, tuple, RANGE_TYPE)) and + not isinstance(mc.fcisolver, addons.StateAverageFCISolver)): + log.warn('Mulitple states found in CASCI solver. First state is ' + 'used to compute the natural orbitals in active space.') + casdm1 = mc.fcisolver.make_rdm1(ci[0], mc.ncas, mc.nelecas) + else: + casdm1 = mc.fcisolver.make_rdm1(ci, mc.ncas, mc.nelecas) + + ncore = mc.ncore + nocc = ncore + mc.ncas + nmo = mo_coeff.shape[1] + fock_ao = mc.get_fock(mo_coeff, ci, eris, casdm1, verbose) + if cas_natorb: + mo_coeff1, ci, occ = mc.cas_natorb(mo_coeff, ci, eris, sort, casdm1, + verbose, with_meta_lowdin) + else: +# Keep the active space unchanged by default. The rotation in active space +# may cause problem for external CI solver eg DMRG. + mo_coeff1 = mo_coeff.copy() + log.info('Density matrix diagonal elements %s', casdm1.diagonal()) + + fock = reduce(numpy.dot, (mo_coeff1.T, fock_ao, mo_coeff1)) + mo_energy = fock.diagonal().copy() + + mask = numpy.ones(nmo, dtype=bool) + frozen = getattr(mc, 'frozen', None) + if frozen is not None: + if isinstance(frozen, (int, numpy.integer)): + mask[:frozen] = False + else: + mask[frozen] = False + core_idx = numpy.where(mask[:ncore])[0] + vir_idx = numpy.where(mask[nocc:])[0] + nocc + + if getattr(mo_coeff, 'orbsym', None) is not None: + orbsym = mo_coeff.orbsym + else: + orbsym = numpy.zeros(nmo, dtype=int) + + if len(core_idx) > 0: + # note the last two args of ._eig for mc1step_symm + # mc._eig function is called to handle symmetry adapated fock + w, c1 = mc._eig(fock[core_idx[:,None],core_idx], 0, ncore, + orbsym[core_idx]) + if sort: + idx = numpy.argsort(w.round(9), kind='mergesort') + w = w[idx] + c1 = c1[:,idx] + orbsym[core_idx] = orbsym[core_idx][idx] + mo_coeff1[:,core_idx] = numpy.dot(mo_coeff1[:,core_idx], c1) + mo_energy[core_idx] = w + + if len(vir_idx) > 0: + w, c1 = mc._eig(fock[vir_idx[:,None],vir_idx], nocc, nmo, + orbsym[vir_idx]) + if sort: + idx = numpy.argsort(w.round(9), kind='mergesort') + w = w[idx] + c1 = c1[:,idx] + orbsym[vir_idx] = orbsym[vir_idx][idx] + mo_coeff1[:,vir_idx] = numpy.dot(mo_coeff1[:,vir_idx], c1) + mo_energy[vir_idx] = w + + if getattr(mo_coeff, 'orbsym', None) is not None: + mo_coeff1 = lib.tag_array(mo_coeff1, orbsym=orbsym) + + if log.verbose >= logger.DEBUG: + for i in range(nmo): + log.debug('i = %d = %12.8f', i+1, mo_energy[i]) +# still return ci coefficients, in case the canonicalization funciton changed +# cas orbitals, the ci coefficients should also be updated. + return mo_coeff1, ci, mo_energy + + +def kernel(casci, mo_coeff=None, ci0=None, verbose=logger.NOTE): + '''CASCI solver + ''' + if mo_coeff is None: mo_coeff = casci.mo_coeff + log = logger.new_logger(casci, verbose) + t0 = (time.clock(), time.time()) + log.debug('Start CASCI') + + ncas = casci.ncas + nelecas = casci.nelecas + + # 2e + eri_cas = casci.get_h2eff(mo_coeff) + t1 = log.timer('integral transformation to CAS space', *t0) + + # 1e + h1eff, energy_core = casci.get_h1eff(mo_coeff) + log.debug('core energy = %.15g', energy_core) + t1 = log.timer('effective h1e in CAS space', *t1) + + if h1eff.shape[0] != ncas: + raise RuntimeError('Active space size error. nmo=%d ncore=%d ncas=%d' % + (mo_coeff.shape[1], casci.ncore, ncas)) + + # FCI + max_memory = max(400, casci.max_memory-lib.current_memory()[0]) + e_tot, fcivec = casci.fcisolver.kernel(h1eff, eri_cas, ncas, nelecas, + ci0=ci0, verbose=log, + max_memory=max_memory, + ecore=energy_core) + + t1 = log.timer('FCI solver', *t1) + e_cas = e_tot - energy_core + return e_tot, e_cas, fcivec + + +def as_scanner(mc): + '''Generating a scanner for CASCI PES. + + The returned solver is a function. This function requires one argument + "mol" as input and returns total CASCI energy. + + The solver will automatically use the results of last calculation as the + initial guess of the new calculation. All parameters of MCSCF object + are automatically applied in the solver. + + Note scanner has side effects. It may change many underlying objects + (_scf, with_df, with_x2c, ...) during calculation. + + Examples: + + >>> from pyscf import gto, scf, mcscf + >>> mf = scf.RHF(gto.Mole().set(verbose=0)) + >>> mc_scanner = mcscf.CASCI(mf, 4, 4).as_scanner() + >>> mc_scanner(gto.M(atom='N 0 0 0; N 0 0 1.1')) + >>> mc_scanner(gto.M(atom='N 0 0 0; N 0 0 1.5')) + ''' + if isinstance(mc, lib.SinglePointScanner): + return mc + + logger.info(mc, 'Create scanner for %s', mc.__class__) + + class CASCI_Scanner(mc.__class__, lib.SinglePointScanner): + def __init__(self, mc): + self.__dict__.update(mc.__dict__) + self._scf = mc._scf.as_scanner() + def __call__(self, mol_or_geom, mo_coeff=None, ci0=None): + if isinstance(mol_or_geom, gto.Mole): + mol = mol_or_geom + else: + mol = self.mol.set_geom_(mol_or_geom, inplace=False) + + # These properties can be updated when calling mf_scanner(mol) if + # they are shared with mc._scf. In certain scenario the properties + # may be created for mc separately, e.g. when mcscf.approx_hessian is + # called. For safety, the code below explicitly resets these + # properties. + for key in ('with_df', 'with_x2c', 'with_solvent', 'with_dftd3'): + sub_mod = getattr(self, key, None) + if sub_mod: + sub_mod.reset(mol) + + if mo_coeff is None: + mf_scanner = self._scf + mf_scanner(mol) + mo_coeff = mf_scanner.mo_coeff + if ci0 is None: + ci0 = self.ci + self.mol = mol + e_tot = self.kernel(mo_coeff, ci0)[0] + return e_tot + return CASCI_Scanner(mc) + + +class GZCASCI(lib.StreamObject): + '''CASCI + + Args: + mf_or_mol : SCF object or Mole object + SCF or Mole to define the problem size. + ncas : int + Number of active orbitals. + nelecas : int + Number of electrons in active space. + + Kwargs: + ncore : int + Number of core orbitals. If not presented, this + parameter can be automatically determined. + + Attributes: + verbose : int + Print level. Default value equals to :class:`Mole.verbose`. + max_memory : float or int + Allowed memory in MB. Default value equals to :class:`Mole.max_memory`. + ncas : int + Active space size. + nelecas : int + Active electrons + ncore : int + Core electron number. + natorb : bool + Whether to transform natural orbital in active space. Be cautious + of this parameter when CASCI/CASSCF are combined with DMRG solver + or selected CI solver because DMRG and selected CI are not invariant + to the rotation in active space. + False by default. + canonicalization : bool + Whether to canonicalize orbitals. Note that canonicalization does + not change the orbitals in active space by default. It only + diagonalizes core and external space of the general Fock matirx. + To get the natural orbitals in active space, attribute natorb + need to be enabled. + True by default. + sorting_mo_energy : bool + Whether to sort the orbitals based on the diagonal elements of the + general Fock matrix. Default is False. + fcisolver : an instance of :class:`FCISolver` + The pyscf.fci module provides several FCISolver for different scenario. Generally, + fci.direct_spin1.FCISolver can be used for all RHF-CASSCF. However, a proper FCISolver + can provide better performance and better numerical stability. One can either use + :func:`fci.solver` function to pick the FCISolver by the program or manually assigen + the FCISolver to this attribute, e.g. + + >>> from pyscf import fci + >>> mc = mcscf.CASSCF(mf, 4, 4) + >>> mc.fcisolver = fci.solver(mol, singlet=True) + >>> mc.fcisolver = fci.direct_spin1.FCISolver(mol) + + You can control FCISolver by setting e.g.:: + + >>> mc.fcisolver.max_cycle = 30 + >>> mc.fcisolver.conv_tol = 1e-7 + + For more details of the parameter for FCISolver, See :mod:`fci`. + + Saved results + + e_tot : float + Total MCSCF energy (electronic energy plus nuclear repulsion) + e_cas : float + CAS space FCI energy + ci : ndarray + CAS space FCI coefficients + mo_coeff : ndarray + When canonicalization is specified, the orbitals are canonical + orbitals which make the general Fock matrix (Fock operator on top + of MCSCF 1-particle density matrix) diagonalized within each + subspace (core, active, external). If natorb (natural orbitals in + active space) is specified, the active segment of the mo_coeff is + natural orbitls. + mo_energy : ndarray + Diagonal elements of general Fock matrix (in mo_coeff + representation). + + Examples: + + >>> from pyscf import gto, scf, mcscf + >>> mol = gto.M(atom='N 0 0 0; N 0 0 1', basis='ccpvdz', verbose=0) + >>> mf = scf.RHF(mol) + >>> mf.scf() + >>> mc = mcscf.CASCI(mf, 6, 6) + >>> mc.kernel()[0] + -108.980200816243354 + ''' + + natorb = getattr(__config__, 'zmcscf_gzcasci_CASCI_natorb', False) + canonicalization = getattr(__config__, 'zmcscf_gzcasci_CASCI_canonicalization', False) + sorting_mo_energy = getattr(__config__, 'zmcscf_gzcasci_CASCI_sorting_mo_energy', False) + + def __init__(self, mf_or_mol, ncas, nelecas, ncore=None): + if isinstance(mf_or_mol, gto.Mole): + mf = scf.X2C(mf_or_mol) + elif isinstance(mf_or_mol, x2c.x2c.X2C) or isinstance(mf_or_mol, x2c.x2c.X2C_UHF): + mf = mf_or_mol + else: + print("we need the mean field object to be of GHF type") + + mol = mf.mol + self.mol = mol + self._scf = mf + self.verbose = mol.verbose + self.stdout = mol.stdout + self.max_memory = mf.max_memory + self.ncas = ncas + if isinstance(nelecas, (int, numpy.integer)): + nelecb = (nelecas-mol.spin)//2 + neleca = nelecas - nelecb + self.nelecas = (neleca, nelecb) + else: + self.nelecas = (nelecas[0],nelecas[1]) + self.ncore = ncore + self.fcisolver = fci.solver(mol, symm=False) +# CI solver parameters are set in fcisolver object + self.fcisolver.max_cycle = getattr(__config__, + 'mcscf_casci_CASCI_fcisolver_max_cycle', 200) + self.fcisolver.conv_tol = getattr(__config__, + 'mcscf_casci_CASCI_fcisolver_conv_tol', 1e-8) + +################################################## +# don't modify the following attributes, they are not input options + self.e_tot = 0 + self.e_cas = None + self.ci = None + self.mo_coeff = (1.+0j)*mf.mo_coeff #complex orbitals + self.mo_energy = mf.mo_energy + self.converged = False + + keys = set(('natorb', 'canonicalization', 'sorting_mo_energy')) + self._keys = set(self.__dict__.keys()).union(keys) + + @property + def ncore(self): + if self._ncore is None: + ncorelec = self.mol.nelectron - sum(self.nelecas) + return ncorelec + else: + return self._ncore + @ncore.setter + def ncore(self, x): + assert(x is None or isinstance(x, (int))) + self._ncore = x + + def dump_flags(self, verbose=None): + log = logger.new_logger(self, verbose) + log.info('') + log.info('******** CASCI flags ********') + ncore = self.ncore + ncas = self.ncas + nvir = self.mo_coeff.shape[1] - ncore - ncas + log.info('CAS (%de+%de, %do), ncore = %d, nvir = %d', \ + self.nelecas[0], self.nelecas[1], ncas, ncore, nvir) + assert(self.ncas > 0) + log.info('natorb = %s', self.natorb) + log.info('canonicalization = %s', self.canonicalization) + log.info('sorting_mo_energy = %s', self.sorting_mo_energy) + log.info('max_memory %d (MB)', self.max_memory) + if getattr(self.fcisolver, 'dump_flags', None): + self.fcisolver.dump_flags(log.verbose) + if self.mo_coeff is None: + log.error('Orbitals for CASCI are not specified. The relevant SCF ' + 'object may not be initialized.') + + if (getattr(self._scf, 'with_solvent', None) and + not getattr(self, 'with_solvent', None)): + log.warn('''Solvent model %s was found at SCF level but not applied to the CASCI object. +The SCF solvent model will not be applied to the current CASCI calculation. +To enable the solvent model for CASCI, the following code needs to be called + from pyscf import solvent + mc = mcscf.CASCI(...) + mc = solvent.ddCOSMO(mc) +''', + self._scf.with_solvent.__class__) + return self + + def reset(self, mol=None): + if mol is not None: + self.mol = mol + self.fcisolver.mol = mol + self._scf.reset(mol) + return self + + def energy_nuc(self): + return self._scf.energy_nuc() + + def get_hcore(self, mol=None): + return self._scf.get_hcore(mol) + + def get_veff(self, mol=None, dm=None, hermi=1): + if mol is None: mol = self.mol + if dm is None: + mocore = self.mo_coeff[:,:self.ncore] + dm = numpy.dot(mocore, mocore.T.conj()) + + j,k = self._scf.get_jk(mol, dm) + return j - k + + + def _eig(self, h, *args): + return scf.hf.eig(h, None) + + def get_h2cas(self, mo_coeff=None): + '''Computing active space two-particle Hamiltonian. + + Note It is different to get_h2eff when df.approx_hessian is applied, + in which get_h2eff function returns the DF integrals while get_h2cas + returns the regular 2-electron integrals. + ''' + return GZCASCI.ao2mo(self,mo_coeff) + + def get_h2eff(self, mo_coeff=None): + '''Computing active space two-particle Hamiltonian. + + Note It is different to get_h2cas when df.approx_hessian is applied. + in which get_h2eff function returns the DF integrals while get_h2cas + returns the regular 2-electron integrals. + ''' + return self.ao2mo(mo_coeff) + + ####THIS SHOULD BE CHECKED### + def ao2mo(self, mo_coeff=None): + ncore = self.ncore + ncas = self.ncas + nocc = ncore + ncas + nao = self.mo_coeff.shape[0]//2 + if mo_coeff is None: + ncore = self.ncore + mo_coeff = self.mo_coeff + + #eri_ao_sp = mol.intor('int2e_spinor', aosym='s1') + eri_cas = ao2mo.kernel(self.mol, mo_coeff[:,ncore:nocc], intor='int2e_spinor') + return eri_cas + + get_h1cas = h1e_for_cas = h1e_for_cas + + def get_h1eff(self, mo_coeff=None, ncas=None, ncore=None): + return self.h1e_for_cas(mo_coeff, ncas, ncore) + get_h1eff.__doc__ = h1e_for_cas.__doc__ + + def casci(self, mo_coeff=None, ci0=None, verbose=None): + return self.kernel(mo_coeff, ci0, verbose) + + def kernel(self, mo_coeff=None, ci0=None, verbose=None): + ''' + Returns: + Five elements, they are + total energy, + active space CI energy, + the active space FCI wavefunction coefficients or DMRG wavefunction ID, + the MCSCF canonical orbital coefficients, + the MCSCF canonical orbital coefficients. + + They are attributes of mcscf object, which can be accessed by + .e_tot, .e_cas, .ci, .mo_coeff, .mo_energy + ''' + if mo_coeff is None: + mo_coeff = self.mo_coeff + else: + self.mo_coeff = mo_coeff + if ci0 is None: + ci0 = self.ci + log = logger.new_logger(self, verbose) + + if self.verbose >= logger.WARN: + self.check_sanity() + self.dump_flags(log) + + self.e_tot, self.e_cas, self.ci = \ + kernel(self, mo_coeff, ci0=ci0, verbose=log) + + if self.canonicalization: + self.canonicalize_(mo_coeff, self.ci, + sort=self.sorting_mo_energy, + cas_natorb=self.natorb, verbose=log) + + if getattr(self.fcisolver, 'converged', None) is not None: + self.converged = numpy.all(self.fcisolver.converged) + if self.converged: + log.info('CASCI converged') + else: + log.info('CASCI not converged') + else: + self.converged = True + self._finalize() + return self.e_tot, self.e_cas, self.ci, self.mo_coeff, self.mo_energy + + def _finalize(self): + log = logger.Logger(self.stdout, self.verbose) + if log.verbose >= logger.NOTE and getattr(self.fcisolver, 'spin_square', None): + if isinstance(self.e_cas, (float, numpy.number)): + ss = self.fcisolver.spin_square(self.ci, self.ncas, self.nelecas) + log.note('CASCI E = %.15g E(CI) = %.15g S^2 = %.7f', + self.e_tot, self.e_cas, ss[0]) + else: + for i, e in enumerate(self.e_cas): + ss = self.fcisolver.spin_square(self.ci[i], self.ncas, self.nelecas) + log.note('CASCI state %d E = %.15g E(CI) = %.15g S^2 = %.7f', + i, self.e_tot[i], e, ss[0]) + else: + if isinstance(self.e_cas, (float, numpy.number)): + log.note('CASCI E = %.15g E(CI) = %.15g', self.e_tot, self.e_cas) + else: + for i, e in enumerate(self.e_cas): + log.note('CASCI state %d E = %.15g E(CI) = %.15g', + i, self.e_tot[i], e) + return self + + as_scanner = as_scanner + + @lib.with_doc(cas_natorb.__doc__) + def cas_natorb(self, mo_coeff=None, ci=None, eris=None, sort=False, + casdm1=None, verbose=None, with_meta_lowdin=WITH_META_LOWDIN): + return cas_natorb(self, mo_coeff, ci, eris, sort, casdm1, verbose, + with_meta_lowdin) + @lib.with_doc(cas_natorb.__doc__) + def cas_natorb_(self, mo_coeff=None, ci=None, eris=None, sort=False, + casdm1=None, verbose=None, with_meta_lowdin=WITH_META_LOWDIN): + self.mo_coeff, self.ci, occ = cas_natorb(self, mo_coeff, ci, eris, + sort, casdm1, verbose) + return self.mo_coeff, self.ci, occ + + def get_fock(self, mo_coeff=None, ci=None, eris=None, casdm1=None, + verbose=None): + return get_fock(self, mo_coeff, ci, eris, casdm1, verbose) + + canonicalize = canonicalize + @lib.with_doc(canonicalize.__doc__) + def canonicalize_(self, mo_coeff=None, ci=None, eris=None, sort=False, + cas_natorb=False, casdm1=None, verbose=None, + with_meta_lowdin=WITH_META_LOWDIN): + self.mo_coeff, ci, self.mo_energy = \ + canonicalize(self, mo_coeff, ci, eris, + sort, cas_natorb, casdm1, verbose, with_meta_lowdin) + if cas_natorb: # When active space is changed, the ci solution needs to be updated + self.ci = ci + return self.mo_coeff, ci, self.mo_energy + + analyze = analyze + + @lib.with_doc(addons.sort_mo.__doc__) + def sort_mo(self, caslst, mo_coeff=None, base=1): + if mo_coeff is None: mo_coeff = self.mo_coeff + return addons.sort_mo(self, mo_coeff, caslst, base) + + @lib.with_doc(addons.state_average.__doc__) + def state_average_(self, weights=(0.5,0.5)): + addons.state_average_(self, weights) + return self + @lib.with_doc(addons.state_average.__doc__) + def state_average(self, weights=(0.5,0.5)): + return addons.state_average(self, weights) + + @lib.with_doc(addons.state_specific_.__doc__) + def state_specific_(self, state=1): + addons.state_specific(self, state) + return self + + def make_rdm1(self, mo_coeff=None, ci=None, ncas=None, nelecas=None, + ncore=None, **kwargs): + '''One-particle density matrix in AO representation + ''' + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ncas is None: ncas = self.ncas + if nelecas is None: nelecas = self.nelecas + if ncore is None: ncore = self.ncore + + casdm1 = self.fcisolver.make_rdm1(ci, ncas, nelecas) + mocore = mo_coeff[:,:ncore] + mocas = mo_coeff[:,ncore:ncore+ncas] + dm1 = numpy.dot(mocore, mocore.T.conj()) * 2 + dm1 = dm1 + reduce(numpy.dot, (mocas, casdm1, mocas.T.conj())) + return dm1 + + + def density_fit(self, auxbasis=None, with_df=None): + from pyscf.mcscf import df + return df.density_fit(self, auxbasis, with_df) + + #####THIS NEEDS TO BE CHANGED TO X2C### + def sfx2c1e(self): + from pyscf.x2c import sfx2c1e + self._scf = sfx2c1e.sfx2c1e(self._scf).run() + self.mo_coeff = self._scf.mo_coeff + self.mo_energy = self._scf.mo_energy + return self + x2c = x2c1e = sfx2c1e + + def nuc_grad_method(self): + from pyscf.grad import casci + return casci.Gradients(self) + + + +if __name__ == '__main__': + from pyscf import mcscf + mol = gto.Mole() + mol.verbose = 4 + mol.output = None#"out_h2o" + mol.atom = [ + ['O', ( 0., 0. , 0. )], + ['H', ( 0., -0.757, 0.587)], + ['H', ( 0., 0.757 , 0.587)],] + + mol.basis = {'H': 'sto-3g', + 'O': 'sto-3g',} + mol.build() + + m = scf.X2C(mol) + ehf = m.kernel() + + mc = GZCASCI(m, 4, 4) + from pyscf.shciscf import shci + mc.fcisolver = shci.SHCI(mol, maxM=1.e-3) + mc.natorb = 1 + emc = mc.kernel()[0] + print(ehf, emc, emc-ehf) + #-75.9577817425 -75.9624554777 -0.00467373522233 + print(emc+75.9624554777) diff --git a/pyscf/zmcscf/zmc2step.py b/pyscf/zmcscf/zmc2step.py new file mode 100644 index 0000000000..0aba37eb8b --- /dev/null +++ b/pyscf/zmcscf/zmc2step.py @@ -0,0 +1,1035 @@ +#!/usr/bin/env python +# Copyright 2014-2018 The PySCF Developers. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author: Qiming Sun +# + +import copy, scipy, time, numpy +from functools import reduce +import pyscf.lib.logger as logger +from pyscf.zmcscf import gzcasci, zmc_ao2mo +from pyscf.shciscf import shci +#from pyscf.mcscf import mc1step +#from pyscf.mcscf import casci +from pyscf import __config__, lib, ao2mo +#from pyscf.mcscf.casci import get_fock, cas_natorb, canonicalize +from pyscf.mcscf import mc_ao2mo +from pyscf.mcscf import chkfile +from scipy.linalg import expm as expmat +from scipy.sparse.linalg import LinearOperator, minres, gmres +from pyscf.soscf import ciah +import pyscf.df +from pyscf import mcscf + +def bracket(A,B): + return numpy.dot(A,B)-numpy.dot(B,A) +def derivOfExp(a,dA, maxT=50): + fact = 1.0 + deriv = 1.*dA + bra = 1.*dA + for i in range(maxT): + bra = bracket(a, bra) + fact *= -1./(i+2.) + deriv += fact*bra + + return deriv + +def arnoldi(A, Q, H, k): + Q[:,k+1] = A(Q[:,k]) + for i in range(k+1): + H[i, k] = numpy.dot(Q[:,i], Q[:,k+1]) + Q[:,k+1] = Q[:,k+1] - H[i,k] * Q[:,i] + + H[k+1,k] = numpy.linalg.norm(Q[:,k+1]) + Q[:,k+1] = Q[:,k+1]/H[k+1,k] + +def GMRES(A, b, x, max_iter=20, conv=1.e-5): + from numpy.linalg import norm + m, n = max_iter, b.shape[0] + + r = b - A(x) + b_norm, r_norm = norm(b), norm(r) + error = r_norm/b_norm + + e1 = numpy.zeros((m+1,)) + e1[0] = r_norm + + Q, H = numpy.zeros((n,m)), numpy.zeros((m+1,m)) + Q[:,0] = r/r_norm + beta = r_norm * e1 + + for k in range(max_iter-1): + arnoldi(A, Q, H, k) + + result = numpy.linalg.lstsq(H, e1, rcond=None)[0] + err = norm(numpy.dot(H,result)-e1) + #print (k, err) + x = numpy.dot(Q, result) + + #print (norm(A(x)-b)) + return x, err + + +def conjugateGradient(A, b, x, max_iter=10, conv=1.e-4): + r = b - A(x) + p = 1*r + rsold = numpy.dot(r,r) + + for i in range(max_iter): + Ap = A(p) + alpha = rsold / numpy.dot(p, Ap) + x += alpha * p + r -= alpha * Ap + rsnew = numpy.dot(r,r) + if (rsnew)**0.5 < conv: + break + p = r + (rsnew / rsold) * p + print ("cg, ", i, rsnew**0.5) + if (i == 5): + #reset it + r = b - A(x) + p = 1*r + rsnew = numpy.dot(r,r) + rsold = rsnew + return x, rsnew**0.5 +def _sqrt(a, tol=1e-14): + e, v = numpy.linalg.eigh(a) + idx = e > tol + return numpy.dot(v[:,idx]*numpy.sqrt(e[idx]), v[:,idx].T.conj()) + +def get_jk_df(cderi, mo_coeff,dm=None, with_j=True, with_k=True): + norb = mo_coeff.shape[1] + mo = mo_coeff + + if dm is None: + dm_ao = numpy.dot(mo, mo.T.conj()) + else: + dm_sqrt = _sqrt(dm) + dm_ao = reduce(numpy.dot, (mo.conj(), dm, mo.T)).conj() + mo = lib.einsum('ij,jk->ik', mo_coeff, dm_sqrt) + + if (with_j): + j1 = lib.einsum('Lij,ji->L', cderi, dm_ao) + j = lib.einsum('Lij,L->ij', cderi, j1) + if (with_k): + cderi_half_trans = lib.einsum('pij,jk->pik', cderi, mo) + k = lib.einsum('pik,pkj->ij', cderi_half_trans, cderi_half_trans.transpose(0,2,1).conj()) + return j,k + ''' + if (with_j): + j1 = lib.einsum('Lij,ji->L', cderi, dm) + j = lib.einsum('Lij,L->ij', cderi, j1) + if (with_k): + kint = lib.einsum('Lij,ja->Lia', cderi, dm) + k = lib.einsum('Laj,Lia->ij', cderi, kint) + return j,k + ''' +def kernel(casscf, mo_coeff, tol=1e-7, conv_tol_grad=None, + ci0=None, callback=None, verbose=None, dump_chk=True): + if verbose is None: + verbose = casscf.verbose + if callback is None: + callback = casscf.callback + + log = logger.Logger(casscf.stdout, verbose) + cput0 = (time.clock(), time.time()) + log.debug('Start 2-step ZCASSCF') + + mo = mo_coeff + nmo = mo.shape[1] + ncore = casscf.ncore + ncas = casscf.ncas + nocc = ncore + ncas + + eris = None + + e_tot, e_cas, fcivec = casscf.casci(mo, ci0, eris, log, locals()) + + if ncas == nmo and not casscf.internal_rotation: + if casscf.canonicalization: + log.debug('CASSCF canonicalization') + mo, fcivec, mo_energy = casscf.canonicalize(mo, fcivec, eris, + casscf.sorting_mo_energy, + casscf.natorb, verbose=log) + else: + mo_energy = None + return True, e_tot, e_cas, fcivec, mo, mo_energy + + if conv_tol_grad is None: + conv_tol_grad = numpy.sqrt(tol) + logger.info(casscf, 'Set conv_tol_grad to %g', conv_tol_grad) + conv_tol_ddm = conv_tol_grad * 3 + conv = False + de, elast = e_tot, e_tot + totmicro = totinner = 0 + casdm1 = 0 + r0 = None + + t2m = t1m = log.timer('Initializing 2-step CASSCF', *cput0) + imacro = 0 + while not conv and imacro < casscf.max_cycle_macro: + imacro += 1 + njk = 0 + t3m = t2m + casdm1_old = casdm1 + casdm1, casdm2 = casscf.fcisolver.make_rdm12Frombin(fcivec, ncas, casscf.nelecas) + norm_ddm = numpy.linalg.norm(casdm1 - casdm1_old) + t3m = log.timer('update CAS DM', *t3m) + + max_cycle_micro = casscf.micro_cycle_scheduler(locals()) + #max_stepsize = casscf.max_stepsize_scheduler(locals()) + + + mo, gorb, njk, norm_gorb0 = casscf.optimizeOrbs(mo, lambda:casdm1, lambda:casdm2, imacro <= 2, + eris, r0, conv_tol_grad*0.3, log) + norm_gorb = numpy.linalg.norm(gorb) + + totinner += njk + + e_tot, e_cas, fcivec = casscf.casci(mo, fcivec, eris, log, locals()) + log.timer('CASCI solver', *t3m) + t2m = t1m = log.timer('macro iter %d'%imacro, *t1m) + + de, elast = e_tot - elast, e_tot + if (abs(de) < tol and + norm_gorb < conv_tol_grad and norm_ddm < conv_tol_ddm): + conv = True + else: + elast = e_tot + + ###FIX THIS### + #if dump_chk: + #casscf.dump_chk(locals()) + + if callable(callback): + callback(locals()) + + if conv: + log.info('2-step CASSCF converged in %d macro (%d JK %d micro) steps', + imacro, totinner, totmicro) + else: + log.info('2-step CASSCF not converged, %d macro (%d JK %d micro) steps', + imacro, totinner, totmicro) + + if casscf.canonicalization: + log.info('CASSCF canonicalization') + mo, fcivec, mo_energy = \ + casscf.canonicalize(mo, fcivec, eris, casscf.sorting_mo_energy, + casscf.natorb, casdm1, log) + if casscf.natorb and dump_chk: # dump_chk may save casdm1 + occ, ucas = casscf._eig(-casdm1, ncore, nocc) + casdm1 = numpy.diag(-occ) + + if dump_chk: + casscf.dump_chk(locals()) + + log.timer('2-step CASSCF', *cput0) + return conv, e_tot, e_cas, fcivec, mo, mo_energy + +class ZCASSCF(gzcasci.GZCASCI): + __doc__ = gzcasci.GZCASCI.__doc__ + '''CASSCF + + Extra attributes for CASSCF: + + conv_tol : float + Converge threshold. Default is 1e-7 + conv_tol_grad : float + Converge threshold for CI gradients and orbital rotation gradients. + Default is 1e-4 + max_cycle_macro : int + Max number of macro iterations. Default is 50. + + internal_rotation: bool. + if the CI solver is not FCI then active-active rotations are not redundant. + Default(True) + chkfile : str + Checkpoint file to save the intermediate orbitals during the CASSCF optimization. + Default is the checkpoint file of mean field object. + callback : function(envs_dict) => None + callback function takes one dict as the argument which is + generated by the builtin function :func:`locals`, so that the + callback function can access all local variables in the current + envrionment. + + Saved results + + e_tot : float + Total MCSCF energy (electronic energy plus nuclear repulsion) + e_cas : float + CAS space FCI energy + ci : ndarray + CAS space FCI coefficients + mo_coeff : ndarray (MxM, but the number of active variables is BB are just first N(=ncore+nact) columns) + Optimized CASSCF orbitals coefficients. When canonicalization is + specified, the returned orbitals make the general Fock matrix + (Fock operator on top of MCSCF 1-particle density matrix) + diagonalized within each subspace (core, active, external). + If natorb (natural orbitals in active space) is specified, + the active segment of the mo_coeff is natural orbitls. + mo_energy : ndarray + Diagonal elements of general Fock matrix (in mo_coeff + representation). + + Examples: + ********CHANGE THIS EXAMPLE*********** + >>> from pyscf import gto, scf, mcscf + >>> mol = gto.M(atom='N 0 0 0; N 0 0 1', basis='ccpvdz', verbose=0) + >>> mf = scf.RHF(mol) + >>> mf.scf() + >>> mc = mcscf.CASSCF(mf, 6, 6) + >>> mc.kernel()[0] + -109.044401882238134 + ''' + +# the max orbital rotation and CI increment, prefer small step size + max_cycle_macro = getattr(__config__, 'mcscf_mc1step_CASSCF_max_cycle_macro', 50) + max_cycle_micro = getattr(__config__, 'mcscf_mc1step_CASSCF_max_cycle_micro', 4) + conv_tol = getattr(__config__, 'zmcscf_zmc2step_ZCASSCF_conv_tol', 1e-7) + conv_tol_grad = getattr(__config__, 'mcscf_zmc2step_ZCASSCF_conv_tol_grad', None) + + ah_level_shift = getattr(__config__, 'mcscf_mc1step_CASSCF_ah_level_shift', 1e-8) + ah_conv_tol = getattr(__config__, 'mcscf_mc1step_CASSCF_ah_conv_tol', 1e-12) + ah_max_cycle = getattr(__config__, 'mcscf_mc1step_CASSCF_ah_max_cycle', 30) + ah_lindep = getattr(__config__, 'mcscf_mc1step_CASSCF_ah_lindep', 1e-14) +# * ah_start_tol and ah_start_cycle control the start point to use AH step. +# In function rotate_orb_cc, the orbital rotation is carried out with the +# approximate aug_hessian step after a few davidson updates of the AH eigen +# problem. Reducing ah_start_tol or increasing ah_start_cycle will delay +# the start point of orbital rotation. +# * We can do early ah_start since it only affect the first few iterations. +# The start tol will be reduced when approach the convergence point. +# * Be careful with the SYMMETRY BROKEN caused by ah_start_tol/ah_start_cycle. +# ah_start_tol/ah_start_cycle actually approximates the hessian to reduce +# the J/K evaluation required by AH. When the system symmetry is higher +# than the one given by mol.symmetry/mol.groupname, symmetry broken might +# occur due to this approximation, e.g. with the default ah_start_tol, +# C2 (16o, 8e) under D2h symmetry might break the degeneracy between +# pi_x, pi_y orbitals since pi_x, pi_y belong to different irreps. It can +# be fixed by increasing the accuracy of AH solver, e.g. +# ah_start_tol = 1e-8; ah_conv_tol = 1e-10 +# * Classic AH can be simulated by setting eg +# ah_start_tol = 1e-7 +# max_stepsize = 1.5 +# ah_grad_trust_region = 1e6 +# ah_grad_trust_region allow gradients being increased in AH optimization + ah_start_tol = getattr(__config__, 'mcscf_mc1step_CASSCF_ah_start_tol', 2.5) + ah_start_cycle = getattr(__config__, 'mcscf_mc1step_CASSCF_ah_start_cycle', 3) + ah_grad_trust_region = getattr(__config__, 'mcscf_mc1step_CASSCF_ah_grad_trust_region', 3.0) + internal_rotation = getattr(__config__, 'zmcscf_zmc2step_ZCASSCF_internal_rotation', True) + kf_interval = getattr(__config__, 'mcscf_mc1step_CASSCF_kf_interval', 4) + kf_trust_region = getattr(__config__, 'mcscf_mc1step_CASSCF_kf_trust_region', 3.0) + + ao2mo_level = getattr(__config__, 'zmcscf_zmc2step_ZCASSCF_ao2mo_level', 2) + natorb = getattr(__config__, 'zmcscf_zmc2step_ZCASSCF_natorb', False) + canonicalization = getattr(__config__, 'zmcscf_zmc2step_ZCASSCF_canonicalization', True) + sorting_mo_energy = getattr(__config__, 'zmcscf_zmc2step_ZCASSCF_sorting_mo_energy', False) + + def __init__(self, mf_or_mol, ncas, nelecas, auxbasis = 'weigend+etb', ncore=None, frozen=None): + gzcasci.GZCASCI.__init__(self, mf_or_mol, ncas, nelecas, ncore) + self.frozen = frozen + + self.callback = None + self.chkfile = self._scf.chkfile + + self.fcisolver.max_cycle = getattr(__config__, + 'zmcscf_zmc2step_ZCASSCF_fcisolver_max_cycle', 50) + self.fcisolver.conv_tol = getattr(__config__, + 'zmcscf_zmc2step_ZCASSCF_fcisolver_conv_tol', 1e-8) + +################################################## +# don't modify the following attributes, they are not input options + self.e_tot = None + self.e_cas = None + self.ci = None + self.mo_coeff = self._scf.mo_coeff + self.mo_energy = self._scf.mo_energy + self.converged = False + self._max_stepsize = None + + #calculate the integrals + self.cderi = pyscf.df.r_incore.cholesky_eri(self.mol, auxbasis=auxbasis, int3c='int3c2e_spinor') + self.cderi.shape = (self.cderi.shape[0], self.mo_coeff.shape[0], self.mo_coeff.shape[1]) + print ("shape", self.cderi.shape) + keys = set(('max_cycle_macro', + 'conv_tol', 'conv_tol_grad', + 'bb_conv_tol', 'bb_max_cycle', + 'internal_rotation', + 'fcisolver_max_cycle', + 'fcisolver_conv_tol', 'natorb', 'canonicalization', + 'sorting_mo_energy', 'scale_restoration')) + self._keys = set(self.__dict__.keys()).union(keys) + + def dump_flags(self, verbose=None): + log = logger.new_logger(self, verbose) + log.info('') + log.info('******** %s ********', self.__class__) + ncore = self.ncore + ncas = self.ncas + nvir = self.mo_coeff.shape[1] - ncore - ncas + log.info('CAS (%de+%de, %do), ncore = %d, nvir = %d', \ + self.nelecas[0], self.nelecas[1], ncas, ncore, nvir) + assert(nvir >= 0 and ncore >= 0 and ncas >= 0) + if self.frozen is not None: + log.info('frozen orbitals %s', str(self.frozen)) + log.info('max_cycle_macro = %d', self.max_cycle_macro) + log.info('conv_tol = %g', self.conv_tol) + log.info('conv_tol_grad = %s', self.conv_tol_grad) + log.info('natorb = %s', self.natorb) + log.info('canonicalization = %s', self.canonicalization) + log.info('sorting_mo_energy = %s', self.sorting_mo_energy) + log.info('ao2mo_level = %d', self.ao2mo_level) + log.info('chkfile = %s', self.chkfile) + log.info('max_memory %d MB (current use %d MB)', + self.max_memory, lib.current_memory()[0]) + log.info('internal_rotation = %s', self.internal_rotation) + if getattr(self.fcisolver, 'dump_flags', None): + self.fcisolver.dump_flags(self.verbose) + if self.mo_coeff is None: + log.error('Orbitals for CASCI are not specified. The relevant SCF ' + 'object may not be initialized.') + + if (getattr(self._scf, 'with_solvent', None) and + not getattr(self, 'with_solvent', None)): + log.warn('''Solvent model %s was found at SCF level but not applied to the CASSCF object. +The SCF solvent model will not be applied to the current CASSCF calculation. +To enable the solvent model for CASSCF, the following code needs to be called + from pyscf import solvent + mc = mcscf.CASSCF(...) + mc = solvent.ddCOSMO(mc) +''', + self._scf.with_solvent.__class__) + return self + + + def kernel(self, mo_coeff=None, ci0=None, callback=None, _kern=kernel): + ''' + Returns: + Five elements, they are + total energy, + active space CI energy, + the active space FCI wavefunction coefficients or DMRG wavefunction ID, + the MCSCF canonical orbital coefficients, + the MCSCF canonical orbital coefficients. + + They are attributes of mcscf object, which can be accessed by + .e_tot, .e_cas, .ci, .mo_coeff, .mo_energy + ''' + if mo_coeff is None: + mo_coeff = self.mo_coeff + else: # overwrite self.mo_coeff because it is needed in many methods of this class + self.mo_coeff = mo_coeff + if callback is None: callback = self.callback + + if self.verbose >= logger.WARN: + self.check_sanity() + self.dump_flags() + + self.converged, self.e_tot, self.e_cas, self.ci, \ + self.mo_coeff, self.mo_energy = \ + _kern(self, mo_coeff, + tol=self.conv_tol, conv_tol_grad=self.conv_tol_grad, + ci0=ci0, callback=callback, verbose=self.verbose) + logger.note(self, 'CASSCF energy = %.15g', self.e_tot) + self._finalize() + return self.e_tot, self.e_cas, self.ci, self.mo_coeff, self.mo_energy + + def mc1step(self, mo_coeff=None, ci0=None, callback=None): + return self.kernel(mo_coeff, ci0, callback) + + def mc2step(self, mo_coeff=None, ci0=None, callback=None): + from pyscf.mcscf import mc2step + return self.kernel(mo_coeff, ci0, callback, mc2step.kernel) + + def micro_cycle_scheduler(self, envs): + return self.max_cycle_micro + + #log_norm_ddm = numpy.log(envs['norm_ddm']) + #return max(self.max_cycle_micro, int(self.max_cycle_micro-1-log_norm_ddm)) + + def ao2mo(self, mo_coeff=None, level=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + if level is None: level=self.ao2mo_level + return zmc_ao2mo._ERIS(self, mo_coeff, method='incore', + level=level) + + def casci(self, mo_coeff, ci0=None, eris=None, verbose=None, envs=None): + log = logger.new_logger(self, verbose) + + fcasci = copy.copy(self) + fcasci.ao2mo = self.get_h2cas + + e_tot, e_cas, fcivec = gzcasci.kernel(fcasci, mo_coeff, ci0, log) + if not isinstance(e_cas, (float, numpy.number)): + raise RuntimeError('Multiple roots are detected in fcisolver. ' + 'CASSCF does not know which state to optimize.\n' + 'See also mcscf.state_average or mcscf.state_specific for excited states.') + elif numpy.ndim(e_cas) != 0: + # This is a workaround for external CI solver compatibility. + e_cas = e_cas[0] + + if envs is not None and log.verbose >= logger.INFO: + log.debug('CAS space CI energy = %.15g', e_cas) + + if getattr(self.fcisolver, 'spin_square', None): + ss = self.fcisolver.spin_square(fcivec, self.ncas, self.nelecas) + else: + ss = None + + if 'imicro' in envs: # Within CASSCF iteration + if ss is None: + log.info('macro iter %d (%d JK %d micro), ' + 'CASSCF E = %.15g dE = %.8g', + envs['imacro'], envs['njk'], envs['imicro'], + e_tot, e_tot-envs['elast']) + else: + log.info('macro iter %d (%d JK %d micro), ' + 'CASSCF E = %.15g dE = %.8g S^2 = %.7f', + envs['imacro'], envs['njk'], envs['imicro'], + e_tot, e_tot-envs['elast'], ss[0]) + if 'norm_gci' in envs: + log.info(' |grad[o]|=%5.3g ' + '|grad[c]|= %s |ddm|=%5.3g', + envs['norm_gorb0'], + envs['norm_gci'], envs['norm_ddm']) + else: + log.info(' |grad[o]|=%5.3g |ddm|=%5.3g', + envs['norm_gorb0'], envs['norm_ddm']) + else: # Initialization step + if ss is None: + log.info('CASCI E = %.15g', e_tot) + else: + log.info('CASCI E = %.15g S^2 = %.7f', e_tot, ss[0]) + return e_tot, e_cas, fcivec + + def dump_chk(self, envs): + if not self.chkfile: + return self + + ncore = self.ncore + nocc = ncore + self.ncas + if 'mo' in envs: + mo_coeff = envs['mo'] + else: + mo_coeff = envs['mo_coeff'] + mo_occ = numpy.zeros(mo_coeff.shape[1]) + mo_occ[:ncore] = 2 + if self.natorb: + occ = self._eig(-envs['casdm1'], ncore, nocc)[0] + mo_occ[ncore:nocc] = -occ + else: + mo_occ[ncore:nocc] = envs['casdm1'].diagonal().real +# Note: mo_energy in active space =/= F_{ii} (F is general Fock) + if 'mo_energy' in envs: + mo_energy = envs['mo_energy'] + else: + mo_energy = 'None' + chkfile.dump_mcscf(self, self.chkfile, 'mcscf', envs['e_tot'], + mo_coeff, ncore, self.ncas, mo_occ, + mo_energy, envs['e_cas'], None, envs['casdm1'], + overwrite_mol=False) + return self + + def update_from_chk(self, chkfile=None): + if chkfile is None: chkfile = self.chkfile + self.__dict__.update(lib.chkfile.load(chkfile, 'mcscf')) + return self + update = update_from_chk + + + #Fully uses AO and is currently not efficient because AO + #integrals are assumed to be available cheaply + def calcGradAO(self, mo, casdm1, casdm2): + hcore = self._scf.get_hcore() + nmo, ncore, nact = mo.shape[0], self.ncore, self.ncas + nocc = ncore+nact + + gradC2 = 0*mo + moc = mo[:,:ncore] + moa = mo[:,ncore:nocc] + + #ecore + dmcas = reduce(numpy.dot, (moa.conj(), casdm1(), moa.T)).conj() + dmcore = numpy.dot(moc, moc.conj().T) + + jc,kc = self._scf.get_jk(self.cderi, dm=dmcore) + ja,ka = self._scf.get_jk(self.cderi, dm=dmcas) + + gradC2[:,:ncore] = numpy.dot( (hcore + (jc-kc)+ja-ka) , moc) + + gradC2[:,ncore:nocc] = reduce(numpy.dot, ( (hcore + jc - kc) , moa, casdm1().T)) + + ###THIS IS THE BIT WE NEED + ''' + eri_ao_sp = mol.intor('int2e_spinor', aosym='s1') + j1 = lib.einsum('wxyz, wp->pxyz', eri_ao_sp, moa.conj()) + jaapp = lib.einsum('pxyz, xq->pqyz', j1, moa) + jaaap = lib.einsum('pqyz, zs->pqys', jaapp, moa) + gradC2[:,ncore:nocc] += lib.einsum('pqys,prqs->yr', jaaap, casdm2()) + ''' + ####### FOR GRADIENT + + ###THIS BIT WILL BE EXPENSIVE + eripaaa = numpy.zeros((nmo, nact, nact,nact), dtype = complex) + for i in range(nact): + for j in range(i+1): + dm = lib.einsum('x,y->xy',moa[:,i], moa[:,j].conj()) + j1 = self._scf.get_j(self.mol, dm = dm, hermi=0) + j1 = numpy.triu(j1) + j1 = j1 + j1.T - numpy.diag(numpy.diag(j1)) + eripaaa[:,:,i,j] = numpy.dot(j1,moa) + if (i != j): + eripaaa[:,:,j,i] = numpy.dot(j1.conj().T,moa) + gradC2[:,ncore:nocc] += lib.einsum('ypqr,sqpr->ys', eripaaa, casdm2()) + + gradC2 = numpy.dot(mo.conj().T, gradC2) + return 2*gradC2 + + + def calcGradDF(self, mo, casdm1, casdm2): + #return self.calcGrad(mo, casdm1, casdm2) + hcore = self._scf.get_hcore() + + nmo, ncore, nact = mo.shape[0], self.ncore, self.ncas + nocc = ncore+nact + nuc_energy = self.energy_nuc() + + Grad = numpy.zeros((nmo, nmo), dtype=complex) + moc, moa = mo[:,:ncore], mo[:,ncore:nocc] + dmcore = numpy.dot(moc, moc.T.conj()) + dmcas = reduce(numpy.dot, (moa.conj(), casdm1(), moa.T)).conj() + j,k=get_jk_df(self.cderi, moc) + ja,ka=get_jk_df(self.cderi, moa, dm= casdm1()) + + hcore = reduce(numpy.dot, (mo.conj().T, hcore , mo)) + Fc = (hcore + reduce(numpy.dot, (mo.conj().T, (j-k), mo))) + Grad[:,:ncore] = hcore[:,:ncore] + reduce(numpy.dot, (mo.conj().T, j + ja - k - ka, moc)) + + Grad[:,ncore:nocc] = numpy.einsum('sp,qp->sq', Fc[:,ncore:nocc], casdm1()) + + Lrq = lib.einsum('Lxy,ya->Lxa',self.cderi, moa) + Lpq = lib.einsum('Lxa,xb->Lba', Lrq, moa.conj()) + Lrq = lib.einsum('Lxa,xy->Lya', Lrq, mo.conj()) + paaa = lib.einsum('Lxa,Lcd->xacd', Lrq, Lpq) + Grad[:,ncore:nocc]+= lib.einsum('ruvw,tvuw->rt', paaa, casdm2()) + + E = nuc_energy + 0.5*numpy.sum((hcore+Fc).diagonal()[:ncore]) + E += numpy.einsum('tu, tu', Fc[ncore:nocc, ncore:nocc], casdm1()) + E += 0.5*lib.einsum('ruvw,rvuw', paaa[ncore:nocc], casdm2()) + + return 2*Grad, E + + def calcEDF(self, mo, casdm1, casdm2): + hcore = self._scf.get_hcore() + + nmo, ncore, nact = mo.shape[0], self.ncore, self.ncas + nocc = ncore+nact + nuc_energy = self.energy_nuc() + + Grad = numpy.zeros((nmo, nmo), dtype=complex) + moc, moa = mo[:,:ncore], mo[:,ncore:nocc] + dmcore = numpy.dot(moc, moc.T.conj()) + dmcas = reduce(numpy.dot, (moa.conj(), casdm1(), moa.T)).conj() + j,k=get_jk_df(self.cderi, moc) + + hcore = reduce(numpy.dot, (mo.conj().T, hcore , mo)) + Fc = (hcore + reduce(numpy.dot, (mo.conj().T, (j-k), mo))) + E = nuc_energy + 0.5*numpy.sum((hcore+Fc).diagonal()[:ncore]) + E += numpy.einsum('tu, tu', Fc[ncore:nocc, ncore:nocc], casdm1()) + + Lrq = lib.einsum('Lxy,ya->Lxa',self.cderi, moa) + Lpq = lib.einsum('Lxa,xb->Lba', Lrq, moa.conj()) + Lrq = lib.einsum('Lxa,xy->Lya', Lrq, moa.conj()) + paaa = lib.einsum('Lxa,Lcd->xacd', Lrq, Lpq) + E += 0.5*lib.einsum('ruvw,rvuw', paaa, casdm2()) + + return E + + def calcGrad(self, mo, casdm1, casdm2, ERIS=None): + if ERIS is None: ERIS = self.ao2mo(mo,level=2) + hcore = self._scf.get_hcore() + nmo, ncore, nact = mo.shape[0], self.ncore, self.ncas + nocc = ncore+nact + + nuc_energy = self.energy_nuc() + Grad = numpy.zeros((nmo, nmo), dtype=complex) + + moc, moa = mo[:,:ncore], mo[:,ncore:nocc] + dmcore = numpy.dot(moc, moc.T.conj()) + dmcas = reduce(numpy.dot, (moa.conj(), casdm1(), moa.T)).conj() + j,k = self._scf.get_jk(self.mol, dm=dmcore) + ja,ka = self._scf.get_jk(self.mol, dm=dmcas) + + + hcore = reduce(numpy.dot, (mo.conj().T, hcore , mo)) + #print (hcore.diagonal()) + Fc = (hcore + reduce(numpy.dot, (mo.conj().T, (j-k), mo))) + Grad[:,:ncore] = hcore[:,:ncore] + reduce(numpy.dot, (mo.conj().T, j + ja - k - ka, moc)) + + Grad[:,ncore:nocc] = numpy.einsum('sp,qp->sq', Fc[:,ncore:nocc], casdm1()) + Grad[:,ncore:nocc]+= numpy.einsum('ruvw,tvuw->rt', ERIS.paaa, casdm2()) + + Ecore = 0.5*numpy.sum((hcore+Fc).diagonal()[:ncore]) + Ecas1 = numpy.einsum('tu, tu', Fc[ncore:nocc, ncore:nocc], casdm1()) + Ecas2 = 0.5*numpy.einsum('tuvw, tvuw', ERIS.paaa[ncore:nocc], casdm2()) + + + return 2*Grad, (Ecore+Ecas1+Ecas2+nuc_energy).real + + + def calcGradOld(self, mo, casdm1, casdm2, ERIS=None): + if ERIS is None: ERIS = self.ao2mo(mo,level=2) + hcore = self._scf.get_hcore() + nmo, ncore, nact = mo.shape[0], self.ncore, self.ncas + nocc = ncore+nact + + Grad = numpy.zeros((nmo, nmo), dtype=complex) + + moc, moa = mo[:,:ncore], mo[:,ncore:nocc] + dmcore = numpy.dot(moc, moc.T.conj()) + dmcas = reduce(numpy.dot, (moa.conj(), casdm1(), moa.T)).conj() + j,k = self._scf.get_jk(self.mol, dm=dmcore) + ja,ka = self._scf.get_jk(self.mol, dm=dmcas) + + + hcore = reduce(numpy.dot, (mo.conj().T, hcore , mo)) + #print (hcore.diagonal()) + Fc = (hcore + reduce(numpy.dot, (mo.conj().T, (j-k), mo))) + Grad[:,:ncore] = hcore[:,:ncore] + reduce(numpy.dot, (mo.conj().T, j + ja - k - ka, moc)) + + Grad[:,ncore:nocc] = numpy.einsum('sp,qp->sq', Fc[:,ncore:nocc], casdm1()) + Grad[:,ncore:nocc]+= numpy.einsum('ruvw,tvuw->rt', ERIS.paaa, casdm2()) + return 2*Grad + + ###IT IS NOT CORRECT, maybe some day i will fix it### + def calcH(self, mo, x, casdm1, casdm2, ERIS): + hcore = self._scf.get_hcore() + nmo, ncore, nact = mo.shape[0], self.ncore, self.ncas + nocc = ncore+nact + + moc, moa = mo[:,:ncore], mo[:,ncore:nocc] + dmcore = numpy.dot(moc, moc.T.conj()) + dmcas = reduce(numpy.dot, (moa.conj(), casdm1(), moa.T)).conj() + j,k = self._scf.get_jk(self.mol, dm=dmcore) + ja,ka = self._scf.get_jk(self.mol, dm=dmcas) + + hcore = reduce(numpy.dot, (mo.conj().T, hcore , mo)) + Fc = (hcore + reduce(numpy.dot, (mo.conj().T, (j-k), mo))) + Fa = reduce(numpy.dot, (mo.conj().T, (ja-ka), mo)) + + Hrr = numpy.zeros((nmo,nocc, nmo, nocc)) + for i in range(ncore): + Hrr[:,i,:,i] = (Fc+Fa).real + + print (Hrr[2,2,3,3]) + Hrr[:,:ncore,:,:ncore] += \ + (numpy.einsum('xijy->xiyj',ERIS.poop[:,:ncore,:ncore])\ + - numpy.einsum('xyji->xiyj',ERIS.ppoo[:,:,:ncore,:ncore])\ + + numpy.einsum('xiyj->xiyj', ERIS.popo[:,:ncore,:,:ncore])\ + - numpy.einsum('xjyi->xiyj', ERIS.popo[:,:ncore,:,:ncore])).real + + Hrr[:,:ncore, :,ncore:nocc] +=\ + (numpy.einsum('xqyi,pq->yixp',ERIS.popo[:,ncore:nocc,:,:ncore], casdm1())\ + +numpy.einsum('xqiy,pq->yixp',ERIS.poop[:,ncore:nocc,:ncore,:], casdm1())\ + -numpy.einsum('xiyq,pq->yixp',ERIS.popo[:,:ncore,:,ncore:nocc], casdm1())\ + -numpy.einsum('yxpi,pq->yixq',ERIS.ppoo[:,:,ncore:nocc,:ncore], casdm1())).real + + Hrr[:,ncore:nocc,:,ncore:nocc] +=\ + (0*numpy.einsum('xy,pq->xpyq',Fc,casdm1())\ + + 1*numpy.einsum('xyrs,prqs->xpyq',ERIS.ppoo[:,:,ncore:nocc,ncore:nocc].real, casdm2())\ + + 0.5*numpy.einsum('xsry,rpqs->xpyq',ERIS.poop[:,ncore:nocc,ncore:nocc,:], casdm2())\ + + 0.5*numpy.einsum('xsry,prsq->xpyq',ERIS.poop[:,ncore:nocc,ncore:nocc,:], casdm2())\ + + 0.5*numpy.einsum('ysxr,pqrs->xpyq',ERIS.popo[:,ncore:nocc,:,ncore:nocc], casdm2())\ + + 0.5*numpy.einsum('yrxs,srpq->xpyq',ERIS.popo[:,ncore:nocc,:,ncore:nocc].conj(), casdm2())).real + + return 2*Hrr + + def calcE(self, mo, casdm1, casdm2, ERIS=None): + if ERIS is None: ERIS = self.ao2mo(mo, level=1) + hcore = self._scf.get_hcore() + ncore, nact = self.ncore, self.ncas + nocc = ncore+nact + nuc_energy = self.energy_nuc() + + moc = mo[:,:ncore] + dmcore = numpy.dot(moc, moc.conj().T) + j,k = self._scf.get_jk(self.mol, dm=dmcore) + + + hcore = reduce(numpy.dot, (mo.conj().T, hcore , mo)) + Fc = ( hcore + reduce(numpy.dot, (mo.conj().T, (j-k), mo))) + Ecore = 0.5*numpy.sum((hcore+Fc).diagonal()[:ncore]) + + Ecas1 = numpy.einsum('tu, tu', Fc[ncore:nocc, ncore:nocc], casdm1()) + Ecas2 = 0.5*numpy.einsum('tuvw, tvuw', ERIS.aaaa, casdm2()) + + return Ecore+Ecas1+Ecas2+nuc_energy + + + def uniq_var_indices(self, nmo, ncore, ncas, frozen=None): + nocc = ncore + ncas + mask = numpy.zeros((nmo,nmo),dtype=bool) + mask[ncore:nocc,:ncore] = True + mask[nocc:,:nocc] = True + if self.internal_rotation: + mask[ncore:nocc,ncore:nocc][numpy.tril_indices(ncas,-1)] = True + if frozen is not None: + if isinstance(frozen, (int, numpy.integer)): + mask[:frozen] = mask[:,:frozen] = False + else: + frozen = numpy.asarray(frozen) + mask[frozen] = mask[:,frozen] = False + return mask + + def pack_vars(self, mat): + nmo = self.mo_coeff.shape[1] + idx = self.uniq_var_indices(nmo, self.ncore, self.ncas, self.frozen) + + vec1 = mat[idx].real + vec2 = mat[idx].imag + vec = numpy.zeros((2*vec1.shape[0],)) + vec[:vec1.shape[0]] = 1*vec1 + vec[vec1.shape[0]:] = 1*vec2 + return vec + + # to anti symmetric matrix + def unpack_vars(self, v): + nmo = self.mo_coeff.shape[1] + idx = self.uniq_var_indices(nmo, self.ncore, self.ncas, self.frozen) + mat = numpy.zeros((nmo,nmo), dtype=complex) + nvars = v.shape[0]//2 + mat[idx] += v[:nvars] + mat[idx] += 1j*v[nvars:] + return mat - mat.T.conj() + + + def optimizeOrbs(self, mo, casdm1, casdm2, addnoise, eris, r0, conv_tol, log): + #the part of mo that is relevant + nmo, ncore, nact = mo.shape[0], self.ncore, self.ncas + nocc = ncore+nact + + + Grad, Gradnew = 0.*mo, 0.*mo + T = numpy.zeros((nmo, nmo),dtype=complex) + + + def NewtonStep(casscf, mo, nocc, Grad): + Gradnewp, Gradnewm, T, monew = 0.*mo, 0.*mo, 0.*mo, 0.*mo + + nvars = casscf.pack_vars(mo).shape[0] + G = casscf.pack_vars(Grad-Grad.conj().T) + + T, monew = 0*mo, 0.*mo + + Grad0, e = casscf.calcGradDF(mo, casdm1, casdm2) + G0 = casscf.pack_vars(Grad0-Grad0.conj().T) + + def hop(x): + Gradnewp, Gradnewm = 0.*mo, 0.*mo + + eps = 1.e-5 + Kappa = eps*casscf.unpack_vars(x) + + monew = numpy.dot(mo, expmat(Kappa)) + Gradnewp, e = casscf.calcGradDF(monew, casdm1, casdm2) + #print (numpy.linalg.norm(Gradnewp-Grad)) + + f = numpy.dot(Kappa.conj().T, Gradnewp) + h = numpy.dot(Gradnewp, Kappa.conj().T) + Gradnewp = Gradnewp -0.5*(f-h) #- Gtemp.conj().T - 0.5*(f-f.conj().T+h-h.conj().T) + Gradnewp = Gradnewp - Gradnewp.conj().T + Gnewp= casscf.pack_vars(Gradnewp) + + + Hx = (Gnewp - G0)/eps + ''' + monew = numpy.dot(mo, expmat(-Kappa)) + Gradnewm,e = casscf.calcGradDF(monew, casdm1, casdm2) + f = numpy.dot(Kappa.conj().T, Gradnewm) + h = numpy.dot(Gradnewm, Kappa.conj().T) + Gradnewm = Gradnewm -0.5*(f-h) #- Gtemp.conj().T - 0.5*(f-f.conj().T+h-h.conj().T) + Gradnewm = Gradnewm - Gradnewm.conj().T + Gnewm= casscf.pack_vars(Gradnewm) + + Hx = (Gnewp - Gnewm)/2./eps + ''' + #print ("hop") + return Hx + + + + x = 0*G + x0 = 0.*G + x, norm = GMRES(hop, -G, x0) + #x, stat = scipy.sparse.linalg.gmres(hop, -G, x0,maxiter = 10) + return x, norm + + + imicro, nmicro, T, Grad = 0, 5, numpy.zeros_like(mo), 0.*mo + Enew = 0. + #Eold = self.calcE(mo, casdm1, casdm2).real + Grad, Eold = self.calcGrad(mo, casdm1, casdm2) + Eolddf = self.calcEDF(mo, casdm1, casdm2).real + while True: + tau = 1.0 + gnorm = numpy.linalg.norm(Grad-Grad.conj().T) + + #if gradient is converged then exit + if ( gnorm < conv_tol or imicro >= nmicro or tau <= 1.e-2): + return mo, Grad-Grad.conj().T, imicro, gnorm + + + #find the newton step direction + x, gnorm = NewtonStep(self, mo, nocc, Grad) + T = self.unpack_vars(x) + + ###do line search along the AH direction + while tau > 1e-2: + monew = numpy.dot(mo, expmat(tau*(T) )) + Enewdf = self.calcEDF(monew, casdm1, casdm2).real + #print ("line search ", Enewdf, Eolddf) + if (Enewdf < Eolddf or tau/2 <= 1.e-3):# - tau * 1e-4*gnorm): + Grad, Enew = self.calcGrad(monew, casdm1, casdm2) + print ("%d %6.3e %18.12g %13.6e g=%6.2e"\ + %(imicro, tau, Enew, Enew-Eold, gnorm)) + Eold = Enew + Eolddf = Enewdf + mo = 1.*monew + break + tau = tau/2. + imicro += 1 + + + + + + +if __name__ == '__main__': + from pyscf import gto + from pyscf import scf + + + mol = gto.Mole() + mol = gto.M( + atom = [["N" , (0. , 0. , 0.)], + ["N" , (0. , 0. , 1.2)] ], + basis = "cc-pvtz", + verbose = 4) + mf = scf.X2C(mol)#.density_fit(auxbasis='cc-pvtz-jkfit') + #mf.max_cycle = 10 + mf.kernel() + + from pyscf import mcscf, zmcscf + from pyscf.shciscf import shci + mf2c = scf.X2C(mol) #.density_fit(auxbasis='cc-pvtz-jkfit') + #mf2c.get_jk = functools.partial(get_jk, mf2c) + #mf2c.max_cycle = 5 + mf2c.chkfile = "scf.chk" + #mf2c.init_guess = 'chkfile' + mf2c.kernel() + + mc2c = zmcscf.zmc2step.ZCASSCF(mf2c, 16, 10, auxbasis='cc-pvtz-jkfit') + mc2c.fcisolver = shci.SHCI(mol) + mc2c.fcisolver.sweep_epsilon = [1e-5] + mc2c.fcisolver.sweep_iter = [0] + mc2c.kernel() + exit(0) + mol = gto.Mole() + mol.verbose = 4 + mol.memory=20000 + mol.output = None#"out_h2o" + mol.atom = [ + ['H', ( 1.,-1. , 0. )], + ['H', ( 0.,-1. ,-1. )], + ['H', ( 1.,-0.5 ,-1. )], + ['H', ( 0.,-0.5 ,-1. )], + ['H', ( 0.,-0.5 ,-0. )], + ['H', ( 0.,-0. ,-1. )], + ['H', ( 1.,-0.5 , 0. )], + ['H', ( 0., 1. , 1. )], + ] + + #mol.basis = 'cc-pvtz' + mol.basis = '6-31g' + mol.build() + + ''' + m = scf.RHF(mol) + ehf = m.kernel() + print (ehf) + mc = mcscf.CASSCF(m, 6,6) + emc = mc.kernel()[0] + print (emc) + ''' + + m = scf.X2C(mol) + #m = scf.GHF(mol) + ehf = m.kernel() + print (ehf) + #mc = ZCASSCF(m, 16, 8) + mc = ZCASSCF(m, 8, 4) + mc.fcisolver = shci.SHCI(mol) + mc.fcisolver.sweep_epsilon=[1.e-5] + mc.fcisolver.sweep_iter=[0] + mc.fcisolver.davidsonTol = 1.e-6 + + mo = 1.*m.mo_coeff + + numpy.random.seed(5) + noise = numpy.zeros(mo.shape, dtype=complex) + noise = numpy.random.random(mo.shape) +\ + numpy.random.random(mo.shape)*1.j + mo = numpy.dot(mo, expmat(-0.01*(noise - noise.T.conj()))) + mc.kernel(mo) + #import cProfile + #cProfile.run('mc.kernel(mo)') + #exit(0) + + ''' + emc = mc.kernel(mo)[0] + exit(0) + print(ehf, emc, emc-ehf) + print(emc - -3.22013929407) + exit(0) + ''' + mol.atom = [ + ['O', ( 0., 0. , 0. )], + ['H', ( 0., -0.757, 0.587)], + ['H', ( 0., 0.757 , 0.587)],] + mol.basis = {'H': 'cc-pvtz', + 'O': 'cc-pvtz',} + mol.build() + + m = scf.DHF(mol) + ehf = m.scf() + + from pyscf.df import density_fit + m2 = density_fit(m, "cc-pvtz-jkfit") + energy = m2.scf() + print (ehf, energy) + exit(0) + + mc = mc1step.CASSCF(m, 6, 4) + mc.verbose = 5 + mo = m.mo_coeff.copy() + mo[:,2:5] = m.mo_coeff[:,[4,2,3]] + emc = mc.mc2step(mo)[0] + print(ehf, emc, emc-ehf) + #-76.0267656731 -76.0873922924 -0.0606266193028 + print(emc - -76.0873923174, emc - -76.0926176464) + + + + + diff --git a/pyscf/zmcscf/zmc_ao2mo.py b/pyscf/zmcscf/zmc_ao2mo.py new file mode 100644 index 0000000000..01b37b0967 --- /dev/null +++ b/pyscf/zmcscf/zmc_ao2mo.py @@ -0,0 +1,67 @@ +import sys, tempfile, ctypes, time, numpy, h5py +from functools import reduce +from pyscf import lib +from pyscf.lib import logger +from pyscf.mcscf import mc_ao2mo +from pyscf.ao2mo import _ao2mo +from pyscf.ao2mo import outcore, r_outcore +from pyscf import ao2mo + + + + +# level = 1: aaaa +# level = 2: paaa +class _ERIS(object): + def __init__(self, zcasscf, mo, method='incore', level=1): + mol = zcasscf.mol + nao, nmo = mo.shape + ncore = zcasscf.ncore + ncas = zcasscf.ncas + nocc = ncore+ncas + + + mem_incore, mem_outcore, mem_basic = mc_ao2mo._mem_usage(ncore, ncas, nmo) + mem_now = lib.current_memory()[0] + eri = zcasscf._scf._eri + moc, moa, moo = mo[:,:ncore], mo[:,ncore:nocc], mo[:,:nocc] + if (method == 'incore' and eri is not None and + (mem_incore+mem_now < zcasscf.max_memory*.9) or + mol.incore_anyway): + if eri is None: + eri = mol.intor('int2e_spinor', aosym='s8') + + if level == 1: + self.aaaa = ao2mo.kernel(eri, moa, intor="int2e_spinor") + elif level == 2: + self.paaa = ao2mo.kernel(eri, (mo, moa, moa, moa), + intor="int2e_spinor") + elif level == 3: + self.ppoo = ao2mo.kernel(eri, (mo, moa, moa, moa), + intor="int2e_spinor") + self.papa = ao2mo.kernel(eri, (mo, moa, moa, moa), + intor="int2e_spinor") + self.paaa = ao2mo.kernel(eri, (mo, moa, moa, moa), + intor="int2e_spinor") + + else: + import gc + gc.collect() + log = logger.Logger(zcasscf.stdout, zcasscf.verbose) + self.feri = lib.H5TmpFile() + max_memory = max(3000, zcasscf.max_memory*.9-mem_now) + if max_memory < mem_basic: + log.warn('Calculation needs %d MB memory, over CASSCF.max_memory (%d MB) limit', + (mem_basic+mem_now)/.9, zcasscf.max_memory) + if level == 1: + self.aaaa = ao2mo.kernel(mol, moa, intor="int2e_spinor") + else: + self.paaa = ao2mo.kernel(mol, (mo, moa, moa, moa), + intor="int2e_spinor") + + + if (level == 1): + self.aaaa.shape = (ncas, ncas, ncas, ncas) + else: + self.paaa.shape = (nmo, ncas, ncas, ncas) + self.aaaa = self.paaa[ncore:nocc]