From 1a3d502e1bf7b3a444f1bbf3124d02b0f0f81176 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 25 Apr 2024 12:59:07 -0500 Subject: [PATCH 01/87] sitools umat_dot_1frag_ --- my_pyscf/lassi/sitools.py | 45 +++++++++++++++++++++++++ tests/lassi/test_sitools.py | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 tests/lassi/test_sitools.py diff --git a/my_pyscf/lassi/sitools.py b/my_pyscf/lassi/sitools.py index d1834a05..716cc5de 100644 --- a/my_pyscf/lassi/sitools.py +++ b/my_pyscf/lassi/sitools.py @@ -540,6 +540,51 @@ def sivec_vacuum_shuffle (si0, nelec_frs, lroots, nelec_vac=None, state=None): return si1 +def umat_dot_1frag_(target, umat, lroots, ifrag, iroot, axis=0): + '''Apply a unitary transformation for 1 fragment in 1 rootspace to a tensor + whose target axis spans all model states. + + Args: + target: ndarray whose length on axis 'axis' is nstates + The object to which the unitary transformation is to be applied. + Modified in-place. + umat: ndarray of shape (lroots[ifrag,iroot],lroots[ifrag,iroot]) + A unitary matrix; the row axis is contracted + lroots: ndarray of shape (nfrags, nroots) + Number of basis states in each fragment in each rootspace + ifrag: integer + Fragment index for the targeted block + iroot: integer + Rootspace index for the targeted block + + Kwargs: + axis: integer + The axis of target to which umat is to be applied + + Returns: + target: same as input target + After application of unitary transformation''' + nprods = np.product (lroots, axis=0) + offs = [0,] + list (np.cumsum (nprods)) + i, j = offs[iroot], offs[iroot+1] + newaxes = [axis,] + list (range (axis)) + list (range (axis+1, target.ndim)) + oldaxes = list (np.argsort (newaxes)) + target = target.transpose (*newaxes) + target[i:j] = _umat_dot_1frag (target[i:j], umat, lroots[:,iroot], ifrag) + target = target.transpose (*oldaxes) + return target + +def _umat_dot_1frag (target, umat, lroots, ifrag): + old_shape = target.shape + new_shape = tuple (lroots) + old_shape[1:] + target = target.reshape (*new_shape) + newaxes = [ifrag,] + list (range (ifrag)) + list (range (ifrag+1, target.ndim)) + oldaxes = list (np.argsort (newaxes)) + target = target.transpose (*newaxes) + target = np.tensordot (umat.T, target, axes=1).transpose (*oldaxes) + return target.reshape (*old_shape) + + diff --git a/tests/lassi/test_sitools.py b/tests/lassi/test_sitools.py new file mode 100644 index 00000000..2904a215 --- /dev/null +++ b/tests/lassi/test_sitools.py @@ -0,0 +1,65 @@ +import copy +import unittest +import numpy as np +from scipy import linalg +from mrh.my_pyscf.lassi import sitools +from pyscf import lib +from pyscf.scf.addons import canonical_orth_ +import itertools + +def setUpModule(): + pass + +def tearDownModule(): + pass + +def case_umat_dot_1frag (ks, rng, nroots, nfrags, nvecs, lroots): + nstates = np.product (lroots, axis=0).sum () + if nvecs > nstates: return + si = np.empty ((0,0)) + for i in range (100): + si = rng.random (size=(nstates,nvecs)) + si = si @ canonical_orth_(si.conj ().T @ si) + if si.shape[1] == nvecs: break + ks.assertEqual (si.shape[1], nvecs) + si0 = si.copy () + UUsi = si.copy () + siT = si.T.copy () + UUsiT = siT.copy () + for ifrag, iroot in itertools.product (range (nfrags), range (nroots)): + lr = lroots[ifrag,iroot] + umat = linalg.qr (rng.random (size=(lr,lr)))[0] + #umat = umat @ canonical_orth_(umat.conj ().T @ umat) + ks.assertEqual (umat.shape[1], lr) + ks.assertAlmostEqual (lib.fp (umat.conj ().T @ umat), lib.fp (np.eye (lr))) + ks.assertAlmostEqual (lib.fp (umat @ umat.conj ().T), lib.fp (np.eye (lr))) + si = sitools.umat_dot_1frag_(si, umat, lroots, ifrag, iroot, axis=0) + UUsi = sitools.umat_dot_1frag_(UUsi, umat, lroots, ifrag, iroot, axis=0) + UUsi = sitools.umat_dot_1frag_(UUsi, umat.conj ().T, lroots, ifrag, iroot, axis=0) + siT = sitools.umat_dot_1frag_(siT, umat, lroots, ifrag, iroot, axis=1) + UUsiT = sitools.umat_dot_1frag_(UUsiT, umat, lroots, ifrag, iroot, axis=1) + UUsiT = sitools.umat_dot_1frag_(UUsiT, umat.conj ().T, lroots, ifrag, iroot, axis=1) + ovlp = si.conj ().T @ si + ovlpT = siT.conj () @ siT.T + ks.assertAlmostEqual (lib.fp (ovlp), lib.fp (np.eye (nvecs))) + ks.assertAlmostEqual (lib.fp (ovlpT), lib.fp (np.eye (nvecs))) + ks.assertAlmostEqual (lib.fp (UUsi), lib.fp (si0)) + ks.assertAlmostEqual (lib.fp (UUsiT), lib.fp (si0.conj ().T)) + return + +class KnownValues(unittest.TestCase): + + def test_umat_dot_1frag (self): + rng = np.random.default_rng () + nroots, nfrags, nvecs = tuple (rng.integers (1, high=6, size=3)) + for nroots, nfrags, nvecs in itertools.product (range (1,6), repeat=3): + lroots = rng.integers (1, high=10, size=(nfrags,nroots)) + with self.subTest (nroots=nroots, nfrags=nfrags, nvecs=nvecs, lroots=lroots): + case_umat_dot_1frag (self, rng, nroots, nfrags, nvecs, lroots) + + +if __name__ == "__main__": + print("Full Tests for LASSI sitools module functions") + unittest.main() + + From 9cd6ad7aa6af917d302e7a7851eb56dfc32494cc Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 25 Apr 2024 19:08:19 -0500 Subject: [PATCH 02/87] lassi op_o1 linequiv LASSI op_o1 now aggressively identifies linearly-equivalent rootspaces and caches the relevant unitary transformation matrices, in order to reduce the number of interactions computed explicitly, except for in the ContractHamCI class, where I still don't know how exactly to implement this. --- my_pyscf/lassi/op_o1.py | 66 ++++++++++++++++++++++++++++++++++----- my_pyscf/lassi/sitools.py | 9 ++++-- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 937b6a34..21db9706 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -6,6 +6,7 @@ from pyscf.fci.addons import cre_a, cre_b, des_a, des_b from pyscf.fci import cistring from itertools import product, combinations, combinations_with_replacement +from mrh.my_pyscf.lassi.sitools import umat_dot_1frag_, _umat_dot_1frag from mrh.my_pyscf.lassi.citools import get_lroots, get_rootaddr_fragaddr import time @@ -204,10 +205,13 @@ class LSTDMint1 (object): Kwargs: dtype : instance of np.dtype Currently not used + screen_linequiv : logical + Whether to compress data by aggressively identifying linearly equivalent + rootspaces and storing the relevant unitary matrices. ''' def __init__(self, ci, hopping_index, zerop_index, onep_index, norb, nroots, nelec_rs, - rootaddr, fragaddr, idx_frag, dtype=np.float64): + rootaddr, fragaddr, idx_frag, dtype=np.float64, screen_linequiv=True): # TODO: if it actually helps, cache the "linkstr" arrays self.ci = ci self.hopping_index = hopping_index @@ -233,7 +237,7 @@ def __init__(self, ci, hopping_index, zerop_index, onep_index, norb, nroots, nel self.ndetb_r = np.array ([cistring.num_strings (norb, nelec[1]) for nelec in self.nelec_r]) self.ci = [c.reshape (-1,na,nb) for c, na, nb in zip (self.ci, self.ndeta_r, self.ndetb_r)] - self.time_crunch = self._init_crunch_() + self.time_crunch = self._init_crunch_(screen_linequiv) # Exception catching @@ -346,7 +350,7 @@ def set_dm2 (self, i, j, x): else: self.dm2[i][j] = x - def _init_crunch_(self): + def _init_crunch_(self, screen_linequiv): ''' Compute the transition density matrix factors. Returns: @@ -367,13 +371,16 @@ def _init_crunch_(self): # index down to only the unique rootspaces self.root_unique = np.ones (self.nroots, dtype=bool) self.unique_root = np.arange (self.nroots, dtype=int) + self.umat_root = {} for i, j in combinations (range (self.nroots), 2): if not self.root_unique[i]: continue if not self.root_unique[j]: continue if self.nelec_r[i] != self.nelec_r[j]: continue + if lroots[i] != lroots[j]: continue if ci[i].shape != ci[j].shape: continue isequal = False - if np.all (ci[i]==ci[j]): isequal = True + if ci[i] is ci[j]: isequal = True + elif np.all (ci[i]==ci[j]): isequal = True elif np.all (np.abs (ci[i]-ci[j]) < 1e-8): isequal=True else: ci_i = ci[i].reshape (lroots[i],-1) @@ -381,6 +388,11 @@ def _init_crunch_(self): ovlp = ci_i.conj () @ ci_j.T isequal = np.allclose (ovlp.diagonal (), 1, rtol=1e-8, atol=1e-8) + if screen_linequiv and (not isequal): + u, svals, vh = linalg.svd (ovlp) + assert (len (svals) == lroots[i]) + isequal = np.allclose (svals, 1, rtol=1e-8, atol=1e-8) + if isequal: self.umat_root[j] = u @ vh if isequal: self.root_unique[j] = False self.unique_root[j] = i @@ -1345,6 +1357,17 @@ def _add_transpose_(self): self.tdm1s += self.tdm1s.conj ().transpose (1,0,2,4,3) self.tdm2s += self.tdm2s.conj ().transpose (1,0,2,4,3,6,5) + def _umat_linequiv_loop_(self, *args): + for ifrag, inti in enumerate (self.ints): + for iroot, umat in inti.umat_root.items (): + self._umat_linequiv_(ifrag, iroot, umat, *args) + + def _umat_linequiv_(self, ifrag, iroot, umat, *args): + self.tdm1s = umat_dot_1frag_(self.tdm1s, umat, self.lroots, ifrag, iroot, axis=0) + self.tdm1s = umat_dot_1frag_(self.tdm1s, umat, self.lroots, ifrag, iroot, axis=1) + self.tdm2s = umat_dot_1frag_(self.tdm2s, umat, self.lroots, ifrag, iroot, axis=0) + self.tdm2s = umat_dot_1frag_(self.tdm2s, umat, self.lroots, ifrag, iroot, axis=1) + def kernel (self): ''' Main driver method of class. @@ -1361,6 +1384,7 @@ def kernel (self): self.tdm1s = np.zeros ([self.nstates,]*2 + [2,] + [self.norb,]*2, dtype=self.dtype) self.tdm2s = np.zeros ([self.nstates,]*2 + [4,] + [self.norb,]*4, dtype=self.dtype) self._crunch_all_() + self._umat_linequiv_loop_() return self.tdm1s, self.tdm2s, t0 def sprint_profile (self): @@ -1424,6 +1448,16 @@ def _add_transpose_(self): self.ham += self.ham.T self.s2 += self.s2.T + def _umat_linequiv_(self, ifrag, iroot, umat, *args): + ovlp = args[0] + self.ham = umat_dot_1frag_(self.ham, umat, self.lroots, ifrag, iroot, axis=0) + self.ham = umat_dot_1frag_(self.ham, umat, self.lroots, ifrag, iroot, axis=1) + self.s2 = umat_dot_1frag_(self.s2, umat, self.lroots, ifrag, iroot, axis=0) + self.s2 = umat_dot_1frag_(self.s2, umat, self.lroots, ifrag, iroot, axis=1) + ovlp = umat_dot_1frag_(ovlp, umat, self.lroots, ifrag, iroot, axis=0) + ovlp = umat_dot_1frag_(ovlp, umat, self.lroots, ifrag, iroot, axis=1) + return ovlp + def kernel (self): ''' Main driver method of class. @@ -1458,6 +1492,7 @@ def crunch_ovlp (bra_sp, ket_sp): ovlp[i0:i1,j0:j1] = o for bra_sp, ket_sp in self.exc_null: crunch_ovlp (bra_sp, ket_sp) ovlp += ovlp.T + self._umat_linequiv_loop_(ovlp) return self.ham, self.s2, ovlp, t0 class LRRDMint (LSTDMint2): @@ -1481,6 +1516,8 @@ def __init__(self, ints, nlas, hopping_index, lroots, si, mask_bra_space=None, LSTDMint2.__init__(self, ints, nlas, hopping_index, lroots, mask_bra_space=mask_bra_space, mask_ket_space=mask_ket_space, log=log, dtype=dtype) self.nroots_si = si.shape[-1] + si = si.copy () + self._umat_linequiv_loop_(si) self.si_dm = np.stack ([np.dot (si[:,i:i+1],si[:,i:i+1].conj ().T) for i in range (self.nroots_si)], axis=-1) @@ -1496,6 +1533,10 @@ def _add_transpose_(self): self.rdm1s += self.rdm1s.conj ().transpose (0,1,3,2) self.rdm2s += self.rdm2s.conj ().transpose (0,1,3,2,5,4) + def _umat_linequiv_(self, ifrag, iroot, umat, *args): + si = args[0] + return umat_dot_1frag_(si, umat.conj ().T, self.lroots, ifrag, iroot, axis=0) + def kernel (self): ''' Main driver method of class. @@ -1652,6 +1693,10 @@ def _put_Svecs_(self, bra, ket, vecs): def _crunch_all_(self): for row in self.exc_1c: self._loop_lroots_(self._crunch_1c_, *row) + def _umat_linequiv_(self, ifrag, iroot, umat, *args): + # TODO: is this even possible? + pass + def kernel (self): ''' Main driver method of class. @@ -1664,10 +1709,11 @@ def kernel (self): for hci_pabq in hci_r_pabq: hci_pabq[:,:,:,:] = 0.0 self._crunch_all_() + self._umat_linequiv_loop_() return self.hci_fr_pabq, t0 -def make_ints (las, ci, nelec_frs): +def make_ints (las, ci, nelec_frs, screen_linequiv=True): ''' Build fragment-local intermediates (`LSTDMint1`) for LASSI o1 Args: @@ -1678,6 +1724,11 @@ def make_ints (las, ci, nelec_frs): Number of electrons of each spin in each rootspace in each fragment + Kwargs: + screen_linequiv : logical + Whether to compress data by aggressively identifying linearly equivalent + rootspaces and storing the relevant unitary matrices. + Returns: hopping_index : ndarray of ints of shape (nfrags, 2, nroots, nroots) element [i,j,k,l] reports the change of number of electrons of @@ -1694,7 +1745,8 @@ def make_ints (las, ci, nelec_frs): ints = [] for ifrag in range (nfrags): tdmint = LSTDMint1 (ci[ifrag], hopping_index[ifrag], zerop_index, onep_index, nlas[ifrag], - nroots, nelec_frs[ifrag], rootaddr, fragaddr[ifrag], ifrag) + nroots, nelec_frs[ifrag], rootaddr, fragaddr[ifrag], ifrag, + screen_linequiv=screen_linequiv) lib.logger.timer (las, 'LAS-state TDM12s fragment {} intermediate crunching'.format ( ifrag), *tdmint.time_crunch) lib.logger.info (las, 'UNIQUE ROOTSPACES OF FRAG %d: %d/%d', ifrag, np.count_nonzero (tdmint.root_unique), nroots) @@ -1868,7 +1920,7 @@ def contract_ham_ci (las, h1, h2, ci_fr_ket, nelec_frs_ket, ci_fr_bra, nelec_frs nelec_frs = np.append (nelec_frs_ket, nelec_frs_bra, axis=1) # First pass: single-fragment intermediates - hopping_index, ints, lroots = make_ints (las, ci, nelec_frs) + hopping_index, ints, lroots = make_ints (las, ci, nelec_frs, screen_linequiv=False) # Second pass: upper-triangle t0 = (lib.logger.process_clock (), lib.logger.perf_counter ()) diff --git a/my_pyscf/lassi/sitools.py b/my_pyscf/lassi/sitools.py index 716cc5de..0c0b7254 100644 --- a/my_pyscf/lassi/sitools.py +++ b/my_pyscf/lassi/sitools.py @@ -2,7 +2,6 @@ from pyscf import lib, symm from scipy import linalg from mrh.my_pyscf.mcscf.lasci import get_space_info -from mrh.my_pyscf.lassi.lassi import ham_2q, root_make_rdm12s, LASSI from mrh.my_pyscf.lassi.citools import get_lroots, get_rootaddr_fragaddr def decompose_sivec_by_rootspace (las, si, ci=None): @@ -181,6 +180,7 @@ def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', Von Neumann entropy of each fragment, considering each rootspace separately If print_all_but >= 0, some rows may be omitted (set to -1) ''' + from mrh.my_pyscf.lassi.lassi import root_make_rdm12s, LASSI if 'prim' in lbasis.lower (): lbasis = 'primitive' elif 'schmidt' in lbasis.lower (): lbasis = 'Schmidt' else: @@ -356,6 +356,7 @@ def analyze_ham (las, si, e_roots, ci=None, state=0, soc=0, print_all_but=1e-8): but this weight of the wave function(s) is accounted for. Set to zero to print everything. ''' + from mrh.my_pyscf.lassi.lassi import ham_2q if ci is None: ci = las.ci ci0 = ci h0 = ham_2q (las, las.mo_coeff, soc=soc)[0] @@ -575,10 +576,12 @@ def umat_dot_1frag_(target, umat, lroots, ifrag, iroot, axis=0): return target def _umat_dot_1frag (target, umat, lroots, ifrag): + # Remember: COLUMN-MAJOR ORDER!! old_shape = target.shape - new_shape = tuple (lroots) + old_shape[1:] + new_shape = tuple (lroots[::-1]) + old_shape[1:] target = target.reshape (*new_shape) - newaxes = [ifrag,] + list (range (ifrag)) + list (range (ifrag+1, target.ndim)) + iifrag = len (lroots) - ifrag - 1 + newaxes = [iifrag,] + list (range (iifrag)) + list (range (iifrag+1, target.ndim)) oldaxes = list (np.argsort (newaxes)) target = target.transpose (*newaxes) target = np.tensordot (umat.T, target, axes=1).transpose (*oldaxes) From 46a4522d7a9374df868dc5fae2dbe567ce3f8552 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 26 Apr 2024 13:42:13 -0500 Subject: [PATCH 03/87] profiling & code cleanup & tighten equiv tol Profile the timing of lassi op_o1 more thoroughly. Move two fns from sitools to citools (the latter being safer to import). Tighten exact-equivalence tolerance in lassi op_o1 screening. --- my_pyscf/lassi/citools.py | 44 ++++++++++++++++ my_pyscf/lassi/op_o1.py | 45 +++++++++++++---- my_pyscf/lassi/sitools.py | 50 +------------------ .../{test_sitools.py => test_citools.py} | 16 +++--- 4 files changed, 90 insertions(+), 65 deletions(-) rename tests/lassi/{test_sitools.py => test_citools.py} (81%) diff --git a/my_pyscf/lassi/citools.py b/my_pyscf/lassi/citools.py index 72a4bd16..d18ab5af 100644 --- a/my_pyscf/lassi/citools.py +++ b/my_pyscf/lassi/citools.py @@ -64,4 +64,48 @@ def get_rootaddr_fragaddr (lroots): rootaddr[i:j] = iroot return rootaddr, fragaddr +def umat_dot_1frag_(target, umat, lroots, ifrag, iroot, axis=0): + '''Apply a unitary transformation for 1 fragment in 1 rootspace to a tensor + whose target axis spans all model states. + Args: + target: ndarray whose length on axis 'axis' is nstates + The object to which the unitary transformation is to be applied. + Modified in-place. + umat: ndarray of shape (lroots[ifrag,iroot],lroots[ifrag,iroot]) + A unitary matrix; the row axis is contracted + lroots: ndarray of shape (nfrags, nroots) + Number of basis states in each fragment in each rootspace + ifrag: integer + Fragment index for the targeted block + iroot: integer + Rootspace index for the targeted block + + Kwargs: + axis: integer + The axis of target to which umat is to be applied + + Returns: + target: same as input target + After application of unitary transformation''' + nprods = np.product (lroots, axis=0) + offs = [0,] + list (np.cumsum (nprods)) + i, j = offs[iroot], offs[iroot+1] + newaxes = [axis,] + list (range (axis)) + list (range (axis+1, target.ndim)) + oldaxes = list (np.argsort (newaxes)) + target = target.transpose (*newaxes) + target[i:j] = _umat_dot_1frag (target[i:j], umat, lroots[:,iroot], ifrag) + target = target.transpose (*oldaxes) + return target + +def _umat_dot_1frag (target, umat, lroots, ifrag): + # Remember: COLUMN-MAJOR ORDER!! + old_shape = target.shape + new_shape = tuple (lroots[::-1]) + old_shape[1:] + target = target.reshape (*new_shape) + iifrag = len (lroots) - ifrag - 1 + newaxes = [iifrag,] + list (range (iifrag)) + list (range (iifrag+1, target.ndim)) + oldaxes = list (np.argsort (newaxes)) + target = target.transpose (*newaxes) + target = np.tensordot (umat.T, target, axes=1).transpose (*oldaxes) + return target.reshape (*old_shape) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 21db9706..ab94c5b9 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -6,8 +6,7 @@ from pyscf.fci.addons import cre_a, cre_b, des_a, des_b from pyscf.fci import cistring from itertools import product, combinations, combinations_with_replacement -from mrh.my_pyscf.lassi.sitools import umat_dot_1frag_, _umat_dot_1frag -from mrh.my_pyscf.lassi.citools import get_lroots, get_rootaddr_fragaddr +from mrh.my_pyscf.lassi.citools import get_lroots, get_rootaddr_fragaddr, umat_dot_1frag_ import time # NOTE: PySCF has a strange convention where @@ -387,7 +386,8 @@ def _init_crunch_(self, screen_linequiv): ci_j = ci[j].reshape (lroots[j],-1) ovlp = ci_i.conj () @ ci_j.T isequal = np.allclose (ovlp.diagonal (), 1, - rtol=1e-8, atol=1e-8) + rtol=1e-10, atol=1e-10) + # need extremely high precision on this one if screen_linequiv and (not isequal): u, svals, vh = linalg.svd (ovlp) assert (len (svals) == lroots[i]) @@ -704,6 +704,9 @@ def init_profiling (self): self.dt_1s, self.dw_1s = 0.0, 0.0 self.dt_1s1c, self.dw_1s1c = 0.0, 0.0 self.dt_2c, self.dw_2c = 0.0, 0.0 + self.dt_o, self.dw_o = 0.0, 0.0 + self.dt_u, self.dw_u = 0.0, 0.0 + self.dt_p, self.dw_p = 0.0, 0.0 def make_exc_tables (self, hopping_index): ''' Generate excitation tables. The nth column of each array is the (n+1)th argument of the @@ -965,12 +968,15 @@ def _prepare_spec_addr_ovlp_(self, rbra, rket, *inv): *inv: integers Indices of nonspectator fragments ''' + t0, w0 = logger.process_clock (), logger.perf_counter () key = tuple ((rbra,rket)) + inv braket_table = self.nonuniq_exc[key] self._spec_addr_ovlp_cache = [] for rbra1, rket1 in braket_table: b, k, o = self._get_spec_addr_ovlp_1space (rbra1, rket1, *inv) self._spec_addr_ovlp_cache.append ((rbra1, rket1, b, k, o)) + dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 + self.dt_o, self.dw_o = self.dt_o + dt, self.dw_o + dw return def _get_spec_addr_ovlp (self, bra, ket, *inv): @@ -1073,15 +1079,21 @@ def _get_D2_(self, bra, ket): return self.d2 def _put_D1_(self, bra, ket, D1, *inv): + t0, w0 = logger.process_clock (), logger.perf_counter () bra1, ket1, wgt = self._get_spec_addr_ovlp (bra, ket, *inv) self._put_SD1_(bra1, ket1, D1, wgt) + dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 + self.dt_p, self.dw_p = self.dt_p + dt, self.dw_p + dw def _put_SD1_(self, bra, ket, D1, wgt): self.tdm1s[bra,ket,:] += np.multiply.outer (wgt, D1) def _put_D2_(self, bra, ket, D2, *inv): + t0, w0 = logger.process_clock (), logger.perf_counter () bra1, ket1, wgt = self._get_spec_addr_ovlp (bra, ket, *inv) self._put_SD2_(bra1, ket1, D2, wgt) + dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 + self.dt_p, self.dw_p = self.dt_p + dt, self.dw_p + dw def _put_SD2_(self, bra, ket, D2, wgt): self.tdm2s[bra,ket,:] += np.multiply.outer (wgt, D2) @@ -1165,10 +1177,10 @@ def _crunch_1c_tdm2 (d2_ijkk, i0, i1, j0, j1, k0, k1): d2_ijjj = fac * np.multiply.outer (self.ints[i].get_p (bra,ket,s1), self.ints[j].get_phh (bra,ket,s1)).transpose (1,0,4,2,3) _crunch_1c_tdm2 (d2_ijjj, p, q, r, s, r, s) - self._put_D1_(bra, ket, d1, i, j) - self._put_D2_(bra, ket, d2, i, j) dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 self.dt_1c, self.dw_1c = self.dt_1c + dt, self.dw_1c + dw + self._put_D1_(bra, ket, d1, i, j) + self._put_D2_(bra, ket, d2, i, j) def _crunch_1c1d_(self, bra, ket, i, j, k, s1): '''Compute the reduced density matrix elements of a coupled electron-hop and @@ -1199,9 +1211,9 @@ def _crunch_1c_tdm2 (d2_ijkk, i0, i1, j0, j1, k0, k1): d1_skk = self.ints[k].get_dm1 (bra, ket) d2_ijkk = fac * np.multiply.outer (d1_ij, d1_skk).transpose (2,0,1,3,4) _crunch_1c_tdm2 (d2_ijkk, p, q, r, s, t, u) - self._put_D2_(bra, ket, d2, i, j, k) dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 self.dt_1c1d, self.dw_1c1d = self.dt_1c1d + dt, self.dw_1c1d + dw + self._put_D2_(bra, ket, d2, i, j, k) def _crunch_1s_(self, bra, ket, i, j): '''Compute the reduced density matrix elements of a spin unit hop; i.e., @@ -1225,9 +1237,9 @@ def _crunch_1s_(self, bra, ket, i, j): self.ints[j].get_sm (bra, ket)) d2[1,p:q,r:s,r:s,p:q] = d2_spsm.transpose (0,3,2,1) d2[2,r:s,p:q,p:q,r:s] = d2_spsm.transpose (2,1,0,3) - self._put_D2_(bra, ket, d2, i, j) dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 self.dt_1s, self.dw_1s = self.dt_1s + dt, self.dw_1s + dw + self._put_D2_(bra, ket, d2, i, j) def _crunch_1s1c_(self, bra, ket, i, j, k): '''Compute the reduced density matrix elements of a spin-charge unit hop; i.e., @@ -1256,9 +1268,9 @@ def _crunch_1s1c_(self, bra, ket, i, j, k): d2_ikkj = fac * np.multiply.outer (sp, sm).transpose (0,3,2,1) # a'bb'a -> a'ab'b transpose d2[1,p:q,t:u,t:u,r:s] = d2_ikkj d2[2,t:u,r:s,p:q,t:u] = d2_ikkj.transpose (2,3,0,1) - self._put_D2_(bra, ket, d2, i, j, k) dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 self.dt_1s1c, self.dw_1s1c = self.dt_1s1c + dt, self.dw_1s1c + dw + self._put_D2_(bra, ket, d2, i, j, k) def _crunch_2c_(self, bra, ket, i, j, k, l, s2lt): '''Compute the reduced density matrix elements of a two-electron hop; i.e., @@ -1327,9 +1339,9 @@ def _crunch_2c_(self, bra, ket, i, j, k, l, s2lt): if s2 == s2T: # same-spin only: exchange happens d2[s2,p:q,v:w,t:u,r:s] = -d2_ijkl.transpose (0,3,2,1) d2[s2,t:u,r:s,p:q,v:w] = -d2_ijkl.transpose (2,1,0,3) - self._put_D2_(bra, ket, d2, i, j, k, l) dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 self.dt_2c, self.dw_2c = self.dt_2c + dt, self.dw_2c + dw + self._put_D2_(bra, ket, d2, i, j, k, l) def _loop_lroots_(self, _crunch_fn, *row): if _crunch_fn.__name__ in ('_crunch_1c_', '_crunch_1c1d_', '_crunch_2c_'): @@ -1358,9 +1370,12 @@ def _add_transpose_(self): self.tdm2s += self.tdm2s.conj ().transpose (1,0,2,4,3,6,5) def _umat_linequiv_loop_(self, *args): + t0, w0 = logger.process_clock (), logger.perf_counter () for ifrag, inti in enumerate (self.ints): for iroot, umat in inti.umat_root.items (): self._umat_linequiv_(ifrag, iroot, umat, *args) + dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 + self.dt_u, self.dw_u = self.dt_u + dt, self.dw_u + dw def _umat_linequiv_(self, ifrag, iroot, umat, *args): self.tdm1s = umat_dot_1frag_(self.tdm1s, umat, self.lroots, ifrag, iroot, axis=0) @@ -1396,6 +1411,9 @@ def sprint_profile (self): profile += '\n' + fmt_str.format ('1s', self.dt_1s, self.dw_1s) profile += '\n' + fmt_str.format ('1s1c', self.dt_1s1c, self.dw_1s1c) profile += '\n' + fmt_str.format ('2c', self.dt_2c, self.dw_2c) + profile += '\n' + fmt_str.format ('o', self.dt_o, self.dw_o) + profile += '\n' + fmt_str.format ('u', self.dt_u, self.dw_u) + profile += '\n' + fmt_str.format ('p', self.dt_p, self.dw_p) return profile class HamS2ovlpint (LSTDMint2): @@ -1424,6 +1442,7 @@ def __init__(self, ints, nlas, hopping_index, lroots, h1, h2, mask_bra_space=Non self.h2 = h2 def _put_D1_(self, bra, ket, D1, *inv): + t0, w0 = logger.process_clock (), logger.perf_counter () ham = np.dot (self.h1.ravel (), D1.ravel ()) M1 = D1[0] - D1[1] D1 = D1.sum (0) @@ -1431,18 +1450,23 @@ def _put_D1_(self, bra, ket, D1, *inv): s2 = 3*np.trace (D1)/4 bra1, ket1, wgt = self._get_spec_addr_ovlp (bra, ket, *inv) self._put_ham_s2_(bra1, ket1, ham, s2, wgt) + dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 + self.dt_p, self.dw_p = self.dt_p + dt, self.dw_p + dw def _put_ham_s2_(self, bra, ket, ham, s2, wgt): self.ham[bra,ket] += wgt * ham self.s2[bra,ket] += wgt * s2 def _put_D2_(self, bra, ket, D2, *inv): + t0, w0 = logger.process_clock (), logger.perf_counter () ham = np.dot (self.h2.ravel (), D2.sum (0).ravel ()) / 2 M2 = np.einsum ('sppqq->s', D2) / 4 s2 = M2[0] + M2[3] - M2[1] - M2[2] s2 -= np.einsum ('pqqp->', D2[1] + D2[2]) / 2 bra1, ket1, wgt = self._get_spec_addr_ovlp (bra, ket, *inv) self._put_ham_s2_(bra1, ket1, ham, s2, wgt) + dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 + self.dt_p, self.dw_p = self.dt_p + dt, self.dw_p + dw def _add_transpose_(self): self.ham += self.ham.T @@ -1681,9 +1705,12 @@ def _get_vecs_(self, bra, ket): return hci_f_ab, excfrags def _put_vecs_(self, bra, ket, vecs, *inv): + t0, w0 = logger.process_clock (), logger.perf_counter () bras, kets, facs = self._get_spec_addr_ovlp (bra, ket, *inv) for bra, ket, fac in zip (bras, kets, facs): self._put_Svecs_(bra, ket, [fac*vec for vec in vecs]) + dt, dw = logger.process_clock () - t0, logger.perf_counter () - w0 + self.dt_p, self.dw_p = self.dt_p + dt, self.dw_p + dw def _put_Svecs_(self, bra, ket, vecs): bra_r, bra_envaddr, excfrags = self._bra_address (bra) diff --git a/my_pyscf/lassi/sitools.py b/my_pyscf/lassi/sitools.py index 0c0b7254..d6b8535d 100644 --- a/my_pyscf/lassi/sitools.py +++ b/my_pyscf/lassi/sitools.py @@ -3,6 +3,8 @@ from scipy import linalg from mrh.my_pyscf.mcscf.lasci import get_space_info from mrh.my_pyscf.lassi.citools import get_lroots, get_rootaddr_fragaddr +from mrh.my_pyscf.lassi.lassi import root_make_rdm12s, LASSI, ham_2q +from mrh.my_pyscf.lassi.op_o1 import fermion_spin_shuffle def decompose_sivec_by_rootspace (las, si, ci=None): '''Decompose a set of LASSI vectors as @@ -180,7 +182,6 @@ def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', Von Neumann entropy of each fragment, considering each rootspace separately If print_all_but >= 0, some rows may be omitted (set to -1) ''' - from mrh.my_pyscf.lassi.lassi import root_make_rdm12s, LASSI if 'prim' in lbasis.lower (): lbasis = 'primitive' elif 'schmidt' in lbasis.lower (): lbasis = 'Schmidt' else: @@ -356,7 +357,6 @@ def analyze_ham (las, si, e_roots, ci=None, state=0, soc=0, print_all_but=1e-8): but this weight of the wave function(s) is accounted for. Set to zero to print everything. ''' - from mrh.my_pyscf.lassi.lassi import ham_2q if ci is None: ci = las.ci ci0 = ci h0 = ham_2q (las, las.mo_coeff, soc=soc)[0] @@ -482,7 +482,6 @@ def sivec_fermion_spin_shuffle (si0, nelec_frs, lroots): si1: ndarray of shape (ndim,*) si0 with permuted row signs ''' - from mrh.my_pyscf.lassi.op_o1 import fermion_spin_shuffle nelec_rsf = np.asarray (nelec_frs).transpose (1,2,0) rootaddr = get_rootaddr_fragaddr (lroots)[0] si1 = si0.copy () @@ -541,51 +540,6 @@ def sivec_vacuum_shuffle (si0, nelec_frs, lroots, nelec_vac=None, state=None): return si1 -def umat_dot_1frag_(target, umat, lroots, ifrag, iroot, axis=0): - '''Apply a unitary transformation for 1 fragment in 1 rootspace to a tensor - whose target axis spans all model states. - - Args: - target: ndarray whose length on axis 'axis' is nstates - The object to which the unitary transformation is to be applied. - Modified in-place. - umat: ndarray of shape (lroots[ifrag,iroot],lroots[ifrag,iroot]) - A unitary matrix; the row axis is contracted - lroots: ndarray of shape (nfrags, nroots) - Number of basis states in each fragment in each rootspace - ifrag: integer - Fragment index for the targeted block - iroot: integer - Rootspace index for the targeted block - - Kwargs: - axis: integer - The axis of target to which umat is to be applied - - Returns: - target: same as input target - After application of unitary transformation''' - nprods = np.product (lroots, axis=0) - offs = [0,] + list (np.cumsum (nprods)) - i, j = offs[iroot], offs[iroot+1] - newaxes = [axis,] + list (range (axis)) + list (range (axis+1, target.ndim)) - oldaxes = list (np.argsort (newaxes)) - target = target.transpose (*newaxes) - target[i:j] = _umat_dot_1frag (target[i:j], umat, lroots[:,iroot], ifrag) - target = target.transpose (*oldaxes) - return target - -def _umat_dot_1frag (target, umat, lroots, ifrag): - # Remember: COLUMN-MAJOR ORDER!! - old_shape = target.shape - new_shape = tuple (lroots[::-1]) + old_shape[1:] - target = target.reshape (*new_shape) - iifrag = len (lroots) - ifrag - 1 - newaxes = [iifrag,] + list (range (iifrag)) + list (range (iifrag+1, target.ndim)) - oldaxes = list (np.argsort (newaxes)) - target = target.transpose (*newaxes) - target = np.tensordot (umat.T, target, axes=1).transpose (*oldaxes) - return target.reshape (*old_shape) diff --git a/tests/lassi/test_sitools.py b/tests/lassi/test_citools.py similarity index 81% rename from tests/lassi/test_sitools.py rename to tests/lassi/test_citools.py index 2904a215..ed59c664 100644 --- a/tests/lassi/test_sitools.py +++ b/tests/lassi/test_citools.py @@ -2,7 +2,7 @@ import unittest import numpy as np from scipy import linalg -from mrh.my_pyscf.lassi import sitools +from mrh.my_pyscf.lassi import citools from pyscf import lib from pyscf.scf.addons import canonical_orth_ import itertools @@ -33,12 +33,12 @@ def case_umat_dot_1frag (ks, rng, nroots, nfrags, nvecs, lroots): ks.assertEqual (umat.shape[1], lr) ks.assertAlmostEqual (lib.fp (umat.conj ().T @ umat), lib.fp (np.eye (lr))) ks.assertAlmostEqual (lib.fp (umat @ umat.conj ().T), lib.fp (np.eye (lr))) - si = sitools.umat_dot_1frag_(si, umat, lroots, ifrag, iroot, axis=0) - UUsi = sitools.umat_dot_1frag_(UUsi, umat, lroots, ifrag, iroot, axis=0) - UUsi = sitools.umat_dot_1frag_(UUsi, umat.conj ().T, lroots, ifrag, iroot, axis=0) - siT = sitools.umat_dot_1frag_(siT, umat, lroots, ifrag, iroot, axis=1) - UUsiT = sitools.umat_dot_1frag_(UUsiT, umat, lroots, ifrag, iroot, axis=1) - UUsiT = sitools.umat_dot_1frag_(UUsiT, umat.conj ().T, lroots, ifrag, iroot, axis=1) + si = citools.umat_dot_1frag_(si, umat, lroots, ifrag, iroot, axis=0) + UUsi = citools.umat_dot_1frag_(UUsi, umat, lroots, ifrag, iroot, axis=0) + UUsi = citools.umat_dot_1frag_(UUsi, umat.conj ().T, lroots, ifrag, iroot, axis=0) + siT = citools.umat_dot_1frag_(siT, umat, lroots, ifrag, iroot, axis=1) + UUsiT = citools.umat_dot_1frag_(UUsiT, umat, lroots, ifrag, iroot, axis=1) + UUsiT = citools.umat_dot_1frag_(UUsiT, umat.conj ().T, lroots, ifrag, iroot, axis=1) ovlp = si.conj ().T @ si ovlpT = siT.conj () @ siT.T ks.assertAlmostEqual (lib.fp (ovlp), lib.fp (np.eye (nvecs))) @@ -59,7 +59,7 @@ def test_umat_dot_1frag (self): if __name__ == "__main__": - print("Full Tests for LASSI sitools module functions") + print("Full Tests for LASSI citools module functions") unittest.main() From 44062f53740af93ea3ada5d85fb6aec86dc933d9 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 26 Apr 2024 14:00:26 -0500 Subject: [PATCH 04/87] lassi.states -> lassi.spaces & op_o1 profile For op_o1 profiling, use more descriptive names in the output and add it around the HamS2Ovlp full overlap matrix steps. --- debug/lassi/debug_22.py | 2 +- debug/lassi/debug_c2h4n4.py | 4 ++-- debug/lassi/debug_lassis_targets_slow.py | 2 +- examples/laspdft/c2h4n4_si_laspdft.py | 4 ++-- examples/sa_si_lasscf/automatic_singles.py | 2 +- examples/sa_si_lasscf/cr2o3n6h21_lassis66_smallbasis.py | 8 ++++---- my_pyscf/lassi/lassirq.py | 4 ++-- my_pyscf/lassi/lassis.py | 8 ++++---- my_pyscf/lassi/op_o1.py | 9 ++++++--- my_pyscf/lassi/{states.py => spaces.py} | 0 tests/lasscf/test_utilities.py | 4 ++-- tests/lassi/test_22.py | 2 +- tests/lassi/test_c2h4n4.py | 4 ++-- tests/lassi/test_excitations.py | 2 +- tests/lassi/test_lassis_targets_slow.py | 2 +- tests/mcpdft/test_lassipdft.py | 2 +- 16 files changed, 31 insertions(+), 28 deletions(-) rename my_pyscf/lassi/{states.py => spaces.py} (100%) diff --git a/debug/lassi/debug_22.py b/debug/lassi/debug_22.py index 2b0d43dc..e8fd9477 100644 --- a/debug/lassi/debug_22.py +++ b/debug/lassi/debug_22.py @@ -20,7 +20,7 @@ from mrh.my_pyscf.mcscf.lasscf_o0 import LASSCF from mrh.my_pyscf.lassi import LASSI from mrh.my_pyscf.lassi.lassi import root_make_rdm12s, make_stdm12s -from mrh.my_pyscf.lassi.states import all_single_excitations, SingleLASRootspace +from mrh.my_pyscf.lassi.spaces import all_single_excitations, SingleLASRootspace from mrh.my_pyscf.mcscf.lasci import get_space_info from mrh.my_pyscf.lassi import op_o0, op_o1, lassis diff --git a/debug/lassi/debug_c2h4n4.py b/debug/lassi/debug_c2h4n4.py index 24ade981..74e95655 100644 --- a/debug/lassi/debug_c2h4n4.py +++ b/debug/lassi/debug_c2h4n4.py @@ -122,7 +122,7 @@ class KnownValues(unittest.TestCase): # self.assertAlmostEqual (e1, e0, 8) #def test_singles_constructor (self): - # from mrh.my_pyscf.lassi.states import all_single_excitations + # from mrh.my_pyscf.lassi.spaces import all_single_excitations # las2 = all_single_excitations (lsi._las) # las2.check_sanity () # # Meaning of tuple: (na+nb,smult) @@ -135,7 +135,7 @@ class KnownValues(unittest.TestCase): # self.assertEqual (las2.nroots, 33) #def test_spin_shuffle (self): - # from mrh.my_pyscf.lassi.states import spin_shuffle, spin_shuffle_ci + # from mrh.my_pyscf.lassi.spaces import spin_shuffle, spin_shuffle_ci # mf = lsi._las._scf # las3 = spin_shuffle (las) # las3.check_sanity () diff --git a/debug/lassi/debug_lassis_targets_slow.py b/debug/lassi/debug_lassis_targets_slow.py index 75181a52..0f16f118 100644 --- a/debug/lassi/debug_lassis_targets_slow.py +++ b/debug/lassi/debug_lassis_targets_slow.py @@ -186,7 +186,7 @@ def test_kremer_cr2_model (self): mo_list = mc_avas.ncore + np.array ([5,6,7,8,9,10,15,16,17,18,19,20]) mo_coeff = las.sort_mo (mo_list, mo_coeff) mo_coeff = las.localize_init_guess (([0],[1]), mo_coeff) - las = lassi.states.spin_shuffle (las) # generate direct-exchange states + las = lassi.spaces.spin_shuffle (las) # generate direct-exchange states las.weights = [1.0/las.nroots,]*las.nroots # set equal weights nroots_ref = las.nroots las.kernel (mo_coeff) # optimize orbitals diff --git a/examples/laspdft/c2h4n4_si_laspdft.py b/examples/laspdft/c2h4n4_si_laspdft.py index 944d1695..11f55410 100755 --- a/examples/laspdft/c2h4n4_si_laspdft.py +++ b/examples/laspdft/c2h4n4_si_laspdft.py @@ -5,7 +5,7 @@ from mrh.my_pyscf import mcpdft from mrh.my_pyscf.tools.molden import from_lasscf from c2h4n4_struct import structure as struct -from mrh.my_pyscf.lassi.states import all_single_excitations +from mrh.my_pyscf.lassi.spaces import all_single_excitations # Mean field calculation mol = struct(0, 0, '6-31g') @@ -23,7 +23,7 @@ mo0 = las.localize_init_guess((list(range (5)), list(range (5,10))), guess_mo) las.kernel(mo0) -las = lassi.states.all_single_excitations (las) +las = lassi.spaces.all_single_excitations (las) las.lasci () lsi = lassi.LASSI(las) diff --git a/examples/sa_si_lasscf/automatic_singles.py b/examples/sa_si_lasscf/automatic_singles.py index 2fa375ec..8e3b21bd 100755 --- a/examples/sa_si_lasscf/automatic_singles.py +++ b/examples/sa_si_lasscf/automatic_singles.py @@ -44,7 +44,7 @@ print ("LASSI(hand) energy =", e_roots[0]) molden.from_lassi (las2, 'c2h4n4_las66si4_631g.molden', si=si_hand) -from mrh.my_pyscf.lassi.states import all_single_excitations +from mrh.my_pyscf.lassi.spaces import all_single_excitations las = all_single_excitations (las) las.lasci () # Optimize the CI vectors las.dump_spaces () # prints all state tables in the output file diff --git a/examples/sa_si_lasscf/cr2o3n6h21_lassis66_smallbasis.py b/examples/sa_si_lasscf/cr2o3n6h21_lassis66_smallbasis.py index fde43462..ee11dbab 100755 --- a/examples/sa_si_lasscf/cr2o3n6h21_lassis66_smallbasis.py +++ b/examples/sa_si_lasscf/cr2o3n6h21_lassis66_smallbasis.py @@ -46,12 +46,12 @@ def yamaguchi (e_roots, s2): mo_coeff = las.localize_init_guess (([0],[1]), mo_coeff) # Direct exchange only result -las = lassi.states.spin_shuffle (las) # generate direct-exchange states +las = lassi.spaces.spin_shuffle (las) # generate direct-exchange states las.weights = [1.0/las.nroots,]*las.nroots # set equal weights las.kernel (mo_coeff) # optimize orbitals lsi0 = lassi.LASSI (las).run () print (("Direct exchange only is modeled by {} states constructed with " - "lassi.states.spin_shuffle").format (las.nroots)) + "lassi.spaces.spin_shuffle").format (las.nroots)) print ("J(LASSI, direct) = %.2f cm^-1" % yamaguchi (lsi0.e_roots, lsi0.s2)) print ("{} rootspaces and {} product states total\n".format (lsi0.nroots, lsi0.si.shape[1])) @@ -70,8 +70,8 @@ def yamaguchi (e_roots, s2): mc.ci.shape[0], mc.ci.shape[1], mc.ci.size)) # Direct exchange & kinetic exchange result -las = lassi.states.all_single_excitations (las) # generate kinetic-exchange states -print (("Use of lassi.states.all_single_excitations generates " +las = lassi.spaces.all_single_excitations (las) # generate kinetic-exchange states +print (("Use of lassi.spaces.all_single_excitations generates " "{} additional kinetic-exchange (i.e., charge-transfer) " "states").format (las.nroots-4)) las.lasci () # do not reoptimize orbitals at this step - not likely to converge diff --git a/my_pyscf/lassi/lassirq.py b/my_pyscf/lassi/lassirq.py index f47b8aa8..f2efadea 100644 --- a/my_pyscf/lassi/lassirq.py +++ b/my_pyscf/lassi/lassirq.py @@ -2,8 +2,8 @@ from pyscf import lib,gto from pyscf.lib import logger from mrh.my_pyscf.lassi import LASSI -from mrh.my_pyscf.lassi.states import spin_shuffle, spin_shuffle_ci -from mrh.my_pyscf.lassi.states import all_single_excitations, SingleLASRootspace +from mrh.my_pyscf.lassi.spaces import spin_shuffle, spin_shuffle_ci +from mrh.my_pyscf.lassi.spaces import all_single_excitations, SingleLASRootspace from mrh.my_pyscf.mcscf.lasci import get_space_info def prepare_states_spin_shuffle (lsi): diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index 97da88c0..98a0f553 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -11,10 +11,10 @@ from mrh.my_pyscf.mcscf.lasci import get_space_info from mrh.my_pyscf.mcscf.productstate import ProductStateFCISolver from mrh.my_pyscf.lassi.excitations import ExcitationPSFCISolver -from mrh.my_pyscf.lassi.states import spin_shuffle, spin_shuffle_ci -from mrh.my_pyscf.lassi.states import _spin_shuffle, _spin_shuffle_ci_ -from mrh.my_pyscf.lassi.states import all_single_excitations, SingleLASRootspace -from mrh.my_pyscf.lassi.states import orthogonal_excitations, combine_orthogonal_excitations +from mrh.my_pyscf.lassi.spaces import spin_shuffle, spin_shuffle_ci +from mrh.my_pyscf.lassi.spaces import _spin_shuffle, _spin_shuffle_ci_ +from mrh.my_pyscf.lassi.spaces import all_single_excitations, SingleLASRootspace +from mrh.my_pyscf.lassi.spaces import orthogonal_excitations, combine_orthogonal_excitations from mrh.my_pyscf.lassi.lassi import LASSI # TODO: split prepare_states into three steps diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index ab94c5b9..aa238a03 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -1411,9 +1411,9 @@ def sprint_profile (self): profile += '\n' + fmt_str.format ('1s', self.dt_1s, self.dw_1s) profile += '\n' + fmt_str.format ('1s1c', self.dt_1s1c, self.dw_1s1c) profile += '\n' + fmt_str.format ('2c', self.dt_2c, self.dw_2c) - profile += '\n' + fmt_str.format ('o', self.dt_o, self.dw_o) - profile += '\n' + fmt_str.format ('u', self.dt_u, self.dw_u) - profile += '\n' + fmt_str.format ('p', self.dt_p, self.dw_p) + profile += '\n' + fmt_str.format ('ovlp', self.dt_o, self.dw_o) + profile += '\n' + fmt_str.format ('umat', self.dt_u, self.dw_u) + profile += '\n' + fmt_str.format ('put', self.dt_p, self.dw_p) return profile class HamS2ovlpint (LSTDMint2): @@ -1500,6 +1500,7 @@ def kernel (self): self.ham = np.zeros ([self.nstates,]*2, dtype=self.dtype) self.s2 = np.zeros ([self.nstates,]*2, dtype=self.dtype) self._crunch_all_() + t1, w1 = lib.logger.process_clock (), lib.logger.perf_counter () ovlp = np.zeros ([self.nstates,]*2, dtype=self.dtype) def crunch_ovlp (bra_sp, ket_sp): i = self.ints[-1] @@ -1516,6 +1517,8 @@ def crunch_ovlp (bra_sp, ket_sp): ovlp[i0:i1,j0:j1] = o for bra_sp, ket_sp in self.exc_null: crunch_ovlp (bra_sp, ket_sp) ovlp += ovlp.T + dt, dw = logger.process_clock () - t1, logger.perf_counter () - w1 + self.dt_o, self.dw_o = self.dt_o + dt, self.dw_o + dw self._umat_linequiv_loop_(ovlp) return self.ham, self.s2, ovlp, t0 diff --git a/my_pyscf/lassi/states.py b/my_pyscf/lassi/spaces.py similarity index 100% rename from my_pyscf/lassi/states.py rename to my_pyscf/lassi/spaces.py diff --git a/tests/lasscf/test_utilities.py b/tests/lasscf/test_utilities.py index 5837ef40..b5e0a927 100644 --- a/tests/lasscf/test_utilities.py +++ b/tests/lasscf/test_utilities.py @@ -11,7 +11,7 @@ class KnownValues (unittest.TestCase): def test_get_single_state_las (self): from mrh.my_pyscf.mcscf.lasscf_async import LASSCF - from mrh.my_pyscf.lassi import states as lassi_states + from mrh.my_pyscf.lassi import spaces as lassi_spaces xyz='''N 0 0 0, N 3 0 0''' mol = gto.M (atom=xyz, basis='cc-pvdz', symmetry=False, verbose=0, output='/dev/null') @@ -19,7 +19,7 @@ def test_get_single_state_las (self): las = LASSCF (mf, (6,6), ((3,0),(0,3))) mo = las.set_fragments_(([0],[1])) - las = lassi_states.spin_shuffle (las) + las = lassi_spaces.spin_shuffle (las) las.weights = [1.0/las.nroots,]*las.nroots las.kernel (mo) for i in range (len (las.e_states)): diff --git a/tests/lassi/test_22.py b/tests/lassi/test_22.py index 64481215..a8fb48ec 100644 --- a/tests/lassi/test_22.py +++ b/tests/lassi/test_22.py @@ -20,7 +20,7 @@ from mrh.my_pyscf.mcscf.lasscf_o0 import LASSCF from mrh.my_pyscf.lassi import LASSI, LASSIrq, LASSIrqCT from mrh.my_pyscf.lassi.lassi import root_make_rdm12s, make_stdm12s -from mrh.my_pyscf.lassi.states import all_single_excitations, SingleLASRootspace +from mrh.my_pyscf.lassi.spaces import all_single_excitations, SingleLASRootspace from mrh.my_pyscf.mcscf.lasci import get_space_info from mrh.my_pyscf.lassi import op_o0, op_o1, lassis diff --git a/tests/lassi/test_c2h4n4.py b/tests/lassi/test_c2h4n4.py index 3ce330de..ae460ab8 100644 --- a/tests/lassi/test_c2h4n4.py +++ b/tests/lassi/test_c2h4n4.py @@ -122,7 +122,7 @@ def test_rdms (self): self.assertAlmostEqual (e1, e0, 8) def test_singles_constructor (self): - from mrh.my_pyscf.lassi.states import all_single_excitations + from mrh.my_pyscf.lassi.spaces import all_single_excitations las2 = all_single_excitations (lsi._las) las2.check_sanity () # Meaning of tuple: (na+nb,smult) @@ -135,7 +135,7 @@ def test_singles_constructor (self): self.assertEqual (las2.nroots, 33) def test_spin_shuffle (self): - from mrh.my_pyscf.lassi.states import spin_shuffle, spin_shuffle_ci + from mrh.my_pyscf.lassi.spaces import spin_shuffle, spin_shuffle_ci mf = lsi._las._scf las3 = spin_shuffle (las) las3.check_sanity () diff --git a/tests/lassi/test_excitations.py b/tests/lassi/test_excitations.py index 6730e475..98c9c01d 100644 --- a/tests/lassi/test_excitations.py +++ b/tests/lassi/test_excitations.py @@ -23,7 +23,7 @@ from mrh.my_pyscf.mcscf.lasscf_o0 import LASSCF from mrh.my_pyscf.lassi import LASSI, op_o0, op_o1 from mrh.my_pyscf.lassi.lassi import root_make_rdm12s, make_stdm12s -from mrh.my_pyscf.lassi.states import all_single_excitations +from mrh.my_pyscf.lassi.spaces import all_single_excitations from mrh.my_pyscf.lassi.excitations import ExcitationPSFCISolver from mrh.my_pyscf.mcscf.lasci import get_space_info from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver diff --git a/tests/lassi/test_lassis_targets_slow.py b/tests/lassi/test_lassis_targets_slow.py index 7e68c511..83b0f953 100644 --- a/tests/lassi/test_lassis_targets_slow.py +++ b/tests/lassi/test_lassis_targets_slow.py @@ -185,7 +185,7 @@ def test_kremer_cr2_model (self): mo_list = mc_avas.ncore + np.array ([5,6,7,8,9,10,15,16,17,18,19,20]) mo_coeff = las.sort_mo (mo_list, mo_coeff) mo_coeff = las.localize_init_guess (([0],[1]), mo_coeff) - las = lassi.states.spin_shuffle (las) # generate direct-exchange states + las = lassi.spaces.spin_shuffle (las) # generate direct-exchange states las.weights = [1.0/las.nroots,]*las.nroots # set equal weights nroots_ref = las.nroots las.kernel (mo_coeff) # optimize orbitals diff --git a/tests/mcpdft/test_lassipdft.py b/tests/mcpdft/test_lassipdft.py index 4ecfa31c..56e6c511 100644 --- a/tests/mcpdft/test_lassipdft.py +++ b/tests/mcpdft/test_lassipdft.py @@ -2,7 +2,7 @@ from pyscf import lib, gto, scf from mrh.my_pyscf.mcscf.lasscf_o0 import LASSCF from mrh.my_pyscf.lassi import LASSI -from mrh.my_pyscf.lassi.states import all_single_excitations +from mrh.my_pyscf.lassi.spaces import all_single_excitations from mrh.my_pyscf.mcscf.lasci import get_space_info class KnownValues(unittest.TestCase): From 9196c06e09a144225e6352f38f405aa480a0f1d9 Mon Sep 17 00:00:00 2001 From: Bhavnesh Jangid Date: Mon, 29 Apr 2024 17:32:38 -0500 Subject: [PATCH 05/87] Adding keyword to plot only active space orbitals --- my_pyscf/tools/molden.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/my_pyscf/tools/molden.py b/my_pyscf/tools/molden.py index 30414e6a..9e25b716 100644 --- a/my_pyscf/tools/molden.py +++ b/my_pyscf/tools/molden.py @@ -41,9 +41,13 @@ def from_si_mcscf (mc, fname, state=None, si=None, cas_natorb=False, cas_mo_ener return from_sa_mcscf (mc, fname, state=state, cas_natorb=cas_natorb, cas_mo_energy=cas_mo_energy, **kwargs) -def from_lasscf (las, fname, state=None, natorb_casdm1=None, **kwargs): +def from_lasscf (las, fname, state=None, natorb_casdm1=None, only_as=False, **kwargs): if state is not None: natorb_casdm1 = las.states_make_casdm1s ()[state].sum (0) mo_coeff, mo_ene, mo_occ = las.canonicalize (natorb_casdm1=natorb_casdm1)[:3] + if only_as: + mo_coeff = mo_coeff[:, las.ncore:las.ncore+las.ncas] + mo_ene = mo_ene[las.ncore:las.ncore+las.ncas] + mo_occ = mo_occ[las.ncore:las.ncore+las.ncas] return from_mo (las.mol, fname, mo_coeff, occ=mo_occ, ene=mo_ene, **kwargs) def from_lassi (lsi, fname, state=0, si=None, opt=1, **kwargs): From ac175a1f9b91f0b4fba5e92e4f6cba300b9ec7a8 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 30 Apr 2024 10:46:56 -0500 Subject: [PATCH 06/87] op_o1 reduce verbosity "unique" messages demoted to DEBUG --- my_pyscf/lassi/op_o1.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index aa238a03..64c5d0e8 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -884,8 +884,8 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) - self.log.info ('%d/%d unique interactions of %s type', - nuniq, nexc, lbl) + self.log.debug ('%d/%d unique interactions of %s type', + nuniq, nexc, lbl) return exc def get_range (self, i): @@ -1779,7 +1779,8 @@ def make_ints (las, ci, nelec_frs, screen_linequiv=True): screen_linequiv=screen_linequiv) lib.logger.timer (las, 'LAS-state TDM12s fragment {} intermediate crunching'.format ( ifrag), *tdmint.time_crunch) - lib.logger.info (las, 'UNIQUE ROOTSPACES OF FRAG %d: %d/%d', ifrag, np.count_nonzero (tdmint.root_unique), nroots) + lib.logger.debug (las, 'UNIQUE ROOTSPACES OF FRAG %d: %d/%d', ifrag, + np.count_nonzero (tdmint.root_unique), nroots) ints.append (tdmint) return hopping_index, ints, lroots From 21a2b110602914731c7c9108b5dfb628fc6750ec Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 30 Apr 2024 13:51:15 -0500 Subject: [PATCH 07/87] assert: ref, not copy --- my_pyscf/lassi/spaces.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/my_pyscf/lassi/spaces.py b/my_pyscf/lassi/spaces.py index a6ae5e64..aaef2810 100644 --- a/my_pyscf/lassi/spaces.py +++ b/my_pyscf/lassi/spaces.py @@ -317,6 +317,7 @@ def single_fragment_spin_change (self, ifrag, new_smult, new_spin, ci=None): nelelas=self.nelelas, stdout=self.stdout, verbose=self.verbose, ci=ci1) sp.entmap = self.entmap + assert (ci is sp.ci[ifrag]) return sp def is_orthogonal_by_smult (self, other): @@ -402,6 +403,10 @@ def combine_orthogonal_excitations (exc1, exc2, ref): product.entmap = tuple (set (exc1.entmap + exc2.entmap)) #assert (np.amax (product.entmap) < 2) assert (len (product.entmap) == len (set (product.entmap))) + for ifrag in range (nfrag): + assert ((product.ci[ifrag] is exc1.ci[ifrag]) or + (product.ci[ifrag] is exc2.ci[ifrag]) or + (product.ci[ifrag] is ref.ci[ifrag])) return product def all_single_excitations (las, verbose=None): From 2c9fda3db2eae968981ae64f72dbabbb44a5646c Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 30 Apr 2024 14:19:42 -0500 Subject: [PATCH 08/87] LASSI slight refactor: LASSCF obj -> spacelist --- my_pyscf/lassi/lassis.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index 98a0f553..2a7df5df 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -57,16 +57,14 @@ def prepare_states (lsi, ncharge=1, nspin=0, sa_heff=True, deactivate_vrv=False, # like spin_flips above. if ncharge: las2 = all_single_excitations (las1) - converged, las2.ci, las2.e_states = single_excitations_ci ( + converged, spaces2 = single_excitations_ci ( lsi, las2, las1, ncharge=ncharge, sa_heff=sa_heff, deactivate_vrv=deactivate_vrv, spin_flips=spin_flips, crash_locmin=crash_locmin ) else: - converged, las2 = las1.converged, las1 - # TODO: make all_single_excitations and single_excitations_ci return spaces2 instead of - # las2, so that you can delete this - spaces2 = [SingleLASRootspace (las2, m, s, c, las2.weights[ix], ci=[c[ix] for c in las2.ci]) - for ix, (c, m, s, w) in enumerate (zip (*get_space_info (las2)))] + converged = las1.converged + spaces2 = [SingleLASRootspace (las1, m, s, c, las1.weights[ix], ci=[c[ix] for c in las1.ci]) + for ix, (c, m, s, w) in enumerate (zip (*get_space_info (las1)))] if lsi.nfrags > 3: spaces2 = charge_excitation_products (lsi, spaces2, las1) # 4. Spin excitations part 2 @@ -79,7 +77,7 @@ def prepare_states (lsi, ncharge=1, nspin=0, sa_heff=True, deactivate_vrv=False, spins = [space.spins for space in spaces3] smults = [space.smults for space in spaces3] ci3 = [[space.ci[ifrag] for space in spaces3] for ifrag in range (lsi.nfrags)] - las3 = las2.state_average (weights=weights, charges=charges, spins=spins, smults=smults, assert_no_dupes=False) + las3 = las1.state_average (weights=weights, charges=charges, spins=spins, smults=smults, assert_no_dupes=False) las3.ci = ci3 las3.lasci (_dry_run=True) log.timer ("LASSIS model space preparation", *t0) @@ -91,8 +89,7 @@ def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_ mol = lsi.mol nfrags = lsi.nfrags e_roots = np.append (las1.e_states, np.zeros (las2.nroots-las1.nroots)) - ci = [[ci_ij for ci_ij in ci_i] for ci_i in las2.ci] - spaces = [SingleLASRootspace (las2, m, s, c, las2.weights[ix], ci=[c[ix] for c in ci]) + spaces = [SingleLASRootspace (las2, m, s, c, las2.weights[ix], ci=[c[ix] for c in las2.ci]) for ix, (c, m, s, w) in enumerate (zip (*get_space_info (las2)))] ncsf = las2.get_ugg ().ncsf_sub auto_singles = False @@ -144,8 +141,6 @@ def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_ if i_ssref is not None: spaces[i].ci = spaces[i].get_spin_shuffle_civecs (spaces[i_ssref]) log.info ("and is a spin shuffle of space %d", i_ssref) - for k in range (nfrags): - ci[k][i] = spaces[i].ci[k] t0 = log.timer ("Space {} excitations".format (i), *t0) lroots_i = spaces[i].get_lroots () assert (np.all (lroots_i==lroots[:,i])), "{} {} {} {}".format ( @@ -185,10 +180,8 @@ def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_ spaces[i].ci = ci1 if not conv: log.warn ("CI vectors for charge-separated rootspace %d not converged", i) converged = converged and conv - for k in range (nfrags): - ci[k][i] = ci1[k] t0 = log.timer ("Space {} excitations".format (i), *t0) - return converged, ci, e_roots + return converged, spaces class SpinFlips (object): '''For a single fragment, bundle the ci vectors of various spin-flipped states with their From 471ea28eaa3b2dc334d9eaa51bd5e758da2a1f56 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 30 Apr 2024 14:40:24 -0500 Subject: [PATCH 09/87] LASSIS refactor: reduce repeated spin_shuffle --- my_pyscf/lassi/lassis.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index 2a7df5df..32f084c1 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -105,8 +105,13 @@ def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_ h0, h1, h2 = lsi.ham_2q () t0 = (logger.process_clock (), logger.perf_counter ()) converged = True - log.info ("LASSIS electron hop spaces: %d-%d", las1.nroots, las2.nroots-1) - for i in range (las1.nroots, las2.nroots): + # Prefilter spin-shuffles + spaces = [spi for i, spi in enumerate (spaces) + if (i Date: Tue, 30 Apr 2024 14:51:12 -0500 Subject: [PATCH 10/87] undo --- my_pyscf/lassi/lassis.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index 32f084c1..2a7df5df 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -105,13 +105,8 @@ def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_ h0, h1, h2 = lsi.ham_2q () t0 = (logger.process_clock (), logger.perf_counter ()) converged = True - # Prefilter spin-shuffles - spaces = [spi for i, spi in enumerate (spaces) - if (i Date: Tue, 30 Apr 2024 15:15:28 -0500 Subject: [PATCH 11/87] undo --- my_pyscf/lassi/lassis.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index 2a7df5df..32f084c1 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -105,8 +105,13 @@ def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_ h0, h1, h2 = lsi.ham_2q () t0 = (logger.process_clock (), logger.perf_counter ()) converged = True - log.info ("LASSIS electron hop spaces: %d-%d", las1.nroots, las2.nroots-1) - for i in range (las1.nroots, las2.nroots): + # Prefilter spin-shuffles + spaces = [spi for i, spi in enumerate (spaces) + if (i Date: Tue, 30 Apr 2024 16:16:25 -0500 Subject: [PATCH 12/87] Memory efficiency lassis _spin_shuffle_ci_ (#90) --- my_pyscf/lassi/lassis.py | 57 +++++++++++++++++++++++++++++++++++++--- my_pyscf/lassi/spaces.py | 9 +++++-- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index 32f084c1..b5aae0a0 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -12,7 +12,7 @@ from mrh.my_pyscf.mcscf.productstate import ProductStateFCISolver from mrh.my_pyscf.lassi.excitations import ExcitationPSFCISolver from mrh.my_pyscf.lassi.spaces import spin_shuffle, spin_shuffle_ci -from mrh.my_pyscf.lassi.spaces import _spin_shuffle, _spin_shuffle_ci_ +from mrh.my_pyscf.lassi.spaces import _spin_shuffle from mrh.my_pyscf.lassi.spaces import all_single_excitations, SingleLASRootspace from mrh.my_pyscf.lassi.spaces import orthogonal_excitations, combine_orthogonal_excitations from mrh.my_pyscf.lassi.lassi import LASSI @@ -141,6 +141,7 @@ def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_ ciref = [[] for j in range (nfrags)] for k in range (nfrags): for space in psref: ciref[k].append (space.ci[k]) + spaces[i].set_entmap_(psref[0]) psref = [space.get_product_state_solver () for space in psref] psexc = ExcitationPSFCISolver (psref, ciref, las2.ncas_sub, las2.nelecas_sub, stdout=mol.stdout, verbose=mol.verbose, @@ -302,6 +303,56 @@ def _spin_flip_products (spaces, spin_flips, nroots_ref=1, frozen_frags=None): spaces = [space for space in spaces if not ((space in seen) or seen.add (space))] return spaces +def _spin_shuffle_ci_(spaces, spin_flips, nroots_ref, nroots_refc): + '''Memory-efficient version of the function spaces._spin_shuffle_ci_. + Based on the fact that we know there has only been one independent set + of vectors per fragment Hilbert space and that all possible individual + fragment spins must be accounted for already, so we are just recombining + them.''' + old_idx = [] + new_idx = [] + nfrag = spaces[0].nfrag + for ix, space in enumerate (spaces): + if space.has_ci (): + old_idx.append (ix) + else: + assert (ix >= nroots_refc) + new_idx.append (ix) + space.ci = [None for ifrag in range (space.nfrag)] + # Prepare charge-hop szrots + spaces_1c = spaces[nroots_ref:nroots_refc] + spaces_1c = [space for space in spaces_1c if len (space.entmap)==1] + ci_szrot_1c = [] + for ix, space in enumerate (spaces_1c): + ifrag, jfrag = space.entmap[0] # must be a tuple of length 2 + ci_szrot_1c.append (space.get_ci_szrot (ifrags=(ifrag,jfrag))) + charges0 = spaces[0].charges + for ix in new_idx: + idx = spaces[ix].excited_fragments (spaces[0]) + space = spaces[ix] + for ifrag in np.where (~idx)[0]: + space.ci[ifrag] = spaces[0].ci[ifrag] + for ifrag in np.where (idx)[0]: + if space.charges[ifrag] != charges0[ifrag]: continue + sf = spin_flips[ifrag] + iflp = sf.smults == space.smults[ifrag] + iflp &= sf.spins == space.spins[ifrag] + assert (np.count_nonzero (iflp) == 1) + iflp = np.where (iflp)[0][0] + space.ci[ifrag] = sf.ci[iflp] + for (ci_i, ci_j), sp_1c in zip (ci_szrot_1c, spaces_1c): + ijfrag = sp_1c.entmap[0] + if ijfrag not in spaces[ix].entmap: continue + if np.any (sp_1c.charges[list(ijfrag)] != space.charges[list(ijfrag)]): continue + if np.any (sp_1c.smults[list(ijfrag)] != space.smults[list(ijfrag)]): continue + ifrag, jfrag = ijfrag + assert (space.ci[ifrag] is None) + assert (space.ci[jfrag] is None) + space.ci[ifrag] = ci_i[space.spins[ifrag]] + space.ci[jfrag] = ci_j[space.spins[jfrag]] + assert (space.has_ci ()) + return spaces + def spin_flip_products (las, spaces, spin_flips, nroots_ref=1): '''Inject spin-flips into las2 in all possible ways''' log = logger.new_logger (las, las.verbose) @@ -309,7 +360,7 @@ def spin_flip_products (las, spaces, spin_flips, nroots_ref=1): spaces = _spin_flip_products (spaces, spin_flips, nroots_ref=nroots_ref) nfrags = spaces[0].nfrag spaces = _spin_shuffle (spaces) - spaces = _spin_shuffle_ci_(spaces) + spaces = _spin_shuffle_ci_(spaces, spin_flips, nroots_ref, las2_nroots) log.info ("LASSIS spin-excitation spaces: %d-%d", las2_nroots, len (spaces)-1) for i, space in enumerate (spaces[las2_nroots:]): if np.any (space.nelec != spaces[0].nelec): @@ -326,8 +377,6 @@ def charge_excitation_products (lsi, spaces, las1): nfrags = lsi.nfrags space0 = spaces[0] i0, j0 = i, j = las1.nroots, len (spaces) - for space1 in spaces[i:j]: - space1.set_entmap_(space0) for product_order in range (2, (nfrags//2)+1): seen = set () for i_list in itertools.combinations (range (i,j), product_order): diff --git a/my_pyscf/lassi/spaces.py b/my_pyscf/lassi/spaces.py index aaef2810..7915a2a1 100644 --- a/my_pyscf/lassi/spaces.py +++ b/my_pyscf/lassi/spaces.py @@ -170,10 +170,14 @@ def has_ci (self): if self.ci is None: return False return all ([c is not None for c in self.ci]) - def get_ci_szrot (self): + def get_ci_szrot (self, ifrags=None): '''Generate the sets of CI vectors in which each vector for each fragment has the sz axis rotated in all possible ways. + Kwargs: + ifrags: list of integers + Optionally restrict ci_sz to particular fragments identified by ifrags + Returns: ci_sz: list of dict of type {integer: ndarray} dict keys are integerified "spin" quantum numbers; i.e., neleca-nelecb. @@ -181,7 +185,8 @@ def get_ci_szrot (self): ''' ci_sz = [] ndet = self.get_ndet () - for ifrag in range (self.nfrag): + if ifrags is None: ifrags = range (self.nfrag) + for ifrag in ifrags: norb, sz, ci = self.nlas[ifrag], self.spins[ifrag], self.ci[ifrag] ndeta, ndetb = ndet[ifrag] nelec = self.neleca[ifrag], self.nelecb[ifrag] From 9f6179a5aa3d0ff397707ff07542bef902a8730b Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 7 May 2024 15:14:58 -0500 Subject: [PATCH 13/87] PySCF version update --- pyscf_version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscf_version.txt b/pyscf_version.txt index 8864a398..ac649743 100644 --- a/pyscf_version.txt +++ b/pyscf_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf.git@6d3b24bb64e2a5edb7990b6e3304068981a33f54 +git+https://github.com/pyscf/pyscf.git@940e4ac16f02eeef3fc944eae190d7f0609a60e7 From 5e4d244b4a20999d6ca31b7fe8ddccdb096b3c48 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 13 May 2024 11:27:45 -0500 Subject: [PATCH 14/87] lassis spin_shuffle_ci nonsinglet ref (#91) --- my_pyscf/lassi/lassis.py | 18 ++++++++++++------ tests/lassi/test_4frag.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index b5aae0a0..f279aa79 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -327,6 +327,7 @@ def _spin_shuffle_ci_(spaces, spin_flips, nroots_ref, nroots_refc): ifrag, jfrag = space.entmap[0] # must be a tuple of length 2 ci_szrot_1c.append (space.get_ci_szrot (ifrags=(ifrag,jfrag))) charges0 = spaces[0].charges + smults0 = spaces[0].smults for ix in new_idx: idx = spaces[ix].excited_fragments (spaces[0]) space = spaces[ix] @@ -334,12 +335,17 @@ def _spin_shuffle_ci_(spaces, spin_flips, nroots_ref, nroots_refc): space.ci[ifrag] = spaces[0].ci[ifrag] for ifrag in np.where (idx)[0]: if space.charges[ifrag] != charges0[ifrag]: continue - sf = spin_flips[ifrag] - iflp = sf.smults == space.smults[ifrag] - iflp &= sf.spins == space.spins[ifrag] - assert (np.count_nonzero (iflp) == 1) - iflp = np.where (iflp)[0][0] - space.ci[ifrag] = sf.ci[iflp] + if space.smults[ifrag] != smults0[ifrag]: + sf = spin_flips[ifrag] + iflp = sf.smults == space.smults[ifrag] + iflp &= sf.spins == space.spins[ifrag] + assert (np.count_nonzero (iflp) == 1) + iflp = np.where (iflp)[0][0] + space.ci[ifrag] = sf.ci[iflp] + else: # Reference-state spin-shuffles + spaces0 = [sp for sp in spaces[:nroots_ref] if sp.spins[ifrag]==space.spins[ifrag]] + assert (len (spaces0)) + space.ci[ifrag] = spaces0[0].ci[ifrag] for (ci_i, ci_j), sp_1c in zip (ci_szrot_1c, spaces_1c): ijfrag = sp_1c.entmap[0] if ijfrag not in spaces[ix].entmap: continue diff --git a/tests/lassi/test_4frag.py b/tests/lassi/test_4frag.py index bb7d2a1f..b997fcad 100644 --- a/tests/lassi/test_4frag.py +++ b/tests/lassi/test_4frag.py @@ -152,6 +152,20 @@ def test_lassis (self): lsi.prepare_states_() self.assertTrue (lsi.converged) + def test_lassis_1111 (self): + xyz='''H 0 0 0 + H 3 0 0 + H 6 0 0 + H 9 0 0''' + mol1 = gto.M (atom=xyz, basis='sto3g', symmetry=False, verbose=0, output='/dev/null') + mf1 = scf.RHF (mol1).run () + + las1 = LASSCF (mf1, (1,1,1,1), ((0,1),(1,0),(0,1),(1,0))) + mo_coeff = las1.localize_init_guess ([[0,],[1,],[2,],[3,]]) + las1.lasci_(mo_coeff) + lsi = LASSIS (las1).run () + self.assertTrue (lsi.converged) + def test_lassis_slow (self): las0 = las.get_single_state_las (state=0) for ifrag in range (len (las0.ci)): From 4e37936ce40b45ec08905f973d00b0063e700fb6 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 16 May 2024 11:25:14 -0500 Subject: [PATCH 15/87] _spin_shuffle_ci_ space coverage Earlier version of this function failed when a spin-flip of one fragment allowed another fragment still in its reference Hilbert space to have a larger range of M values. New version is slightly memory-inefficient, but that is due to the initial spin_shuffle step. --- my_pyscf/lassi/lassis.py | 7 ++++--- tests/lassi/test_4frag.py | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index f279aa79..1881b8b0 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -39,6 +39,7 @@ def prepare_states (lsi, ncharge=1, nspin=0, sa_heff=True, deactivate_vrv=False, las1 = las else: las1 = spin_shuffle (las, equal_weights=True) + # TODO: memory efficiency; the line below makes copies las1.ci = spin_shuffle_ci (las1, las1.ci) las1.converged = las.converged nroots_ref = las1.nroots @@ -328,6 +329,8 @@ def _spin_shuffle_ci_(spaces, spin_flips, nroots_ref, nroots_refc): ci_szrot_1c.append (space.get_ci_szrot (ifrags=(ifrag,jfrag))) charges0 = spaces[0].charges smults0 = spaces[0].smults + # Prepare reference szrots + ci_szrot_ref = spaces[0].get_ci_szrot () for ix in new_idx: idx = spaces[ix].excited_fragments (spaces[0]) space = spaces[ix] @@ -343,9 +346,7 @@ def _spin_shuffle_ci_(spaces, spin_flips, nroots_ref, nroots_refc): iflp = np.where (iflp)[0][0] space.ci[ifrag] = sf.ci[iflp] else: # Reference-state spin-shuffles - spaces0 = [sp for sp in spaces[:nroots_ref] if sp.spins[ifrag]==space.spins[ifrag]] - assert (len (spaces0)) - space.ci[ifrag] = spaces0[0].ci[ifrag] + space.ci[ifrag] = ci_szrot_ref[ifrag][space.spins[ifrag]] for (ci_i, ci_j), sp_1c in zip (ci_szrot_1c, spaces_1c): ijfrag = sp_1c.entmap[0] if ijfrag not in spaces[ix].entmap: continue diff --git a/tests/lassi/test_4frag.py b/tests/lassi/test_4frag.py index b997fcad..8b0b68b4 100644 --- a/tests/lassi/test_4frag.py +++ b/tests/lassi/test_4frag.py @@ -165,6 +165,7 @@ def test_lassis_1111 (self): las1.lasci_(mo_coeff) lsi = LASSIS (las1).run () self.assertTrue (lsi.converged) + self.assertAlmostEqual (lsi.e_roots[0], -1.867291372401379, 6) def test_lassis_slow (self): las0 = las.get_single_state_las (state=0) From 2c5eace5e0a0bdb8cd959c0066afaf18829bb3a4 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 16 May 2024 13:38:50 -0500 Subject: [PATCH 16/87] LASSCF ao2mo abstraction to "loop" Use "loop" instead of "_cderi" to transform 2-electron integrals in LASCI/LASSCF. Should enable PBC in principle. Still needs memory handling. --- my_pyscf/mcscf/lasci.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 4aaa897d..d76ff4bc 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -934,7 +934,9 @@ def ao2mo (self, mo_coeff=None): mo = [mo_coeff, mo_cas, mo_cas, mo_cas] if getattr (self, 'with_df', None) is not None: # Store intermediate with one contracted ao index for faster calculation of exchange! - bPmn = sparsedf_array (self.with_df._cderi) + for cderi in self.with_df.loop (blksize=self.with_df.get_naoaux ()): + pass + bPmn = sparsedf_array (cderi) bmuP = bPmn.contract1 (mo_cas) buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) From 2c5ba6e385f60928bd4055a46c3332e1b4e33537 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 17 May 2024 11:43:10 -0500 Subject: [PATCH 17/87] Better LASSCF ao2mo abstraction to "loop" Discovered in the course of debugging #93: the length of the DF cderi array is not always naoaux. This should now be more general. --- my_pyscf/mcscf/lasci.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index d76ff4bc..ba87a5e1 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -934,8 +934,7 @@ def ao2mo (self, mo_coeff=None): mo = [mo_coeff, mo_cas, mo_cas, mo_cas] if getattr (self, 'with_df', None) is not None: # Store intermediate with one contracted ao index for faster calculation of exchange! - for cderi in self.with_df.loop (blksize=self.with_df.get_naoaux ()): - pass + cderi = np.concatenate ([c for c in self.with_df.loop ()], axis=0) bPmn = sparsedf_array (cderi) bmuP = bPmn.contract1 (mo_cas) buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) From c0e4bc9d75d668362a7cfaf547cf11d94bb319f3 Mon Sep 17 00:00:00 2001 From: Bhavnesh Jangid Date: Fri, 17 May 2024 14:15:27 -0500 Subject: [PATCH 18/87] LASCI at gamma point --- examples/pbc/lasci_gamma_point.py | 74 +++++++++++++++++++++++++++++++ tests/pbc/test_pbc_lasci.py | 35 +++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 examples/pbc/lasci_gamma_point.py create mode 100644 tests/pbc/test_pbc_lasci.py diff --git a/examples/pbc/lasci_gamma_point.py b/examples/pbc/lasci_gamma_point.py new file mode 100644 index 00000000..5173e2a9 --- /dev/null +++ b/examples/pbc/lasci_gamma_point.py @@ -0,0 +1,74 @@ +import numpy +from pyscf import gto, scf +from pyscf import mcscf +from mrh.my_pyscf.mcscf.lasscf_o0 import LASSCF +from mrh.my_pyscf.tools.molden import from_lasscf + +''' +Here I am doing first gamma point LASCI calculation +For reference I did molecular calculation, periodic calculation +with large unit cell (supercell) and at gamma point should be +equals to the molecular values +Outcome of these calculations +1. HF, CACI, and LASCI is converging to molecular value + +Molecular calculations are done without density fitting, but +for periodic calculations FFTDF is by default on. +Here i am using GDF +Probably that's the reason of slow convergence! +''' + +# Molecular Calculation +mol = gto.M(atom = ''' + H -6.37665 2.20769 0.00000 + H -5.81119 2.63374 -0.00000 + ''', + basis = '631g', + verbose = 4,max_memory=10000) + +mf = scf.RHF(mol) +memf = mf.kernel() + +mc = mcscf.CASCI(mf, 2, 2) +mecasci = mc.kernel()[0] + +las = LASSCF(mf, (2,), (2,)) +mo0 = las.localize_init_guess((list(range(2)),)) +melasci = las.lasci(mo0) + +del mol, mf, mc, las + +# Periodic Calculation +from pyscf.pbc import gto, scf + +def cellObject(x): + cell = gto.M(a = numpy.eye(3)*x, + atom = ''' + H -6.37665 2.20769 0.00000 + H -5.81119 2.63374 -0.00000 + ''', + basis = '631g', + verbose = 1, max_memory=10000) + cell.build() + + mf = scf.RHF(cell).density_fit() # GDF + mf.exxdiv = None + emf = mf.kernel() + + mc = mcscf.CASCI(mf, 2, 2) + ecasci = mc.kernel()[0] + + las = LASSCF(mf, (2,), (2,)) + mo0 = las.localize_init_guess((list(range(2)), )) + elasci = las.lasci(mo0) + + del cell, mc, mf, las + return x, emf, ecasci, elasci + +print(" Energy Comparision with cubic unit cell size ") +print(f"{'LatticeVector(a)':<20} {'HF':<20} {'CASCI':<15} {'LASCI':<20}") +print(f"{'Reference':<18} {memf:<18.9f} {mecasci:<18.9f} {melasci[1]:<18.9f}") + +for x in range(3,17, 2): + x, emf, ecasci, elasci = cellObject(x) + print(f"{x:<18.1f} {emf:<18.9f} {ecasci:<18.9f} {elasci[1]:<18.9f}") diff --git a/tests/pbc/test_pbc_lasci.py b/tests/pbc/test_pbc_lasci.py new file mode 100644 index 00000000..4d17456f --- /dev/null +++ b/tests/pbc/test_pbc_lasci.py @@ -0,0 +1,35 @@ +import numpy +import sys +from pyscf import mcscf +from mrh.my_pyscf.mcscf.lasscf_o0 import LASSCF +from pyscf.pbc import gto, scf +import unittest + +class KnownValues(unittest.TestCase): + def test_h2(self): + cell = gto.M(a = numpy.eye(3)*5, + atom = ''' + H -6.37665 2.20769 3.00000 + H -5.81119 2.63374 3.00000 + ''', + basis = 'sto3g', + verbose = 1, max_memory=10000) + cell.output = '/dev/null' + cell.build() + + mf = scf.RHF(cell).density_fit() # GDF + mf.exxdiv = None + emf = mf.kernel() + + mc = mcscf.CASCI(mf, 2, 2) + ecasci = mc.kernel()[0] + + las = LASSCF(mf, (2,), (2,)) + mo0 = las.localize_init_guess((list(range(2)), )) + elasci = las.lasci(mo0)[1] + + self.assertAlmostEqual (elasci, ecasci, 7) + +if __name__ == "__main__": + print("Full Tests for PBC-LASCI") + unittest.main() From 74c82dac4970290867bf09584b2fc469d9180a73 Mon Sep 17 00:00:00 2001 From: Bhavnesh Jangid Date: Fri, 17 May 2024 14:22:43 -0500 Subject: [PATCH 19/87] Two Fragment LASCI at gamma point --- examples/pbc/lasci_gamma_point.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/pbc/lasci_gamma_point.py b/examples/pbc/lasci_gamma_point.py index 5173e2a9..c6015b70 100644 --- a/examples/pbc/lasci_gamma_point.py +++ b/examples/pbc/lasci_gamma_point.py @@ -22,6 +22,8 @@ mol = gto.M(atom = ''' H -6.37665 2.20769 0.00000 H -5.81119 2.63374 -0.00000 + H -6.37665 2.20769 2.00000 + H -5.81119 2.63374 2.00000 ''', basis = '631g', verbose = 4,max_memory=10000) @@ -29,11 +31,11 @@ mf = scf.RHF(mol) memf = mf.kernel() -mc = mcscf.CASCI(mf, 2, 2) +mc = mcscf.CASCI(mf, 4, 4) mecasci = mc.kernel()[0] -las = LASSCF(mf, (2,), (2,)) -mo0 = las.localize_init_guess((list(range(2)),)) +las = LASSCF(mf, (2,2), (2,2)) +mo0 = las.localize_init_guess((list(range(2)), list(range(2, 4)))) melasci = las.lasci(mo0) del mol, mf, mc, las @@ -46,6 +48,8 @@ def cellObject(x): atom = ''' H -6.37665 2.20769 0.00000 H -5.81119 2.63374 -0.00000 + H -6.37665 2.20769 2.00000 + H -5.81119 2.63374 2.00000 ''', basis = '631g', verbose = 1, max_memory=10000) @@ -55,11 +59,11 @@ def cellObject(x): mf.exxdiv = None emf = mf.kernel() - mc = mcscf.CASCI(mf, 2, 2) + mc = mcscf.CASCI(mf, 4, 4) ecasci = mc.kernel()[0] - las = LASSCF(mf, (2,), (2,)) - mo0 = las.localize_init_guess((list(range(2)), )) + las = LASSCF(mf, (2,2), (2,2)) + mo0 = las.localize_init_guess((list(range(2)),list(range(2, 4)))) elasci = las.lasci(mo0) del cell, mc, mf, las @@ -69,6 +73,6 @@ def cellObject(x): print(f"{'LatticeVector(a)':<20} {'HF':<20} {'CASCI':<15} {'LASCI':<20}") print(f"{'Reference':<18} {memf:<18.9f} {mecasci:<18.9f} {melasci[1]:<18.9f}") -for x in range(3,17, 2): +for x in range(7,10,1): x, emf, ecasci, elasci = cellObject(x) print(f"{x:<18.1f} {emf:<18.9f} {ecasci:<18.9f} {elasci[1]:<18.9f}") From bfe529f9ee778b3cf56169eb5454c56933e46df1 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 17 May 2024 16:09:04 -0500 Subject: [PATCH 20/87] Separate get_h2eff from lasci.py Anticipating the implementation of memory handling --- my_pyscf/mcscf/las_ao2mo.py | 50 +++++++++++++++++++++++++++++++++++++ my_pyscf/mcscf/lasci.py | 50 +++---------------------------------- 2 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 my_pyscf/mcscf/las_ao2mo.py diff --git a/my_pyscf/mcscf/las_ao2mo.py b/my_pyscf/mcscf/las_ao2mo.py new file mode 100644 index 00000000..6e552673 --- /dev/null +++ b/my_pyscf/mcscf/las_ao2mo.py @@ -0,0 +1,50 @@ +import numpy as np +from scipy import linalg +from pyscf import ao2mo, lib +from mrh.my_pyscf.df.sparse_df import sparsedf_array + +def get_h2eff (las, mo_coeff=None): + if mo_coeff is None: mo_coeff = las.mo_coeff + nao, nmo = mo_coeff.shape + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + mo_cas = mo_coeff[:,ncore:nocc] + mo = [mo_coeff, mo_cas, mo_cas, mo_cas] + if getattr (las, 'with_df', None) is not None: + # Store intermediate with one contracted ao index for faster calculation of exchange! + cderi = np.concatenate ([c for c in las.with_df.loop ()], axis=0) + bPmn = sparsedf_array (cderi) + bmuP = bPmn.contract1 (mo_cas) + buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) + eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) + eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) + eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) + eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) + if las.verbose > lib.logger.DEBUG: + eri_comp = las.with_df.ao2mo (mo, compact=True) + lib.logger.debug(las,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) + elif getattr (las._scf, '_eri', None) is not None: + eri = ao2mo.incore.general (las._scf._eri, mo, compact=True) + else: + eri = ao2mo.outcore.general_iofree (las.mol, mo, compact=True) + if eri.shape != (nmo,ncas*ncas*(ncas+1)//2): + try: + eri = eri.reshape (nmo, ncas*ncas*(ncas+1)//2) + except ValueError as e: + assert (nmo == ncas), str (e) + eri = ao2mo.restore ('2kl', eri, nmo).reshape (nmo, ncas*ncas*(ncas+1)//2) + return eri + +def get_h2eff_slice (las, h2eff, idx, compact=None): + ncas_cum = np.cumsum ([0] + las.ncas_sub.tolist ()) + i = ncas_cum[idx] + j = ncas_cum[idx+1] + ncore = las.ncore + nocc = ncore + las.ncas + eri = h2eff[ncore:nocc,:].reshape (las.ncas*las.ncas, -1) + ix_i, ix_j = np.tril_indices (las.ncas) + eri = eri[(ix_i*las.ncas)+ix_j,:] + eri = ao2mo.restore (1, eri, las.ncas)[i:j,i:j,i:j,i:j] + if compact: eri = ao2mo.restore (compact, eri, j-i) + return eri + diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index ba87a5e1..19e1a6e4 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -5,7 +5,7 @@ from pyscf import symm, gto, scf, ao2mo, lib from pyscf.fci.direct_spin1 import _unpack_nelec from mrh.my_pyscf.mcscf.addons import state_average_n_mix, get_h1e_zipped_fcisolver, las2cas_civec -from mrh.my_pyscf.mcscf import lasci_sync, _DFLASCI, lasscf_guess +from mrh.my_pyscf.mcscf import lasci_sync, _DFLASCI, lasscf_guess, las_ao2mo from mrh.my_pyscf.fci import csf_solver from mrh.my_pyscf.df.sparse_df import sparsedf_array from mrh.my_pyscf.mcscf import chkfile @@ -925,53 +925,9 @@ def get_mo_slice (self, idx, mo_coeff=None): mo = mo[:,:self.ncas_sub[idx]] return mo - def ao2mo (self, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - nao, nmo = mo_coeff.shape - ncore, ncas = self.ncore, self.ncas - nocc = ncore + ncas - mo_cas = mo_coeff[:,ncore:nocc] - mo = [mo_coeff, mo_cas, mo_cas, mo_cas] - if getattr (self, 'with_df', None) is not None: - # Store intermediate with one contracted ao index for faster calculation of exchange! - cderi = np.concatenate ([c for c in self.with_df.loop ()], axis=0) - bPmn = sparsedf_array (cderi) - bmuP = bPmn.contract1 (mo_cas) - buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) - eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) - eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) - eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) - eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) - if self.verbose > lib.logger.DEBUG: - eri_comp = self.with_df.ao2mo (mo, compact=True) - lib.logger.debug(self,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) - elif getattr (self._scf, '_eri', None) is not None: - eri = ao2mo.incore.general (self._scf._eri, mo, compact=True) - else: - eri = ao2mo.outcore.general_iofree (self.mol, mo, compact=True) - if eri.shape != (nmo,ncas*ncas*(ncas+1)//2): - try: - eri = eri.reshape (nmo, ncas*ncas*(ncas+1)//2) - except ValueError as e: - assert (nmo == ncas), str (e) - eri = ao2mo.restore ('2kl', eri, nmo).reshape (nmo, ncas*ncas*(ncas+1)//2) - return eri - - def get_h2eff_slice (self, h2eff, idx, compact=None): - ncas_cum = np.cumsum ([0] + self.ncas_sub.tolist ()) - i = ncas_cum[idx] - j = ncas_cum[idx+1] - ncore = self.ncore - nocc = ncore + self.ncas - eri = h2eff[ncore:nocc,:].reshape (self.ncas*self.ncas, -1) - ix_i, ix_j = np.tril_indices (self.ncas) - eri = eri[(ix_i*self.ncas)+ix_j,:] - eri = ao2mo.restore (1, eri, self.ncas)[i:j,i:j,i:j,i:j] - if compact: eri = ao2mo.restore (compact, eri, j-i) - return eri - get_h1eff = get_h1las = h1e_for_las = h1e_for_las - get_h2eff = ao2mo + get_h2eff = ao2mo = las_ao2mo.get_h2eff + get_h2eff_slice = las_ao2mo.get_h2eff_slice ''' def get_h2eff (self, mo_coeff=None): if mo_coeff is None: mo_coeff = self.mo_coeff From 005a4e8fc4dc3a00f3babf32b83db9cb526b8e41 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 17 May 2024 20:07:18 -0500 Subject: [PATCH 21/87] DF LAS memory management safety commit --- my_pyscf/mcscf/las_ao2mo.py | 44 +++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/my_pyscf/mcscf/las_ao2mo.py b/my_pyscf/mcscf/las_ao2mo.py index 6e552673..e71d8f7d 100644 --- a/my_pyscf/mcscf/las_ao2mo.py +++ b/my_pyscf/mcscf/las_ao2mo.py @@ -3,6 +3,32 @@ from pyscf import ao2mo, lib from mrh.my_pyscf.df.sparse_df import sparsedf_array +def get_h2eff_df (las, mo_coeff): + # Store intermediate with one contracted ao index for faster calculation of exchange! + nao, nmo = mo_coeff.shape + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + mo_cas = mo_coeff[:,ncore:nocc] + naux = las.with_df.get_naoaux () + mem_eris = 8*nmo*ncas*ncas*(ncas+1) // 2 // 1e6 + mem_av = las.max_memory - lib.current_memory ()[0] - mem_eris + mem_int = 8*naux*ncas*nao // 1e6 + mem_enough_int = mem_av > mem_int + if mem_enough_int: + mem_av -= mem_int + cderi = np.concatenate ([c for c in las.with_df.loop ()], axis=0) + bPmn = sparsedf_array (cderi) + bmuP = bPmn.contract1 (mo_cas) + buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) + eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) + eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) + eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) + eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) + if las.verbose > lib.logger.DEBUG: + eri_comp = las.with_df.ao2mo (mo, compact=True) + lib.logger.debug(las,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) + return eri + def get_h2eff (las, mo_coeff=None): if mo_coeff is None: mo_coeff = las.mo_coeff nao, nmo = mo_coeff.shape @@ -10,19 +36,13 @@ def get_h2eff (las, mo_coeff=None): nocc = ncore + ncas mo_cas = mo_coeff[:,ncore:nocc] mo = [mo_coeff, mo_cas, mo_cas, mo_cas] + mem_eris = 8*nmo*ncas*ncas*(ncas+1) // 2 // 1e6 + mem_remaining = las.max_memory - lib.current_memory ()[0] + if mem_eris > mem_remaining: + raise MemoryError ("{} MB of {}/{} MB av/total for ERI array".format ( + mem_eris, mem_remaining, las.max_memory)) if getattr (las, 'with_df', None) is not None: - # Store intermediate with one contracted ao index for faster calculation of exchange! - cderi = np.concatenate ([c for c in las.with_df.loop ()], axis=0) - bPmn = sparsedf_array (cderi) - bmuP = bPmn.contract1 (mo_cas) - buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) - eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) - eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) - eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) - eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) - if las.verbose > lib.logger.DEBUG: - eri_comp = las.with_df.ao2mo (mo, compact=True) - lib.logger.debug(las,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) + eri = get_h2eff_df (las, mo_coeff) elif getattr (las._scf, '_eri', None) is not None: eri = ao2mo.incore.general (las._scf._eri, mo, compact=True) else: From e766da514d3716d677bcce1e47844d040036d934 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Sun, 19 May 2024 01:05:50 -0500 Subject: [PATCH 22/87] Implemented make_casdm2s - not verified yet --- my_pyscf/mcscf/lasci.py | 308 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 303 insertions(+), 5 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index bece9c69..d554121d 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1,4 +1,5 @@ from pyscf.scf.rohf import get_roothaan_fock +from pyscf import fci from pyscf.fci import cistring from pyscf.mcscf import casci, casci_symm, df from pyscf.tools import dump_mat @@ -11,7 +12,7 @@ from mrh.my_pyscf.mcscf import chkfile from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver from mrh.util.la import matrix_svd_control_options -from itertools import combinations +from itertools import combinations, product from scipy.sparse import linalg as sparse_linalg from scipy import linalg import numpy as np @@ -271,6 +272,7 @@ def h1e_for_las (las, mo_coeff=None, ncas=None, ncore=None, nelecas=None, ci=Non h1e_r = np.empty ((las.nroots, 2, ncas, ncas), dtype=h1e.dtype) h2e = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, ncas, ncas)[ncore:nocc,:,:,:] + #print ("SV h2e = ",h2e, h2e.shape) avgdm1s = np.stack ([linalg.block_diag (*[dm[spin] for dm in casdm1s_sub]) for spin in range (2)], axis=0) for state in range (las.nroots): @@ -1006,7 +1008,7 @@ def states_make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **k for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): if ci_i is None: dm1a = dm1b = np.zeros ((ncas, ncas)) - else: + else: dm1a, dm1b = fcibox.states_make_rdm1s (ci_i, ncas, nelecas) casdm1s.append (np.stack ([dm1a, dm1b], axis=1)) return casdm1s @@ -1043,6 +1045,125 @@ def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=No return [np.einsum ('rijkl,r->ijkl', dm2, box.weights) for dm2, box in zip (casdm2fr, self.fciboxes)] + #SV casdm2s + + #def make_casdm2s(self, ci=None, **kwargs): + # # Make the full-dimensional casdm2s spanning the collective active space + # casdm2s_sub = self.make_casdm2s_sub (ci=ci, **kwargs) + # casdm2a = linalg.block_diag (*[dm[0] for dm in casdm2s_sub]) + # casdm2b = linalg.block_diag (*[dm[1] for dm in casdm2s_sub]) + # return np.stack ([casdm2a, casdm2b], axis=0) + + + #SV casdm2s_sub + def make_casdm2s_sub(self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2frs=None, w=None, **kwargs): + if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + if w is None: w = self.weights + print("SV w = ", w) + return [np.einsum ('rsijkl,r->sijkl', dm2, w) for dm2 in casdm2frs] + + #SV states_make_casdm2s_sub + #There are two ways to construct states_make_casdm2s_sub, one is thisi coming from states_make_casdm1s_sub + + def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + # Spin-separated 2-RDMs in the MO basis for each subspace in sequence + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: + return [np.zeros ((self.nroots,2,ncas,ncas,ncas,ncas)) for ncas in ncas_sub] + casdm2s = [] + for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + if ci_i is None: + dm2aa = dm2ab, dm2bb = np.zeros ((ncas, ncas,ncas,ncas)) + else: + dm2aa, dm2ab, dm2bb = fcibox.states_make_rdm12s (ci_i, ncas, nelecas)[-1] # IDK what this third ncas*4 matrix is, so just created a placeholder for it + casdm2s.append (np.stack ([dm2aa, dm2ab, dm2bb], axis=1)) + return casdm2s + + #SV states_make_casdm2s_sub + #This is the second coming from states_make_casdm2_sub. I think the above one must be the one to be used but the problem is IDK whether states_make_rdm2s exists or not, maybe states_make_rdm12s exists + ''' + def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + #Spin-separated 1-RDMs in the MO basis for each subspace in sequence + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + casdm2s = [] + for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) + return casdm2 + ''' + + #SV states_make_casdm2s + def states_make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, casdm2fr=None, casdm2frs=None, **kwargs): + ''' Make the full-dimensional casdm2s spanning the collective active space ''' + print ("SV entering states_make_casdm2s") + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + print ("SV shape of casdm2fr = ", casdm2fr) + if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + print ("SV casdm2frs =", casdm2frs, "and ", casdm2frs[0] ) # casdm2frs[i] -- [0]=faa, [1]=fab, [2]=fbb for fragment i + + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + + casdm2rs = np.zeros ((self.nroots,3,ncas,ncas,ncas,ncas)) + for isub, dm2 in enumerate (casdm2frs): + print ("SV dm2 = ", dm2, dm2.shape, isub, dm2[:,1]) # dm2 = root,spin,ncas,ncas,ncas,ncas | dm2[:,x] = x spin part of ncas,ncas,ncas,ncas + i = ncas_cum[isub] + j = ncas_cum[isub+1] + for spin in [0,1,2]:#0=aa, 1=ab, 2=bb + casdm2rs[:, spin, i:j, i:j, i:j, i:j] = dm2[:,spin,:,:,:,:] + + for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + for spin in [0,1,2]: + for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2rs[:,spin]): + dma1, dmb1 = dm1s1[0], dm1s1[1] + dma2, dmb2 = dm1s2[0], dm1s2[1] + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) + +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + + return casdm2rs + + #SV make_casdm3_sub + def make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3fr=None, **kwargs): + if casdm3fr is None: casdm3fr = self.states_make_casdm3_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, **kwargs) + for dm3, box in zip(casdm3fr, self.fciboxes): + print ("SV dm3, box = ", dm3, box.weights) + casdm3_sub = np.einsum('rijklpq,r->ijklpq', dm3, box.weights) + return casdm3_sub + + + #SV states_make_casdm3_sub + def states_make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + ''' Spin-separated 3-RDMs in the MO basis for each subspace in sequence ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + casdm3 = [] + for ci_i, ncas, nel in zip (ci, ncas_sub, nelecas_sub): + casdm3.append (fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel)[-1]) + return casdm3 + def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, nelecas_sub=None, casdm1rs=None, casdm1frs=None, **kwargs): if mo_coeff is None: mo_coeff = self.mo_coeff @@ -1190,7 +1311,58 @@ def state_make_casdm2(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) return casdm2 - + + #SV make_casdm2s + def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2rs=None, casdm2fs=None, casdm1frs=None, casdm2frs=None, + **kwargs): + + if casdm2rs is not None: + return np.einsum ('rsijkl,r->sijkl', casdm2rs, self.weights) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2fs is None: casdm2fs = self.make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2frs=casdm2frs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + weights = self.weights + casdm2s = np.zeros ((3,ncas,ncas,ncas,ncas)) + + # Diagonal of aa,ab,bb + for isub, dm2 in enumerate (casdm2fs): + print ("SV dm2 = ", dm2[1], dm2.shape) # dm2 = 3,ncas_sub*4 + i = ncas_cum[isub] + j = ncas_cum[isub+1] + for spin in [0,1,2]: + casdm2s[spin][i:j, i:j, i:j, i:j] = dm2[spin] + + # Off-diagonal + for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] + dm1r = dma1r + dmb1r + dm2r = dma2r + dmb2r + for spin in [0,1,2]: + # Coulomb slice + casdm2s[spin][i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) + casdm2s[spin][k:l, k:l, i:j, i:j] = casdm2s[spin][i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) + casdm2s[spin][i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) + casdm2s[spin][k:l, i:j, i:j, k:l] = casdm2s[spin][i:j, k:l, k:l, i:j].transpose (1,0,3,2) + + print ("SV casdm2s = ", casdm2s) + return casdm2s + + def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, **kwargs): @@ -1230,8 +1402,134 @@ def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2 + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) # IDU my eqn says [2,3,0,1] + return casdm2 + + #SV make_casdm3 + def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm3r=None, casdm3f=None, casdm2frs=None, casdm3fr=None, casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, + **kwargs): + ''' Make the full-dimensional casdm3 spanning the collective active space ''' + if casdm3r is not None: + return np.einsum ('rijklmn,r->ijklmn', casdm3r, self.weights) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) + #if casdm3f is None: casdm3f = self.make_casdm3_sub (ci=ci, + # ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm3fr=casdm3fr) + if casdm3f is None: casdm3f = self.states_make_casdm3_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + print ("SV ncas_cum = ", ncas_cum) + weights = self.weights + casdm3 = np.zeros ((ncas,ncas,ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm3 in enumerate (casdm3f): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 + # Off-diagonal + for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + m = ncas_cum[isub3] + n = ncas_cum[isub3+1] + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] # size of 2-RDMs + dma3r, dmb3r = dm1rs3[:,0], dm1rs3[:,1] + dm1r = dma1r + dmb1r + dm2r = dma2r + dmb2r + #print ("SV dm1r, dm2r = ", dm1rs1, dma2r) + #print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) + + # Term 1- 61a + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) + casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) + casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,0,1,2,3) + casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,2,3,0,1) + casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) + casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) + + # Term 2- 61b + d3sigma = (lib.einsum('r,rkl,rmn->knml',weights,dma2r,dma3r)+lib.einsum('r,rkl,rmn->knml',weights,dmb2r,dmb3r)) + casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= lib.einsum('ij,klmn->ijklmn', dm1r, d3sigma) + casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) + casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) + casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) + casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,2,3,0,1) + casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,0,1,2,3) + + # Term 3- 61c + d3sigma = (lib.einsum('r,rij,rkl->ilkj',weights,dma1r,dma2r)+lib.einsum('r,rij,rkl->ilkj',weights,dmb1r,dmb2r)) + casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= lib.einsum('ijkl,mn->ijklmn', d3sigma, dm3r) + casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) + casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) + casdm3[k:l, i:j, m:n, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,4,5,0,1) + casdm3[m:n, m:n, k:l, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,2,3,0,1) + casdm3[m:n, m:n, i:j, k:l, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,0,1,2,3) + + #Terms 4- 61d + d3sigma = (lib.einsum('r,rij,rkl,rmn->ilknmj',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->ilknmj',weights,dmb1r,dmb2r,dm3br)) + casdm3[i:j, k:l, k:l, m:n, m:n, i:j] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(0,1,4,5,2,3) + casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,0,1,2,3) + casdm3[m:n, i:j, k:l, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,2,3,0,1) + casdm3[k:l, m:n, m:n, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,4,5,0,1) + casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,0,1,4,5) + + #Terms 5- 61e + d3sigma = (lib.einsum('r,rij,rkl,rmn->inklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->inklmj',weights,dmb1r,dm2r,dmb3r)) + casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) + casdm3[m:n, i:j, i:j, m:n, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,0,1,2,3) + casdm3[m:n, i:j, k:l, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,2,3,0,1) + casdm3[k:l, k:l, m:n, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,4,5,0,1) + casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) + + #Terms 6- 61f + d3sigma = (lib.einsum('r,rij,rkl,rmn->inkjml',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->ilknmj',weights,dmb1r,dmb2r,dm3br)) + casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) + + #Last 3 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 + + for (isub1, dm1rs1), (isub2, dm2rs2) in product(enumerate(casdm1frs), enumerate(casdm2frs)): + i = ncas_cum[isub2] + j = ncas_cum[isub2+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + m = ncas_cum[isub1] + n = ncas_cum[isub1+1] + + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs + #print ("SV dm2rs2 = ", dm2rs2, dm2rs2.shape, dm2rs2[:,1]) + dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] # size of 2-RDMs + dm1r = dma1r + dmb1r + + d3sigma = (lib.einsum('r,rmn,rijkl->mnkjil',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dmb1r,dmbb2r)) + casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) + + return casdm3 + def get_veff (self, mol=None, dm1s=None, hermi=1, spin_sep=False, **kwargs): ''' Returns a spin-summed veff! If dm1s isn't provided, builds from self.mo_coeff, self.ci From 0aa8ff68fc4a9733b3c140ba6983a4df77e26e45 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Sun, 19 May 2024 15:46:58 -0500 Subject: [PATCH 23/87] Adding all casdm3 functions: not tested yet --- my_pyscf/mcscf/lasci.py | 53 ++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index d554121d..33b1f350 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1356,6 +1356,7 @@ def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, # Exchange slice d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) + print ("SV d2exc = ", d2exc, d2exc.shape) casdm2s[spin][i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) casdm2s[spin][k:l, i:j, i:j, k:l] = casdm2s[spin][i:j, k:l, k:l, i:j].transpose (1,0,3,2) @@ -1450,7 +1451,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, dm1r = dma1r + dmb1r dm2r = dma2r + dmb2r #print ("SV dm1r, dm2r = ", dm1rs1, dma2r) - #print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) + print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) # Term 1- 61a casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) @@ -1461,7 +1462,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) # Term 2- 61b - d3sigma = (lib.einsum('r,rkl,rmn->knml',weights,dma2r,dma3r)+lib.einsum('r,rkl,rmn->knml',weights,dmb2r,dmb3r)) + d3sigma = (lib.einsum('r,rkl,rmn->rknml',weights,dma2r,dma3r)+lib.einsum('r,rkl,rmn->rknml',weights,dmb2r,dmb3r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= lib.einsum('ij,klmn->ijklmn', dm1r, d3sigma) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) @@ -1470,7 +1471,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,0,1,2,3) # Term 3- 61c - d3sigma = (lib.einsum('r,rij,rkl->ilkj',weights,dma1r,dma2r)+lib.einsum('r,rij,rkl->ilkj',weights,dmb1r,dmb2r)) + d3sigma = (lib.einsum('r,rij,rkl->rilkj',weights,dma1r,dma2r)+lib.einsum('r,rij,rkl->rilkj',weights,dmb1r,dmb2r)) casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= lib.einsum('ijkl,mn->ijklmn', d3sigma, dm3r) casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) @@ -1479,7 +1480,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[m:n, m:n, i:j, k:l, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,0,1,2,3) #Terms 4- 61d - d3sigma = (lib.einsum('r,rij,rkl,rmn->ilknmj',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->ilknmj',weights,dmb1r,dmb2r,dm3br)) + d3sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) casdm3[i:j, k:l, k:l, m:n, m:n, i:j] = np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(0,1,4,5,2,3) casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,0,1,2,3) @@ -1488,7 +1489,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,0,1,4,5) #Terms 5- 61e - d3sigma = (lib.einsum('r,rij,rkl,rmn->inklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->inklmj',weights,dmb1r,dm2r,dmb3r)) + d3sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) casdm3[m:n, i:j, i:j, m:n, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,0,1,2,3) @@ -1497,16 +1498,49 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) #Terms 6- 61f - d3sigma = (lib.einsum('r,rij,rkl,rmn->inkjml',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->ilknmj',weights,dmb1r,dmb2r,dm3br)) + d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) + print ("SV d3sigma = ", d3sigma.shape) casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) - #Last 3 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 + #Last 3 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 + for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub1] + l = ncas_cum[isub1+1] + m = ncas_cum[isub2] + n = ncas_cum[isub2+1] + + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dm1r = dma1r + dmb1r + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] + dm2r = dma2r + dmb2r + + # Terms 7- 61g + d3sigma = (lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm1r,dm2r)-(lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dma2r))+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dmb2r)) + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) + casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) + casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) + casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) + casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) + + # Terms 8- 61h + d3sigma = (-lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma1r,dma2r)-lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijknml',weights,dma1r,dma1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijknml',weights,dmb1r,dmb1r,dmb2r)) + casdm3[i:j, i:j, k:l, m:n, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) + + # Terms 9- 61i for (isub1, dm1rs1), (isub2, dm2rs2) in product(enumerate(casdm1frs), enumerate(casdm2frs)): i = ncas_cum[isub2] j = ncas_cum[isub2+1] @@ -1519,8 +1553,9 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, #print ("SV dm2rs2 = ", dm2rs2, dm2rs2.shape, dm2rs2[:,1]) dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] # size of 2-RDMs dm1r = dma1r + dmb1r - - d3sigma = (lib.einsum('r,rmn,rijkl->mnkjil',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dmb1r,dmbb2r)) + print ("SV dm1r = ", dm1r.shape) + d3sigma = (lib.einsum('r,rmn,rijkl->rmnkjil',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rmnkjil',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rmnkjil',weights,dmb1r,dmbb2r)) + print ("SV d3sigma = ", d3sigma.shape) casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) From 241384165bb2b74bf3b75d4612264ee69f6548e3 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 20 May 2024 13:40:55 -0500 Subject: [PATCH 24/87] DF LAS memory management safety commit --- my_pyscf/mcscf/las_ao2mo.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/my_pyscf/mcscf/las_ao2mo.py b/my_pyscf/mcscf/las_ao2mo.py index e71d8f7d..afb1e3ce 100644 --- a/my_pyscf/mcscf/las_ao2mo.py +++ b/my_pyscf/mcscf/las_ao2mo.py @@ -16,14 +16,26 @@ def get_h2eff_df (las, mo_coeff): mem_enough_int = mem_av > mem_int if mem_enough_int: mem_av -= mem_int - cderi = np.concatenate ([c for c in las.with_df.loop ()], axis=0) - bPmn = sparsedf_array (cderi) - bmuP = bPmn.contract1 (mo_cas) - buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) - eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) - eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) - eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) - eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) + bmuP = [] + mem_per_aux = nao*(nao+1)//2 # cderi / bPmn + mem_per_aux += nao*ncas # bmuP + mem_per_aux += ncas*ncas # buvP + mem_per_aux += nao*lib.num_threads () # wrk in contract1 + mem_per_aux *= 8 // 1e6 + mem_per_aux = max (1, mem_per_aux) + blksize = max (1, min (naux, int (mem_av // mem_per_aux))) + assert (blksize>1) + eri = 0 + for cderi in las.with_df.loop (blksize=blksize): + bPmn = sparsedf_array (cderi) + bmuP1 = bPmn.contract1 (mo_cas) + if mem_enough_int: bmuP.append (bmuP1) + buvP = np.tensordot (mo_cas.conjugate (), bmuP1, axes=((0),(0))) + eri1 = np.tensordot (bmuP1, buvP, axes=((2),(2))) + eri1 = np.tensordot (mo_coeff.conjugate (), eri1, axes=((0),(0))) + eri += lib.pack_tril (eri1.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) + if mem_enough_int: + eri = lib.tag_array (eri, bmPu=np.concatenate (bmuP, axis=-1).transpose (0,2,1)) if las.verbose > lib.logger.DEBUG: eri_comp = las.with_df.ao2mo (mo, compact=True) lib.logger.debug(las,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) @@ -37,6 +49,8 @@ def get_h2eff (las, mo_coeff=None): mo_cas = mo_coeff[:,ncore:nocc] mo = [mo_coeff, mo_cas, mo_cas, mo_cas] mem_eris = 8*nmo*ncas*ncas*(ncas+1) // 2 // 1e6 + mem_eris += 8*nao*ncas*ncas*ncas // 1e6 # intermediate + mem_eris += 8*lib.num_threads ()*nao*nmo // 1e6 # intermediate mem_remaining = las.max_memory - lib.current_memory ()[0] if mem_eris > mem_remaining: raise MemoryError ("{} MB of {}/{} MB av/total for ERI array".format ( From fa20737e6938fb2c9322b35d8ecd2ebeb79e6c81 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Mon, 20 May 2024 14:17:19 -0500 Subject: [PATCH 25/87] modified 9th term of eqn 61 in make_casdm3 --- my_pyscf/mcscf/lasci.py | 42 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 33b1f350..3b791faa 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1149,19 +1149,21 @@ def make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3fr=No nelecas_sub=nelecas_sub, **kwargs) for dm3, box in zip(casdm3fr, self.fciboxes): print ("SV dm3, box = ", dm3, box.weights) - casdm3_sub = np.einsum('rijklpq,r->ijklpq', dm3, box.weights) + casdm3_sub = np.einsum('rijklmn,r->ijklmn', dm3, box.weights) + #casdm3_sub = np.einsum('rijklmn,r->ijklpq', dm3, box.weights) return casdm3_sub #SV states_make_casdm3_sub def states_make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 3-RDMs in the MO basis for each subspace in sequence ''' + ''' Spin-separated 3-RDMs in the MO basis for each subspace in sequence, currently this does not have weights so it's not really a STATES ''' if ci is None: ci = self.ci if ncas_sub is None: ncas_sub = self.ncas_sub if nelecas_sub is None: nelecas_sub = self.nelecas_sub casdm3 = [] for ci_i, ncas, nel in zip (ci, ncas_sub, nelecas_sub): casdm3.append (fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel)[-1]) + #print ("SV casdm3_sub = ", casdm3_sub return casdm3 def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, @@ -1434,9 +1436,11 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3 = np.zeros ((ncas,ncas,ncas,ncas,ncas,ncas)) # Diagonal for isub, dm3 in enumerate (casdm3f): + print ("SV dm3 = ", dm3, isub, dm3.shape) i = ncas_cum[isub] j = ncas_cum[isub+1] casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 + print ("SV casdm3 = ", casdm3) # Off-diagonal for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): i = ncas_cum[isub1] @@ -1524,7 +1528,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, # Terms 7- 61g d3sigma = (lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm1r,dm2r)-(lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dma2r))+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dmb2r)) - casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = d3sigma#np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) @@ -1541,27 +1545,31 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) # Terms 9- 61i + for (isub1, dm1rs1), (isub2, dm2rs2) in product(enumerate(casdm1frs), enumerate(casdm2frs)): + # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same fragment but they must come from different frags! i = ncas_cum[isub2] j = ncas_cum[isub2+1] k = ncas_cum[isub2] l = ncas_cum[isub2+1] m = ncas_cum[isub1] n = ncas_cum[isub1+1] - - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs - #print ("SV dm2rs2 = ", dm2rs2, dm2rs2.shape, dm2rs2[:,1]) - dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] # size of 2-RDMs - dm1r = dma1r + dmb1r - print ("SV dm1r = ", dm1r.shape) - d3sigma = (lib.einsum('r,rmn,rijkl->rmnkjil',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rmnkjil',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rmnkjil',weights,dmb1r,dmbb2r)) - print ("SV d3sigma = ", d3sigma.shape) - casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) + + if isub1 != isub2: + + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs + #print ("SV dm2rs2 = ", dm2rs2, dm2rs2.shape, dm2rs2[:,1]) + dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] # size of 2-RDMs + dm1r = dma1r + dmb1r + print ("SV dm1r = ", dm1r.shape) + d3sigma = (lib.einsum('r,rmn,rijkl->rmnkjil',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rmnkjil',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rmnkjil',weights,dmb1r,dmbb2r)) + print ("SV d3sigma = ", d3sigma.shape) + casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) return casdm3 From f36df2ef0b2fac118e55af79d8cc16902fc1cba9 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 20 May 2024 19:20:20 -0500 Subject: [PATCH 26/87] LASCI/LASSCF DF ERI memory management Choose blocksize of with_df.loop () to fill max_memory. There is a factor of 3 in the actual memory footprint that I don't quite understand... --- my_pyscf/mcscf/las_ao2mo.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/my_pyscf/mcscf/las_ao2mo.py b/my_pyscf/mcscf/las_ao2mo.py index afb1e3ce..6a24bde4 100644 --- a/my_pyscf/mcscf/las_ao2mo.py +++ b/my_pyscf/mcscf/las_ao2mo.py @@ -5,35 +5,54 @@ def get_h2eff_df (las, mo_coeff): # Store intermediate with one contracted ao index for faster calculation of exchange! + log = lib.logger.new_logger (las, las.verbose) nao, nmo = mo_coeff.shape ncore, ncas = las.ncore, las.ncas nocc = ncore + ncas mo_cas = mo_coeff[:,ncore:nocc] naux = las.with_df.get_naoaux () - mem_eris = 8*nmo*ncas*ncas*(ncas+1) // 2 // 1e6 + log.debug2 ("LAS DF ERIs: %d MB used of %d MB total available", lib.current_memory ()[0], las.max_memory) + mem_eris = 8*(nao+nmo)*ncas*ncas*ncas / 1e6 + mem_eris += 8*lib.num_threads ()*nao*nmo / 1e6 mem_av = las.max_memory - lib.current_memory ()[0] - mem_eris - mem_int = 8*naux*ncas*nao // 1e6 + mem_int = 16*naux*ncas*nao / 1e6 mem_enough_int = mem_av > mem_int if mem_enough_int: mem_av -= mem_int bmuP = [] - mem_per_aux = nao*(nao+1)//2 # cderi / bPmn - mem_per_aux += nao*ncas # bmuP + log.debug ("LAS DF ERI including intermediate cache") + else: + log.debug ("LAS DF ERI not including intermediate cache") + safety_factor = 1.1 + mem_per_aux = nao*ncas # bmuP mem_per_aux += ncas*ncas # buvP mem_per_aux += nao*lib.num_threads () # wrk in contract1 - mem_per_aux *= 8 // 1e6 + if not isinstance (getattr (las.with_df, '_cderi', None), np.ndarray): + mem_per_aux += 3*nao*(nao+1)//2 # cderi / bPmn + # NOTE: I think a linalg.norm operation in sparsedf_array might be doubling the memory + # footprint of bPmn below + else: + mem_per_aux += nao*(nao+1) # see note above + mem_per_aux *= safety_factor * 8 / 1e6 mem_per_aux = max (1, mem_per_aux) - blksize = max (1, min (naux, int (mem_av // mem_per_aux))) + blksize = max (1, min (naux, int (mem_av / mem_per_aux))) assert (blksize>1) + log.debug2 ("LAS DF ERI blksize = %d, mem_av = %d MB, mem_per_aux = %d MB", blksize, mem_av, mem_per_aux) + log.debug2 ("LAS DF ERI naux = %d, nao = %d, nmo = %d", naux, nao, nmo) eri = 0 for cderi in las.with_df.loop (blksize=blksize): bPmn = sparsedf_array (cderi) + log.debug2 ("LAS DF ERI bPmn shape = %s; shares memory? %s %s; C_CONTIGUOUS? %s", + str (bPmn.shape), str (np.shares_memory (bPmn, cderi)), + str (np.may_share_memory (bPmn, cderi)), + str (bPmn.flags['C_CONTIGUOUS'])) bmuP1 = bPmn.contract1 (mo_cas) if mem_enough_int: bmuP.append (bmuP1) buvP = np.tensordot (mo_cas.conjugate (), bmuP1, axes=((0),(0))) eri1 = np.tensordot (bmuP1, buvP, axes=((2),(2))) eri1 = np.tensordot (mo_coeff.conjugate (), eri1, axes=((0),(0))) eri += lib.pack_tril (eri1.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) + cderi = bPmn = bmuP1 = buvP = eri1 = None if mem_enough_int: eri = lib.tag_array (eri, bmPu=np.concatenate (bmuP, axis=-1).transpose (0,2,1)) if las.verbose > lib.logger.DEBUG: @@ -48,8 +67,7 @@ def get_h2eff (las, mo_coeff=None): nocc = ncore + ncas mo_cas = mo_coeff[:,ncore:nocc] mo = [mo_coeff, mo_cas, mo_cas, mo_cas] - mem_eris = 8*nmo*ncas*ncas*(ncas+1) // 2 // 1e6 - mem_eris += 8*nao*ncas*ncas*ncas // 1e6 # intermediate + mem_eris = 8*(nao+nmo)*ncas*ncas*ncas // 1e6 mem_eris += 8*lib.num_threads ()*nao*nmo // 1e6 # intermediate mem_remaining = las.max_memory - lib.current_memory ()[0] if mem_eris > mem_remaining: From d91fc3a6b0fc03197e1e78a7b0f3a13dcaf8f85d Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 21 May 2024 13:52:00 -0500 Subject: [PATCH 27/87] implement lasci.get_h2cas (only (aa|aa) ERIs) LASCI and LASSI calculations do not need the (pa|aa) ERIs, let alone the cached CDERI intermediates. --- my_pyscf/lassi/lassi.py | 11 ++++++----- my_pyscf/mcscf/las_ao2mo.py | 17 +++++++++++++++++ my_pyscf/mcscf/lasci.py | 19 +++++++++---------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/my_pyscf/lassi/lassi.py b/my_pyscf/lassi/lassi.py index a32aa605..ddfac37b 100644 --- a/my_pyscf/lassi/lassi.py +++ b/my_pyscf/lassi/lassi.py @@ -4,7 +4,7 @@ from mrh.my_pyscf.lassi import op_o0 from mrh.my_pyscf.lassi import op_o1 from mrh.my_pyscf.lassi.citools import get_lroots -from pyscf import lib, symm +from pyscf import lib, symm, ao2mo from pyscf.scf.addons import canonical_orth_ from pyscf.lib.numpy_helper import tag_array from pyscf.fci.direct_spin1 import _unpack_nelec @@ -80,8 +80,6 @@ def ham_2q (las, mo_coeff, veff_c=None, h2eff_sub=None, soc=0): if veff_c is None: dm_core = 2 * mo_core @ mo_core.conj ().T veff_c = las.get_veff (dm1s=dm_core) - if h2eff_sub is None: - h2eff_sub = las.ao2mo (mo_coeff) h0 = las._scf.energy_nuc () + 2 * (((hcore + veff_c/2) @ mo_core) * mo_core).sum () @@ -97,8 +95,11 @@ def ham_2q (las, mo_coeff, veff_c=None, h2eff_sub=None, soc=0): h1[0:ncas,0:ncas] += hso[2] # a'a h1[ncas:2*ncas,ncas:2*ncas] -= hso[2] # b'b - h2 = h2eff_sub[ncore:nocc].reshape (ncas*ncas, ncas * (ncas+1) // 2) - h2 = lib.numpy_helper.unpack_tril (h2).reshape (ncas, ncas, ncas, ncas) + if h2eff_sub is None: + h2 = las.get_h2cas (mo_coeff) + else: + h2 = h2eff_sub[ncore:nocc].reshape (ncas*ncas, ncas * (ncas+1) // 2) + h2 = lib.numpy_helper.unpack_tril (h2).reshape (ncas, ncas, ncas, ncas) return h0, h1, h2 diff --git a/my_pyscf/mcscf/las_ao2mo.py b/my_pyscf/mcscf/las_ao2mo.py index 6a24bde4..6293c357 100644 --- a/my_pyscf/mcscf/las_ao2mo.py +++ b/my_pyscf/mcscf/las_ao2mo.py @@ -100,3 +100,20 @@ def get_h2eff_slice (las, h2eff, idx, compact=None): if compact: eri = ao2mo.restore (compact, eri, j-i) return eri +def get_h2cas (las, mo_coeff=None): + ''' Get the 2-electron integral of the active orbitals only ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + nao, nmo = mo_coeff.shape + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + mo_cas = mo_coeff[:,ncore:nocc] + if getattr (las, 'with_df', None) is not None: + eri = las.with_df.ao2mo (mo_cas) + elif getattr (las._scf, '_eri', None) is not None: + eri = ao2mo.full (las._scf._eri, mo_cas, max_memory=las.max_memory) + else: + eri = ao2mo.full (las.mol, mo_cas, verbose=las.verbose, max_memory=las.max_memory) + return ao2mo.restore (1, eri, ncas) + + + diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 19e1a6e4..61b9da2d 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -485,18 +485,19 @@ def canonicalize (las, mo_coeff=None, ci=None, casdm1fs=None, natorb_casdm1=None return mo_coeff, mo_ene, mo_occ, ci, h2eff_sub -def get_init_guess_ci (las, mo_coeff=None, h2eff_sub=None, ci0=None): +def get_init_guess_ci (las, mo_coeff=None, h2eff_sub=None, ci0=None, eri_cas=None): if mo_coeff is None: mo_coeff = las.mo_coeff if ci0 is None: ci0 = [[None for i in range (las.nroots)] for j in range (las.nfrags)] - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) nmo = mo_coeff.shape[-1] ncore, ncas = las.ncore, las.ncas nocc = ncore + ncas + if eri_cas is None: + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + eri_cas = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)) + eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) + eri_cas = eri_cas[ncore:nocc] dm1_core= 2 * mo_coeff[:,:ncore] @ mo_coeff[:,:ncore].conj ().T h1e_ao = las._scf.get_fock (dm=dm1_core) - eri_cas = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)) - eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) - eri_cas = eri_cas[ncore:nocc] for ix, (fcibox, norb, nelecas) in enumerate (zip (las.fciboxes,las.ncas_sub,las.nelecas_sub)): i = sum (las.ncas_sub[:ix]) j = i + norb @@ -812,13 +813,10 @@ def run_lasci (las, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose h1eff, energy_core = las.h1e_for_cas (mo_coeff=mo_coeff, ncas=las.ncas, ncore=las.ncore) - h2eff = las.get_h2eff (mo_coeff) + eri_cas = las.get_h2cas (mo_coeff) if (ci0 is None or any ([c is None for c in ci0]) or any ([any ([c2 is None for c2 in c1]) for c1 in ci0])): - ci0 = las.get_init_guess_ci (mo_coeff, h2eff, ci0) - eri_cas = lib.numpy_helper.unpack_tril ( - h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, - ncas, ncas)[ncore:nocc] + ci0 = las.get_init_guess_ci (mo_coeff, ci0=ci0, eri_cas=eri_cas) e_cas = np.empty (las.nroots) e_states = np.empty (las.nroots) @@ -927,6 +925,7 @@ def get_mo_slice (self, idx, mo_coeff=None): get_h1eff = get_h1las = h1e_for_las = h1e_for_las get_h2eff = ao2mo = las_ao2mo.get_h2eff + get_h2cas = las_ao2mo.get_h2cas get_h2eff_slice = las_ao2mo.get_h2eff_slice ''' def get_h2eff (self, mo_coeff=None): From 9e90c3a1f8d0f4bd7ece0187dfae142d5b99529a Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 21 May 2024 14:48:57 -0500 Subject: [PATCH 28/87] Minimize ERI recomputation in LASSI(S) --- my_pyscf/lassi/lassi.py | 5 ++++- my_pyscf/lassi/lassis.py | 32 ++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/my_pyscf/lassi/lassi.py b/my_pyscf/lassi/lassi.py index ddfac37b..4f553e52 100644 --- a/my_pyscf/lassi/lassi.py +++ b/my_pyscf/lassi/lassi.py @@ -249,7 +249,10 @@ def lassi (las, mo_coeff=None, ci=None, veff_c=None, h2eff_sub=None, orbsym=None raise RuntimeError ('Insufficient memory to use o0 LASSI algorithm') # Construct second-quantization Hamiltonian - e0, h1, h2 = ham_2q (las, mo_coeff, veff_c=veff_c, h2eff_sub=h2eff_sub, soc=soc) + if callable (getattr (las, 'ham_2q', None)): + e0, h1, h2 = las.ham_2q (mo_coeff, veff_c=veff_c, h2eff_sub=h2eff_sub, soc=soc) + else: + e0, h1, h2 = ham_2q (las, mo_coeff, veff_c=veff_c, h2eff_sub=h2eff_sub, soc=soc) # Symmetry tuple: neleca, nelecb, irrep statesym, s2_states = las_symm_tuple (las, break_spin=soc, break_symmetry=break_symmetry) diff --git a/my_pyscf/lassi/lassis.py b/my_pyscf/lassi/lassis.py index 1881b8b0..96053d76 100644 --- a/my_pyscf/lassi/lassis.py +++ b/my_pyscf/lassi/lassis.py @@ -31,6 +31,7 @@ def prepare_states (lsi, ncharge=1, nspin=0, sa_heff=True, deactivate_vrv=False, # TODO: make states_energy_elec capable of handling lroots and address inconsistency # between definition of e_states array for neutral and charge-separated rootspaces t0 = (logger.process_clock (), logger.perf_counter ()) + ham_2q = lsi.ham_2q () log = logger.new_logger (lsi, lsi.verbose) las = lsi._las.get_single_state_las (state=0) # 1. Spin shuffle step @@ -51,7 +52,7 @@ def prepare_states (lsi, ncharge=1, nspin=0, sa_heff=True, deactivate_vrv=False, log.info ("Reference space %d:", ix) SingleLASRootspace (las1, m, s, c, 0, ci=[c[ix] for c in las1.ci]).table_printlog () # 2. Spin excitations part 1 - spin_flips = all_spin_flips (lsi, las1, nspin=nspin) if nspin else None + spin_flips = all_spin_flips (lsi, las1, nspin=nspin, ham_2q=ham_2q) if nspin else None las1.e_states = las1.energy_nuc () + np.array (las1.states_energy_elec ()) # 3. Charge excitations # TODO: Store the irreducible degrees of freedom of the charge excitations more transparently, @@ -60,7 +61,7 @@ def prepare_states (lsi, ncharge=1, nspin=0, sa_heff=True, deactivate_vrv=False, las2 = all_single_excitations (las1) converged, spaces2 = single_excitations_ci ( lsi, las2, las1, ncharge=ncharge, sa_heff=sa_heff, deactivate_vrv=deactivate_vrv, - spin_flips=spin_flips, crash_locmin=crash_locmin + spin_flips=spin_flips, crash_locmin=crash_locmin, ham_2q=ham_2q ) else: converged = las1.converged @@ -85,7 +86,7 @@ def prepare_states (lsi, ncharge=1, nspin=0, sa_heff=True, deactivate_vrv=False, return converged, las3 def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_vrv=False, - spin_flips=None, crash_locmin=False): + spin_flips=None, crash_locmin=False, ham_2q=None): log = logger.new_logger (lsi, lsi.verbose) mol = lsi.mol nfrags = lsi.nfrags @@ -103,7 +104,10 @@ def single_excitations_ci (lsi, las2, las1, ncharge=1, sa_heff=True, deactivate_ else: raise RuntimeError ("Valid ncharge values are integers or 's'") lroots = np.minimum (ncharge, ncsf) - h0, h1, h2 = lsi.ham_2q () + if ham_2q is None: + h0, h1, h2 = lsi.ham_2q () + else: + h0, h1, h2 = ham_2q t0 = (logger.process_clock (), logger.perf_counter ()) converged = True # Prefilter spin-shuffles @@ -185,7 +189,7 @@ def __init__(self, ci, spins, smults): self.spins = spins self.smults = smults -def all_spin_flips (lsi, las, nspin=1): +def all_spin_flips (lsi, las, nspin=1, ham_2q=None): # NOTE: this actually only uses the -first- rootspace in las, so it can be done before # the initial spin shuffle log = logger.new_logger (lsi, lsi.verbose) @@ -203,7 +207,10 @@ def all_spin_flips (lsi, las, nspin=1): smults1 = [] spins1 = [] ci1 = [] - h0, h1, h2 = lsi.ham_2q () + if ham_2q is None: + h0, h1, h2 = lsi.ham_2q () + else: + h0, h1, h2 = ham_2q casdm1s = las.make_casdm1s () f1 = h1 + np.tensordot (h2, casdm1s.sum (0), axes=2) f1 = f1[None,:,:] - np.tensordot (casdm1s, h2, axes=((1,2),(2,1))) @@ -485,10 +492,15 @@ def kernel (self, ncharge=None, nspin=None, sa_heff=None, deactivate_vrv=None, crash_locmin=None, **kwargs): t0 = (logger.process_clock (), logger.perf_counter ()) log = logger.new_logger (self, self.verbose) - self.converged = self.prepare_states_(ncharge=ncharge, nspin=nspin, - sa_heff=sa_heff, deactivate_vrv=deactivate_vrv, - crash_locmin=crash_locmin) - self.e_roots, self.si = self.eig (**kwargs) + h0, h1, h2 = self.ham_2q () + t1 = log.timer ("LASSIS integral transformation", *t0) + with lib.temporary_env (self, ham_2q=lambda *args, **kwargs: (h0, h1, h2)): + self.converged = self.prepare_states_(ncharge=ncharge, nspin=nspin, + sa_heff=sa_heff, deactivate_vrv=deactivate_vrv, + crash_locmin=crash_locmin) + t1 = log.timer ("LASSIS state preparation", *t1) + self.e_roots, self.si = self.eig (**kwargs) + t1 = log.timer ("LASSIS diagonalization", *t1) log.timer ("LASSIS", *t0) return self.e_roots, self.si From a89e835beea448c00981d30cfad75f1b01c2ea11 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Wed, 22 May 2024 12:12:21 -0500 Subject: [PATCH 29/87] PySCF(-forge) compat check --- pyscf-forge_version.txt | 2 +- pyscf_version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyscf-forge_version.txt b/pyscf-forge_version.txt index 1e196016..d5ddfa20 100644 --- a/pyscf-forge_version.txt +++ b/pyscf-forge_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf-forge.git@6fa7530498f434404a323bd7b116a04d8f7c1f12 +git+https://github.com/pyscf/pyscf-forge.git@2ef4c002985d8d234e7e7720fad3affa0cfedce5 diff --git a/pyscf_version.txt b/pyscf_version.txt index ac649743..d13332d8 100644 --- a/pyscf_version.txt +++ b/pyscf_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf.git@940e4ac16f02eeef3fc944eae190d7f0609a60e7 +git+https://github.com/pyscf/pyscf.git@7d94887d2ca817287226c51371845037f4469325 From 5c92406d7e967ed9b1dcef22e29e8ce62edfa375 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Thu, 23 May 2024 11:38:48 -0500 Subject: [PATCH 30/87] reduced the number of terms required for make_casdm3 --- my_pyscf/mcscf/lasci.py | 120 +++++++++++----------------------------- 1 file changed, 32 insertions(+), 88 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 3b791faa..d64abaf8 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1045,15 +1045,6 @@ def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=No return [np.einsum ('rijkl,r->ijkl', dm2, box.weights) for dm2, box in zip (casdm2fr, self.fciboxes)] - #SV casdm2s - - #def make_casdm2s(self, ci=None, **kwargs): - # # Make the full-dimensional casdm2s spanning the collective active space - # casdm2s_sub = self.make_casdm2s_sub (ci=ci, **kwargs) - # casdm2a = linalg.block_diag (*[dm[0] for dm in casdm2s_sub]) - # casdm2b = linalg.block_diag (*[dm[1] for dm in casdm2s_sub]) - # return np.stack ([casdm2a, casdm2b], axis=0) - #SV casdm2s_sub def make_casdm2s_sub(self, ci=None, ncas_sub=None, nelecas_sub=None, @@ -1405,7 +1396,7 @@ def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) # IDU my eqn says [2,3,0,1] + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) # IDU my eqn says [2,3,0,1] --> it's same! return casdm2 #SV make_casdm3 @@ -1440,7 +1431,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, i = ncas_cum[isub] j = ncas_cum[isub+1] casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 - print ("SV casdm3 = ", casdm3) + #print ("SV casdm3 = ", casdm3) # Off-diagonal for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): i = ncas_cum[isub1] @@ -1457,7 +1448,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, #print ("SV dm1r, dm2r = ", dm1rs1, dma2r) print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) - # Term 1- 61a + # Term 1- 62a casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,0,1,2,3) @@ -1465,7 +1456,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) - # Term 2- 61b + # Term 2- 62b d3sigma = (lib.einsum('r,rkl,rmn->rknml',weights,dma2r,dma3r)+lib.einsum('r,rkl,rmn->rknml',weights,dmb2r,dmb3r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= lib.einsum('ij,klmn->ijklmn', dm1r, d3sigma) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) @@ -1474,16 +1465,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,2,3,0,1) casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,0,1,2,3) - # Term 3- 61c - d3sigma = (lib.einsum('r,rij,rkl->rilkj',weights,dma1r,dma2r)+lib.einsum('r,rij,rkl->rilkj',weights,dmb1r,dmb2r)) - casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= lib.einsum('ijkl,mn->ijklmn', d3sigma, dm3r) - casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) - casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) - casdm3[k:l, i:j, m:n, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,4,5,0,1) - casdm3[m:n, m:n, k:l, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,2,3,0,1) - casdm3[m:n, m:n, i:j, k:l, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,0,1,2,3) - #Terms 4- 61d + #Terms 4- 62c d3sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) casdm3[i:j, k:l, k:l, m:n, m:n, i:j] = np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(0,1,4,5,2,3) @@ -1492,84 +1475,45 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, m:n, m:n, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,4,5,0,1) casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,0,1,4,5) - #Terms 5- 61e - d3sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) - casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) - casdm3[m:n, i:j, i:j, m:n, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,0,1,2,3) - casdm3[m:n, i:j, k:l, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,2,3,0,1) - casdm3[k:l, k:l, m:n, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,4,5,0,1) - casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) - - #Terms 6- 61f - d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) - casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) - print ("SV d3sigma = ", d3sigma.shape) - casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) - - #Last 3 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 - - for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + #Last 2 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 + + for (isub1, dm2rs1), (isub2, dm1rs1) in product(enumerate(casdm2frs), enumerate(casdm1frs)): + # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same fragment but they must come from different frags! i = ncas_cum[isub1] j = ncas_cum[isub1+1] k = ncas_cum[isub1] l = ncas_cum[isub1+1] m = ncas_cum[isub2] n = ncas_cum[isub2+1] - - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] - dm1r = dma1r + dmb1r - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] - dm2r = dma2r + dmb2r - - # Terms 7- 61g - d3sigma = (lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm1r,dm2r)-(lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dma2r))+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dmb2r)) - casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = d3sigma#np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) - casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) - casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) - casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) - casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) - - # Terms 8- 61h - d3sigma = (-lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma1r,dma2r)-lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijknml',weights,dma1r,dma1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijknml',weights,dmb1r,dmb1r,dmb2r)) - casdm3[i:j, i:j, k:l, m:n, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) - - # Terms 9- 61i - - for (isub1, dm1rs1), (isub2, dm2rs2) in product(enumerate(casdm1frs), enumerate(casdm2frs)): - # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same fragment but they must come from different frags! - i = ncas_cum[isub2] - j = ncas_cum[isub2+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - m = ncas_cum[isub1] - n = ncas_cum[isub1+1] if isub1 != isub2: + print ("SV ijklmn = ", i,j,k,l,m,n) dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs #print ("SV dm2rs2 = ", dm2rs2, dm2rs2.shape, dm2rs2[:,1]) - dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] # size of 2-RDMs + dmaa2r, dmab2r, dmbb2r = dm2rs1[:,0], dm2rs1[:,1], dm2rs1[:,2] # size of 2-RDMs dm1r = dma1r + dmb1r - print ("SV dm1r = ", dm1r.shape) - d3sigma = (lib.einsum('r,rmn,rijkl->rmnkjil',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rmnkjil',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->mnkjil',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rmnkjil',weights,dmb1r,dmbb2r)) - print ("SV d3sigma = ", d3sigma.shape) - casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) + print ("SV dm1r = ", dm1rs1, dm1r) + print ("SV dm2rs1 = ", dm2rs1, dmaa2r) + + # Term 4 - 62d + d4sigma = (lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) + #print ("SV d9sigma = ", d9sigma) + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot (weights, d4sigma, axes=1) + casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) + casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) + casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) + casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) + casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) + + # Term 5 - 62e + d5sigma = (lib.einsum('r,rmn,rilkj->rijknml',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rilkj->rijknml',weights,dmb1r,dmbb2r)) + casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot (weights, d5sigma, axes=1) + casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) return casdm3 From 9aa572c7cdb4062de6cf282deb096f2e901a64e2 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Thu, 23 May 2024 15:39:58 -0500 Subject: [PATCH 31/87] corrected normal ordering in 3-rdm --- my_pyscf/mcscf/lasci.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index d64abaf8..d2458f8b 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1153,7 +1153,9 @@ def states_make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kw if nelecas_sub is None: nelecas_sub = self.nelecas_sub casdm3 = [] for ci_i, ncas, nel in zip (ci, ncas_sub, nelecas_sub): - casdm3.append (fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel)[-1]) + dm1_not_no, dm2_not_no, dm3_not_no = fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel) # not normal ordered + dm3_no = fci.rdm.reorder_dm123(dm1_not_no, dm2_not_no, dm3_not_no)[-1] + casdm3.append (dm3_no) #print ("SV casdm3_sub = ", casdm3_sub return casdm3 From 8834ba37e70cd7801f7cb81f8ecfb51a6460102b Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Thu, 23 May 2024 18:44:18 -0500 Subject: [PATCH 32/87] correct 4th, 5th terms of make_casdm3 --- my_pyscf/mcscf/lasci.py | 55 +++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index d2458f8b..1d639c4e 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1070,7 +1070,7 @@ def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **k if ci_i is None: dm2aa = dm2ab, dm2bb = np.zeros ((ncas, ncas,ncas,ncas)) else: - dm2aa, dm2ab, dm2bb = fcibox.states_make_rdm12s (ci_i, ncas, nelecas)[-1] # IDK what this third ncas*4 matrix is, so just created a placeholder for it + dm2aa, dm2ab, dm2bb = fcibox.states_make_rdm12s (ci_i, ncas, nelecas)[-1] casdm2s.append (np.stack ([dm2aa, dm2ab, dm2bb], axis=1)) return casdm2s @@ -1433,7 +1433,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, i = ncas_cum[isub] j = ncas_cum[isub+1] casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 - #print ("SV casdm3 = ", casdm3) + # Off-diagonal for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): i = ncas_cum[isub1] @@ -1442,11 +1442,12 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, l = ncas_cum[isub2+1] m = ncas_cum[isub3] n = ncas_cum[isub3+1] - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] # size of 2-RDMs + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] dma3r, dmb3r = dm1rs3[:,0], dm1rs3[:,1] dm1r = dma1r + dmb1r dm2r = dma2r + dmb2r + dm3r = dma3r + dmb3r #print ("SV dm1r, dm2r = ", dm1rs1, dma2r) print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) @@ -1459,8 +1460,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) # Term 2- 62b - d3sigma = (lib.einsum('r,rkl,rmn->rknml',weights,dma2r,dma3r)+lib.einsum('r,rkl,rmn->rknml',weights,dmb2r,dmb3r)) - casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= lib.einsum('ij,klmn->ijklmn', dm1r, d3sigma) + d2sigma = (lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb2r,dmb3r)) + casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= lib.einsum('ij,klmn->ijklmn', weights, d2sigma) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) @@ -1469,8 +1470,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, #Terms 4- 62c - d3sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) - casdm3[i:j, k:l, k:l, m:n, m:n, i:j] = np.tensordot (weights, d3sigma, axes=1) + d3sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dmb3r)) + casdm3[i:j, k:l, k:l, m:n, m:n, i:j] += np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(0,1,4,5,2,3) casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,0,1,2,3) casdm3[m:n, i:j, k:l, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,2,3,0,1) @@ -1479,37 +1480,37 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, #Last 2 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 - for (isub1, dm2rs1), (isub2, dm1rs1) in product(enumerate(casdm2frs), enumerate(casdm1frs)): + for (isub1, dm2rs2), (isub2, dm1rs1) in product(enumerate(casdm2frs), enumerate(casdm1frs)): # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same fragment but they must come from different frags! - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub1] - l = ncas_cum[isub1+1] - m = ncas_cum[isub2] - n = ncas_cum[isub2+1] - - if isub1 != isub2: + if isub1 != isub2: + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub1] + l = ncas_cum[isub1+1] + m = ncas_cum[isub2] + n = ncas_cum[isub2+1] + print ("SV ijklmn = ", i,j,k,l,m,n) - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs - #print ("SV dm2rs2 = ", dm2rs2, dm2rs2.shape, dm2rs2[:,1]) - dmaa2r, dmab2r, dmbb2r = dm2rs1[:,0], dm2rs1[:,1], dm2rs1[:,2] # size of 2-RDMs + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] dm1r = dma1r + dmb1r + #print ("SV dm1rs1 = ", dm1rs1, dm1rs1[:,0], dm1rs1[:,1]) + dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] print ("SV dm1r = ", dm1rs1, dm1r) - print ("SV dm2rs1 = ", dm2rs1, dmaa2r) + print ("SV dm2rs1 = ", dm2rs2, dmaa2r) # Term 4 - 62d - d4sigma = (lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) - #print ("SV d9sigma = ", d9sigma) + d4sigma = (lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) + print ("SV d4sigma = ", d4sigma) casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot (weights, d4sigma, axes=1) casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) - casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) - casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) - casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) + #casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) + #casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) + #casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) # Term 5 - 62e - d5sigma = (lib.einsum('r,rmn,rilkj->rijknml',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rilkj->rijknml',weights,dmb1r,dmbb2r)) + d5sigma = (lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmbb2r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot (weights, d5sigma, axes=1) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) From 3d06408bf130a35e10108e861ad91814655cf9ce Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Thu, 23 May 2024 23:45:50 -0500 Subject: [PATCH 33/87] looks like these 5 terms are good but some terms are missing --- my_pyscf/mcscf/las_ao2mo.py | 21 + my_pyscf/mcscf/lasci.py | 33 +- my_pyscf/mcscf/lasci_original.py | 1719 ++++++++++++++++++++++ my_pyscf/mcscf/lasci_sv_expanded.py | 2060 +++++++++++++++++++++++++++ my_pyscf/mcscf/sv_lasci.py | 1844 ++++++++++++++++++++++++ 5 files changed, 5661 insertions(+), 16 deletions(-) create mode 100644 my_pyscf/mcscf/las_ao2mo.py create mode 100644 my_pyscf/mcscf/lasci_original.py create mode 100644 my_pyscf/mcscf/lasci_sv_expanded.py create mode 100644 my_pyscf/mcscf/sv_lasci.py diff --git a/my_pyscf/mcscf/las_ao2mo.py b/my_pyscf/mcscf/las_ao2mo.py new file mode 100644 index 00000000..15629728 --- /dev/null +++ b/my_pyscf/mcscf/las_ao2mo.py @@ -0,0 +1,21 @@ +import ctypes + +from functools import reduce +import numpy +import h5py +from pyscf import lib +from pyscf.lib import logger +from pyscf import ao2mo +from pyscf.ao2mo import _ao2mo +from pyscf.ao2mo import outcore + +libmcscf = lib.load_library('libmcscf') + +def _mem_usage(las, mo_coeff, ncore, ncas, nmo): + ncore = las.ncore + ncas = las.ncas + nmo = mo_coeff.shape[-1] + nocc = ncore + ncas + outcore = basic = ncas**2*nmo**2*2 * 8/1e6 + incore = outcore + (ncore+ncas)*nmo**3*4/1e6 + return incore, outcore, basic diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 1d639c4e..0877885f 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1452,31 +1452,32 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) # Term 1- 62a - casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) + d1sigma = lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm2r,dm3r) + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot(weights, d1sigma, axes=1)#lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,0,1,2,3) casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,2,3,0,1) - casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) + casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) # Term 2- 62b d2sigma = (lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb2r,dmb3r)) - casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= lib.einsum('ij,klmn->ijklmn', weights, d2sigma) + casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot(weights, d2sigma, axes=1) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) - casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) - casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) - casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,2,3,0,1) casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,0,1,2,3) - - - #Terms 4- 62c - d3sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dmb3r)) - casdm3[i:j, k:l, k:l, m:n, m:n, i:j] += np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(0,1,4,5,2,3) - casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,0,1,2,3) - casdm3[m:n, i:j, k:l, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,2,3,0,1) - casdm3[k:l, m:n, m:n, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,4,5,0,1) - casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,0,1,4,5) + casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,2,3,0,1) + casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) + casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) + + # Term 3- 62c + d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dmb1r,dmb2r,dmb3r)) + casdm3[i:j, m:n, k:l, i:j, m:n, k:l] += np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (4,5,2,3,0,1) + casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,4,5,0,1) + casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,0,1,4,5) + #Last 2 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 diff --git a/my_pyscf/mcscf/lasci_original.py b/my_pyscf/mcscf/lasci_original.py new file mode 100644 index 00000000..bece9c69 --- /dev/null +++ b/my_pyscf/mcscf/lasci_original.py @@ -0,0 +1,1719 @@ +from pyscf.scf.rohf import get_roothaan_fock +from pyscf.fci import cistring +from pyscf.mcscf import casci, casci_symm, df +from pyscf.tools import dump_mat +from pyscf import symm, gto, scf, ao2mo, lib +from pyscf.fci.direct_spin1 import _unpack_nelec +from mrh.my_pyscf.mcscf.addons import state_average_n_mix, get_h1e_zipped_fcisolver, las2cas_civec +from mrh.my_pyscf.mcscf import lasci_sync, _DFLASCI, lasscf_guess +from mrh.my_pyscf.fci import csf_solver +from mrh.my_pyscf.df.sparse_df import sparsedf_array +from mrh.my_pyscf.mcscf import chkfile +from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver +from mrh.util.la import matrix_svd_control_options +from itertools import combinations +from scipy.sparse import linalg as sparse_linalg +from scipy import linalg +import numpy as np +import copy + +def LASCI (mf_or_mol, ncas_sub, nelecas_sub, **kwargs): + if isinstance(mf_or_mol, gto.Mole): + mf = scf.RHF(mf_or_mol) + else: + mf = mf_or_mol + if mf.mol.symmetry: + las = LASCISymm (mf, ncas_sub, nelecas_sub, **kwargs) + else: + las = LASCINoSymm (mf, ncas_sub, nelecas_sub, **kwargs) + if getattr (mf, 'with_df', None): + las = density_fit (las, with_df = mf.with_df) + return las + +def get_grad (las, mo_coeff=None, ci=None, ugg=None, h1eff_sub=None, h2eff_sub=None, + veff=None, dm1s=None): + '''Return energy gradient for orbital rotation and CI relaxation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + ugg : instance of :class:`LASCI_UnitaryGroupGenerators` + h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray + Contains effective one-electron Hamiltonians experienced by each fragment + in each state + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + dm1s : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-RDM in the AO basis + + Returns: + gorb : ndarray of shape (ugg.nvar_orb,) + Orbital rotation gradients as a flat array + gci : ndarray of shape (sum(ugg.ncsf_sub),) + CI relaxation gradients as a flat array + gx : ndarray + Orbital rotation gradients for temporarily frozen orbitals in the "LASCI" problem + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if ugg is None: ugg = las.get_ugg (mo_coeff, ci) + if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if veff is None: + veff = las.get_veff (dm1s = dm1s.sum (0)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) + if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, + h2eff_sub=h2eff_sub) + + gorb = get_grad_orb (las, mo_coeff=mo_coeff, ci=ci, h2eff_sub=h2eff_sub, veff=veff, dm1s=dm1s) + gci = get_grad_ci (las, mo_coeff=mo_coeff, ci=ci, h1eff_sub=h1eff_sub, h2eff_sub=h2eff_sub, + veff=veff) + + idx = ugg.get_gx_idx () + gx = gorb[idx] + gint = ugg.pack (gorb, gci) + gorb = gint[:ugg.nvar_orb] + gci = gint[ugg.nvar_orb:] + return gorb, gci, gx.ravel () + +def get_grad_orb (las, mo_coeff=None, ci=None, h2eff_sub=None, veff=None, dm1s=None, hermi=-1): + '''Return energy gradient for orbital rotation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + dm1s : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-RDM in the AO basis + hermi : integer + Control (anti-)symmetrization. 0 means to return the effective Fock matrix, + F1 = h.D + g.d. -1 means to return the true orbital-rotation gradient, which is skew- + symmetric: gorb = F1 - F1.T. +1 means to return the symmetrized effective Fock matrix, + (F1 + F1.T) / 2. The factor of 2 difference between hermi=-1 and the other two options + is intentional and necessary. + + Returns: + gorb : ndarray of shape (nmo,nmo) + Orbital rotation gradients as a square antihermitian array + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if veff is None: + veff = las.get_veff (dm1s = dm1s.sum (0)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) + nao, nmo = mo_coeff.shape + ncore = las.ncore + ncas = las.ncas + nocc = las.ncore + las.ncas + smo_cas = las._scf.get_ovlp () @ mo_coeff[:,ncore:nocc] + smoH_cas = smo_cas.conj ().T + + # The orbrot part + h1s = las.get_hcore ()[None,:,:] + veff + f1 = h1s[0] @ dm1s[0] + h1s[1] @ dm1s[1] + f1 = mo_coeff.conjugate ().T @ f1 @ las._scf.get_ovlp () @ mo_coeff + # ^ I need the ovlp there to get dm1s back into its correct basis + casdm2 = las.make_casdm2 (ci=ci) + casdm1s = np.stack ([smoH_cas @ d @ smo_cas for d in dm1s], axis=0) + casdm1 = casdm1s.sum (0) + casdm2 -= np.multiply.outer (casdm1, casdm1) + casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) + casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) + eri = h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2) + eri = lib.numpy_helper.unpack_tril (eri).reshape (nmo, ncas, ncas, ncas) + f1[:,ncore:nocc] += np.tensordot (eri, casdm2, axes=((1,2,3),(1,2,3))) + + if hermi == -1: + return f1 - f1.T + elif hermi == 1: + return .5*(f1+f1.T) + elif hermi == 0: + return f1 + else: + raise ValueError ("kwarg 'hermi' must = -1, 0, or +1") + +def get_grad_ci (las, mo_coeff=None, ci=None, h1eff_sub=None, h2eff_sub=None, veff=None): + '''Return energy gradient for CI relaxation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray + Contains effective one-electron Hamiltonians experienced by each fragment + in each state + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + + Returns: + gci : list (length=nfrags) of list (length=nroots) of ndarray + CI relaxation gradients in the shape of CI vectors + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, + h2eff_sub=h2eff_sub) + gci = [] + for isub, (fcibox, h1e, ci0, ncas, nelecas) in enumerate (zip ( + las.fciboxes, h1eff_sub, ci, las.ncas_sub, las.nelecas_sub)): + eri_cas = las.get_h2eff_slice (h2eff_sub, isub, compact=8) + linkstrl = fcibox.states_gen_linkstr (ncas, nelecas, True) + linkstr = fcibox.states_gen_linkstr (ncas, nelecas, False) + h2eff = fcibox.states_absorb_h1e(h1e, eri_cas, ncas, nelecas, .5) + hc0 = fcibox.states_contract_2e(h2eff, ci0, ncas, nelecas, link_index=linkstrl) + hc0 = [hc.ravel () for hc in hc0] + ci0 = [c.ravel () for c in ci0] + gci.append ([2.0 * (hc - c * (c.dot (hc))) for c, hc in zip (ci0, hc0)]) + return gci + +def density_fit (las, auxbasis=None, with_df=None): + ''' Here I ONLY need to attach the tag and the df object because I put conditionals in + LASCINoSymm to make my life easier ''' + las_class = las.__class__ + if with_df is None: + if (getattr(las._scf, 'with_df', None) and + (auxbasis is None or auxbasis == las._scf.with_df.auxbasis)): + with_df = las._scf.with_df + else: + with_df = df.DF(las.mol) + with_df.max_memory = las.max_memory + with_df.stdout = las.stdout + with_df.verbose = las.verbose + with_df.auxbasis = auxbasis + class DFLASCI (las_class, _DFLASCI): + def __init__(self, scf, ncas_sub, nelecas_sub): + self.with_df = with_df + self._keys = self._keys.union(['with_df']) + las_class.__init__(self, scf, ncas_sub, nelecas_sub) + new_las = DFLASCI (las._scf, las.ncas_sub, las.nelecas_sub) + new_las.__dict__.update (las.__dict__) + return new_las + +def h1e_for_las (las, mo_coeff=None, ncas=None, ncore=None, nelecas=None, ci=None, ncas_sub=None, + nelecas_sub=None, veff=None, h2eff_sub=None, casdm1s_sub=None, casdm1frs=None): + ''' Effective one-body Hamiltonians (plural) for a LASCI problem + + Args: + las: a LASCI object + + Kwargs: + mo_coeff: ndarray of shape (nao,nmo) + Orbital coefficients ordered on the columns as: + core orbitals, subspace 1, subspace 2, ..., external orbitals + ncas: integer + As in PySCF's existing CASCI/CASSCF implementation + nelecas: sequence of 2 integers + As in PySCF's existing CASCI/CASSCF implementation + ci: list (length=nfrags) of list (length=nroots) of ndarrays + Contains CI vectors + ncas_sub: ndarray of shape (nsub) + Number of active orbitals in each subspace + nelecas_sub: ndarray of shape (nsub,2) + na, nb in each subspace + veff: ndarray of shape (2, nao, nao) + Contains spin-separated, state-averaged effective potential + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + casdm1s_sub : list (length=nfrags) of ndarrays + Contains state-averaged, spin-separated 1-RDMs in the localized active subspaces + casdm1frs : list (length=nfrags) of list (length=nroots) of ndarrays + Contains spin-separated 1-RDMs for each state in the localized active subspaces + + Returns: + h1e_fr: list (length=nfrags) of list (length=nroots) of ndarrays + Spin-separated 1-body Hamiltonian operator for each fragment and state + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ncas is None: ncas = las.ncas + if ncore is None: ncore = las.ncore + if ncas_sub is None: ncas_sub = las.ncas_sub + if nelecas_sub is None: nelecas_sub = las.nelecas_sub + if ncore is None: ncore = las.ncore + if ci is None: ci = las.ci + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if casdm1frs is None: casdm1frs = las.states_make_casdm1s_sub (ci=ci) + if casdm1s_sub is None: casdm1s_sub = [np.einsum ('rsij,r->sij',dm,las.weights) + for dm in casdm1frs] + if veff is None: + veff = las.get_veff (dm1s = las.make_rdm1 (mo_coeff=mo_coeff, ci=ci)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci, casdm1s_sub=casdm1s_sub) + + # First pass: split by root + nocc = ncore + ncas + nmo = mo_coeff.shape[-1] + mo_cas = mo_coeff[:,ncore:nocc] + moH_cas = mo_cas.conj ().T + h1e = moH_cas @ (las.get_hcore ()[None,:,:] + veff) @ mo_cas + h1e_r = np.empty ((las.nroots, 2, ncas, ncas), dtype=h1e.dtype) + h2e = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, + ncas*(ncas+1)//2)).reshape (nmo, ncas, ncas, ncas)[ncore:nocc,:,:,:] + avgdm1s = np.stack ([linalg.block_diag (*[dm[spin] for dm in casdm1s_sub]) + for spin in range (2)], axis=0) + for state in range (las.nroots): + statedm1s = np.stack ([linalg.block_diag (*[dm[state][spin] for dm in casdm1frs]) + for spin in range (2)], axis=0) + dm1s = statedm1s - avgdm1s + j = np.tensordot (dm1s, h2e, axes=((1,2),(2,3))) + k = np.tensordot (dm1s, h2e, axes=((1,2),(2,1))) + h1e_r[state] = h1e + j + j[::-1] - k + + + # Second pass: split by fragment and subtract double-counting + h1e_fr = [] + for ix, casdm1s_r in enumerate (casdm1frs): + p = sum (las.ncas_sub[:ix]) + q = p + las.ncas_sub[ix] + h1e = h1e_r[:,:,p:q,p:q] + h2e = las.get_h2eff_slice (h2eff_sub, ix) + j = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,3))) + k = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,1))) + h1e_fr.append (h1e - j - j[:,::-1] + k) + + return h1e_fr + +def get_fock (las, mo_coeff=None, ci=None, eris=None, casdm1s=None, verbose=None, veff=None, + dm1s=None): + ''' f_pq = h_pq + (g_pqrs - g_psrq/2) D_rs, AO basis + Note the difference between this and h1e_for_las: h1e_for_las only has + JK terms from electrons outside the "current" active subspace; get_fock + includes JK from all electrons. This is also NOT the "generalized Fock matrix" + of orbital gradients (but it can be used in calculating those if you do a + semi-cumulant decomposition). + The "eris" kwarg does not do anything and is retained only for backwards + compatibility (also why I don't just call las.make_rdm1) ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if dm1s is None: + if casdm1s is None: casdm1s = las.make_casdm1s (ci=ci) + mo_cas = mo_coeff[:,las.ncore:][:,:las.ncas] + moH_cas = mo_cas.conjugate ().T + mo_core = mo_coeff[:,:las.ncore] + moH_core = mo_core.conjugate ().T + dm1s = [(mo_core @ moH_core) + (mo_cas @ d @ moH_cas) for d in list(casdm1s)] + if veff is not None: + fock = las.get_hcore()[None,:,:] + veff + return get_roothaan_fock (fock, dm1s, las._scf.get_ovlp ()) + dm1 = dm1s[0] + dm1s[1] + if isinstance (las, _DFLASCI): + vj, vk = las.with_df.get_jk(dm1, hermi=1) + else: + vj, vk = las._scf.get_jk(las.mol, dm1, hermi=1) + fock = las.get_hcore () + vj - (vk/2) + return fock + +def _eig_inactive_virtual (las, fock, orbsym=None): + '''Generate the unitary matrix canonicalizing the inactive and virtual orbitals only. + + Args: + las : object of :class:`LASCINoSymm` + fock : ndarray of shape (nmo,nmo) + Contains Fock matrix in MO basis + + Kwargs: + orbsym : list of length nmo + umat : ndarray of shape (nmo, nmo) + + Returns: + ene : ndarray of shape (nmo,) + umat : ndarray of shape (nmo, nmo)''' + nmo = fock.shape[0] + ncore = las.ncore + nocc = ncore + las.ncas + ene = np.zeros (nmo) + umat = np.eye (nmo) + if ncore: + orbsym_i = None if orbsym is None else orbsym[:ncore] + fock_i = fock[:ncore,:ncore] + ene[:ncore], umat[:ncore,:ncore] = las._eig (fock_i, 0, 0, orbsym_i) + if nmo-nocc: + orbsym_i = None if orbsym is None else orbsym[nocc:] + fock_i = fock[nocc:,nocc:] + ene[nocc:], umat[nocc:,nocc:] = las._eig (fock_i, 0, 0, orbsym_i) + return ene, umat + +def canonicalize (las, mo_coeff=None, ci=None, casdm1fs=None, natorb_casdm1=None, veff=None, + h2eff_sub=None, orbsym=None): + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + + # In-place safety + mo_coeff = mo_coeff.copy () + ci = copy.deepcopy (ci) + + # Temporary lroots safety + # The desired behavior is that the inactive and external orbitals should + # be canonicalized according to the density matrix used for orbital optimization + # TODO: once orbital optimization with lroots is enabled, change this behavior + # TODO: possibly move this logic to the make_casdm* functions + if casdm1fs is None: + ci_dm = [] + for i in range (len (ci)): + ci_i = [] + for j in range (len (ci[i])): + if ci[i][j].ndim>2: + ci_i.append (ci[i][j][0]) + else: + ci_i.append (ci[i][j]) + ci_dm.append (ci_i) + casdm1fs = las.make_casdm1s_sub (ci=ci_dm) + + nao, nmo = mo_coeff.shape + ncore = las.ncore + nocc = ncore + las.ncas + ncas_sub = las.ncas_sub + nelecas_sub = las.nelecas_sub + + # Passing casdm1 or lasdm1 only affects the canonicalization of the active orbitals + umat = np.zeros_like (mo_coeff) + casdm1s = np.stack ([linalg.block_diag (*[dm[0] for dm in casdm1fs]), + linalg.block_diag (*[dm[1] for dm in casdm1fs])], axis=0) + fock = mo_coeff.conjugate ().T @ las.get_fock (mo_coeff=mo_coeff, casdm1s=casdm1s, veff=veff) + fock = fock @ mo_coeff + if natorb_casdm1 is None: # State-average natural orbitals by default + natorb_casdm1 = casdm1s.sum (0) + + # Inactive-inactive and virtual-virtual + ene, umat = _eig_inactive_virtual (las, fock, orbsym=orbsym) + idx = np.arange (nmo, dtype=int) + if ncore: idx[:ncore] = idx[:ncore][np.argsort (ene[:ncore])] + if nmo-nocc: idx[nocc:] = idx[nocc:][np.argsort (ene[nocc:])] + umat = umat[:,idx] + if orbsym is not None: orbsym = orbsym[idx] + # Active-active + check_diag = natorb_casdm1.copy () + for ix, ncas in enumerate (ncas_sub): + i = sum (ncas_sub[:ix]) + j = i + ncas + check_diag[i:j,i:j] = 0.0 + is_block_diag = np.amax (np.abs (check_diag)) < 1e-8 + if is_block_diag: + # No off-diagonal RDM elements -> extra effort to prevent diagonalizer from breaking frags + for isub, (ncas, nelecas) in enumerate (zip (ncas_sub, nelecas_sub)): + i = sum (ncas_sub[:isub]) + j = i + ncas + dm1 = natorb_casdm1[i:j,i:j] + i += ncore + j += ncore + orbsym_i = None if orbsym is None else orbsym[i:j] + occ, umat[i:j,i:j] = las._eig (dm1, 0, 0, orbsym_i) + idx = np.argsort (occ)[::-1] + umat[i:j,i:j] = umat[i:j,i:j][:,idx] + if orbsym_i is not None: orbsym[i:j] = orbsym[i:j][idx] + if ci is not None: + fcibox = las.fciboxes[isub] + ci[isub] = fcibox.states_transform_ci_for_orbital_rotation ( + ci[isub], ncas, nelecas, umat[i:j,i:j]) + else: # You can't get proper LAS-type CI vectors w/out active space fragmentation + ci = None + orbsym_cas = None if orbsym is None else orbsym[ncore:nocc] + occ, umat[ncore:nocc,ncore:nocc] = las._eig (natorb_casdm1, 0, 0, orbsym_cas) + idx = np.argsort (occ)[::-1] + umat[ncore:nocc,ncore:nocc] = umat[ncore:nocc,ncore:nocc][:,idx] + if orbsym_cas is not None: orbsym[ncore:nocc] = orbsym[ncore:nocc][idx] + + # Final + mo_occ = np.zeros (nmo, dtype=natorb_casdm1.dtype) + if ncore: mo_occ[:ncore] = 2 + ucas = umat[ncore:nocc,ncore:nocc] + mo_occ[ncore:nocc] = ((natorb_casdm1 @ ucas) * ucas).sum (0) + mo_ene = ((fock @ umat) * umat.conjugate ()).sum (0) + mo_ene[ncore:][:sum (ncas_sub)] = 0.0 + mo_coeff = mo_coeff @ umat + if orbsym is not None: + ''' + print ("This is the second call to label_orb_symm inside of canonicalize") + orbsym = symm.label_orb_symm (las.mol, las.mol.irrep_id, + las.mol.symm_orb, mo_coeff, + s=las._scf.get_ovlp ()) + #mo_coeff = las.label_symmetry_(mo_coeff) + ''' + mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) + if h2eff_sub is not None: + h2eff_sub = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*las.ncas, -1)) + h2eff_sub = h2eff_sub.reshape (nmo, las.ncas, las.ncas, las.ncas) + h2eff_sub = np.tensordot (umat, h2eff_sub, axes=((0),(0))) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(1))).transpose (1,0,2,3) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(2))).transpose (1,2,0,3) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(3))).transpose (1,2,3,0) + h2eff_sub = h2eff_sub.reshape (nmo*las.ncas, las.ncas, las.ncas) + h2eff_sub = lib.numpy_helper.pack_tril (h2eff_sub).reshape (nmo, -1) + + # I/O + log = lib.logger.new_logger (las, las.verbose) + if las.verbose >= lib.logger.INFO: + if is_block_diag: + for isub, nlas in enumerate (ncas_sub): + log.info ("Fragment %d natural orbitals", isub) + i = ncore + sum (ncas_sub[:isub]) + j = i + nlas + log.info ('Natural occ %s', str (mo_occ[i:j])) + log.info ('Natural orbital (expansion on AOs) in CAS space') + label = las.mol.ao_labels() + mo_las = mo_coeff[:,i:j] + dump_mat.dump_rec(log.stdout, mo_las, label, start=1) + else: + log.info ("Delocalized natural orbitals do not reflect LAS fragmentation") + log.info ('Natural occ %s', str (mo_occ[ncore:nocc])) + log.info ('Natural orbital (expansion on AOs) in CAS space') + label = las.mol.ao_labels() + mo_las = mo_coeff[:,ncore:nocc] + dump_mat.dump_rec(log.stdout, mo_las, label, start=1) + + return mo_coeff, mo_ene, mo_occ, ci, h2eff_sub + +def get_init_guess_ci (las, mo_coeff=None, h2eff_sub=None, ci0=None): + # TODO: come up with a better algorithm? This might be working better than what I had before + # but it omits inter-active Coulomb and exchange interactions altogether. Is there a + # non-outer-product algorithm for finding the lowest-energy single product of CSFs? + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci0 is None: ci0 = [[None for i in range (las.nroots)] for j in range (las.nfrags)] + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + nmo = mo_coeff.shape[-1] + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + dm1_core= 2 * mo_coeff[:,:ncore] @ mo_coeff[:,:ncore].conj ().T + h1e_ao = las._scf.get_fock (dm=dm1_core) + eri_cas = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)) + eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) + eri_cas = eri_cas[ncore:nocc] + for ix, (fcibox, norb, nelecas) in enumerate (zip (las.fciboxes,las.ncas_sub,las.nelecas_sub)): + i = sum (las.ncas_sub[:ix]) + j = i + norb + mo = mo_coeff[:,ncore+i:ncore+j] + moH = mo.conj ().T + h1e = moH @ h1e_ao @ mo + h1e = [h1e, h1e] + eri = eri_cas[i:j,i:j,i:j,i:j] + for iy, solver in enumerate (fcibox.fcisolvers): + nelec = fcibox._get_nelec (solver, nelecas) + ndet = tuple ([cistring.num_strings (norb, n) for n in nelec]) + if isinstance (ci0[ix][iy], np.ndarray) and ci0[ix][iy].size==ndet[0]*ndet[1]: continue + if hasattr (mo_coeff, 'orbsym'): + solver.orbsym = mo_coeff.orbsym[ncore+i:ncore+j] + hdiag_csf = solver.make_hdiag_csf (h1e, eri, norb, nelec, max_memory=las.max_memory) + ci0[ix][iy] = solver.get_init_guess (norb, nelec, solver.nroots, hdiag_csf) + if solver.nroots==1: + ci0[ix][iy] = ci0[ix][iy][0] + else: + ci0[ix][iy] = np.stack (ci0[ix][iy], axis=0) + return ci0 + +def get_space_info (las): + ''' Retrieve the quantum numbers defining the states of a LASSCF calculation ''' + nfrags, nroots = las.nfrags, las.nroots + charges = np.zeros ((nroots, nfrags), dtype=np.int32) + wfnsyms, spins, smults = charges.copy (), charges.copy (), charges.copy () + for ifrag, fcibox in enumerate (las.fciboxes): + for iroot, solver in enumerate (fcibox.fcisolvers): + nelec = fcibox._get_nelec (solver, las.nelecas_sub[ifrag]) + charges[iroot,ifrag] = np.sum (las.nelecas_sub[ifrag]) - np.sum (nelec) + spins[iroot,ifrag] = nelec[0]-nelec[1] + smults[iroot,ifrag] = solver.smult + try: + wfnsyms[iroot,ifrag] = solver.wfnsym or 0 + except ValueError as e: + wfnsyms[iroot,ifrag] = symm.irrep_name2id (las.mol.groupname, solver.wfnsym) + return charges, spins, smults, wfnsyms + +def assert_no_duplicates (las, tab=None): + log = lib.logger.new_logger (las, las.verbose) + if tab is None: tab = np.stack (get_space_info (las), axis=-1) + tab_uniq, uniq_idx, uniq_inv, uniq_cnts = np.unique (tab, return_index=True, + return_inverse=True, return_counts=True, axis=0) + idx_dupe = uniq_cnts>1 + try: + err_str = ('LAS state basis has duplicates; details in logfile for ' + 'verbose >= INFO (4) [more details for verbose > INFO].\n' + '(Disable this assertion by passing assert_no_dupes=False ' + 'to the kernel, lasci, and state_average(_) functions.)') + assert (~np.any (idx_dupe)), err_str + except AssertionError as e: + dupe_idx = uniq_idx[idx_dupe] + dupe_cnts = uniq_cnts[idx_dupe] + for i, (ix, cnt, col) in enumerate (zip (uniq_idx, uniq_cnts, tab_uniq)): + if cnt==1: continue + log.info ('State %d appears %d times', ix, cnt) + idx_thisdupe = np.where (uniq_inv==i)[0] + row = col.T + log.debug ('As states {}'.format (idx_thisdupe)) + log.debug ('Charges = {}'.format (row[0])) + log.debug ('2M_S = {}'.format (row[1])) + log.debug ('2S+1 = {}'.format (row[2])) + log.debug ('Wfnsyms = {}'.format (row[3])) + raise e from None + +def state_average_(las, weights=[0.5,0.5], charges=None, spins=None, + smults=None, wfnsyms=None, assert_no_dupes=True): + ''' Transform LASCI/LASSCF object into state-average LASCI/LASSCF + + Args: + las: LASCI/LASSCF instance + + Kwargs: + weights: list of float; required + E_SA = sum_i weights[i] E[i] is used to optimize the orbitals + charges: 2d ndarray or nested list of integers + spins: 2d ndarray or nested list of integers + For the jth fragment in the ith state, + neleca = (sum(las.nelecas_sub[j]) - charges[i][j] + spins[i][j]) // 2 + nelecb = (sum(las.nelecas_sub[j]) - charges[i][j] - spins[i][j]) // 2 + Defaults to + charges[i][j] = 0 + spins[i][j] = las.nelecas_sub[j][0] - las.nelecas_sub[j][1] + smults: 2d ndarray or nested list of integers + For the jth fragment in the ith state, + smults[i][j] = (2*s)+1 + where "s" is the total spin quantum number, + S^2|j,i> = s*(s+1)|j,i> + Defaults to + smults[i][j] = abs (spins[i][j]) + 1 + wfnsyms: 2d ndarray or nested list of integers or strings + For the jth fragment of the ith state, + wfnsyms[i][j] + identifies the point-group irreducible representation + Defaults to all zeros (i.e., the totally-symmetric irrep) + + Returns: + las: LASCI/LASSCF instance + The first positional argument, modified in-place into a + state-averaged LASCI/LASSCF instance. + + ''' + old_states = np.stack (get_space_info (las), axis=-1) + nroots = len (weights) + nfrags = las.nfrags + if charges is None: charges = np.zeros ((nroots, nfrags), dtype=np.int32) + if wfnsyms is None: wfnsyms = np.zeros ((nroots, nfrags), dtype=np.int32) + if spins is None: spins = np.asarray ([[n[0]-n[1] for n in las.nelecas_sub] for i in weights]) + if smults is None: smults = np.abs (spins)+1 + + charges = np.asarray (charges) + wfnsyms = np.asarray (wfnsyms) + spins = np.asarray (spins) + smults = np.asarray (smults) + if np.issubsctype (wfnsyms.dtype, np.str_): + wfnsyms_str = wfnsyms + wfnsyms = np.zeros (wfnsyms_str.shape, dtype=np.int32) + for ix, wfnsym in enumerate (wfnsyms_str.flat): + try: + wfnsyms.flat[ix] = symm.irrep_name2id (las.mol.groupname, wfnsym) + except (TypeError, KeyError) as e: + wfnsyms.flat[ix] = int (wfnsym) + if nfrags == 1: + charges = np.atleast_2d (np.squeeze (charges)).T + wfnsyms = np.atleast_2d (np.squeeze (wfnsyms)).T + spins = np.atleast_2d (np.squeeze (spins)).T + smults = np.atleast_2d (np.squeeze (smults)).T + new_states = np.stack ([charges, spins, smults, wfnsyms], axis=-1) + if assert_no_dupes: assert_no_duplicates (las, tab=new_states) + + las.fciboxes = [get_h1e_zipped_fcisolver (state_average_n_mix ( + las, [csf_solver (las.mol, smult=s2p1).set (charge=c, spin=m2, wfnsym=ir) + for c, m2, s2p1, ir in zip (c_r, m2_r, s2p1_r, ir_r)], weights).fcisolver) + for c_r, m2_r, s2p1_r, ir_r in zip (charges.T, spins.T, smults.T, wfnsyms.T)] + las.e_states = np.zeros (nroots) + las.nroots = nroots + las.weights = weights + + if las.ci is not None: + log = lib.logger.new_logger(las, las.verbose) + log.debug (("lasci.state_average: Cached CI vectors may be present.\n" + "Looking for matches between old and new LAS states...")) + ci0 = [[None for i in range (nroots)] for j in range (nfrags)] + new_states = np.stack ([charges, spins, smults, wfnsyms], + axis=-1).reshape (nroots, nfrags*4) + old_states = old_states.reshape (-1, nfrags*4) + for iroot, row in enumerate (old_states): + idx = np.all (new_states == row[None,:], axis=1) + if np.count_nonzero (idx) == 1: + jroot = np.where (idx)[0][0] + log.debug ("Old state {} -> New state {}".format (iroot, jroot)) + for ifrag in range (nfrags): + ci0[ifrag][jroot] = las.ci[ifrag][iroot] + elif np.count_nonzero (idx) > 1: + raise RuntimeError ("Duplicate states specified?\n{}".format (idx)) + las.ci = ci0 + las.converged = False + return las + +@lib.with_doc(''' A version of lasci.state_average_ that creates a copy instead of modifying the + LASCI/LASSCF method instance in place. + + See lasci.state_average_ docstring below:\n\n''' + state_average_.__doc__) +def state_average (las, weights=[0.5,0.5], charges=None, spins=None, + smults=None, wfnsyms=None, assert_no_dupes=True): + is_scanner = isinstance (las, lib.SinglePointScanner) + if is_scanner: las = las.undo_scanner () + new_las = las.__class__(las._scf, las.ncas_sub, las.nelecas_sub) + new_las.__dict__.update (las.__dict__) + new_las.mo_coeff = las.mo_coeff.copy () + if getattr (las.mo_coeff, 'orbsym', None) is not None: + new_las.mo_coeff = lib.tag_array (new_las.mo_coeff, + orbsym=las.mo_coeff.orbsym) + new_las.ci = None + if las.ci is not None: + new_las.ci = [[c2.copy () if isinstance (c2, np.ndarray) else None + for c2 in c1] for c1 in las.ci] + las = state_average_(new_las, weights=weights, charges=charges, spins=spins, + smults=smults, wfnsyms=wfnsyms, assert_no_dupes=assert_no_dupes) + if is_scanner: las = las.as_scanner () + return las + +def get_single_state_las (las, state=0): + ''' Quickly extract a state-specific las calculation from a state-average one ''' + charges, spins, smults, wfnsyms = get_space_info (las) + charges = charges[state:state+1] + spins = spins[state:state+1] + smults = smults[state:state+1] + wfnsyms = wfnsyms[state:state+1] + weights = [1,] + return state_average (las, weights=weights, charges=charges, spins=spins, smults=smults, + wfnsyms=wfnsyms) + +def run_lasci (las, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=0, + assert_no_dupes=False, _dry_run=False): + '''Self-consistently optimize the CI vectors of a LAS state with + frozen orbitals using a fixed-point algorithm. "lasci_" (with the + trailing underscore) sets self.mo_coeff from the kwarg if it is passed; + "lasci" (without the trailing underscore) leaves self.mo_coeff unchanged. + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + MO coefficients; defaults to self.mo_coeff + ci0 : list (length nfrags) of list (length nroots) of ndarrays + Contains CI vectors for initial guess + lroots : ndarray of shape (nfrags,nroots) + Number of local roots in each fragment for each global state. + The corresponding local weights are set to [1,0,0,0,...]. + lweights : list of length nfrags of list of length nroots of sequence + Weights of local roots in each fragment for each global state. + Passing lweights is incompatible with passing lroots. Defaults + to, i.e., np.ones (las.nfrags, las.nroots, 1).tolist () + verbose : integer + See pyscf.lib.logger. + assert_no_dupes : logical + If True, checks state list for duplicate states + _dry_run : logical + If True, sets up the fcisolvers with the appropriate tags, but does + not run fcisolver kernels. + + Returns: + converged : list of length nroots of logical + Stores whether the calculation for each state successfully converged + e_tot : float + (State-averaged) total energy + e_states : list of length nroots + List of each state energy + e_cas : list of length nroots + List of the CAS space energy of each state + ci : list (length nfrags) of list (length nroots) of ndarrays + Contains optimized CI vectors + ''' + if assert_no_dupes: assert_no_duplicates (las) + if lroots is not None and lweights is not None: + raise RuntimeError ("lroots sets lweights: pass either or none but not both") + elif lweights is None: + if lroots is None: lroots = np.ones ((las.nfrags, las.nroots), dtype=int) + lweights = [] + for i in range (las.nfrags): + lwi = [] + for j in range (las.nroots): + lwij = np.zeros (lroots[i,j]) + lwij[0] = 1 + lwi.append (lwij) + lweights.append (lwi) + nao, nmo = mo_coeff.shape + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + ncas_sub = las.ncas_sub + nelecas_sub = las.nelecas_sub + orbsym = getattr (mo_coeff, 'orbsym', None) + if orbsym is not None: orbsym=orbsym[ncore:nocc] + elif isinstance (las, LASCISymm): + mo_coeff = las.label_symmetry_(mo_coeff) + orbsym = mo_coeff.orbsym[ncore:nocc] + log = lib.logger.new_logger (las, verbose) + + h1eff, energy_core = las.h1e_for_cas (mo_coeff=mo_coeff, + ncas=las.ncas, ncore=las.ncore) + h2eff = las.get_h2eff (mo_coeff) + if (ci0 is None or any ([c is None for c in ci0]) or + any ([any ([c2 is None for c2 in c1]) for c1 in ci0])): + ci0 = las.get_init_guess_ci (mo_coeff, h2eff, ci0) + eri_cas = lib.numpy_helper.unpack_tril ( + h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, + ncas, ncas)[ncore:nocc] + + e_cas = np.empty (las.nroots) + e_states = np.empty (las.nroots) + ci1 = [[None for c2 in c1] for c1 in ci0] + converged = [] + t = (lib.logger.process_clock(), lib.logger.perf_counter()) + e_lexc = [[None for i in range (las.nroots)] for j in range (las.nfrags)] + for state in range (las.nroots): + fcisolvers = [b.fcisolvers[state] for b in las.fciboxes] + ci0_i = [c[state] for c in ci0] + solver = ImpureProductStateFCISolver (fcisolvers, stdout=las.stdout, + lweights=[l[state] for l in lweights], verbose=verbose) + # TODO: better handling of CSF symmetry quantum numbers in general + for ix, s in enumerate (solver.fcisolvers): + i = sum (ncas_sub[:ix]) + j = i + ncas_sub[ix] + if orbsym is not None: s.orbsym = orbsym[i:j] + s.norb = ncas_sub[ix] + s.nelec = solver._get_nelec (s, nelecas_sub[ix]) + s.check_transformer_cache () + if _dry_run: continue + conv, e_i, ci_i = solver.kernel (h1eff, eri_cas, ncas_sub, nelecas_sub, + ecore=0, ci0=ci0_i, orbsym=orbsym, conv_tol_grad=las.conv_tol_grad, + conv_tol_self=las.conv_tol_self, max_cycle_macro=las.max_cycle_macro) + e_cas[state] = e_i + e_states[state] = e_i + energy_core + for frag, s in enumerate (solver.fcisolvers): + e_loc = np.atleast_1d (getattr (s, 'e_states', e_i)) + e_lexc[frag][state] = e_loc - e_i + for c1, c2, s, no, ne in zip (ci1, ci_i, solver.fcisolvers, ncas_sub, nelecas_sub): + ne = solver._get_nelec (s, ne) + ndeta, ndetb = [cistring.num_strings (no, n) for n in ne] + shape = [s.nroots, ndeta, ndetb] if s.nroots>1 else [ndeta, ndetb] + c1[state] = np.asarray (c2).reshape (*shape) + if not conv: log.warn ('State %d LASCI not converged!', state) + converged.append (conv) + t = log.timer ('State {} LASCI'.format (state), *t) + + e_tot = np.dot (las.weights, e_states) + return converged, e_tot, e_states, e_cas, e_lexc, ci1 + +class LASCINoSymm (casci.CASCI): + + def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, frozen=None, **kwargs): + if isinstance(ncas,int): + ncas = [ncas] + ncas_tot = sum (ncas) + nel_tot = [0, 0] + new_nelecas = [] + for ix, nel in enumerate (nelecas): + if isinstance (nel, (int, np.integer)): + nb = nel // 2 + na = nb + (nel % 2) + else: + na, nb = nel + new_nelecas.append ((na, nb)) + nel_tot[0] += na + nel_tot[1] += nb + nelecas = new_nelecas + self.nroots = 1 + super().__init__(mf, ncas=ncas_tot, nelecas=nel_tot, ncore=ncore) + self.chkfile = self._scf.chkfile + if spin_sub is None: spin_sub = [1 + abs(ne[0]-ne[1]) for ne in nelecas] + self.ncas_sub = np.asarray (ncas) + self.nelecas_sub = np.asarray (nelecas) + assert (len (self.nelecas_sub) == self.nfrags) + self.frozen = frozen + self.conv_tol_grad = 1e-4 + self.conv_tol_self = 1e-10 + self.ah_level_shift = 1e-8 + self.max_cycle_macro = 50 + self.max_cycle_micro = 5 + keys = set(('e_states', 'fciboxes', 'nroots', 'weights', 'ncas_sub', 'nelecas_sub', + 'conv_tol_grad', 'conv_tol_self', 'max_cycle_macro', 'max_cycle_micro', + 'ah_level_shift', 'states_converged', 'chkfile', 'e_lexc')) + self._keys = set(self.__dict__.keys()).union(keys) + self.fciboxes = [] + if isinstance(spin_sub,int): + self.fciboxes.append(self._init_fcibox(spin_sub,self.nelecas_sub[0])) + else: + assert (len (spin_sub) == self.nfrags) + for smult, nel in zip (spin_sub, self.nelecas_sub): + self.fciboxes.append (self._init_fcibox (smult, nel)) + self.weights = [1.0] + self.e_states = [0.0] + self.e_lexc = [[np.array ([0]),],] + + def _init_fcibox (self, smult, nel): + s = csf_solver (self.mol, smult=smult) + s.spin = nel[0] - nel[1] + return get_h1e_zipped_fcisolver (state_average_n_mix (self, [s], [1.0]).fcisolver) + + @property + def nfrags (self): return len (self.ncas_sub) + + def get_mo_slice (self, idx, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + mo = mo_coeff[:,self.ncore:] + for offs in self.ncas_sub[:idx]: + mo = mo[:,offs:] + mo = mo[:,:self.ncas_sub[idx]] + return mo + + def ao2mo (self, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + nao, nmo = mo_coeff.shape + ncore, ncas = self.ncore, self.ncas + nocc = ncore + ncas + mo_cas = mo_coeff[:,ncore:nocc] + mo = [mo_coeff, mo_cas, mo_cas, mo_cas] + if getattr (self, 'with_df', None) is not None: + # Store intermediate with one contracted ao index for faster calculation of exchange! + bPmn = sparsedf_array (self.with_df._cderi) + bmuP = bPmn.contract1 (mo_cas) + buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) + eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) + eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) + eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) + eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) + if self.verbose > lib.logger.DEBUG: + eri_comp = self.with_df.ao2mo (mo, compact=True) + lib.logger.debug(self,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) + elif getattr (self._scf, '_eri', None) is not None: + eri = ao2mo.incore.general (self._scf._eri, mo, compact=True) + else: + eri = ao2mo.outcore.general_iofree (self.mol, mo, compact=True) + if eri.shape != (nmo,ncas*ncas*(ncas+1)//2): + try: + eri = eri.reshape (nmo, ncas*ncas*(ncas+1)//2) + except ValueError as e: + assert (nmo == ncas), str (e) + eri = ao2mo.restore ('2kl', eri, nmo).reshape (nmo, ncas*ncas*(ncas+1)//2) + return eri + + def get_h2eff_slice (self, h2eff, idx, compact=None): + ncas_cum = np.cumsum ([0] + self.ncas_sub.tolist ()) + i = ncas_cum[idx] + j = ncas_cum[idx+1] + ncore = self.ncore + nocc = ncore + self.ncas + eri = h2eff[ncore:nocc,:].reshape (self.ncas*self.ncas, -1) + ix_i, ix_j = np.tril_indices (self.ncas) + eri = eri[(ix_i*self.ncas)+ix_j,:] + eri = ao2mo.restore (1, eri, self.ncas)[i:j,i:j,i:j,i:j] + if compact: eri = ao2mo.restore (compact, eri, j-i) + return eri + + get_h1eff = get_h1las = h1e_for_las = h1e_for_las + get_h2eff = ao2mo + ''' + def get_h2eff (self, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + if isinstance (self, _DFLASCI): + mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] + return self.with_df.ao2mo (mo_cas) + return self.ao2mo (mo_coeff) + ''' + + get_fock = get_fock + get_grad = get_grad + get_grad_orb = get_grad_orb + get_grad_ci = get_grad_ci + _hop = lasci_sync.LASCI_HessianOperator + _kern = lasci_sync.kernel + def get_hop (self, mo_coeff=None, ci=None, ugg=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ugg is None: ugg = self.get_ugg () + return self._hop (self, ugg, mo_coeff=mo_coeff, ci=ci, **kwargs) + canonicalize = canonicalize + + def _finalize(self): + log = lib.logger.new_logger (self, self.verbose) + nroots_prt = len (self.e_states) + if self.verbose <= lib.logger.INFO: + nroots_prt = min (nroots_prt, 100) + if nroots_prt < len (self.e_states): + log.info (("Printing a maximum of 100 state energies;" + " increase self.verbose to see them all")) + if nroots_prt > 1: + log.info ("LASCI state-average energy = %.15g", self.e_tot) + for i, e in enumerate (self.e_states[:nroots_prt]): + log.info ("LASCI state %d energy = %.15g", i, e) + else: + log.info ("LASCI energy = %.15g", self.e_tot) + return + + + def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, conv_tol_grad=None, + assert_no_dupes=False, verbose=None, _kern=None): + if mo_coeff is None: + mo_coeff = self.mo_coeff + else: + self.mo_coeff = mo_coeff + if ci0 is None: ci0 = self.ci + if verbose is None: verbose = self.verbose + if conv_tol_grad is None: conv_tol_grad = self.conv_tol_grad + if _kern is None: _kern = self._kern + log = lib.logger.new_logger(self, verbose) + + if self.verbose >= lib.logger.WARN: + self.check_sanity() + self.dump_flags(log) + + for fcibox in self.fciboxes: + fcibox.verbose = self.verbose + fcibox.stdout = self.stdout + fcibox.nroots = self.nroots + fcibox.weights = self.weights + # TODO: local excitations and locally-impure states in LASSCF kernel + do_warn=False + if ci0 is not None: + for i, ci0_i in enumerate (ci0): + if ci0_i is None: continue + for j, ci0_ij in enumerate (ci0_i): + if ci0_ij is None: continue + if np.asarray (ci0_ij).ndim>2: + do_warn=True + ci0_i[j] = ci0_ij[0] + if do_warn: log.warn ("Discarding all but the first root of guess CI vectors!") + + self.converged, self.e_tot, self.e_states, self.mo_energy, self.mo_coeff, self.e_cas, \ + self.ci, h2eff_sub, veff = _kern(mo_coeff=mo_coeff, ci0=ci0, verbose=verbose, \ + casdm0_fr=casdm0_fr, conv_tol_grad=conv_tol_grad, assert_no_dupes=assert_no_dupes) + + self._finalize () + return self.e_tot, self.e_cas, self.ci, self.mo_coeff, self.mo_energy, h2eff_sub, veff + + def states_make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: + return [np.zeros ((self.nroots,2,ncas,ncas)) for ncas in ncas_sub] + casdm1s = [] + for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + if ci_i is None: + dm1a = dm1b = np.zeros ((ncas, ncas)) + else: + dm1a, dm1b = fcibox.states_make_rdm1s (ci_i, ncas, nelecas) + casdm1s.append (np.stack ([dm1a, dm1b], axis=1)) + return casdm1s + + def make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, w=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + if w is None: w = self.weights + return [np.einsum ('rspq,r->spq', dm1, w) for dm1 in casdm1frs] + + def states_make_casdm1s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + return np.stack ([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] + for dm1rs in casdm1frs]) + for ispin in (0, 1)], axis=0) + for iroot in range (self.nroots)], axis=0) + + def states_make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + casdm2 = [] + for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) + return casdm2 + + def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=None, **kwargs): + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, **kwargs) + return [np.einsum ('rijkl,r->ijkl', dm2, box.weights) + for dm2, box in zip (casdm2fr, self.fciboxes)] + + def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, + nelecas_sub=None, casdm1rs=None, casdm1frs=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1rs is None: casdm1rs = self.states_make_casdm1s (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, + **kwargs) + mo_core = mo_coeff[:,:self.ncore] + mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] + dm1rs = np.tensordot (mo_cas.conj (), np.dot (casdm1rs, mo_cas.conj ().T), axes=((1),(2))) + dm1rs = dm1rs.transpose (1,2,0,3) + dm1rs += (mo_core @ mo_core.conj ().T)[None,None,:,:] + return dm1rs + + def make_rdm1s_sub (self, mo_coeff=None, ci=None, ncas_sub=None, + nelecas_sub=None, include_core=False, casdm1s_sub=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ''' Same as make_casdm1s_sub, but in the ao basis ''' + rdm1s = [] + for idx, casdm1s in enumerate (casdm1s_sub): + mo = self.get_mo_slice (idx, mo_coeff=mo_coeff) + moH = mo.conjugate ().T + rdm1s.append (np.tensordot (mo, np.dot (casdm1s,moH), axes=((1),(1))).transpose(1,0,2)) + if include_core and self.ncore: + mo_core = mo_coeff[:,:self.ncore] + moH_core = mo_core.conjugate ().T + dm_core = mo_core @ moH_core + rdm1s = [np.stack ([dm_core, dm_core], axis=0)] + rdm1s + rdm1s = np.stack (rdm1s, axis=0) + return rdm1s + + def make_rdm1_sub (self, **kwargs): + return self.make_rdm1s_sub (**kwargs).sum (1) + + def make_rdm1s (self, mo_coeff=None, ncore=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + mo = mo_coeff[:,:ncore] + moH = mo.conjugate ().T + dm_core = mo @ moH + dm_cas = self.make_rdm1s_sub (mo_coeff=mo_coeff, **kwargs).sum (0) + return dm_core[None,:,:] + dm_cas + + def make_rdm1 (self, mo_coeff=None, ci=None, **kwargs): + return self.make_rdm1s (mo_coeff=mo_coeff, ci=ci, **kwargs).sum (0) + + def make_casdm1s (self, ci=None, **kwargs): + ''' Make the full-dimensional casdm1s spanning the collective active space ''' + casdm1s_sub = self.make_casdm1s_sub (ci=ci, **kwargs) + casdm1a = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) + casdm1b = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) + return np.stack ([casdm1a, casdm1b], axis=0) + + def make_casdm1 (self, ci=None, **kwargs): + ''' Spin-sum make_casdm1s ''' + return self.make_casdm1s (ci=ci, **kwargs).sum (0) + + def states_make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' Make the full-dimensional casdm2 spanning the collective active space ''' + raise DeprecationWarning ( + ("states_make_casdm2 is BANNED. There is no reason to EVER make an array this huge.\n" + "Use states_make_casdm*_sub instead, and substitute the factorization into your " + "expressions.") + ) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + casdm2r = np.zeros ((self.nroots,ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2 in enumerate (casdm2fr): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2r[:, i:j, i:j, i:j, i:j] = dm2 + # Off-diagonal + for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2r): + dma1, dmb1 = dm1s1[0], dm1s1[1] + dma2, dmb2 = dm1s2[0], dm1s2[1] + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) + +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2r + + def state_make_casdm1s(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + casdm1s = np.stack([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] + for dm1rs in casdm1frs]) + for ispin in (0, 1)], axis=0) + for iroot in range (self.nroots)], axis=0) + return casdm1s[state] + + def state_make_casdm2(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' State wise casdm2 spanning the collective active space. ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2_r in enumerate (casdm2fr): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2[i:j, i:j, i:j, i:j] = dm2_r[state] + # Off-diagonal + for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1, dmb1 = dm1s1_r[state][0], dm1s1_r[state][1] + dma2, dmb2 = dm1s2_r[state][0], dm1s2_r[state][1] + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) + +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2 + + def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, + **kwargs): + ''' Make the full-dimensional casdm2 spanning the collective active space ''' + if casdm2r is not None: + return np.einsum ('rijkl,r->ijkl', casdm2r, self.weights) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + weights = self.weights + casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2 in enumerate (casdm2f): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2[i:j, i:j, i:j, i:j] = dm2 + # Off-diagonal + for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] + dm1r = dma1r + dmb1r + dm2r = dma2r + dmb2r + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) + casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2 + + def get_veff (self, mol=None, dm1s=None, hermi=1, spin_sep=False, **kwargs): + ''' Returns a spin-summed veff! If dm1s isn't provided, builds from self.mo_coeff, self.ci + etc. ''' + if mol is None: mol = self.mol + nao = mol.nao_nr () + if dm1s is None: dm1s = self.make_rdm1 (include_core=True, **kwargs).reshape (nao, nao) + dm1s = np.asarray (dm1s) + if dm1s.ndim == 2: dm1s = dm1s[None,:,:] + if isinstance (self, _DFLASCI): + vj, vk = self.with_df.get_jk(dm1s, hermi=hermi) + else: + vj, vk = self._scf.get_jk(mol, dm1s, hermi=hermi) + if spin_sep: + assert (dm1s.shape[0] == 2) + return vj.sum (0)[None,:,:] - vk + else: + veff = np.stack ([j - k/2 for j, k in zip (vj, vk)], axis=0) + return np.squeeze (veff) + + def split_veff (self, veff, h2eff_sub, mo_coeff=None, ci=None, casdm1s_sub=None): + ''' Split a spin-summed veff into alpha and beta terms using the h2eff eri array. + Note that this will omit v(up_active - down_active)^virtual_inactive by necessity; + this won't affect anything because the inactive density matrix has no spin component. + On the other hand, it ~is~ necessary to correctly do + + v(up_active - down_active)^unactive_active + + in order to calculate the external orbital gradient at the end of the calculation. + This means that I need h2eff_sub spanning both at least two active subspaces + ~and~ the full orbital range. ''' + veff_c = veff.copy () + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci = ci) + ncore = self.ncore + ncas = self.ncas + nocc = ncore + ncas + nao, nmo = mo_coeff.shape + moH_coeff = mo_coeff.conjugate ().T + smo_coeff = self._scf.get_ovlp () @ mo_coeff + smoH_coeff = smo_coeff.conjugate ().T + veff_s = np.zeros_like (veff_c) + for ix, (ncas_i, casdm1s) in enumerate (zip (self.ncas_sub, casdm1s_sub)): + i = sum (self.ncas_sub[:ix]) + j = i + ncas_i + eri_k = h2eff_sub.reshape (nmo, ncas, -1)[:,i:j,...].reshape (nmo*ncas_i, -1) + eri_k = lib.numpy_helper.unpack_tril (eri_k)[:,i:j,:] + eri_k = eri_k.reshape (nmo, ncas_i, ncas_i, ncas) + sdm = casdm1s[0] - casdm1s[1] + vk_pa = -np.tensordot (eri_k, sdm, axes=((1,2),(0,1))) / 2 + veff_s[:,ncore:nocc] += vk_pa + veff_s[ncore:nocc,:] += vk_pa.T + veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:] / 2 + veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:].T / 2 + veff_s = smo_coeff @ veff_s @ smoH_coeff + veffa = veff_c + veff_s + veffb = veff_c - veff_s + return np.stack ([veffa, veffb], axis=0) + + + def states_energy_elec (self, mo_coeff=None, ncore=None, ncas=None, + ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, + casdm1frs=None, casdm2fr=None, veff_core=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + if ncas is None: ncas = self.ncas + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: ci = self.ci + if h2eff is None: h2eff = self.get_h2eff (mo_coeff) + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub) + nao, nmo = mo_coeff.shape + nocc = ncore + ncas + mo_core = mo_coeff[:,:ncore] + mo_cas = mo_coeff[:,ncore:nocc] + dm_core = 2*mo_core @ mo_core.conj ().T + if veff_core is None: veff_core = getattr (veff, 'c', None) + if veff_core is None: veff_core = self.get_veff (dm1s=dm_core) + h1eff = self.get_hcore () + veff_core + e0 = 2*np.dot (((h1eff-(veff_core/2)) @ mo_core).ravel (), mo_core.conj().ravel ()) + h1eff = mo_cas.conj ().T @ h1eff @ mo_cas + eri_cas = lib.numpy_helper.unpack_tril (h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)) + eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) + eri_cas = eri_cas[ncore:nocc] + casdm1rs = self.states_make_casdm1s (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, + casdm1frs=casdm1frs) + vj_r = np.tensordot (casdm1rs.sum (1), eri_cas, axes=2) + vk_rs = np.tensordot (casdm1rs, eri_cas, axes=((2,3),(2,1))) + veff_rs = vj_r[:,None,:,:] - vk_rs + + energy_elec = [] + for idx, (dm1s, v) in enumerate (zip (casdm1rs, veff_rs)): + casdm1fs = [dm[idx] for dm in casdm1frs] + casdm2f = [dm[idx] for dm in casdm2fr] + + # 1-body veff terms + h1e = h1eff[None,:,:] + v/2 + e1 = np.dot (h1e.ravel (), dm1s.ravel ()) + + # 2-body cumulant terms + e2 = 0 + for isub, (dm1s, dm2) in enumerate (zip (casdm1fs, casdm2f)): + dm1a, dm1b = dm1s[0], dm1s[1] + dm1 = dm1a + dm1b + cdm2 = dm2 - np.multiply.outer (dm1, dm1) + cdm2 += np.multiply.outer (dm1a, dm1a).transpose (0,3,2,1) + cdm2 += np.multiply.outer (dm1b, dm1b).transpose (0,3,2,1) + eri = self.get_h2eff_slice (h2eff, isub) + te2 = np.tensordot (eri, cdm2, axes=4) / 2 + e2 += te2 + energy_elec.append (e0 + e1 + e2) + self._e1_ref = e0 + e1 + self._e2_ref = e2 + + return energy_elec + + def energy_elec (self, mo_coeff=None, ncore=None, ncas=None, + ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' Since the LASCI energy cannot be calculated as simply as ecas + ecore, I need this ''' + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + if ncas is None: ncas = self.ncas + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: ci = self.ci + if h2eff is None: h2eff = self.get_h2eff (mo_coeff) + casdm1s_sub = self.make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, + casdm1frs=casdm1frs) + if veff is None: + veff = self.get_veff (dm1s = self.make_rdm1(mo_coeff=mo_coeff,casdm1s_sub=casdm1s_sub)) + veff = self.split_veff (veff, h2eff, mo_coeff=mo_coeff, casdm1s_sub=casdm1s_sub) + + # 1-body veff terms + h1e = self.get_hcore ()[None,:,:] + veff/2 + dm1s = self.make_rdm1s (mo_coeff=mo_coeff, ncore=ncore, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1s_sub=casdm1s_sub) + e1 = np.dot (h1e.ravel (), dm1s.ravel ()) + + # 2-body cumulant terms + casdm1s = self.make_casdm1s (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1frs=casdm1frs) + casdm1 = casdm1s.sum (0) + casdm2 = self.make_casdm2 (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, casdm2fr=casdm2fr) + casdm2 -= np.multiply.outer (casdm1, casdm1) + casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) + casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) + ncore, ncas, nocc = self.ncore, self.ncas, self.ncore + self.ncas + eri = lib.numpy_helper.unpack_tril (h2eff[ncore:nocc].reshape (ncas*ncas, -1)) + eri = eri.reshape ([ncas,]*4) + e2 = np.tensordot (eri, casdm2, axes=4)/2 + + e0 = self.energy_nuc () + self._e1_test = e1 + self._e2_test = e2 + return e1 + e2 + + _ugg = lasci_sync.LASCI_UnitaryGroupGenerators + def get_ugg (self, mo_coeff=None, ci=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + return self._ugg (self, mo_coeff, ci) + + def cderi_ao2mo (self, mo_i, mo_j, compact=False): + assert (isinstance (self, _DFLASCI)) + nmo_i, nmo_j = mo_i.shape[-1], mo_j.shape[-1] + if compact: + assert (nmo_i == nmo_j) + bPij = np.empty ((self.with_df.get_naoaux (), nmo_i*(nmo_i+1)//2), dtype=mo_i.dtype) + else: + bPij = np.empty ((self.with_df.get_naoaux (), nmo_i, nmo_j), dtype=mo_i.dtype) + ijmosym, mij_pair, moij, ijslice = ao2mo.incore._conc_mos (mo_i, mo_j, compact=compact) + b0 = 0 + for eri1 in self.with_df.loop (): + b1 = b0 + eri1.shape[0] + eri2 = bPij[b0:b1] + eri2 = ao2mo._ao2mo.nr_e2 (eri1, moij, ijslice, aosym='s2', mosym=ijmosym, out=eri2) + b0 = b1 + return bPij + + def fast_veffa (self, casdm1s_sub, h2eff_sub, mo_coeff=None, ci=None, _full=False): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + assert (isinstance (self, _DFLASCI) or _full) + ncore = self.ncore + ncas_sub = self.ncas_sub + ncas = sum (ncas_sub) + nocc = ncore + ncas + nao, nmo = mo_coeff.shape + + mo_cas = mo_coeff[:,ncore:nocc] + moH_cas = mo_cas.conjugate ().T + moH_coeff = mo_coeff.conjugate ().T + dma = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) + dmb = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) + casdm1s = np.stack ([dma, dmb], axis=0) + if not (isinstance (self, _DFLASCI)): + dm1s = np.dot (mo_cas, np.dot (casdm1s, moH_cas)).transpose (1,0,2) + return self.get_veff (dm1s = dm1s, spin_sep=True) + casdm1 = casdm1s.sum (0) + dm1 = np.dot (mo_cas, np.dot (casdm1, moH_cas)) + bPmn = sparsedf_array (self.with_df._cderi) + + # vj + dm_tril = dm1 + dm1.T - np.diag (np.diag (dm1.T)) + rho = np.dot (bPmn, lib.pack_tril (dm_tril)) + vj = lib.unpack_tril (np.dot (rho, bPmn)) + + # vk + bmPu = h2eff_sub.bmPu + if _full: + vmPsu = np.dot (bmPu, casdm1s) + vk = np.tensordot (vmPsu, bmPu, axes=((1,3),(1,2))).transpose (1,0,2) + return vj[None,:,:] - vk + else: + vmPu = np.dot (bmPu, casdm1) + vk = np.tensordot (vmPu, bmPu, axes=((1,2),(1,2))) + return vj - vk/2 + + @lib.with_doc(run_lasci.__doc__) + def lasci (self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, + assert_no_dupes=False, _dry_run=False): + if mo_coeff is None: mo_coeff=self.mo_coeff + if ci0 is None: ci0 = self.ci + if verbose is None: verbose = self.verbose + converged, e_tot, e_states, e_cas, e_lexc, ci = run_lasci ( + self, mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, + verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) + if _dry_run: return + self.converged, self.ci = converged, ci + self.e_tot, self.e_states, self.e_cas, self.e_lexc = e_tot, e_states, e_cas, e_lexc + if mo_coeff is self.mo_coeff: + self.dump_chk () + elif getattr (self, 'chkfile', None) is not None: + lib.logger.warn (self, 'orbitals changed; chkfile not dumped!') + self._finalize () + return self.converged, self.e_tot, self.e_states, self.e_cas, e_lexc, self.ci + + @lib.with_doc(run_lasci.__doc__) + def lasci_(self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, + assert_no_dupes=False, _dry_run=False): + if mo_coeff is not None: + self.mo_coeff = mo_coeff + return self.lasci (mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, + verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) + + state_average = state_average + state_average_ = state_average_ + get_single_state_las = get_single_state_las + + def lassi(self, mo_coeff=None, ci=None, veff_c=None, h2eff_sub=None, orbsym=None, + soc=False, break_symmetry=False, opt=1, **kwargs): + #import warnings + #lassi_kernel_warn = "Now LASSI have kernel, which takes las instance as input. This [las.lassi()] function " \ + # "will be removed soon." + #warnings.warn(lassi_kernel_warn, stacklevel=3) + from mrh.my_pyscf.lassi import lassi + mylassi = lassi.LASSI(self, mo_coeff=mo_coeff, ci=ci, soc=soc, opt=opt, + break_symmetry=break_symmetry, **kwargs) + return mylassi.kernel(mo_coeff=mo_coeff, ci=ci, veff_c=veff_c, h2eff_sub=h2eff_sub, + orbsym=orbsym) + + las2cas_civec = las2cas_civec + assert_no_duplicates = assert_no_duplicates + get_init_guess_ci = get_init_guess_ci + localize_init_guess=lasscf_guess.localize_init_guess + def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): + if s is None: s = self._scf.get_ovlp () + return matrix_svd_control_options (s, lspace=mo_lspace, rspace=mo_rspace, full_matrices=True)[:3] + + def dump_flags (self, verbose=None, _method_name='LASCI'): + log = lib.logger.new_logger (self, verbose) + log.info ('') + log.info ('******** %s flags ********', _method_name) + ncore = self.ncore + ncas = self.ncas + nvir = self.mo_coeff.shape[1] - ncore - ncas + nfrags = len (self.nelecas_sub) + log.info ('CAS (%de+%de, %do), ncore = %d, nvir = %d', + self.nelecas[0], self.nelecas[1], ncas, ncore, nvir) + log.info ('Divided into %d LAS spaces', nfrags) + for i, (no, ne) in enumerate (zip (self.ncas_sub, self.nelecas_sub)): + log.info ('LAS %d : (%de+%de, %do)', i, ne[0], ne[1], no) + log.info ('nroots = %d', self.nroots) + log.info ('max_memory %d (MB)', self.max_memory) + for i, fcibox in enumerate (self.fciboxes): + if getattr (fcibox, 'dump_flags', None): + log.info ('fragment %d FCI solver flags:', i) + fcibox.dump_flags (log.verbose) + + @property + def converged (self): + return all (self.states_converged) + @converged.setter + def converged (self, x): + if hasattr (x, '__len__'): + self.states_converged = list (x) + else: + self.states_converged = [x,]*self.nroots + + def dump_spaces (self, nroots=None, sort_energy=False): + log = lib.logger.new_logger (self, self.verbose) + log.info ("******** LAS space tables ********") + ci = self.ci + if nroots is None and self.verbose <= lib.logger.INFO: + nroots = min (self.nroots, 100) + elif nroots is None: + nroots = self.nroots + if nroots < self.nroots: + log.warn ("Dumping only 100 of %d spaces", self.nroots) + log.warn ("To see more, explicitly pass nroots to dump_spaces or increase verbosity") + if sort_energy: + idx = np.argsort (self.e_states) + else: + idx = range (nroots) + for state in idx: + neleca_f = [] + nelecb_f = [] + wfnsym_f = [] + wfnsym = 0 + m_f = [] + s_f = [] + lroots = [] + s2_tot = 0 + for ifrag, (fcibox, nelecas) in enumerate (zip (self.fciboxes, self.nelecas_sub)): + solver = fcibox.fcisolvers[state] + na, nb = _unpack_nelec (fcibox._get_nelec (solver, nelecas)) + neleca_f.append (na) + nelecb_f.append (nb) + m_f.append ((na-nb)/2) + s_f.append ((solver.smult-1)/2) + s2_tot += s_f[-1] * (s_f[-1] + 1) + fragsym = getattr (solver, 'wfnsym', 0) or 0 + if isinstance (fragsym, str): + fragsym_str = fragsym + fragsym_id = symm.irrep_name2id (solver.mol.groupname, fragsym) + else: + fragsym_id = fragsym + fragsym_str = symm.irrep_id2name (solver.mol.groupname, fragsym) + wfnsym ^= fragsym_id + wfnsym_f.append (fragsym_str) + lroots_i = 0 + if ci is not None: + if ci[ifrag] is not None: + ci_i = ci[ifrag] + if ci_i[state] is not None: + ci_ij = ci_i[state] + lroots_i = 1 if ci_ij.ndim<3 else ci_ij.shape[0] + lroots.append (lroots_i) + s2_tot += sum ([2*m1*m2 for m1, m2 in combinations (m_f, 2)]) + s_f, m_f = np.asarray (s_f), np.asarray (m_f) + if np.all (m_f<0): m_f *= -1 + s_pure = bool (np.all (s_f==m_f)) + wfnsym = symm.irrep_id2name (self.mol.groupname, wfnsym) + neleca = sum (neleca_f) + nelecb = sum (nelecb_f) + log.info ("LAS space %d: (%de+%de,%do) wfynsm=%s", state, neleca, nelecb, self.ncas, wfnsym) + log.info ("Converged? %s", self.states_converged[state]) + log.info ("E(LAS) = %.15g", self.e_states[state]) + log.info ("S^2 = %.7f (%s)", s2_tot, ('Impure','Pure')[s_pure]) + log.info ("Space table") + log.info (" frag (ae+be,no) 2S+1 ir lroots") + for i in range (self.nfrags): + smult_f = int (round (2*s_f[i] + 1)) + tupstr = '({}e+{}e,{}o)'.format (neleca_f[i], nelecb_f[i], self.ncas_sub[i]) + log.info (" %4d %13s %4d %3s %6d", i, tupstr, smult_f, wfnsym_f[i], lroots[i]) + + def check_sanity (self): + casci.CASCI.check_sanity (self) + self.get_ugg () # constructor encounters impossible states and raises error + + dump_chk = chkfile.dump_las + load_chk = load_chk_ = chkfile.load_las_ + +class LASCISymm (casci_symm.CASCI, LASCINoSymm): + + def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, wfnsym_sub=None, frozen=None, + **kwargs): + LASCINoSymm.__init__(self, mf, ncas, nelecas, ncore=ncore, spin_sub=spin_sub, + frozen=frozen, **kwargs) + if getattr (self.mol, 'groupname', None) in ('Dooh', 'Coov'): + raise NotImplementedError ("LASSCF support for cylindrical point group {}".format ( + self.mol.groupname)) + if wfnsym_sub is None: wfnsym_sub = [0 for icas in self.ncas_sub] + # TODO: guess wfnsym_sub intelligently (0 can be impossible for some multiplicities) + for wfnsym, frag in zip (wfnsym_sub, self.fciboxes): + if isinstance (wfnsym, (str, np.str_)): + wfnsym = symm.irrep_name2id (self.mol.groupname, wfnsym) + frag.fcisolvers[0].wfnsym = wfnsym + + make_rdm1s = LASCINoSymm.make_rdm1s + make_rdm1 = LASCINoSymm.make_rdm1 + get_veff = LASCINoSymm.get_veff + get_h1eff = get_h1las = h1e_for_las + dump_flags = LASCINoSymm.dump_flags + dump_spaces = LASCINoSymm.dump_spaces + check_sanity = LASCINoSymm.check_sanity + _ugg = lasci_sync.LASCISymm_UnitaryGroupGenerators + + @property + def wfnsym (self): + ''' This now returns the product of the irreps of the subspaces ''' + wfnsym = [0,]*self.nroots + for frag in self.fciboxes: + for state, solver in enumerate (frag.fcisolvers): + wfnsym[state] ^= solver.wfnsym + if self.nroots == 1: wfnsym = wfnsym[0] + return wfnsym + @wfnsym.setter + def wfnsym (self, ir): + raise RuntimeError (("Cannot assign the whole-system symmetry of a LASCI wave function. " + "Address fciboxes[ifrag].fcisolvers[istate].wfnsym instead.")) + + def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, verbose=None, assert_no_dupes=False): + if mo_coeff is None: + mo_coeff = self.mo_coeff + if ci0 is None: + ci0 = self.ci + + # Initialize/overwrite mo_coeff.orbsym. Don't pass ci0 because it's not the right shape + lib.logger.info (self, ("LASCI lazy hack note: lines below reflect the point-group " + "symmetry of the whole molecule but not of the individual " + "subspaces")) + mo_coeff = self.mo_coeff = self.label_symmetry_(mo_coeff) + return LASCINoSymm.kernel(self, mo_coeff=mo_coeff, ci0=ci0, + casdm0_fr=casdm0_fr, verbose=verbose, assert_no_dupes=assert_no_dupes) + + def canonicalize (self, mo_coeff=None, ci=None, natorb_casdm1=None, veff=None, h2eff_sub=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + mo_coeff = self.label_symmetry_(mo_coeff) + return canonicalize (self, mo_coeff=mo_coeff, ci=ci, natorb_casdm1=natorb_casdm1, + h2eff_sub=h2eff_sub, orbsym=mo_coeff.orbsym) + + def label_symmetry_(self, mo_coeff=None): + if mo_coeff is None: mo_coeff=self.mo_coeff + ncore = self.ncore + ncas_sub = self.ncas_sub + nocc = ncore + sum (ncas_sub) + mo_coeff[:,:ncore] = symm.symmetrize_space (self.mol, mo_coeff[:,:ncore]) + for isub, ncas in enumerate (ncas_sub): + i = ncore + sum (ncas_sub[:isub]) + j = i + ncas + mo_coeff[:,i:j] = symm.symmetrize_space (self.mol, mo_coeff[:,i:j]) + mo_coeff[:,nocc:] = symm.symmetrize_space (self.mol, mo_coeff[:,nocc:]) + orbsym = symm.label_orb_symm (self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_coeff, + s=self._scf.get_ovlp ()) + mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) + return mo_coeff + + @lib.with_doc(LASCINoSymm.localize_init_guess.__doc__) + def localize_init_guess (self, frags_atoms, mo_coeff=None, spin=None, lo_coeff=None, fock=None, + freeze_cas_spaces=False): + if mo_coeff is None: + mo_coeff = self.mo_coeff + mo_coeff = casci_symm.label_symmetry_(self, mo_coeff) + return LASCINoSymm.localize_init_guess (self, frags_atoms, mo_coeff=mo_coeff, spin=spin, + lo_coeff=lo_coeff, fock=fock, freeze_cas_spaces=freeze_cas_spaces) + + def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): + if s is None: s = self._scf.get_ovlp () + lsymm = getattr (mo_lspace, 'orbsym', None) + if lsymm is None: + mo_lspace = symm.symmetrize_space (self.mol, mo_lspace) + lsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_lspace, s=s) + rsymm = getattr (mo_rspace, 'orbsym', None) + if rsymm is None: + mo_rspace = symm.symmetrize_space (self.mol, mo_rspace) + rsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_rspace, s=s) + decomp = matrix_svd_control_options (s, + lspace=mo_lspace, rspace=mo_rspace, + lspace_symmetry=lsymm, rspace_symmetry=rsymm, + full_matrices=True, strong_symm=True) + mo_lvecs, svals, mo_rvecs, lsymm, rsymm = decomp + mo_lvecs = lib.tag_array (mo_lvecs, orbsym=lsymm) + mo_rvecs = lib.tag_array (mo_rvecs, orbsym=rsymm) + return mo_lvecs, svals, mo_rvecs + diff --git a/my_pyscf/mcscf/lasci_sv_expanded.py b/my_pyscf/mcscf/lasci_sv_expanded.py new file mode 100644 index 00000000..e10ce953 --- /dev/null +++ b/my_pyscf/mcscf/lasci_sv_expanded.py @@ -0,0 +1,2060 @@ +from pyscf.scf.rohf import get_roothaan_fock +from pyscf import fci +from pyscf.fci import cistring +from pyscf.mcscf import casci, casci_symm, df +from pyscf.tools import dump_mat +from pyscf import symm, gto, scf, ao2mo, lib +from pyscf.fci.direct_spin1 import _unpack_nelec +from mrh.my_pyscf.mcscf.addons import state_average_n_mix, get_h1e_zipped_fcisolver, las2cas_civec +from mrh.my_pyscf.mcscf import lasci_sync, _DFLASCI, lasscf_guess +from mrh.my_pyscf.fci import csf_solver +from mrh.my_pyscf.df.sparse_df import sparsedf_array +from mrh.my_pyscf.mcscf import chkfile +from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver +from mrh.util.la import matrix_svd_control_options +from itertools import combinations, product +from scipy.sparse import linalg as sparse_linalg +from scipy import linalg +import numpy as np +import copy + +def LASCI (mf_or_mol, ncas_sub, nelecas_sub, **kwargs): + if isinstance(mf_or_mol, gto.Mole): + mf = scf.RHF(mf_or_mol) + else: + mf = mf_or_mol + if mf.mol.symmetry: + las = LASCISymm (mf, ncas_sub, nelecas_sub, **kwargs) + else: + las = LASCINoSymm (mf, ncas_sub, nelecas_sub, **kwargs) + if getattr (mf, 'with_df', None): + las = density_fit (las, with_df = mf.with_df) + return las + +def get_grad (las, mo_coeff=None, ci=None, ugg=None, h1eff_sub=None, h2eff_sub=None, + veff=None, dm1s=None): + '''Return energy gradient for orbital rotation and CI relaxation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + ugg : instance of :class:`LASCI_UnitaryGroupGenerators` + h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray + Contains effective one-electron Hamiltonians experienced by each fragment + in each state + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + dm1s : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-RDM in the AO basis + + Returns: + gorb : ndarray of shape (ugg.nvar_orb,) + Orbital rotation gradients as a flat array + gci : ndarray of shape (sum(ugg.ncsf_sub),) + CI relaxation gradients as a flat array + gx : ndarray + Orbital rotation gradients for temporarily frozen orbitals in the "LASCI" problem + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if ugg is None: ugg = las.get_ugg (mo_coeff, ci) + if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if veff is None: + veff = las.get_veff (dm1s = dm1s.sum (0)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) + if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, + h2eff_sub=h2eff_sub) + + gorb = get_grad_orb (las, mo_coeff=mo_coeff, ci=ci, h2eff_sub=h2eff_sub, veff=veff, dm1s=dm1s) + gci = get_grad_ci (las, mo_coeff=mo_coeff, ci=ci, h1eff_sub=h1eff_sub, h2eff_sub=h2eff_sub, + veff=veff) + + idx = ugg.get_gx_idx () + gx = gorb[idx] + gint = ugg.pack (gorb, gci) + gorb = gint[:ugg.nvar_orb] + gci = gint[ugg.nvar_orb:] + return gorb, gci, gx.ravel () + +def get_grad_orb (las, mo_coeff=None, ci=None, h2eff_sub=None, veff=None, dm1s=None, hermi=-1): + '''Return energy gradient for orbital rotation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + dm1s : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-RDM in the AO basis + hermi : integer + Control (anti-)symmetrization. 0 means to return the effective Fock matrix, + F1 = h.D + g.d. -1 means to return the true orbital-rotation gradient, which is skew- + symmetric: gorb = F1 - F1.T. +1 means to return the symmetrized effective Fock matrix, + (F1 + F1.T) / 2. The factor of 2 difference between hermi=-1 and the other two options + is intentional and necessary. + + Returns: + gorb : ndarray of shape (nmo,nmo) + Orbital rotation gradients as a square antihermitian array + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if veff is None: + veff = las.get_veff (dm1s = dm1s.sum (0)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) + nao, nmo = mo_coeff.shape + ncore = las.ncore + ncas = las.ncas + nocc = las.ncore + las.ncas + smo_cas = las._scf.get_ovlp () @ mo_coeff[:,ncore:nocc] + smoH_cas = smo_cas.conj ().T + + # The orbrot part + h1s = las.get_hcore ()[None,:,:] + veff + f1 = h1s[0] @ dm1s[0] + h1s[1] @ dm1s[1] + f1 = mo_coeff.conjugate ().T @ f1 @ las._scf.get_ovlp () @ mo_coeff + # ^ I need the ovlp there to get dm1s back into its correct basis + casdm2 = las.make_casdm2 (ci=ci) + casdm1s = np.stack ([smoH_cas @ d @ smo_cas for d in dm1s], axis=0) + casdm1 = casdm1s.sum (0) + casdm2 -= np.multiply.outer (casdm1, casdm1) + casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) + casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) + eri = h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2) + eri = lib.numpy_helper.unpack_tril (eri).reshape (nmo, ncas, ncas, ncas) + f1[:,ncore:nocc] += np.tensordot (eri, casdm2, axes=((1,2,3),(1,2,3))) + + if hermi == -1: + return f1 - f1.T + elif hermi == 1: + return .5*(f1+f1.T) + elif hermi == 0: + return f1 + else: + raise ValueError ("kwarg 'hermi' must = -1, 0, or +1") + +def get_grad_ci (las, mo_coeff=None, ci=None, h1eff_sub=None, h2eff_sub=None, veff=None): + '''Return energy gradient for CI relaxation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray + Contains effective one-electron Hamiltonians experienced by each fragment + in each state + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + + Returns: + gci : list (length=nfrags) of list (length=nroots) of ndarray + CI relaxation gradients in the shape of CI vectors + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, + h2eff_sub=h2eff_sub) + gci = [] + for isub, (fcibox, h1e, ci0, ncas, nelecas) in enumerate (zip ( + las.fciboxes, h1eff_sub, ci, las.ncas_sub, las.nelecas_sub)): + eri_cas = las.get_h2eff_slice (h2eff_sub, isub, compact=8) + linkstrl = fcibox.states_gen_linkstr (ncas, nelecas, True) + linkstr = fcibox.states_gen_linkstr (ncas, nelecas, False) + h2eff = fcibox.states_absorb_h1e(h1e, eri_cas, ncas, nelecas, .5) + hc0 = fcibox.states_contract_2e(h2eff, ci0, ncas, nelecas, link_index=linkstrl) + hc0 = [hc.ravel () for hc in hc0] + ci0 = [c.ravel () for c in ci0] + gci.append ([2.0 * (hc - c * (c.dot (hc))) for c, hc in zip (ci0, hc0)]) + return gci + +def density_fit (las, auxbasis=None, with_df=None): + ''' Here I ONLY need to attach the tag and the df object because I put conditionals in + LASCINoSymm to make my life easier ''' + las_class = las.__class__ + if with_df is None: + if (getattr(las._scf, 'with_df', None) and + (auxbasis is None or auxbasis == las._scf.with_df.auxbasis)): + with_df = las._scf.with_df + else: + with_df = df.DF(las.mol) + with_df.max_memory = las.max_memory + with_df.stdout = las.stdout + with_df.verbose = las.verbose + with_df.auxbasis = auxbasis + class DFLASCI (las_class, _DFLASCI): + def __init__(self, scf, ncas_sub, nelecas_sub): + self.with_df = with_df + self._keys = self._keys.union(['with_df']) + las_class.__init__(self, scf, ncas_sub, nelecas_sub) + new_las = DFLASCI (las._scf, las.ncas_sub, las.nelecas_sub) + new_las.__dict__.update (las.__dict__) + return new_las + +def h1e_for_las (las, mo_coeff=None, ncas=None, ncore=None, nelecas=None, ci=None, ncas_sub=None, + nelecas_sub=None, veff=None, h2eff_sub=None, casdm1s_sub=None, casdm1frs=None): + ''' Effective one-body Hamiltonians (plural) for a LASCI problem + + Args: + las: a LASCI object + + Kwargs: + mo_coeff: ndarray of shape (nao,nmo) + Orbital coefficients ordered on the columns as: + core orbitals, subspace 1, subspace 2, ..., external orbitals + ncas: integer + As in PySCF's existing CASCI/CASSCF implementation + nelecas: sequence of 2 integers + As in PySCF's existing CASCI/CASSCF implementation + ci: list (length=nfrags) of list (length=nroots) of ndarrays + Contains CI vectors + ncas_sub: ndarray of shape (nsub) + Number of active orbitals in each subspace + nelecas_sub: ndarray of shape (nsub,2) + na, nb in each subspace + veff: ndarray of shape (2, nao, nao) + Contains spin-separated, state-averaged effective potential + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + casdm1s_sub : list (length=nfrags) of ndarrays + Contains state-averaged, spin-separated 1-RDMs in the localized active subspaces + casdm1frs : list (length=nfrags) of list (length=nroots) of ndarrays + Contains spin-separated 1-RDMs for each state in the localized active subspaces + + Returns: + h1e_fr: list (length=nfrags) of list (length=nroots) of ndarrays + Spin-separated 1-body Hamiltonian operator for each fragment and state + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ncas is None: ncas = las.ncas + if ncore is None: ncore = las.ncore + if ncas_sub is None: ncas_sub = las.ncas_sub + if nelecas_sub is None: nelecas_sub = las.nelecas_sub + if ncore is None: ncore = las.ncore + if ci is None: ci = las.ci + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if casdm1frs is None: casdm1frs = las.states_make_casdm1s_sub (ci=ci) + if casdm1s_sub is None: casdm1s_sub = [np.einsum ('rsij,r->sij',dm,las.weights) + for dm in casdm1frs] + if veff is None: + veff = las.get_veff (dm1s = las.make_rdm1 (mo_coeff=mo_coeff, ci=ci)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci, casdm1s_sub=casdm1s_sub) + + # First pass: split by root + nocc = ncore + ncas + nmo = mo_coeff.shape[-1] + mo_cas = mo_coeff[:,ncore:nocc] + moH_cas = mo_cas.conj ().T + h1e = moH_cas @ (las.get_hcore ()[None,:,:] + veff) @ mo_cas + h1e_r = np.empty ((las.nroots, 2, ncas, ncas), dtype=h1e.dtype) + h2e = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, + ncas*(ncas+1)//2)).reshape (nmo, ncas, ncas, ncas)[ncore:nocc,:,:,:] + #print ("SV h2e = ",h2e, h2e.shape) + avgdm1s = np.stack ([linalg.block_diag (*[dm[spin] for dm in casdm1s_sub]) + for spin in range (2)], axis=0) + for state in range (las.nroots): + statedm1s = np.stack ([linalg.block_diag (*[dm[state][spin] for dm in casdm1frs]) + for spin in range (2)], axis=0) + dm1s = statedm1s - avgdm1s + j = np.tensordot (dm1s, h2e, axes=((1,2),(2,3))) + k = np.tensordot (dm1s, h2e, axes=((1,2),(2,1))) + h1e_r[state] = h1e + j + j[::-1] - k + + + # Second pass: split by fragment and subtract double-counting + h1e_fr = [] + for ix, casdm1s_r in enumerate (casdm1frs): + p = sum (las.ncas_sub[:ix]) + q = p + las.ncas_sub[ix] + h1e = h1e_r[:,:,p:q,p:q] + h2e = las.get_h2eff_slice (h2eff_sub, ix) + j = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,3))) + k = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,1))) + h1e_fr.append (h1e - j - j[:,::-1] + k) + + return h1e_fr + +def get_fock (las, mo_coeff=None, ci=None, eris=None, casdm1s=None, verbose=None, veff=None, + dm1s=None): + ''' f_pq = h_pq + (g_pqrs - g_psrq/2) D_rs, AO basis + Note the difference between this and h1e_for_las: h1e_for_las only has + JK terms from electrons outside the "current" active subspace; get_fock + includes JK from all electrons. This is also NOT the "generalized Fock matrix" + of orbital gradients (but it can be used in calculating those if you do a + semi-cumulant decomposition). + The "eris" kwarg does not do anything and is retained only for backwards + compatibility (also why I don't just call las.make_rdm1) ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if dm1s is None: + if casdm1s is None: casdm1s = las.make_casdm1s (ci=ci) + mo_cas = mo_coeff[:,las.ncore:][:,:las.ncas] + moH_cas = mo_cas.conjugate ().T + mo_core = mo_coeff[:,:las.ncore] + moH_core = mo_core.conjugate ().T + dm1s = [(mo_core @ moH_core) + (mo_cas @ d @ moH_cas) for d in list(casdm1s)] + if veff is not None: + fock = las.get_hcore()[None,:,:] + veff + return get_roothaan_fock (fock, dm1s, las._scf.get_ovlp ()) + dm1 = dm1s[0] + dm1s[1] + if isinstance (las, _DFLASCI): + vj, vk = las.with_df.get_jk(dm1, hermi=1) + else: + vj, vk = las._scf.get_jk(las.mol, dm1, hermi=1) + fock = las.get_hcore () + vj - (vk/2) + return fock + +def _eig_inactive_virtual (las, fock, orbsym=None): + '''Generate the unitary matrix canonicalizing the inactive and virtual orbitals only. + + Args: + las : object of :class:`LASCINoSymm` + fock : ndarray of shape (nmo,nmo) + Contains Fock matrix in MO basis + + Kwargs: + orbsym : list of length nmo + umat : ndarray of shape (nmo, nmo) + + Returns: + ene : ndarray of shape (nmo,) + umat : ndarray of shape (nmo, nmo)''' + nmo = fock.shape[0] + ncore = las.ncore + nocc = ncore + las.ncas + ene = np.zeros (nmo) + umat = np.eye (nmo) + if ncore: + orbsym_i = None if orbsym is None else orbsym[:ncore] + fock_i = fock[:ncore,:ncore] + ene[:ncore], umat[:ncore,:ncore] = las._eig (fock_i, 0, 0, orbsym_i) + if nmo-nocc: + orbsym_i = None if orbsym is None else orbsym[nocc:] + fock_i = fock[nocc:,nocc:] + ene[nocc:], umat[nocc:,nocc:] = las._eig (fock_i, 0, 0, orbsym_i) + return ene, umat + +def canonicalize (las, mo_coeff=None, ci=None, casdm1fs=None, natorb_casdm1=None, veff=None, + h2eff_sub=None, orbsym=None): + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + + # In-place safety + mo_coeff = mo_coeff.copy () + ci = copy.deepcopy (ci) + + # Temporary lroots safety + # The desired behavior is that the inactive and external orbitals should + # be canonicalized according to the density matrix used for orbital optimization + # TODO: once orbital optimization with lroots is enabled, change this behavior + # TODO: possibly move this logic to the make_casdm* functions + if casdm1fs is None: + ci_dm = [] + for i in range (len (ci)): + ci_i = [] + for j in range (len (ci[i])): + if ci[i][j].ndim>2: + ci_i.append (ci[i][j][0]) + else: + ci_i.append (ci[i][j]) + ci_dm.append (ci_i) + casdm1fs = las.make_casdm1s_sub (ci=ci_dm) + + nao, nmo = mo_coeff.shape + ncore = las.ncore + nocc = ncore + las.ncas + ncas_sub = las.ncas_sub + nelecas_sub = las.nelecas_sub + + # Passing casdm1 or lasdm1 only affects the canonicalization of the active orbitals + umat = np.zeros_like (mo_coeff) + casdm1s = np.stack ([linalg.block_diag (*[dm[0] for dm in casdm1fs]), + linalg.block_diag (*[dm[1] for dm in casdm1fs])], axis=0) + fock = mo_coeff.conjugate ().T @ las.get_fock (mo_coeff=mo_coeff, casdm1s=casdm1s, veff=veff) + fock = fock @ mo_coeff + if natorb_casdm1 is None: # State-average natural orbitals by default + natorb_casdm1 = casdm1s.sum (0) + + # Inactive-inactive and virtual-virtual + ene, umat = _eig_inactive_virtual (las, fock, orbsym=orbsym) + idx = np.arange (nmo, dtype=int) + if ncore: idx[:ncore] = idx[:ncore][np.argsort (ene[:ncore])] + if nmo-nocc: idx[nocc:] = idx[nocc:][np.argsort (ene[nocc:])] + umat = umat[:,idx] + if orbsym is not None: orbsym = orbsym[idx] + # Active-active + check_diag = natorb_casdm1.copy () + for ix, ncas in enumerate (ncas_sub): + i = sum (ncas_sub[:ix]) + j = i + ncas + check_diag[i:j,i:j] = 0.0 + is_block_diag = np.amax (np.abs (check_diag)) < 1e-8 + if is_block_diag: + # No off-diagonal RDM elements -> extra effort to prevent diagonalizer from breaking frags + for isub, (ncas, nelecas) in enumerate (zip (ncas_sub, nelecas_sub)): + i = sum (ncas_sub[:isub]) + j = i + ncas + dm1 = natorb_casdm1[i:j,i:j] + i += ncore + j += ncore + orbsym_i = None if orbsym is None else orbsym[i:j] + occ, umat[i:j,i:j] = las._eig (dm1, 0, 0, orbsym_i) + idx = np.argsort (occ)[::-1] + umat[i:j,i:j] = umat[i:j,i:j][:,idx] + if orbsym_i is not None: orbsym[i:j] = orbsym[i:j][idx] + if ci is not None: + fcibox = las.fciboxes[isub] + ci[isub] = fcibox.states_transform_ci_for_orbital_rotation ( + ci[isub], ncas, nelecas, umat[i:j,i:j]) + else: # You can't get proper LAS-type CI vectors w/out active space fragmentation + ci = None + orbsym_cas = None if orbsym is None else orbsym[ncore:nocc] + occ, umat[ncore:nocc,ncore:nocc] = las._eig (natorb_casdm1, 0, 0, orbsym_cas) + idx = np.argsort (occ)[::-1] + umat[ncore:nocc,ncore:nocc] = umat[ncore:nocc,ncore:nocc][:,idx] + if orbsym_cas is not None: orbsym[ncore:nocc] = orbsym[ncore:nocc][idx] + + # Final + mo_occ = np.zeros (nmo, dtype=natorb_casdm1.dtype) + if ncore: mo_occ[:ncore] = 2 + ucas = umat[ncore:nocc,ncore:nocc] + mo_occ[ncore:nocc] = ((natorb_casdm1 @ ucas) * ucas).sum (0) + mo_ene = ((fock @ umat) * umat.conjugate ()).sum (0) + mo_ene[ncore:][:sum (ncas_sub)] = 0.0 + mo_coeff = mo_coeff @ umat + if orbsym is not None: + ''' + print ("This is the second call to label_orb_symm inside of canonicalize") + orbsym = symm.label_orb_symm (las.mol, las.mol.irrep_id, + las.mol.symm_orb, mo_coeff, + s=las._scf.get_ovlp ()) + #mo_coeff = las.label_symmetry_(mo_coeff) + ''' + mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) + if h2eff_sub is not None: + h2eff_sub = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*las.ncas, -1)) + h2eff_sub = h2eff_sub.reshape (nmo, las.ncas, las.ncas, las.ncas) + h2eff_sub = np.tensordot (umat, h2eff_sub, axes=((0),(0))) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(1))).transpose (1,0,2,3) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(2))).transpose (1,2,0,3) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(3))).transpose (1,2,3,0) + h2eff_sub = h2eff_sub.reshape (nmo*las.ncas, las.ncas, las.ncas) + h2eff_sub = lib.numpy_helper.pack_tril (h2eff_sub).reshape (nmo, -1) + + # I/O + log = lib.logger.new_logger (las, las.verbose) + if las.verbose >= lib.logger.INFO: + if is_block_diag: + for isub, nlas in enumerate (ncas_sub): + log.info ("Fragment %d natural orbitals", isub) + i = ncore + sum (ncas_sub[:isub]) + j = i + nlas + log.info ('Natural occ %s', str (mo_occ[i:j])) + log.info ('Natural orbital (expansion on AOs) in CAS space') + label = las.mol.ao_labels() + mo_las = mo_coeff[:,i:j] + dump_mat.dump_rec(log.stdout, mo_las, label, start=1) + else: + log.info ("Delocalized natural orbitals do not reflect LAS fragmentation") + log.info ('Natural occ %s', str (mo_occ[ncore:nocc])) + log.info ('Natural orbital (expansion on AOs) in CAS space') + label = las.mol.ao_labels() + mo_las = mo_coeff[:,ncore:nocc] + dump_mat.dump_rec(log.stdout, mo_las, label, start=1) + + return mo_coeff, mo_ene, mo_occ, ci, h2eff_sub + +def get_init_guess_ci (las, mo_coeff=None, h2eff_sub=None, ci0=None): + # TODO: come up with a better algorithm? This might be working better than what I had before + # but it omits inter-active Coulomb and exchange interactions altogether. Is there a + # non-outer-product algorithm for finding the lowest-energy single product of CSFs? + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci0 is None: ci0 = [[None for i in range (las.nroots)] for j in range (las.nfrags)] + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + nmo = mo_coeff.shape[-1] + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + dm1_core= 2 * mo_coeff[:,:ncore] @ mo_coeff[:,:ncore].conj ().T + h1e_ao = las._scf.get_fock (dm=dm1_core) + eri_cas = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)) + eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) + eri_cas = eri_cas[ncore:nocc] + for ix, (fcibox, norb, nelecas) in enumerate (zip (las.fciboxes,las.ncas_sub,las.nelecas_sub)): + i = sum (las.ncas_sub[:ix]) + j = i + norb + mo = mo_coeff[:,ncore+i:ncore+j] + moH = mo.conj ().T + h1e = moH @ h1e_ao @ mo + h1e = [h1e, h1e] + eri = eri_cas[i:j,i:j,i:j,i:j] + for iy, solver in enumerate (fcibox.fcisolvers): + nelec = fcibox._get_nelec (solver, nelecas) + ndet = tuple ([cistring.num_strings (norb, n) for n in nelec]) + if isinstance (ci0[ix][iy], np.ndarray) and ci0[ix][iy].size==ndet[0]*ndet[1]: continue + if hasattr (mo_coeff, 'orbsym'): + solver.orbsym = mo_coeff.orbsym[ncore+i:ncore+j] + hdiag_csf = solver.make_hdiag_csf (h1e, eri, norb, nelec, max_memory=las.max_memory) + ci0[ix][iy] = solver.get_init_guess (norb, nelec, solver.nroots, hdiag_csf) + if solver.nroots==1: + ci0[ix][iy] = ci0[ix][iy][0] + else: + ci0[ix][iy] = np.stack (ci0[ix][iy], axis=0) + return ci0 + +def get_space_info (las): + ''' Retrieve the quantum numbers defining the states of a LASSCF calculation ''' + nfrags, nroots = las.nfrags, las.nroots + charges = np.zeros ((nroots, nfrags), dtype=np.int32) + wfnsyms, spins, smults = charges.copy (), charges.copy (), charges.copy () + for ifrag, fcibox in enumerate (las.fciboxes): + for iroot, solver in enumerate (fcibox.fcisolvers): + nelec = fcibox._get_nelec (solver, las.nelecas_sub[ifrag]) + charges[iroot,ifrag] = np.sum (las.nelecas_sub[ifrag]) - np.sum (nelec) + spins[iroot,ifrag] = nelec[0]-nelec[1] + smults[iroot,ifrag] = solver.smult + try: + wfnsyms[iroot,ifrag] = solver.wfnsym or 0 + except ValueError as e: + wfnsyms[iroot,ifrag] = symm.irrep_name2id (las.mol.groupname, solver.wfnsym) + return charges, spins, smults, wfnsyms + +def assert_no_duplicates (las, tab=None): + log = lib.logger.new_logger (las, las.verbose) + if tab is None: tab = np.stack (get_space_info (las), axis=-1) + tab_uniq, uniq_idx, uniq_inv, uniq_cnts = np.unique (tab, return_index=True, + return_inverse=True, return_counts=True, axis=0) + idx_dupe = uniq_cnts>1 + try: + err_str = ('LAS state basis has duplicates; details in logfile for ' + 'verbose >= INFO (4) [more details for verbose > INFO].\n' + '(Disable this assertion by passing assert_no_dupes=False ' + 'to the kernel, lasci, and state_average(_) functions.)') + assert (~np.any (idx_dupe)), err_str + except AssertionError as e: + dupe_idx = uniq_idx[idx_dupe] + dupe_cnts = uniq_cnts[idx_dupe] + for i, (ix, cnt, col) in enumerate (zip (uniq_idx, uniq_cnts, tab_uniq)): + if cnt==1: continue + log.info ('State %d appears %d times', ix, cnt) + idx_thisdupe = np.where (uniq_inv==i)[0] + row = col.T + log.debug ('As states {}'.format (idx_thisdupe)) + log.debug ('Charges = {}'.format (row[0])) + log.debug ('2M_S = {}'.format (row[1])) + log.debug ('2S+1 = {}'.format (row[2])) + log.debug ('Wfnsyms = {}'.format (row[3])) + raise e from None + +def state_average_(las, weights=[0.5,0.5], charges=None, spins=None, + smults=None, wfnsyms=None, assert_no_dupes=True): + ''' Transform LASCI/LASSCF object into state-average LASCI/LASSCF + + Args: + las: LASCI/LASSCF instance + + Kwargs: + weights: list of float; required + E_SA = sum_i weights[i] E[i] is used to optimize the orbitals + charges: 2d ndarray or nested list of integers + spins: 2d ndarray or nested list of integers + For the jth fragment in the ith state, + neleca = (sum(las.nelecas_sub[j]) - charges[i][j] + spins[i][j]) // 2 + nelecb = (sum(las.nelecas_sub[j]) - charges[i][j] - spins[i][j]) // 2 + Defaults to + charges[i][j] = 0 + spins[i][j] = las.nelecas_sub[j][0] - las.nelecas_sub[j][1] + smults: 2d ndarray or nested list of integers + For the jth fragment in the ith state, + smults[i][j] = (2*s)+1 + where "s" is the total spin quantum number, + S^2|j,i> = s*(s+1)|j,i> + Defaults to + smults[i][j] = abs (spins[i][j]) + 1 + wfnsyms: 2d ndarray or nested list of integers or strings + For the jth fragment of the ith state, + wfnsyms[i][j] + identifies the point-group irreducible representation + Defaults to all zeros (i.e., the totally-symmetric irrep) + + Returns: + las: LASCI/LASSCF instance + The first positional argument, modified in-place into a + state-averaged LASCI/LASSCF instance. + + ''' + old_states = np.stack (get_space_info (las), axis=-1) + nroots = len (weights) + nfrags = las.nfrags + if charges is None: charges = np.zeros ((nroots, nfrags), dtype=np.int32) + if wfnsyms is None: wfnsyms = np.zeros ((nroots, nfrags), dtype=np.int32) + if spins is None: spins = np.asarray ([[n[0]-n[1] for n in las.nelecas_sub] for i in weights]) + if smults is None: smults = np.abs (spins)+1 + + charges = np.asarray (charges) + wfnsyms = np.asarray (wfnsyms) + spins = np.asarray (spins) + smults = np.asarray (smults) + if np.issubsctype (wfnsyms.dtype, np.str_): + wfnsyms_str = wfnsyms + wfnsyms = np.zeros (wfnsyms_str.shape, dtype=np.int32) + for ix, wfnsym in enumerate (wfnsyms_str.flat): + try: + wfnsyms.flat[ix] = symm.irrep_name2id (las.mol.groupname, wfnsym) + except (TypeError, KeyError) as e: + wfnsyms.flat[ix] = int (wfnsym) + if nfrags == 1: + charges = np.atleast_2d (np.squeeze (charges)).T + wfnsyms = np.atleast_2d (np.squeeze (wfnsyms)).T + spins = np.atleast_2d (np.squeeze (spins)).T + smults = np.atleast_2d (np.squeeze (smults)).T + new_states = np.stack ([charges, spins, smults, wfnsyms], axis=-1) + if assert_no_dupes: assert_no_duplicates (las, tab=new_states) + + las.fciboxes = [get_h1e_zipped_fcisolver (state_average_n_mix ( + las, [csf_solver (las.mol, smult=s2p1).set (charge=c, spin=m2, wfnsym=ir) + for c, m2, s2p1, ir in zip (c_r, m2_r, s2p1_r, ir_r)], weights).fcisolver) + for c_r, m2_r, s2p1_r, ir_r in zip (charges.T, spins.T, smults.T, wfnsyms.T)] + las.e_states = np.zeros (nroots) + las.nroots = nroots + las.weights = weights + + if las.ci is not None: + log = lib.logger.new_logger(las, las.verbose) + log.debug (("lasci.state_average: Cached CI vectors may be present.\n" + "Looking for matches between old and new LAS states...")) + ci0 = [[None for i in range (nroots)] for j in range (nfrags)] + new_states = np.stack ([charges, spins, smults, wfnsyms], + axis=-1).reshape (nroots, nfrags*4) + old_states = old_states.reshape (-1, nfrags*4) + for iroot, row in enumerate (old_states): + idx = np.all (new_states == row[None,:], axis=1) + if np.count_nonzero (idx) == 1: + jroot = np.where (idx)[0][0] + log.debug ("Old state {} -> New state {}".format (iroot, jroot)) + for ifrag in range (nfrags): + ci0[ifrag][jroot] = las.ci[ifrag][iroot] + elif np.count_nonzero (idx) > 1: + raise RuntimeError ("Duplicate states specified?\n{}".format (idx)) + las.ci = ci0 + las.converged = False + return las + +@lib.with_doc(''' A version of lasci.state_average_ that creates a copy instead of modifying the + LASCI/LASSCF method instance in place. + + See lasci.state_average_ docstring below:\n\n''' + state_average_.__doc__) +def state_average (las, weights=[0.5,0.5], charges=None, spins=None, + smults=None, wfnsyms=None, assert_no_dupes=True): + is_scanner = isinstance (las, lib.SinglePointScanner) + if is_scanner: las = las.undo_scanner () + new_las = las.__class__(las._scf, las.ncas_sub, las.nelecas_sub) + new_las.__dict__.update (las.__dict__) + new_las.mo_coeff = las.mo_coeff.copy () + if getattr (las.mo_coeff, 'orbsym', None) is not None: + new_las.mo_coeff = lib.tag_array (new_las.mo_coeff, + orbsym=las.mo_coeff.orbsym) + new_las.ci = None + if las.ci is not None: + new_las.ci = [[c2.copy () if isinstance (c2, np.ndarray) else None + for c2 in c1] for c1 in las.ci] + las = state_average_(new_las, weights=weights, charges=charges, spins=spins, + smults=smults, wfnsyms=wfnsyms, assert_no_dupes=assert_no_dupes) + if is_scanner: las = las.as_scanner () + return las + +def get_single_state_las (las, state=0): + ''' Quickly extract a state-specific las calculation from a state-average one ''' + charges, spins, smults, wfnsyms = get_space_info (las) + charges = charges[state:state+1] + spins = spins[state:state+1] + smults = smults[state:state+1] + wfnsyms = wfnsyms[state:state+1] + weights = [1,] + return state_average (las, weights=weights, charges=charges, spins=spins, smults=smults, + wfnsyms=wfnsyms) + +def run_lasci (las, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=0, + assert_no_dupes=False, _dry_run=False): + '''Self-consistently optimize the CI vectors of a LAS state with + frozen orbitals using a fixed-point algorithm. "lasci_" (with the + trailing underscore) sets self.mo_coeff from the kwarg if it is passed; + "lasci" (without the trailing underscore) leaves self.mo_coeff unchanged. + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + MO coefficients; defaults to self.mo_coeff + ci0 : list (length nfrags) of list (length nroots) of ndarrays + Contains CI vectors for initial guess + lroots : ndarray of shape (nfrags,nroots) + Number of local roots in each fragment for each global state. + The corresponding local weights are set to [1,0,0,0,...]. + lweights : list of length nfrags of list of length nroots of sequence + Weights of local roots in each fragment for each global state. + Passing lweights is incompatible with passing lroots. Defaults + to, i.e., np.ones (las.nfrags, las.nroots, 1).tolist () + verbose : integer + See pyscf.lib.logger. + assert_no_dupes : logical + If True, checks state list for duplicate states + _dry_run : logical + If True, sets up the fcisolvers with the appropriate tags, but does + not run fcisolver kernels. + + Returns: + converged : list of length nroots of logical + Stores whether the calculation for each state successfully converged + e_tot : float + (State-averaged) total energy + e_states : list of length nroots + List of each state energy + e_cas : list of length nroots + List of the CAS space energy of each state + ci : list (length nfrags) of list (length nroots) of ndarrays + Contains optimized CI vectors + ''' + if assert_no_dupes: assert_no_duplicates (las) + if lroots is not None and lweights is not None: + raise RuntimeError ("lroots sets lweights: pass either or none but not both") + elif lweights is None: + if lroots is None: lroots = np.ones ((las.nfrags, las.nroots), dtype=int) + lweights = [] + for i in range (las.nfrags): + lwi = [] + for j in range (las.nroots): + lwij = np.zeros (lroots[i,j]) + lwij[0] = 1 + lwi.append (lwij) + lweights.append (lwi) + nao, nmo = mo_coeff.shape + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + ncas_sub = las.ncas_sub + nelecas_sub = las.nelecas_sub + orbsym = getattr (mo_coeff, 'orbsym', None) + if orbsym is not None: orbsym=orbsym[ncore:nocc] + elif isinstance (las, LASCISymm): + mo_coeff = las.label_symmetry_(mo_coeff) + orbsym = mo_coeff.orbsym[ncore:nocc] + log = lib.logger.new_logger (las, verbose) + + h1eff, energy_core = las.h1e_for_cas (mo_coeff=mo_coeff, + ncas=las.ncas, ncore=las.ncore) + h2eff = las.get_h2eff (mo_coeff) + if (ci0 is None or any ([c is None for c in ci0]) or + any ([any ([c2 is None for c2 in c1]) for c1 in ci0])): + ci0 = las.get_init_guess_ci (mo_coeff, h2eff, ci0) + eri_cas = lib.numpy_helper.unpack_tril ( + h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, + ncas, ncas)[ncore:nocc] + + e_cas = np.empty (las.nroots) + e_states = np.empty (las.nroots) + ci1 = [[None for c2 in c1] for c1 in ci0] + converged = [] + t = (lib.logger.process_clock(), lib.logger.perf_counter()) + e_lexc = [[None for i in range (las.nroots)] for j in range (las.nfrags)] + for state in range (las.nroots): + fcisolvers = [b.fcisolvers[state] for b in las.fciboxes] + ci0_i = [c[state] for c in ci0] + solver = ImpureProductStateFCISolver (fcisolvers, stdout=las.stdout, + lweights=[l[state] for l in lweights], verbose=verbose) + # TODO: better handling of CSF symmetry quantum numbers in general + for ix, s in enumerate (solver.fcisolvers): + i = sum (ncas_sub[:ix]) + j = i + ncas_sub[ix] + if orbsym is not None: s.orbsym = orbsym[i:j] + s.norb = ncas_sub[ix] + s.nelec = solver._get_nelec (s, nelecas_sub[ix]) + s.check_transformer_cache () + if _dry_run: continue + conv, e_i, ci_i = solver.kernel (h1eff, eri_cas, ncas_sub, nelecas_sub, + ecore=0, ci0=ci0_i, orbsym=orbsym, conv_tol_grad=las.conv_tol_grad, + conv_tol_self=las.conv_tol_self, max_cycle_macro=las.max_cycle_macro) + e_cas[state] = e_i + e_states[state] = e_i + energy_core + for frag, s in enumerate (solver.fcisolvers): + e_loc = np.atleast_1d (getattr (s, 'e_states', e_i)) + e_lexc[frag][state] = e_loc - e_i + for c1, c2, s, no, ne in zip (ci1, ci_i, solver.fcisolvers, ncas_sub, nelecas_sub): + ne = solver._get_nelec (s, ne) + ndeta, ndetb = [cistring.num_strings (no, n) for n in ne] + shape = [s.nroots, ndeta, ndetb] if s.nroots>1 else [ndeta, ndetb] + c1[state] = np.asarray (c2).reshape (*shape) + if not conv: log.warn ('State %d LASCI not converged!', state) + converged.append (conv) + t = log.timer ('State {} LASCI'.format (state), *t) + + e_tot = np.dot (las.weights, e_states) + return converged, e_tot, e_states, e_cas, e_lexc, ci1 + +class LASCINoSymm (casci.CASCI): + + def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, frozen=None, **kwargs): + if isinstance(ncas,int): + ncas = [ncas] + ncas_tot = sum (ncas) + nel_tot = [0, 0] + new_nelecas = [] + for ix, nel in enumerate (nelecas): + if isinstance (nel, (int, np.integer)): + nb = nel // 2 + na = nb + (nel % 2) + else: + na, nb = nel + new_nelecas.append ((na, nb)) + nel_tot[0] += na + nel_tot[1] += nb + nelecas = new_nelecas + self.nroots = 1 + super().__init__(mf, ncas=ncas_tot, nelecas=nel_tot, ncore=ncore) + self.chkfile = self._scf.chkfile + if spin_sub is None: spin_sub = [1 + abs(ne[0]-ne[1]) for ne in nelecas] + self.ncas_sub = np.asarray (ncas) + self.nelecas_sub = np.asarray (nelecas) + assert (len (self.nelecas_sub) == self.nfrags) + self.frozen = frozen + self.conv_tol_grad = 1e-4 + self.conv_tol_self = 1e-10 + self.ah_level_shift = 1e-8 + self.max_cycle_macro = 50 + self.max_cycle_micro = 5 + keys = set(('e_states', 'fciboxes', 'nroots', 'weights', 'ncas_sub', 'nelecas_sub', + 'conv_tol_grad', 'conv_tol_self', 'max_cycle_macro', 'max_cycle_micro', + 'ah_level_shift', 'states_converged', 'chkfile', 'e_lexc')) + self._keys = set(self.__dict__.keys()).union(keys) + self.fciboxes = [] + if isinstance(spin_sub,int): + self.fciboxes.append(self._init_fcibox(spin_sub,self.nelecas_sub[0])) + else: + assert (len (spin_sub) == self.nfrags) + for smult, nel in zip (spin_sub, self.nelecas_sub): + self.fciboxes.append (self._init_fcibox (smult, nel)) + self.weights = [1.0] + self.e_states = [0.0] + self.e_lexc = [[np.array ([0]),],] + + def _init_fcibox (self, smult, nel): + s = csf_solver (self.mol, smult=smult) + s.spin = nel[0] - nel[1] + return get_h1e_zipped_fcisolver (state_average_n_mix (self, [s], [1.0]).fcisolver) + + @property + def nfrags (self): return len (self.ncas_sub) + + def get_mo_slice (self, idx, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + mo = mo_coeff[:,self.ncore:] + for offs in self.ncas_sub[:idx]: + mo = mo[:,offs:] + mo = mo[:,:self.ncas_sub[idx]] + return mo + + def ao2mo (self, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + nao, nmo = mo_coeff.shape + ncore, ncas = self.ncore, self.ncas + nocc = ncore + ncas + mo_cas = mo_coeff[:,ncore:nocc] + mo = [mo_coeff, mo_cas, mo_cas, mo_cas] + if getattr (self, 'with_df', None) is not None: + # Store intermediate with one contracted ao index for faster calculation of exchange! + bPmn = sparsedf_array (self.with_df._cderi) + bmuP = bPmn.contract1 (mo_cas) + buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) + eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) + eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) + eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) + eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) + if self.verbose > lib.logger.DEBUG: + eri_comp = self.with_df.ao2mo (mo, compact=True) + lib.logger.debug(self,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) + elif getattr (self._scf, '_eri', None) is not None: + eri = ao2mo.incore.general (self._scf._eri, mo, compact=True) + else: + eri = ao2mo.outcore.general_iofree (self.mol, mo, compact=True) + if eri.shape != (nmo,ncas*ncas*(ncas+1)//2): + try: + eri = eri.reshape (nmo, ncas*ncas*(ncas+1)//2) + except ValueError as e: + assert (nmo == ncas), str (e) + eri = ao2mo.restore ('2kl', eri, nmo).reshape (nmo, ncas*ncas*(ncas+1)//2) + return eri + + def get_h2eff_slice (self, h2eff, idx, compact=None): + ncas_cum = np.cumsum ([0] + self.ncas_sub.tolist ()) + i = ncas_cum[idx] + j = ncas_cum[idx+1] + ncore = self.ncore + nocc = ncore + self.ncas + eri = h2eff[ncore:nocc,:].reshape (self.ncas*self.ncas, -1) + ix_i, ix_j = np.tril_indices (self.ncas) + eri = eri[(ix_i*self.ncas)+ix_j,:] + eri = ao2mo.restore (1, eri, self.ncas)[i:j,i:j,i:j,i:j] + if compact: eri = ao2mo.restore (compact, eri, j-i) + return eri + + get_h1eff = get_h1las = h1e_for_las = h1e_for_las + get_h2eff = ao2mo + ''' + def get_h2eff (self, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + if isinstance (self, _DFLASCI): + mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] + return self.with_df.ao2mo (mo_cas) + return self.ao2mo (mo_coeff) + ''' + + get_fock = get_fock + get_grad = get_grad + get_grad_orb = get_grad_orb + get_grad_ci = get_grad_ci + _hop = lasci_sync.LASCI_HessianOperator + _kern = lasci_sync.kernel + def get_hop (self, mo_coeff=None, ci=None, ugg=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ugg is None: ugg = self.get_ugg () + return self._hop (self, ugg, mo_coeff=mo_coeff, ci=ci, **kwargs) + canonicalize = canonicalize + + def _finalize(self): + log = lib.logger.new_logger (self, self.verbose) + nroots_prt = len (self.e_states) + if self.verbose <= lib.logger.INFO: + nroots_prt = min (nroots_prt, 100) + if nroots_prt < len (self.e_states): + log.info (("Printing a maximum of 100 state energies;" + " increase self.verbose to see them all")) + if nroots_prt > 1: + log.info ("LASCI state-average energy = %.15g", self.e_tot) + for i, e in enumerate (self.e_states[:nroots_prt]): + log.info ("LASCI state %d energy = %.15g", i, e) + else: + log.info ("LASCI energy = %.15g", self.e_tot) + return + + + def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, conv_tol_grad=None, + assert_no_dupes=False, verbose=None, _kern=None): + if mo_coeff is None: + mo_coeff = self.mo_coeff + else: + self.mo_coeff = mo_coeff + if ci0 is None: ci0 = self.ci + if verbose is None: verbose = self.verbose + if conv_tol_grad is None: conv_tol_grad = self.conv_tol_grad + if _kern is None: _kern = self._kern + log = lib.logger.new_logger(self, verbose) + + if self.verbose >= lib.logger.WARN: + self.check_sanity() + self.dump_flags(log) + + for fcibox in self.fciboxes: + fcibox.verbose = self.verbose + fcibox.stdout = self.stdout + fcibox.nroots = self.nroots + fcibox.weights = self.weights + # TODO: local excitations and locally-impure states in LASSCF kernel + do_warn=False + if ci0 is not None: + for i, ci0_i in enumerate (ci0): + if ci0_i is None: continue + for j, ci0_ij in enumerate (ci0_i): + if ci0_ij is None: continue + if np.asarray (ci0_ij).ndim>2: + do_warn=True + ci0_i[j] = ci0_ij[0] + if do_warn: log.warn ("Discarding all but the first root of guess CI vectors!") + + self.converged, self.e_tot, self.e_states, self.mo_energy, self.mo_coeff, self.e_cas, \ + self.ci, h2eff_sub, veff = _kern(mo_coeff=mo_coeff, ci0=ci0, verbose=verbose, \ + casdm0_fr=casdm0_fr, conv_tol_grad=conv_tol_grad, assert_no_dupes=assert_no_dupes) + + self._finalize () + return self.e_tot, self.e_cas, self.ci, self.mo_coeff, self.mo_energy, h2eff_sub, veff + + def states_make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: + return [np.zeros ((self.nroots,2,ncas,ncas)) for ncas in ncas_sub] + casdm1s = [] + for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + if ci_i is None: + dm1a = dm1b = np.zeros ((ncas, ncas)) + else: + dm1a, dm1b = fcibox.states_make_rdm1s (ci_i, ncas, nelecas) + casdm1s.append (np.stack ([dm1a, dm1b], axis=1)) + return casdm1s + + def make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, w=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + if w is None: w = self.weights + return [np.einsum ('rspq,r->spq', dm1, w) for dm1 in casdm1frs] + + def states_make_casdm1s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + return np.stack ([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] + for dm1rs in casdm1frs]) + for ispin in (0, 1)], axis=0) + for iroot in range (self.nroots)], axis=0) + + def states_make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + casdm2 = [] + for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) + return casdm2 + + def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=None, **kwargs): + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, **kwargs) + return [np.einsum ('rijkl,r->ijkl', dm2, box.weights) + for dm2, box in zip (casdm2fr, self.fciboxes)] + + #SV casdm2s + + #def make_casdm2s(self, ci=None, **kwargs): + # # Make the full-dimensional casdm2s spanning the collective active space + # casdm2s_sub = self.make_casdm2s_sub (ci=ci, **kwargs) + # casdm2a = linalg.block_diag (*[dm[0] for dm in casdm2s_sub]) + # casdm2b = linalg.block_diag (*[dm[1] for dm in casdm2s_sub]) + # return np.stack ([casdm2a, casdm2b], axis=0) + + + #SV casdm2s_sub + def make_casdm2s_sub(self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2frs=None, w=None, **kwargs): + if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + if w is None: w = self.weights + print("SV w = ", w) + return [np.einsum ('rsijkl,r->sijkl', dm2, w) for dm2 in casdm2frs] + + #SV states_make_casdm2s_sub + #There are two ways to construct states_make_casdm2s_sub, one is thisi coming from states_make_casdm1s_sub + + def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + # Spin-separated 2-RDMs in the MO basis for each subspace in sequence + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: + return [np.zeros ((self.nroots,2,ncas,ncas,ncas,ncas)) for ncas in ncas_sub] + casdm2s = [] + for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + if ci_i is None: + dm2aa = dm2ab, dm2bb = np.zeros ((ncas, ncas,ncas,ncas)) + else: + dm2aa, dm2ab, dm2bb = fcibox.states_make_rdm12s (ci_i, ncas, nelecas)[-1] # IDK what this third ncas*4 matrix is, so just created a placeholder for it + casdm2s.append (np.stack ([dm2aa, dm2ab, dm2bb], axis=1)) + return casdm2s + + #SV states_make_casdm2s_sub + #This is the second coming from states_make_casdm2_sub. I think the above one must be the one to be used but the problem is IDK whether states_make_rdm2s exists or not, maybe states_make_rdm12s exists + ''' + def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + #Spin-separated 1-RDMs in the MO basis for each subspace in sequence + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + casdm2s = [] + for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) + return casdm2 + ''' + + #SV states_make_casdm2s + def states_make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, casdm2fr=None, casdm2frs=None, **kwargs): + ''' Make the full-dimensional casdm2s spanning the collective active space ''' + print ("SV entering states_make_casdm2s") + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + print ("SV shape of casdm2fr = ", casdm2fr) + if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + print ("SV casdm2frs =", casdm2frs, "and ", casdm2frs[0] ) # casdm2frs[i] -- [0]=faa, [1]=fab, [2]=fbb for fragment i + + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + + casdm2rs = np.zeros ((self.nroots,3,ncas,ncas,ncas,ncas)) + for isub, dm2 in enumerate (casdm2frs): + print ("SV dm2 = ", dm2, dm2.shape, isub, dm2[:,1]) # dm2 = root,spin,ncas,ncas,ncas,ncas | dm2[:,x] = x spin part of ncas,ncas,ncas,ncas + i = ncas_cum[isub] + j = ncas_cum[isub+1] + for spin in [0,1,2]:#0=aa, 1=ab, 2=bb + casdm2rs[:, spin, i:j, i:j, i:j, i:j] = dm2[:,spin,:,:,:,:] + + for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + for spin in [0,1,2]: + for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2rs[:,spin]): + dma1, dmb1 = dm1s1[0], dm1s1[1] + dma2, dmb2 = dm1s2[0], dm1s2[1] + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) + +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + + return casdm2rs + + #SV make_casdm3_sub + def make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3fr=None, **kwargs): + if casdm3fr is None: casdm3fr = self.states_make_casdm3_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, **kwargs) + for dm3, box in zip(casdm3fr, self.fciboxes): + print ("SV dm3, box = ", dm3, box.weights) + casdm3_sub = np.einsum('rijklmn,r->ijklmn', dm3, box.weights) + #casdm3_sub = np.einsum('rijklmn,r->ijklpq', dm3, box.weights) + return casdm3_sub + + + #SV states_make_casdm3_sub + def states_make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + ''' Spin-separated 3-RDMs in the MO basis for each subspace in sequence, currently this does not have weights so it's not really a STATES ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + casdm3 = [] + for ci_i, ncas, nel in zip (ci, ncas_sub, nelecas_sub): + casdm3.append (fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel)[-1]) + #print ("SV casdm3_sub = ", casdm3_sub + return casdm3 + + def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, + nelecas_sub=None, casdm1rs=None, casdm1frs=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1rs is None: casdm1rs = self.states_make_casdm1s (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, + **kwargs) + mo_core = mo_coeff[:,:self.ncore] + mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] + dm1rs = np.tensordot (mo_cas.conj (), np.dot (casdm1rs, mo_cas.conj ().T), axes=((1),(2))) + dm1rs = dm1rs.transpose (1,2,0,3) + dm1rs += (mo_core @ mo_core.conj ().T)[None,None,:,:] + return dm1rs + + def make_rdm1s_sub (self, mo_coeff=None, ci=None, ncas_sub=None, + nelecas_sub=None, include_core=False, casdm1s_sub=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ''' Same as make_casdm1s_sub, but in the ao basis ''' + rdm1s = [] + for idx, casdm1s in enumerate (casdm1s_sub): + mo = self.get_mo_slice (idx, mo_coeff=mo_coeff) + moH = mo.conjugate ().T + rdm1s.append (np.tensordot (mo, np.dot (casdm1s,moH), axes=((1),(1))).transpose(1,0,2)) + if include_core and self.ncore: + mo_core = mo_coeff[:,:self.ncore] + moH_core = mo_core.conjugate ().T + dm_core = mo_core @ moH_core + rdm1s = [np.stack ([dm_core, dm_core], axis=0)] + rdm1s + rdm1s = np.stack (rdm1s, axis=0) + return rdm1s + + def make_rdm1_sub (self, **kwargs): + return self.make_rdm1s_sub (**kwargs).sum (1) + + def make_rdm1s (self, mo_coeff=None, ncore=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + mo = mo_coeff[:,:ncore] + moH = mo.conjugate ().T + dm_core = mo @ moH + dm_cas = self.make_rdm1s_sub (mo_coeff=mo_coeff, **kwargs).sum (0) + return dm_core[None,:,:] + dm_cas + + def make_rdm1 (self, mo_coeff=None, ci=None, **kwargs): + return self.make_rdm1s (mo_coeff=mo_coeff, ci=ci, **kwargs).sum (0) + + def make_casdm1s (self, ci=None, **kwargs): + ''' Make the full-dimensional casdm1s spanning the collective active space ''' + casdm1s_sub = self.make_casdm1s_sub (ci=ci, **kwargs) + casdm1a = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) + casdm1b = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) + return np.stack ([casdm1a, casdm1b], axis=0) + + def make_casdm1 (self, ci=None, **kwargs): + ''' Spin-sum make_casdm1s ''' + return self.make_casdm1s (ci=ci, **kwargs).sum (0) + + def states_make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' Make the full-dimensional casdm2 spanning the collective active space ''' + raise DeprecationWarning ( + ("states_make_casdm2 is BANNED. There is no reason to EVER make an array this huge.\n" + "Use states_make_casdm*_sub instead, and substitute the factorization into your " + "expressions.") + ) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + casdm2r = np.zeros ((self.nroots,ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2 in enumerate (casdm2fr): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2r[:, i:j, i:j, i:j, i:j] = dm2 + # Off-diagonal + for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2r): + dma1, dmb1 = dm1s1[0], dm1s1[1] + dma2, dmb2 = dm1s2[0], dm1s2[1] + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) + +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2r + + def state_make_casdm1s(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + casdm1s = np.stack([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] + for dm1rs in casdm1frs]) + for ispin in (0, 1)], axis=0) + for iroot in range (self.nroots)], axis=0) + return casdm1s[state] + + def state_make_casdm2(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' State wise casdm2 spanning the collective active space. ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2_r in enumerate (casdm2fr): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2[i:j, i:j, i:j, i:j] = dm2_r[state] + # Off-diagonal + for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1, dmb1 = dm1s1_r[state][0], dm1s1_r[state][1] + dma2, dmb2 = dm1s2_r[state][0], dm1s2_r[state][1] + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) + +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2 + + #SV make_casdm2s + def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2rs=None, casdm2fs=None, casdm1frs=None, casdm2frs=None, + **kwargs): + + if casdm2rs is not None: + return np.einsum ('rsijkl,r->sijkl', casdm2rs, self.weights) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2fs is None: casdm2fs = self.make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2frs=casdm2frs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + weights = self.weights + casdm2s = np.zeros ((3,ncas,ncas,ncas,ncas)) + + # Diagonal of aa,ab,bb + for isub, dm2 in enumerate (casdm2fs): + print ("SV dm2 = ", dm2[1], dm2.shape) # dm2 = 3,ncas_sub*4 + i = ncas_cum[isub] + j = ncas_cum[isub+1] + for spin in [0,1,2]: + casdm2s[spin][i:j, i:j, i:j, i:j] = dm2[spin] + + # Off-diagonal + for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] + dm1r = dma1r + dmb1r + dm2r = dma2r + dmb2r + for spin in [0,1,2]: + # Coulomb slice + casdm2s[spin][i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) + casdm2s[spin][k:l, k:l, i:j, i:j] = casdm2s[spin][i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) + print ("SV d2exc = ", d2exc, d2exc.shape) + casdm2s[spin][i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) + casdm2s[spin][k:l, i:j, i:j, k:l] = casdm2s[spin][i:j, k:l, k:l, i:j].transpose (1,0,3,2) + + print ("SV casdm2s = ", casdm2s) + return casdm2s + + + def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, + **kwargs): + ''' Make the full-dimensional casdm2 spanning the collective active space ''' + if casdm2r is not None: + return np.einsum ('rijkl,r->ijkl', casdm2r, self.weights) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + weights = self.weights + casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2 in enumerate (casdm2f): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2[i:j, i:j, i:j, i:j] = dm2 + # Off-diagonal + for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] + dm1r = dma1r + dmb1r + dm2r = dma2r + dmb2r + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) + casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) # IDU my eqn says [2,3,0,1] --> it's same! + return casdm2 + + #SV make_casdm3 + def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm3r=None, casdm3f=None, casdm2frs=None, casdm3fr=None, casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, + **kwargs): + ''' Make the full-dimensional casdm3 spanning the collective active space ''' + if casdm3r is not None: + return np.einsum ('rijklmn,r->ijklmn', casdm3r, self.weights) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) + #if casdm3f is None: casdm3f = self.make_casdm3_sub (ci=ci, + # ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm3fr=casdm3fr) + if casdm3f is None: casdm3f = self.states_make_casdm3_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + print ("SV ncas_cum = ", ncas_cum) + weights = self.weights + casdm3 = np.zeros ((ncas,ncas,ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm3 in enumerate (casdm3f): + print ("SV dm3 = ", dm3, isub, dm3.shape) + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 + print ("SV casdm3 = ", casdm3) + # Off-diagonal + for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + m = ncas_cum[isub3] + n = ncas_cum[isub3+1] + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] # size of 2-RDMs + dma3r, dmb3r = dm1rs3[:,0], dm1rs3[:,1] + dm1r = dma1r + dmb1r + dm2r = dma2r + dmb2r + #print ("SV dm1r, dm2r = ", dm1rs1, dma2r) + print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) + + # Term 1- 61a + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) + casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) + casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,0,1,2,3) + casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,2,3,0,1) + casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) + casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) + + # Term 2- 61b + d3sigma = (lib.einsum('r,rkl,rmn->rknml',weights,dma2r,dma3r)+lib.einsum('r,rkl,rmn->rknml',weights,dmb2r,dmb3r)) + casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= lib.einsum('ij,klmn->ijklmn', dm1r, d3sigma) + casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) + casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) + casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) + casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,2,3,0,1) + casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,0,1,2,3) + + # Term 3- 61c + d3sigma = (lib.einsum('r,rij,rkl->rilkj',weights,dma1r,dma2r)+lib.einsum('r,rij,rkl->rilkj',weights,dmb1r,dmb2r)) + casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= lib.einsum('ijkl,mn->ijklmn', d3sigma, dm3r) + casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) + casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) + casdm3[k:l, i:j, m:n, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,4,5,0,1) + casdm3[m:n, m:n, k:l, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,2,3,0,1) + casdm3[m:n, m:n, i:j, k:l, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,0,1,2,3) + + #Terms 4- 61d + d3sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) + casdm3[i:j, k:l, k:l, m:n, m:n, i:j] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(0,1,4,5,2,3) + casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,0,1,2,3) + casdm3[m:n, i:j, k:l, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,2,3,0,1) + casdm3[k:l, m:n, m:n, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,4,5,0,1) + casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,0,1,4,5) + + #Terms 5- 61e + d3sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) + casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) + casdm3[m:n, i:j, i:j, m:n, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,0,1,2,3) + casdm3[m:n, i:j, k:l, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,2,3,0,1) + casdm3[k:l, k:l, m:n, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,4,5,0,1) + casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) + + #Terms 6- 61f + d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) + casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) + print ("SV d3sigma = ", d3sigma.shape) + casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) + + #Last 3 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 + + for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub1] + l = ncas_cum[isub1+1] + m = ncas_cum[isub2] + n = ncas_cum[isub2+1] + + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dm1r = dma1r + dmb1r + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] + dm2r = dma2r + dmb2r + + # Terms 7- 61g + d3sigma = (lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm1r,dm2r)-(lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dma2r))+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dmb2r)) + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) + casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) + casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) + casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) + casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) + + # Terms 8- 61h + d3sigma = (-lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma1r,dma2r)-lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijknml',weights,dma1r,dma1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijknml',weights,dmb1r,dmb1r,dmb2r)) + casdm3[i:j, i:j, k:l, m:n, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) + + # Terms 9- 61i + + for (isub1, dm1rs1), (isub2, dm2rs2) in product(enumerate(casdm1frs), enumerate(casdm2frs)): + # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same fragment but they must come from different frags! + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub1] + l = ncas_cum[isub1+1] + m = ncas_cum[isub2] + n = ncas_cum[isub2+1] + + if isub1 != isub2: + + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs + #print ("SV dm2rs2 = ", dm2rs2, dm2rs2.shape, dm2rs2[:,1]) + dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] # size of 2-RDMs + dm1r = dma1r + dmb1r + print ("SV dm1r = ", dm1r.shape) + d3sigma = (-lib.einsum('r,rmn,rkjil->rinkjml',weights,dma1r,dmaa2r)-lib.einsum('r,rmn,rkjil->rinkjml',weights,dma1r,dmab2r)-lib.einsum('r,rmn,rkjil->rinkjml',weights,dmb1r,dmab2r)-lib.einsum('r,rmn,rkjil->rinkjml',weights,dmb1r,dmbb2r)) + print ("SV d3sigma = ", d3sigma.shape) + casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) + casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) + + return casdm3 + + + def get_veff (self, mol=None, dm1s=None, hermi=1, spin_sep=False, **kwargs): + ''' Returns a spin-summed veff! If dm1s isn't provided, builds from self.mo_coeff, self.ci + etc. ''' + if mol is None: mol = self.mol + nao = mol.nao_nr () + if dm1s is None: dm1s = self.make_rdm1 (include_core=True, **kwargs).reshape (nao, nao) + dm1s = np.asarray (dm1s) + if dm1s.ndim == 2: dm1s = dm1s[None,:,:] + if isinstance (self, _DFLASCI): + vj, vk = self.with_df.get_jk(dm1s, hermi=hermi) + else: + vj, vk = self._scf.get_jk(mol, dm1s, hermi=hermi) + if spin_sep: + assert (dm1s.shape[0] == 2) + return vj.sum (0)[None,:,:] - vk + else: + veff = np.stack ([j - k/2 for j, k in zip (vj, vk)], axis=0) + return np.squeeze (veff) + + def split_veff (self, veff, h2eff_sub, mo_coeff=None, ci=None, casdm1s_sub=None): + ''' Split a spin-summed veff into alpha and beta terms using the h2eff eri array. + Note that this will omit v(up_active - down_active)^virtual_inactive by necessity; + this won't affect anything because the inactive density matrix has no spin component. + On the other hand, it ~is~ necessary to correctly do + + v(up_active - down_active)^unactive_active + + in order to calculate the external orbital gradient at the end of the calculation. + This means that I need h2eff_sub spanning both at least two active subspaces + ~and~ the full orbital range. ''' + veff_c = veff.copy () + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci = ci) + ncore = self.ncore + ncas = self.ncas + nocc = ncore + ncas + nao, nmo = mo_coeff.shape + moH_coeff = mo_coeff.conjugate ().T + smo_coeff = self._scf.get_ovlp () @ mo_coeff + smoH_coeff = smo_coeff.conjugate ().T + veff_s = np.zeros_like (veff_c) + for ix, (ncas_i, casdm1s) in enumerate (zip (self.ncas_sub, casdm1s_sub)): + i = sum (self.ncas_sub[:ix]) + j = i + ncas_i + eri_k = h2eff_sub.reshape (nmo, ncas, -1)[:,i:j,...].reshape (nmo*ncas_i, -1) + eri_k = lib.numpy_helper.unpack_tril (eri_k)[:,i:j,:] + eri_k = eri_k.reshape (nmo, ncas_i, ncas_i, ncas) + sdm = casdm1s[0] - casdm1s[1] + vk_pa = -np.tensordot (eri_k, sdm, axes=((1,2),(0,1))) / 2 + veff_s[:,ncore:nocc] += vk_pa + veff_s[ncore:nocc,:] += vk_pa.T + veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:] / 2 + veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:].T / 2 + veff_s = smo_coeff @ veff_s @ smoH_coeff + veffa = veff_c + veff_s + veffb = veff_c - veff_s + return np.stack ([veffa, veffb], axis=0) + + + def states_energy_elec (self, mo_coeff=None, ncore=None, ncas=None, + ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, + casdm1frs=None, casdm2fr=None, veff_core=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + if ncas is None: ncas = self.ncas + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: ci = self.ci + if h2eff is None: h2eff = self.get_h2eff (mo_coeff) + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub) + nao, nmo = mo_coeff.shape + nocc = ncore + ncas + mo_core = mo_coeff[:,:ncore] + mo_cas = mo_coeff[:,ncore:nocc] + dm_core = 2*mo_core @ mo_core.conj ().T + if veff_core is None: veff_core = getattr (veff, 'c', None) + if veff_core is None: veff_core = self.get_veff (dm1s=dm_core) + h1eff = self.get_hcore () + veff_core + e0 = 2*np.dot (((h1eff-(veff_core/2)) @ mo_core).ravel (), mo_core.conj().ravel ()) + h1eff = mo_cas.conj ().T @ h1eff @ mo_cas + eri_cas = lib.numpy_helper.unpack_tril (h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)) + eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) + eri_cas = eri_cas[ncore:nocc] + casdm1rs = self.states_make_casdm1s (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, + casdm1frs=casdm1frs) + vj_r = np.tensordot (casdm1rs.sum (1), eri_cas, axes=2) + vk_rs = np.tensordot (casdm1rs, eri_cas, axes=((2,3),(2,1))) + veff_rs = vj_r[:,None,:,:] - vk_rs + + energy_elec = [] + for idx, (dm1s, v) in enumerate (zip (casdm1rs, veff_rs)): + casdm1fs = [dm[idx] for dm in casdm1frs] + casdm2f = [dm[idx] for dm in casdm2fr] + + # 1-body veff terms + h1e = h1eff[None,:,:] + v/2 + e1 = np.dot (h1e.ravel (), dm1s.ravel ()) + + # 2-body cumulant terms + e2 = 0 + for isub, (dm1s, dm2) in enumerate (zip (casdm1fs, casdm2f)): + dm1a, dm1b = dm1s[0], dm1s[1] + dm1 = dm1a + dm1b + cdm2 = dm2 - np.multiply.outer (dm1, dm1) + cdm2 += np.multiply.outer (dm1a, dm1a).transpose (0,3,2,1) + cdm2 += np.multiply.outer (dm1b, dm1b).transpose (0,3,2,1) + eri = self.get_h2eff_slice (h2eff, isub) + te2 = np.tensordot (eri, cdm2, axes=4) / 2 + e2 += te2 + energy_elec.append (e0 + e1 + e2) + self._e1_ref = e0 + e1 + self._e2_ref = e2 + + return energy_elec + + def energy_elec (self, mo_coeff=None, ncore=None, ncas=None, + ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' Since the LASCI energy cannot be calculated as simply as ecas + ecore, I need this ''' + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + if ncas is None: ncas = self.ncas + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: ci = self.ci + if h2eff is None: h2eff = self.get_h2eff (mo_coeff) + casdm1s_sub = self.make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, + casdm1frs=casdm1frs) + if veff is None: + veff = self.get_veff (dm1s = self.make_rdm1(mo_coeff=mo_coeff,casdm1s_sub=casdm1s_sub)) + veff = self.split_veff (veff, h2eff, mo_coeff=mo_coeff, casdm1s_sub=casdm1s_sub) + + # 1-body veff terms + h1e = self.get_hcore ()[None,:,:] + veff/2 + dm1s = self.make_rdm1s (mo_coeff=mo_coeff, ncore=ncore, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1s_sub=casdm1s_sub) + e1 = np.dot (h1e.ravel (), dm1s.ravel ()) + + # 2-body cumulant terms + casdm1s = self.make_casdm1s (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1frs=casdm1frs) + casdm1 = casdm1s.sum (0) + casdm2 = self.make_casdm2 (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, casdm2fr=casdm2fr) + casdm2 -= np.multiply.outer (casdm1, casdm1) + casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) + casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) + ncore, ncas, nocc = self.ncore, self.ncas, self.ncore + self.ncas + eri = lib.numpy_helper.unpack_tril (h2eff[ncore:nocc].reshape (ncas*ncas, -1)) + eri = eri.reshape ([ncas,]*4) + e2 = np.tensordot (eri, casdm2, axes=4)/2 + + e0 = self.energy_nuc () + self._e1_test = e1 + self._e2_test = e2 + return e1 + e2 + + _ugg = lasci_sync.LASCI_UnitaryGroupGenerators + def get_ugg (self, mo_coeff=None, ci=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + return self._ugg (self, mo_coeff, ci) + + def cderi_ao2mo (self, mo_i, mo_j, compact=False): + assert (isinstance (self, _DFLASCI)) + nmo_i, nmo_j = mo_i.shape[-1], mo_j.shape[-1] + if compact: + assert (nmo_i == nmo_j) + bPij = np.empty ((self.with_df.get_naoaux (), nmo_i*(nmo_i+1)//2), dtype=mo_i.dtype) + else: + bPij = np.empty ((self.with_df.get_naoaux (), nmo_i, nmo_j), dtype=mo_i.dtype) + ijmosym, mij_pair, moij, ijslice = ao2mo.incore._conc_mos (mo_i, mo_j, compact=compact) + b0 = 0 + for eri1 in self.with_df.loop (): + b1 = b0 + eri1.shape[0] + eri2 = bPij[b0:b1] + eri2 = ao2mo._ao2mo.nr_e2 (eri1, moij, ijslice, aosym='s2', mosym=ijmosym, out=eri2) + b0 = b1 + return bPij + + def fast_veffa (self, casdm1s_sub, h2eff_sub, mo_coeff=None, ci=None, _full=False): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + assert (isinstance (self, _DFLASCI) or _full) + ncore = self.ncore + ncas_sub = self.ncas_sub + ncas = sum (ncas_sub) + nocc = ncore + ncas + nao, nmo = mo_coeff.shape + + mo_cas = mo_coeff[:,ncore:nocc] + moH_cas = mo_cas.conjugate ().T + moH_coeff = mo_coeff.conjugate ().T + dma = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) + dmb = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) + casdm1s = np.stack ([dma, dmb], axis=0) + if not (isinstance (self, _DFLASCI)): + dm1s = np.dot (mo_cas, np.dot (casdm1s, moH_cas)).transpose (1,0,2) + return self.get_veff (dm1s = dm1s, spin_sep=True) + casdm1 = casdm1s.sum (0) + dm1 = np.dot (mo_cas, np.dot (casdm1, moH_cas)) + bPmn = sparsedf_array (self.with_df._cderi) + + # vj + dm_tril = dm1 + dm1.T - np.diag (np.diag (dm1.T)) + rho = np.dot (bPmn, lib.pack_tril (dm_tril)) + vj = lib.unpack_tril (np.dot (rho, bPmn)) + + # vk + bmPu = h2eff_sub.bmPu + if _full: + vmPsu = np.dot (bmPu, casdm1s) + vk = np.tensordot (vmPsu, bmPu, axes=((1,3),(1,2))).transpose (1,0,2) + return vj[None,:,:] - vk + else: + vmPu = np.dot (bmPu, casdm1) + vk = np.tensordot (vmPu, bmPu, axes=((1,2),(1,2))) + return vj - vk/2 + + @lib.with_doc(run_lasci.__doc__) + def lasci (self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, + assert_no_dupes=False, _dry_run=False): + if mo_coeff is None: mo_coeff=self.mo_coeff + if ci0 is None: ci0 = self.ci + if verbose is None: verbose = self.verbose + converged, e_tot, e_states, e_cas, e_lexc, ci = run_lasci ( + self, mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, + verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) + if _dry_run: return + self.converged, self.ci = converged, ci + self.e_tot, self.e_states, self.e_cas, self.e_lexc = e_tot, e_states, e_cas, e_lexc + if mo_coeff is self.mo_coeff: + self.dump_chk () + elif getattr (self, 'chkfile', None) is not None: + lib.logger.warn (self, 'orbitals changed; chkfile not dumped!') + self._finalize () + return self.converged, self.e_tot, self.e_states, self.e_cas, e_lexc, self.ci + + @lib.with_doc(run_lasci.__doc__) + def lasci_(self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, + assert_no_dupes=False, _dry_run=False): + if mo_coeff is not None: + self.mo_coeff = mo_coeff + return self.lasci (mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, + verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) + + state_average = state_average + state_average_ = state_average_ + get_single_state_las = get_single_state_las + + def lassi(self, mo_coeff=None, ci=None, veff_c=None, h2eff_sub=None, orbsym=None, + soc=False, break_symmetry=False, opt=1, **kwargs): + #import warnings + #lassi_kernel_warn = "Now LASSI have kernel, which takes las instance as input. This [las.lassi()] function " \ + # "will be removed soon." + #warnings.warn(lassi_kernel_warn, stacklevel=3) + from mrh.my_pyscf.lassi import lassi + mylassi = lassi.LASSI(self, mo_coeff=mo_coeff, ci=ci, soc=soc, opt=opt, + break_symmetry=break_symmetry, **kwargs) + return mylassi.kernel(mo_coeff=mo_coeff, ci=ci, veff_c=veff_c, h2eff_sub=h2eff_sub, + orbsym=orbsym) + + las2cas_civec = las2cas_civec + assert_no_duplicates = assert_no_duplicates + get_init_guess_ci = get_init_guess_ci + localize_init_guess=lasscf_guess.localize_init_guess + def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): + if s is None: s = self._scf.get_ovlp () + return matrix_svd_control_options (s, lspace=mo_lspace, rspace=mo_rspace, full_matrices=True)[:3] + + def dump_flags (self, verbose=None, _method_name='LASCI'): + log = lib.logger.new_logger (self, verbose) + log.info ('') + log.info ('******** %s flags ********', _method_name) + ncore = self.ncore + ncas = self.ncas + nvir = self.mo_coeff.shape[1] - ncore - ncas + nfrags = len (self.nelecas_sub) + log.info ('CAS (%de+%de, %do), ncore = %d, nvir = %d', + self.nelecas[0], self.nelecas[1], ncas, ncore, nvir) + log.info ('Divided into %d LAS spaces', nfrags) + for i, (no, ne) in enumerate (zip (self.ncas_sub, self.nelecas_sub)): + log.info ('LAS %d : (%de+%de, %do)', i, ne[0], ne[1], no) + log.info ('nroots = %d', self.nroots) + log.info ('max_memory %d (MB)', self.max_memory) + for i, fcibox in enumerate (self.fciboxes): + if getattr (fcibox, 'dump_flags', None): + log.info ('fragment %d FCI solver flags:', i) + fcibox.dump_flags (log.verbose) + + @property + def converged (self): + return all (self.states_converged) + @converged.setter + def converged (self, x): + if hasattr (x, '__len__'): + self.states_converged = list (x) + else: + self.states_converged = [x,]*self.nroots + + def dump_spaces (self, nroots=None, sort_energy=False): + log = lib.logger.new_logger (self, self.verbose) + log.info ("******** LAS space tables ********") + ci = self.ci + if nroots is None and self.verbose <= lib.logger.INFO: + nroots = min (self.nroots, 100) + elif nroots is None: + nroots = self.nroots + if nroots < self.nroots: + log.warn ("Dumping only 100 of %d spaces", self.nroots) + log.warn ("To see more, explicitly pass nroots to dump_spaces or increase verbosity") + if sort_energy: + idx = np.argsort (self.e_states) + else: + idx = range (nroots) + for state in idx: + neleca_f = [] + nelecb_f = [] + wfnsym_f = [] + wfnsym = 0 + m_f = [] + s_f = [] + lroots = [] + s2_tot = 0 + for ifrag, (fcibox, nelecas) in enumerate (zip (self.fciboxes, self.nelecas_sub)): + solver = fcibox.fcisolvers[state] + na, nb = _unpack_nelec (fcibox._get_nelec (solver, nelecas)) + neleca_f.append (na) + nelecb_f.append (nb) + m_f.append ((na-nb)/2) + s_f.append ((solver.smult-1)/2) + s2_tot += s_f[-1] * (s_f[-1] + 1) + fragsym = getattr (solver, 'wfnsym', 0) or 0 + if isinstance (fragsym, str): + fragsym_str = fragsym + fragsym_id = symm.irrep_name2id (solver.mol.groupname, fragsym) + else: + fragsym_id = fragsym + fragsym_str = symm.irrep_id2name (solver.mol.groupname, fragsym) + wfnsym ^= fragsym_id + wfnsym_f.append (fragsym_str) + lroots_i = 0 + if ci is not None: + if ci[ifrag] is not None: + ci_i = ci[ifrag] + if ci_i[state] is not None: + ci_ij = ci_i[state] + lroots_i = 1 if ci_ij.ndim<3 else ci_ij.shape[0] + lroots.append (lroots_i) + s2_tot += sum ([2*m1*m2 for m1, m2 in combinations (m_f, 2)]) + s_f, m_f = np.asarray (s_f), np.asarray (m_f) + if np.all (m_f<0): m_f *= -1 + s_pure = bool (np.all (s_f==m_f)) + wfnsym = symm.irrep_id2name (self.mol.groupname, wfnsym) + neleca = sum (neleca_f) + nelecb = sum (nelecb_f) + log.info ("LAS space %d: (%de+%de,%do) wfynsm=%s", state, neleca, nelecb, self.ncas, wfnsym) + log.info ("Converged? %s", self.states_converged[state]) + log.info ("E(LAS) = %.15g", self.e_states[state]) + log.info ("S^2 = %.7f (%s)", s2_tot, ('Impure','Pure')[s_pure]) + log.info ("Space table") + log.info (" frag (ae+be,no) 2S+1 ir lroots") + for i in range (self.nfrags): + smult_f = int (round (2*s_f[i] + 1)) + tupstr = '({}e+{}e,{}o)'.format (neleca_f[i], nelecb_f[i], self.ncas_sub[i]) + log.info (" %4d %13s %4d %3s %6d", i, tupstr, smult_f, wfnsym_f[i], lroots[i]) + + def check_sanity (self): + casci.CASCI.check_sanity (self) + self.get_ugg () # constructor encounters impossible states and raises error + + dump_chk = chkfile.dump_las + load_chk = load_chk_ = chkfile.load_las_ + +class LASCISymm (casci_symm.CASCI, LASCINoSymm): + + def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, wfnsym_sub=None, frozen=None, + **kwargs): + LASCINoSymm.__init__(self, mf, ncas, nelecas, ncore=ncore, spin_sub=spin_sub, + frozen=frozen, **kwargs) + if getattr (self.mol, 'groupname', None) in ('Dooh', 'Coov'): + raise NotImplementedError ("LASSCF support for cylindrical point group {}".format ( + self.mol.groupname)) + if wfnsym_sub is None: wfnsym_sub = [0 for icas in self.ncas_sub] + # TODO: guess wfnsym_sub intelligently (0 can be impossible for some multiplicities) + for wfnsym, frag in zip (wfnsym_sub, self.fciboxes): + if isinstance (wfnsym, (str, np.str_)): + wfnsym = symm.irrep_name2id (self.mol.groupname, wfnsym) + frag.fcisolvers[0].wfnsym = wfnsym + + make_rdm1s = LASCINoSymm.make_rdm1s + make_rdm1 = LASCINoSymm.make_rdm1 + get_veff = LASCINoSymm.get_veff + get_h1eff = get_h1las = h1e_for_las + dump_flags = LASCINoSymm.dump_flags + dump_spaces = LASCINoSymm.dump_spaces + check_sanity = LASCINoSymm.check_sanity + _ugg = lasci_sync.LASCISymm_UnitaryGroupGenerators + + @property + def wfnsym (self): + ''' This now returns the product of the irreps of the subspaces ''' + wfnsym = [0,]*self.nroots + for frag in self.fciboxes: + for state, solver in enumerate (frag.fcisolvers): + wfnsym[state] ^= solver.wfnsym + if self.nroots == 1: wfnsym = wfnsym[0] + return wfnsym + @wfnsym.setter + def wfnsym (self, ir): + raise RuntimeError (("Cannot assign the whole-system symmetry of a LASCI wave function. " + "Address fciboxes[ifrag].fcisolvers[istate].wfnsym instead.")) + + def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, verbose=None, assert_no_dupes=False): + if mo_coeff is None: + mo_coeff = self.mo_coeff + if ci0 is None: + ci0 = self.ci + + # Initialize/overwrite mo_coeff.orbsym. Don't pass ci0 because it's not the right shape + lib.logger.info (self, ("LASCI lazy hack note: lines below reflect the point-group " + "symmetry of the whole molecule but not of the individual " + "subspaces")) + mo_coeff = self.mo_coeff = self.label_symmetry_(mo_coeff) + return LASCINoSymm.kernel(self, mo_coeff=mo_coeff, ci0=ci0, + casdm0_fr=casdm0_fr, verbose=verbose, assert_no_dupes=assert_no_dupes) + + def canonicalize (self, mo_coeff=None, ci=None, natorb_casdm1=None, veff=None, h2eff_sub=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + mo_coeff = self.label_symmetry_(mo_coeff) + return canonicalize (self, mo_coeff=mo_coeff, ci=ci, natorb_casdm1=natorb_casdm1, + h2eff_sub=h2eff_sub, orbsym=mo_coeff.orbsym) + + def label_symmetry_(self, mo_coeff=None): + if mo_coeff is None: mo_coeff=self.mo_coeff + ncore = self.ncore + ncas_sub = self.ncas_sub + nocc = ncore + sum (ncas_sub) + mo_coeff[:,:ncore] = symm.symmetrize_space (self.mol, mo_coeff[:,:ncore]) + for isub, ncas in enumerate (ncas_sub): + i = ncore + sum (ncas_sub[:isub]) + j = i + ncas + mo_coeff[:,i:j] = symm.symmetrize_space (self.mol, mo_coeff[:,i:j]) + mo_coeff[:,nocc:] = symm.symmetrize_space (self.mol, mo_coeff[:,nocc:]) + orbsym = symm.label_orb_symm (self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_coeff, + s=self._scf.get_ovlp ()) + mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) + return mo_coeff + + @lib.with_doc(LASCINoSymm.localize_init_guess.__doc__) + def localize_init_guess (self, frags_atoms, mo_coeff=None, spin=None, lo_coeff=None, fock=None, + freeze_cas_spaces=False): + if mo_coeff is None: + mo_coeff = self.mo_coeff + mo_coeff = casci_symm.label_symmetry_(self, mo_coeff) + return LASCINoSymm.localize_init_guess (self, frags_atoms, mo_coeff=mo_coeff, spin=spin, + lo_coeff=lo_coeff, fock=fock, freeze_cas_spaces=freeze_cas_spaces) + + def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): + if s is None: s = self._scf.get_ovlp () + lsymm = getattr (mo_lspace, 'orbsym', None) + if lsymm is None: + mo_lspace = symm.symmetrize_space (self.mol, mo_lspace) + lsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_lspace, s=s) + rsymm = getattr (mo_rspace, 'orbsym', None) + if rsymm is None: + mo_rspace = symm.symmetrize_space (self.mol, mo_rspace) + rsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_rspace, s=s) + decomp = matrix_svd_control_options (s, + lspace=mo_lspace, rspace=mo_rspace, + lspace_symmetry=lsymm, rspace_symmetry=rsymm, + full_matrices=True, strong_symm=True) + mo_lvecs, svals, mo_rvecs, lsymm, rsymm = decomp + mo_lvecs = lib.tag_array (mo_lvecs, orbsym=lsymm) + mo_rvecs = lib.tag_array (mo_rvecs, orbsym=rsymm) + return mo_lvecs, svals, mo_rvecs + diff --git a/my_pyscf/mcscf/sv_lasci.py b/my_pyscf/mcscf/sv_lasci.py new file mode 100644 index 00000000..eaf153a4 --- /dev/null +++ b/my_pyscf/mcscf/sv_lasci.py @@ -0,0 +1,1844 @@ +from pyscf.scf.rohf import get_roothaan_fock +from pyscf.fci import cistring +from pyscf.mcscf import casci, casci_symm, df +from pyscf.tools import dump_mat +from pyscf import symm, gto, scf, ao2mo, lib +from pyscf.fci.direct_spin1 import _unpack_nelec +from mrh.my_pyscf.mcscf.addons import state_average_n_mix, get_h1e_zipped_fcisolver, las2cas_civec +from mrh.my_pyscf.mcscf import lasci_sync, _DFLASCI, lasscf_guess +from mrh.my_pyscf.fci import csf_solver +from mrh.my_pyscf.df.sparse_df import sparsedf_array +from mrh.my_pyscf.mcscf import chkfile +from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver +from mrh.util.la import matrix_svd_control_options +from itertools import combinations +from scipy.sparse import linalg as sparse_linalg +from scipy import linalg +import numpy as np +import copy + +def LASCI (mf_or_mol, ncas_sub, nelecas_sub, **kwargs): + if isinstance(mf_or_mol, gto.Mole): + mf = scf.RHF(mf_or_mol) + else: + mf = mf_or_mol + if mf.mol.symmetry: + las = LASCISymm (mf, ncas_sub, nelecas_sub, **kwargs) + else: + las = LASCINoSymm (mf, ncas_sub, nelecas_sub, **kwargs) + if getattr (mf, 'with_df', None): + las = density_fit (las, with_df = mf.with_df) + return las + +def get_grad (las, mo_coeff=None, ci=None, ugg=None, h1eff_sub=None, h2eff_sub=None, + veff=None, dm1s=None): + '''Return energy gradient for orbital rotation and CI relaxation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + ugg : instance of :class:`LASCI_UnitaryGroupGenerators` + h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray + Contains effective one-electron Hamiltonians experienced by each fragment + in each state + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + dm1s : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-RDM in the AO basis + + Returns: + gorb : ndarray of shape (ugg.nvar_orb,) + Orbital rotation gradients as a flat array + gci : ndarray of shape (sum(ugg.ncsf_sub),) + CI relaxation gradients as a flat array + gx : ndarray + Orbital rotation gradients for temporarily frozen orbitals in the "LASCI" problem + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if ugg is None: ugg = las.get_ugg (mo_coeff, ci) + if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if veff is None: + veff = las.get_veff (dm1s = dm1s.sum (0)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) + if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, + h2eff_sub=h2eff_sub) + + gorb = get_grad_orb (las, mo_coeff=mo_coeff, ci=ci, h2eff_sub=h2eff_sub, veff=veff, dm1s=dm1s) + gci = get_grad_ci (las, mo_coeff=mo_coeff, ci=ci, h1eff_sub=h1eff_sub, h2eff_sub=h2eff_sub, + veff=veff) + + idx = ugg.get_gx_idx () + gx = gorb[idx] + gint = ugg.pack (gorb, gci) + gorb = gint[:ugg.nvar_orb] + gci = gint[ugg.nvar_orb:] + return gorb, gci, gx.ravel () + +def get_grad_orb (las, mo_coeff=None, ci=None, h2eff_sub=None, veff=None, dm1s=None, hermi=-1): + '''Return energy gradient for orbital rotation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + dm1s : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-RDM in the AO basis + hermi : integer + Control (anti-)symmetrization. 0 means to return the effective Fock matrix, + F1 = h.D + g.d. -1 means to return the true orbital-rotation gradient, which is skew- + symmetric: gorb = F1 - F1.T. +1 means to return the symmetrized effective Fock matrix, + (F1 + F1.T) / 2. The factor of 2 difference between hermi=-1 and the other two options + is intentional and necessary. + + Returns: + gorb : ndarray of shape (nmo,nmo) + Orbital rotation gradients as a square antihermitian array + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if veff is None: + veff = las.get_veff (dm1s = dm1s.sum (0)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) + nao, nmo = mo_coeff.shape + ncore = las.ncore + ncas = las.ncas + nocc = las.ncore + las.ncas + smo_cas = las._scf.get_ovlp () @ mo_coeff[:,ncore:nocc] + smoH_cas = smo_cas.conj ().T + + # The orbrot part + h1s = las.get_hcore ()[None,:,:] + veff + f1 = h1s[0] @ dm1s[0] + h1s[1] @ dm1s[1] + f1 = mo_coeff.conjugate ().T @ f1 @ las._scf.get_ovlp () @ mo_coeff + # ^ I need the ovlp there to get dm1s back into its correct basis + casdm2 = las.make_casdm2 (ci=ci) + casdm1s = np.stack ([smoH_cas @ d @ smo_cas for d in dm1s], axis=0) + casdm1 = casdm1s.sum (0) + casdm2 -= np.multiply.outer (casdm1, casdm1) + casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) + casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) + eri = h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2) + eri = lib.numpy_helper.unpack_tril (eri).reshape (nmo, ncas, ncas, ncas) + f1[:,ncore:nocc] += np.tensordot (eri, casdm2, axes=((1,2,3),(1,2,3))) + + if hermi == -1: + return f1 - f1.T + elif hermi == 1: + return .5*(f1+f1.T) + elif hermi == 0: + return f1 + else: + raise ValueError ("kwarg 'hermi' must = -1, 0, or +1") + +def get_grad_ci (las, mo_coeff=None, ci=None, h1eff_sub=None, h2eff_sub=None, veff=None): + '''Return energy gradient for CI relaxation. + + Args: + las : instance of :class:`LASCINoSymm` + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + Contains molecular orbitals + ci : list (length=nfrags) of list (length=nroots) of ndarray + Contains CI vectors + h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray + Contains effective one-electron Hamiltonians experienced by each fragment + in each state + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + veff : ndarray of shape (2,nao,nao) + Spin-separated, state-averaged 1-electron mean-field potential in the AO basis + + Returns: + gci : list (length=nfrags) of list (length=nroots) of ndarray + CI relaxation gradients in the shape of CI vectors + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, + h2eff_sub=h2eff_sub) + gci = [] + for isub, (fcibox, h1e, ci0, ncas, nelecas) in enumerate (zip ( + las.fciboxes, h1eff_sub, ci, las.ncas_sub, las.nelecas_sub)): + eri_cas = las.get_h2eff_slice (h2eff_sub, isub, compact=8) + linkstrl = fcibox.states_gen_linkstr (ncas, nelecas, True) + linkstr = fcibox.states_gen_linkstr (ncas, nelecas, False) + h2eff = fcibox.states_absorb_h1e(h1e, eri_cas, ncas, nelecas, .5) + hc0 = fcibox.states_contract_2e(h2eff, ci0, ncas, nelecas, link_index=linkstrl) + hc0 = [hc.ravel () for hc in hc0] + ci0 = [c.ravel () for c in ci0] + gci.append ([2.0 * (hc - c * (c.dot (hc))) for c, hc in zip (ci0, hc0)]) + return gci + +def density_fit (las, auxbasis=None, with_df=None): + ''' Here I ONLY need to attach the tag and the df object because I put conditionals in + LASCINoSymm to make my life easier ''' + las_class = las.__class__ + if with_df is None: + if (getattr(las._scf, 'with_df', None) and + (auxbasis is None or auxbasis == las._scf.with_df.auxbasis)): + with_df = las._scf.with_df + else: + with_df = df.DF(las.mol) + with_df.max_memory = las.max_memory + with_df.stdout = las.stdout + with_df.verbose = las.verbose + with_df.auxbasis = auxbasis + class DFLASCI (las_class, _DFLASCI): + def __init__(self, scf, ncas_sub, nelecas_sub): + self.with_df = with_df + self._keys = self._keys.union(['with_df']) + las_class.__init__(self, scf, ncas_sub, nelecas_sub) + new_las = DFLASCI (las._scf, las.ncas_sub, las.nelecas_sub) + new_las.__dict__.update (las.__dict__) + return new_las + +def h1e_for_las (las, mo_coeff=None, ncas=None, ncore=None, nelecas=None, ci=None, ncas_sub=None, + nelecas_sub=None, veff=None, h2eff_sub=None, casdm1s_sub=None, casdm1frs=None): + ''' Effective one-body Hamiltonians (plural) for a LASCI problem + + Args: + las: a LASCI object + + Kwargs: + mo_coeff: ndarray of shape (nao,nmo) + Orbital coefficients ordered on the columns as: + core orbitals, subspace 1, subspace 2, ..., external orbitals + ncas: integer + As in PySCF's existing CASCI/CASSCF implementation + nelecas: sequence of 2 integers + As in PySCF's existing CASCI/CASSCF implementation + ci: list (length=nfrags) of list (length=nroots) of ndarrays + Contains CI vectors + ncas_sub: ndarray of shape (nsub) + Number of active orbitals in each subspace + nelecas_sub: ndarray of shape (nsub,2) + na, nb in each subspace + veff: ndarray of shape (2, nao, nao) + Contains spin-separated, state-averaged effective potential + h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) + Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices + casdm1s_sub : list (length=nfrags) of ndarrays + Contains state-averaged, spin-separated 1-RDMs in the localized active subspaces + casdm1frs : list (length=nfrags) of list (length=nroots) of ndarrays + Contains spin-separated 1-RDMs for each state in the localized active subspaces + + Returns: + h1e_fr: list (length=nfrags) of list (length=nroots) of ndarrays + Spin-separated 1-body Hamiltonian operator for each fragment and state + ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ncas is None: ncas = las.ncas + if ncore is None: ncore = las.ncore + if ncas_sub is None: ncas_sub = las.ncas_sub + if nelecas_sub is None: nelecas_sub = las.nelecas_sub + if ncore is None: ncore = las.ncore + if ci is None: ci = las.ci + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + if casdm1frs is None: casdm1frs = las.states_make_casdm1s_sub (ci=ci) + if casdm1s_sub is None: casdm1s_sub = [np.einsum ('rsij,r->sij',dm,las.weights) + for dm in casdm1frs] + if veff is None: + veff = las.get_veff (dm1s = las.make_rdm1 (mo_coeff=mo_coeff, ci=ci)) + veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci, casdm1s_sub=casdm1s_sub) + + # First pass: split by root + nocc = ncore + ncas + nmo = mo_coeff.shape[-1] + mo_cas = mo_coeff[:,ncore:nocc] + moH_cas = mo_cas.conj ().T + h1e = moH_cas @ (las.get_hcore ()[None,:,:] + veff) @ mo_cas + h1e_r = np.empty ((las.nroots, 2, ncas, ncas), dtype=h1e.dtype) + h2e = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, + ncas*(ncas+1)//2)).reshape (nmo, ncas, ncas, ncas)[ncore:nocc,:,:,:] + #print ("SV h2e = ",h2e, h2e.shape) + avgdm1s = np.stack ([linalg.block_diag (*[dm[spin] for dm in casdm1s_sub]) + for spin in range (2)], axis=0) + for state in range (las.nroots): + statedm1s = np.stack ([linalg.block_diag (*[dm[state][spin] for dm in casdm1frs]) + for spin in range (2)], axis=0) + dm1s = statedm1s - avgdm1s + j = np.tensordot (dm1s, h2e, axes=((1,2),(2,3))) + k = np.tensordot (dm1s, h2e, axes=((1,2),(2,1))) + h1e_r[state] = h1e + j + j[::-1] - k + + + # Second pass: split by fragment and subtract double-counting + h1e_fr = [] + for ix, casdm1s_r in enumerate (casdm1frs): + p = sum (las.ncas_sub[:ix]) + q = p + las.ncas_sub[ix] + h1e = h1e_r[:,:,p:q,p:q] + h2e = las.get_h2eff_slice (h2eff_sub, ix) + j = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,3))) + k = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,1))) + h1e_fr.append (h1e - j - j[:,::-1] + k) + + return h1e_fr + +def get_fock (las, mo_coeff=None, ci=None, eris=None, casdm1s=None, verbose=None, veff=None, + dm1s=None): + ''' f_pq = h_pq + (g_pqrs - g_psrq/2) D_rs, AO basis + Note the difference between this and h1e_for_las: h1e_for_las only has + JK terms from electrons outside the "current" active subspace; get_fock + includes JK from all electrons. This is also NOT the "generalized Fock matrix" + of orbital gradients (but it can be used in calculating those if you do a + semi-cumulant decomposition). + The "eris" kwarg does not do anything and is retained only for backwards + compatibility (also why I don't just call las.make_rdm1) ''' + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + if dm1s is None: + if casdm1s is None: casdm1s = las.make_casdm1s (ci=ci) + mo_cas = mo_coeff[:,las.ncore:][:,:las.ncas] + moH_cas = mo_cas.conjugate ().T + mo_core = mo_coeff[:,:las.ncore] + moH_core = mo_core.conjugate ().T + dm1s = [(mo_core @ moH_core) + (mo_cas @ d @ moH_cas) for d in list(casdm1s)] + if veff is not None: + fock = las.get_hcore()[None,:,:] + veff + return get_roothaan_fock (fock, dm1s, las._scf.get_ovlp ()) + dm1 = dm1s[0] + dm1s[1] + if isinstance (las, _DFLASCI): + vj, vk = las.with_df.get_jk(dm1, hermi=1) + else: + vj, vk = las._scf.get_jk(las.mol, dm1, hermi=1) + fock = las.get_hcore () + vj - (vk/2) + return fock + +def _eig_inactive_virtual (las, fock, orbsym=None): + '''Generate the unitary matrix canonicalizing the inactive and virtual orbitals only. + + Args: + las : object of :class:`LASCINoSymm` + fock : ndarray of shape (nmo,nmo) + Contains Fock matrix in MO basis + + Kwargs: + orbsym : list of length nmo + umat : ndarray of shape (nmo, nmo) + + Returns: + ene : ndarray of shape (nmo,) + umat : ndarray of shape (nmo, nmo)''' + nmo = fock.shape[0] + ncore = las.ncore + nocc = ncore + las.ncas + ene = np.zeros (nmo) + umat = np.eye (nmo) + if ncore: + orbsym_i = None if orbsym is None else orbsym[:ncore] + fock_i = fock[:ncore,:ncore] + ene[:ncore], umat[:ncore,:ncore] = las._eig (fock_i, 0, 0, orbsym_i) + if nmo-nocc: + orbsym_i = None if orbsym is None else orbsym[nocc:] + fock_i = fock[nocc:,nocc:] + ene[nocc:], umat[nocc:,nocc:] = las._eig (fock_i, 0, 0, orbsym_i) + return ene, umat + +def canonicalize (las, mo_coeff=None, ci=None, casdm1fs=None, natorb_casdm1=None, veff=None, + h2eff_sub=None, orbsym=None): + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci is None: ci = las.ci + + # In-place safety + mo_coeff = mo_coeff.copy () + ci = copy.deepcopy (ci) + + # Temporary lroots safety + # The desired behavior is that the inactive and external orbitals should + # be canonicalized according to the density matrix used for orbital optimization + # TODO: once orbital optimization with lroots is enabled, change this behavior + # TODO: possibly move this logic to the make_casdm* functions + if casdm1fs is None: + ci_dm = [] + for i in range (len (ci)): + ci_i = [] + for j in range (len (ci[i])): + if ci[i][j].ndim>2: + ci_i.append (ci[i][j][0]) + else: + ci_i.append (ci[i][j]) + ci_dm.append (ci_i) + casdm1fs = las.make_casdm1s_sub (ci=ci_dm) + + nao, nmo = mo_coeff.shape + ncore = las.ncore + nocc = ncore + las.ncas + ncas_sub = las.ncas_sub + nelecas_sub = las.nelecas_sub + + # Passing casdm1 or lasdm1 only affects the canonicalization of the active orbitals + umat = np.zeros_like (mo_coeff) + casdm1s = np.stack ([linalg.block_diag (*[dm[0] for dm in casdm1fs]), + linalg.block_diag (*[dm[1] for dm in casdm1fs])], axis=0) + fock = mo_coeff.conjugate ().T @ las.get_fock (mo_coeff=mo_coeff, casdm1s=casdm1s, veff=veff) + fock = fock @ mo_coeff + if natorb_casdm1 is None: # State-average natural orbitals by default + natorb_casdm1 = casdm1s.sum (0) + + # Inactive-inactive and virtual-virtual + ene, umat = _eig_inactive_virtual (las, fock, orbsym=orbsym) + idx = np.arange (nmo, dtype=int) + if ncore: idx[:ncore] = idx[:ncore][np.argsort (ene[:ncore])] + if nmo-nocc: idx[nocc:] = idx[nocc:][np.argsort (ene[nocc:])] + umat = umat[:,idx] + if orbsym is not None: orbsym = orbsym[idx] + # Active-active + check_diag = natorb_casdm1.copy () + for ix, ncas in enumerate (ncas_sub): + i = sum (ncas_sub[:ix]) + j = i + ncas + check_diag[i:j,i:j] = 0.0 + is_block_diag = np.amax (np.abs (check_diag)) < 1e-8 + if is_block_diag: + # No off-diagonal RDM elements -> extra effort to prevent diagonalizer from breaking frags + for isub, (ncas, nelecas) in enumerate (zip (ncas_sub, nelecas_sub)): + i = sum (ncas_sub[:isub]) + j = i + ncas + dm1 = natorb_casdm1[i:j,i:j] + i += ncore + j += ncore + orbsym_i = None if orbsym is None else orbsym[i:j] + occ, umat[i:j,i:j] = las._eig (dm1, 0, 0, orbsym_i) + idx = np.argsort (occ)[::-1] + umat[i:j,i:j] = umat[i:j,i:j][:,idx] + if orbsym_i is not None: orbsym[i:j] = orbsym[i:j][idx] + if ci is not None: + fcibox = las.fciboxes[isub] + ci[isub] = fcibox.states_transform_ci_for_orbital_rotation ( + ci[isub], ncas, nelecas, umat[i:j,i:j]) + else: # You can't get proper LAS-type CI vectors w/out active space fragmentation + ci = None + orbsym_cas = None if orbsym is None else orbsym[ncore:nocc] + occ, umat[ncore:nocc,ncore:nocc] = las._eig (natorb_casdm1, 0, 0, orbsym_cas) + idx = np.argsort (occ)[::-1] + umat[ncore:nocc,ncore:nocc] = umat[ncore:nocc,ncore:nocc][:,idx] + if orbsym_cas is not None: orbsym[ncore:nocc] = orbsym[ncore:nocc][idx] + + # Final + mo_occ = np.zeros (nmo, dtype=natorb_casdm1.dtype) + if ncore: mo_occ[:ncore] = 2 + ucas = umat[ncore:nocc,ncore:nocc] + mo_occ[ncore:nocc] = ((natorb_casdm1 @ ucas) * ucas).sum (0) + mo_ene = ((fock @ umat) * umat.conjugate ()).sum (0) + mo_ene[ncore:][:sum (ncas_sub)] = 0.0 + mo_coeff = mo_coeff @ umat + if orbsym is not None: + ''' + print ("This is the second call to label_orb_symm inside of canonicalize") + orbsym = symm.label_orb_symm (las.mol, las.mol.irrep_id, + las.mol.symm_orb, mo_coeff, + s=las._scf.get_ovlp ()) + #mo_coeff = las.label_symmetry_(mo_coeff) + ''' + mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) + if h2eff_sub is not None: + h2eff_sub = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*las.ncas, -1)) + h2eff_sub = h2eff_sub.reshape (nmo, las.ncas, las.ncas, las.ncas) + h2eff_sub = np.tensordot (umat, h2eff_sub, axes=((0),(0))) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(1))).transpose (1,0,2,3) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(2))).transpose (1,2,0,3) + h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(3))).transpose (1,2,3,0) + h2eff_sub = h2eff_sub.reshape (nmo*las.ncas, las.ncas, las.ncas) + h2eff_sub = lib.numpy_helper.pack_tril (h2eff_sub).reshape (nmo, -1) + + # I/O + log = lib.logger.new_logger (las, las.verbose) + if las.verbose >= lib.logger.INFO: + if is_block_diag: + for isub, nlas in enumerate (ncas_sub): + log.info ("Fragment %d natural orbitals", isub) + i = ncore + sum (ncas_sub[:isub]) + j = i + nlas + log.info ('Natural occ %s', str (mo_occ[i:j])) + log.info ('Natural orbital (expansion on AOs) in CAS space') + label = las.mol.ao_labels() + mo_las = mo_coeff[:,i:j] + dump_mat.dump_rec(log.stdout, mo_las, label, start=1) + else: + log.info ("Delocalized natural orbitals do not reflect LAS fragmentation") + log.info ('Natural occ %s', str (mo_occ[ncore:nocc])) + log.info ('Natural orbital (expansion on AOs) in CAS space') + label = las.mol.ao_labels() + mo_las = mo_coeff[:,ncore:nocc] + dump_mat.dump_rec(log.stdout, mo_las, label, start=1) + + return mo_coeff, mo_ene, mo_occ, ci, h2eff_sub + +def get_init_guess_ci (las, mo_coeff=None, h2eff_sub=None, ci0=None): + # TODO: come up with a better algorithm? This might be working better than what I had before + # but it omits inter-active Coulomb and exchange interactions altogether. Is there a + # non-outer-product algorithm for finding the lowest-energy single product of CSFs? + if mo_coeff is None: mo_coeff = las.mo_coeff + if ci0 is None: ci0 = [[None for i in range (las.nroots)] for j in range (las.nfrags)] + if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) + nmo = mo_coeff.shape[-1] + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + dm1_core= 2 * mo_coeff[:,:ncore] @ mo_coeff[:,:ncore].conj ().T + h1e_ao = las._scf.get_fock (dm=dm1_core) + eri_cas = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)) + eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) + eri_cas = eri_cas[ncore:nocc] + for ix, (fcibox, norb, nelecas) in enumerate (zip (las.fciboxes,las.ncas_sub,las.nelecas_sub)): + i = sum (las.ncas_sub[:ix]) + j = i + norb + mo = mo_coeff[:,ncore+i:ncore+j] + moH = mo.conj ().T + h1e = moH @ h1e_ao @ mo + h1e = [h1e, h1e] + eri = eri_cas[i:j,i:j,i:j,i:j] + for iy, solver in enumerate (fcibox.fcisolvers): + nelec = fcibox._get_nelec (solver, nelecas) + ndet = tuple ([cistring.num_strings (norb, n) for n in nelec]) + if isinstance (ci0[ix][iy], np.ndarray) and ci0[ix][iy].size==ndet[0]*ndet[1]: continue + if hasattr (mo_coeff, 'orbsym'): + solver.orbsym = mo_coeff.orbsym[ncore+i:ncore+j] + hdiag_csf = solver.make_hdiag_csf (h1e, eri, norb, nelec, max_memory=las.max_memory) + ci0[ix][iy] = solver.get_init_guess (norb, nelec, solver.nroots, hdiag_csf) + if solver.nroots==1: + ci0[ix][iy] = ci0[ix][iy][0] + else: + ci0[ix][iy] = np.stack (ci0[ix][iy], axis=0) + return ci0 + +def get_space_info (las): + ''' Retrieve the quantum numbers defining the states of a LASSCF calculation ''' + nfrags, nroots = las.nfrags, las.nroots + charges = np.zeros ((nroots, nfrags), dtype=np.int32) + wfnsyms, spins, smults = charges.copy (), charges.copy (), charges.copy () + for ifrag, fcibox in enumerate (las.fciboxes): + for iroot, solver in enumerate (fcibox.fcisolvers): + nelec = fcibox._get_nelec (solver, las.nelecas_sub[ifrag]) + charges[iroot,ifrag] = np.sum (las.nelecas_sub[ifrag]) - np.sum (nelec) + spins[iroot,ifrag] = nelec[0]-nelec[1] + smults[iroot,ifrag] = solver.smult + try: + wfnsyms[iroot,ifrag] = solver.wfnsym or 0 + except ValueError as e: + wfnsyms[iroot,ifrag] = symm.irrep_name2id (las.mol.groupname, solver.wfnsym) + return charges, spins, smults, wfnsyms + +def assert_no_duplicates (las, tab=None): + log = lib.logger.new_logger (las, las.verbose) + if tab is None: tab = np.stack (get_space_info (las), axis=-1) + tab_uniq, uniq_idx, uniq_inv, uniq_cnts = np.unique (tab, return_index=True, + return_inverse=True, return_counts=True, axis=0) + idx_dupe = uniq_cnts>1 + try: + err_str = ('LAS state basis has duplicates; details in logfile for ' + 'verbose >= INFO (4) [more details for verbose > INFO].\n' + '(Disable this assertion by passing assert_no_dupes=False ' + 'to the kernel, lasci, and state_average(_) functions.)') + assert (~np.any (idx_dupe)), err_str + except AssertionError as e: + dupe_idx = uniq_idx[idx_dupe] + dupe_cnts = uniq_cnts[idx_dupe] + for i, (ix, cnt, col) in enumerate (zip (uniq_idx, uniq_cnts, tab_uniq)): + if cnt==1: continue + log.info ('State %d appears %d times', ix, cnt) + idx_thisdupe = np.where (uniq_inv==i)[0] + row = col.T + log.debug ('As states {}'.format (idx_thisdupe)) + log.debug ('Charges = {}'.format (row[0])) + log.debug ('2M_S = {}'.format (row[1])) + log.debug ('2S+1 = {}'.format (row[2])) + log.debug ('Wfnsyms = {}'.format (row[3])) + raise e from None + +def state_average_(las, weights=[0.5,0.5], charges=None, spins=None, + smults=None, wfnsyms=None, assert_no_dupes=True): + ''' Transform LASCI/LASSCF object into state-average LASCI/LASSCF + + Args: + las: LASCI/LASSCF instance + + Kwargs: + weights: list of float; required + E_SA = sum_i weights[i] E[i] is used to optimize the orbitals + charges: 2d ndarray or nested list of integers + spins: 2d ndarray or nested list of integers + For the jth fragment in the ith state, + neleca = (sum(las.nelecas_sub[j]) - charges[i][j] + spins[i][j]) // 2 + nelecb = (sum(las.nelecas_sub[j]) - charges[i][j] - spins[i][j]) // 2 + Defaults to + charges[i][j] = 0 + spins[i][j] = las.nelecas_sub[j][0] - las.nelecas_sub[j][1] + smults: 2d ndarray or nested list of integers + For the jth fragment in the ith state, + smults[i][j] = (2*s)+1 + where "s" is the total spin quantum number, + S^2|j,i> = s*(s+1)|j,i> + Defaults to + smults[i][j] = abs (spins[i][j]) + 1 + wfnsyms: 2d ndarray or nested list of integers or strings + For the jth fragment of the ith state, + wfnsyms[i][j] + identifies the point-group irreducible representation + Defaults to all zeros (i.e., the totally-symmetric irrep) + + Returns: + las: LASCI/LASSCF instance + The first positional argument, modified in-place into a + state-averaged LASCI/LASSCF instance. + + ''' + old_states = np.stack (get_space_info (las), axis=-1) + nroots = len (weights) + nfrags = las.nfrags + if charges is None: charges = np.zeros ((nroots, nfrags), dtype=np.int32) + if wfnsyms is None: wfnsyms = np.zeros ((nroots, nfrags), dtype=np.int32) + if spins is None: spins = np.asarray ([[n[0]-n[1] for n in las.nelecas_sub] for i in weights]) + if smults is None: smults = np.abs (spins)+1 + + charges = np.asarray (charges) + wfnsyms = np.asarray (wfnsyms) + spins = np.asarray (spins) + smults = np.asarray (smults) + if np.issubsctype (wfnsyms.dtype, np.str_): + wfnsyms_str = wfnsyms + wfnsyms = np.zeros (wfnsyms_str.shape, dtype=np.int32) + for ix, wfnsym in enumerate (wfnsyms_str.flat): + try: + wfnsyms.flat[ix] = symm.irrep_name2id (las.mol.groupname, wfnsym) + except (TypeError, KeyError) as e: + wfnsyms.flat[ix] = int (wfnsym) + if nfrags == 1: + charges = np.atleast_2d (np.squeeze (charges)).T + wfnsyms = np.atleast_2d (np.squeeze (wfnsyms)).T + spins = np.atleast_2d (np.squeeze (spins)).T + smults = np.atleast_2d (np.squeeze (smults)).T + new_states = np.stack ([charges, spins, smults, wfnsyms], axis=-1) + if assert_no_dupes: assert_no_duplicates (las, tab=new_states) + + las.fciboxes = [get_h1e_zipped_fcisolver (state_average_n_mix ( + las, [csf_solver (las.mol, smult=s2p1).set (charge=c, spin=m2, wfnsym=ir) + for c, m2, s2p1, ir in zip (c_r, m2_r, s2p1_r, ir_r)], weights).fcisolver) + for c_r, m2_r, s2p1_r, ir_r in zip (charges.T, spins.T, smults.T, wfnsyms.T)] + las.e_states = np.zeros (nroots) + las.nroots = nroots + las.weights = weights + + if las.ci is not None: + log = lib.logger.new_logger(las, las.verbose) + log.debug (("lasci.state_average: Cached CI vectors may be present.\n" + "Looking for matches between old and new LAS states...")) + ci0 = [[None for i in range (nroots)] for j in range (nfrags)] + new_states = np.stack ([charges, spins, smults, wfnsyms], + axis=-1).reshape (nroots, nfrags*4) + old_states = old_states.reshape (-1, nfrags*4) + for iroot, row in enumerate (old_states): + idx = np.all (new_states == row[None,:], axis=1) + if np.count_nonzero (idx) == 1: + jroot = np.where (idx)[0][0] + log.debug ("Old state {} -> New state {}".format (iroot, jroot)) + for ifrag in range (nfrags): + ci0[ifrag][jroot] = las.ci[ifrag][iroot] + elif np.count_nonzero (idx) > 1: + raise RuntimeError ("Duplicate states specified?\n{}".format (idx)) + las.ci = ci0 + las.converged = False + return las + +@lib.with_doc(''' A version of lasci.state_average_ that creates a copy instead of modifying the + LASCI/LASSCF method instance in place. + + See lasci.state_average_ docstring below:\n\n''' + state_average_.__doc__) +def state_average (las, weights=[0.5,0.5], charges=None, spins=None, + smults=None, wfnsyms=None, assert_no_dupes=True): + is_scanner = isinstance (las, lib.SinglePointScanner) + if is_scanner: las = las.undo_scanner () + new_las = las.__class__(las._scf, las.ncas_sub, las.nelecas_sub) + new_las.__dict__.update (las.__dict__) + new_las.mo_coeff = las.mo_coeff.copy () + if getattr (las.mo_coeff, 'orbsym', None) is not None: + new_las.mo_coeff = lib.tag_array (new_las.mo_coeff, + orbsym=las.mo_coeff.orbsym) + new_las.ci = None + if las.ci is not None: + new_las.ci = [[c2.copy () if isinstance (c2, np.ndarray) else None + for c2 in c1] for c1 in las.ci] + las = state_average_(new_las, weights=weights, charges=charges, spins=spins, + smults=smults, wfnsyms=wfnsyms, assert_no_dupes=assert_no_dupes) + if is_scanner: las = las.as_scanner () + return las + +def get_single_state_las (las, state=0): + ''' Quickly extract a state-specific las calculation from a state-average one ''' + charges, spins, smults, wfnsyms = get_space_info (las) + charges = charges[state:state+1] + spins = spins[state:state+1] + smults = smults[state:state+1] + wfnsyms = wfnsyms[state:state+1] + weights = [1,] + return state_average (las, weights=weights, charges=charges, spins=spins, smults=smults, + wfnsyms=wfnsyms) + +def run_lasci (las, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=0, + assert_no_dupes=False, _dry_run=False): + '''Self-consistently optimize the CI vectors of a LAS state with + frozen orbitals using a fixed-point algorithm. "lasci_" (with the + trailing underscore) sets self.mo_coeff from the kwarg if it is passed; + "lasci" (without the trailing underscore) leaves self.mo_coeff unchanged. + + Kwargs: + mo_coeff : ndarray of shape (nao,nmo) + MO coefficients; defaults to self.mo_coeff + ci0 : list (length nfrags) of list (length nroots) of ndarrays + Contains CI vectors for initial guess + lroots : ndarray of shape (nfrags,nroots) + Number of local roots in each fragment for each global state. + The corresponding local weights are set to [1,0,0,0,...]. + lweights : list of length nfrags of list of length nroots of sequence + Weights of local roots in each fragment for each global state. + Passing lweights is incompatible with passing lroots. Defaults + to, i.e., np.ones (las.nfrags, las.nroots, 1).tolist () + verbose : integer + See pyscf.lib.logger. + assert_no_dupes : logical + If True, checks state list for duplicate states + _dry_run : logical + If True, sets up the fcisolvers with the appropriate tags, but does + not run fcisolver kernels. + + Returns: + converged : list of length nroots of logical + Stores whether the calculation for each state successfully converged + e_tot : float + (State-averaged) total energy + e_states : list of length nroots + List of each state energy + e_cas : list of length nroots + List of the CAS space energy of each state + ci : list (length nfrags) of list (length nroots) of ndarrays + Contains optimized CI vectors + ''' + if assert_no_dupes: assert_no_duplicates (las) + if lroots is not None and lweights is not None: + raise RuntimeError ("lroots sets lweights: pass either or none but not both") + elif lweights is None: + if lroots is None: lroots = np.ones ((las.nfrags, las.nroots), dtype=int) + lweights = [] + for i in range (las.nfrags): + lwi = [] + for j in range (las.nroots): + lwij = np.zeros (lroots[i,j]) + lwij[0] = 1 + lwi.append (lwij) + lweights.append (lwi) + nao, nmo = mo_coeff.shape + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + ncas_sub = las.ncas_sub + nelecas_sub = las.nelecas_sub + orbsym = getattr (mo_coeff, 'orbsym', None) + if orbsym is not None: orbsym=orbsym[ncore:nocc] + elif isinstance (las, LASCISymm): + mo_coeff = las.label_symmetry_(mo_coeff) + orbsym = mo_coeff.orbsym[ncore:nocc] + log = lib.logger.new_logger (las, verbose) + + h1eff, energy_core = las.h1e_for_cas (mo_coeff=mo_coeff, + ncas=las.ncas, ncore=las.ncore) + h2eff = las.get_h2eff (mo_coeff) + if (ci0 is None or any ([c is None for c in ci0]) or + any ([any ([c2 is None for c2 in c1]) for c1 in ci0])): + ci0 = las.get_init_guess_ci (mo_coeff, h2eff, ci0) + eri_cas = lib.numpy_helper.unpack_tril ( + h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, + ncas, ncas)[ncore:nocc] + + e_cas = np.empty (las.nroots) + e_states = np.empty (las.nroots) + ci1 = [[None for c2 in c1] for c1 in ci0] + converged = [] + t = (lib.logger.process_clock(), lib.logger.perf_counter()) + e_lexc = [[None for i in range (las.nroots)] for j in range (las.nfrags)] + for state in range (las.nroots): + fcisolvers = [b.fcisolvers[state] for b in las.fciboxes] + ci0_i = [c[state] for c in ci0] + solver = ImpureProductStateFCISolver (fcisolvers, stdout=las.stdout, + lweights=[l[state] for l in lweights], verbose=verbose) + # TODO: better handling of CSF symmetry quantum numbers in general + for ix, s in enumerate (solver.fcisolvers): + i = sum (ncas_sub[:ix]) + j = i + ncas_sub[ix] + if orbsym is not None: s.orbsym = orbsym[i:j] + s.norb = ncas_sub[ix] + s.nelec = solver._get_nelec (s, nelecas_sub[ix]) + s.check_transformer_cache () + if _dry_run: continue + conv, e_i, ci_i = solver.kernel (h1eff, eri_cas, ncas_sub, nelecas_sub, + ecore=0, ci0=ci0_i, orbsym=orbsym, conv_tol_grad=las.conv_tol_grad, + conv_tol_self=las.conv_tol_self, max_cycle_macro=las.max_cycle_macro) + e_cas[state] = e_i + e_states[state] = e_i + energy_core + for frag, s in enumerate (solver.fcisolvers): + e_loc = np.atleast_1d (getattr (s, 'e_states', e_i)) + e_lexc[frag][state] = e_loc - e_i + for c1, c2, s, no, ne in zip (ci1, ci_i, solver.fcisolvers, ncas_sub, nelecas_sub): + ne = solver._get_nelec (s, ne) + ndeta, ndetb = [cistring.num_strings (no, n) for n in ne] + shape = [s.nroots, ndeta, ndetb] if s.nroots>1 else [ndeta, ndetb] + c1[state] = np.asarray (c2).reshape (*shape) + if not conv: log.warn ('State %d LASCI not converged!', state) + converged.append (conv) + t = log.timer ('State {} LASCI'.format (state), *t) + + e_tot = np.dot (las.weights, e_states) + return converged, e_tot, e_states, e_cas, e_lexc, ci1 + +class LASCINoSymm (casci.CASCI): + + def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, frozen=None, **kwargs): + if isinstance(ncas,int): + ncas = [ncas] + ncas_tot = sum (ncas) + nel_tot = [0, 0] + new_nelecas = [] + for ix, nel in enumerate (nelecas): + if isinstance (nel, (int, np.integer)): + nb = nel // 2 + na = nb + (nel % 2) + else: + na, nb = nel + new_nelecas.append ((na, nb)) + nel_tot[0] += na + nel_tot[1] += nb + nelecas = new_nelecas + self.nroots = 1 + super().__init__(mf, ncas=ncas_tot, nelecas=nel_tot, ncore=ncore) + self.chkfile = self._scf.chkfile + if spin_sub is None: spin_sub = [1 + abs(ne[0]-ne[1]) for ne in nelecas] + self.ncas_sub = np.asarray (ncas) + self.nelecas_sub = np.asarray (nelecas) + assert (len (self.nelecas_sub) == self.nfrags) + self.frozen = frozen + self.conv_tol_grad = 1e-4 + self.conv_tol_self = 1e-10 + self.ah_level_shift = 1e-8 + self.max_cycle_macro = 50 + self.max_cycle_micro = 5 + keys = set(('e_states', 'fciboxes', 'nroots', 'weights', 'ncas_sub', 'nelecas_sub', + 'conv_tol_grad', 'conv_tol_self', 'max_cycle_macro', 'max_cycle_micro', + 'ah_level_shift', 'states_converged', 'chkfile', 'e_lexc')) + self._keys = set(self.__dict__.keys()).union(keys) + self.fciboxes = [] + if isinstance(spin_sub,int): + self.fciboxes.append(self._init_fcibox(spin_sub,self.nelecas_sub[0])) + else: + assert (len (spin_sub) == self.nfrags) + for smult, nel in zip (spin_sub, self.nelecas_sub): + self.fciboxes.append (self._init_fcibox (smult, nel)) + self.weights = [1.0] + self.e_states = [0.0] + self.e_lexc = [[np.array ([0]),],] + + def _init_fcibox (self, smult, nel): + s = csf_solver (self.mol, smult=smult) + s.spin = nel[0] - nel[1] + return get_h1e_zipped_fcisolver (state_average_n_mix (self, [s], [1.0]).fcisolver) + + @property + def nfrags (self): return len (self.ncas_sub) + + def get_mo_slice (self, idx, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + mo = mo_coeff[:,self.ncore:] + for offs in self.ncas_sub[:idx]: + mo = mo[:,offs:] + mo = mo[:,:self.ncas_sub[idx]] + return mo + + def ao2mo (self, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + nao, nmo = mo_coeff.shape + ncore, ncas = self.ncore, self.ncas + nocc = ncore + ncas + mo_cas = mo_coeff[:,ncore:nocc] + mo = [mo_coeff, mo_cas, mo_cas, mo_cas] + if getattr (self, 'with_df', None) is not None: + # Store intermediate with one contracted ao index for faster calculation of exchange! + bPmn = sparsedf_array (self.with_df._cderi) + bmuP = bPmn.contract1 (mo_cas) + buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) + eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) + eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) + eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) + eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) + if self.verbose > lib.logger.DEBUG: + eri_comp = self.with_df.ao2mo (mo, compact=True) + lib.logger.debug(self,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) + elif getattr (self._scf, '_eri', None) is not None: + eri = ao2mo.incore.general (self._scf._eri, mo, compact=True) + else: + eri = ao2mo.outcore.general_iofree (self.mol, mo, compact=True) + if eri.shape != (nmo,ncas*ncas*(ncas+1)//2): + try: + eri = eri.reshape (nmo, ncas*ncas*(ncas+1)//2) + except ValueError as e: + assert (nmo == ncas), str (e) + eri = ao2mo.restore ('2kl', eri, nmo).reshape (nmo, ncas*ncas*(ncas+1)//2) + return eri + + def get_h2eff_slice (self, h2eff, idx, compact=None): + ncas_cum = np.cumsum ([0] + self.ncas_sub.tolist ()) + i = ncas_cum[idx] + j = ncas_cum[idx+1] + ncore = self.ncore + nocc = ncore + self.ncas + eri = h2eff[ncore:nocc,:].reshape (self.ncas*self.ncas, -1) + ix_i, ix_j = np.tril_indices (self.ncas) + eri = eri[(ix_i*self.ncas)+ix_j,:] + eri = ao2mo.restore (1, eri, self.ncas)[i:j,i:j,i:j,i:j] + if compact: eri = ao2mo.restore (compact, eri, j-i) + return eri + + get_h1eff = get_h1las = h1e_for_las = h1e_for_las + get_h2eff = ao2mo + ''' + def get_h2eff (self, mo_coeff=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + if isinstance (self, _DFLASCI): + mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] + return self.with_df.ao2mo (mo_cas) + return self.ao2mo (mo_coeff) + ''' + + get_fock = get_fock + get_grad = get_grad + get_grad_orb = get_grad_orb + get_grad_ci = get_grad_ci + _hop = lasci_sync.LASCI_HessianOperator + _kern = lasci_sync.kernel + def get_hop (self, mo_coeff=None, ci=None, ugg=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ugg is None: ugg = self.get_ugg () + return self._hop (self, ugg, mo_coeff=mo_coeff, ci=ci, **kwargs) + canonicalize = canonicalize + + def _finalize(self): + log = lib.logger.new_logger (self, self.verbose) + nroots_prt = len (self.e_states) + if self.verbose <= lib.logger.INFO: + nroots_prt = min (nroots_prt, 100) + if nroots_prt < len (self.e_states): + log.info (("Printing a maximum of 100 state energies;" + " increase self.verbose to see them all")) + if nroots_prt > 1: + log.info ("LASCI state-average energy = %.15g", self.e_tot) + for i, e in enumerate (self.e_states[:nroots_prt]): + log.info ("LASCI state %d energy = %.15g", i, e) + else: + log.info ("LASCI energy = %.15g", self.e_tot) + return + + + def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, conv_tol_grad=None, + assert_no_dupes=False, verbose=None, _kern=None): + if mo_coeff is None: + mo_coeff = self.mo_coeff + else: + self.mo_coeff = mo_coeff + if ci0 is None: ci0 = self.ci + if verbose is None: verbose = self.verbose + if conv_tol_grad is None: conv_tol_grad = self.conv_tol_grad + if _kern is None: _kern = self._kern + log = lib.logger.new_logger(self, verbose) + + if self.verbose >= lib.logger.WARN: + self.check_sanity() + self.dump_flags(log) + + for fcibox in self.fciboxes: + fcibox.verbose = self.verbose + fcibox.stdout = self.stdout + fcibox.nroots = self.nroots + fcibox.weights = self.weights + # TODO: local excitations and locally-impure states in LASSCF kernel + do_warn=False + if ci0 is not None: + for i, ci0_i in enumerate (ci0): + if ci0_i is None: continue + for j, ci0_ij in enumerate (ci0_i): + if ci0_ij is None: continue + if np.asarray (ci0_ij).ndim>2: + do_warn=True + ci0_i[j] = ci0_ij[0] + if do_warn: log.warn ("Discarding all but the first root of guess CI vectors!") + + self.converged, self.e_tot, self.e_states, self.mo_energy, self.mo_coeff, self.e_cas, \ + self.ci, h2eff_sub, veff = _kern(mo_coeff=mo_coeff, ci0=ci0, verbose=verbose, \ + casdm0_fr=casdm0_fr, conv_tol_grad=conv_tol_grad, assert_no_dupes=assert_no_dupes) + + self._finalize () + return self.e_tot, self.e_cas, self.ci, self.mo_coeff, self.mo_energy, h2eff_sub, veff + + def states_make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: + return [np.zeros ((self.nroots,2,ncas,ncas)) for ncas in ncas_sub] + casdm1s = [] + for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + if ci_i is None: + dm1a = dm1b = np.zeros ((ncas, ncas)) + else: + dm1a, dm1b = fcibox.states_make_rdm1s (ci_i, ncas, nelecas) + casdm1s.append (np.stack ([dm1a, dm1b], axis=1)) + return casdm1s + + def make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, w=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + if w is None: w = self.weights + return [np.einsum ('rspq,r->spq', dm1, w) for dm1 in casdm1frs] + + def states_make_casdm1s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + return np.stack ([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] + for dm1rs in casdm1frs]) + for ispin in (0, 1)], axis=0) + for iroot in range (self.nroots)], axis=0) + + def states_make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + casdm2 = [] + for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) + return casdm2 + + def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=None, **kwargs): + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, **kwargs) + return [np.einsum ('rijkl,r->ijkl', dm2, box.weights) + for dm2, box in zip (casdm2fr, self.fciboxes)] + + #SV casdm2s + + def make_casdm2s(self, ci=None, **kwargs): + # Make the full-dimensional casdm2s spanning the collective active space + casdm2s_sub = self.make_casdm2s_sub (ci=ci, **kwargs) + casdm2a = linalg.block_diag (*[dm[0] for dm in casdm2s_sub]) + casdm2b = linalg.block_diag (*[dm[1] for dm in casdm2s_sub]) + return np.stack ([casdm2a, casdm2b], axis=0) + + + #SV casdm2s_sub + def make_casdm2s_sub(self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2frs=None, w=None, **kwargs): + if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + if w is None: w = self.weights + print("SV w = ", w) + return [np.einsum ('rsijkl,r->sijkl', dm2, w) for dm2 in casdm2frs] + + #SV states_make_casdm2s_sub + #There are two ways to construct states_make_casdm2s_sub, one is thisi coming from states_make_casdm1s_sub + + def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + # Spin-separated 2-RDMs in the MO basis for each subspace in sequence + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: + return [np.zeros ((self.nroots,2,ncas,ncas,ncas,ncas)) for ncas in ncas_sub] + casdm2s = [] + for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + if ci_i is None: + dm2a = dm2b = np.zeros ((ncas, ncas,ncas,ncas)) + else: + dm2a, dm2b, _ = fcibox.states_make_rdm12s (ci_i, ncas, nelecas)[1] # IDK what this third ncas*4 matrix is, so just created a placeholder for it + casdm2s.append (np.stack ([dm2a, dm2b], axis=1)) + return casdm2s + + #SV states_make_casdm2s_sub + #This is the second coming from states_make_casdm2_sub. I think the above one must be the one to be used but the problem is IDK whether states_make_rdm2s exists or not, maybe states_make_rdm12s exists + ''' + def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): + #Spin-separated 1-RDMs in the MO basis for each subspace in sequence + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + casdm2s = [] + for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): + casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) + return casdm2 + ''' + + #SV states_make_casdm2s + def states_make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2frs=None, **kwargs): + if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + print ("SV self.nroots = ", self.nroots) + + for iroot in range (self.nroots): + spin_matrices = [] + for ispin in (0, 1): + selected_arrays = np.array([dm2rs[iroot][ispin]for dm2rs in casdm2frs]) + print ("SV selected_arrays = ", selected_arrays, selected_arrays.shape) + block_diag_matrix = linalg.block_diag (*selected_arrays) + spin_matrices.append(block_diag_matrix) + spin_matrices_stacked = np.stack(spin_matrices, axis=0) + print ("SV spin_matrices_stacked = ", spin_matrices_stacked) + + + return np.stack ([np.stack ([linalg.block_diag (*[dm2rs[iroot][ispin] + for dm2rs in casdm2frs]) + for ispin in (0, 1)], axis=0) + for iroot in range (self.nroots)], axis=0) + + def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, + nelecas_sub=None, casdm1rs=None, casdm1frs=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1rs is None: casdm1rs = self.states_make_casdm1s (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, + **kwargs) + mo_core = mo_coeff[:,:self.ncore] + mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] + dm1rs = np.tensordot (mo_cas.conj (), np.dot (casdm2rs, mo_cas.conj ().T), axes=((1),(2))) + dm1rs = dm1rs.transpose (1,2,0,3) + dm1rs += (mo_core @ mo_core.conj ().T)[None,None,:,:] + return dm1rs + + def make_rdm1s_sub (self, mo_coeff=None, ci=None, ncas_sub=None, + nelecas_sub=None, include_core=False, casdm1s_sub=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ''' Same as make_casdm1s_sub, but in the ao basis ''' + rdm1s = [] + for idx, casdm1s in enumerate (casdm1s_sub): + mo = self.get_mo_slice (idx, mo_coeff=mo_coeff) + moH = mo.conjugate ().T + rdm1s.append (np.tensordot (mo, np.dot (casdm1s,moH), axes=((1),(1))).transpose(1,0,2)) + if include_core and self.ncore: + mo_core = mo_coeff[:,:self.ncore] + moH_core = mo_core.conjugate ().T + dm_core = mo_core @ moH_core + rdm1s = [np.stack ([dm_core, dm_core], axis=0)] + rdm1s + rdm1s = np.stack (rdm1s, axis=0) + return rdm1s + + def make_rdm1_sub (self, **kwargs): + return self.make_rdm1s_sub (**kwargs).sum (1) + + def make_rdm1s (self, mo_coeff=None, ncore=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + mo = mo_coeff[:,:ncore] + moH = mo.conjugate ().T + dm_core = mo @ moH + dm_cas = self.make_rdm1s_sub (mo_coeff=mo_coeff, **kwargs).sum (0) + return dm_core[None,:,:] + dm_cas + + def make_rdm1 (self, mo_coeff=None, ci=None, **kwargs): + return self.make_rdm1s (mo_coeff=mo_coeff, ci=ci, **kwargs).sum (0) + + def make_casdm1s (self, ci=None, **kwargs): + ''' Make the full-dimensional casdm1s spanning the collective active space ''' + casdm1s_sub = self.make_casdm1s_sub (ci=ci, **kwargs) + casdm1a = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) + casdm1b = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) + return np.stack ([casdm1a, casdm1b], axis=0) + + def make_casdm1 (self, ci=None, **kwargs): + ''' Spin-sum make_casdm1s ''' + return self.make_casdm1s (ci=ci, **kwargs).sum (0) + + def states_make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' Make the full-dimensional casdm2 spanning the collective active space ''' + raise DeprecationWarning ( + ("states_make_casdm2 is BANNED. There is no reason to EVER make an array this huge.\n" + "Use states_make_casdm*_sub instead, and substitute the factorization into your " + "expressions.") + ) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + casdm2r = np.zeros ((self.nroots,ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2 in enumerate (casdm2fr): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2r[:, i:j, i:j, i:j, i:j] = dm2 + # Off-diagonal + for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2r): + dma1, dmb1 = dm1s1[0], dm1s1[1] + dma2, dmb2 = dm1s2[0], dm1s2[1] + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) + +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2r + + def state_make_casdm1s(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, **kwargs): + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + casdm1s = np.stack([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] + for dm1rs in casdm1frs]) + for ispin in (0, 1)], axis=0) + for iroot in range (self.nroots)], axis=0) + return casdm1s[state] + + def state_make_casdm2(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' State wise casdm2 spanning the collective active space. ''' + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2_r in enumerate (casdm2fr): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2[i:j, i:j, i:j, i:j] = dm2_r[state] + # Off-diagonal + for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1, dmb1 = dm1s1_r[state][0], dm1s1_r[state][1] + dma2, dmb2 = dm1s2_r[state][0], dm1s2_r[state][1] + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) + +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2 + + #SV make_casdm2s + def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2rs=None, casdm2fs=None, casdm1frs=None, casdm2frs=None, + **kwargs): + + if casdm2r is not None: + return np.einsum ('rijkl,r->ijkl', casdm2r, self.weights) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2fs is None: casdm2f = self.make_casdm2s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2frs=casdm2frs) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + weights = self.weights + casdm2s = np.zeros ((2,ncas,ncas,ncas,ncas)) + # Diagonal of alpha + for isub, dm2 in enumerate (casdm2fs[0]): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2s[0][i:j, i:j, i:j, i:j] = dm2 + # Diagonal of alpha + for isub, dm2 in enumerate (casdm2fs[1]): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2s[1][i:j, i:j, i:j, i:j] = dm2 + # Off-diagonal + for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] + dm1r = dma1r + dmb1r + dm2r = dma2r + dmb2r + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) + casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2 + + + def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, + casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, + **kwargs): + ''' Make the full-dimensional casdm2 spanning the collective active space ''' + if casdm2r is not None: + return np.einsum ('rijkl,r->ijkl', casdm2r, self.weights) + if ci is None: ci = self.ci + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) + if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, + ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) + ncas = sum (ncas_sub) + ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) + weights = self.weights + casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) + # Diagonal + for isub, dm2 in enumerate (casdm2f): + i = ncas_cum[isub] + j = ncas_cum[isub+1] + casdm2[i:j, i:j, i:j, i:j] = dm2 + # Off-diagonal + for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] + dm1r = dma1r + dmb1r + dm2r = dma2r + dmb2r + # Coulomb slice + casdm2[i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) + casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) + # Exchange slice + d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) + casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + return casdm2 + + def get_veff (self, mol=None, dm1s=None, hermi=1, spin_sep=False, **kwargs): + ''' Returns a spin-summed veff! If dm1s isn't provided, builds from self.mo_coeff, self.ci + etc. ''' + if mol is None: mol = self.mol + nao = mol.nao_nr () + if dm1s is None: dm1s = self.make_rdm1 (include_core=True, **kwargs).reshape (nao, nao) + dm1s = np.asarray (dm1s) + if dm1s.ndim == 2: dm1s = dm1s[None,:,:] + if isinstance (self, _DFLASCI): + vj, vk = self.with_df.get_jk(dm1s, hermi=hermi) + else: + vj, vk = self._scf.get_jk(mol, dm1s, hermi=hermi) + if spin_sep: + assert (dm1s.shape[0] == 2) + return vj.sum (0)[None,:,:] - vk + else: + veff = np.stack ([j - k/2 for j, k in zip (vj, vk)], axis=0) + return np.squeeze (veff) + + def split_veff (self, veff, h2eff_sub, mo_coeff=None, ci=None, casdm1s_sub=None): + ''' Split a spin-summed veff into alpha and beta terms using the h2eff eri array. + Note that this will omit v(up_active - down_active)^virtual_inactive by necessity; + this won't affect anything because the inactive density matrix has no spin component. + On the other hand, it ~is~ necessary to correctly do + + v(up_active - down_active)^unactive_active + + in order to calculate the external orbital gradient at the end of the calculation. + This means that I need h2eff_sub spanning both at least two active subspaces + ~and~ the full orbital range. ''' + veff_c = veff.copy () + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci = ci) + ncore = self.ncore + ncas = self.ncas + nocc = ncore + ncas + nao, nmo = mo_coeff.shape + moH_coeff = mo_coeff.conjugate ().T + smo_coeff = self._scf.get_ovlp () @ mo_coeff + smoH_coeff = smo_coeff.conjugate ().T + veff_s = np.zeros_like (veff_c) + for ix, (ncas_i, casdm1s) in enumerate (zip (self.ncas_sub, casdm1s_sub)): + i = sum (self.ncas_sub[:ix]) + j = i + ncas_i + eri_k = h2eff_sub.reshape (nmo, ncas, -1)[:,i:j,...].reshape (nmo*ncas_i, -1) + eri_k = lib.numpy_helper.unpack_tril (eri_k)[:,i:j,:] + eri_k = eri_k.reshape (nmo, ncas_i, ncas_i, ncas) + sdm = casdm1s[0] - casdm1s[1] + vk_pa = -np.tensordot (eri_k, sdm, axes=((1,2),(0,1))) / 2 + veff_s[:,ncore:nocc] += vk_pa + veff_s[ncore:nocc,:] += vk_pa.T + veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:] / 2 + veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:].T / 2 + veff_s = smo_coeff @ veff_s @ smoH_coeff + veffa = veff_c + veff_s + veffb = veff_c - veff_s + return np.stack ([veffa, veffb], axis=0) + + + def states_energy_elec (self, mo_coeff=None, ncore=None, ncas=None, + ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, + casdm1frs=None, casdm2fr=None, veff_core=None, **kwargs): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + if ncas is None: ncas = self.ncas + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: ci = self.ci + if h2eff is None: h2eff = self.get_h2eff (mo_coeff) + if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub) + if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub) + nao, nmo = mo_coeff.shape + nocc = ncore + ncas + mo_core = mo_coeff[:,:ncore] + mo_cas = mo_coeff[:,ncore:nocc] + dm_core = 2*mo_core @ mo_core.conj ().T + if veff_core is None: veff_core = getattr (veff, 'c', None) + if veff_core is None: veff_core = self.get_veff (dm1s=dm_core) + h1eff = self.get_hcore () + veff_core + e0 = 2*np.dot (((h1eff-(veff_core/2)) @ mo_core).ravel (), mo_core.conj().ravel ()) + h1eff = mo_cas.conj ().T @ h1eff @ mo_cas + eri_cas = lib.numpy_helper.unpack_tril (h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)) + eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) + eri_cas = eri_cas[ncore:nocc] + casdm1rs = self.states_make_casdm1s (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, + casdm1frs=casdm1frs) + vj_r = np.tensordot (casdm1rs.sum (1), eri_cas, axes=2) + vk_rs = np.tensordot (casdm1rs, eri_cas, axes=((2,3),(2,1))) + veff_rs = vj_r[:,None,:,:] - vk_rs + + energy_elec = [] + for idx, (dm1s, v) in enumerate (zip (casdm1rs, veff_rs)): + casdm1fs = [dm[idx] for dm in casdm1frs] + casdm2f = [dm[idx] for dm in casdm2fr] + + # 1-body veff terms + h1e = h1eff[None,:,:] + v/2 + e1 = np.dot (h1e.ravel (), dm1s.ravel ()) + + # 2-body cumulant terms + e2 = 0 + for isub, (dm1s, dm2) in enumerate (zip (casdm1fs, casdm2f)): + dm1a, dm1b = dm1s[0], dm1s[1] + dm1 = dm1a + dm1b + cdm2 = dm2 - np.multiply.outer (dm1, dm1) + cdm2 += np.multiply.outer (dm1a, dm1a).transpose (0,3,2,1) + cdm2 += np.multiply.outer (dm1b, dm1b).transpose (0,3,2,1) + eri = self.get_h2eff_slice (h2eff, isub) + te2 = np.tensordot (eri, cdm2, axes=4) / 2 + e2 += te2 + energy_elec.append (e0 + e1 + e2) + self._e1_ref = e0 + e1 + self._e2_ref = e2 + + return energy_elec + + def energy_elec (self, mo_coeff=None, ncore=None, ncas=None, + ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, + casdm1frs=None, casdm2fr=None, **kwargs): + ''' Since the LASCI energy cannot be calculated as simply as ecas + ecore, I need this ''' + if mo_coeff is None: mo_coeff = self.mo_coeff + if ncore is None: ncore = self.ncore + if ncas is None: ncas = self.ncas + if ncas_sub is None: ncas_sub = self.ncas_sub + if nelecas_sub is None: nelecas_sub = self.nelecas_sub + if ci is None: ci = self.ci + if h2eff is None: h2eff = self.get_h2eff (mo_coeff) + casdm1s_sub = self.make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, + casdm1frs=casdm1frs) + if veff is None: + veff = self.get_veff (dm1s = self.make_rdm1(mo_coeff=mo_coeff,casdm1s_sub=casdm1s_sub)) + veff = self.split_veff (veff, h2eff, mo_coeff=mo_coeff, casdm1s_sub=casdm1s_sub) + + # 1-body veff terms + h1e = self.get_hcore ()[None,:,:] + veff/2 + dm1s = self.make_rdm1s (mo_coeff=mo_coeff, ncore=ncore, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1s_sub=casdm1s_sub) + e1 = np.dot (h1e.ravel (), dm1s.ravel ()) + + # 2-body cumulant terms + casdm1s = self.make_casdm1s (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1frs=casdm1frs) + casdm1 = casdm1s.sum (0) + casdm2 = self.make_casdm2 (ci=ci, ncas_sub=ncas_sub, + nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, casdm2fr=casdm2fr) + casdm2 -= np.multiply.outer (casdm1, casdm1) + casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) + casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) + ncore, ncas, nocc = self.ncore, self.ncas, self.ncore + self.ncas + eri = lib.numpy_helper.unpack_tril (h2eff[ncore:nocc].reshape (ncas*ncas, -1)) + eri = eri.reshape ([ncas,]*4) + e2 = np.tensordot (eri, casdm2, axes=4)/2 + + e0 = self.energy_nuc () + self._e1_test = e1 + self._e2_test = e2 + return e1 + e2 + + _ugg = lasci_sync.LASCI_UnitaryGroupGenerators + def get_ugg (self, mo_coeff=None, ci=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + return self._ugg (self, mo_coeff, ci) + + def cderi_ao2mo (self, mo_i, mo_j, compact=False): + assert (isinstance (self, _DFLASCI)) + nmo_i, nmo_j = mo_i.shape[-1], mo_j.shape[-1] + if compact: + assert (nmo_i == nmo_j) + bPij = np.empty ((self.with_df.get_naoaux (), nmo_i*(nmo_i+1)//2), dtype=mo_i.dtype) + else: + bPij = np.empty ((self.with_df.get_naoaux (), nmo_i, nmo_j), dtype=mo_i.dtype) + ijmosym, mij_pair, moij, ijslice = ao2mo.incore._conc_mos (mo_i, mo_j, compact=compact) + b0 = 0 + for eri1 in self.with_df.loop (): + b1 = b0 + eri1.shape[0] + eri2 = bPij[b0:b1] + eri2 = ao2mo._ao2mo.nr_e2 (eri1, moij, ijslice, aosym='s2', mosym=ijmosym, out=eri2) + b0 = b1 + return bPij + + def fast_veffa (self, casdm1s_sub, h2eff_sub, mo_coeff=None, ci=None, _full=False): + if mo_coeff is None: mo_coeff = self.mo_coeff + if ci is None: ci = self.ci + assert (isinstance (self, _DFLASCI) or _full) + ncore = self.ncore + ncas_sub = self.ncas_sub + ncas = sum (ncas_sub) + nocc = ncore + ncas + nao, nmo = mo_coeff.shape + + mo_cas = mo_coeff[:,ncore:nocc] + moH_cas = mo_cas.conjugate ().T + moH_coeff = mo_coeff.conjugate ().T + dma = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) + dmb = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) + casdm1s = np.stack ([dma, dmb], axis=0) + if not (isinstance (self, _DFLASCI)): + dm1s = np.dot (mo_cas, np.dot (casdm1s, moH_cas)).transpose (1,0,2) + return self.get_veff (dm1s = dm1s, spin_sep=True) + casdm1 = casdm1s.sum (0) + dm1 = np.dot (mo_cas, np.dot (casdm1, moH_cas)) + bPmn = sparsedf_array (self.with_df._cderi) + + # vj + dm_tril = dm1 + dm1.T - np.diag (np.diag (dm1.T)) + rho = np.dot (bPmn, lib.pack_tril (dm_tril)) + vj = lib.unpack_tril (np.dot (rho, bPmn)) + + # vk + bmPu = h2eff_sub.bmPu + if _full: + vmPsu = np.dot (bmPu, casdm1s) + vk = np.tensordot (vmPsu, bmPu, axes=((1,3),(1,2))).transpose (1,0,2) + return vj[None,:,:] - vk + else: + vmPu = np.dot (bmPu, casdm1) + vk = np.tensordot (vmPu, bmPu, axes=((1,2),(1,2))) + return vj - vk/2 + + @lib.with_doc(run_lasci.__doc__) + def lasci (self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, + assert_no_dupes=False, _dry_run=False): + if mo_coeff is None: mo_coeff=self.mo_coeff + if ci0 is None: ci0 = self.ci + if verbose is None: verbose = self.verbose + converged, e_tot, e_states, e_cas, e_lexc, ci = run_lasci ( + self, mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, + verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) + if _dry_run: return + self.converged, self.ci = converged, ci + self.e_tot, self.e_states, self.e_cas, self.e_lexc = e_tot, e_states, e_cas, e_lexc + if mo_coeff is self.mo_coeff: + self.dump_chk () + elif getattr (self, 'chkfile', None) is not None: + lib.logger.warn (self, 'orbitals changed; chkfile not dumped!') + self._finalize () + return self.converged, self.e_tot, self.e_states, self.e_cas, e_lexc, self.ci + + @lib.with_doc(run_lasci.__doc__) + def lasci_(self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, + assert_no_dupes=False, _dry_run=False): + if mo_coeff is not None: + self.mo_coeff = mo_coeff + return self.lasci (mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, + verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) + + state_average = state_average + state_average_ = state_average_ + get_single_state_las = get_single_state_las + + def lassi(self, mo_coeff=None, ci=None, veff_c=None, h2eff_sub=None, orbsym=None, + soc=False, break_symmetry=False, opt=1, **kwargs): + #import warnings + #lassi_kernel_warn = "Now LASSI have kernel, which takes las instance as input. This [las.lassi()] function " \ + # "will be removed soon." + #warnings.warn(lassi_kernel_warn, stacklevel=3) + from mrh.my_pyscf.lassi import lassi + mylassi = lassi.LASSI(self, mo_coeff=mo_coeff, ci=ci, soc=soc, opt=opt, + break_symmetry=break_symmetry, **kwargs) + return mylassi.kernel(mo_coeff=mo_coeff, ci=ci, veff_c=veff_c, h2eff_sub=h2eff_sub, + orbsym=orbsym) + + las2cas_civec = las2cas_civec + assert_no_duplicates = assert_no_duplicates + get_init_guess_ci = get_init_guess_ci + localize_init_guess=lasscf_guess.localize_init_guess + def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): + if s is None: s = self._scf.get_ovlp () + return matrix_svd_control_options (s, lspace=mo_lspace, rspace=mo_rspace, full_matrices=True)[:3] + + def dump_flags (self, verbose=None, _method_name='LASCI'): + log = lib.logger.new_logger (self, verbose) + log.info ('') + log.info ('******** %s flags ********', _method_name) + ncore = self.ncore + ncas = self.ncas + nvir = self.mo_coeff.shape[1] - ncore - ncas + nfrags = len (self.nelecas_sub) + log.info ('CAS (%de+%de, %do), ncore = %d, nvir = %d', + self.nelecas[0], self.nelecas[1], ncas, ncore, nvir) + log.info ('Divided into %d LAS spaces', nfrags) + for i, (no, ne) in enumerate (zip (self.ncas_sub, self.nelecas_sub)): + log.info ('LAS %d : (%de+%de, %do)', i, ne[0], ne[1], no) + log.info ('nroots = %d', self.nroots) + log.info ('max_memory %d (MB)', self.max_memory) + for i, fcibox in enumerate (self.fciboxes): + if getattr (fcibox, 'dump_flags', None): + log.info ('fragment %d FCI solver flags:', i) + fcibox.dump_flags (log.verbose) + + @property + def converged (self): + return all (self.states_converged) + @converged.setter + def converged (self, x): + if hasattr (x, '__len__'): + self.states_converged = list (x) + else: + self.states_converged = [x,]*self.nroots + + def dump_spaces (self, nroots=None, sort_energy=False): + log = lib.logger.new_logger (self, self.verbose) + log.info ("******** LAS space tables ********") + ci = self.ci + if nroots is None and self.verbose <= lib.logger.INFO: + nroots = min (self.nroots, 100) + elif nroots is None: + nroots = self.nroots + if nroots < self.nroots: + log.warn ("Dumping only 100 of %d spaces", self.nroots) + log.warn ("To see more, explicitly pass nroots to dump_spaces or increase verbosity") + if sort_energy: + idx = np.argsort (self.e_states) + else: + idx = range (nroots) + for state in idx: + neleca_f = [] + nelecb_f = [] + wfnsym_f = [] + wfnsym = 0 + m_f = [] + s_f = [] + lroots = [] + s2_tot = 0 + for ifrag, (fcibox, nelecas) in enumerate (zip (self.fciboxes, self.nelecas_sub)): + solver = fcibox.fcisolvers[state] + na, nb = _unpack_nelec (fcibox._get_nelec (solver, nelecas)) + neleca_f.append (na) + nelecb_f.append (nb) + m_f.append ((na-nb)/2) + s_f.append ((solver.smult-1)/2) + s2_tot += s_f[-1] * (s_f[-1] + 1) + fragsym = getattr (solver, 'wfnsym', 0) or 0 + if isinstance (fragsym, str): + fragsym_str = fragsym + fragsym_id = symm.irrep_name2id (solver.mol.groupname, fragsym) + else: + fragsym_id = fragsym + fragsym_str = symm.irrep_id2name (solver.mol.groupname, fragsym) + wfnsym ^= fragsym_id + wfnsym_f.append (fragsym_str) + lroots_i = 0 + if ci is not None: + if ci[ifrag] is not None: + ci_i = ci[ifrag] + if ci_i[state] is not None: + ci_ij = ci_i[state] + lroots_i = 1 if ci_ij.ndim<3 else ci_ij.shape[0] + lroots.append (lroots_i) + s2_tot += sum ([2*m1*m2 for m1, m2 in combinations (m_f, 2)]) + s_f, m_f = np.asarray (s_f), np.asarray (m_f) + if np.all (m_f<0): m_f *= -1 + s_pure = bool (np.all (s_f==m_f)) + wfnsym = symm.irrep_id2name (self.mol.groupname, wfnsym) + neleca = sum (neleca_f) + nelecb = sum (nelecb_f) + log.info ("LAS space %d: (%de+%de,%do) wfynsm=%s", state, neleca, nelecb, self.ncas, wfnsym) + log.info ("Converged? %s", self.states_converged[state]) + log.info ("E(LAS) = %.15g", self.e_states[state]) + log.info ("S^2 = %.7f (%s)", s2_tot, ('Impure','Pure')[s_pure]) + log.info ("Space table") + log.info (" frag (ae+be,no) 2S+1 ir lroots") + for i in range (self.nfrags): + smult_f = int (round (2*s_f[i] + 1)) + tupstr = '({}e+{}e,{}o)'.format (neleca_f[i], nelecb_f[i], self.ncas_sub[i]) + log.info (" %4d %13s %4d %3s %6d", i, tupstr, smult_f, wfnsym_f[i], lroots[i]) + + def check_sanity (self): + casci.CASCI.check_sanity (self) + self.get_ugg () # constructor encounters impossible states and raises error + + dump_chk = chkfile.dump_las + load_chk = load_chk_ = chkfile.load_las_ + +class LASCISymm (casci_symm.CASCI, LASCINoSymm): + + def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, wfnsym_sub=None, frozen=None, + **kwargs): + LASCINoSymm.__init__(self, mf, ncas, nelecas, ncore=ncore, spin_sub=spin_sub, + frozen=frozen, **kwargs) + if getattr (self.mol, 'groupname', None) in ('Dooh', 'Coov'): + raise NotImplementedError ("LASSCF support for cylindrical point group {}".format ( + self.mol.groupname)) + if wfnsym_sub is None: wfnsym_sub = [0 for icas in self.ncas_sub] + # TODO: guess wfnsym_sub intelligently (0 can be impossible for some multiplicities) + for wfnsym, frag in zip (wfnsym_sub, self.fciboxes): + if isinstance (wfnsym, (str, np.str_)): + wfnsym = symm.irrep_name2id (self.mol.groupname, wfnsym) + frag.fcisolvers[0].wfnsym = wfnsym + + make_rdm1s = LASCINoSymm.make_rdm1s + make_rdm1 = LASCINoSymm.make_rdm1 + get_veff = LASCINoSymm.get_veff + get_h1eff = get_h1las = h1e_for_las + dump_flags = LASCINoSymm.dump_flags + dump_spaces = LASCINoSymm.dump_spaces + check_sanity = LASCINoSymm.check_sanity + _ugg = lasci_sync.LASCISymm_UnitaryGroupGenerators + + @property + def wfnsym (self): + ''' This now returns the product of the irreps of the subspaces ''' + wfnsym = [0,]*self.nroots + for frag in self.fciboxes: + for state, solver in enumerate (frag.fcisolvers): + wfnsym[state] ^= solver.wfnsym + if self.nroots == 1: wfnsym = wfnsym[0] + return wfnsym + @wfnsym.setter + def wfnsym (self, ir): + raise RuntimeError (("Cannot assign the whole-system symmetry of a LASCI wave function. " + "Address fciboxes[ifrag].fcisolvers[istate].wfnsym instead.")) + + def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, verbose=None, assert_no_dupes=False): + if mo_coeff is None: + mo_coeff = self.mo_coeff + if ci0 is None: + ci0 = self.ci + + # Initialize/overwrite mo_coeff.orbsym. Don't pass ci0 because it's not the right shape + lib.logger.info (self, ("LASCI lazy hack note: lines below reflect the point-group " + "symmetry of the whole molecule but not of the individual " + "subspaces")) + mo_coeff = self.mo_coeff = self.label_symmetry_(mo_coeff) + return LASCINoSymm.kernel(self, mo_coeff=mo_coeff, ci0=ci0, + casdm0_fr=casdm0_fr, verbose=verbose, assert_no_dupes=assert_no_dupes) + + def canonicalize (self, mo_coeff=None, ci=None, natorb_casdm1=None, veff=None, h2eff_sub=None): + if mo_coeff is None: mo_coeff = self.mo_coeff + mo_coeff = self.label_symmetry_(mo_coeff) + return canonicalize (self, mo_coeff=mo_coeff, ci=ci, natorb_casdm1=natorb_casdm1, + h2eff_sub=h2eff_sub, orbsym=mo_coeff.orbsym) + + def label_symmetry_(self, mo_coeff=None): + if mo_coeff is None: mo_coeff=self.mo_coeff + ncore = self.ncore + ncas_sub = self.ncas_sub + nocc = ncore + sum (ncas_sub) + mo_coeff[:,:ncore] = symm.symmetrize_space (self.mol, mo_coeff[:,:ncore]) + for isub, ncas in enumerate (ncas_sub): + i = ncore + sum (ncas_sub[:isub]) + j = i + ncas + mo_coeff[:,i:j] = symm.symmetrize_space (self.mol, mo_coeff[:,i:j]) + mo_coeff[:,nocc:] = symm.symmetrize_space (self.mol, mo_coeff[:,nocc:]) + orbsym = symm.label_orb_symm (self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_coeff, + s=self._scf.get_ovlp ()) + mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) + return mo_coeff + + @lib.with_doc(LASCINoSymm.localize_init_guess.__doc__) + def localize_init_guess (self, frags_atoms, mo_coeff=None, spin=None, lo_coeff=None, fock=None, + freeze_cas_spaces=False): + if mo_coeff is None: + mo_coeff = self.mo_coeff + mo_coeff = casci_symm.label_symmetry_(self, mo_coeff) + return LASCINoSymm.localize_init_guess (self, frags_atoms, mo_coeff=mo_coeff, spin=spin, + lo_coeff=lo_coeff, fock=fock, freeze_cas_spaces=freeze_cas_spaces) + + def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): + if s is None: s = self._scf.get_ovlp () + lsymm = getattr (mo_lspace, 'orbsym', None) + if lsymm is None: + mo_lspace = symm.symmetrize_space (self.mol, mo_lspace) + lsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_lspace, s=s) + rsymm = getattr (mo_rspace, 'orbsym', None) + if rsymm is None: + mo_rspace = symm.symmetrize_space (self.mol, mo_rspace) + rsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, + self.mol.symm_orb, mo_rspace, s=s) + decomp = matrix_svd_control_options (s, + lspace=mo_lspace, rspace=mo_rspace, + lspace_symmetry=lsymm, rspace_symmetry=rsymm, + full_matrices=True, strong_symm=True) + mo_lvecs, svals, mo_rvecs, lsymm, rsymm = decomp + mo_lvecs = lib.tag_array (mo_lvecs, orbsym=lsymm) + mo_rvecs = lib.tag_array (mo_rvecs, orbsym=rsymm) + return mo_lvecs, svals, mo_rvecs + From 7b6d9bc5dcbe52c04cebca94cc830eb956a82f63 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Fri, 24 May 2024 19:24:19 -0500 Subject: [PATCH 34/87] tested 3-rdm for 3, 4 fragments; different spin test remaining --- my_pyscf/mcscf/lasci.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 0877885f..36f19dd7 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1435,6 +1435,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 # Off-diagonal + from itertools import permutations for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): i = ncas_cum[isub1] j = ncas_cum[isub1+1] @@ -1477,6 +1478,34 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (4,5,2,3,0,1) casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,4,5,0,1) casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,0,1,4,5) + + # Term 4- 62d + d4sigma = (lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dma1r,dma2r,dm3r)+lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dmb1r,dmb2r,dm3r)) + casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= np.tensordot(weights, d4sigma, axes=1) + casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) + casdm3[m:n, m:n, i:j, k:l, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,0,1,2,3) + casdm3[m:n, m:n, k:l, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,2,3,0,1) + casdm3[k:l, i:j, m:n, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,4,5,0,1) + casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) + + # Term 5- 62e + d5sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) + casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot(weights, d5sigma, axes=1) + casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) + casdm3[m:n, i:j, i:j, m:n, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,0,1,2,3) + casdm3[m:n, i:j, k:l, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,2,3,0,1) + casdm3[k:l, k:l, m:n, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,4,5,0,1) + casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) + + # Term 6- 62f + d6sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dmb3r)) + casdm3[i:j, k:l, k:l, m:n, m:n, i:j] += np.tensordot(weights, d6sigma, axes=1) + casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (0,1,4,5,2,3) + casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (4,5,0,1,2,3) + casdm3[m:n, i:j, k:l, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (4,5,2,3,0,1) + casdm3[k:l, m:n, m:n, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (2,3,4,5,0,1) + casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (2,3,0,1,4,5) + #Last 2 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 From 0d3848d124918dc1b69a064c3a1974734664fc90 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Fri, 24 May 2024 21:09:22 -0500 Subject: [PATCH 35/87] tested 2,3,4 fragments with different spins --- my_pyscf/mcscf/lasci.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 36f19dd7..c5db1dfb 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1435,6 +1435,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 # Off-diagonal + #Last 2 terms - combs of f1+f2+f3: dm1rs1=f1, dm1rs2=f2, dm1rs3=f3 from itertools import permutations for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): i = ncas_cum[isub1] @@ -1449,10 +1450,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, dm1r = dma1r + dmb1r dm2r = dma2r + dmb2r dm3r = dma3r + dmb3r - #print ("SV dm1r, dm2r = ", dm1rs1, dma2r) - print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) - # Term 1- 62a + # Term 1- 30a d1sigma = lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm2r,dm3r) casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot(weights, d1sigma, axes=1)#lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) @@ -1461,7 +1460,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) - # Term 2- 62b + # Term 2- 30b d2sigma = (lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb2r,dmb3r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot(weights, d2sigma, axes=1) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) @@ -1470,7 +1469,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) - # Term 3- 62c + # Term 3- 30c d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dmb1r,dmb2r,dmb3r)) casdm3[i:j, m:n, k:l, i:j, m:n, k:l] += np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (0,1,4,5,2,3) @@ -1479,7 +1478,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,4,5,0,1) casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,0,1,4,5) - # Term 4- 62d + # Term 4- 30d d4sigma = (lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dma1r,dma2r,dm3r)+lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dmb1r,dmb2r,dm3r)) casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= np.tensordot(weights, d4sigma, axes=1) casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) @@ -1488,7 +1487,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, i:j, m:n, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,4,5,0,1) casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) - # Term 5- 62e + # Term 5- 30e d5sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot(weights, d5sigma, axes=1) casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) @@ -1497,7 +1496,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, m:n, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,4,5,0,1) casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) - # Term 6- 62f + # Term 6- 30f d6sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dmb3r)) casdm3[i:j, k:l, k:l, m:n, m:n, i:j] += np.tensordot(weights, d6sigma, axes=1) casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (0,1,4,5,2,3) @@ -1506,8 +1505,6 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, m:n, m:n, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (2,3,4,5,0,1) casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (2,3,0,1,4,5) - - #Last 2 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 for (isub1, dm2rs2), (isub2, dm1rs1) in product(enumerate(casdm2frs), enumerate(casdm1frs)): @@ -1524,12 +1521,11 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, print ("SV ijklmn = ", i,j,k,l,m,n) dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] dm1r = dma1r + dmb1r - #print ("SV dm1rs1 = ", dm1rs1, dm1rs1[:,0], dm1rs1[:,1]) dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] print ("SV dm1r = ", dm1rs1, dm1r) print ("SV dm2rs1 = ", dm2rs2, dmaa2r) - # Term 4 - 62d + # Term 4 - 30g d4sigma = (lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) print ("SV d4sigma = ", d4sigma) casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot (weights, d4sigma, axes=1) @@ -1539,7 +1535,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, #casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) #casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) - # Term 5 - 62e + # Term 5 - 30h d5sigma = (lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmbb2r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot (weights, d5sigma, axes=1) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) From 2ec0aa461c0c67566f3ccd529700257a69b7cc84 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Fri, 24 May 2024 21:19:09 -0500 Subject: [PATCH 36/87] tested 2,3,4 fragments with different spins with cleaned up code --- my_pyscf/mcscf/lasci.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index c5db1dfb..80a12240 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1056,8 +1056,6 @@ def make_casdm2s_sub(self, ci=None, ncas_sub=None, nelecas_sub=None, return [np.einsum ('rsijkl,r->sijkl', dm2, w) for dm2 in casdm2frs] #SV states_make_casdm2s_sub - #There are two ways to construct states_make_casdm2s_sub, one is thisi coming from states_make_casdm1s_sub - def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): # Spin-separated 2-RDMs in the MO basis for each subspace in sequence if ci is None: ci = self.ci @@ -1074,42 +1072,25 @@ def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **k casdm2s.append (np.stack ([dm2aa, dm2ab, dm2bb], axis=1)) return casdm2s - #SV states_make_casdm2s_sub - #This is the second coming from states_make_casdm2_sub. I think the above one must be the one to be used but the problem is IDK whether states_make_rdm2s exists or not, maybe states_make_rdm12s exists - ''' - def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - #Spin-separated 1-RDMs in the MO basis for each subspace in sequence - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - casdm2s = [] - for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) - return casdm2 - ''' - #SV states_make_casdm2s def states_make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm1frs=None, casdm2fr=None, casdm2frs=None, **kwargs): ''' Make the full-dimensional casdm2s spanning the collective active space ''' - print ("SV entering states_make_casdm2s") if ci is None: ci = self.ci if ncas_sub is None: ncas_sub = self.ncas_sub if nelecas_sub is None: nelecas_sub = self.nelecas_sub if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - print ("SV shape of casdm2fr = ", casdm2fr) if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - print ("SV casdm2frs =", casdm2frs, "and ", casdm2frs[0] ) # casdm2frs[i] -- [0]=faa, [1]=fab, [2]=fbb for fragment i ncas = sum (ncas_sub) ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) casdm2rs = np.zeros ((self.nroots,3,ncas,ncas,ncas,ncas)) for isub, dm2 in enumerate (casdm2frs): - print ("SV dm2 = ", dm2, dm2.shape, isub, dm2[:,1]) # dm2 = root,spin,ncas,ncas,ncas,ncas | dm2[:,x] = x spin part of ncas,ncas,ncas,ncas + # print ("SV dm2 = ", dm2, dm2.shape, isub, dm2[:,1]) # dm2 = root,spin,ncas,ncas,ncas,ncas | dm2[:,x] = x spin part of ncas,ncas,ncas,ncas i = ncas_cum[isub] j = ncas_cum[isub+1] for spin in [0,1,2]:#0=aa, 1=ab, 2=bb @@ -1139,7 +1120,7 @@ def make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3fr=No if casdm3fr is None: casdm3fr = self.states_make_casdm3_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) for dm3, box in zip(casdm3fr, self.fciboxes): - print ("SV dm3, box = ", dm3, box.weights) + #print ("SV dm3, box = ", dm3, box.weights) casdm3_sub = np.einsum('rijklmn,r->ijklmn', dm3, box.weights) #casdm3_sub = np.einsum('rijklmn,r->ijklpq', dm3, box.weights) return casdm3_sub From c4e8f3921c9043b823a5bf9df177de19196517a7 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Fri, 24 May 2024 23:37:16 -0500 Subject: [PATCH 37/87] adding unit test for LASSCF 3-RDMs --- tests/lasscf_3rdm/test_lasscf_3rdm.py | 98 +++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/lasscf_3rdm/test_lasscf_3rdm.py diff --git a/tests/lasscf_3rdm/test_lasscf_3rdm.py b/tests/lasscf_3rdm/test_lasscf_3rdm.py new file mode 100644 index 00000000..b41b31a5 --- /dev/null +++ b/tests/lasscf_3rdm/test_lasscf_3rdm.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# +# Author: Shreya Verma + +# The following tests are broken down into a couple of different categories. +# 1. Check accuracy of LASSCF analytical gradients with single fragment to CASSCF gradients for a diatomic molecule. +# 2. Check the implemmentation as scanner object. + +import unittest + +from pyscf import scf, gto, df, lib, fci +from pyscf import mcscf +from mrh.my_pyscf.mcscf.lasscf_o0 import LASSCF +from mrh.my_pyscf.mcscf.addons import las2cas_civec +import numpy as np + +def generate_geom (natom = 8, dist = 1.0): + """Generate H chain of natoms each with dist distance linearly""" + coords = [] + for i in range(natom): + coords.append(f"H 0.0 0.0 {i*dist}") + return "\n".join(coords) + +def generate_ci_rdm123 (ci,nelec): + nelec = nelec[0][0]+nelec[0][1] + rdm1_fci, rdm2_fci, rdm3_fci = fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci,ci, nelec, nelec) + rdm1_no, rdm2_no, rdm3_no = fci.rdm.reorder_dm123 (rdm1_fci, rdm2_fci, rdm3_fci) #normal ordered RDMs + return rdm3_no + +def mult_frags(nelesub, norbsub, charge=None, spin_sub=None, frag_atom_list=None, density_fit=False): + """Used for checking systems with more than three LAS fragments to see if the 3-rdms are generated properly, here for H8 with 4 fragments.""" + xyz = generate_geom (natom = 8, dist = 1.0) + mol = gto.M(atom=xyz, basis='sto-3g', charge=charge, verbose=0) + mf = scf.RHF(mol).run() + + if spin_sub is None: + spin_sub = (1,1,1,1) + else: + spin_sub = spin_sub + + if frag_atom_list is None: frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) + + las = LASSCF (mf, nelesub, norbsub, spin_sub=spin_sub) + mo_loc = las.localize_init_guess (frag_atom_list, mf.mo_coeff) + las.kernel(mo_loc) + + rdm3_las = las.make_casdm3() + + ci, nelec = las2cas_civec(las) + rdm3_no = generate_ci_rdm123(ci,nelec) + + return rdm3_las, rdm3_no + +class KnownValues(unittest.TestCase): + + def test_rdm3_h8_sto3g_spin1(self): + """Spin cas 1""" + spin_sub = (1,1,1,1) + frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) + nelesub = (2,2,2,2) + norbsub = (2,2,2,2) + rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=None, density_fit=False) + + self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) + + def test_rdm3_h8_sto3g_spin2(self): + """Spin cas 2""" + spin_sub = (1,3,1,3) + frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) + nelesub = (2,2,2,2) + norbsub = (2,2,2,2) + rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=None, density_fit=False) + + self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) + + def test_rdm3_h8_sto3g_spin3(self): + """Spin cas 3""" + spin_sub = (1,3,3,3) + frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) + nelesub = (2,2,2,2) + norbsub = (2,2,2,2) + rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=None, density_fit=False) + + self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) + + def test_rdm3_h8_sto3g_spin4(self): + """Spin cas 4""" + spin_sub = (3,3,3,3) + frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) + nelesub = (2,2,2,2) + norbsub = (2,2,2,2) + rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=None, density_fit=False) + + self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) + +if __name__ == "__main__": + print("Full Tests for LASSCF 3-RDMs") + unittest.main() From 93a764d30f827e748d170d7574d6670494b2b73e Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Fri, 24 May 2024 23:41:47 -0500 Subject: [PATCH 38/87] Modified the description --- tests/lasscf_3rdm/test_lasscf_3rdm.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/lasscf_3rdm/test_lasscf_3rdm.py b/tests/lasscf_3rdm/test_lasscf_3rdm.py index b41b31a5..1d1faa43 100644 --- a/tests/lasscf_3rdm/test_lasscf_3rdm.py +++ b/tests/lasscf_3rdm/test_lasscf_3rdm.py @@ -2,9 +2,8 @@ # # Author: Shreya Verma -# The following tests are broken down into a couple of different categories. -# 1. Check accuracy of LASSCF analytical gradients with single fragment to CASSCF gradients for a diatomic molecule. -# 2. Check the implemmentation as scanner object. +# The following test is performed for different spin states: +# 1. Check accuracy of LASSCF 3-RDMs against 3-RDMs from exact ci-vectors for H8 with 4 fragments. import unittest From 491775f1c9a11a25c5264433d99e4ff3e571895f Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Sat, 25 May 2024 01:34:50 -0500 Subject: [PATCH 39/87] correction --- tests/lasscf_3rdm/test_lasscf_3rdm.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/lasscf_3rdm/test_lasscf_3rdm.py b/tests/lasscf_3rdm/test_lasscf_3rdm.py index 1d1faa43..4f8cf342 100644 --- a/tests/lasscf_3rdm/test_lasscf_3rdm.py +++ b/tests/lasscf_3rdm/test_lasscf_3rdm.py @@ -2,8 +2,9 @@ # # Author: Shreya Verma -# The following test is performed for different spin states: -# 1. Check accuracy of LASSCF 3-RDMs against 3-RDMs from exact ci-vectors for H8 with 4 fragments. +# The following tests are broken down into a couple of different categories. +# 1. Check accuracy of LASSCF analytical gradients with single fragment to CASSCF gradients for a diatomic molecule. +# 2. Check the implemmentation as scanner object. import unittest @@ -53,7 +54,7 @@ def mult_frags(nelesub, norbsub, charge=None, spin_sub=None, frag_atom_list=None class KnownValues(unittest.TestCase): def test_rdm3_h8_sto3g_spin1(self): - """Spin cas 1""" + """Spin case 1""" spin_sub = (1,1,1,1) frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) @@ -63,27 +64,27 @@ def test_rdm3_h8_sto3g_spin1(self): self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) def test_rdm3_h8_sto3g_spin2(self): - """Spin cas 2""" + """Spin case 2""" spin_sub = (1,3,1,3) frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) norbsub = (2,2,2,2) - rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=None, density_fit=False) + rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=spin_sub, density_fit=False) self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) def test_rdm3_h8_sto3g_spin3(self): - """Spin cas 3""" + """Spin case 3""" spin_sub = (1,3,3,3) frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) norbsub = (2,2,2,2) - rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=None, density_fit=False) + rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=spin_sub, density_fit=False) self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) def test_rdm3_h8_sto3g_spin4(self): - """Spin cas 4""" + """Spin case 4""" spin_sub = (3,3,3,3) frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) From 077bf5bcf5e6f54284f29ffd14c9d07f970c8375 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Sat, 25 May 2024 01:37:07 -0500 Subject: [PATCH 40/87] correction --- tests/lasscf_3rdm/test_lasscf_3rdm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lasscf_3rdm/test_lasscf_3rdm.py b/tests/lasscf_3rdm/test_lasscf_3rdm.py index 4f8cf342..62230c62 100644 --- a/tests/lasscf_3rdm/test_lasscf_3rdm.py +++ b/tests/lasscf_3rdm/test_lasscf_3rdm.py @@ -89,7 +89,7 @@ def test_rdm3_h8_sto3g_spin4(self): frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) norbsub = (2,2,2,2) - rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=None, density_fit=False) + rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=spin_sub, density_fit=False) self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) From f6c40ef866836d6af08d2e5fc1bf48be6ab616d5 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Sat, 25 May 2024 17:28:30 -0500 Subject: [PATCH 41/87] correction --- tests/lasscf_3rdm/test_lasscf_3rdm.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/lasscf_3rdm/test_lasscf_3rdm.py b/tests/lasscf_3rdm/test_lasscf_3rdm.py index 62230c62..93e78ae0 100644 --- a/tests/lasscf_3rdm/test_lasscf_3rdm.py +++ b/tests/lasscf_3rdm/test_lasscf_3rdm.py @@ -2,9 +2,9 @@ # # Author: Shreya Verma -# The following tests are broken down into a couple of different categories. -# 1. Check accuracy of LASSCF analytical gradients with single fragment to CASSCF gradients for a diatomic molecule. -# 2. Check the implemmentation as scanner object. +# The tests are performed for the following purposes: +# 1. Check accuracy of LASSCF 3-RDMs against 3-RDMs created by LAS ci vectors using the PySCF rdm generator- only for H8 with 4 fragments +# 2. Check the same for different sub-spins of LASSCF fragments import unittest @@ -54,44 +54,44 @@ def mult_frags(nelesub, norbsub, charge=None, spin_sub=None, frag_atom_list=None class KnownValues(unittest.TestCase): def test_rdm3_h8_sto3g_spin1(self): - """Spin case 1""" + """Spin cas 1""" spin_sub = (1,1,1,1) frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) norbsub = (2,2,2,2) - rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=None, density_fit=False) + rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=spin_sub, density_fit=False) - self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) + self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_no), 12) def test_rdm3_h8_sto3g_spin2(self): - """Spin case 2""" + """Spin cas 2""" spin_sub = (1,3,1,3) frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) norbsub = (2,2,2,2) rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=spin_sub, density_fit=False) - self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) + self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_no), 12) def test_rdm3_h8_sto3g_spin3(self): - """Spin case 3""" + """Spin cas 3""" spin_sub = (1,3,3,3) frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) norbsub = (2,2,2,2) rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=spin_sub, density_fit=False) - self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) + self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_no), 12) def test_rdm3_h8_sto3g_spin4(self): - """Spin case 4""" + """Spin cas 4""" spin_sub = (3,3,3,3) frag_atom_list = ((0, 1), (2, 3), (4, 5), (6,7)) nelesub = (2,2,2,2) norbsub = (2,2,2,2) rdm3_las, rdm3_no = mult_frags(nelesub, norbsub, charge=None, spin_sub=spin_sub, density_fit=False) - self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_las), 12) + self.assertAlmostEqual (lib.fp (rdm3_las), lib.fp (rdm3_no), 12) if __name__ == "__main__": print("Full Tests for LASSCF 3-RDMs") From b506e260c95f55c6b77918f89b2f6932bb785431 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Sat, 25 May 2024 17:34:37 -0500 Subject: [PATCH 42/87] remove mistaked files from pull request --- my_pyscf/mcscf/lasci_original.py | 1719 ---------------------- my_pyscf/mcscf/lasci_sv_expanded.py | 2060 --------------------------- my_pyscf/mcscf/sv_lasci.py | 1844 ------------------------ 3 files changed, 5623 deletions(-) delete mode 100644 my_pyscf/mcscf/lasci_original.py delete mode 100644 my_pyscf/mcscf/lasci_sv_expanded.py delete mode 100644 my_pyscf/mcscf/sv_lasci.py diff --git a/my_pyscf/mcscf/lasci_original.py b/my_pyscf/mcscf/lasci_original.py deleted file mode 100644 index bece9c69..00000000 --- a/my_pyscf/mcscf/lasci_original.py +++ /dev/null @@ -1,1719 +0,0 @@ -from pyscf.scf.rohf import get_roothaan_fock -from pyscf.fci import cistring -from pyscf.mcscf import casci, casci_symm, df -from pyscf.tools import dump_mat -from pyscf import symm, gto, scf, ao2mo, lib -from pyscf.fci.direct_spin1 import _unpack_nelec -from mrh.my_pyscf.mcscf.addons import state_average_n_mix, get_h1e_zipped_fcisolver, las2cas_civec -from mrh.my_pyscf.mcscf import lasci_sync, _DFLASCI, lasscf_guess -from mrh.my_pyscf.fci import csf_solver -from mrh.my_pyscf.df.sparse_df import sparsedf_array -from mrh.my_pyscf.mcscf import chkfile -from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver -from mrh.util.la import matrix_svd_control_options -from itertools import combinations -from scipy.sparse import linalg as sparse_linalg -from scipy import linalg -import numpy as np -import copy - -def LASCI (mf_or_mol, ncas_sub, nelecas_sub, **kwargs): - if isinstance(mf_or_mol, gto.Mole): - mf = scf.RHF(mf_or_mol) - else: - mf = mf_or_mol - if mf.mol.symmetry: - las = LASCISymm (mf, ncas_sub, nelecas_sub, **kwargs) - else: - las = LASCINoSymm (mf, ncas_sub, nelecas_sub, **kwargs) - if getattr (mf, 'with_df', None): - las = density_fit (las, with_df = mf.with_df) - return las - -def get_grad (las, mo_coeff=None, ci=None, ugg=None, h1eff_sub=None, h2eff_sub=None, - veff=None, dm1s=None): - '''Return energy gradient for orbital rotation and CI relaxation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - ugg : instance of :class:`LASCI_UnitaryGroupGenerators` - h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray - Contains effective one-electron Hamiltonians experienced by each fragment - in each state - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - dm1s : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-RDM in the AO basis - - Returns: - gorb : ndarray of shape (ugg.nvar_orb,) - Orbital rotation gradients as a flat array - gci : ndarray of shape (sum(ugg.ncsf_sub),) - CI relaxation gradients as a flat array - gx : ndarray - Orbital rotation gradients for temporarily frozen orbitals in the "LASCI" problem - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if ugg is None: ugg = las.get_ugg (mo_coeff, ci) - if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if veff is None: - veff = las.get_veff (dm1s = dm1s.sum (0)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) - if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, - h2eff_sub=h2eff_sub) - - gorb = get_grad_orb (las, mo_coeff=mo_coeff, ci=ci, h2eff_sub=h2eff_sub, veff=veff, dm1s=dm1s) - gci = get_grad_ci (las, mo_coeff=mo_coeff, ci=ci, h1eff_sub=h1eff_sub, h2eff_sub=h2eff_sub, - veff=veff) - - idx = ugg.get_gx_idx () - gx = gorb[idx] - gint = ugg.pack (gorb, gci) - gorb = gint[:ugg.nvar_orb] - gci = gint[ugg.nvar_orb:] - return gorb, gci, gx.ravel () - -def get_grad_orb (las, mo_coeff=None, ci=None, h2eff_sub=None, veff=None, dm1s=None, hermi=-1): - '''Return energy gradient for orbital rotation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - dm1s : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-RDM in the AO basis - hermi : integer - Control (anti-)symmetrization. 0 means to return the effective Fock matrix, - F1 = h.D + g.d. -1 means to return the true orbital-rotation gradient, which is skew- - symmetric: gorb = F1 - F1.T. +1 means to return the symmetrized effective Fock matrix, - (F1 + F1.T) / 2. The factor of 2 difference between hermi=-1 and the other two options - is intentional and necessary. - - Returns: - gorb : ndarray of shape (nmo,nmo) - Orbital rotation gradients as a square antihermitian array - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if veff is None: - veff = las.get_veff (dm1s = dm1s.sum (0)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) - nao, nmo = mo_coeff.shape - ncore = las.ncore - ncas = las.ncas - nocc = las.ncore + las.ncas - smo_cas = las._scf.get_ovlp () @ mo_coeff[:,ncore:nocc] - smoH_cas = smo_cas.conj ().T - - # The orbrot part - h1s = las.get_hcore ()[None,:,:] + veff - f1 = h1s[0] @ dm1s[0] + h1s[1] @ dm1s[1] - f1 = mo_coeff.conjugate ().T @ f1 @ las._scf.get_ovlp () @ mo_coeff - # ^ I need the ovlp there to get dm1s back into its correct basis - casdm2 = las.make_casdm2 (ci=ci) - casdm1s = np.stack ([smoH_cas @ d @ smo_cas for d in dm1s], axis=0) - casdm1 = casdm1s.sum (0) - casdm2 -= np.multiply.outer (casdm1, casdm1) - casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) - casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) - eri = h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2) - eri = lib.numpy_helper.unpack_tril (eri).reshape (nmo, ncas, ncas, ncas) - f1[:,ncore:nocc] += np.tensordot (eri, casdm2, axes=((1,2,3),(1,2,3))) - - if hermi == -1: - return f1 - f1.T - elif hermi == 1: - return .5*(f1+f1.T) - elif hermi == 0: - return f1 - else: - raise ValueError ("kwarg 'hermi' must = -1, 0, or +1") - -def get_grad_ci (las, mo_coeff=None, ci=None, h1eff_sub=None, h2eff_sub=None, veff=None): - '''Return energy gradient for CI relaxation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray - Contains effective one-electron Hamiltonians experienced by each fragment - in each state - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - - Returns: - gci : list (length=nfrags) of list (length=nroots) of ndarray - CI relaxation gradients in the shape of CI vectors - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, - h2eff_sub=h2eff_sub) - gci = [] - for isub, (fcibox, h1e, ci0, ncas, nelecas) in enumerate (zip ( - las.fciboxes, h1eff_sub, ci, las.ncas_sub, las.nelecas_sub)): - eri_cas = las.get_h2eff_slice (h2eff_sub, isub, compact=8) - linkstrl = fcibox.states_gen_linkstr (ncas, nelecas, True) - linkstr = fcibox.states_gen_linkstr (ncas, nelecas, False) - h2eff = fcibox.states_absorb_h1e(h1e, eri_cas, ncas, nelecas, .5) - hc0 = fcibox.states_contract_2e(h2eff, ci0, ncas, nelecas, link_index=linkstrl) - hc0 = [hc.ravel () for hc in hc0] - ci0 = [c.ravel () for c in ci0] - gci.append ([2.0 * (hc - c * (c.dot (hc))) for c, hc in zip (ci0, hc0)]) - return gci - -def density_fit (las, auxbasis=None, with_df=None): - ''' Here I ONLY need to attach the tag and the df object because I put conditionals in - LASCINoSymm to make my life easier ''' - las_class = las.__class__ - if with_df is None: - if (getattr(las._scf, 'with_df', None) and - (auxbasis is None or auxbasis == las._scf.with_df.auxbasis)): - with_df = las._scf.with_df - else: - with_df = df.DF(las.mol) - with_df.max_memory = las.max_memory - with_df.stdout = las.stdout - with_df.verbose = las.verbose - with_df.auxbasis = auxbasis - class DFLASCI (las_class, _DFLASCI): - def __init__(self, scf, ncas_sub, nelecas_sub): - self.with_df = with_df - self._keys = self._keys.union(['with_df']) - las_class.__init__(self, scf, ncas_sub, nelecas_sub) - new_las = DFLASCI (las._scf, las.ncas_sub, las.nelecas_sub) - new_las.__dict__.update (las.__dict__) - return new_las - -def h1e_for_las (las, mo_coeff=None, ncas=None, ncore=None, nelecas=None, ci=None, ncas_sub=None, - nelecas_sub=None, veff=None, h2eff_sub=None, casdm1s_sub=None, casdm1frs=None): - ''' Effective one-body Hamiltonians (plural) for a LASCI problem - - Args: - las: a LASCI object - - Kwargs: - mo_coeff: ndarray of shape (nao,nmo) - Orbital coefficients ordered on the columns as: - core orbitals, subspace 1, subspace 2, ..., external orbitals - ncas: integer - As in PySCF's existing CASCI/CASSCF implementation - nelecas: sequence of 2 integers - As in PySCF's existing CASCI/CASSCF implementation - ci: list (length=nfrags) of list (length=nroots) of ndarrays - Contains CI vectors - ncas_sub: ndarray of shape (nsub) - Number of active orbitals in each subspace - nelecas_sub: ndarray of shape (nsub,2) - na, nb in each subspace - veff: ndarray of shape (2, nao, nao) - Contains spin-separated, state-averaged effective potential - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - casdm1s_sub : list (length=nfrags) of ndarrays - Contains state-averaged, spin-separated 1-RDMs in the localized active subspaces - casdm1frs : list (length=nfrags) of list (length=nroots) of ndarrays - Contains spin-separated 1-RDMs for each state in the localized active subspaces - - Returns: - h1e_fr: list (length=nfrags) of list (length=nroots) of ndarrays - Spin-separated 1-body Hamiltonian operator for each fragment and state - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ncas is None: ncas = las.ncas - if ncore is None: ncore = las.ncore - if ncas_sub is None: ncas_sub = las.ncas_sub - if nelecas_sub is None: nelecas_sub = las.nelecas_sub - if ncore is None: ncore = las.ncore - if ci is None: ci = las.ci - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if casdm1frs is None: casdm1frs = las.states_make_casdm1s_sub (ci=ci) - if casdm1s_sub is None: casdm1s_sub = [np.einsum ('rsij,r->sij',dm,las.weights) - for dm in casdm1frs] - if veff is None: - veff = las.get_veff (dm1s = las.make_rdm1 (mo_coeff=mo_coeff, ci=ci)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci, casdm1s_sub=casdm1s_sub) - - # First pass: split by root - nocc = ncore + ncas - nmo = mo_coeff.shape[-1] - mo_cas = mo_coeff[:,ncore:nocc] - moH_cas = mo_cas.conj ().T - h1e = moH_cas @ (las.get_hcore ()[None,:,:] + veff) @ mo_cas - h1e_r = np.empty ((las.nroots, 2, ncas, ncas), dtype=h1e.dtype) - h2e = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, - ncas*(ncas+1)//2)).reshape (nmo, ncas, ncas, ncas)[ncore:nocc,:,:,:] - avgdm1s = np.stack ([linalg.block_diag (*[dm[spin] for dm in casdm1s_sub]) - for spin in range (2)], axis=0) - for state in range (las.nroots): - statedm1s = np.stack ([linalg.block_diag (*[dm[state][spin] for dm in casdm1frs]) - for spin in range (2)], axis=0) - dm1s = statedm1s - avgdm1s - j = np.tensordot (dm1s, h2e, axes=((1,2),(2,3))) - k = np.tensordot (dm1s, h2e, axes=((1,2),(2,1))) - h1e_r[state] = h1e + j + j[::-1] - k - - - # Second pass: split by fragment and subtract double-counting - h1e_fr = [] - for ix, casdm1s_r in enumerate (casdm1frs): - p = sum (las.ncas_sub[:ix]) - q = p + las.ncas_sub[ix] - h1e = h1e_r[:,:,p:q,p:q] - h2e = las.get_h2eff_slice (h2eff_sub, ix) - j = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,3))) - k = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,1))) - h1e_fr.append (h1e - j - j[:,::-1] + k) - - return h1e_fr - -def get_fock (las, mo_coeff=None, ci=None, eris=None, casdm1s=None, verbose=None, veff=None, - dm1s=None): - ''' f_pq = h_pq + (g_pqrs - g_psrq/2) D_rs, AO basis - Note the difference between this and h1e_for_las: h1e_for_las only has - JK terms from electrons outside the "current" active subspace; get_fock - includes JK from all electrons. This is also NOT the "generalized Fock matrix" - of orbital gradients (but it can be used in calculating those if you do a - semi-cumulant decomposition). - The "eris" kwarg does not do anything and is retained only for backwards - compatibility (also why I don't just call las.make_rdm1) ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if dm1s is None: - if casdm1s is None: casdm1s = las.make_casdm1s (ci=ci) - mo_cas = mo_coeff[:,las.ncore:][:,:las.ncas] - moH_cas = mo_cas.conjugate ().T - mo_core = mo_coeff[:,:las.ncore] - moH_core = mo_core.conjugate ().T - dm1s = [(mo_core @ moH_core) + (mo_cas @ d @ moH_cas) for d in list(casdm1s)] - if veff is not None: - fock = las.get_hcore()[None,:,:] + veff - return get_roothaan_fock (fock, dm1s, las._scf.get_ovlp ()) - dm1 = dm1s[0] + dm1s[1] - if isinstance (las, _DFLASCI): - vj, vk = las.with_df.get_jk(dm1, hermi=1) - else: - vj, vk = las._scf.get_jk(las.mol, dm1, hermi=1) - fock = las.get_hcore () + vj - (vk/2) - return fock - -def _eig_inactive_virtual (las, fock, orbsym=None): - '''Generate the unitary matrix canonicalizing the inactive and virtual orbitals only. - - Args: - las : object of :class:`LASCINoSymm` - fock : ndarray of shape (nmo,nmo) - Contains Fock matrix in MO basis - - Kwargs: - orbsym : list of length nmo - umat : ndarray of shape (nmo, nmo) - - Returns: - ene : ndarray of shape (nmo,) - umat : ndarray of shape (nmo, nmo)''' - nmo = fock.shape[0] - ncore = las.ncore - nocc = ncore + las.ncas - ene = np.zeros (nmo) - umat = np.eye (nmo) - if ncore: - orbsym_i = None if orbsym is None else orbsym[:ncore] - fock_i = fock[:ncore,:ncore] - ene[:ncore], umat[:ncore,:ncore] = las._eig (fock_i, 0, 0, orbsym_i) - if nmo-nocc: - orbsym_i = None if orbsym is None else orbsym[nocc:] - fock_i = fock[nocc:,nocc:] - ene[nocc:], umat[nocc:,nocc:] = las._eig (fock_i, 0, 0, orbsym_i) - return ene, umat - -def canonicalize (las, mo_coeff=None, ci=None, casdm1fs=None, natorb_casdm1=None, veff=None, - h2eff_sub=None, orbsym=None): - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - - # In-place safety - mo_coeff = mo_coeff.copy () - ci = copy.deepcopy (ci) - - # Temporary lroots safety - # The desired behavior is that the inactive and external orbitals should - # be canonicalized according to the density matrix used for orbital optimization - # TODO: once orbital optimization with lroots is enabled, change this behavior - # TODO: possibly move this logic to the make_casdm* functions - if casdm1fs is None: - ci_dm = [] - for i in range (len (ci)): - ci_i = [] - for j in range (len (ci[i])): - if ci[i][j].ndim>2: - ci_i.append (ci[i][j][0]) - else: - ci_i.append (ci[i][j]) - ci_dm.append (ci_i) - casdm1fs = las.make_casdm1s_sub (ci=ci_dm) - - nao, nmo = mo_coeff.shape - ncore = las.ncore - nocc = ncore + las.ncas - ncas_sub = las.ncas_sub - nelecas_sub = las.nelecas_sub - - # Passing casdm1 or lasdm1 only affects the canonicalization of the active orbitals - umat = np.zeros_like (mo_coeff) - casdm1s = np.stack ([linalg.block_diag (*[dm[0] for dm in casdm1fs]), - linalg.block_diag (*[dm[1] for dm in casdm1fs])], axis=0) - fock = mo_coeff.conjugate ().T @ las.get_fock (mo_coeff=mo_coeff, casdm1s=casdm1s, veff=veff) - fock = fock @ mo_coeff - if natorb_casdm1 is None: # State-average natural orbitals by default - natorb_casdm1 = casdm1s.sum (0) - - # Inactive-inactive and virtual-virtual - ene, umat = _eig_inactive_virtual (las, fock, orbsym=orbsym) - idx = np.arange (nmo, dtype=int) - if ncore: idx[:ncore] = idx[:ncore][np.argsort (ene[:ncore])] - if nmo-nocc: idx[nocc:] = idx[nocc:][np.argsort (ene[nocc:])] - umat = umat[:,idx] - if orbsym is not None: orbsym = orbsym[idx] - # Active-active - check_diag = natorb_casdm1.copy () - for ix, ncas in enumerate (ncas_sub): - i = sum (ncas_sub[:ix]) - j = i + ncas - check_diag[i:j,i:j] = 0.0 - is_block_diag = np.amax (np.abs (check_diag)) < 1e-8 - if is_block_diag: - # No off-diagonal RDM elements -> extra effort to prevent diagonalizer from breaking frags - for isub, (ncas, nelecas) in enumerate (zip (ncas_sub, nelecas_sub)): - i = sum (ncas_sub[:isub]) - j = i + ncas - dm1 = natorb_casdm1[i:j,i:j] - i += ncore - j += ncore - orbsym_i = None if orbsym is None else orbsym[i:j] - occ, umat[i:j,i:j] = las._eig (dm1, 0, 0, orbsym_i) - idx = np.argsort (occ)[::-1] - umat[i:j,i:j] = umat[i:j,i:j][:,idx] - if orbsym_i is not None: orbsym[i:j] = orbsym[i:j][idx] - if ci is not None: - fcibox = las.fciboxes[isub] - ci[isub] = fcibox.states_transform_ci_for_orbital_rotation ( - ci[isub], ncas, nelecas, umat[i:j,i:j]) - else: # You can't get proper LAS-type CI vectors w/out active space fragmentation - ci = None - orbsym_cas = None if orbsym is None else orbsym[ncore:nocc] - occ, umat[ncore:nocc,ncore:nocc] = las._eig (natorb_casdm1, 0, 0, orbsym_cas) - idx = np.argsort (occ)[::-1] - umat[ncore:nocc,ncore:nocc] = umat[ncore:nocc,ncore:nocc][:,idx] - if orbsym_cas is not None: orbsym[ncore:nocc] = orbsym[ncore:nocc][idx] - - # Final - mo_occ = np.zeros (nmo, dtype=natorb_casdm1.dtype) - if ncore: mo_occ[:ncore] = 2 - ucas = umat[ncore:nocc,ncore:nocc] - mo_occ[ncore:nocc] = ((natorb_casdm1 @ ucas) * ucas).sum (0) - mo_ene = ((fock @ umat) * umat.conjugate ()).sum (0) - mo_ene[ncore:][:sum (ncas_sub)] = 0.0 - mo_coeff = mo_coeff @ umat - if orbsym is not None: - ''' - print ("This is the second call to label_orb_symm inside of canonicalize") - orbsym = symm.label_orb_symm (las.mol, las.mol.irrep_id, - las.mol.symm_orb, mo_coeff, - s=las._scf.get_ovlp ()) - #mo_coeff = las.label_symmetry_(mo_coeff) - ''' - mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) - if h2eff_sub is not None: - h2eff_sub = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*las.ncas, -1)) - h2eff_sub = h2eff_sub.reshape (nmo, las.ncas, las.ncas, las.ncas) - h2eff_sub = np.tensordot (umat, h2eff_sub, axes=((0),(0))) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(1))).transpose (1,0,2,3) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(2))).transpose (1,2,0,3) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(3))).transpose (1,2,3,0) - h2eff_sub = h2eff_sub.reshape (nmo*las.ncas, las.ncas, las.ncas) - h2eff_sub = lib.numpy_helper.pack_tril (h2eff_sub).reshape (nmo, -1) - - # I/O - log = lib.logger.new_logger (las, las.verbose) - if las.verbose >= lib.logger.INFO: - if is_block_diag: - for isub, nlas in enumerate (ncas_sub): - log.info ("Fragment %d natural orbitals", isub) - i = ncore + sum (ncas_sub[:isub]) - j = i + nlas - log.info ('Natural occ %s', str (mo_occ[i:j])) - log.info ('Natural orbital (expansion on AOs) in CAS space') - label = las.mol.ao_labels() - mo_las = mo_coeff[:,i:j] - dump_mat.dump_rec(log.stdout, mo_las, label, start=1) - else: - log.info ("Delocalized natural orbitals do not reflect LAS fragmentation") - log.info ('Natural occ %s', str (mo_occ[ncore:nocc])) - log.info ('Natural orbital (expansion on AOs) in CAS space') - label = las.mol.ao_labels() - mo_las = mo_coeff[:,ncore:nocc] - dump_mat.dump_rec(log.stdout, mo_las, label, start=1) - - return mo_coeff, mo_ene, mo_occ, ci, h2eff_sub - -def get_init_guess_ci (las, mo_coeff=None, h2eff_sub=None, ci0=None): - # TODO: come up with a better algorithm? This might be working better than what I had before - # but it omits inter-active Coulomb and exchange interactions altogether. Is there a - # non-outer-product algorithm for finding the lowest-energy single product of CSFs? - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci0 is None: ci0 = [[None for i in range (las.nroots)] for j in range (las.nfrags)] - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - nmo = mo_coeff.shape[-1] - ncore, ncas = las.ncore, las.ncas - nocc = ncore + ncas - dm1_core= 2 * mo_coeff[:,:ncore] @ mo_coeff[:,:ncore].conj ().T - h1e_ao = las._scf.get_fock (dm=dm1_core) - eri_cas = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)) - eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) - eri_cas = eri_cas[ncore:nocc] - for ix, (fcibox, norb, nelecas) in enumerate (zip (las.fciboxes,las.ncas_sub,las.nelecas_sub)): - i = sum (las.ncas_sub[:ix]) - j = i + norb - mo = mo_coeff[:,ncore+i:ncore+j] - moH = mo.conj ().T - h1e = moH @ h1e_ao @ mo - h1e = [h1e, h1e] - eri = eri_cas[i:j,i:j,i:j,i:j] - for iy, solver in enumerate (fcibox.fcisolvers): - nelec = fcibox._get_nelec (solver, nelecas) - ndet = tuple ([cistring.num_strings (norb, n) for n in nelec]) - if isinstance (ci0[ix][iy], np.ndarray) and ci0[ix][iy].size==ndet[0]*ndet[1]: continue - if hasattr (mo_coeff, 'orbsym'): - solver.orbsym = mo_coeff.orbsym[ncore+i:ncore+j] - hdiag_csf = solver.make_hdiag_csf (h1e, eri, norb, nelec, max_memory=las.max_memory) - ci0[ix][iy] = solver.get_init_guess (norb, nelec, solver.nroots, hdiag_csf) - if solver.nroots==1: - ci0[ix][iy] = ci0[ix][iy][0] - else: - ci0[ix][iy] = np.stack (ci0[ix][iy], axis=0) - return ci0 - -def get_space_info (las): - ''' Retrieve the quantum numbers defining the states of a LASSCF calculation ''' - nfrags, nroots = las.nfrags, las.nroots - charges = np.zeros ((nroots, nfrags), dtype=np.int32) - wfnsyms, spins, smults = charges.copy (), charges.copy (), charges.copy () - for ifrag, fcibox in enumerate (las.fciboxes): - for iroot, solver in enumerate (fcibox.fcisolvers): - nelec = fcibox._get_nelec (solver, las.nelecas_sub[ifrag]) - charges[iroot,ifrag] = np.sum (las.nelecas_sub[ifrag]) - np.sum (nelec) - spins[iroot,ifrag] = nelec[0]-nelec[1] - smults[iroot,ifrag] = solver.smult - try: - wfnsyms[iroot,ifrag] = solver.wfnsym or 0 - except ValueError as e: - wfnsyms[iroot,ifrag] = symm.irrep_name2id (las.mol.groupname, solver.wfnsym) - return charges, spins, smults, wfnsyms - -def assert_no_duplicates (las, tab=None): - log = lib.logger.new_logger (las, las.verbose) - if tab is None: tab = np.stack (get_space_info (las), axis=-1) - tab_uniq, uniq_idx, uniq_inv, uniq_cnts = np.unique (tab, return_index=True, - return_inverse=True, return_counts=True, axis=0) - idx_dupe = uniq_cnts>1 - try: - err_str = ('LAS state basis has duplicates; details in logfile for ' - 'verbose >= INFO (4) [more details for verbose > INFO].\n' - '(Disable this assertion by passing assert_no_dupes=False ' - 'to the kernel, lasci, and state_average(_) functions.)') - assert (~np.any (idx_dupe)), err_str - except AssertionError as e: - dupe_idx = uniq_idx[idx_dupe] - dupe_cnts = uniq_cnts[idx_dupe] - for i, (ix, cnt, col) in enumerate (zip (uniq_idx, uniq_cnts, tab_uniq)): - if cnt==1: continue - log.info ('State %d appears %d times', ix, cnt) - idx_thisdupe = np.where (uniq_inv==i)[0] - row = col.T - log.debug ('As states {}'.format (idx_thisdupe)) - log.debug ('Charges = {}'.format (row[0])) - log.debug ('2M_S = {}'.format (row[1])) - log.debug ('2S+1 = {}'.format (row[2])) - log.debug ('Wfnsyms = {}'.format (row[3])) - raise e from None - -def state_average_(las, weights=[0.5,0.5], charges=None, spins=None, - smults=None, wfnsyms=None, assert_no_dupes=True): - ''' Transform LASCI/LASSCF object into state-average LASCI/LASSCF - - Args: - las: LASCI/LASSCF instance - - Kwargs: - weights: list of float; required - E_SA = sum_i weights[i] E[i] is used to optimize the orbitals - charges: 2d ndarray or nested list of integers - spins: 2d ndarray or nested list of integers - For the jth fragment in the ith state, - neleca = (sum(las.nelecas_sub[j]) - charges[i][j] + spins[i][j]) // 2 - nelecb = (sum(las.nelecas_sub[j]) - charges[i][j] - spins[i][j]) // 2 - Defaults to - charges[i][j] = 0 - spins[i][j] = las.nelecas_sub[j][0] - las.nelecas_sub[j][1] - smults: 2d ndarray or nested list of integers - For the jth fragment in the ith state, - smults[i][j] = (2*s)+1 - where "s" is the total spin quantum number, - S^2|j,i> = s*(s+1)|j,i> - Defaults to - smults[i][j] = abs (spins[i][j]) + 1 - wfnsyms: 2d ndarray or nested list of integers or strings - For the jth fragment of the ith state, - wfnsyms[i][j] - identifies the point-group irreducible representation - Defaults to all zeros (i.e., the totally-symmetric irrep) - - Returns: - las: LASCI/LASSCF instance - The first positional argument, modified in-place into a - state-averaged LASCI/LASSCF instance. - - ''' - old_states = np.stack (get_space_info (las), axis=-1) - nroots = len (weights) - nfrags = las.nfrags - if charges is None: charges = np.zeros ((nroots, nfrags), dtype=np.int32) - if wfnsyms is None: wfnsyms = np.zeros ((nroots, nfrags), dtype=np.int32) - if spins is None: spins = np.asarray ([[n[0]-n[1] for n in las.nelecas_sub] for i in weights]) - if smults is None: smults = np.abs (spins)+1 - - charges = np.asarray (charges) - wfnsyms = np.asarray (wfnsyms) - spins = np.asarray (spins) - smults = np.asarray (smults) - if np.issubsctype (wfnsyms.dtype, np.str_): - wfnsyms_str = wfnsyms - wfnsyms = np.zeros (wfnsyms_str.shape, dtype=np.int32) - for ix, wfnsym in enumerate (wfnsyms_str.flat): - try: - wfnsyms.flat[ix] = symm.irrep_name2id (las.mol.groupname, wfnsym) - except (TypeError, KeyError) as e: - wfnsyms.flat[ix] = int (wfnsym) - if nfrags == 1: - charges = np.atleast_2d (np.squeeze (charges)).T - wfnsyms = np.atleast_2d (np.squeeze (wfnsyms)).T - spins = np.atleast_2d (np.squeeze (spins)).T - smults = np.atleast_2d (np.squeeze (smults)).T - new_states = np.stack ([charges, spins, smults, wfnsyms], axis=-1) - if assert_no_dupes: assert_no_duplicates (las, tab=new_states) - - las.fciboxes = [get_h1e_zipped_fcisolver (state_average_n_mix ( - las, [csf_solver (las.mol, smult=s2p1).set (charge=c, spin=m2, wfnsym=ir) - for c, m2, s2p1, ir in zip (c_r, m2_r, s2p1_r, ir_r)], weights).fcisolver) - for c_r, m2_r, s2p1_r, ir_r in zip (charges.T, spins.T, smults.T, wfnsyms.T)] - las.e_states = np.zeros (nroots) - las.nroots = nroots - las.weights = weights - - if las.ci is not None: - log = lib.logger.new_logger(las, las.verbose) - log.debug (("lasci.state_average: Cached CI vectors may be present.\n" - "Looking for matches between old and new LAS states...")) - ci0 = [[None for i in range (nroots)] for j in range (nfrags)] - new_states = np.stack ([charges, spins, smults, wfnsyms], - axis=-1).reshape (nroots, nfrags*4) - old_states = old_states.reshape (-1, nfrags*4) - for iroot, row in enumerate (old_states): - idx = np.all (new_states == row[None,:], axis=1) - if np.count_nonzero (idx) == 1: - jroot = np.where (idx)[0][0] - log.debug ("Old state {} -> New state {}".format (iroot, jroot)) - for ifrag in range (nfrags): - ci0[ifrag][jroot] = las.ci[ifrag][iroot] - elif np.count_nonzero (idx) > 1: - raise RuntimeError ("Duplicate states specified?\n{}".format (idx)) - las.ci = ci0 - las.converged = False - return las - -@lib.with_doc(''' A version of lasci.state_average_ that creates a copy instead of modifying the - LASCI/LASSCF method instance in place. - - See lasci.state_average_ docstring below:\n\n''' + state_average_.__doc__) -def state_average (las, weights=[0.5,0.5], charges=None, spins=None, - smults=None, wfnsyms=None, assert_no_dupes=True): - is_scanner = isinstance (las, lib.SinglePointScanner) - if is_scanner: las = las.undo_scanner () - new_las = las.__class__(las._scf, las.ncas_sub, las.nelecas_sub) - new_las.__dict__.update (las.__dict__) - new_las.mo_coeff = las.mo_coeff.copy () - if getattr (las.mo_coeff, 'orbsym', None) is not None: - new_las.mo_coeff = lib.tag_array (new_las.mo_coeff, - orbsym=las.mo_coeff.orbsym) - new_las.ci = None - if las.ci is not None: - new_las.ci = [[c2.copy () if isinstance (c2, np.ndarray) else None - for c2 in c1] for c1 in las.ci] - las = state_average_(new_las, weights=weights, charges=charges, spins=spins, - smults=smults, wfnsyms=wfnsyms, assert_no_dupes=assert_no_dupes) - if is_scanner: las = las.as_scanner () - return las - -def get_single_state_las (las, state=0): - ''' Quickly extract a state-specific las calculation from a state-average one ''' - charges, spins, smults, wfnsyms = get_space_info (las) - charges = charges[state:state+1] - spins = spins[state:state+1] - smults = smults[state:state+1] - wfnsyms = wfnsyms[state:state+1] - weights = [1,] - return state_average (las, weights=weights, charges=charges, spins=spins, smults=smults, - wfnsyms=wfnsyms) - -def run_lasci (las, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=0, - assert_no_dupes=False, _dry_run=False): - '''Self-consistently optimize the CI vectors of a LAS state with - frozen orbitals using a fixed-point algorithm. "lasci_" (with the - trailing underscore) sets self.mo_coeff from the kwarg if it is passed; - "lasci" (without the trailing underscore) leaves self.mo_coeff unchanged. - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - MO coefficients; defaults to self.mo_coeff - ci0 : list (length nfrags) of list (length nroots) of ndarrays - Contains CI vectors for initial guess - lroots : ndarray of shape (nfrags,nroots) - Number of local roots in each fragment for each global state. - The corresponding local weights are set to [1,0,0,0,...]. - lweights : list of length nfrags of list of length nroots of sequence - Weights of local roots in each fragment for each global state. - Passing lweights is incompatible with passing lroots. Defaults - to, i.e., np.ones (las.nfrags, las.nroots, 1).tolist () - verbose : integer - See pyscf.lib.logger. - assert_no_dupes : logical - If True, checks state list for duplicate states - _dry_run : logical - If True, sets up the fcisolvers with the appropriate tags, but does - not run fcisolver kernels. - - Returns: - converged : list of length nroots of logical - Stores whether the calculation for each state successfully converged - e_tot : float - (State-averaged) total energy - e_states : list of length nroots - List of each state energy - e_cas : list of length nroots - List of the CAS space energy of each state - ci : list (length nfrags) of list (length nroots) of ndarrays - Contains optimized CI vectors - ''' - if assert_no_dupes: assert_no_duplicates (las) - if lroots is not None and lweights is not None: - raise RuntimeError ("lroots sets lweights: pass either or none but not both") - elif lweights is None: - if lroots is None: lroots = np.ones ((las.nfrags, las.nroots), dtype=int) - lweights = [] - for i in range (las.nfrags): - lwi = [] - for j in range (las.nroots): - lwij = np.zeros (lroots[i,j]) - lwij[0] = 1 - lwi.append (lwij) - lweights.append (lwi) - nao, nmo = mo_coeff.shape - ncore, ncas = las.ncore, las.ncas - nocc = ncore + ncas - ncas_sub = las.ncas_sub - nelecas_sub = las.nelecas_sub - orbsym = getattr (mo_coeff, 'orbsym', None) - if orbsym is not None: orbsym=orbsym[ncore:nocc] - elif isinstance (las, LASCISymm): - mo_coeff = las.label_symmetry_(mo_coeff) - orbsym = mo_coeff.orbsym[ncore:nocc] - log = lib.logger.new_logger (las, verbose) - - h1eff, energy_core = las.h1e_for_cas (mo_coeff=mo_coeff, - ncas=las.ncas, ncore=las.ncore) - h2eff = las.get_h2eff (mo_coeff) - if (ci0 is None or any ([c is None for c in ci0]) or - any ([any ([c2 is None for c2 in c1]) for c1 in ci0])): - ci0 = las.get_init_guess_ci (mo_coeff, h2eff, ci0) - eri_cas = lib.numpy_helper.unpack_tril ( - h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, - ncas, ncas)[ncore:nocc] - - e_cas = np.empty (las.nroots) - e_states = np.empty (las.nroots) - ci1 = [[None for c2 in c1] for c1 in ci0] - converged = [] - t = (lib.logger.process_clock(), lib.logger.perf_counter()) - e_lexc = [[None for i in range (las.nroots)] for j in range (las.nfrags)] - for state in range (las.nroots): - fcisolvers = [b.fcisolvers[state] for b in las.fciboxes] - ci0_i = [c[state] for c in ci0] - solver = ImpureProductStateFCISolver (fcisolvers, stdout=las.stdout, - lweights=[l[state] for l in lweights], verbose=verbose) - # TODO: better handling of CSF symmetry quantum numbers in general - for ix, s in enumerate (solver.fcisolvers): - i = sum (ncas_sub[:ix]) - j = i + ncas_sub[ix] - if orbsym is not None: s.orbsym = orbsym[i:j] - s.norb = ncas_sub[ix] - s.nelec = solver._get_nelec (s, nelecas_sub[ix]) - s.check_transformer_cache () - if _dry_run: continue - conv, e_i, ci_i = solver.kernel (h1eff, eri_cas, ncas_sub, nelecas_sub, - ecore=0, ci0=ci0_i, orbsym=orbsym, conv_tol_grad=las.conv_tol_grad, - conv_tol_self=las.conv_tol_self, max_cycle_macro=las.max_cycle_macro) - e_cas[state] = e_i - e_states[state] = e_i + energy_core - for frag, s in enumerate (solver.fcisolvers): - e_loc = np.atleast_1d (getattr (s, 'e_states', e_i)) - e_lexc[frag][state] = e_loc - e_i - for c1, c2, s, no, ne in zip (ci1, ci_i, solver.fcisolvers, ncas_sub, nelecas_sub): - ne = solver._get_nelec (s, ne) - ndeta, ndetb = [cistring.num_strings (no, n) for n in ne] - shape = [s.nroots, ndeta, ndetb] if s.nroots>1 else [ndeta, ndetb] - c1[state] = np.asarray (c2).reshape (*shape) - if not conv: log.warn ('State %d LASCI not converged!', state) - converged.append (conv) - t = log.timer ('State {} LASCI'.format (state), *t) - - e_tot = np.dot (las.weights, e_states) - return converged, e_tot, e_states, e_cas, e_lexc, ci1 - -class LASCINoSymm (casci.CASCI): - - def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, frozen=None, **kwargs): - if isinstance(ncas,int): - ncas = [ncas] - ncas_tot = sum (ncas) - nel_tot = [0, 0] - new_nelecas = [] - for ix, nel in enumerate (nelecas): - if isinstance (nel, (int, np.integer)): - nb = nel // 2 - na = nb + (nel % 2) - else: - na, nb = nel - new_nelecas.append ((na, nb)) - nel_tot[0] += na - nel_tot[1] += nb - nelecas = new_nelecas - self.nroots = 1 - super().__init__(mf, ncas=ncas_tot, nelecas=nel_tot, ncore=ncore) - self.chkfile = self._scf.chkfile - if spin_sub is None: spin_sub = [1 + abs(ne[0]-ne[1]) for ne in nelecas] - self.ncas_sub = np.asarray (ncas) - self.nelecas_sub = np.asarray (nelecas) - assert (len (self.nelecas_sub) == self.nfrags) - self.frozen = frozen - self.conv_tol_grad = 1e-4 - self.conv_tol_self = 1e-10 - self.ah_level_shift = 1e-8 - self.max_cycle_macro = 50 - self.max_cycle_micro = 5 - keys = set(('e_states', 'fciboxes', 'nroots', 'weights', 'ncas_sub', 'nelecas_sub', - 'conv_tol_grad', 'conv_tol_self', 'max_cycle_macro', 'max_cycle_micro', - 'ah_level_shift', 'states_converged', 'chkfile', 'e_lexc')) - self._keys = set(self.__dict__.keys()).union(keys) - self.fciboxes = [] - if isinstance(spin_sub,int): - self.fciboxes.append(self._init_fcibox(spin_sub,self.nelecas_sub[0])) - else: - assert (len (spin_sub) == self.nfrags) - for smult, nel in zip (spin_sub, self.nelecas_sub): - self.fciboxes.append (self._init_fcibox (smult, nel)) - self.weights = [1.0] - self.e_states = [0.0] - self.e_lexc = [[np.array ([0]),],] - - def _init_fcibox (self, smult, nel): - s = csf_solver (self.mol, smult=smult) - s.spin = nel[0] - nel[1] - return get_h1e_zipped_fcisolver (state_average_n_mix (self, [s], [1.0]).fcisolver) - - @property - def nfrags (self): return len (self.ncas_sub) - - def get_mo_slice (self, idx, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - mo = mo_coeff[:,self.ncore:] - for offs in self.ncas_sub[:idx]: - mo = mo[:,offs:] - mo = mo[:,:self.ncas_sub[idx]] - return mo - - def ao2mo (self, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - nao, nmo = mo_coeff.shape - ncore, ncas = self.ncore, self.ncas - nocc = ncore + ncas - mo_cas = mo_coeff[:,ncore:nocc] - mo = [mo_coeff, mo_cas, mo_cas, mo_cas] - if getattr (self, 'with_df', None) is not None: - # Store intermediate with one contracted ao index for faster calculation of exchange! - bPmn = sparsedf_array (self.with_df._cderi) - bmuP = bPmn.contract1 (mo_cas) - buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) - eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) - eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) - eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) - eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) - if self.verbose > lib.logger.DEBUG: - eri_comp = self.with_df.ao2mo (mo, compact=True) - lib.logger.debug(self,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) - elif getattr (self._scf, '_eri', None) is not None: - eri = ao2mo.incore.general (self._scf._eri, mo, compact=True) - else: - eri = ao2mo.outcore.general_iofree (self.mol, mo, compact=True) - if eri.shape != (nmo,ncas*ncas*(ncas+1)//2): - try: - eri = eri.reshape (nmo, ncas*ncas*(ncas+1)//2) - except ValueError as e: - assert (nmo == ncas), str (e) - eri = ao2mo.restore ('2kl', eri, nmo).reshape (nmo, ncas*ncas*(ncas+1)//2) - return eri - - def get_h2eff_slice (self, h2eff, idx, compact=None): - ncas_cum = np.cumsum ([0] + self.ncas_sub.tolist ()) - i = ncas_cum[idx] - j = ncas_cum[idx+1] - ncore = self.ncore - nocc = ncore + self.ncas - eri = h2eff[ncore:nocc,:].reshape (self.ncas*self.ncas, -1) - ix_i, ix_j = np.tril_indices (self.ncas) - eri = eri[(ix_i*self.ncas)+ix_j,:] - eri = ao2mo.restore (1, eri, self.ncas)[i:j,i:j,i:j,i:j] - if compact: eri = ao2mo.restore (compact, eri, j-i) - return eri - - get_h1eff = get_h1las = h1e_for_las = h1e_for_las - get_h2eff = ao2mo - ''' - def get_h2eff (self, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - if isinstance (self, _DFLASCI): - mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] - return self.with_df.ao2mo (mo_cas) - return self.ao2mo (mo_coeff) - ''' - - get_fock = get_fock - get_grad = get_grad - get_grad_orb = get_grad_orb - get_grad_ci = get_grad_ci - _hop = lasci_sync.LASCI_HessianOperator - _kern = lasci_sync.kernel - def get_hop (self, mo_coeff=None, ci=None, ugg=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ugg is None: ugg = self.get_ugg () - return self._hop (self, ugg, mo_coeff=mo_coeff, ci=ci, **kwargs) - canonicalize = canonicalize - - def _finalize(self): - log = lib.logger.new_logger (self, self.verbose) - nroots_prt = len (self.e_states) - if self.verbose <= lib.logger.INFO: - nroots_prt = min (nroots_prt, 100) - if nroots_prt < len (self.e_states): - log.info (("Printing a maximum of 100 state energies;" - " increase self.verbose to see them all")) - if nroots_prt > 1: - log.info ("LASCI state-average energy = %.15g", self.e_tot) - for i, e in enumerate (self.e_states[:nroots_prt]): - log.info ("LASCI state %d energy = %.15g", i, e) - else: - log.info ("LASCI energy = %.15g", self.e_tot) - return - - - def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, conv_tol_grad=None, - assert_no_dupes=False, verbose=None, _kern=None): - if mo_coeff is None: - mo_coeff = self.mo_coeff - else: - self.mo_coeff = mo_coeff - if ci0 is None: ci0 = self.ci - if verbose is None: verbose = self.verbose - if conv_tol_grad is None: conv_tol_grad = self.conv_tol_grad - if _kern is None: _kern = self._kern - log = lib.logger.new_logger(self, verbose) - - if self.verbose >= lib.logger.WARN: - self.check_sanity() - self.dump_flags(log) - - for fcibox in self.fciboxes: - fcibox.verbose = self.verbose - fcibox.stdout = self.stdout - fcibox.nroots = self.nroots - fcibox.weights = self.weights - # TODO: local excitations and locally-impure states in LASSCF kernel - do_warn=False - if ci0 is not None: - for i, ci0_i in enumerate (ci0): - if ci0_i is None: continue - for j, ci0_ij in enumerate (ci0_i): - if ci0_ij is None: continue - if np.asarray (ci0_ij).ndim>2: - do_warn=True - ci0_i[j] = ci0_ij[0] - if do_warn: log.warn ("Discarding all but the first root of guess CI vectors!") - - self.converged, self.e_tot, self.e_states, self.mo_energy, self.mo_coeff, self.e_cas, \ - self.ci, h2eff_sub, veff = _kern(mo_coeff=mo_coeff, ci0=ci0, verbose=verbose, \ - casdm0_fr=casdm0_fr, conv_tol_grad=conv_tol_grad, assert_no_dupes=assert_no_dupes) - - self._finalize () - return self.e_tot, self.e_cas, self.ci, self.mo_coeff, self.mo_energy, h2eff_sub, veff - - def states_make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: - return [np.zeros ((self.nroots,2,ncas,ncas)) for ncas in ncas_sub] - casdm1s = [] - for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - if ci_i is None: - dm1a = dm1b = np.zeros ((ncas, ncas)) - else: - dm1a, dm1b = fcibox.states_make_rdm1s (ci_i, ncas, nelecas) - casdm1s.append (np.stack ([dm1a, dm1b], axis=1)) - return casdm1s - - def make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, w=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - if w is None: w = self.weights - return [np.einsum ('rspq,r->spq', dm1, w) for dm1 in casdm1frs] - - def states_make_casdm1s (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - return np.stack ([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] - for dm1rs in casdm1frs]) - for ispin in (0, 1)], axis=0) - for iroot in range (self.nroots)], axis=0) - - def states_make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - casdm2 = [] - for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) - return casdm2 - - def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=None, **kwargs): - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, **kwargs) - return [np.einsum ('rijkl,r->ijkl', dm2, box.weights) - for dm2, box in zip (casdm2fr, self.fciboxes)] - - def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, - nelecas_sub=None, casdm1rs=None, casdm1frs=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1rs is None: casdm1rs = self.states_make_casdm1s (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, - **kwargs) - mo_core = mo_coeff[:,:self.ncore] - mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] - dm1rs = np.tensordot (mo_cas.conj (), np.dot (casdm1rs, mo_cas.conj ().T), axes=((1),(2))) - dm1rs = dm1rs.transpose (1,2,0,3) - dm1rs += (mo_core @ mo_core.conj ().T)[None,None,:,:] - return dm1rs - - def make_rdm1s_sub (self, mo_coeff=None, ci=None, ncas_sub=None, - nelecas_sub=None, include_core=False, casdm1s_sub=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ''' Same as make_casdm1s_sub, but in the ao basis ''' - rdm1s = [] - for idx, casdm1s in enumerate (casdm1s_sub): - mo = self.get_mo_slice (idx, mo_coeff=mo_coeff) - moH = mo.conjugate ().T - rdm1s.append (np.tensordot (mo, np.dot (casdm1s,moH), axes=((1),(1))).transpose(1,0,2)) - if include_core and self.ncore: - mo_core = mo_coeff[:,:self.ncore] - moH_core = mo_core.conjugate ().T - dm_core = mo_core @ moH_core - rdm1s = [np.stack ([dm_core, dm_core], axis=0)] + rdm1s - rdm1s = np.stack (rdm1s, axis=0) - return rdm1s - - def make_rdm1_sub (self, **kwargs): - return self.make_rdm1s_sub (**kwargs).sum (1) - - def make_rdm1s (self, mo_coeff=None, ncore=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - mo = mo_coeff[:,:ncore] - moH = mo.conjugate ().T - dm_core = mo @ moH - dm_cas = self.make_rdm1s_sub (mo_coeff=mo_coeff, **kwargs).sum (0) - return dm_core[None,:,:] + dm_cas - - def make_rdm1 (self, mo_coeff=None, ci=None, **kwargs): - return self.make_rdm1s (mo_coeff=mo_coeff, ci=ci, **kwargs).sum (0) - - def make_casdm1s (self, ci=None, **kwargs): - ''' Make the full-dimensional casdm1s spanning the collective active space ''' - casdm1s_sub = self.make_casdm1s_sub (ci=ci, **kwargs) - casdm1a = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) - casdm1b = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) - return np.stack ([casdm1a, casdm1b], axis=0) - - def make_casdm1 (self, ci=None, **kwargs): - ''' Spin-sum make_casdm1s ''' - return self.make_casdm1s (ci=ci, **kwargs).sum (0) - - def states_make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' Make the full-dimensional casdm2 spanning the collective active space ''' - raise DeprecationWarning ( - ("states_make_casdm2 is BANNED. There is no reason to EVER make an array this huge.\n" - "Use states_make_casdm*_sub instead, and substitute the factorization into your " - "expressions.") - ) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - casdm2r = np.zeros ((self.nroots,ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2 in enumerate (casdm2fr): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2r[:, i:j, i:j, i:j, i:j] = dm2 - # Off-diagonal - for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2r): - dma1, dmb1 = dm1s1[0], dm1s1[1] - dma2, dmb2 = dm1s2[0], dm1s2[1] - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) - +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2r - - def state_make_casdm1s(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - casdm1s = np.stack([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] - for dm1rs in casdm1frs]) - for ispin in (0, 1)], axis=0) - for iroot in range (self.nroots)], axis=0) - return casdm1s[state] - - def state_make_casdm2(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' State wise casdm2 spanning the collective active space. ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2_r in enumerate (casdm2fr): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2[i:j, i:j, i:j, i:j] = dm2_r[state] - # Off-diagonal - for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - dma1, dmb1 = dm1s1_r[state][0], dm1s1_r[state][1] - dma2, dmb2 = dm1s2_r[state][0], dm1s2_r[state][1] - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) - +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2 - - def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, - **kwargs): - ''' Make the full-dimensional casdm2 spanning the collective active space ''' - if casdm2r is not None: - return np.einsum ('rijkl,r->ijkl', casdm2r, self.weights) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) - if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - weights = self.weights - casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2 in enumerate (casdm2f): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2[i:j, i:j, i:j, i:j] = dm2 - # Off-diagonal - for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] - dm1r = dma1r + dmb1r - dm2r = dma2r + dmb2r - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) - + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) - casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2 - - def get_veff (self, mol=None, dm1s=None, hermi=1, spin_sep=False, **kwargs): - ''' Returns a spin-summed veff! If dm1s isn't provided, builds from self.mo_coeff, self.ci - etc. ''' - if mol is None: mol = self.mol - nao = mol.nao_nr () - if dm1s is None: dm1s = self.make_rdm1 (include_core=True, **kwargs).reshape (nao, nao) - dm1s = np.asarray (dm1s) - if dm1s.ndim == 2: dm1s = dm1s[None,:,:] - if isinstance (self, _DFLASCI): - vj, vk = self.with_df.get_jk(dm1s, hermi=hermi) - else: - vj, vk = self._scf.get_jk(mol, dm1s, hermi=hermi) - if spin_sep: - assert (dm1s.shape[0] == 2) - return vj.sum (0)[None,:,:] - vk - else: - veff = np.stack ([j - k/2 for j, k in zip (vj, vk)], axis=0) - return np.squeeze (veff) - - def split_veff (self, veff, h2eff_sub, mo_coeff=None, ci=None, casdm1s_sub=None): - ''' Split a spin-summed veff into alpha and beta terms using the h2eff eri array. - Note that this will omit v(up_active - down_active)^virtual_inactive by necessity; - this won't affect anything because the inactive density matrix has no spin component. - On the other hand, it ~is~ necessary to correctly do - - v(up_active - down_active)^unactive_active - - in order to calculate the external orbital gradient at the end of the calculation. - This means that I need h2eff_sub spanning both at least two active subspaces - ~and~ the full orbital range. ''' - veff_c = veff.copy () - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci = ci) - ncore = self.ncore - ncas = self.ncas - nocc = ncore + ncas - nao, nmo = mo_coeff.shape - moH_coeff = mo_coeff.conjugate ().T - smo_coeff = self._scf.get_ovlp () @ mo_coeff - smoH_coeff = smo_coeff.conjugate ().T - veff_s = np.zeros_like (veff_c) - for ix, (ncas_i, casdm1s) in enumerate (zip (self.ncas_sub, casdm1s_sub)): - i = sum (self.ncas_sub[:ix]) - j = i + ncas_i - eri_k = h2eff_sub.reshape (nmo, ncas, -1)[:,i:j,...].reshape (nmo*ncas_i, -1) - eri_k = lib.numpy_helper.unpack_tril (eri_k)[:,i:j,:] - eri_k = eri_k.reshape (nmo, ncas_i, ncas_i, ncas) - sdm = casdm1s[0] - casdm1s[1] - vk_pa = -np.tensordot (eri_k, sdm, axes=((1,2),(0,1))) / 2 - veff_s[:,ncore:nocc] += vk_pa - veff_s[ncore:nocc,:] += vk_pa.T - veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:] / 2 - veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:].T / 2 - veff_s = smo_coeff @ veff_s @ smoH_coeff - veffa = veff_c + veff_s - veffb = veff_c - veff_s - return np.stack ([veffa, veffb], axis=0) - - - def states_energy_elec (self, mo_coeff=None, ncore=None, ncas=None, - ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, - casdm1frs=None, casdm2fr=None, veff_core=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - if ncas is None: ncas = self.ncas - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: ci = self.ci - if h2eff is None: h2eff = self.get_h2eff (mo_coeff) - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub) - nao, nmo = mo_coeff.shape - nocc = ncore + ncas - mo_core = mo_coeff[:,:ncore] - mo_cas = mo_coeff[:,ncore:nocc] - dm_core = 2*mo_core @ mo_core.conj ().T - if veff_core is None: veff_core = getattr (veff, 'c', None) - if veff_core is None: veff_core = self.get_veff (dm1s=dm_core) - h1eff = self.get_hcore () + veff_core - e0 = 2*np.dot (((h1eff-(veff_core/2)) @ mo_core).ravel (), mo_core.conj().ravel ()) - h1eff = mo_cas.conj ().T @ h1eff @ mo_cas - eri_cas = lib.numpy_helper.unpack_tril (h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)) - eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) - eri_cas = eri_cas[ncore:nocc] - casdm1rs = self.states_make_casdm1s (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, - casdm1frs=casdm1frs) - vj_r = np.tensordot (casdm1rs.sum (1), eri_cas, axes=2) - vk_rs = np.tensordot (casdm1rs, eri_cas, axes=((2,3),(2,1))) - veff_rs = vj_r[:,None,:,:] - vk_rs - - energy_elec = [] - for idx, (dm1s, v) in enumerate (zip (casdm1rs, veff_rs)): - casdm1fs = [dm[idx] for dm in casdm1frs] - casdm2f = [dm[idx] for dm in casdm2fr] - - # 1-body veff terms - h1e = h1eff[None,:,:] + v/2 - e1 = np.dot (h1e.ravel (), dm1s.ravel ()) - - # 2-body cumulant terms - e2 = 0 - for isub, (dm1s, dm2) in enumerate (zip (casdm1fs, casdm2f)): - dm1a, dm1b = dm1s[0], dm1s[1] - dm1 = dm1a + dm1b - cdm2 = dm2 - np.multiply.outer (dm1, dm1) - cdm2 += np.multiply.outer (dm1a, dm1a).transpose (0,3,2,1) - cdm2 += np.multiply.outer (dm1b, dm1b).transpose (0,3,2,1) - eri = self.get_h2eff_slice (h2eff, isub) - te2 = np.tensordot (eri, cdm2, axes=4) / 2 - e2 += te2 - energy_elec.append (e0 + e1 + e2) - self._e1_ref = e0 + e1 - self._e2_ref = e2 - - return energy_elec - - def energy_elec (self, mo_coeff=None, ncore=None, ncas=None, - ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' Since the LASCI energy cannot be calculated as simply as ecas + ecore, I need this ''' - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - if ncas is None: ncas = self.ncas - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: ci = self.ci - if h2eff is None: h2eff = self.get_h2eff (mo_coeff) - casdm1s_sub = self.make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, - casdm1frs=casdm1frs) - if veff is None: - veff = self.get_veff (dm1s = self.make_rdm1(mo_coeff=mo_coeff,casdm1s_sub=casdm1s_sub)) - veff = self.split_veff (veff, h2eff, mo_coeff=mo_coeff, casdm1s_sub=casdm1s_sub) - - # 1-body veff terms - h1e = self.get_hcore ()[None,:,:] + veff/2 - dm1s = self.make_rdm1s (mo_coeff=mo_coeff, ncore=ncore, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1s_sub=casdm1s_sub) - e1 = np.dot (h1e.ravel (), dm1s.ravel ()) - - # 2-body cumulant terms - casdm1s = self.make_casdm1s (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1frs=casdm1frs) - casdm1 = casdm1s.sum (0) - casdm2 = self.make_casdm2 (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, casdm2fr=casdm2fr) - casdm2 -= np.multiply.outer (casdm1, casdm1) - casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) - casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) - ncore, ncas, nocc = self.ncore, self.ncas, self.ncore + self.ncas - eri = lib.numpy_helper.unpack_tril (h2eff[ncore:nocc].reshape (ncas*ncas, -1)) - eri = eri.reshape ([ncas,]*4) - e2 = np.tensordot (eri, casdm2, axes=4)/2 - - e0 = self.energy_nuc () - self._e1_test = e1 - self._e2_test = e2 - return e1 + e2 - - _ugg = lasci_sync.LASCI_UnitaryGroupGenerators - def get_ugg (self, mo_coeff=None, ci=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - return self._ugg (self, mo_coeff, ci) - - def cderi_ao2mo (self, mo_i, mo_j, compact=False): - assert (isinstance (self, _DFLASCI)) - nmo_i, nmo_j = mo_i.shape[-1], mo_j.shape[-1] - if compact: - assert (nmo_i == nmo_j) - bPij = np.empty ((self.with_df.get_naoaux (), nmo_i*(nmo_i+1)//2), dtype=mo_i.dtype) - else: - bPij = np.empty ((self.with_df.get_naoaux (), nmo_i, nmo_j), dtype=mo_i.dtype) - ijmosym, mij_pair, moij, ijslice = ao2mo.incore._conc_mos (mo_i, mo_j, compact=compact) - b0 = 0 - for eri1 in self.with_df.loop (): - b1 = b0 + eri1.shape[0] - eri2 = bPij[b0:b1] - eri2 = ao2mo._ao2mo.nr_e2 (eri1, moij, ijslice, aosym='s2', mosym=ijmosym, out=eri2) - b0 = b1 - return bPij - - def fast_veffa (self, casdm1s_sub, h2eff_sub, mo_coeff=None, ci=None, _full=False): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - assert (isinstance (self, _DFLASCI) or _full) - ncore = self.ncore - ncas_sub = self.ncas_sub - ncas = sum (ncas_sub) - nocc = ncore + ncas - nao, nmo = mo_coeff.shape - - mo_cas = mo_coeff[:,ncore:nocc] - moH_cas = mo_cas.conjugate ().T - moH_coeff = mo_coeff.conjugate ().T - dma = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) - dmb = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) - casdm1s = np.stack ([dma, dmb], axis=0) - if not (isinstance (self, _DFLASCI)): - dm1s = np.dot (mo_cas, np.dot (casdm1s, moH_cas)).transpose (1,0,2) - return self.get_veff (dm1s = dm1s, spin_sep=True) - casdm1 = casdm1s.sum (0) - dm1 = np.dot (mo_cas, np.dot (casdm1, moH_cas)) - bPmn = sparsedf_array (self.with_df._cderi) - - # vj - dm_tril = dm1 + dm1.T - np.diag (np.diag (dm1.T)) - rho = np.dot (bPmn, lib.pack_tril (dm_tril)) - vj = lib.unpack_tril (np.dot (rho, bPmn)) - - # vk - bmPu = h2eff_sub.bmPu - if _full: - vmPsu = np.dot (bmPu, casdm1s) - vk = np.tensordot (vmPsu, bmPu, axes=((1,3),(1,2))).transpose (1,0,2) - return vj[None,:,:] - vk - else: - vmPu = np.dot (bmPu, casdm1) - vk = np.tensordot (vmPu, bmPu, axes=((1,2),(1,2))) - return vj - vk/2 - - @lib.with_doc(run_lasci.__doc__) - def lasci (self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, - assert_no_dupes=False, _dry_run=False): - if mo_coeff is None: mo_coeff=self.mo_coeff - if ci0 is None: ci0 = self.ci - if verbose is None: verbose = self.verbose - converged, e_tot, e_states, e_cas, e_lexc, ci = run_lasci ( - self, mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, - verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) - if _dry_run: return - self.converged, self.ci = converged, ci - self.e_tot, self.e_states, self.e_cas, self.e_lexc = e_tot, e_states, e_cas, e_lexc - if mo_coeff is self.mo_coeff: - self.dump_chk () - elif getattr (self, 'chkfile', None) is not None: - lib.logger.warn (self, 'orbitals changed; chkfile not dumped!') - self._finalize () - return self.converged, self.e_tot, self.e_states, self.e_cas, e_lexc, self.ci - - @lib.with_doc(run_lasci.__doc__) - def lasci_(self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, - assert_no_dupes=False, _dry_run=False): - if mo_coeff is not None: - self.mo_coeff = mo_coeff - return self.lasci (mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, - verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) - - state_average = state_average - state_average_ = state_average_ - get_single_state_las = get_single_state_las - - def lassi(self, mo_coeff=None, ci=None, veff_c=None, h2eff_sub=None, orbsym=None, - soc=False, break_symmetry=False, opt=1, **kwargs): - #import warnings - #lassi_kernel_warn = "Now LASSI have kernel, which takes las instance as input. This [las.lassi()] function " \ - # "will be removed soon." - #warnings.warn(lassi_kernel_warn, stacklevel=3) - from mrh.my_pyscf.lassi import lassi - mylassi = lassi.LASSI(self, mo_coeff=mo_coeff, ci=ci, soc=soc, opt=opt, - break_symmetry=break_symmetry, **kwargs) - return mylassi.kernel(mo_coeff=mo_coeff, ci=ci, veff_c=veff_c, h2eff_sub=h2eff_sub, - orbsym=orbsym) - - las2cas_civec = las2cas_civec - assert_no_duplicates = assert_no_duplicates - get_init_guess_ci = get_init_guess_ci - localize_init_guess=lasscf_guess.localize_init_guess - def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): - if s is None: s = self._scf.get_ovlp () - return matrix_svd_control_options (s, lspace=mo_lspace, rspace=mo_rspace, full_matrices=True)[:3] - - def dump_flags (self, verbose=None, _method_name='LASCI'): - log = lib.logger.new_logger (self, verbose) - log.info ('') - log.info ('******** %s flags ********', _method_name) - ncore = self.ncore - ncas = self.ncas - nvir = self.mo_coeff.shape[1] - ncore - ncas - nfrags = len (self.nelecas_sub) - log.info ('CAS (%de+%de, %do), ncore = %d, nvir = %d', - self.nelecas[0], self.nelecas[1], ncas, ncore, nvir) - log.info ('Divided into %d LAS spaces', nfrags) - for i, (no, ne) in enumerate (zip (self.ncas_sub, self.nelecas_sub)): - log.info ('LAS %d : (%de+%de, %do)', i, ne[0], ne[1], no) - log.info ('nroots = %d', self.nroots) - log.info ('max_memory %d (MB)', self.max_memory) - for i, fcibox in enumerate (self.fciboxes): - if getattr (fcibox, 'dump_flags', None): - log.info ('fragment %d FCI solver flags:', i) - fcibox.dump_flags (log.verbose) - - @property - def converged (self): - return all (self.states_converged) - @converged.setter - def converged (self, x): - if hasattr (x, '__len__'): - self.states_converged = list (x) - else: - self.states_converged = [x,]*self.nroots - - def dump_spaces (self, nroots=None, sort_energy=False): - log = lib.logger.new_logger (self, self.verbose) - log.info ("******** LAS space tables ********") - ci = self.ci - if nroots is None and self.verbose <= lib.logger.INFO: - nroots = min (self.nroots, 100) - elif nroots is None: - nroots = self.nroots - if nroots < self.nroots: - log.warn ("Dumping only 100 of %d spaces", self.nroots) - log.warn ("To see more, explicitly pass nroots to dump_spaces or increase verbosity") - if sort_energy: - idx = np.argsort (self.e_states) - else: - idx = range (nroots) - for state in idx: - neleca_f = [] - nelecb_f = [] - wfnsym_f = [] - wfnsym = 0 - m_f = [] - s_f = [] - lroots = [] - s2_tot = 0 - for ifrag, (fcibox, nelecas) in enumerate (zip (self.fciboxes, self.nelecas_sub)): - solver = fcibox.fcisolvers[state] - na, nb = _unpack_nelec (fcibox._get_nelec (solver, nelecas)) - neleca_f.append (na) - nelecb_f.append (nb) - m_f.append ((na-nb)/2) - s_f.append ((solver.smult-1)/2) - s2_tot += s_f[-1] * (s_f[-1] + 1) - fragsym = getattr (solver, 'wfnsym', 0) or 0 - if isinstance (fragsym, str): - fragsym_str = fragsym - fragsym_id = symm.irrep_name2id (solver.mol.groupname, fragsym) - else: - fragsym_id = fragsym - fragsym_str = symm.irrep_id2name (solver.mol.groupname, fragsym) - wfnsym ^= fragsym_id - wfnsym_f.append (fragsym_str) - lroots_i = 0 - if ci is not None: - if ci[ifrag] is not None: - ci_i = ci[ifrag] - if ci_i[state] is not None: - ci_ij = ci_i[state] - lroots_i = 1 if ci_ij.ndim<3 else ci_ij.shape[0] - lroots.append (lroots_i) - s2_tot += sum ([2*m1*m2 for m1, m2 in combinations (m_f, 2)]) - s_f, m_f = np.asarray (s_f), np.asarray (m_f) - if np.all (m_f<0): m_f *= -1 - s_pure = bool (np.all (s_f==m_f)) - wfnsym = symm.irrep_id2name (self.mol.groupname, wfnsym) - neleca = sum (neleca_f) - nelecb = sum (nelecb_f) - log.info ("LAS space %d: (%de+%de,%do) wfynsm=%s", state, neleca, nelecb, self.ncas, wfnsym) - log.info ("Converged? %s", self.states_converged[state]) - log.info ("E(LAS) = %.15g", self.e_states[state]) - log.info ("S^2 = %.7f (%s)", s2_tot, ('Impure','Pure')[s_pure]) - log.info ("Space table") - log.info (" frag (ae+be,no) 2S+1 ir lroots") - for i in range (self.nfrags): - smult_f = int (round (2*s_f[i] + 1)) - tupstr = '({}e+{}e,{}o)'.format (neleca_f[i], nelecb_f[i], self.ncas_sub[i]) - log.info (" %4d %13s %4d %3s %6d", i, tupstr, smult_f, wfnsym_f[i], lroots[i]) - - def check_sanity (self): - casci.CASCI.check_sanity (self) - self.get_ugg () # constructor encounters impossible states and raises error - - dump_chk = chkfile.dump_las - load_chk = load_chk_ = chkfile.load_las_ - -class LASCISymm (casci_symm.CASCI, LASCINoSymm): - - def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, wfnsym_sub=None, frozen=None, - **kwargs): - LASCINoSymm.__init__(self, mf, ncas, nelecas, ncore=ncore, spin_sub=spin_sub, - frozen=frozen, **kwargs) - if getattr (self.mol, 'groupname', None) in ('Dooh', 'Coov'): - raise NotImplementedError ("LASSCF support for cylindrical point group {}".format ( - self.mol.groupname)) - if wfnsym_sub is None: wfnsym_sub = [0 for icas in self.ncas_sub] - # TODO: guess wfnsym_sub intelligently (0 can be impossible for some multiplicities) - for wfnsym, frag in zip (wfnsym_sub, self.fciboxes): - if isinstance (wfnsym, (str, np.str_)): - wfnsym = symm.irrep_name2id (self.mol.groupname, wfnsym) - frag.fcisolvers[0].wfnsym = wfnsym - - make_rdm1s = LASCINoSymm.make_rdm1s - make_rdm1 = LASCINoSymm.make_rdm1 - get_veff = LASCINoSymm.get_veff - get_h1eff = get_h1las = h1e_for_las - dump_flags = LASCINoSymm.dump_flags - dump_spaces = LASCINoSymm.dump_spaces - check_sanity = LASCINoSymm.check_sanity - _ugg = lasci_sync.LASCISymm_UnitaryGroupGenerators - - @property - def wfnsym (self): - ''' This now returns the product of the irreps of the subspaces ''' - wfnsym = [0,]*self.nroots - for frag in self.fciboxes: - for state, solver in enumerate (frag.fcisolvers): - wfnsym[state] ^= solver.wfnsym - if self.nroots == 1: wfnsym = wfnsym[0] - return wfnsym - @wfnsym.setter - def wfnsym (self, ir): - raise RuntimeError (("Cannot assign the whole-system symmetry of a LASCI wave function. " - "Address fciboxes[ifrag].fcisolvers[istate].wfnsym instead.")) - - def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, verbose=None, assert_no_dupes=False): - if mo_coeff is None: - mo_coeff = self.mo_coeff - if ci0 is None: - ci0 = self.ci - - # Initialize/overwrite mo_coeff.orbsym. Don't pass ci0 because it's not the right shape - lib.logger.info (self, ("LASCI lazy hack note: lines below reflect the point-group " - "symmetry of the whole molecule but not of the individual " - "subspaces")) - mo_coeff = self.mo_coeff = self.label_symmetry_(mo_coeff) - return LASCINoSymm.kernel(self, mo_coeff=mo_coeff, ci0=ci0, - casdm0_fr=casdm0_fr, verbose=verbose, assert_no_dupes=assert_no_dupes) - - def canonicalize (self, mo_coeff=None, ci=None, natorb_casdm1=None, veff=None, h2eff_sub=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - mo_coeff = self.label_symmetry_(mo_coeff) - return canonicalize (self, mo_coeff=mo_coeff, ci=ci, natorb_casdm1=natorb_casdm1, - h2eff_sub=h2eff_sub, orbsym=mo_coeff.orbsym) - - def label_symmetry_(self, mo_coeff=None): - if mo_coeff is None: mo_coeff=self.mo_coeff - ncore = self.ncore - ncas_sub = self.ncas_sub - nocc = ncore + sum (ncas_sub) - mo_coeff[:,:ncore] = symm.symmetrize_space (self.mol, mo_coeff[:,:ncore]) - for isub, ncas in enumerate (ncas_sub): - i = ncore + sum (ncas_sub[:isub]) - j = i + ncas - mo_coeff[:,i:j] = symm.symmetrize_space (self.mol, mo_coeff[:,i:j]) - mo_coeff[:,nocc:] = symm.symmetrize_space (self.mol, mo_coeff[:,nocc:]) - orbsym = symm.label_orb_symm (self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_coeff, - s=self._scf.get_ovlp ()) - mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) - return mo_coeff - - @lib.with_doc(LASCINoSymm.localize_init_guess.__doc__) - def localize_init_guess (self, frags_atoms, mo_coeff=None, spin=None, lo_coeff=None, fock=None, - freeze_cas_spaces=False): - if mo_coeff is None: - mo_coeff = self.mo_coeff - mo_coeff = casci_symm.label_symmetry_(self, mo_coeff) - return LASCINoSymm.localize_init_guess (self, frags_atoms, mo_coeff=mo_coeff, spin=spin, - lo_coeff=lo_coeff, fock=fock, freeze_cas_spaces=freeze_cas_spaces) - - def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): - if s is None: s = self._scf.get_ovlp () - lsymm = getattr (mo_lspace, 'orbsym', None) - if lsymm is None: - mo_lspace = symm.symmetrize_space (self.mol, mo_lspace) - lsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_lspace, s=s) - rsymm = getattr (mo_rspace, 'orbsym', None) - if rsymm is None: - mo_rspace = symm.symmetrize_space (self.mol, mo_rspace) - rsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_rspace, s=s) - decomp = matrix_svd_control_options (s, - lspace=mo_lspace, rspace=mo_rspace, - lspace_symmetry=lsymm, rspace_symmetry=rsymm, - full_matrices=True, strong_symm=True) - mo_lvecs, svals, mo_rvecs, lsymm, rsymm = decomp - mo_lvecs = lib.tag_array (mo_lvecs, orbsym=lsymm) - mo_rvecs = lib.tag_array (mo_rvecs, orbsym=rsymm) - return mo_lvecs, svals, mo_rvecs - diff --git a/my_pyscf/mcscf/lasci_sv_expanded.py b/my_pyscf/mcscf/lasci_sv_expanded.py deleted file mode 100644 index e10ce953..00000000 --- a/my_pyscf/mcscf/lasci_sv_expanded.py +++ /dev/null @@ -1,2060 +0,0 @@ -from pyscf.scf.rohf import get_roothaan_fock -from pyscf import fci -from pyscf.fci import cistring -from pyscf.mcscf import casci, casci_symm, df -from pyscf.tools import dump_mat -from pyscf import symm, gto, scf, ao2mo, lib -from pyscf.fci.direct_spin1 import _unpack_nelec -from mrh.my_pyscf.mcscf.addons import state_average_n_mix, get_h1e_zipped_fcisolver, las2cas_civec -from mrh.my_pyscf.mcscf import lasci_sync, _DFLASCI, lasscf_guess -from mrh.my_pyscf.fci import csf_solver -from mrh.my_pyscf.df.sparse_df import sparsedf_array -from mrh.my_pyscf.mcscf import chkfile -from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver -from mrh.util.la import matrix_svd_control_options -from itertools import combinations, product -from scipy.sparse import linalg as sparse_linalg -from scipy import linalg -import numpy as np -import copy - -def LASCI (mf_or_mol, ncas_sub, nelecas_sub, **kwargs): - if isinstance(mf_or_mol, gto.Mole): - mf = scf.RHF(mf_or_mol) - else: - mf = mf_or_mol - if mf.mol.symmetry: - las = LASCISymm (mf, ncas_sub, nelecas_sub, **kwargs) - else: - las = LASCINoSymm (mf, ncas_sub, nelecas_sub, **kwargs) - if getattr (mf, 'with_df', None): - las = density_fit (las, with_df = mf.with_df) - return las - -def get_grad (las, mo_coeff=None, ci=None, ugg=None, h1eff_sub=None, h2eff_sub=None, - veff=None, dm1s=None): - '''Return energy gradient for orbital rotation and CI relaxation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - ugg : instance of :class:`LASCI_UnitaryGroupGenerators` - h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray - Contains effective one-electron Hamiltonians experienced by each fragment - in each state - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - dm1s : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-RDM in the AO basis - - Returns: - gorb : ndarray of shape (ugg.nvar_orb,) - Orbital rotation gradients as a flat array - gci : ndarray of shape (sum(ugg.ncsf_sub),) - CI relaxation gradients as a flat array - gx : ndarray - Orbital rotation gradients for temporarily frozen orbitals in the "LASCI" problem - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if ugg is None: ugg = las.get_ugg (mo_coeff, ci) - if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if veff is None: - veff = las.get_veff (dm1s = dm1s.sum (0)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) - if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, - h2eff_sub=h2eff_sub) - - gorb = get_grad_orb (las, mo_coeff=mo_coeff, ci=ci, h2eff_sub=h2eff_sub, veff=veff, dm1s=dm1s) - gci = get_grad_ci (las, mo_coeff=mo_coeff, ci=ci, h1eff_sub=h1eff_sub, h2eff_sub=h2eff_sub, - veff=veff) - - idx = ugg.get_gx_idx () - gx = gorb[idx] - gint = ugg.pack (gorb, gci) - gorb = gint[:ugg.nvar_orb] - gci = gint[ugg.nvar_orb:] - return gorb, gci, gx.ravel () - -def get_grad_orb (las, mo_coeff=None, ci=None, h2eff_sub=None, veff=None, dm1s=None, hermi=-1): - '''Return energy gradient for orbital rotation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - dm1s : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-RDM in the AO basis - hermi : integer - Control (anti-)symmetrization. 0 means to return the effective Fock matrix, - F1 = h.D + g.d. -1 means to return the true orbital-rotation gradient, which is skew- - symmetric: gorb = F1 - F1.T. +1 means to return the symmetrized effective Fock matrix, - (F1 + F1.T) / 2. The factor of 2 difference between hermi=-1 and the other two options - is intentional and necessary. - - Returns: - gorb : ndarray of shape (nmo,nmo) - Orbital rotation gradients as a square antihermitian array - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if veff is None: - veff = las.get_veff (dm1s = dm1s.sum (0)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) - nao, nmo = mo_coeff.shape - ncore = las.ncore - ncas = las.ncas - nocc = las.ncore + las.ncas - smo_cas = las._scf.get_ovlp () @ mo_coeff[:,ncore:nocc] - smoH_cas = smo_cas.conj ().T - - # The orbrot part - h1s = las.get_hcore ()[None,:,:] + veff - f1 = h1s[0] @ dm1s[0] + h1s[1] @ dm1s[1] - f1 = mo_coeff.conjugate ().T @ f1 @ las._scf.get_ovlp () @ mo_coeff - # ^ I need the ovlp there to get dm1s back into its correct basis - casdm2 = las.make_casdm2 (ci=ci) - casdm1s = np.stack ([smoH_cas @ d @ smo_cas for d in dm1s], axis=0) - casdm1 = casdm1s.sum (0) - casdm2 -= np.multiply.outer (casdm1, casdm1) - casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) - casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) - eri = h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2) - eri = lib.numpy_helper.unpack_tril (eri).reshape (nmo, ncas, ncas, ncas) - f1[:,ncore:nocc] += np.tensordot (eri, casdm2, axes=((1,2,3),(1,2,3))) - - if hermi == -1: - return f1 - f1.T - elif hermi == 1: - return .5*(f1+f1.T) - elif hermi == 0: - return f1 - else: - raise ValueError ("kwarg 'hermi' must = -1, 0, or +1") - -def get_grad_ci (las, mo_coeff=None, ci=None, h1eff_sub=None, h2eff_sub=None, veff=None): - '''Return energy gradient for CI relaxation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray - Contains effective one-electron Hamiltonians experienced by each fragment - in each state - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - - Returns: - gci : list (length=nfrags) of list (length=nroots) of ndarray - CI relaxation gradients in the shape of CI vectors - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, - h2eff_sub=h2eff_sub) - gci = [] - for isub, (fcibox, h1e, ci0, ncas, nelecas) in enumerate (zip ( - las.fciboxes, h1eff_sub, ci, las.ncas_sub, las.nelecas_sub)): - eri_cas = las.get_h2eff_slice (h2eff_sub, isub, compact=8) - linkstrl = fcibox.states_gen_linkstr (ncas, nelecas, True) - linkstr = fcibox.states_gen_linkstr (ncas, nelecas, False) - h2eff = fcibox.states_absorb_h1e(h1e, eri_cas, ncas, nelecas, .5) - hc0 = fcibox.states_contract_2e(h2eff, ci0, ncas, nelecas, link_index=linkstrl) - hc0 = [hc.ravel () for hc in hc0] - ci0 = [c.ravel () for c in ci0] - gci.append ([2.0 * (hc - c * (c.dot (hc))) for c, hc in zip (ci0, hc0)]) - return gci - -def density_fit (las, auxbasis=None, with_df=None): - ''' Here I ONLY need to attach the tag and the df object because I put conditionals in - LASCINoSymm to make my life easier ''' - las_class = las.__class__ - if with_df is None: - if (getattr(las._scf, 'with_df', None) and - (auxbasis is None or auxbasis == las._scf.with_df.auxbasis)): - with_df = las._scf.with_df - else: - with_df = df.DF(las.mol) - with_df.max_memory = las.max_memory - with_df.stdout = las.stdout - with_df.verbose = las.verbose - with_df.auxbasis = auxbasis - class DFLASCI (las_class, _DFLASCI): - def __init__(self, scf, ncas_sub, nelecas_sub): - self.with_df = with_df - self._keys = self._keys.union(['with_df']) - las_class.__init__(self, scf, ncas_sub, nelecas_sub) - new_las = DFLASCI (las._scf, las.ncas_sub, las.nelecas_sub) - new_las.__dict__.update (las.__dict__) - return new_las - -def h1e_for_las (las, mo_coeff=None, ncas=None, ncore=None, nelecas=None, ci=None, ncas_sub=None, - nelecas_sub=None, veff=None, h2eff_sub=None, casdm1s_sub=None, casdm1frs=None): - ''' Effective one-body Hamiltonians (plural) for a LASCI problem - - Args: - las: a LASCI object - - Kwargs: - mo_coeff: ndarray of shape (nao,nmo) - Orbital coefficients ordered on the columns as: - core orbitals, subspace 1, subspace 2, ..., external orbitals - ncas: integer - As in PySCF's existing CASCI/CASSCF implementation - nelecas: sequence of 2 integers - As in PySCF's existing CASCI/CASSCF implementation - ci: list (length=nfrags) of list (length=nroots) of ndarrays - Contains CI vectors - ncas_sub: ndarray of shape (nsub) - Number of active orbitals in each subspace - nelecas_sub: ndarray of shape (nsub,2) - na, nb in each subspace - veff: ndarray of shape (2, nao, nao) - Contains spin-separated, state-averaged effective potential - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - casdm1s_sub : list (length=nfrags) of ndarrays - Contains state-averaged, spin-separated 1-RDMs in the localized active subspaces - casdm1frs : list (length=nfrags) of list (length=nroots) of ndarrays - Contains spin-separated 1-RDMs for each state in the localized active subspaces - - Returns: - h1e_fr: list (length=nfrags) of list (length=nroots) of ndarrays - Spin-separated 1-body Hamiltonian operator for each fragment and state - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ncas is None: ncas = las.ncas - if ncore is None: ncore = las.ncore - if ncas_sub is None: ncas_sub = las.ncas_sub - if nelecas_sub is None: nelecas_sub = las.nelecas_sub - if ncore is None: ncore = las.ncore - if ci is None: ci = las.ci - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if casdm1frs is None: casdm1frs = las.states_make_casdm1s_sub (ci=ci) - if casdm1s_sub is None: casdm1s_sub = [np.einsum ('rsij,r->sij',dm,las.weights) - for dm in casdm1frs] - if veff is None: - veff = las.get_veff (dm1s = las.make_rdm1 (mo_coeff=mo_coeff, ci=ci)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci, casdm1s_sub=casdm1s_sub) - - # First pass: split by root - nocc = ncore + ncas - nmo = mo_coeff.shape[-1] - mo_cas = mo_coeff[:,ncore:nocc] - moH_cas = mo_cas.conj ().T - h1e = moH_cas @ (las.get_hcore ()[None,:,:] + veff) @ mo_cas - h1e_r = np.empty ((las.nroots, 2, ncas, ncas), dtype=h1e.dtype) - h2e = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, - ncas*(ncas+1)//2)).reshape (nmo, ncas, ncas, ncas)[ncore:nocc,:,:,:] - #print ("SV h2e = ",h2e, h2e.shape) - avgdm1s = np.stack ([linalg.block_diag (*[dm[spin] for dm in casdm1s_sub]) - for spin in range (2)], axis=0) - for state in range (las.nroots): - statedm1s = np.stack ([linalg.block_diag (*[dm[state][spin] for dm in casdm1frs]) - for spin in range (2)], axis=0) - dm1s = statedm1s - avgdm1s - j = np.tensordot (dm1s, h2e, axes=((1,2),(2,3))) - k = np.tensordot (dm1s, h2e, axes=((1,2),(2,1))) - h1e_r[state] = h1e + j + j[::-1] - k - - - # Second pass: split by fragment and subtract double-counting - h1e_fr = [] - for ix, casdm1s_r in enumerate (casdm1frs): - p = sum (las.ncas_sub[:ix]) - q = p + las.ncas_sub[ix] - h1e = h1e_r[:,:,p:q,p:q] - h2e = las.get_h2eff_slice (h2eff_sub, ix) - j = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,3))) - k = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,1))) - h1e_fr.append (h1e - j - j[:,::-1] + k) - - return h1e_fr - -def get_fock (las, mo_coeff=None, ci=None, eris=None, casdm1s=None, verbose=None, veff=None, - dm1s=None): - ''' f_pq = h_pq + (g_pqrs - g_psrq/2) D_rs, AO basis - Note the difference between this and h1e_for_las: h1e_for_las only has - JK terms from electrons outside the "current" active subspace; get_fock - includes JK from all electrons. This is also NOT the "generalized Fock matrix" - of orbital gradients (but it can be used in calculating those if you do a - semi-cumulant decomposition). - The "eris" kwarg does not do anything and is retained only for backwards - compatibility (also why I don't just call las.make_rdm1) ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if dm1s is None: - if casdm1s is None: casdm1s = las.make_casdm1s (ci=ci) - mo_cas = mo_coeff[:,las.ncore:][:,:las.ncas] - moH_cas = mo_cas.conjugate ().T - mo_core = mo_coeff[:,:las.ncore] - moH_core = mo_core.conjugate ().T - dm1s = [(mo_core @ moH_core) + (mo_cas @ d @ moH_cas) for d in list(casdm1s)] - if veff is not None: - fock = las.get_hcore()[None,:,:] + veff - return get_roothaan_fock (fock, dm1s, las._scf.get_ovlp ()) - dm1 = dm1s[0] + dm1s[1] - if isinstance (las, _DFLASCI): - vj, vk = las.with_df.get_jk(dm1, hermi=1) - else: - vj, vk = las._scf.get_jk(las.mol, dm1, hermi=1) - fock = las.get_hcore () + vj - (vk/2) - return fock - -def _eig_inactive_virtual (las, fock, orbsym=None): - '''Generate the unitary matrix canonicalizing the inactive and virtual orbitals only. - - Args: - las : object of :class:`LASCINoSymm` - fock : ndarray of shape (nmo,nmo) - Contains Fock matrix in MO basis - - Kwargs: - orbsym : list of length nmo - umat : ndarray of shape (nmo, nmo) - - Returns: - ene : ndarray of shape (nmo,) - umat : ndarray of shape (nmo, nmo)''' - nmo = fock.shape[0] - ncore = las.ncore - nocc = ncore + las.ncas - ene = np.zeros (nmo) - umat = np.eye (nmo) - if ncore: - orbsym_i = None if orbsym is None else orbsym[:ncore] - fock_i = fock[:ncore,:ncore] - ene[:ncore], umat[:ncore,:ncore] = las._eig (fock_i, 0, 0, orbsym_i) - if nmo-nocc: - orbsym_i = None if orbsym is None else orbsym[nocc:] - fock_i = fock[nocc:,nocc:] - ene[nocc:], umat[nocc:,nocc:] = las._eig (fock_i, 0, 0, orbsym_i) - return ene, umat - -def canonicalize (las, mo_coeff=None, ci=None, casdm1fs=None, natorb_casdm1=None, veff=None, - h2eff_sub=None, orbsym=None): - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - - # In-place safety - mo_coeff = mo_coeff.copy () - ci = copy.deepcopy (ci) - - # Temporary lroots safety - # The desired behavior is that the inactive and external orbitals should - # be canonicalized according to the density matrix used for orbital optimization - # TODO: once orbital optimization with lroots is enabled, change this behavior - # TODO: possibly move this logic to the make_casdm* functions - if casdm1fs is None: - ci_dm = [] - for i in range (len (ci)): - ci_i = [] - for j in range (len (ci[i])): - if ci[i][j].ndim>2: - ci_i.append (ci[i][j][0]) - else: - ci_i.append (ci[i][j]) - ci_dm.append (ci_i) - casdm1fs = las.make_casdm1s_sub (ci=ci_dm) - - nao, nmo = mo_coeff.shape - ncore = las.ncore - nocc = ncore + las.ncas - ncas_sub = las.ncas_sub - nelecas_sub = las.nelecas_sub - - # Passing casdm1 or lasdm1 only affects the canonicalization of the active orbitals - umat = np.zeros_like (mo_coeff) - casdm1s = np.stack ([linalg.block_diag (*[dm[0] for dm in casdm1fs]), - linalg.block_diag (*[dm[1] for dm in casdm1fs])], axis=0) - fock = mo_coeff.conjugate ().T @ las.get_fock (mo_coeff=mo_coeff, casdm1s=casdm1s, veff=veff) - fock = fock @ mo_coeff - if natorb_casdm1 is None: # State-average natural orbitals by default - natorb_casdm1 = casdm1s.sum (0) - - # Inactive-inactive and virtual-virtual - ene, umat = _eig_inactive_virtual (las, fock, orbsym=orbsym) - idx = np.arange (nmo, dtype=int) - if ncore: idx[:ncore] = idx[:ncore][np.argsort (ene[:ncore])] - if nmo-nocc: idx[nocc:] = idx[nocc:][np.argsort (ene[nocc:])] - umat = umat[:,idx] - if orbsym is not None: orbsym = orbsym[idx] - # Active-active - check_diag = natorb_casdm1.copy () - for ix, ncas in enumerate (ncas_sub): - i = sum (ncas_sub[:ix]) - j = i + ncas - check_diag[i:j,i:j] = 0.0 - is_block_diag = np.amax (np.abs (check_diag)) < 1e-8 - if is_block_diag: - # No off-diagonal RDM elements -> extra effort to prevent diagonalizer from breaking frags - for isub, (ncas, nelecas) in enumerate (zip (ncas_sub, nelecas_sub)): - i = sum (ncas_sub[:isub]) - j = i + ncas - dm1 = natorb_casdm1[i:j,i:j] - i += ncore - j += ncore - orbsym_i = None if orbsym is None else orbsym[i:j] - occ, umat[i:j,i:j] = las._eig (dm1, 0, 0, orbsym_i) - idx = np.argsort (occ)[::-1] - umat[i:j,i:j] = umat[i:j,i:j][:,idx] - if orbsym_i is not None: orbsym[i:j] = orbsym[i:j][idx] - if ci is not None: - fcibox = las.fciboxes[isub] - ci[isub] = fcibox.states_transform_ci_for_orbital_rotation ( - ci[isub], ncas, nelecas, umat[i:j,i:j]) - else: # You can't get proper LAS-type CI vectors w/out active space fragmentation - ci = None - orbsym_cas = None if orbsym is None else orbsym[ncore:nocc] - occ, umat[ncore:nocc,ncore:nocc] = las._eig (natorb_casdm1, 0, 0, orbsym_cas) - idx = np.argsort (occ)[::-1] - umat[ncore:nocc,ncore:nocc] = umat[ncore:nocc,ncore:nocc][:,idx] - if orbsym_cas is not None: orbsym[ncore:nocc] = orbsym[ncore:nocc][idx] - - # Final - mo_occ = np.zeros (nmo, dtype=natorb_casdm1.dtype) - if ncore: mo_occ[:ncore] = 2 - ucas = umat[ncore:nocc,ncore:nocc] - mo_occ[ncore:nocc] = ((natorb_casdm1 @ ucas) * ucas).sum (0) - mo_ene = ((fock @ umat) * umat.conjugate ()).sum (0) - mo_ene[ncore:][:sum (ncas_sub)] = 0.0 - mo_coeff = mo_coeff @ umat - if orbsym is not None: - ''' - print ("This is the second call to label_orb_symm inside of canonicalize") - orbsym = symm.label_orb_symm (las.mol, las.mol.irrep_id, - las.mol.symm_orb, mo_coeff, - s=las._scf.get_ovlp ()) - #mo_coeff = las.label_symmetry_(mo_coeff) - ''' - mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) - if h2eff_sub is not None: - h2eff_sub = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*las.ncas, -1)) - h2eff_sub = h2eff_sub.reshape (nmo, las.ncas, las.ncas, las.ncas) - h2eff_sub = np.tensordot (umat, h2eff_sub, axes=((0),(0))) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(1))).transpose (1,0,2,3) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(2))).transpose (1,2,0,3) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(3))).transpose (1,2,3,0) - h2eff_sub = h2eff_sub.reshape (nmo*las.ncas, las.ncas, las.ncas) - h2eff_sub = lib.numpy_helper.pack_tril (h2eff_sub).reshape (nmo, -1) - - # I/O - log = lib.logger.new_logger (las, las.verbose) - if las.verbose >= lib.logger.INFO: - if is_block_diag: - for isub, nlas in enumerate (ncas_sub): - log.info ("Fragment %d natural orbitals", isub) - i = ncore + sum (ncas_sub[:isub]) - j = i + nlas - log.info ('Natural occ %s', str (mo_occ[i:j])) - log.info ('Natural orbital (expansion on AOs) in CAS space') - label = las.mol.ao_labels() - mo_las = mo_coeff[:,i:j] - dump_mat.dump_rec(log.stdout, mo_las, label, start=1) - else: - log.info ("Delocalized natural orbitals do not reflect LAS fragmentation") - log.info ('Natural occ %s', str (mo_occ[ncore:nocc])) - log.info ('Natural orbital (expansion on AOs) in CAS space') - label = las.mol.ao_labels() - mo_las = mo_coeff[:,ncore:nocc] - dump_mat.dump_rec(log.stdout, mo_las, label, start=1) - - return mo_coeff, mo_ene, mo_occ, ci, h2eff_sub - -def get_init_guess_ci (las, mo_coeff=None, h2eff_sub=None, ci0=None): - # TODO: come up with a better algorithm? This might be working better than what I had before - # but it omits inter-active Coulomb and exchange interactions altogether. Is there a - # non-outer-product algorithm for finding the lowest-energy single product of CSFs? - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci0 is None: ci0 = [[None for i in range (las.nroots)] for j in range (las.nfrags)] - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - nmo = mo_coeff.shape[-1] - ncore, ncas = las.ncore, las.ncas - nocc = ncore + ncas - dm1_core= 2 * mo_coeff[:,:ncore] @ mo_coeff[:,:ncore].conj ().T - h1e_ao = las._scf.get_fock (dm=dm1_core) - eri_cas = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)) - eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) - eri_cas = eri_cas[ncore:nocc] - for ix, (fcibox, norb, nelecas) in enumerate (zip (las.fciboxes,las.ncas_sub,las.nelecas_sub)): - i = sum (las.ncas_sub[:ix]) - j = i + norb - mo = mo_coeff[:,ncore+i:ncore+j] - moH = mo.conj ().T - h1e = moH @ h1e_ao @ mo - h1e = [h1e, h1e] - eri = eri_cas[i:j,i:j,i:j,i:j] - for iy, solver in enumerate (fcibox.fcisolvers): - nelec = fcibox._get_nelec (solver, nelecas) - ndet = tuple ([cistring.num_strings (norb, n) for n in nelec]) - if isinstance (ci0[ix][iy], np.ndarray) and ci0[ix][iy].size==ndet[0]*ndet[1]: continue - if hasattr (mo_coeff, 'orbsym'): - solver.orbsym = mo_coeff.orbsym[ncore+i:ncore+j] - hdiag_csf = solver.make_hdiag_csf (h1e, eri, norb, nelec, max_memory=las.max_memory) - ci0[ix][iy] = solver.get_init_guess (norb, nelec, solver.nroots, hdiag_csf) - if solver.nroots==1: - ci0[ix][iy] = ci0[ix][iy][0] - else: - ci0[ix][iy] = np.stack (ci0[ix][iy], axis=0) - return ci0 - -def get_space_info (las): - ''' Retrieve the quantum numbers defining the states of a LASSCF calculation ''' - nfrags, nroots = las.nfrags, las.nroots - charges = np.zeros ((nroots, nfrags), dtype=np.int32) - wfnsyms, spins, smults = charges.copy (), charges.copy (), charges.copy () - for ifrag, fcibox in enumerate (las.fciboxes): - for iroot, solver in enumerate (fcibox.fcisolvers): - nelec = fcibox._get_nelec (solver, las.nelecas_sub[ifrag]) - charges[iroot,ifrag] = np.sum (las.nelecas_sub[ifrag]) - np.sum (nelec) - spins[iroot,ifrag] = nelec[0]-nelec[1] - smults[iroot,ifrag] = solver.smult - try: - wfnsyms[iroot,ifrag] = solver.wfnsym or 0 - except ValueError as e: - wfnsyms[iroot,ifrag] = symm.irrep_name2id (las.mol.groupname, solver.wfnsym) - return charges, spins, smults, wfnsyms - -def assert_no_duplicates (las, tab=None): - log = lib.logger.new_logger (las, las.verbose) - if tab is None: tab = np.stack (get_space_info (las), axis=-1) - tab_uniq, uniq_idx, uniq_inv, uniq_cnts = np.unique (tab, return_index=True, - return_inverse=True, return_counts=True, axis=0) - idx_dupe = uniq_cnts>1 - try: - err_str = ('LAS state basis has duplicates; details in logfile for ' - 'verbose >= INFO (4) [more details for verbose > INFO].\n' - '(Disable this assertion by passing assert_no_dupes=False ' - 'to the kernel, lasci, and state_average(_) functions.)') - assert (~np.any (idx_dupe)), err_str - except AssertionError as e: - dupe_idx = uniq_idx[idx_dupe] - dupe_cnts = uniq_cnts[idx_dupe] - for i, (ix, cnt, col) in enumerate (zip (uniq_idx, uniq_cnts, tab_uniq)): - if cnt==1: continue - log.info ('State %d appears %d times', ix, cnt) - idx_thisdupe = np.where (uniq_inv==i)[0] - row = col.T - log.debug ('As states {}'.format (idx_thisdupe)) - log.debug ('Charges = {}'.format (row[0])) - log.debug ('2M_S = {}'.format (row[1])) - log.debug ('2S+1 = {}'.format (row[2])) - log.debug ('Wfnsyms = {}'.format (row[3])) - raise e from None - -def state_average_(las, weights=[0.5,0.5], charges=None, spins=None, - smults=None, wfnsyms=None, assert_no_dupes=True): - ''' Transform LASCI/LASSCF object into state-average LASCI/LASSCF - - Args: - las: LASCI/LASSCF instance - - Kwargs: - weights: list of float; required - E_SA = sum_i weights[i] E[i] is used to optimize the orbitals - charges: 2d ndarray or nested list of integers - spins: 2d ndarray or nested list of integers - For the jth fragment in the ith state, - neleca = (sum(las.nelecas_sub[j]) - charges[i][j] + spins[i][j]) // 2 - nelecb = (sum(las.nelecas_sub[j]) - charges[i][j] - spins[i][j]) // 2 - Defaults to - charges[i][j] = 0 - spins[i][j] = las.nelecas_sub[j][0] - las.nelecas_sub[j][1] - smults: 2d ndarray or nested list of integers - For the jth fragment in the ith state, - smults[i][j] = (2*s)+1 - where "s" is the total spin quantum number, - S^2|j,i> = s*(s+1)|j,i> - Defaults to - smults[i][j] = abs (spins[i][j]) + 1 - wfnsyms: 2d ndarray or nested list of integers or strings - For the jth fragment of the ith state, - wfnsyms[i][j] - identifies the point-group irreducible representation - Defaults to all zeros (i.e., the totally-symmetric irrep) - - Returns: - las: LASCI/LASSCF instance - The first positional argument, modified in-place into a - state-averaged LASCI/LASSCF instance. - - ''' - old_states = np.stack (get_space_info (las), axis=-1) - nroots = len (weights) - nfrags = las.nfrags - if charges is None: charges = np.zeros ((nroots, nfrags), dtype=np.int32) - if wfnsyms is None: wfnsyms = np.zeros ((nroots, nfrags), dtype=np.int32) - if spins is None: spins = np.asarray ([[n[0]-n[1] for n in las.nelecas_sub] for i in weights]) - if smults is None: smults = np.abs (spins)+1 - - charges = np.asarray (charges) - wfnsyms = np.asarray (wfnsyms) - spins = np.asarray (spins) - smults = np.asarray (smults) - if np.issubsctype (wfnsyms.dtype, np.str_): - wfnsyms_str = wfnsyms - wfnsyms = np.zeros (wfnsyms_str.shape, dtype=np.int32) - for ix, wfnsym in enumerate (wfnsyms_str.flat): - try: - wfnsyms.flat[ix] = symm.irrep_name2id (las.mol.groupname, wfnsym) - except (TypeError, KeyError) as e: - wfnsyms.flat[ix] = int (wfnsym) - if nfrags == 1: - charges = np.atleast_2d (np.squeeze (charges)).T - wfnsyms = np.atleast_2d (np.squeeze (wfnsyms)).T - spins = np.atleast_2d (np.squeeze (spins)).T - smults = np.atleast_2d (np.squeeze (smults)).T - new_states = np.stack ([charges, spins, smults, wfnsyms], axis=-1) - if assert_no_dupes: assert_no_duplicates (las, tab=new_states) - - las.fciboxes = [get_h1e_zipped_fcisolver (state_average_n_mix ( - las, [csf_solver (las.mol, smult=s2p1).set (charge=c, spin=m2, wfnsym=ir) - for c, m2, s2p1, ir in zip (c_r, m2_r, s2p1_r, ir_r)], weights).fcisolver) - for c_r, m2_r, s2p1_r, ir_r in zip (charges.T, spins.T, smults.T, wfnsyms.T)] - las.e_states = np.zeros (nroots) - las.nroots = nroots - las.weights = weights - - if las.ci is not None: - log = lib.logger.new_logger(las, las.verbose) - log.debug (("lasci.state_average: Cached CI vectors may be present.\n" - "Looking for matches between old and new LAS states...")) - ci0 = [[None for i in range (nroots)] for j in range (nfrags)] - new_states = np.stack ([charges, spins, smults, wfnsyms], - axis=-1).reshape (nroots, nfrags*4) - old_states = old_states.reshape (-1, nfrags*4) - for iroot, row in enumerate (old_states): - idx = np.all (new_states == row[None,:], axis=1) - if np.count_nonzero (idx) == 1: - jroot = np.where (idx)[0][0] - log.debug ("Old state {} -> New state {}".format (iroot, jroot)) - for ifrag in range (nfrags): - ci0[ifrag][jroot] = las.ci[ifrag][iroot] - elif np.count_nonzero (idx) > 1: - raise RuntimeError ("Duplicate states specified?\n{}".format (idx)) - las.ci = ci0 - las.converged = False - return las - -@lib.with_doc(''' A version of lasci.state_average_ that creates a copy instead of modifying the - LASCI/LASSCF method instance in place. - - See lasci.state_average_ docstring below:\n\n''' + state_average_.__doc__) -def state_average (las, weights=[0.5,0.5], charges=None, spins=None, - smults=None, wfnsyms=None, assert_no_dupes=True): - is_scanner = isinstance (las, lib.SinglePointScanner) - if is_scanner: las = las.undo_scanner () - new_las = las.__class__(las._scf, las.ncas_sub, las.nelecas_sub) - new_las.__dict__.update (las.__dict__) - new_las.mo_coeff = las.mo_coeff.copy () - if getattr (las.mo_coeff, 'orbsym', None) is not None: - new_las.mo_coeff = lib.tag_array (new_las.mo_coeff, - orbsym=las.mo_coeff.orbsym) - new_las.ci = None - if las.ci is not None: - new_las.ci = [[c2.copy () if isinstance (c2, np.ndarray) else None - for c2 in c1] for c1 in las.ci] - las = state_average_(new_las, weights=weights, charges=charges, spins=spins, - smults=smults, wfnsyms=wfnsyms, assert_no_dupes=assert_no_dupes) - if is_scanner: las = las.as_scanner () - return las - -def get_single_state_las (las, state=0): - ''' Quickly extract a state-specific las calculation from a state-average one ''' - charges, spins, smults, wfnsyms = get_space_info (las) - charges = charges[state:state+1] - spins = spins[state:state+1] - smults = smults[state:state+1] - wfnsyms = wfnsyms[state:state+1] - weights = [1,] - return state_average (las, weights=weights, charges=charges, spins=spins, smults=smults, - wfnsyms=wfnsyms) - -def run_lasci (las, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=0, - assert_no_dupes=False, _dry_run=False): - '''Self-consistently optimize the CI vectors of a LAS state with - frozen orbitals using a fixed-point algorithm. "lasci_" (with the - trailing underscore) sets self.mo_coeff from the kwarg if it is passed; - "lasci" (without the trailing underscore) leaves self.mo_coeff unchanged. - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - MO coefficients; defaults to self.mo_coeff - ci0 : list (length nfrags) of list (length nroots) of ndarrays - Contains CI vectors for initial guess - lroots : ndarray of shape (nfrags,nroots) - Number of local roots in each fragment for each global state. - The corresponding local weights are set to [1,0,0,0,...]. - lweights : list of length nfrags of list of length nroots of sequence - Weights of local roots in each fragment for each global state. - Passing lweights is incompatible with passing lroots. Defaults - to, i.e., np.ones (las.nfrags, las.nroots, 1).tolist () - verbose : integer - See pyscf.lib.logger. - assert_no_dupes : logical - If True, checks state list for duplicate states - _dry_run : logical - If True, sets up the fcisolvers with the appropriate tags, but does - not run fcisolver kernels. - - Returns: - converged : list of length nroots of logical - Stores whether the calculation for each state successfully converged - e_tot : float - (State-averaged) total energy - e_states : list of length nroots - List of each state energy - e_cas : list of length nroots - List of the CAS space energy of each state - ci : list (length nfrags) of list (length nroots) of ndarrays - Contains optimized CI vectors - ''' - if assert_no_dupes: assert_no_duplicates (las) - if lroots is not None and lweights is not None: - raise RuntimeError ("lroots sets lweights: pass either or none but not both") - elif lweights is None: - if lroots is None: lroots = np.ones ((las.nfrags, las.nroots), dtype=int) - lweights = [] - for i in range (las.nfrags): - lwi = [] - for j in range (las.nroots): - lwij = np.zeros (lroots[i,j]) - lwij[0] = 1 - lwi.append (lwij) - lweights.append (lwi) - nao, nmo = mo_coeff.shape - ncore, ncas = las.ncore, las.ncas - nocc = ncore + ncas - ncas_sub = las.ncas_sub - nelecas_sub = las.nelecas_sub - orbsym = getattr (mo_coeff, 'orbsym', None) - if orbsym is not None: orbsym=orbsym[ncore:nocc] - elif isinstance (las, LASCISymm): - mo_coeff = las.label_symmetry_(mo_coeff) - orbsym = mo_coeff.orbsym[ncore:nocc] - log = lib.logger.new_logger (las, verbose) - - h1eff, energy_core = las.h1e_for_cas (mo_coeff=mo_coeff, - ncas=las.ncas, ncore=las.ncore) - h2eff = las.get_h2eff (mo_coeff) - if (ci0 is None or any ([c is None for c in ci0]) or - any ([any ([c2 is None for c2 in c1]) for c1 in ci0])): - ci0 = las.get_init_guess_ci (mo_coeff, h2eff, ci0) - eri_cas = lib.numpy_helper.unpack_tril ( - h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, - ncas, ncas)[ncore:nocc] - - e_cas = np.empty (las.nroots) - e_states = np.empty (las.nroots) - ci1 = [[None for c2 in c1] for c1 in ci0] - converged = [] - t = (lib.logger.process_clock(), lib.logger.perf_counter()) - e_lexc = [[None for i in range (las.nroots)] for j in range (las.nfrags)] - for state in range (las.nroots): - fcisolvers = [b.fcisolvers[state] for b in las.fciboxes] - ci0_i = [c[state] for c in ci0] - solver = ImpureProductStateFCISolver (fcisolvers, stdout=las.stdout, - lweights=[l[state] for l in lweights], verbose=verbose) - # TODO: better handling of CSF symmetry quantum numbers in general - for ix, s in enumerate (solver.fcisolvers): - i = sum (ncas_sub[:ix]) - j = i + ncas_sub[ix] - if orbsym is not None: s.orbsym = orbsym[i:j] - s.norb = ncas_sub[ix] - s.nelec = solver._get_nelec (s, nelecas_sub[ix]) - s.check_transformer_cache () - if _dry_run: continue - conv, e_i, ci_i = solver.kernel (h1eff, eri_cas, ncas_sub, nelecas_sub, - ecore=0, ci0=ci0_i, orbsym=orbsym, conv_tol_grad=las.conv_tol_grad, - conv_tol_self=las.conv_tol_self, max_cycle_macro=las.max_cycle_macro) - e_cas[state] = e_i - e_states[state] = e_i + energy_core - for frag, s in enumerate (solver.fcisolvers): - e_loc = np.atleast_1d (getattr (s, 'e_states', e_i)) - e_lexc[frag][state] = e_loc - e_i - for c1, c2, s, no, ne in zip (ci1, ci_i, solver.fcisolvers, ncas_sub, nelecas_sub): - ne = solver._get_nelec (s, ne) - ndeta, ndetb = [cistring.num_strings (no, n) for n in ne] - shape = [s.nroots, ndeta, ndetb] if s.nroots>1 else [ndeta, ndetb] - c1[state] = np.asarray (c2).reshape (*shape) - if not conv: log.warn ('State %d LASCI not converged!', state) - converged.append (conv) - t = log.timer ('State {} LASCI'.format (state), *t) - - e_tot = np.dot (las.weights, e_states) - return converged, e_tot, e_states, e_cas, e_lexc, ci1 - -class LASCINoSymm (casci.CASCI): - - def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, frozen=None, **kwargs): - if isinstance(ncas,int): - ncas = [ncas] - ncas_tot = sum (ncas) - nel_tot = [0, 0] - new_nelecas = [] - for ix, nel in enumerate (nelecas): - if isinstance (nel, (int, np.integer)): - nb = nel // 2 - na = nb + (nel % 2) - else: - na, nb = nel - new_nelecas.append ((na, nb)) - nel_tot[0] += na - nel_tot[1] += nb - nelecas = new_nelecas - self.nroots = 1 - super().__init__(mf, ncas=ncas_tot, nelecas=nel_tot, ncore=ncore) - self.chkfile = self._scf.chkfile - if spin_sub is None: spin_sub = [1 + abs(ne[0]-ne[1]) for ne in nelecas] - self.ncas_sub = np.asarray (ncas) - self.nelecas_sub = np.asarray (nelecas) - assert (len (self.nelecas_sub) == self.nfrags) - self.frozen = frozen - self.conv_tol_grad = 1e-4 - self.conv_tol_self = 1e-10 - self.ah_level_shift = 1e-8 - self.max_cycle_macro = 50 - self.max_cycle_micro = 5 - keys = set(('e_states', 'fciboxes', 'nroots', 'weights', 'ncas_sub', 'nelecas_sub', - 'conv_tol_grad', 'conv_tol_self', 'max_cycle_macro', 'max_cycle_micro', - 'ah_level_shift', 'states_converged', 'chkfile', 'e_lexc')) - self._keys = set(self.__dict__.keys()).union(keys) - self.fciboxes = [] - if isinstance(spin_sub,int): - self.fciboxes.append(self._init_fcibox(spin_sub,self.nelecas_sub[0])) - else: - assert (len (spin_sub) == self.nfrags) - for smult, nel in zip (spin_sub, self.nelecas_sub): - self.fciboxes.append (self._init_fcibox (smult, nel)) - self.weights = [1.0] - self.e_states = [0.0] - self.e_lexc = [[np.array ([0]),],] - - def _init_fcibox (self, smult, nel): - s = csf_solver (self.mol, smult=smult) - s.spin = nel[0] - nel[1] - return get_h1e_zipped_fcisolver (state_average_n_mix (self, [s], [1.0]).fcisolver) - - @property - def nfrags (self): return len (self.ncas_sub) - - def get_mo_slice (self, idx, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - mo = mo_coeff[:,self.ncore:] - for offs in self.ncas_sub[:idx]: - mo = mo[:,offs:] - mo = mo[:,:self.ncas_sub[idx]] - return mo - - def ao2mo (self, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - nao, nmo = mo_coeff.shape - ncore, ncas = self.ncore, self.ncas - nocc = ncore + ncas - mo_cas = mo_coeff[:,ncore:nocc] - mo = [mo_coeff, mo_cas, mo_cas, mo_cas] - if getattr (self, 'with_df', None) is not None: - # Store intermediate with one contracted ao index for faster calculation of exchange! - bPmn = sparsedf_array (self.with_df._cderi) - bmuP = bPmn.contract1 (mo_cas) - buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) - eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) - eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) - eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) - eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) - if self.verbose > lib.logger.DEBUG: - eri_comp = self.with_df.ao2mo (mo, compact=True) - lib.logger.debug(self,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) - elif getattr (self._scf, '_eri', None) is not None: - eri = ao2mo.incore.general (self._scf._eri, mo, compact=True) - else: - eri = ao2mo.outcore.general_iofree (self.mol, mo, compact=True) - if eri.shape != (nmo,ncas*ncas*(ncas+1)//2): - try: - eri = eri.reshape (nmo, ncas*ncas*(ncas+1)//2) - except ValueError as e: - assert (nmo == ncas), str (e) - eri = ao2mo.restore ('2kl', eri, nmo).reshape (nmo, ncas*ncas*(ncas+1)//2) - return eri - - def get_h2eff_slice (self, h2eff, idx, compact=None): - ncas_cum = np.cumsum ([0] + self.ncas_sub.tolist ()) - i = ncas_cum[idx] - j = ncas_cum[idx+1] - ncore = self.ncore - nocc = ncore + self.ncas - eri = h2eff[ncore:nocc,:].reshape (self.ncas*self.ncas, -1) - ix_i, ix_j = np.tril_indices (self.ncas) - eri = eri[(ix_i*self.ncas)+ix_j,:] - eri = ao2mo.restore (1, eri, self.ncas)[i:j,i:j,i:j,i:j] - if compact: eri = ao2mo.restore (compact, eri, j-i) - return eri - - get_h1eff = get_h1las = h1e_for_las = h1e_for_las - get_h2eff = ao2mo - ''' - def get_h2eff (self, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - if isinstance (self, _DFLASCI): - mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] - return self.with_df.ao2mo (mo_cas) - return self.ao2mo (mo_coeff) - ''' - - get_fock = get_fock - get_grad = get_grad - get_grad_orb = get_grad_orb - get_grad_ci = get_grad_ci - _hop = lasci_sync.LASCI_HessianOperator - _kern = lasci_sync.kernel - def get_hop (self, mo_coeff=None, ci=None, ugg=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ugg is None: ugg = self.get_ugg () - return self._hop (self, ugg, mo_coeff=mo_coeff, ci=ci, **kwargs) - canonicalize = canonicalize - - def _finalize(self): - log = lib.logger.new_logger (self, self.verbose) - nroots_prt = len (self.e_states) - if self.verbose <= lib.logger.INFO: - nroots_prt = min (nroots_prt, 100) - if nroots_prt < len (self.e_states): - log.info (("Printing a maximum of 100 state energies;" - " increase self.verbose to see them all")) - if nroots_prt > 1: - log.info ("LASCI state-average energy = %.15g", self.e_tot) - for i, e in enumerate (self.e_states[:nroots_prt]): - log.info ("LASCI state %d energy = %.15g", i, e) - else: - log.info ("LASCI energy = %.15g", self.e_tot) - return - - - def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, conv_tol_grad=None, - assert_no_dupes=False, verbose=None, _kern=None): - if mo_coeff is None: - mo_coeff = self.mo_coeff - else: - self.mo_coeff = mo_coeff - if ci0 is None: ci0 = self.ci - if verbose is None: verbose = self.verbose - if conv_tol_grad is None: conv_tol_grad = self.conv_tol_grad - if _kern is None: _kern = self._kern - log = lib.logger.new_logger(self, verbose) - - if self.verbose >= lib.logger.WARN: - self.check_sanity() - self.dump_flags(log) - - for fcibox in self.fciboxes: - fcibox.verbose = self.verbose - fcibox.stdout = self.stdout - fcibox.nroots = self.nroots - fcibox.weights = self.weights - # TODO: local excitations and locally-impure states in LASSCF kernel - do_warn=False - if ci0 is not None: - for i, ci0_i in enumerate (ci0): - if ci0_i is None: continue - for j, ci0_ij in enumerate (ci0_i): - if ci0_ij is None: continue - if np.asarray (ci0_ij).ndim>2: - do_warn=True - ci0_i[j] = ci0_ij[0] - if do_warn: log.warn ("Discarding all but the first root of guess CI vectors!") - - self.converged, self.e_tot, self.e_states, self.mo_energy, self.mo_coeff, self.e_cas, \ - self.ci, h2eff_sub, veff = _kern(mo_coeff=mo_coeff, ci0=ci0, verbose=verbose, \ - casdm0_fr=casdm0_fr, conv_tol_grad=conv_tol_grad, assert_no_dupes=assert_no_dupes) - - self._finalize () - return self.e_tot, self.e_cas, self.ci, self.mo_coeff, self.mo_energy, h2eff_sub, veff - - def states_make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: - return [np.zeros ((self.nroots,2,ncas,ncas)) for ncas in ncas_sub] - casdm1s = [] - for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - if ci_i is None: - dm1a = dm1b = np.zeros ((ncas, ncas)) - else: - dm1a, dm1b = fcibox.states_make_rdm1s (ci_i, ncas, nelecas) - casdm1s.append (np.stack ([dm1a, dm1b], axis=1)) - return casdm1s - - def make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, w=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - if w is None: w = self.weights - return [np.einsum ('rspq,r->spq', dm1, w) for dm1 in casdm1frs] - - def states_make_casdm1s (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - return np.stack ([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] - for dm1rs in casdm1frs]) - for ispin in (0, 1)], axis=0) - for iroot in range (self.nroots)], axis=0) - - def states_make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - casdm2 = [] - for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) - return casdm2 - - def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=None, **kwargs): - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, **kwargs) - return [np.einsum ('rijkl,r->ijkl', dm2, box.weights) - for dm2, box in zip (casdm2fr, self.fciboxes)] - - #SV casdm2s - - #def make_casdm2s(self, ci=None, **kwargs): - # # Make the full-dimensional casdm2s spanning the collective active space - # casdm2s_sub = self.make_casdm2s_sub (ci=ci, **kwargs) - # casdm2a = linalg.block_diag (*[dm[0] for dm in casdm2s_sub]) - # casdm2b = linalg.block_diag (*[dm[1] for dm in casdm2s_sub]) - # return np.stack ([casdm2a, casdm2b], axis=0) - - - #SV casdm2s_sub - def make_casdm2s_sub(self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm2frs=None, w=None, **kwargs): - if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - if w is None: w = self.weights - print("SV w = ", w) - return [np.einsum ('rsijkl,r->sijkl', dm2, w) for dm2 in casdm2frs] - - #SV states_make_casdm2s_sub - #There are two ways to construct states_make_casdm2s_sub, one is thisi coming from states_make_casdm1s_sub - - def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - # Spin-separated 2-RDMs in the MO basis for each subspace in sequence - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: - return [np.zeros ((self.nroots,2,ncas,ncas,ncas,ncas)) for ncas in ncas_sub] - casdm2s = [] - for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - if ci_i is None: - dm2aa = dm2ab, dm2bb = np.zeros ((ncas, ncas,ncas,ncas)) - else: - dm2aa, dm2ab, dm2bb = fcibox.states_make_rdm12s (ci_i, ncas, nelecas)[-1] # IDK what this third ncas*4 matrix is, so just created a placeholder for it - casdm2s.append (np.stack ([dm2aa, dm2ab, dm2bb], axis=1)) - return casdm2s - - #SV states_make_casdm2s_sub - #This is the second coming from states_make_casdm2_sub. I think the above one must be the one to be used but the problem is IDK whether states_make_rdm2s exists or not, maybe states_make_rdm12s exists - ''' - def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - #Spin-separated 1-RDMs in the MO basis for each subspace in sequence - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - casdm2s = [] - for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) - return casdm2 - ''' - - #SV states_make_casdm2s - def states_make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, casdm2fr=None, casdm2frs=None, **kwargs): - ''' Make the full-dimensional casdm2s spanning the collective active space ''' - print ("SV entering states_make_casdm2s") - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - print ("SV shape of casdm2fr = ", casdm2fr) - if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - print ("SV casdm2frs =", casdm2frs, "and ", casdm2frs[0] ) # casdm2frs[i] -- [0]=faa, [1]=fab, [2]=fbb for fragment i - - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - - casdm2rs = np.zeros ((self.nroots,3,ncas,ncas,ncas,ncas)) - for isub, dm2 in enumerate (casdm2frs): - print ("SV dm2 = ", dm2, dm2.shape, isub, dm2[:,1]) # dm2 = root,spin,ncas,ncas,ncas,ncas | dm2[:,x] = x spin part of ncas,ncas,ncas,ncas - i = ncas_cum[isub] - j = ncas_cum[isub+1] - for spin in [0,1,2]:#0=aa, 1=ab, 2=bb - casdm2rs[:, spin, i:j, i:j, i:j, i:j] = dm2[:,spin,:,:,:,:] - - for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - for spin in [0,1,2]: - for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2rs[:,spin]): - dma1, dmb1 = dm1s1[0], dm1s1[1] - dma2, dmb2 = dm1s2[0], dm1s2[1] - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) - +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - - return casdm2rs - - #SV make_casdm3_sub - def make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3fr=None, **kwargs): - if casdm3fr is None: casdm3fr = self.states_make_casdm3_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, **kwargs) - for dm3, box in zip(casdm3fr, self.fciboxes): - print ("SV dm3, box = ", dm3, box.weights) - casdm3_sub = np.einsum('rijklmn,r->ijklmn', dm3, box.weights) - #casdm3_sub = np.einsum('rijklmn,r->ijklpq', dm3, box.weights) - return casdm3_sub - - - #SV states_make_casdm3_sub - def states_make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 3-RDMs in the MO basis for each subspace in sequence, currently this does not have weights so it's not really a STATES ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - casdm3 = [] - for ci_i, ncas, nel in zip (ci, ncas_sub, nelecas_sub): - casdm3.append (fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel)[-1]) - #print ("SV casdm3_sub = ", casdm3_sub - return casdm3 - - def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, - nelecas_sub=None, casdm1rs=None, casdm1frs=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1rs is None: casdm1rs = self.states_make_casdm1s (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, - **kwargs) - mo_core = mo_coeff[:,:self.ncore] - mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] - dm1rs = np.tensordot (mo_cas.conj (), np.dot (casdm1rs, mo_cas.conj ().T), axes=((1),(2))) - dm1rs = dm1rs.transpose (1,2,0,3) - dm1rs += (mo_core @ mo_core.conj ().T)[None,None,:,:] - return dm1rs - - def make_rdm1s_sub (self, mo_coeff=None, ci=None, ncas_sub=None, - nelecas_sub=None, include_core=False, casdm1s_sub=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ''' Same as make_casdm1s_sub, but in the ao basis ''' - rdm1s = [] - for idx, casdm1s in enumerate (casdm1s_sub): - mo = self.get_mo_slice (idx, mo_coeff=mo_coeff) - moH = mo.conjugate ().T - rdm1s.append (np.tensordot (mo, np.dot (casdm1s,moH), axes=((1),(1))).transpose(1,0,2)) - if include_core and self.ncore: - mo_core = mo_coeff[:,:self.ncore] - moH_core = mo_core.conjugate ().T - dm_core = mo_core @ moH_core - rdm1s = [np.stack ([dm_core, dm_core], axis=0)] + rdm1s - rdm1s = np.stack (rdm1s, axis=0) - return rdm1s - - def make_rdm1_sub (self, **kwargs): - return self.make_rdm1s_sub (**kwargs).sum (1) - - def make_rdm1s (self, mo_coeff=None, ncore=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - mo = mo_coeff[:,:ncore] - moH = mo.conjugate ().T - dm_core = mo @ moH - dm_cas = self.make_rdm1s_sub (mo_coeff=mo_coeff, **kwargs).sum (0) - return dm_core[None,:,:] + dm_cas - - def make_rdm1 (self, mo_coeff=None, ci=None, **kwargs): - return self.make_rdm1s (mo_coeff=mo_coeff, ci=ci, **kwargs).sum (0) - - def make_casdm1s (self, ci=None, **kwargs): - ''' Make the full-dimensional casdm1s spanning the collective active space ''' - casdm1s_sub = self.make_casdm1s_sub (ci=ci, **kwargs) - casdm1a = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) - casdm1b = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) - return np.stack ([casdm1a, casdm1b], axis=0) - - def make_casdm1 (self, ci=None, **kwargs): - ''' Spin-sum make_casdm1s ''' - return self.make_casdm1s (ci=ci, **kwargs).sum (0) - - def states_make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' Make the full-dimensional casdm2 spanning the collective active space ''' - raise DeprecationWarning ( - ("states_make_casdm2 is BANNED. There is no reason to EVER make an array this huge.\n" - "Use states_make_casdm*_sub instead, and substitute the factorization into your " - "expressions.") - ) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - casdm2r = np.zeros ((self.nroots,ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2 in enumerate (casdm2fr): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2r[:, i:j, i:j, i:j, i:j] = dm2 - # Off-diagonal - for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2r): - dma1, dmb1 = dm1s1[0], dm1s1[1] - dma2, dmb2 = dm1s2[0], dm1s2[1] - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) - +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2r - - def state_make_casdm1s(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - casdm1s = np.stack([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] - for dm1rs in casdm1frs]) - for ispin in (0, 1)], axis=0) - for iroot in range (self.nroots)], axis=0) - return casdm1s[state] - - def state_make_casdm2(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' State wise casdm2 spanning the collective active space. ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2_r in enumerate (casdm2fr): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2[i:j, i:j, i:j, i:j] = dm2_r[state] - # Off-diagonal - for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - dma1, dmb1 = dm1s1_r[state][0], dm1s1_r[state][1] - dma2, dmb2 = dm1s2_r[state][0], dm1s2_r[state][1] - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) - +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2 - - #SV make_casdm2s - def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm2rs=None, casdm2fs=None, casdm1frs=None, casdm2frs=None, - **kwargs): - - if casdm2rs is not None: - return np.einsum ('rsijkl,r->sijkl', casdm2rs, self.weights) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) - if casdm2fs is None: casdm2fs = self.make_casdm2s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2frs=casdm2frs) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - weights = self.weights - casdm2s = np.zeros ((3,ncas,ncas,ncas,ncas)) - - # Diagonal of aa,ab,bb - for isub, dm2 in enumerate (casdm2fs): - print ("SV dm2 = ", dm2[1], dm2.shape) # dm2 = 3,ncas_sub*4 - i = ncas_cum[isub] - j = ncas_cum[isub+1] - for spin in [0,1,2]: - casdm2s[spin][i:j, i:j, i:j, i:j] = dm2[spin] - - # Off-diagonal - for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] - dm1r = dma1r + dmb1r - dm2r = dma2r + dmb2r - for spin in [0,1,2]: - # Coulomb slice - casdm2s[spin][i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) - casdm2s[spin][k:l, k:l, i:j, i:j] = casdm2s[spin][i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) - + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) - print ("SV d2exc = ", d2exc, d2exc.shape) - casdm2s[spin][i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2s[spin][k:l, i:j, i:j, k:l] = casdm2s[spin][i:j, k:l, k:l, i:j].transpose (1,0,3,2) - - print ("SV casdm2s = ", casdm2s) - return casdm2s - - - def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, - **kwargs): - ''' Make the full-dimensional casdm2 spanning the collective active space ''' - if casdm2r is not None: - return np.einsum ('rijkl,r->ijkl', casdm2r, self.weights) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) - if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - weights = self.weights - casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2 in enumerate (casdm2f): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2[i:j, i:j, i:j, i:j] = dm2 - # Off-diagonal - for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] - dm1r = dma1r + dmb1r - dm2r = dma2r + dmb2r - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) - + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) - casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) # IDU my eqn says [2,3,0,1] --> it's same! - return casdm2 - - #SV make_casdm3 - def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm3r=None, casdm3f=None, casdm2frs=None, casdm3fr=None, casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, - **kwargs): - ''' Make the full-dimensional casdm3 spanning the collective active space ''' - if casdm3r is not None: - return np.einsum ('rijklmn,r->ijklmn', casdm3r, self.weights) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) - if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) - if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) - #if casdm3f is None: casdm3f = self.make_casdm3_sub (ci=ci, - # ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm3fr=casdm3fr) - if casdm3f is None: casdm3f = self.states_make_casdm3_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) - - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - print ("SV ncas_cum = ", ncas_cum) - weights = self.weights - casdm3 = np.zeros ((ncas,ncas,ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm3 in enumerate (casdm3f): - print ("SV dm3 = ", dm3, isub, dm3.shape) - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 - print ("SV casdm3 = ", casdm3) - # Off-diagonal - for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - m = ncas_cum[isub3] - n = ncas_cum[isub3+1] - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] # size of 2-RDMs - dma3r, dmb3r = dm1rs3[:,0], dm1rs3[:,1] - dm1r = dma1r + dmb1r - dm2r = dma2r + dmb2r - #print ("SV dm1r, dm2r = ", dm1rs1, dma2r) - print ("SV dma1r[:,0] = ", dm1rs1[:,0], dm1r) - - # Term 1- 61a - casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) - casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) - casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,0,1,2,3) - casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,2,3,0,1) - casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) - casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) - - # Term 2- 61b - d3sigma = (lib.einsum('r,rkl,rmn->rknml',weights,dma2r,dma3r)+lib.einsum('r,rkl,rmn->rknml',weights,dmb2r,dmb3r)) - casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= lib.einsum('ij,klmn->ijklmn', dm1r, d3sigma) - casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) - casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) - casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) - casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,2,3,0,1) - casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,0,1,2,3) - - # Term 3- 61c - d3sigma = (lib.einsum('r,rij,rkl->rilkj',weights,dma1r,dma2r)+lib.einsum('r,rij,rkl->rilkj',weights,dmb1r,dmb2r)) - casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= lib.einsum('ijkl,mn->ijklmn', d3sigma, dm3r) - casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) - casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) - casdm3[k:l, i:j, m:n, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,4,5,0,1) - casdm3[m:n, m:n, k:l, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,2,3,0,1) - casdm3[m:n, m:n, i:j, k:l, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,0,1,2,3) - - #Terms 4- 61d - d3sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) - casdm3[i:j, k:l, k:l, m:n, m:n, i:j] = np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(0,1,4,5,2,3) - casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,0,1,2,3) - casdm3[m:n, i:j, k:l, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(4,5,2,3,0,1) - casdm3[k:l, m:n, m:n, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,4,5,0,1) - casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose(2,3,0,1,4,5) - - #Terms 5- 61e - d3sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) - casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) - casdm3[m:n, i:j, i:j, m:n, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,0,1,2,3) - casdm3[m:n, i:j, k:l, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,2,3,0,1) - casdm3[k:l, k:l, m:n, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,4,5,0,1) - casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) - - #Terms 6- 61f - d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dm3ar)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dm3br)) - casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) - print ("SV d3sigma = ", d3sigma.shape) - casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) - - #Last 3 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 - - for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub1] - l = ncas_cum[isub1+1] - m = ncas_cum[isub2] - n = ncas_cum[isub2+1] - - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] - dm1r = dma1r + dmb1r - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] - dm2r = dma2r + dmb2r - - # Terms 7- 61g - d3sigma = (lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm1r,dm2r)-(lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dma2r))+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dma1r,dma1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijklmn',weights,dmb1r,dmb1r,dmb2r)) - casdm3[i:j, i:j, k:l, k:l, m:n, m:n] = np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) - casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) - casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) - casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) - casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) - - # Terms 8- 61h - d3sigma = (-lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma1r,dma2r)-lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb1r,dmb2r)+lib.einsum('r,ril,rkj,rmn->rijknml',weights,dma1r,dma1r,dma2r)+lib.einsum('r,ril,rkj,rmn->rijknml',weights,dmb1r,dmb1r,dmb2r)) - casdm3[i:j, i:j, k:l, m:n, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) - - # Terms 9- 61i - - for (isub1, dm1rs1), (isub2, dm2rs2) in product(enumerate(casdm1frs), enumerate(casdm2frs)): - # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same fragment but they must come from different frags! - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub1] - l = ncas_cum[isub1+1] - m = ncas_cum[isub2] - n = ncas_cum[isub2+1] - - if isub1 != isub2: - - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] # size of 2-RDMs - #print ("SV dm2rs2 = ", dm2rs2, dm2rs2.shape, dm2rs2[:,1]) - dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] # size of 2-RDMs - dm1r = dma1r + dmb1r - print ("SV dm1r = ", dm1r.shape) - d3sigma = (-lib.einsum('r,rmn,rkjil->rinkjml',weights,dma1r,dmaa2r)-lib.einsum('r,rmn,rkjil->rinkjml',weights,dma1r,dmab2r)-lib.einsum('r,rmn,rkjil->rinkjml',weights,dmb1r,dmab2r)-lib.einsum('r,rmn,rkjil->rinkjml',weights,dmb1r,dmbb2r)) - print ("SV d3sigma = ", d3sigma.shape) - casdm3[i:j, m:n, k:l, i:j, m:n, k:l] = np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose(2,3,0,1,4,5) - - return casdm3 - - - def get_veff (self, mol=None, dm1s=None, hermi=1, spin_sep=False, **kwargs): - ''' Returns a spin-summed veff! If dm1s isn't provided, builds from self.mo_coeff, self.ci - etc. ''' - if mol is None: mol = self.mol - nao = mol.nao_nr () - if dm1s is None: dm1s = self.make_rdm1 (include_core=True, **kwargs).reshape (nao, nao) - dm1s = np.asarray (dm1s) - if dm1s.ndim == 2: dm1s = dm1s[None,:,:] - if isinstance (self, _DFLASCI): - vj, vk = self.with_df.get_jk(dm1s, hermi=hermi) - else: - vj, vk = self._scf.get_jk(mol, dm1s, hermi=hermi) - if spin_sep: - assert (dm1s.shape[0] == 2) - return vj.sum (0)[None,:,:] - vk - else: - veff = np.stack ([j - k/2 for j, k in zip (vj, vk)], axis=0) - return np.squeeze (veff) - - def split_veff (self, veff, h2eff_sub, mo_coeff=None, ci=None, casdm1s_sub=None): - ''' Split a spin-summed veff into alpha and beta terms using the h2eff eri array. - Note that this will omit v(up_active - down_active)^virtual_inactive by necessity; - this won't affect anything because the inactive density matrix has no spin component. - On the other hand, it ~is~ necessary to correctly do - - v(up_active - down_active)^unactive_active - - in order to calculate the external orbital gradient at the end of the calculation. - This means that I need h2eff_sub spanning both at least two active subspaces - ~and~ the full orbital range. ''' - veff_c = veff.copy () - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci = ci) - ncore = self.ncore - ncas = self.ncas - nocc = ncore + ncas - nao, nmo = mo_coeff.shape - moH_coeff = mo_coeff.conjugate ().T - smo_coeff = self._scf.get_ovlp () @ mo_coeff - smoH_coeff = smo_coeff.conjugate ().T - veff_s = np.zeros_like (veff_c) - for ix, (ncas_i, casdm1s) in enumerate (zip (self.ncas_sub, casdm1s_sub)): - i = sum (self.ncas_sub[:ix]) - j = i + ncas_i - eri_k = h2eff_sub.reshape (nmo, ncas, -1)[:,i:j,...].reshape (nmo*ncas_i, -1) - eri_k = lib.numpy_helper.unpack_tril (eri_k)[:,i:j,:] - eri_k = eri_k.reshape (nmo, ncas_i, ncas_i, ncas) - sdm = casdm1s[0] - casdm1s[1] - vk_pa = -np.tensordot (eri_k, sdm, axes=((1,2),(0,1))) / 2 - veff_s[:,ncore:nocc] += vk_pa - veff_s[ncore:nocc,:] += vk_pa.T - veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:] / 2 - veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:].T / 2 - veff_s = smo_coeff @ veff_s @ smoH_coeff - veffa = veff_c + veff_s - veffb = veff_c - veff_s - return np.stack ([veffa, veffb], axis=0) - - - def states_energy_elec (self, mo_coeff=None, ncore=None, ncas=None, - ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, - casdm1frs=None, casdm2fr=None, veff_core=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - if ncas is None: ncas = self.ncas - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: ci = self.ci - if h2eff is None: h2eff = self.get_h2eff (mo_coeff) - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub) - nao, nmo = mo_coeff.shape - nocc = ncore + ncas - mo_core = mo_coeff[:,:ncore] - mo_cas = mo_coeff[:,ncore:nocc] - dm_core = 2*mo_core @ mo_core.conj ().T - if veff_core is None: veff_core = getattr (veff, 'c', None) - if veff_core is None: veff_core = self.get_veff (dm1s=dm_core) - h1eff = self.get_hcore () + veff_core - e0 = 2*np.dot (((h1eff-(veff_core/2)) @ mo_core).ravel (), mo_core.conj().ravel ()) - h1eff = mo_cas.conj ().T @ h1eff @ mo_cas - eri_cas = lib.numpy_helper.unpack_tril (h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)) - eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) - eri_cas = eri_cas[ncore:nocc] - casdm1rs = self.states_make_casdm1s (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, - casdm1frs=casdm1frs) - vj_r = np.tensordot (casdm1rs.sum (1), eri_cas, axes=2) - vk_rs = np.tensordot (casdm1rs, eri_cas, axes=((2,3),(2,1))) - veff_rs = vj_r[:,None,:,:] - vk_rs - - energy_elec = [] - for idx, (dm1s, v) in enumerate (zip (casdm1rs, veff_rs)): - casdm1fs = [dm[idx] for dm in casdm1frs] - casdm2f = [dm[idx] for dm in casdm2fr] - - # 1-body veff terms - h1e = h1eff[None,:,:] + v/2 - e1 = np.dot (h1e.ravel (), dm1s.ravel ()) - - # 2-body cumulant terms - e2 = 0 - for isub, (dm1s, dm2) in enumerate (zip (casdm1fs, casdm2f)): - dm1a, dm1b = dm1s[0], dm1s[1] - dm1 = dm1a + dm1b - cdm2 = dm2 - np.multiply.outer (dm1, dm1) - cdm2 += np.multiply.outer (dm1a, dm1a).transpose (0,3,2,1) - cdm2 += np.multiply.outer (dm1b, dm1b).transpose (0,3,2,1) - eri = self.get_h2eff_slice (h2eff, isub) - te2 = np.tensordot (eri, cdm2, axes=4) / 2 - e2 += te2 - energy_elec.append (e0 + e1 + e2) - self._e1_ref = e0 + e1 - self._e2_ref = e2 - - return energy_elec - - def energy_elec (self, mo_coeff=None, ncore=None, ncas=None, - ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' Since the LASCI energy cannot be calculated as simply as ecas + ecore, I need this ''' - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - if ncas is None: ncas = self.ncas - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: ci = self.ci - if h2eff is None: h2eff = self.get_h2eff (mo_coeff) - casdm1s_sub = self.make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, - casdm1frs=casdm1frs) - if veff is None: - veff = self.get_veff (dm1s = self.make_rdm1(mo_coeff=mo_coeff,casdm1s_sub=casdm1s_sub)) - veff = self.split_veff (veff, h2eff, mo_coeff=mo_coeff, casdm1s_sub=casdm1s_sub) - - # 1-body veff terms - h1e = self.get_hcore ()[None,:,:] + veff/2 - dm1s = self.make_rdm1s (mo_coeff=mo_coeff, ncore=ncore, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1s_sub=casdm1s_sub) - e1 = np.dot (h1e.ravel (), dm1s.ravel ()) - - # 2-body cumulant terms - casdm1s = self.make_casdm1s (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1frs=casdm1frs) - casdm1 = casdm1s.sum (0) - casdm2 = self.make_casdm2 (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, casdm2fr=casdm2fr) - casdm2 -= np.multiply.outer (casdm1, casdm1) - casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) - casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) - ncore, ncas, nocc = self.ncore, self.ncas, self.ncore + self.ncas - eri = lib.numpy_helper.unpack_tril (h2eff[ncore:nocc].reshape (ncas*ncas, -1)) - eri = eri.reshape ([ncas,]*4) - e2 = np.tensordot (eri, casdm2, axes=4)/2 - - e0 = self.energy_nuc () - self._e1_test = e1 - self._e2_test = e2 - return e1 + e2 - - _ugg = lasci_sync.LASCI_UnitaryGroupGenerators - def get_ugg (self, mo_coeff=None, ci=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - return self._ugg (self, mo_coeff, ci) - - def cderi_ao2mo (self, mo_i, mo_j, compact=False): - assert (isinstance (self, _DFLASCI)) - nmo_i, nmo_j = mo_i.shape[-1], mo_j.shape[-1] - if compact: - assert (nmo_i == nmo_j) - bPij = np.empty ((self.with_df.get_naoaux (), nmo_i*(nmo_i+1)//2), dtype=mo_i.dtype) - else: - bPij = np.empty ((self.with_df.get_naoaux (), nmo_i, nmo_j), dtype=mo_i.dtype) - ijmosym, mij_pair, moij, ijslice = ao2mo.incore._conc_mos (mo_i, mo_j, compact=compact) - b0 = 0 - for eri1 in self.with_df.loop (): - b1 = b0 + eri1.shape[0] - eri2 = bPij[b0:b1] - eri2 = ao2mo._ao2mo.nr_e2 (eri1, moij, ijslice, aosym='s2', mosym=ijmosym, out=eri2) - b0 = b1 - return bPij - - def fast_veffa (self, casdm1s_sub, h2eff_sub, mo_coeff=None, ci=None, _full=False): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - assert (isinstance (self, _DFLASCI) or _full) - ncore = self.ncore - ncas_sub = self.ncas_sub - ncas = sum (ncas_sub) - nocc = ncore + ncas - nao, nmo = mo_coeff.shape - - mo_cas = mo_coeff[:,ncore:nocc] - moH_cas = mo_cas.conjugate ().T - moH_coeff = mo_coeff.conjugate ().T - dma = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) - dmb = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) - casdm1s = np.stack ([dma, dmb], axis=0) - if not (isinstance (self, _DFLASCI)): - dm1s = np.dot (mo_cas, np.dot (casdm1s, moH_cas)).transpose (1,0,2) - return self.get_veff (dm1s = dm1s, spin_sep=True) - casdm1 = casdm1s.sum (0) - dm1 = np.dot (mo_cas, np.dot (casdm1, moH_cas)) - bPmn = sparsedf_array (self.with_df._cderi) - - # vj - dm_tril = dm1 + dm1.T - np.diag (np.diag (dm1.T)) - rho = np.dot (bPmn, lib.pack_tril (dm_tril)) - vj = lib.unpack_tril (np.dot (rho, bPmn)) - - # vk - bmPu = h2eff_sub.bmPu - if _full: - vmPsu = np.dot (bmPu, casdm1s) - vk = np.tensordot (vmPsu, bmPu, axes=((1,3),(1,2))).transpose (1,0,2) - return vj[None,:,:] - vk - else: - vmPu = np.dot (bmPu, casdm1) - vk = np.tensordot (vmPu, bmPu, axes=((1,2),(1,2))) - return vj - vk/2 - - @lib.with_doc(run_lasci.__doc__) - def lasci (self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, - assert_no_dupes=False, _dry_run=False): - if mo_coeff is None: mo_coeff=self.mo_coeff - if ci0 is None: ci0 = self.ci - if verbose is None: verbose = self.verbose - converged, e_tot, e_states, e_cas, e_lexc, ci = run_lasci ( - self, mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, - verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) - if _dry_run: return - self.converged, self.ci = converged, ci - self.e_tot, self.e_states, self.e_cas, self.e_lexc = e_tot, e_states, e_cas, e_lexc - if mo_coeff is self.mo_coeff: - self.dump_chk () - elif getattr (self, 'chkfile', None) is not None: - lib.logger.warn (self, 'orbitals changed; chkfile not dumped!') - self._finalize () - return self.converged, self.e_tot, self.e_states, self.e_cas, e_lexc, self.ci - - @lib.with_doc(run_lasci.__doc__) - def lasci_(self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, - assert_no_dupes=False, _dry_run=False): - if mo_coeff is not None: - self.mo_coeff = mo_coeff - return self.lasci (mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, - verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) - - state_average = state_average - state_average_ = state_average_ - get_single_state_las = get_single_state_las - - def lassi(self, mo_coeff=None, ci=None, veff_c=None, h2eff_sub=None, orbsym=None, - soc=False, break_symmetry=False, opt=1, **kwargs): - #import warnings - #lassi_kernel_warn = "Now LASSI have kernel, which takes las instance as input. This [las.lassi()] function " \ - # "will be removed soon." - #warnings.warn(lassi_kernel_warn, stacklevel=3) - from mrh.my_pyscf.lassi import lassi - mylassi = lassi.LASSI(self, mo_coeff=mo_coeff, ci=ci, soc=soc, opt=opt, - break_symmetry=break_symmetry, **kwargs) - return mylassi.kernel(mo_coeff=mo_coeff, ci=ci, veff_c=veff_c, h2eff_sub=h2eff_sub, - orbsym=orbsym) - - las2cas_civec = las2cas_civec - assert_no_duplicates = assert_no_duplicates - get_init_guess_ci = get_init_guess_ci - localize_init_guess=lasscf_guess.localize_init_guess - def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): - if s is None: s = self._scf.get_ovlp () - return matrix_svd_control_options (s, lspace=mo_lspace, rspace=mo_rspace, full_matrices=True)[:3] - - def dump_flags (self, verbose=None, _method_name='LASCI'): - log = lib.logger.new_logger (self, verbose) - log.info ('') - log.info ('******** %s flags ********', _method_name) - ncore = self.ncore - ncas = self.ncas - nvir = self.mo_coeff.shape[1] - ncore - ncas - nfrags = len (self.nelecas_sub) - log.info ('CAS (%de+%de, %do), ncore = %d, nvir = %d', - self.nelecas[0], self.nelecas[1], ncas, ncore, nvir) - log.info ('Divided into %d LAS spaces', nfrags) - for i, (no, ne) in enumerate (zip (self.ncas_sub, self.nelecas_sub)): - log.info ('LAS %d : (%de+%de, %do)', i, ne[0], ne[1], no) - log.info ('nroots = %d', self.nroots) - log.info ('max_memory %d (MB)', self.max_memory) - for i, fcibox in enumerate (self.fciboxes): - if getattr (fcibox, 'dump_flags', None): - log.info ('fragment %d FCI solver flags:', i) - fcibox.dump_flags (log.verbose) - - @property - def converged (self): - return all (self.states_converged) - @converged.setter - def converged (self, x): - if hasattr (x, '__len__'): - self.states_converged = list (x) - else: - self.states_converged = [x,]*self.nroots - - def dump_spaces (self, nroots=None, sort_energy=False): - log = lib.logger.new_logger (self, self.verbose) - log.info ("******** LAS space tables ********") - ci = self.ci - if nroots is None and self.verbose <= lib.logger.INFO: - nroots = min (self.nroots, 100) - elif nroots is None: - nroots = self.nroots - if nroots < self.nroots: - log.warn ("Dumping only 100 of %d spaces", self.nroots) - log.warn ("To see more, explicitly pass nroots to dump_spaces or increase verbosity") - if sort_energy: - idx = np.argsort (self.e_states) - else: - idx = range (nroots) - for state in idx: - neleca_f = [] - nelecb_f = [] - wfnsym_f = [] - wfnsym = 0 - m_f = [] - s_f = [] - lroots = [] - s2_tot = 0 - for ifrag, (fcibox, nelecas) in enumerate (zip (self.fciboxes, self.nelecas_sub)): - solver = fcibox.fcisolvers[state] - na, nb = _unpack_nelec (fcibox._get_nelec (solver, nelecas)) - neleca_f.append (na) - nelecb_f.append (nb) - m_f.append ((na-nb)/2) - s_f.append ((solver.smult-1)/2) - s2_tot += s_f[-1] * (s_f[-1] + 1) - fragsym = getattr (solver, 'wfnsym', 0) or 0 - if isinstance (fragsym, str): - fragsym_str = fragsym - fragsym_id = symm.irrep_name2id (solver.mol.groupname, fragsym) - else: - fragsym_id = fragsym - fragsym_str = symm.irrep_id2name (solver.mol.groupname, fragsym) - wfnsym ^= fragsym_id - wfnsym_f.append (fragsym_str) - lroots_i = 0 - if ci is not None: - if ci[ifrag] is not None: - ci_i = ci[ifrag] - if ci_i[state] is not None: - ci_ij = ci_i[state] - lroots_i = 1 if ci_ij.ndim<3 else ci_ij.shape[0] - lroots.append (lroots_i) - s2_tot += sum ([2*m1*m2 for m1, m2 in combinations (m_f, 2)]) - s_f, m_f = np.asarray (s_f), np.asarray (m_f) - if np.all (m_f<0): m_f *= -1 - s_pure = bool (np.all (s_f==m_f)) - wfnsym = symm.irrep_id2name (self.mol.groupname, wfnsym) - neleca = sum (neleca_f) - nelecb = sum (nelecb_f) - log.info ("LAS space %d: (%de+%de,%do) wfynsm=%s", state, neleca, nelecb, self.ncas, wfnsym) - log.info ("Converged? %s", self.states_converged[state]) - log.info ("E(LAS) = %.15g", self.e_states[state]) - log.info ("S^2 = %.7f (%s)", s2_tot, ('Impure','Pure')[s_pure]) - log.info ("Space table") - log.info (" frag (ae+be,no) 2S+1 ir lroots") - for i in range (self.nfrags): - smult_f = int (round (2*s_f[i] + 1)) - tupstr = '({}e+{}e,{}o)'.format (neleca_f[i], nelecb_f[i], self.ncas_sub[i]) - log.info (" %4d %13s %4d %3s %6d", i, tupstr, smult_f, wfnsym_f[i], lroots[i]) - - def check_sanity (self): - casci.CASCI.check_sanity (self) - self.get_ugg () # constructor encounters impossible states and raises error - - dump_chk = chkfile.dump_las - load_chk = load_chk_ = chkfile.load_las_ - -class LASCISymm (casci_symm.CASCI, LASCINoSymm): - - def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, wfnsym_sub=None, frozen=None, - **kwargs): - LASCINoSymm.__init__(self, mf, ncas, nelecas, ncore=ncore, spin_sub=spin_sub, - frozen=frozen, **kwargs) - if getattr (self.mol, 'groupname', None) in ('Dooh', 'Coov'): - raise NotImplementedError ("LASSCF support for cylindrical point group {}".format ( - self.mol.groupname)) - if wfnsym_sub is None: wfnsym_sub = [0 for icas in self.ncas_sub] - # TODO: guess wfnsym_sub intelligently (0 can be impossible for some multiplicities) - for wfnsym, frag in zip (wfnsym_sub, self.fciboxes): - if isinstance (wfnsym, (str, np.str_)): - wfnsym = symm.irrep_name2id (self.mol.groupname, wfnsym) - frag.fcisolvers[0].wfnsym = wfnsym - - make_rdm1s = LASCINoSymm.make_rdm1s - make_rdm1 = LASCINoSymm.make_rdm1 - get_veff = LASCINoSymm.get_veff - get_h1eff = get_h1las = h1e_for_las - dump_flags = LASCINoSymm.dump_flags - dump_spaces = LASCINoSymm.dump_spaces - check_sanity = LASCINoSymm.check_sanity - _ugg = lasci_sync.LASCISymm_UnitaryGroupGenerators - - @property - def wfnsym (self): - ''' This now returns the product of the irreps of the subspaces ''' - wfnsym = [0,]*self.nroots - for frag in self.fciboxes: - for state, solver in enumerate (frag.fcisolvers): - wfnsym[state] ^= solver.wfnsym - if self.nroots == 1: wfnsym = wfnsym[0] - return wfnsym - @wfnsym.setter - def wfnsym (self, ir): - raise RuntimeError (("Cannot assign the whole-system symmetry of a LASCI wave function. " - "Address fciboxes[ifrag].fcisolvers[istate].wfnsym instead.")) - - def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, verbose=None, assert_no_dupes=False): - if mo_coeff is None: - mo_coeff = self.mo_coeff - if ci0 is None: - ci0 = self.ci - - # Initialize/overwrite mo_coeff.orbsym. Don't pass ci0 because it's not the right shape - lib.logger.info (self, ("LASCI lazy hack note: lines below reflect the point-group " - "symmetry of the whole molecule but not of the individual " - "subspaces")) - mo_coeff = self.mo_coeff = self.label_symmetry_(mo_coeff) - return LASCINoSymm.kernel(self, mo_coeff=mo_coeff, ci0=ci0, - casdm0_fr=casdm0_fr, verbose=verbose, assert_no_dupes=assert_no_dupes) - - def canonicalize (self, mo_coeff=None, ci=None, natorb_casdm1=None, veff=None, h2eff_sub=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - mo_coeff = self.label_symmetry_(mo_coeff) - return canonicalize (self, mo_coeff=mo_coeff, ci=ci, natorb_casdm1=natorb_casdm1, - h2eff_sub=h2eff_sub, orbsym=mo_coeff.orbsym) - - def label_symmetry_(self, mo_coeff=None): - if mo_coeff is None: mo_coeff=self.mo_coeff - ncore = self.ncore - ncas_sub = self.ncas_sub - nocc = ncore + sum (ncas_sub) - mo_coeff[:,:ncore] = symm.symmetrize_space (self.mol, mo_coeff[:,:ncore]) - for isub, ncas in enumerate (ncas_sub): - i = ncore + sum (ncas_sub[:isub]) - j = i + ncas - mo_coeff[:,i:j] = symm.symmetrize_space (self.mol, mo_coeff[:,i:j]) - mo_coeff[:,nocc:] = symm.symmetrize_space (self.mol, mo_coeff[:,nocc:]) - orbsym = symm.label_orb_symm (self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_coeff, - s=self._scf.get_ovlp ()) - mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) - return mo_coeff - - @lib.with_doc(LASCINoSymm.localize_init_guess.__doc__) - def localize_init_guess (self, frags_atoms, mo_coeff=None, spin=None, lo_coeff=None, fock=None, - freeze_cas_spaces=False): - if mo_coeff is None: - mo_coeff = self.mo_coeff - mo_coeff = casci_symm.label_symmetry_(self, mo_coeff) - return LASCINoSymm.localize_init_guess (self, frags_atoms, mo_coeff=mo_coeff, spin=spin, - lo_coeff=lo_coeff, fock=fock, freeze_cas_spaces=freeze_cas_spaces) - - def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): - if s is None: s = self._scf.get_ovlp () - lsymm = getattr (mo_lspace, 'orbsym', None) - if lsymm is None: - mo_lspace = symm.symmetrize_space (self.mol, mo_lspace) - lsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_lspace, s=s) - rsymm = getattr (mo_rspace, 'orbsym', None) - if rsymm is None: - mo_rspace = symm.symmetrize_space (self.mol, mo_rspace) - rsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_rspace, s=s) - decomp = matrix_svd_control_options (s, - lspace=mo_lspace, rspace=mo_rspace, - lspace_symmetry=lsymm, rspace_symmetry=rsymm, - full_matrices=True, strong_symm=True) - mo_lvecs, svals, mo_rvecs, lsymm, rsymm = decomp - mo_lvecs = lib.tag_array (mo_lvecs, orbsym=lsymm) - mo_rvecs = lib.tag_array (mo_rvecs, orbsym=rsymm) - return mo_lvecs, svals, mo_rvecs - diff --git a/my_pyscf/mcscf/sv_lasci.py b/my_pyscf/mcscf/sv_lasci.py deleted file mode 100644 index eaf153a4..00000000 --- a/my_pyscf/mcscf/sv_lasci.py +++ /dev/null @@ -1,1844 +0,0 @@ -from pyscf.scf.rohf import get_roothaan_fock -from pyscf.fci import cistring -from pyscf.mcscf import casci, casci_symm, df -from pyscf.tools import dump_mat -from pyscf import symm, gto, scf, ao2mo, lib -from pyscf.fci.direct_spin1 import _unpack_nelec -from mrh.my_pyscf.mcscf.addons import state_average_n_mix, get_h1e_zipped_fcisolver, las2cas_civec -from mrh.my_pyscf.mcscf import lasci_sync, _DFLASCI, lasscf_guess -from mrh.my_pyscf.fci import csf_solver -from mrh.my_pyscf.df.sparse_df import sparsedf_array -from mrh.my_pyscf.mcscf import chkfile -from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver -from mrh.util.la import matrix_svd_control_options -from itertools import combinations -from scipy.sparse import linalg as sparse_linalg -from scipy import linalg -import numpy as np -import copy - -def LASCI (mf_or_mol, ncas_sub, nelecas_sub, **kwargs): - if isinstance(mf_or_mol, gto.Mole): - mf = scf.RHF(mf_or_mol) - else: - mf = mf_or_mol - if mf.mol.symmetry: - las = LASCISymm (mf, ncas_sub, nelecas_sub, **kwargs) - else: - las = LASCINoSymm (mf, ncas_sub, nelecas_sub, **kwargs) - if getattr (mf, 'with_df', None): - las = density_fit (las, with_df = mf.with_df) - return las - -def get_grad (las, mo_coeff=None, ci=None, ugg=None, h1eff_sub=None, h2eff_sub=None, - veff=None, dm1s=None): - '''Return energy gradient for orbital rotation and CI relaxation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - ugg : instance of :class:`LASCI_UnitaryGroupGenerators` - h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray - Contains effective one-electron Hamiltonians experienced by each fragment - in each state - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - dm1s : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-RDM in the AO basis - - Returns: - gorb : ndarray of shape (ugg.nvar_orb,) - Orbital rotation gradients as a flat array - gci : ndarray of shape (sum(ugg.ncsf_sub),) - CI relaxation gradients as a flat array - gx : ndarray - Orbital rotation gradients for temporarily frozen orbitals in the "LASCI" problem - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if ugg is None: ugg = las.get_ugg (mo_coeff, ci) - if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if veff is None: - veff = las.get_veff (dm1s = dm1s.sum (0)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) - if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, - h2eff_sub=h2eff_sub) - - gorb = get_grad_orb (las, mo_coeff=mo_coeff, ci=ci, h2eff_sub=h2eff_sub, veff=veff, dm1s=dm1s) - gci = get_grad_ci (las, mo_coeff=mo_coeff, ci=ci, h1eff_sub=h1eff_sub, h2eff_sub=h2eff_sub, - veff=veff) - - idx = ugg.get_gx_idx () - gx = gorb[idx] - gint = ugg.pack (gorb, gci) - gorb = gint[:ugg.nvar_orb] - gci = gint[ugg.nvar_orb:] - return gorb, gci, gx.ravel () - -def get_grad_orb (las, mo_coeff=None, ci=None, h2eff_sub=None, veff=None, dm1s=None, hermi=-1): - '''Return energy gradient for orbital rotation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - dm1s : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-RDM in the AO basis - hermi : integer - Control (anti-)symmetrization. 0 means to return the effective Fock matrix, - F1 = h.D + g.d. -1 means to return the true orbital-rotation gradient, which is skew- - symmetric: gorb = F1 - F1.T. +1 means to return the symmetrized effective Fock matrix, - (F1 + F1.T) / 2. The factor of 2 difference between hermi=-1 and the other two options - is intentional and necessary. - - Returns: - gorb : ndarray of shape (nmo,nmo) - Orbital rotation gradients as a square antihermitian array - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if dm1s is None: dm1s = las.make_rdm1s (mo_coeff=mo_coeff, ci=ci) - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if veff is None: - veff = las.get_veff (dm1s = dm1s.sum (0)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci) - nao, nmo = mo_coeff.shape - ncore = las.ncore - ncas = las.ncas - nocc = las.ncore + las.ncas - smo_cas = las._scf.get_ovlp () @ mo_coeff[:,ncore:nocc] - smoH_cas = smo_cas.conj ().T - - # The orbrot part - h1s = las.get_hcore ()[None,:,:] + veff - f1 = h1s[0] @ dm1s[0] + h1s[1] @ dm1s[1] - f1 = mo_coeff.conjugate ().T @ f1 @ las._scf.get_ovlp () @ mo_coeff - # ^ I need the ovlp there to get dm1s back into its correct basis - casdm2 = las.make_casdm2 (ci=ci) - casdm1s = np.stack ([smoH_cas @ d @ smo_cas for d in dm1s], axis=0) - casdm1 = casdm1s.sum (0) - casdm2 -= np.multiply.outer (casdm1, casdm1) - casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) - casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) - eri = h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2) - eri = lib.numpy_helper.unpack_tril (eri).reshape (nmo, ncas, ncas, ncas) - f1[:,ncore:nocc] += np.tensordot (eri, casdm2, axes=((1,2,3),(1,2,3))) - - if hermi == -1: - return f1 - f1.T - elif hermi == 1: - return .5*(f1+f1.T) - elif hermi == 0: - return f1 - else: - raise ValueError ("kwarg 'hermi' must = -1, 0, or +1") - -def get_grad_ci (las, mo_coeff=None, ci=None, h1eff_sub=None, h2eff_sub=None, veff=None): - '''Return energy gradient for CI relaxation. - - Args: - las : instance of :class:`LASCINoSymm` - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - Contains molecular orbitals - ci : list (length=nfrags) of list (length=nroots) of ndarray - Contains CI vectors - h1eff_sub : list (length=nfrags) of list (length=nroots) of ndarray - Contains effective one-electron Hamiltonians experienced by each fragment - in each state - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - veff : ndarray of shape (2,nao,nao) - Spin-separated, state-averaged 1-electron mean-field potential in the AO basis - - Returns: - gci : list (length=nfrags) of list (length=nroots) of ndarray - CI relaxation gradients in the shape of CI vectors - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if h1eff_sub is None: h1eff_sub = las.get_h1eff (mo_coeff, ci=ci, veff=veff, - h2eff_sub=h2eff_sub) - gci = [] - for isub, (fcibox, h1e, ci0, ncas, nelecas) in enumerate (zip ( - las.fciboxes, h1eff_sub, ci, las.ncas_sub, las.nelecas_sub)): - eri_cas = las.get_h2eff_slice (h2eff_sub, isub, compact=8) - linkstrl = fcibox.states_gen_linkstr (ncas, nelecas, True) - linkstr = fcibox.states_gen_linkstr (ncas, nelecas, False) - h2eff = fcibox.states_absorb_h1e(h1e, eri_cas, ncas, nelecas, .5) - hc0 = fcibox.states_contract_2e(h2eff, ci0, ncas, nelecas, link_index=linkstrl) - hc0 = [hc.ravel () for hc in hc0] - ci0 = [c.ravel () for c in ci0] - gci.append ([2.0 * (hc - c * (c.dot (hc))) for c, hc in zip (ci0, hc0)]) - return gci - -def density_fit (las, auxbasis=None, with_df=None): - ''' Here I ONLY need to attach the tag and the df object because I put conditionals in - LASCINoSymm to make my life easier ''' - las_class = las.__class__ - if with_df is None: - if (getattr(las._scf, 'with_df', None) and - (auxbasis is None or auxbasis == las._scf.with_df.auxbasis)): - with_df = las._scf.with_df - else: - with_df = df.DF(las.mol) - with_df.max_memory = las.max_memory - with_df.stdout = las.stdout - with_df.verbose = las.verbose - with_df.auxbasis = auxbasis - class DFLASCI (las_class, _DFLASCI): - def __init__(self, scf, ncas_sub, nelecas_sub): - self.with_df = with_df - self._keys = self._keys.union(['with_df']) - las_class.__init__(self, scf, ncas_sub, nelecas_sub) - new_las = DFLASCI (las._scf, las.ncas_sub, las.nelecas_sub) - new_las.__dict__.update (las.__dict__) - return new_las - -def h1e_for_las (las, mo_coeff=None, ncas=None, ncore=None, nelecas=None, ci=None, ncas_sub=None, - nelecas_sub=None, veff=None, h2eff_sub=None, casdm1s_sub=None, casdm1frs=None): - ''' Effective one-body Hamiltonians (plural) for a LASCI problem - - Args: - las: a LASCI object - - Kwargs: - mo_coeff: ndarray of shape (nao,nmo) - Orbital coefficients ordered on the columns as: - core orbitals, subspace 1, subspace 2, ..., external orbitals - ncas: integer - As in PySCF's existing CASCI/CASSCF implementation - nelecas: sequence of 2 integers - As in PySCF's existing CASCI/CASSCF implementation - ci: list (length=nfrags) of list (length=nroots) of ndarrays - Contains CI vectors - ncas_sub: ndarray of shape (nsub) - Number of active orbitals in each subspace - nelecas_sub: ndarray of shape (nsub,2) - na, nb in each subspace - veff: ndarray of shape (2, nao, nao) - Contains spin-separated, state-averaged effective potential - h2eff_sub : ndarray of shape (nmo,ncas**2*(ncas+1)/2) - Contains ERIs (p1a1|a2a3), lower-triangular in the a2a3 indices - casdm1s_sub : list (length=nfrags) of ndarrays - Contains state-averaged, spin-separated 1-RDMs in the localized active subspaces - casdm1frs : list (length=nfrags) of list (length=nroots) of ndarrays - Contains spin-separated 1-RDMs for each state in the localized active subspaces - - Returns: - h1e_fr: list (length=nfrags) of list (length=nroots) of ndarrays - Spin-separated 1-body Hamiltonian operator for each fragment and state - ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ncas is None: ncas = las.ncas - if ncore is None: ncore = las.ncore - if ncas_sub is None: ncas_sub = las.ncas_sub - if nelecas_sub is None: nelecas_sub = las.nelecas_sub - if ncore is None: ncore = las.ncore - if ci is None: ci = las.ci - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - if casdm1frs is None: casdm1frs = las.states_make_casdm1s_sub (ci=ci) - if casdm1s_sub is None: casdm1s_sub = [np.einsum ('rsij,r->sij',dm,las.weights) - for dm in casdm1frs] - if veff is None: - veff = las.get_veff (dm1s = las.make_rdm1 (mo_coeff=mo_coeff, ci=ci)) - veff = las.split_veff (veff, h2eff_sub, mo_coeff=mo_coeff, ci=ci, casdm1s_sub=casdm1s_sub) - - # First pass: split by root - nocc = ncore + ncas - nmo = mo_coeff.shape[-1] - mo_cas = mo_coeff[:,ncore:nocc] - moH_cas = mo_cas.conj ().T - h1e = moH_cas @ (las.get_hcore ()[None,:,:] + veff) @ mo_cas - h1e_r = np.empty ((las.nroots, 2, ncas, ncas), dtype=h1e.dtype) - h2e = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, - ncas*(ncas+1)//2)).reshape (nmo, ncas, ncas, ncas)[ncore:nocc,:,:,:] - #print ("SV h2e = ",h2e, h2e.shape) - avgdm1s = np.stack ([linalg.block_diag (*[dm[spin] for dm in casdm1s_sub]) - for spin in range (2)], axis=0) - for state in range (las.nroots): - statedm1s = np.stack ([linalg.block_diag (*[dm[state][spin] for dm in casdm1frs]) - for spin in range (2)], axis=0) - dm1s = statedm1s - avgdm1s - j = np.tensordot (dm1s, h2e, axes=((1,2),(2,3))) - k = np.tensordot (dm1s, h2e, axes=((1,2),(2,1))) - h1e_r[state] = h1e + j + j[::-1] - k - - - # Second pass: split by fragment and subtract double-counting - h1e_fr = [] - for ix, casdm1s_r in enumerate (casdm1frs): - p = sum (las.ncas_sub[:ix]) - q = p + las.ncas_sub[ix] - h1e = h1e_r[:,:,p:q,p:q] - h2e = las.get_h2eff_slice (h2eff_sub, ix) - j = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,3))) - k = np.tensordot (casdm1s_r, h2e, axes=((2,3),(2,1))) - h1e_fr.append (h1e - j - j[:,::-1] + k) - - return h1e_fr - -def get_fock (las, mo_coeff=None, ci=None, eris=None, casdm1s=None, verbose=None, veff=None, - dm1s=None): - ''' f_pq = h_pq + (g_pqrs - g_psrq/2) D_rs, AO basis - Note the difference between this and h1e_for_las: h1e_for_las only has - JK terms from electrons outside the "current" active subspace; get_fock - includes JK from all electrons. This is also NOT the "generalized Fock matrix" - of orbital gradients (but it can be used in calculating those if you do a - semi-cumulant decomposition). - The "eris" kwarg does not do anything and is retained only for backwards - compatibility (also why I don't just call las.make_rdm1) ''' - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - if dm1s is None: - if casdm1s is None: casdm1s = las.make_casdm1s (ci=ci) - mo_cas = mo_coeff[:,las.ncore:][:,:las.ncas] - moH_cas = mo_cas.conjugate ().T - mo_core = mo_coeff[:,:las.ncore] - moH_core = mo_core.conjugate ().T - dm1s = [(mo_core @ moH_core) + (mo_cas @ d @ moH_cas) for d in list(casdm1s)] - if veff is not None: - fock = las.get_hcore()[None,:,:] + veff - return get_roothaan_fock (fock, dm1s, las._scf.get_ovlp ()) - dm1 = dm1s[0] + dm1s[1] - if isinstance (las, _DFLASCI): - vj, vk = las.with_df.get_jk(dm1, hermi=1) - else: - vj, vk = las._scf.get_jk(las.mol, dm1, hermi=1) - fock = las.get_hcore () + vj - (vk/2) - return fock - -def _eig_inactive_virtual (las, fock, orbsym=None): - '''Generate the unitary matrix canonicalizing the inactive and virtual orbitals only. - - Args: - las : object of :class:`LASCINoSymm` - fock : ndarray of shape (nmo,nmo) - Contains Fock matrix in MO basis - - Kwargs: - orbsym : list of length nmo - umat : ndarray of shape (nmo, nmo) - - Returns: - ene : ndarray of shape (nmo,) - umat : ndarray of shape (nmo, nmo)''' - nmo = fock.shape[0] - ncore = las.ncore - nocc = ncore + las.ncas - ene = np.zeros (nmo) - umat = np.eye (nmo) - if ncore: - orbsym_i = None if orbsym is None else orbsym[:ncore] - fock_i = fock[:ncore,:ncore] - ene[:ncore], umat[:ncore,:ncore] = las._eig (fock_i, 0, 0, orbsym_i) - if nmo-nocc: - orbsym_i = None if orbsym is None else orbsym[nocc:] - fock_i = fock[nocc:,nocc:] - ene[nocc:], umat[nocc:,nocc:] = las._eig (fock_i, 0, 0, orbsym_i) - return ene, umat - -def canonicalize (las, mo_coeff=None, ci=None, casdm1fs=None, natorb_casdm1=None, veff=None, - h2eff_sub=None, orbsym=None): - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci is None: ci = las.ci - - # In-place safety - mo_coeff = mo_coeff.copy () - ci = copy.deepcopy (ci) - - # Temporary lroots safety - # The desired behavior is that the inactive and external orbitals should - # be canonicalized according to the density matrix used for orbital optimization - # TODO: once orbital optimization with lroots is enabled, change this behavior - # TODO: possibly move this logic to the make_casdm* functions - if casdm1fs is None: - ci_dm = [] - for i in range (len (ci)): - ci_i = [] - for j in range (len (ci[i])): - if ci[i][j].ndim>2: - ci_i.append (ci[i][j][0]) - else: - ci_i.append (ci[i][j]) - ci_dm.append (ci_i) - casdm1fs = las.make_casdm1s_sub (ci=ci_dm) - - nao, nmo = mo_coeff.shape - ncore = las.ncore - nocc = ncore + las.ncas - ncas_sub = las.ncas_sub - nelecas_sub = las.nelecas_sub - - # Passing casdm1 or lasdm1 only affects the canonicalization of the active orbitals - umat = np.zeros_like (mo_coeff) - casdm1s = np.stack ([linalg.block_diag (*[dm[0] for dm in casdm1fs]), - linalg.block_diag (*[dm[1] for dm in casdm1fs])], axis=0) - fock = mo_coeff.conjugate ().T @ las.get_fock (mo_coeff=mo_coeff, casdm1s=casdm1s, veff=veff) - fock = fock @ mo_coeff - if natorb_casdm1 is None: # State-average natural orbitals by default - natorb_casdm1 = casdm1s.sum (0) - - # Inactive-inactive and virtual-virtual - ene, umat = _eig_inactive_virtual (las, fock, orbsym=orbsym) - idx = np.arange (nmo, dtype=int) - if ncore: idx[:ncore] = idx[:ncore][np.argsort (ene[:ncore])] - if nmo-nocc: idx[nocc:] = idx[nocc:][np.argsort (ene[nocc:])] - umat = umat[:,idx] - if orbsym is not None: orbsym = orbsym[idx] - # Active-active - check_diag = natorb_casdm1.copy () - for ix, ncas in enumerate (ncas_sub): - i = sum (ncas_sub[:ix]) - j = i + ncas - check_diag[i:j,i:j] = 0.0 - is_block_diag = np.amax (np.abs (check_diag)) < 1e-8 - if is_block_diag: - # No off-diagonal RDM elements -> extra effort to prevent diagonalizer from breaking frags - for isub, (ncas, nelecas) in enumerate (zip (ncas_sub, nelecas_sub)): - i = sum (ncas_sub[:isub]) - j = i + ncas - dm1 = natorb_casdm1[i:j,i:j] - i += ncore - j += ncore - orbsym_i = None if orbsym is None else orbsym[i:j] - occ, umat[i:j,i:j] = las._eig (dm1, 0, 0, orbsym_i) - idx = np.argsort (occ)[::-1] - umat[i:j,i:j] = umat[i:j,i:j][:,idx] - if orbsym_i is not None: orbsym[i:j] = orbsym[i:j][idx] - if ci is not None: - fcibox = las.fciboxes[isub] - ci[isub] = fcibox.states_transform_ci_for_orbital_rotation ( - ci[isub], ncas, nelecas, umat[i:j,i:j]) - else: # You can't get proper LAS-type CI vectors w/out active space fragmentation - ci = None - orbsym_cas = None if orbsym is None else orbsym[ncore:nocc] - occ, umat[ncore:nocc,ncore:nocc] = las._eig (natorb_casdm1, 0, 0, orbsym_cas) - idx = np.argsort (occ)[::-1] - umat[ncore:nocc,ncore:nocc] = umat[ncore:nocc,ncore:nocc][:,idx] - if orbsym_cas is not None: orbsym[ncore:nocc] = orbsym[ncore:nocc][idx] - - # Final - mo_occ = np.zeros (nmo, dtype=natorb_casdm1.dtype) - if ncore: mo_occ[:ncore] = 2 - ucas = umat[ncore:nocc,ncore:nocc] - mo_occ[ncore:nocc] = ((natorb_casdm1 @ ucas) * ucas).sum (0) - mo_ene = ((fock @ umat) * umat.conjugate ()).sum (0) - mo_ene[ncore:][:sum (ncas_sub)] = 0.0 - mo_coeff = mo_coeff @ umat - if orbsym is not None: - ''' - print ("This is the second call to label_orb_symm inside of canonicalize") - orbsym = symm.label_orb_symm (las.mol, las.mol.irrep_id, - las.mol.symm_orb, mo_coeff, - s=las._scf.get_ovlp ()) - #mo_coeff = las.label_symmetry_(mo_coeff) - ''' - mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) - if h2eff_sub is not None: - h2eff_sub = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*las.ncas, -1)) - h2eff_sub = h2eff_sub.reshape (nmo, las.ncas, las.ncas, las.ncas) - h2eff_sub = np.tensordot (umat, h2eff_sub, axes=((0),(0))) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(1))).transpose (1,0,2,3) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(2))).transpose (1,2,0,3) - h2eff_sub = np.tensordot (ucas, h2eff_sub, axes=((0),(3))).transpose (1,2,3,0) - h2eff_sub = h2eff_sub.reshape (nmo*las.ncas, las.ncas, las.ncas) - h2eff_sub = lib.numpy_helper.pack_tril (h2eff_sub).reshape (nmo, -1) - - # I/O - log = lib.logger.new_logger (las, las.verbose) - if las.verbose >= lib.logger.INFO: - if is_block_diag: - for isub, nlas in enumerate (ncas_sub): - log.info ("Fragment %d natural orbitals", isub) - i = ncore + sum (ncas_sub[:isub]) - j = i + nlas - log.info ('Natural occ %s', str (mo_occ[i:j])) - log.info ('Natural orbital (expansion on AOs) in CAS space') - label = las.mol.ao_labels() - mo_las = mo_coeff[:,i:j] - dump_mat.dump_rec(log.stdout, mo_las, label, start=1) - else: - log.info ("Delocalized natural orbitals do not reflect LAS fragmentation") - log.info ('Natural occ %s', str (mo_occ[ncore:nocc])) - log.info ('Natural orbital (expansion on AOs) in CAS space') - label = las.mol.ao_labels() - mo_las = mo_coeff[:,ncore:nocc] - dump_mat.dump_rec(log.stdout, mo_las, label, start=1) - - return mo_coeff, mo_ene, mo_occ, ci, h2eff_sub - -def get_init_guess_ci (las, mo_coeff=None, h2eff_sub=None, ci0=None): - # TODO: come up with a better algorithm? This might be working better than what I had before - # but it omits inter-active Coulomb and exchange interactions altogether. Is there a - # non-outer-product algorithm for finding the lowest-energy single product of CSFs? - if mo_coeff is None: mo_coeff = las.mo_coeff - if ci0 is None: ci0 = [[None for i in range (las.nroots)] for j in range (las.nfrags)] - if h2eff_sub is None: h2eff_sub = las.get_h2eff (mo_coeff) - nmo = mo_coeff.shape[-1] - ncore, ncas = las.ncore, las.ncas - nocc = ncore + ncas - dm1_core= 2 * mo_coeff[:,:ncore] @ mo_coeff[:,:ncore].conj ().T - h1e_ao = las._scf.get_fock (dm=dm1_core) - eri_cas = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)) - eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) - eri_cas = eri_cas[ncore:nocc] - for ix, (fcibox, norb, nelecas) in enumerate (zip (las.fciboxes,las.ncas_sub,las.nelecas_sub)): - i = sum (las.ncas_sub[:ix]) - j = i + norb - mo = mo_coeff[:,ncore+i:ncore+j] - moH = mo.conj ().T - h1e = moH @ h1e_ao @ mo - h1e = [h1e, h1e] - eri = eri_cas[i:j,i:j,i:j,i:j] - for iy, solver in enumerate (fcibox.fcisolvers): - nelec = fcibox._get_nelec (solver, nelecas) - ndet = tuple ([cistring.num_strings (norb, n) for n in nelec]) - if isinstance (ci0[ix][iy], np.ndarray) and ci0[ix][iy].size==ndet[0]*ndet[1]: continue - if hasattr (mo_coeff, 'orbsym'): - solver.orbsym = mo_coeff.orbsym[ncore+i:ncore+j] - hdiag_csf = solver.make_hdiag_csf (h1e, eri, norb, nelec, max_memory=las.max_memory) - ci0[ix][iy] = solver.get_init_guess (norb, nelec, solver.nroots, hdiag_csf) - if solver.nroots==1: - ci0[ix][iy] = ci0[ix][iy][0] - else: - ci0[ix][iy] = np.stack (ci0[ix][iy], axis=0) - return ci0 - -def get_space_info (las): - ''' Retrieve the quantum numbers defining the states of a LASSCF calculation ''' - nfrags, nroots = las.nfrags, las.nroots - charges = np.zeros ((nroots, nfrags), dtype=np.int32) - wfnsyms, spins, smults = charges.copy (), charges.copy (), charges.copy () - for ifrag, fcibox in enumerate (las.fciboxes): - for iroot, solver in enumerate (fcibox.fcisolvers): - nelec = fcibox._get_nelec (solver, las.nelecas_sub[ifrag]) - charges[iroot,ifrag] = np.sum (las.nelecas_sub[ifrag]) - np.sum (nelec) - spins[iroot,ifrag] = nelec[0]-nelec[1] - smults[iroot,ifrag] = solver.smult - try: - wfnsyms[iroot,ifrag] = solver.wfnsym or 0 - except ValueError as e: - wfnsyms[iroot,ifrag] = symm.irrep_name2id (las.mol.groupname, solver.wfnsym) - return charges, spins, smults, wfnsyms - -def assert_no_duplicates (las, tab=None): - log = lib.logger.new_logger (las, las.verbose) - if tab is None: tab = np.stack (get_space_info (las), axis=-1) - tab_uniq, uniq_idx, uniq_inv, uniq_cnts = np.unique (tab, return_index=True, - return_inverse=True, return_counts=True, axis=0) - idx_dupe = uniq_cnts>1 - try: - err_str = ('LAS state basis has duplicates; details in logfile for ' - 'verbose >= INFO (4) [more details for verbose > INFO].\n' - '(Disable this assertion by passing assert_no_dupes=False ' - 'to the kernel, lasci, and state_average(_) functions.)') - assert (~np.any (idx_dupe)), err_str - except AssertionError as e: - dupe_idx = uniq_idx[idx_dupe] - dupe_cnts = uniq_cnts[idx_dupe] - for i, (ix, cnt, col) in enumerate (zip (uniq_idx, uniq_cnts, tab_uniq)): - if cnt==1: continue - log.info ('State %d appears %d times', ix, cnt) - idx_thisdupe = np.where (uniq_inv==i)[0] - row = col.T - log.debug ('As states {}'.format (idx_thisdupe)) - log.debug ('Charges = {}'.format (row[0])) - log.debug ('2M_S = {}'.format (row[1])) - log.debug ('2S+1 = {}'.format (row[2])) - log.debug ('Wfnsyms = {}'.format (row[3])) - raise e from None - -def state_average_(las, weights=[0.5,0.5], charges=None, spins=None, - smults=None, wfnsyms=None, assert_no_dupes=True): - ''' Transform LASCI/LASSCF object into state-average LASCI/LASSCF - - Args: - las: LASCI/LASSCF instance - - Kwargs: - weights: list of float; required - E_SA = sum_i weights[i] E[i] is used to optimize the orbitals - charges: 2d ndarray or nested list of integers - spins: 2d ndarray or nested list of integers - For the jth fragment in the ith state, - neleca = (sum(las.nelecas_sub[j]) - charges[i][j] + spins[i][j]) // 2 - nelecb = (sum(las.nelecas_sub[j]) - charges[i][j] - spins[i][j]) // 2 - Defaults to - charges[i][j] = 0 - spins[i][j] = las.nelecas_sub[j][0] - las.nelecas_sub[j][1] - smults: 2d ndarray or nested list of integers - For the jth fragment in the ith state, - smults[i][j] = (2*s)+1 - where "s" is the total spin quantum number, - S^2|j,i> = s*(s+1)|j,i> - Defaults to - smults[i][j] = abs (spins[i][j]) + 1 - wfnsyms: 2d ndarray or nested list of integers or strings - For the jth fragment of the ith state, - wfnsyms[i][j] - identifies the point-group irreducible representation - Defaults to all zeros (i.e., the totally-symmetric irrep) - - Returns: - las: LASCI/LASSCF instance - The first positional argument, modified in-place into a - state-averaged LASCI/LASSCF instance. - - ''' - old_states = np.stack (get_space_info (las), axis=-1) - nroots = len (weights) - nfrags = las.nfrags - if charges is None: charges = np.zeros ((nroots, nfrags), dtype=np.int32) - if wfnsyms is None: wfnsyms = np.zeros ((nroots, nfrags), dtype=np.int32) - if spins is None: spins = np.asarray ([[n[0]-n[1] for n in las.nelecas_sub] for i in weights]) - if smults is None: smults = np.abs (spins)+1 - - charges = np.asarray (charges) - wfnsyms = np.asarray (wfnsyms) - spins = np.asarray (spins) - smults = np.asarray (smults) - if np.issubsctype (wfnsyms.dtype, np.str_): - wfnsyms_str = wfnsyms - wfnsyms = np.zeros (wfnsyms_str.shape, dtype=np.int32) - for ix, wfnsym in enumerate (wfnsyms_str.flat): - try: - wfnsyms.flat[ix] = symm.irrep_name2id (las.mol.groupname, wfnsym) - except (TypeError, KeyError) as e: - wfnsyms.flat[ix] = int (wfnsym) - if nfrags == 1: - charges = np.atleast_2d (np.squeeze (charges)).T - wfnsyms = np.atleast_2d (np.squeeze (wfnsyms)).T - spins = np.atleast_2d (np.squeeze (spins)).T - smults = np.atleast_2d (np.squeeze (smults)).T - new_states = np.stack ([charges, spins, smults, wfnsyms], axis=-1) - if assert_no_dupes: assert_no_duplicates (las, tab=new_states) - - las.fciboxes = [get_h1e_zipped_fcisolver (state_average_n_mix ( - las, [csf_solver (las.mol, smult=s2p1).set (charge=c, spin=m2, wfnsym=ir) - for c, m2, s2p1, ir in zip (c_r, m2_r, s2p1_r, ir_r)], weights).fcisolver) - for c_r, m2_r, s2p1_r, ir_r in zip (charges.T, spins.T, smults.T, wfnsyms.T)] - las.e_states = np.zeros (nroots) - las.nroots = nroots - las.weights = weights - - if las.ci is not None: - log = lib.logger.new_logger(las, las.verbose) - log.debug (("lasci.state_average: Cached CI vectors may be present.\n" - "Looking for matches between old and new LAS states...")) - ci0 = [[None for i in range (nroots)] for j in range (nfrags)] - new_states = np.stack ([charges, spins, smults, wfnsyms], - axis=-1).reshape (nroots, nfrags*4) - old_states = old_states.reshape (-1, nfrags*4) - for iroot, row in enumerate (old_states): - idx = np.all (new_states == row[None,:], axis=1) - if np.count_nonzero (idx) == 1: - jroot = np.where (idx)[0][0] - log.debug ("Old state {} -> New state {}".format (iroot, jroot)) - for ifrag in range (nfrags): - ci0[ifrag][jroot] = las.ci[ifrag][iroot] - elif np.count_nonzero (idx) > 1: - raise RuntimeError ("Duplicate states specified?\n{}".format (idx)) - las.ci = ci0 - las.converged = False - return las - -@lib.with_doc(''' A version of lasci.state_average_ that creates a copy instead of modifying the - LASCI/LASSCF method instance in place. - - See lasci.state_average_ docstring below:\n\n''' + state_average_.__doc__) -def state_average (las, weights=[0.5,0.5], charges=None, spins=None, - smults=None, wfnsyms=None, assert_no_dupes=True): - is_scanner = isinstance (las, lib.SinglePointScanner) - if is_scanner: las = las.undo_scanner () - new_las = las.__class__(las._scf, las.ncas_sub, las.nelecas_sub) - new_las.__dict__.update (las.__dict__) - new_las.mo_coeff = las.mo_coeff.copy () - if getattr (las.mo_coeff, 'orbsym', None) is not None: - new_las.mo_coeff = lib.tag_array (new_las.mo_coeff, - orbsym=las.mo_coeff.orbsym) - new_las.ci = None - if las.ci is not None: - new_las.ci = [[c2.copy () if isinstance (c2, np.ndarray) else None - for c2 in c1] for c1 in las.ci] - las = state_average_(new_las, weights=weights, charges=charges, spins=spins, - smults=smults, wfnsyms=wfnsyms, assert_no_dupes=assert_no_dupes) - if is_scanner: las = las.as_scanner () - return las - -def get_single_state_las (las, state=0): - ''' Quickly extract a state-specific las calculation from a state-average one ''' - charges, spins, smults, wfnsyms = get_space_info (las) - charges = charges[state:state+1] - spins = spins[state:state+1] - smults = smults[state:state+1] - wfnsyms = wfnsyms[state:state+1] - weights = [1,] - return state_average (las, weights=weights, charges=charges, spins=spins, smults=smults, - wfnsyms=wfnsyms) - -def run_lasci (las, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=0, - assert_no_dupes=False, _dry_run=False): - '''Self-consistently optimize the CI vectors of a LAS state with - frozen orbitals using a fixed-point algorithm. "lasci_" (with the - trailing underscore) sets self.mo_coeff from the kwarg if it is passed; - "lasci" (without the trailing underscore) leaves self.mo_coeff unchanged. - - Kwargs: - mo_coeff : ndarray of shape (nao,nmo) - MO coefficients; defaults to self.mo_coeff - ci0 : list (length nfrags) of list (length nroots) of ndarrays - Contains CI vectors for initial guess - lroots : ndarray of shape (nfrags,nroots) - Number of local roots in each fragment for each global state. - The corresponding local weights are set to [1,0,0,0,...]. - lweights : list of length nfrags of list of length nroots of sequence - Weights of local roots in each fragment for each global state. - Passing lweights is incompatible with passing lroots. Defaults - to, i.e., np.ones (las.nfrags, las.nroots, 1).tolist () - verbose : integer - See pyscf.lib.logger. - assert_no_dupes : logical - If True, checks state list for duplicate states - _dry_run : logical - If True, sets up the fcisolvers with the appropriate tags, but does - not run fcisolver kernels. - - Returns: - converged : list of length nroots of logical - Stores whether the calculation for each state successfully converged - e_tot : float - (State-averaged) total energy - e_states : list of length nroots - List of each state energy - e_cas : list of length nroots - List of the CAS space energy of each state - ci : list (length nfrags) of list (length nroots) of ndarrays - Contains optimized CI vectors - ''' - if assert_no_dupes: assert_no_duplicates (las) - if lroots is not None and lweights is not None: - raise RuntimeError ("lroots sets lweights: pass either or none but not both") - elif lweights is None: - if lroots is None: lroots = np.ones ((las.nfrags, las.nroots), dtype=int) - lweights = [] - for i in range (las.nfrags): - lwi = [] - for j in range (las.nroots): - lwij = np.zeros (lroots[i,j]) - lwij[0] = 1 - lwi.append (lwij) - lweights.append (lwi) - nao, nmo = mo_coeff.shape - ncore, ncas = las.ncore, las.ncas - nocc = ncore + ncas - ncas_sub = las.ncas_sub - nelecas_sub = las.nelecas_sub - orbsym = getattr (mo_coeff, 'orbsym', None) - if orbsym is not None: orbsym=orbsym[ncore:nocc] - elif isinstance (las, LASCISymm): - mo_coeff = las.label_symmetry_(mo_coeff) - orbsym = mo_coeff.orbsym[ncore:nocc] - log = lib.logger.new_logger (las, verbose) - - h1eff, energy_core = las.h1e_for_cas (mo_coeff=mo_coeff, - ncas=las.ncas, ncore=las.ncore) - h2eff = las.get_h2eff (mo_coeff) - if (ci0 is None or any ([c is None for c in ci0]) or - any ([any ([c2 is None for c2 in c1]) for c1 in ci0])): - ci0 = las.get_init_guess_ci (mo_coeff, h2eff, ci0) - eri_cas = lib.numpy_helper.unpack_tril ( - h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, - ncas, ncas)[ncore:nocc] - - e_cas = np.empty (las.nroots) - e_states = np.empty (las.nroots) - ci1 = [[None for c2 in c1] for c1 in ci0] - converged = [] - t = (lib.logger.process_clock(), lib.logger.perf_counter()) - e_lexc = [[None for i in range (las.nroots)] for j in range (las.nfrags)] - for state in range (las.nroots): - fcisolvers = [b.fcisolvers[state] for b in las.fciboxes] - ci0_i = [c[state] for c in ci0] - solver = ImpureProductStateFCISolver (fcisolvers, stdout=las.stdout, - lweights=[l[state] for l in lweights], verbose=verbose) - # TODO: better handling of CSF symmetry quantum numbers in general - for ix, s in enumerate (solver.fcisolvers): - i = sum (ncas_sub[:ix]) - j = i + ncas_sub[ix] - if orbsym is not None: s.orbsym = orbsym[i:j] - s.norb = ncas_sub[ix] - s.nelec = solver._get_nelec (s, nelecas_sub[ix]) - s.check_transformer_cache () - if _dry_run: continue - conv, e_i, ci_i = solver.kernel (h1eff, eri_cas, ncas_sub, nelecas_sub, - ecore=0, ci0=ci0_i, orbsym=orbsym, conv_tol_grad=las.conv_tol_grad, - conv_tol_self=las.conv_tol_self, max_cycle_macro=las.max_cycle_macro) - e_cas[state] = e_i - e_states[state] = e_i + energy_core - for frag, s in enumerate (solver.fcisolvers): - e_loc = np.atleast_1d (getattr (s, 'e_states', e_i)) - e_lexc[frag][state] = e_loc - e_i - for c1, c2, s, no, ne in zip (ci1, ci_i, solver.fcisolvers, ncas_sub, nelecas_sub): - ne = solver._get_nelec (s, ne) - ndeta, ndetb = [cistring.num_strings (no, n) for n in ne] - shape = [s.nroots, ndeta, ndetb] if s.nroots>1 else [ndeta, ndetb] - c1[state] = np.asarray (c2).reshape (*shape) - if not conv: log.warn ('State %d LASCI not converged!', state) - converged.append (conv) - t = log.timer ('State {} LASCI'.format (state), *t) - - e_tot = np.dot (las.weights, e_states) - return converged, e_tot, e_states, e_cas, e_lexc, ci1 - -class LASCINoSymm (casci.CASCI): - - def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, frozen=None, **kwargs): - if isinstance(ncas,int): - ncas = [ncas] - ncas_tot = sum (ncas) - nel_tot = [0, 0] - new_nelecas = [] - for ix, nel in enumerate (nelecas): - if isinstance (nel, (int, np.integer)): - nb = nel // 2 - na = nb + (nel % 2) - else: - na, nb = nel - new_nelecas.append ((na, nb)) - nel_tot[0] += na - nel_tot[1] += nb - nelecas = new_nelecas - self.nroots = 1 - super().__init__(mf, ncas=ncas_tot, nelecas=nel_tot, ncore=ncore) - self.chkfile = self._scf.chkfile - if spin_sub is None: spin_sub = [1 + abs(ne[0]-ne[1]) for ne in nelecas] - self.ncas_sub = np.asarray (ncas) - self.nelecas_sub = np.asarray (nelecas) - assert (len (self.nelecas_sub) == self.nfrags) - self.frozen = frozen - self.conv_tol_grad = 1e-4 - self.conv_tol_self = 1e-10 - self.ah_level_shift = 1e-8 - self.max_cycle_macro = 50 - self.max_cycle_micro = 5 - keys = set(('e_states', 'fciboxes', 'nroots', 'weights', 'ncas_sub', 'nelecas_sub', - 'conv_tol_grad', 'conv_tol_self', 'max_cycle_macro', 'max_cycle_micro', - 'ah_level_shift', 'states_converged', 'chkfile', 'e_lexc')) - self._keys = set(self.__dict__.keys()).union(keys) - self.fciboxes = [] - if isinstance(spin_sub,int): - self.fciboxes.append(self._init_fcibox(spin_sub,self.nelecas_sub[0])) - else: - assert (len (spin_sub) == self.nfrags) - for smult, nel in zip (spin_sub, self.nelecas_sub): - self.fciboxes.append (self._init_fcibox (smult, nel)) - self.weights = [1.0] - self.e_states = [0.0] - self.e_lexc = [[np.array ([0]),],] - - def _init_fcibox (self, smult, nel): - s = csf_solver (self.mol, smult=smult) - s.spin = nel[0] - nel[1] - return get_h1e_zipped_fcisolver (state_average_n_mix (self, [s], [1.0]).fcisolver) - - @property - def nfrags (self): return len (self.ncas_sub) - - def get_mo_slice (self, idx, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - mo = mo_coeff[:,self.ncore:] - for offs in self.ncas_sub[:idx]: - mo = mo[:,offs:] - mo = mo[:,:self.ncas_sub[idx]] - return mo - - def ao2mo (self, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - nao, nmo = mo_coeff.shape - ncore, ncas = self.ncore, self.ncas - nocc = ncore + ncas - mo_cas = mo_coeff[:,ncore:nocc] - mo = [mo_coeff, mo_cas, mo_cas, mo_cas] - if getattr (self, 'with_df', None) is not None: - # Store intermediate with one contracted ao index for faster calculation of exchange! - bPmn = sparsedf_array (self.with_df._cderi) - bmuP = bPmn.contract1 (mo_cas) - buvP = np.tensordot (mo_cas.conjugate (), bmuP, axes=((0),(0))) - eri_muxy = np.tensordot (bmuP, buvP, axes=((2),(2))) - eri = np.tensordot (mo_coeff.conjugate (), eri_muxy, axes=((0),(0))) - eri = lib.pack_tril (eri.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) - eri = lib.tag_array (eri, bmPu=bmuP.transpose (0,2,1)) - if self.verbose > lib.logger.DEBUG: - eri_comp = self.with_df.ao2mo (mo, compact=True) - lib.logger.debug(self,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) - elif getattr (self._scf, '_eri', None) is not None: - eri = ao2mo.incore.general (self._scf._eri, mo, compact=True) - else: - eri = ao2mo.outcore.general_iofree (self.mol, mo, compact=True) - if eri.shape != (nmo,ncas*ncas*(ncas+1)//2): - try: - eri = eri.reshape (nmo, ncas*ncas*(ncas+1)//2) - except ValueError as e: - assert (nmo == ncas), str (e) - eri = ao2mo.restore ('2kl', eri, nmo).reshape (nmo, ncas*ncas*(ncas+1)//2) - return eri - - def get_h2eff_slice (self, h2eff, idx, compact=None): - ncas_cum = np.cumsum ([0] + self.ncas_sub.tolist ()) - i = ncas_cum[idx] - j = ncas_cum[idx+1] - ncore = self.ncore - nocc = ncore + self.ncas - eri = h2eff[ncore:nocc,:].reshape (self.ncas*self.ncas, -1) - ix_i, ix_j = np.tril_indices (self.ncas) - eri = eri[(ix_i*self.ncas)+ix_j,:] - eri = ao2mo.restore (1, eri, self.ncas)[i:j,i:j,i:j,i:j] - if compact: eri = ao2mo.restore (compact, eri, j-i) - return eri - - get_h1eff = get_h1las = h1e_for_las = h1e_for_las - get_h2eff = ao2mo - ''' - def get_h2eff (self, mo_coeff=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - if isinstance (self, _DFLASCI): - mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] - return self.with_df.ao2mo (mo_cas) - return self.ao2mo (mo_coeff) - ''' - - get_fock = get_fock - get_grad = get_grad - get_grad_orb = get_grad_orb - get_grad_ci = get_grad_ci - _hop = lasci_sync.LASCI_HessianOperator - _kern = lasci_sync.kernel - def get_hop (self, mo_coeff=None, ci=None, ugg=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ugg is None: ugg = self.get_ugg () - return self._hop (self, ugg, mo_coeff=mo_coeff, ci=ci, **kwargs) - canonicalize = canonicalize - - def _finalize(self): - log = lib.logger.new_logger (self, self.verbose) - nroots_prt = len (self.e_states) - if self.verbose <= lib.logger.INFO: - nroots_prt = min (nroots_prt, 100) - if nroots_prt < len (self.e_states): - log.info (("Printing a maximum of 100 state energies;" - " increase self.verbose to see them all")) - if nroots_prt > 1: - log.info ("LASCI state-average energy = %.15g", self.e_tot) - for i, e in enumerate (self.e_states[:nroots_prt]): - log.info ("LASCI state %d energy = %.15g", i, e) - else: - log.info ("LASCI energy = %.15g", self.e_tot) - return - - - def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, conv_tol_grad=None, - assert_no_dupes=False, verbose=None, _kern=None): - if mo_coeff is None: - mo_coeff = self.mo_coeff - else: - self.mo_coeff = mo_coeff - if ci0 is None: ci0 = self.ci - if verbose is None: verbose = self.verbose - if conv_tol_grad is None: conv_tol_grad = self.conv_tol_grad - if _kern is None: _kern = self._kern - log = lib.logger.new_logger(self, verbose) - - if self.verbose >= lib.logger.WARN: - self.check_sanity() - self.dump_flags(log) - - for fcibox in self.fciboxes: - fcibox.verbose = self.verbose - fcibox.stdout = self.stdout - fcibox.nroots = self.nroots - fcibox.weights = self.weights - # TODO: local excitations and locally-impure states in LASSCF kernel - do_warn=False - if ci0 is not None: - for i, ci0_i in enumerate (ci0): - if ci0_i is None: continue - for j, ci0_ij in enumerate (ci0_i): - if ci0_ij is None: continue - if np.asarray (ci0_ij).ndim>2: - do_warn=True - ci0_i[j] = ci0_ij[0] - if do_warn: log.warn ("Discarding all but the first root of guess CI vectors!") - - self.converged, self.e_tot, self.e_states, self.mo_energy, self.mo_coeff, self.e_cas, \ - self.ci, h2eff_sub, veff = _kern(mo_coeff=mo_coeff, ci0=ci0, verbose=verbose, \ - casdm0_fr=casdm0_fr, conv_tol_grad=conv_tol_grad, assert_no_dupes=assert_no_dupes) - - self._finalize () - return self.e_tot, self.e_cas, self.ci, self.mo_coeff, self.mo_energy, h2eff_sub, veff - - def states_make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: - return [np.zeros ((self.nroots,2,ncas,ncas)) for ncas in ncas_sub] - casdm1s = [] - for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - if ci_i is None: - dm1a = dm1b = np.zeros ((ncas, ncas)) - else: - dm1a, dm1b = fcibox.states_make_rdm1s (ci_i, ncas, nelecas) - casdm1s.append (np.stack ([dm1a, dm1b], axis=1)) - return casdm1s - - def make_casdm1s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, w=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - if w is None: w = self.weights - return [np.einsum ('rspq,r->spq', dm1, w) for dm1 in casdm1frs] - - def states_make_casdm1s (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - return np.stack ([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] - for dm1rs in casdm1frs]) - for ispin in (0, 1)], axis=0) - for iroot in range (self.nroots)], axis=0) - - def states_make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 1-RDMs in the MO basis for each subspace in sequence ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - casdm2 = [] - for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) - return casdm2 - - def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=None, **kwargs): - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, **kwargs) - return [np.einsum ('rijkl,r->ijkl', dm2, box.weights) - for dm2, box in zip (casdm2fr, self.fciboxes)] - - #SV casdm2s - - def make_casdm2s(self, ci=None, **kwargs): - # Make the full-dimensional casdm2s spanning the collective active space - casdm2s_sub = self.make_casdm2s_sub (ci=ci, **kwargs) - casdm2a = linalg.block_diag (*[dm[0] for dm in casdm2s_sub]) - casdm2b = linalg.block_diag (*[dm[1] for dm in casdm2s_sub]) - return np.stack ([casdm2a, casdm2b], axis=0) - - - #SV casdm2s_sub - def make_casdm2s_sub(self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm2frs=None, w=None, **kwargs): - if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - if w is None: w = self.weights - print("SV w = ", w) - return [np.einsum ('rsijkl,r->sijkl', dm2, w) for dm2 in casdm2frs] - - #SV states_make_casdm2s_sub - #There are two ways to construct states_make_casdm2s_sub, one is thisi coming from states_make_casdm1s_sub - - def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - # Spin-separated 2-RDMs in the MO basis for each subspace in sequence - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: - return [np.zeros ((self.nroots,2,ncas,ncas,ncas,ncas)) for ncas in ncas_sub] - casdm2s = [] - for fcibox, ci_i, ncas, nelecas in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - if ci_i is None: - dm2a = dm2b = np.zeros ((ncas, ncas,ncas,ncas)) - else: - dm2a, dm2b, _ = fcibox.states_make_rdm12s (ci_i, ncas, nelecas)[1] # IDK what this third ncas*4 matrix is, so just created a placeholder for it - casdm2s.append (np.stack ([dm2a, dm2b], axis=1)) - return casdm2s - - #SV states_make_casdm2s_sub - #This is the second coming from states_make_casdm2_sub. I think the above one must be the one to be used but the problem is IDK whether states_make_rdm2s exists or not, maybe states_make_rdm12s exists - ''' - def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - #Spin-separated 1-RDMs in the MO basis for each subspace in sequence - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - casdm2s = [] - for fcibox, ci_i, ncas, nel in zip (self.fciboxes, ci, ncas_sub, nelecas_sub): - casdm2.append (fcibox.states_make_rdm12 (ci_i, ncas, nel)[-1]) - return casdm2 - ''' - - #SV states_make_casdm2s - def states_make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm2frs=None, **kwargs): - if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - print ("SV self.nroots = ", self.nroots) - - for iroot in range (self.nroots): - spin_matrices = [] - for ispin in (0, 1): - selected_arrays = np.array([dm2rs[iroot][ispin]for dm2rs in casdm2frs]) - print ("SV selected_arrays = ", selected_arrays, selected_arrays.shape) - block_diag_matrix = linalg.block_diag (*selected_arrays) - spin_matrices.append(block_diag_matrix) - spin_matrices_stacked = np.stack(spin_matrices, axis=0) - print ("SV spin_matrices_stacked = ", spin_matrices_stacked) - - - return np.stack ([np.stack ([linalg.block_diag (*[dm2rs[iroot][ispin] - for dm2rs in casdm2frs]) - for ispin in (0, 1)], axis=0) - for iroot in range (self.nroots)], axis=0) - - def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, - nelecas_sub=None, casdm1rs=None, casdm1frs=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1rs is None: casdm1rs = self.states_make_casdm1s (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, - **kwargs) - mo_core = mo_coeff[:,:self.ncore] - mo_cas = mo_coeff[:,self.ncore:][:,:self.ncas] - dm1rs = np.tensordot (mo_cas.conj (), np.dot (casdm2rs, mo_cas.conj ().T), axes=((1),(2))) - dm1rs = dm1rs.transpose (1,2,0,3) - dm1rs += (mo_core @ mo_core.conj ().T)[None,None,:,:] - return dm1rs - - def make_rdm1s_sub (self, mo_coeff=None, ci=None, ncas_sub=None, - nelecas_sub=None, include_core=False, casdm1s_sub=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ''' Same as make_casdm1s_sub, but in the ao basis ''' - rdm1s = [] - for idx, casdm1s in enumerate (casdm1s_sub): - mo = self.get_mo_slice (idx, mo_coeff=mo_coeff) - moH = mo.conjugate ().T - rdm1s.append (np.tensordot (mo, np.dot (casdm1s,moH), axes=((1),(1))).transpose(1,0,2)) - if include_core and self.ncore: - mo_core = mo_coeff[:,:self.ncore] - moH_core = mo_core.conjugate ().T - dm_core = mo_core @ moH_core - rdm1s = [np.stack ([dm_core, dm_core], axis=0)] + rdm1s - rdm1s = np.stack (rdm1s, axis=0) - return rdm1s - - def make_rdm1_sub (self, **kwargs): - return self.make_rdm1s_sub (**kwargs).sum (1) - - def make_rdm1s (self, mo_coeff=None, ncore=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - mo = mo_coeff[:,:ncore] - moH = mo.conjugate ().T - dm_core = mo @ moH - dm_cas = self.make_rdm1s_sub (mo_coeff=mo_coeff, **kwargs).sum (0) - return dm_core[None,:,:] + dm_cas - - def make_rdm1 (self, mo_coeff=None, ci=None, **kwargs): - return self.make_rdm1s (mo_coeff=mo_coeff, ci=ci, **kwargs).sum (0) - - def make_casdm1s (self, ci=None, **kwargs): - ''' Make the full-dimensional casdm1s spanning the collective active space ''' - casdm1s_sub = self.make_casdm1s_sub (ci=ci, **kwargs) - casdm1a = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) - casdm1b = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) - return np.stack ([casdm1a, casdm1b], axis=0) - - def make_casdm1 (self, ci=None, **kwargs): - ''' Spin-sum make_casdm1s ''' - return self.make_casdm1s (ci=ci, **kwargs).sum (0) - - def states_make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' Make the full-dimensional casdm2 spanning the collective active space ''' - raise DeprecationWarning ( - ("states_make_casdm2 is BANNED. There is no reason to EVER make an array this huge.\n" - "Use states_make_casdm*_sub instead, and substitute the factorization into your " - "expressions.") - ) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - casdm2r = np.zeros ((self.nroots,ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2 in enumerate (casdm2fr): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2r[:, i:j, i:j, i:j, i:j] = dm2 - # Off-diagonal - for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2r): - dma1, dmb1 = dm1s1[0], dm1s1[1] - dma2, dmb2 = dm1s2[0], dm1s2[1] - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) - +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2r - - def state_make_casdm1s(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, **kwargs): - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - casdm1s = np.stack([np.stack ([linalg.block_diag (*[dm1rs[iroot][ispin] - for dm1rs in casdm1frs]) - for ispin in (0, 1)], axis=0) - for iroot in range (self.nroots)], axis=0) - return casdm1s[state] - - def state_make_casdm2(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' State wise casdm2 spanning the collective active space. ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2_r in enumerate (casdm2fr): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2[i:j, i:j, i:j, i:j] = dm2_r[state] - # Off-diagonal - for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - dma1, dmb1 = dm1s1_r[state][0], dm1s1_r[state][1] - dma2, dmb2 = dm1s2_r[state][0], dm1s2_r[state][1] - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) - +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2 - - #SV make_casdm2s - def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm2rs=None, casdm2fs=None, casdm1frs=None, casdm2frs=None, - **kwargs): - - if casdm2r is not None: - return np.einsum ('rijkl,r->ijkl', casdm2r, self.weights) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) - if casdm2fs is None: casdm2f = self.make_casdm2s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2frs=casdm2frs) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - weights = self.weights - casdm2s = np.zeros ((2,ncas,ncas,ncas,ncas)) - # Diagonal of alpha - for isub, dm2 in enumerate (casdm2fs[0]): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2s[0][i:j, i:j, i:j, i:j] = dm2 - # Diagonal of alpha - for isub, dm2 in enumerate (casdm2fs[1]): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2s[1][i:j, i:j, i:j, i:j] = dm2 - # Off-diagonal - for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] - dm1r = dma1r + dmb1r - dm2r = dma2r + dmb2r - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) - + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) - casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2 - - - def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, - **kwargs): - ''' Make the full-dimensional casdm2 spanning the collective active space ''' - if casdm2r is not None: - return np.einsum ('rijkl,r->ijkl', casdm2r, self.weights) - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) - if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - weights = self.weights - casdm2 = np.zeros ((ncas,ncas,ncas,ncas)) - # Diagonal - for isub, dm2 in enumerate (casdm2f): - i = ncas_cum[isub] - j = ncas_cum[isub+1] - casdm2[i:j, i:j, i:j, i:j] = dm2 - # Off-diagonal - for (isub1, dm1rs1), (isub2, dm1rs2) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] - dma2r, dmb2r = dm1rs2[:,0], dm1rs2[:,1] - dm1r = dma1r + dmb1r - dm2r = dma2r + dmb2r - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) - + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) - casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - return casdm2 - - def get_veff (self, mol=None, dm1s=None, hermi=1, spin_sep=False, **kwargs): - ''' Returns a spin-summed veff! If dm1s isn't provided, builds from self.mo_coeff, self.ci - etc. ''' - if mol is None: mol = self.mol - nao = mol.nao_nr () - if dm1s is None: dm1s = self.make_rdm1 (include_core=True, **kwargs).reshape (nao, nao) - dm1s = np.asarray (dm1s) - if dm1s.ndim == 2: dm1s = dm1s[None,:,:] - if isinstance (self, _DFLASCI): - vj, vk = self.with_df.get_jk(dm1s, hermi=hermi) - else: - vj, vk = self._scf.get_jk(mol, dm1s, hermi=hermi) - if spin_sep: - assert (dm1s.shape[0] == 2) - return vj.sum (0)[None,:,:] - vk - else: - veff = np.stack ([j - k/2 for j, k in zip (vj, vk)], axis=0) - return np.squeeze (veff) - - def split_veff (self, veff, h2eff_sub, mo_coeff=None, ci=None, casdm1s_sub=None): - ''' Split a spin-summed veff into alpha and beta terms using the h2eff eri array. - Note that this will omit v(up_active - down_active)^virtual_inactive by necessity; - this won't affect anything because the inactive density matrix has no spin component. - On the other hand, it ~is~ necessary to correctly do - - v(up_active - down_active)^unactive_active - - in order to calculate the external orbital gradient at the end of the calculation. - This means that I need h2eff_sub spanning both at least two active subspaces - ~and~ the full orbital range. ''' - veff_c = veff.copy () - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - if casdm1s_sub is None: casdm1s_sub = self.make_casdm1s_sub (ci = ci) - ncore = self.ncore - ncas = self.ncas - nocc = ncore + ncas - nao, nmo = mo_coeff.shape - moH_coeff = mo_coeff.conjugate ().T - smo_coeff = self._scf.get_ovlp () @ mo_coeff - smoH_coeff = smo_coeff.conjugate ().T - veff_s = np.zeros_like (veff_c) - for ix, (ncas_i, casdm1s) in enumerate (zip (self.ncas_sub, casdm1s_sub)): - i = sum (self.ncas_sub[:ix]) - j = i + ncas_i - eri_k = h2eff_sub.reshape (nmo, ncas, -1)[:,i:j,...].reshape (nmo*ncas_i, -1) - eri_k = lib.numpy_helper.unpack_tril (eri_k)[:,i:j,:] - eri_k = eri_k.reshape (nmo, ncas_i, ncas_i, ncas) - sdm = casdm1s[0] - casdm1s[1] - vk_pa = -np.tensordot (eri_k, sdm, axes=((1,2),(0,1))) / 2 - veff_s[:,ncore:nocc] += vk_pa - veff_s[ncore:nocc,:] += vk_pa.T - veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:] / 2 - veff_s[ncore:nocc,ncore:nocc] -= vk_pa[ncore:nocc,:].T / 2 - veff_s = smo_coeff @ veff_s @ smoH_coeff - veffa = veff_c + veff_s - veffb = veff_c - veff_s - return np.stack ([veffa, veffb], axis=0) - - - def states_energy_elec (self, mo_coeff=None, ncore=None, ncas=None, - ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, - casdm1frs=None, casdm2fr=None, veff_core=None, **kwargs): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - if ncas is None: ncas = self.ncas - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: ci = self.ci - if h2eff is None: h2eff = self.get_h2eff (mo_coeff) - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub) - nao, nmo = mo_coeff.shape - nocc = ncore + ncas - mo_core = mo_coeff[:,:ncore] - mo_cas = mo_coeff[:,ncore:nocc] - dm_core = 2*mo_core @ mo_core.conj ().T - if veff_core is None: veff_core = getattr (veff, 'c', None) - if veff_core is None: veff_core = self.get_veff (dm1s=dm_core) - h1eff = self.get_hcore () + veff_core - e0 = 2*np.dot (((h1eff-(veff_core/2)) @ mo_core).ravel (), mo_core.conj().ravel ()) - h1eff = mo_cas.conj ().T @ h1eff @ mo_cas - eri_cas = lib.numpy_helper.unpack_tril (h2eff.reshape (nmo*ncas, ncas*(ncas+1)//2)) - eri_cas = eri_cas.reshape (nmo, ncas, ncas, ncas) - eri_cas = eri_cas[ncore:nocc] - casdm1rs = self.states_make_casdm1s (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, - casdm1frs=casdm1frs) - vj_r = np.tensordot (casdm1rs.sum (1), eri_cas, axes=2) - vk_rs = np.tensordot (casdm1rs, eri_cas, axes=((2,3),(2,1))) - veff_rs = vj_r[:,None,:,:] - vk_rs - - energy_elec = [] - for idx, (dm1s, v) in enumerate (zip (casdm1rs, veff_rs)): - casdm1fs = [dm[idx] for dm in casdm1frs] - casdm2f = [dm[idx] for dm in casdm2fr] - - # 1-body veff terms - h1e = h1eff[None,:,:] + v/2 - e1 = np.dot (h1e.ravel (), dm1s.ravel ()) - - # 2-body cumulant terms - e2 = 0 - for isub, (dm1s, dm2) in enumerate (zip (casdm1fs, casdm2f)): - dm1a, dm1b = dm1s[0], dm1s[1] - dm1 = dm1a + dm1b - cdm2 = dm2 - np.multiply.outer (dm1, dm1) - cdm2 += np.multiply.outer (dm1a, dm1a).transpose (0,3,2,1) - cdm2 += np.multiply.outer (dm1b, dm1b).transpose (0,3,2,1) - eri = self.get_h2eff_slice (h2eff, isub) - te2 = np.tensordot (eri, cdm2, axes=4) / 2 - e2 += te2 - energy_elec.append (e0 + e1 + e2) - self._e1_ref = e0 + e1 - self._e2_ref = e2 - - return energy_elec - - def energy_elec (self, mo_coeff=None, ncore=None, ncas=None, - ncas_sub=None, nelecas_sub=None, ci=None, h2eff=None, veff=None, - casdm1frs=None, casdm2fr=None, **kwargs): - ''' Since the LASCI energy cannot be calculated as simply as ecas + ecore, I need this ''' - if mo_coeff is None: mo_coeff = self.mo_coeff - if ncore is None: ncore = self.ncore - if ncas is None: ncas = self.ncas - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if ci is None: ci = self.ci - if h2eff is None: h2eff = self.get_h2eff (mo_coeff) - casdm1s_sub = self.make_casdm1s_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, - casdm1frs=casdm1frs) - if veff is None: - veff = self.get_veff (dm1s = self.make_rdm1(mo_coeff=mo_coeff,casdm1s_sub=casdm1s_sub)) - veff = self.split_veff (veff, h2eff, mo_coeff=mo_coeff, casdm1s_sub=casdm1s_sub) - - # 1-body veff terms - h1e = self.get_hcore ()[None,:,:] + veff/2 - dm1s = self.make_rdm1s (mo_coeff=mo_coeff, ncore=ncore, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1s_sub=casdm1s_sub) - e1 = np.dot (h1e.ravel (), dm1s.ravel ()) - - # 2-body cumulant terms - casdm1s = self.make_casdm1s (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1frs=casdm1frs) - casdm1 = casdm1s.sum (0) - casdm2 = self.make_casdm2 (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, casdm1frs=casdm1frs, casdm2fr=casdm2fr) - casdm2 -= np.multiply.outer (casdm1, casdm1) - casdm2 += np.multiply.outer (casdm1s[0], casdm1s[0]).transpose (0,3,2,1) - casdm2 += np.multiply.outer (casdm1s[1], casdm1s[1]).transpose (0,3,2,1) - ncore, ncas, nocc = self.ncore, self.ncas, self.ncore + self.ncas - eri = lib.numpy_helper.unpack_tril (h2eff[ncore:nocc].reshape (ncas*ncas, -1)) - eri = eri.reshape ([ncas,]*4) - e2 = np.tensordot (eri, casdm2, axes=4)/2 - - e0 = self.energy_nuc () - self._e1_test = e1 - self._e2_test = e2 - return e1 + e2 - - _ugg = lasci_sync.LASCI_UnitaryGroupGenerators - def get_ugg (self, mo_coeff=None, ci=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - return self._ugg (self, mo_coeff, ci) - - def cderi_ao2mo (self, mo_i, mo_j, compact=False): - assert (isinstance (self, _DFLASCI)) - nmo_i, nmo_j = mo_i.shape[-1], mo_j.shape[-1] - if compact: - assert (nmo_i == nmo_j) - bPij = np.empty ((self.with_df.get_naoaux (), nmo_i*(nmo_i+1)//2), dtype=mo_i.dtype) - else: - bPij = np.empty ((self.with_df.get_naoaux (), nmo_i, nmo_j), dtype=mo_i.dtype) - ijmosym, mij_pair, moij, ijslice = ao2mo.incore._conc_mos (mo_i, mo_j, compact=compact) - b0 = 0 - for eri1 in self.with_df.loop (): - b1 = b0 + eri1.shape[0] - eri2 = bPij[b0:b1] - eri2 = ao2mo._ao2mo.nr_e2 (eri1, moij, ijslice, aosym='s2', mosym=ijmosym, out=eri2) - b0 = b1 - return bPij - - def fast_veffa (self, casdm1s_sub, h2eff_sub, mo_coeff=None, ci=None, _full=False): - if mo_coeff is None: mo_coeff = self.mo_coeff - if ci is None: ci = self.ci - assert (isinstance (self, _DFLASCI) or _full) - ncore = self.ncore - ncas_sub = self.ncas_sub - ncas = sum (ncas_sub) - nocc = ncore + ncas - nao, nmo = mo_coeff.shape - - mo_cas = mo_coeff[:,ncore:nocc] - moH_cas = mo_cas.conjugate ().T - moH_coeff = mo_coeff.conjugate ().T - dma = linalg.block_diag (*[dm[0] for dm in casdm1s_sub]) - dmb = linalg.block_diag (*[dm[1] for dm in casdm1s_sub]) - casdm1s = np.stack ([dma, dmb], axis=0) - if not (isinstance (self, _DFLASCI)): - dm1s = np.dot (mo_cas, np.dot (casdm1s, moH_cas)).transpose (1,0,2) - return self.get_veff (dm1s = dm1s, spin_sep=True) - casdm1 = casdm1s.sum (0) - dm1 = np.dot (mo_cas, np.dot (casdm1, moH_cas)) - bPmn = sparsedf_array (self.with_df._cderi) - - # vj - dm_tril = dm1 + dm1.T - np.diag (np.diag (dm1.T)) - rho = np.dot (bPmn, lib.pack_tril (dm_tril)) - vj = lib.unpack_tril (np.dot (rho, bPmn)) - - # vk - bmPu = h2eff_sub.bmPu - if _full: - vmPsu = np.dot (bmPu, casdm1s) - vk = np.tensordot (vmPsu, bmPu, axes=((1,3),(1,2))).transpose (1,0,2) - return vj[None,:,:] - vk - else: - vmPu = np.dot (bmPu, casdm1) - vk = np.tensordot (vmPu, bmPu, axes=((1,2),(1,2))) - return vj - vk/2 - - @lib.with_doc(run_lasci.__doc__) - def lasci (self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, - assert_no_dupes=False, _dry_run=False): - if mo_coeff is None: mo_coeff=self.mo_coeff - if ci0 is None: ci0 = self.ci - if verbose is None: verbose = self.verbose - converged, e_tot, e_states, e_cas, e_lexc, ci = run_lasci ( - self, mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, - verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) - if _dry_run: return - self.converged, self.ci = converged, ci - self.e_tot, self.e_states, self.e_cas, self.e_lexc = e_tot, e_states, e_cas, e_lexc - if mo_coeff is self.mo_coeff: - self.dump_chk () - elif getattr (self, 'chkfile', None) is not None: - lib.logger.warn (self, 'orbitals changed; chkfile not dumped!') - self._finalize () - return self.converged, self.e_tot, self.e_states, self.e_cas, e_lexc, self.ci - - @lib.with_doc(run_lasci.__doc__) - def lasci_(self, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose=None, - assert_no_dupes=False, _dry_run=False): - if mo_coeff is not None: - self.mo_coeff = mo_coeff - return self.lasci (mo_coeff=mo_coeff, ci0=ci0, lroots=lroots, lweights=lweights, - verbose=verbose, assert_no_dupes=assert_no_dupes, _dry_run=_dry_run) - - state_average = state_average - state_average_ = state_average_ - get_single_state_las = get_single_state_las - - def lassi(self, mo_coeff=None, ci=None, veff_c=None, h2eff_sub=None, orbsym=None, - soc=False, break_symmetry=False, opt=1, **kwargs): - #import warnings - #lassi_kernel_warn = "Now LASSI have kernel, which takes las instance as input. This [las.lassi()] function " \ - # "will be removed soon." - #warnings.warn(lassi_kernel_warn, stacklevel=3) - from mrh.my_pyscf.lassi import lassi - mylassi = lassi.LASSI(self, mo_coeff=mo_coeff, ci=ci, soc=soc, opt=opt, - break_symmetry=break_symmetry, **kwargs) - return mylassi.kernel(mo_coeff=mo_coeff, ci=ci, veff_c=veff_c, h2eff_sub=h2eff_sub, - orbsym=orbsym) - - las2cas_civec = las2cas_civec - assert_no_duplicates = assert_no_duplicates - get_init_guess_ci = get_init_guess_ci - localize_init_guess=lasscf_guess.localize_init_guess - def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): - if s is None: s = self._scf.get_ovlp () - return matrix_svd_control_options (s, lspace=mo_lspace, rspace=mo_rspace, full_matrices=True)[:3] - - def dump_flags (self, verbose=None, _method_name='LASCI'): - log = lib.logger.new_logger (self, verbose) - log.info ('') - log.info ('******** %s flags ********', _method_name) - ncore = self.ncore - ncas = self.ncas - nvir = self.mo_coeff.shape[1] - ncore - ncas - nfrags = len (self.nelecas_sub) - log.info ('CAS (%de+%de, %do), ncore = %d, nvir = %d', - self.nelecas[0], self.nelecas[1], ncas, ncore, nvir) - log.info ('Divided into %d LAS spaces', nfrags) - for i, (no, ne) in enumerate (zip (self.ncas_sub, self.nelecas_sub)): - log.info ('LAS %d : (%de+%de, %do)', i, ne[0], ne[1], no) - log.info ('nroots = %d', self.nroots) - log.info ('max_memory %d (MB)', self.max_memory) - for i, fcibox in enumerate (self.fciboxes): - if getattr (fcibox, 'dump_flags', None): - log.info ('fragment %d FCI solver flags:', i) - fcibox.dump_flags (log.verbose) - - @property - def converged (self): - return all (self.states_converged) - @converged.setter - def converged (self, x): - if hasattr (x, '__len__'): - self.states_converged = list (x) - else: - self.states_converged = [x,]*self.nroots - - def dump_spaces (self, nroots=None, sort_energy=False): - log = lib.logger.new_logger (self, self.verbose) - log.info ("******** LAS space tables ********") - ci = self.ci - if nroots is None and self.verbose <= lib.logger.INFO: - nroots = min (self.nroots, 100) - elif nroots is None: - nroots = self.nroots - if nroots < self.nroots: - log.warn ("Dumping only 100 of %d spaces", self.nroots) - log.warn ("To see more, explicitly pass nroots to dump_spaces or increase verbosity") - if sort_energy: - idx = np.argsort (self.e_states) - else: - idx = range (nroots) - for state in idx: - neleca_f = [] - nelecb_f = [] - wfnsym_f = [] - wfnsym = 0 - m_f = [] - s_f = [] - lroots = [] - s2_tot = 0 - for ifrag, (fcibox, nelecas) in enumerate (zip (self.fciboxes, self.nelecas_sub)): - solver = fcibox.fcisolvers[state] - na, nb = _unpack_nelec (fcibox._get_nelec (solver, nelecas)) - neleca_f.append (na) - nelecb_f.append (nb) - m_f.append ((na-nb)/2) - s_f.append ((solver.smult-1)/2) - s2_tot += s_f[-1] * (s_f[-1] + 1) - fragsym = getattr (solver, 'wfnsym', 0) or 0 - if isinstance (fragsym, str): - fragsym_str = fragsym - fragsym_id = symm.irrep_name2id (solver.mol.groupname, fragsym) - else: - fragsym_id = fragsym - fragsym_str = symm.irrep_id2name (solver.mol.groupname, fragsym) - wfnsym ^= fragsym_id - wfnsym_f.append (fragsym_str) - lroots_i = 0 - if ci is not None: - if ci[ifrag] is not None: - ci_i = ci[ifrag] - if ci_i[state] is not None: - ci_ij = ci_i[state] - lroots_i = 1 if ci_ij.ndim<3 else ci_ij.shape[0] - lroots.append (lroots_i) - s2_tot += sum ([2*m1*m2 for m1, m2 in combinations (m_f, 2)]) - s_f, m_f = np.asarray (s_f), np.asarray (m_f) - if np.all (m_f<0): m_f *= -1 - s_pure = bool (np.all (s_f==m_f)) - wfnsym = symm.irrep_id2name (self.mol.groupname, wfnsym) - neleca = sum (neleca_f) - nelecb = sum (nelecb_f) - log.info ("LAS space %d: (%de+%de,%do) wfynsm=%s", state, neleca, nelecb, self.ncas, wfnsym) - log.info ("Converged? %s", self.states_converged[state]) - log.info ("E(LAS) = %.15g", self.e_states[state]) - log.info ("S^2 = %.7f (%s)", s2_tot, ('Impure','Pure')[s_pure]) - log.info ("Space table") - log.info (" frag (ae+be,no) 2S+1 ir lroots") - for i in range (self.nfrags): - smult_f = int (round (2*s_f[i] + 1)) - tupstr = '({}e+{}e,{}o)'.format (neleca_f[i], nelecb_f[i], self.ncas_sub[i]) - log.info (" %4d %13s %4d %3s %6d", i, tupstr, smult_f, wfnsym_f[i], lroots[i]) - - def check_sanity (self): - casci.CASCI.check_sanity (self) - self.get_ugg () # constructor encounters impossible states and raises error - - dump_chk = chkfile.dump_las - load_chk = load_chk_ = chkfile.load_las_ - -class LASCISymm (casci_symm.CASCI, LASCINoSymm): - - def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, wfnsym_sub=None, frozen=None, - **kwargs): - LASCINoSymm.__init__(self, mf, ncas, nelecas, ncore=ncore, spin_sub=spin_sub, - frozen=frozen, **kwargs) - if getattr (self.mol, 'groupname', None) in ('Dooh', 'Coov'): - raise NotImplementedError ("LASSCF support for cylindrical point group {}".format ( - self.mol.groupname)) - if wfnsym_sub is None: wfnsym_sub = [0 for icas in self.ncas_sub] - # TODO: guess wfnsym_sub intelligently (0 can be impossible for some multiplicities) - for wfnsym, frag in zip (wfnsym_sub, self.fciboxes): - if isinstance (wfnsym, (str, np.str_)): - wfnsym = symm.irrep_name2id (self.mol.groupname, wfnsym) - frag.fcisolvers[0].wfnsym = wfnsym - - make_rdm1s = LASCINoSymm.make_rdm1s - make_rdm1 = LASCINoSymm.make_rdm1 - get_veff = LASCINoSymm.get_veff - get_h1eff = get_h1las = h1e_for_las - dump_flags = LASCINoSymm.dump_flags - dump_spaces = LASCINoSymm.dump_spaces - check_sanity = LASCINoSymm.check_sanity - _ugg = lasci_sync.LASCISymm_UnitaryGroupGenerators - - @property - def wfnsym (self): - ''' This now returns the product of the irreps of the subspaces ''' - wfnsym = [0,]*self.nroots - for frag in self.fciboxes: - for state, solver in enumerate (frag.fcisolvers): - wfnsym[state] ^= solver.wfnsym - if self.nroots == 1: wfnsym = wfnsym[0] - return wfnsym - @wfnsym.setter - def wfnsym (self, ir): - raise RuntimeError (("Cannot assign the whole-system symmetry of a LASCI wave function. " - "Address fciboxes[ifrag].fcisolvers[istate].wfnsym instead.")) - - def kernel(self, mo_coeff=None, ci0=None, casdm0_fr=None, verbose=None, assert_no_dupes=False): - if mo_coeff is None: - mo_coeff = self.mo_coeff - if ci0 is None: - ci0 = self.ci - - # Initialize/overwrite mo_coeff.orbsym. Don't pass ci0 because it's not the right shape - lib.logger.info (self, ("LASCI lazy hack note: lines below reflect the point-group " - "symmetry of the whole molecule but not of the individual " - "subspaces")) - mo_coeff = self.mo_coeff = self.label_symmetry_(mo_coeff) - return LASCINoSymm.kernel(self, mo_coeff=mo_coeff, ci0=ci0, - casdm0_fr=casdm0_fr, verbose=verbose, assert_no_dupes=assert_no_dupes) - - def canonicalize (self, mo_coeff=None, ci=None, natorb_casdm1=None, veff=None, h2eff_sub=None): - if mo_coeff is None: mo_coeff = self.mo_coeff - mo_coeff = self.label_symmetry_(mo_coeff) - return canonicalize (self, mo_coeff=mo_coeff, ci=ci, natorb_casdm1=natorb_casdm1, - h2eff_sub=h2eff_sub, orbsym=mo_coeff.orbsym) - - def label_symmetry_(self, mo_coeff=None): - if mo_coeff is None: mo_coeff=self.mo_coeff - ncore = self.ncore - ncas_sub = self.ncas_sub - nocc = ncore + sum (ncas_sub) - mo_coeff[:,:ncore] = symm.symmetrize_space (self.mol, mo_coeff[:,:ncore]) - for isub, ncas in enumerate (ncas_sub): - i = ncore + sum (ncas_sub[:isub]) - j = i + ncas - mo_coeff[:,i:j] = symm.symmetrize_space (self.mol, mo_coeff[:,i:j]) - mo_coeff[:,nocc:] = symm.symmetrize_space (self.mol, mo_coeff[:,nocc:]) - orbsym = symm.label_orb_symm (self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_coeff, - s=self._scf.get_ovlp ()) - mo_coeff = lib.tag_array (mo_coeff, orbsym=orbsym) - return mo_coeff - - @lib.with_doc(LASCINoSymm.localize_init_guess.__doc__) - def localize_init_guess (self, frags_atoms, mo_coeff=None, spin=None, lo_coeff=None, fock=None, - freeze_cas_spaces=False): - if mo_coeff is None: - mo_coeff = self.mo_coeff - mo_coeff = casci_symm.label_symmetry_(self, mo_coeff) - return LASCINoSymm.localize_init_guess (self, frags_atoms, mo_coeff=mo_coeff, spin=spin, - lo_coeff=lo_coeff, fock=fock, freeze_cas_spaces=freeze_cas_spaces) - - def _svd (self, mo_lspace, mo_rspace, s=None, **kwargs): - if s is None: s = self._scf.get_ovlp () - lsymm = getattr (mo_lspace, 'orbsym', None) - if lsymm is None: - mo_lspace = symm.symmetrize_space (self.mol, mo_lspace) - lsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_lspace, s=s) - rsymm = getattr (mo_rspace, 'orbsym', None) - if rsymm is None: - mo_rspace = symm.symmetrize_space (self.mol, mo_rspace) - rsymm = symm.label_orb_symm(self.mol, self.mol.irrep_id, - self.mol.symm_orb, mo_rspace, s=s) - decomp = matrix_svd_control_options (s, - lspace=mo_lspace, rspace=mo_rspace, - lspace_symmetry=lsymm, rspace_symmetry=rsymm, - full_matrices=True, strong_symm=True) - mo_lvecs, svals, mo_rvecs, lsymm, rsymm = decomp - mo_lvecs = lib.tag_array (mo_lvecs, orbsym=lsymm) - mo_rvecs = lib.tag_array (mo_rvecs, orbsym=rsymm) - return mo_lvecs, svals, mo_rvecs - From 893eb403177534cd6e9b889870587d449db0f8b9 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Mon, 27 May 2024 13:59:50 -0500 Subject: [PATCH 43/87] removed print lines, states_make_casdm2s, make_casdm3_sub --- my_pyscf/mcscf/lasci.py | 100 +++++++--------------------------------- 1 file changed, 16 insertions(+), 84 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 80a12240..efd759a8 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -272,7 +272,6 @@ def h1e_for_las (las, mo_coeff=None, ncas=None, ncore=None, nelecas=None, ci=Non h1e_r = np.empty ((las.nroots, 2, ncas, ncas), dtype=h1e.dtype) h2e = lib.numpy_helper.unpack_tril (h2eff_sub.reshape (nmo*ncas, ncas*(ncas+1)//2)).reshape (nmo, ncas, ncas, ncas)[ncore:nocc,:,:,:] - #print ("SV h2e = ",h2e, h2e.shape) avgdm1s = np.stack ([linalg.block_diag (*[dm[spin] for dm in casdm1s_sub]) for spin in range (2)], axis=0) for state in range (las.nroots): @@ -1046,18 +1045,15 @@ def make_casdm2_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2fr=No for dm2, box in zip (casdm2fr, self.fciboxes)] - #SV casdm2s_sub def make_casdm2s_sub(self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2frs=None, w=None, **kwargs): if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) if w is None: w = self.weights - print("SV w = ", w) return [np.einsum ('rsijkl,r->sijkl', dm2, w) for dm2 in casdm2frs] - #SV states_make_casdm2s_sub def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - # Spin-separated 2-RDMs in the MO basis for each subspace in sequence + '''Spin-separated 2-RDMs in the MO basis for each subspace in sequence''' if ci is None: ci = self.ci if ncas_sub is None: ncas_sub = self.ncas_sub if nelecas_sub is None: nelecas_sub = self.nelecas_sub @@ -1072,61 +1068,14 @@ def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **k casdm2s.append (np.stack ([dm2aa, dm2ab, dm2bb], axis=1)) return casdm2s - #SV states_make_casdm2s - def states_make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm1frs=None, casdm2fr=None, casdm2frs=None, **kwargs): - ''' Make the full-dimensional casdm2s spanning the collective active space ''' - if ci is None: ci = self.ci - if ncas_sub is None: ncas_sub = self.ncas_sub - if nelecas_sub is None: nelecas_sub = self.nelecas_sub - if casdm1frs is None: casdm1frs = self.states_make_casdm1s_sub (ci=ci) - if casdm2fr is None: casdm2fr = self.states_make_casdm2_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - if casdm2frs is None: casdm2frs = self.states_make_casdm2s_sub (ci=ci, - ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, **kwargs) - - ncas = sum (ncas_sub) - ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - - casdm2rs = np.zeros ((self.nroots,3,ncas,ncas,ncas,ncas)) - for isub, dm2 in enumerate (casdm2frs): - # print ("SV dm2 = ", dm2, dm2.shape, isub, dm2[:,1]) # dm2 = root,spin,ncas,ncas,ncas,ncas | dm2[:,x] = x spin part of ncas,ncas,ncas,ncas - i = ncas_cum[isub] - j = ncas_cum[isub+1] - for spin in [0,1,2]:#0=aa, 1=ab, 2=bb - casdm2rs[:, spin, i:j, i:j, i:j, i:j] = dm2[:,spin,:,:,:,:] - - for (isub1, dm1s1_r), (isub2, dm1s2_r) in combinations (enumerate (casdm1frs), 2): - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub2] - l = ncas_cum[isub2+1] - for spin in [0,1,2]: - for dm1s1, dm1s2, casdm2 in zip (dm1s1_r, dm1s2_r, casdm2rs[:,spin]): - dma1, dmb1 = dm1s1[0], dm1s1[1] - dma2, dmb2 = dm1s2[0], dm1s2[1] - # Coulomb slice - casdm2[i:j, i:j, k:l, k:l] = np.multiply.outer (dma1+dmb1, dma2+dmb2) - casdm2[k:l, k:l, i:j, i:j] = casdm2[i:j, i:j, k:l, k:l].transpose (2,3,0,1) - # Exchange slice - casdm2[i:j, k:l, k:l, i:j] = -(np.multiply.outer (dma1, dma2) - +np.multiply.outer (dmb1, dmb2)).transpose (0,3,2,1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) - - return casdm2rs - - #SV make_casdm3_sub - def make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3fr=None, **kwargs): - if casdm3fr is None: casdm3fr = self.states_make_casdm3_sub (ci=ci, ncas_sub=ncas_sub, - nelecas_sub=nelecas_sub, **kwargs) - for dm3, box in zip(casdm3fr, self.fciboxes): - #print ("SV dm3, box = ", dm3, box.weights) - casdm3_sub = np.einsum('rijklmn,r->ijklmn', dm3, box.weights) - #casdm3_sub = np.einsum('rijklmn,r->ijklpq', dm3, box.weights) - return casdm3_sub + #def make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3fr=None, **kwargs): + # if casdm3fr is None: casdm3fr = self.states_make_casdm3_sub (ci=ci, ncas_sub=ncas_sub, + # nelecas_sub=nelecas_sub, **kwargs) + # for dm3, box in zip(casdm3fr, self.fciboxes): + # casdm3_sub = np.einsum('rijklmn,r->ijklmn', dm3, box.weights) + # return casdm3_sub - #SV states_make_casdm3_sub def states_make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): ''' Spin-separated 3-RDMs in the MO basis for each subspace in sequence, currently this does not have weights so it's not really a STATES ''' if ci is None: ci = self.ci @@ -1137,7 +1086,6 @@ def states_make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kw dm1_not_no, dm2_not_no, dm3_not_no = fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel) # not normal ordered dm3_no = fci.rdm.reorder_dm123(dm1_not_no, dm2_not_no, dm3_not_no)[-1] casdm3.append (dm3_no) - #print ("SV casdm3_sub = ", casdm3_sub return casdm3 def states_make_rdm1s (self, mo_coeff=None, ci=None, ncas_sub=None, @@ -1288,7 +1236,6 @@ def state_make_casdm2(self, ci=None, state=0, ncas_sub=None, nelecas_sub=None, casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) return casdm2 - #SV make_casdm2s def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2rs=None, casdm2fs=None, casdm1frs=None, casdm2frs=None, **kwargs): @@ -1309,7 +1256,6 @@ def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, # Diagonal of aa,ab,bb for isub, dm2 in enumerate (casdm2fs): - print ("SV dm2 = ", dm2[1], dm2.shape) # dm2 = 3,ncas_sub*4 i = ncas_cum[isub] j = ncas_cum[isub+1] for spin in [0,1,2]: @@ -1332,11 +1278,9 @@ def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, # Exchange slice d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) - print ("SV d2exc = ", d2exc, d2exc.shape) casdm2s[spin][i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) casdm2s[spin][k:l, i:j, i:j, k:l] = casdm2s[spin][i:j, k:l, k:l, i:j].transpose (1,0,3,2) - print ("SV casdm2s = ", casdm2s) return casdm2s @@ -1382,7 +1326,6 @@ def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) # IDU my eqn says [2,3,0,1] --> it's same! return casdm2 - #SV make_casdm3 def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3r=None, casdm3f=None, casdm2frs=None, casdm3fr=None, casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, **kwargs): @@ -1398,25 +1341,21 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) if casdm2f is None: casdm2f = self.make_casdm2_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm2fr=casdm2fr) - #if casdm3f is None: casdm3f = self.make_casdm3_sub (ci=ci, - # ncas_sub=ncas_sub, nelecas_sub=nelecas_sub, casdm3fr=casdm3fr) if casdm3f is None: casdm3f = self.states_make_casdm3_sub (ci=ci, ncas_sub=ncas_sub, nelecas_sub=nelecas_sub) ncas = sum (ncas_sub) ncas_cum = np.cumsum ([0] + ncas_sub.tolist ()) - print ("SV ncas_cum = ", ncas_cum) weights = self.weights casdm3 = np.zeros ((ncas,ncas,ncas,ncas,ncas,ncas)) # Diagonal for isub, dm3 in enumerate (casdm3f): - print ("SV dm3 = ", dm3, isub, dm3.shape) i = ncas_cum[isub] j = ncas_cum[isub+1] casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 # Off-diagonal - #Last 2 terms - combs of f1+f2+f3: dm1rs1=f1, dm1rs2=f2, dm1rs3=f3 + # First 6 terms - combs of f1+f2+f3: dm1rs1=f1, dm1rs2=f2, dm1rs3=f3 from itertools import permutations for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): i = ncas_cum[isub1] @@ -1432,7 +1371,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, dm2r = dma2r + dmb2r dm3r = dma3r + dmb3r - # Term 1- 30a + # Term 1- 29a d1sigma = lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm2r,dm3r) casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot(weights, d1sigma, axes=1)#lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) @@ -1441,7 +1380,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) - # Term 2- 30b + # Term 2- 29b d2sigma = (lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb2r,dmb3r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot(weights, d2sigma, axes=1) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) @@ -1450,7 +1389,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) - # Term 3- 30c + # Term 3- 29c d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dmb1r,dmb2r,dmb3r)) casdm3[i:j, m:n, k:l, i:j, m:n, k:l] += np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (0,1,4,5,2,3) @@ -1459,7 +1398,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,4,5,0,1) casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,0,1,4,5) - # Term 4- 30d + # Term 4- 29d d4sigma = (lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dma1r,dma2r,dm3r)+lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dmb1r,dmb2r,dm3r)) casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= np.tensordot(weights, d4sigma, axes=1) casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) @@ -1468,7 +1407,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, i:j, m:n, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,4,5,0,1) casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) - # Term 5- 30e + # Term 5- 29e d5sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot(weights, d5sigma, axes=1) casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) @@ -1477,7 +1416,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, m:n, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,4,5,0,1) casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) - # Term 6- 30f + # Term 6- 29f d6sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dmb3r)) casdm3[i:j, k:l, k:l, m:n, m:n, i:j] += np.tensordot(weights, d6sigma, axes=1) casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (0,1,4,5,2,3) @@ -1499,24 +1438,17 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, m = ncas_cum[isub2] n = ncas_cum[isub2+1] - print ("SV ijklmn = ", i,j,k,l,m,n) dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] dm1r = dma1r + dmb1r dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] - print ("SV dm1r = ", dm1rs1, dm1r) - print ("SV dm2rs1 = ", dm2rs2, dmaa2r) - # Term 4 - 30g + # Term 7 - 29g d4sigma = (lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) - print ("SV d4sigma = ", d4sigma) casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot (weights, d4sigma, axes=1) casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) - #casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,2,3,0,1) - #casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,4,5,0,1) - #casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(2,3,0,1,4,5) - # Term 5 - 30h + # Term 8 - 29h d5sigma = (lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmbb2r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot (weights, d5sigma, axes=1) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) From ef36f3f57f2e83bed146b15dd3eee3da79ed7922 Mon Sep 17 00:00:00 2001 From: Shreya Verma Date: Mon, 27 May 2024 14:14:14 -0500 Subject: [PATCH 44/87] removed las_ao2mo.py --- my_pyscf/mcscf/las_ao2mo.py | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 my_pyscf/mcscf/las_ao2mo.py diff --git a/my_pyscf/mcscf/las_ao2mo.py b/my_pyscf/mcscf/las_ao2mo.py deleted file mode 100644 index 15629728..00000000 --- a/my_pyscf/mcscf/las_ao2mo.py +++ /dev/null @@ -1,21 +0,0 @@ -import ctypes - -from functools import reduce -import numpy -import h5py -from pyscf import lib -from pyscf.lib import logger -from pyscf import ao2mo -from pyscf.ao2mo import _ao2mo -from pyscf.ao2mo import outcore - -libmcscf = lib.load_library('libmcscf') - -def _mem_usage(las, mo_coeff, ncore, ncas, nmo): - ncore = las.ncore - ncas = las.ncas - nmo = mo_coeff.shape[-1] - nocc = ncore + ncas - outcore = basic = ncas**2*nmo**2*2 * 8/1e6 - incore = outcore + (ncore+ncas)*nmo**3*4/1e6 - return incore, outcore, basic From a5582c153454baebe48989769d1468f28a07b01f Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 28 May 2024 11:27:42 -0500 Subject: [PATCH 45/87] PySCF compat check --- pyscf_version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscf_version.txt b/pyscf_version.txt index d13332d8..a50f2dbc 100644 --- a/pyscf_version.txt +++ b/pyscf_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf.git@7d94887d2ca817287226c51371845037f4469325 +git+https://github.com/pyscf/pyscf.git@a64f740c978eb6cc10eec53f0c50098fc4f543b2 From 78cece47e3348046761d94796b1748b874c201d8 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Wed, 29 May 2024 11:04:13 -0500 Subject: [PATCH 46/87] line wrap and directory cleanup --- my_pyscf/mcscf/lasci.py | 103 ++++++++++-------- .../test_lasscf_3rdm.py | 0 2 files changed, 60 insertions(+), 43 deletions(-) rename tests/{lasscf_3rdm => lasscf}/test_lasscf_3rdm.py (100%) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 0235c70f..c1050d1f 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1170,13 +1170,15 @@ def states_make_casdm2s_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **k def states_make_casdm3_sub (self, ci=None, ncas_sub=None, nelecas_sub=None, **kwargs): - ''' Spin-separated 3-RDMs in the MO basis for each subspace in sequence, currently this does not have weights so it's not really a STATES ''' + ''' Spin-separated 3-RDMs in the MO basis for each subspace in sequence, currently this + does not have weights so it's not really a STATES ''' if ci is None: ci = self.ci if ncas_sub is None: ncas_sub = self.ncas_sub if nelecas_sub is None: nelecas_sub = self.nelecas_sub casdm3 = [] for ci_i, ncas, nel in zip (ci, ncas_sub, nelecas_sub): - dm1_not_no, dm2_not_no, dm3_not_no = fci.rdm.make_dm123 ('FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel) # not normal ordered + dm1_not_no, dm2_not_no, dm3_not_no = fci.rdm.make_dm123 ( + 'FCI3pdm_kern_sf',ci_i,ci_i, ncas, nel) # not normal ordered dm3_no = fci.rdm.reorder_dm123(dm1_not_no, dm2_not_no, dm3_not_no)[-1] casdm3.append (dm3_no) return casdm3 @@ -1532,13 +1534,13 @@ def make_casdm2s (self, ci=None, ncas_sub=None, nelecas_sub=None, dm2r = dma2r + dmb2r for spin in [0,1,2]: # Coulomb slice - casdm2s[spin][i:j, i:j, k:l, k:l] = lib.einsum ('r,rij,rkl->ijkl', weights, dm1r, dm2r) - casdm2s[spin][k:l, k:l, i:j, i:j] = casdm2s[spin][i:j, i:j, k:l, k:l].transpose (2,3,0,1) + casdm2s[spin][i:j,i:j,k:l,k:l] = lib.einsum ('r,rij,rkl->ijkl', weights,dm1r,dm2r) + casdm2s[spin][k:l,k:l,i:j,i:j] = casdm2s[spin][i:j,i:j,k:l,k:l].transpose (2,3,0,1) # Exchange slice d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) - casdm2s[spin][i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2s[spin][k:l, i:j, i:j, k:l] = casdm2s[spin][i:j, k:l, k:l, i:j].transpose (1,0,3,2) + casdm2s[spin][i:j,k:l,k:l,i:j] -= np.tensordot (weights, d2exc, axes=1) + casdm2s[spin][k:l,i:j,i:j,k:l] = casdm2s[spin][i:j,k:l,k:l,i:j].transpose (1,0,3,2) return casdm2s @@ -1611,12 +1613,13 @@ def make_casdm2 (self, ci=None, ncas_sub=None, nelecas_sub=None, d2exc = (lib.einsum ('rij,rkl->rilkj', dma1r, dma2r) + lib.einsum ('rij,rkl->rilkj', dmb1r, dmb2r)) casdm2[i:j, k:l, k:l, i:j] -= np.tensordot (weights, d2exc, axes=1) - casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) # IDU my eqn says [2,3,0,1] --> it's same! + casdm2[k:l, i:j, i:j, k:l] = casdm2[i:j, k:l, k:l, i:j].transpose (1,0,3,2) + # IDU my eqn says [2,3,0,1] --> it's same! return casdm2 def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, - casdm3r=None, casdm3f=None, casdm2frs=None, casdm3fr=None, casdm2r=None, casdm2f=None, casdm1frs=None, casdm2fr=None, - **kwargs): + casdm3r=None, casdm3f=None, casdm2frs=None, casdm3fr=None, casdm2r=None, casdm2f=None, + casdm1frs=None, casdm2fr=None, **kwargs): ''' Make the full-dimensional casdm3 spanning the collective active space ''' if casdm3r is not None: return np.einsum ('rijklmn,r->ijklmn', casdm3r, self.weights) @@ -1645,7 +1648,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, # Off-diagonal # First 6 terms - combs of f1+f2+f3: dm1rs1=f1, dm1rs2=f2, dm1rs3=f3 from itertools import permutations - for (isub1, dm1rs1), (isub2, dm1rs2), (isub3,dm1rs3) in combinations (enumerate (casdm1frs), 3): + for (isub1,dm1rs1),(isub2,dm1rs2),(isub3,dm1rs3) in combinations (enumerate(casdm1frs), 3): i = ncas_cum[isub1] j = ncas_cum[isub1+1] k = ncas_cum[isub2] @@ -1661,7 +1664,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, # Term 1- 29a d1sigma = lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm2r,dm3r) - casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot(weights, d1sigma, axes=1)#lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot(weights, d1sigma, axes=1) + #lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,0,1,2,3) casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,2,3,0,1) @@ -1669,7 +1673,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) # Term 2- 29b - d2sigma = (lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb2r,dmb3r)) + d2sigma = (lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma2r,dma3r) + +lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb2r,dmb3r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot(weights, d2sigma, axes=1) casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,0,1,2,3) @@ -1678,7 +1683,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) # Term 3- 29c - d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dmb1r,dmb2r,dmb3r)) + d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dma3r) + +lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dmb1r,dmb2r,dmb3r)) casdm3[i:j, m:n, k:l, i:j, m:n, k:l] += np.tensordot (weights, d3sigma, axes=1) casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (0,1,4,5,2,3) casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (4,5,0,1,2,3) @@ -1687,7 +1693,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,0,1,4,5) # Term 4- 29d - d4sigma = (lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dma1r,dma2r,dm3r)+lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dmb1r,dmb2r,dm3r)) + d4sigma = (lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dma1r,dma2r,dm3r) + +lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dmb1r,dmb2r,dm3r)) casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= np.tensordot(weights, d4sigma, axes=1) casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) casdm3[m:n, m:n, i:j, k:l, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,0,1,2,3) @@ -1696,7 +1703,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) # Term 5- 29e - d5sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) + d5sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r) + +lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot(weights, d5sigma, axes=1) casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) casdm3[m:n, i:j, i:j, m:n, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,0,1,2,3) @@ -1705,7 +1713,8 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) # Term 6- 29f - d6sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dma3r)+lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dmb3r)) + d6sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dma3r) + +lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dmb3r)) casdm3[i:j, k:l, k:l, m:n, m:n, i:j] += np.tensordot(weights, d6sigma, axes=1) casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (0,1,4,5,2,3) casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (4,5,0,1,2,3) @@ -1715,35 +1724,43 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, #Last 2 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 - for (isub1, dm2rs2), (isub2, dm1rs1) in product(enumerate(casdm2frs), enumerate(casdm1frs)): - # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same fragment but they must come from different frags! + for (isub1,dm2rs2), (isub2,dm1rs1) in product(enumerate(casdm2frs), enumerate(casdm1frs)): + # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same + # fragment but they must come from different frags! + + if isub1 == isub2: continue - if isub1 != isub2: - i = ncas_cum[isub1] - j = ncas_cum[isub1+1] - k = ncas_cum[isub1] - l = ncas_cum[isub1+1] - m = ncas_cum[isub2] - n = ncas_cum[isub2+1] + i = ncas_cum[isub1] + j = ncas_cum[isub1+1] + k = ncas_cum[isub1] + l = ncas_cum[isub1+1] + m = ncas_cum[isub2] + n = ncas_cum[isub2+1] - dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] - dm1r = dma1r + dmb1r - dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] - - # Term 7 - 29g - d4sigma = (lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) - casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot (weights, d4sigma, axes=1) - casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) - casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) - - # Term 8 - 29h - d5sigma = (lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmaa2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmab2r)+lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmbb2r)) - casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot (weights, d5sigma, axes=1) - casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) + dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] + dm1r = dma1r + dmb1r + dmaa2r, dmab2r, dmbb2r = dm2rs2[:,0], dm2rs2[:,1], dm2rs2[:,2] + + # Term 7 - 29g + d4sigma = (lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmaa2r) + +lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r) + +lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r) + +lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) + casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot (weights, d4sigma, axes=1) + casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) + casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) + + # Term 8 - 29h + d5sigma = (lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmaa2r) + +lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmab2r) + +lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmab2r) + +lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmbb2r)) + casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot (weights, d5sigma, axes=1) + casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) + casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) + casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,2,3,0,1) + casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,4,5,0,1) + casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) return casdm3 diff --git a/tests/lasscf_3rdm/test_lasscf_3rdm.py b/tests/lasscf/test_lasscf_3rdm.py similarity index 100% rename from tests/lasscf_3rdm/test_lasscf_3rdm.py rename to tests/lasscf/test_lasscf_3rdm.py From dbe04515e4ee287e190fa90b18ad666221fb658e Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Wed, 29 May 2024 11:27:15 -0500 Subject: [PATCH 47/87] cleanup/readability --- my_pyscf/mcscf/lasci.py | 67 ++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index c1050d1f..4447036d 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -12,7 +12,7 @@ from mrh.my_pyscf.mcscf import chkfile from mrh.my_pyscf.mcscf.productstate import ImpureProductStateFCISolver, state_average_fcisolver from mrh.util.la import matrix_svd_control_options -from itertools import combinations, product +from itertools import combinations, permutations, product from scipy.sparse import linalg as sparse_linalg from scipy import linalg import numpy as np @@ -1644,7 +1644,15 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, i = ncas_cum[isub] j = ncas_cum[isub+1] casdm3[i:j, i:j, i:j, i:j, i:j, i:j] = dm3 - + + def _transpose_dm3_(i0, i1, i2, i3, j0, j1, j2, j3, k0, k1, k2, k3): + d = casdm3[i0:i1, i2:i3, j0:j1, j2:j3, k0:k1, k2:k3] + casdm3[i0:i1, i2:i3, k0:k1, k2:k3, j0:j1, j2:j3] = d.transpose (0,1,4,5,2,3) + casdm3[k0:k1, k2:k3, i0:i1, i2:i3, j0:j1, j2:j3] = d.transpose (4,5,0,1,2,3) + casdm3[k0:k1, k2:k3, j0:j1, j2:j3, i0:i1, i2:i3] = d.transpose (4,5,2,3,0,1) + casdm3[j0:j1, j2:j3, k0:k1, k2:k3, i0:i1, i2:i3] = d.transpose (2,3,4,5,0,1) + casdm3[j0:j1, j2:j3, i0:i1, i2:i3, k0:k1, k2:k3] = d.transpose (2,3,0,1,4,5) + # Off-diagonal # First 6 terms - combs of f1+f2+f3: dm1rs1=f1, dm1rs2=f2, dm1rs3=f3 from itertools import permutations @@ -1666,69 +1674,43 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, d1sigma = lib.einsum('r,rij,rkl,rmn->rijklmn',weights,dm1r,dm2r,dm3r) casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot(weights, d1sigma, axes=1) #lib.einsum ('r,rij,rkl,rmn->ijklmn', weights, dm1r, dm2r, dm3r) - casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (0,1,4,5,2,3) - casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,0,1,2,3) - casdm3[m:n, m:n, k:l, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (4,5,2,3,0,1) - casdm3[k:l, k:l, m:n, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,4,5,0,1) - casdm3[k:l, k:l, i:j, i:j, m:n, m:n] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose (2,3,0,1,4,5) + _transpose_dm3_(i,j, i,j, k,l, k,l, m,n, m,n) # Term 2- 29b d2sigma = (lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dma2r,dma3r) +lib.einsum('r,rij,rkl,rmn->rijknml',weights,dm1r,dmb2r,dmb3r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot(weights, d2sigma, axes=1) - casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (4,5,2,3,0,1) - casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,4,5,0,1) - casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose (2,3,0,1,4,5) + _transpose_dm3_(i,j, i,j, k,l, m,n, m,n, k,l) # Term 3- 29c d3sigma = (lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dma1r,dma2r,dma3r) +lib.einsum('r,rij,rkl,rmn->rinkjml',weights,dmb1r,dmb2r,dmb3r)) casdm3[i:j, m:n, k:l, i:j, m:n, k:l] += np.tensordot (weights, d3sigma, axes=1) - casdm3[i:j, m:n, m:n, k:l, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, m:n, k:l, i:j] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (4,5,2,3,0,1) - casdm3[k:l, i:j, m:n, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,4,5,0,1) - casdm3[k:l, i:j, i:j, m:n, m:n, k:l] = casdm3[i:j, m:n, k:l, i:j, m:n, k:l].transpose (2,3,0,1,4,5) + _transpose_dm3_(i,j, m,n, k,l, i,j, m,n, k,l) # Term 4- 29d d4sigma = (lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dma1r,dma2r,dm3r) +lib.einsum('r,rij,rkl,rmn->rilkjmn',weights,dmb1r,dmb2r,dm3r)) casdm3[i:j, k:l, k:l, i:j, m:n, m:n] -= np.tensordot(weights, d4sigma, axes=1) - casdm3[i:j, k:l, m:n, m:n, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (0,1,4,5,2,3) - casdm3[m:n, m:n, i:j, k:l, k:l, i:j] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,0,1,2,3) - casdm3[m:n, m:n, k:l, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (4,5,2,3,0,1) - casdm3[k:l, i:j, m:n, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,4,5,0,1) - casdm3[k:l, i:j, i:j, k:l, m:n, m:n] = casdm3[i:j, k:l, k:l, i:j, m:n, m:n].transpose (2,3,0,1,4,5) + _transpose_dm3_(i,j, k,l, k,l, i,j, m,n, m,n) # Term 5- 29e d5sigma = (lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dma1r,dm2r,dma3r) +lib.einsum('r,rij,rkl,rmn->rinklmj',weights,dmb1r,dm2r,dmb3r)) casdm3[i:j, m:n, k:l, k:l, m:n, i:j] -= np.tensordot(weights, d5sigma, axes=1) - casdm3[i:j, m:n, m:n, i:j, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (0,1,4,5,2,3) - casdm3[m:n, i:j, i:j, m:n, k:l, k:l] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,0,1,2,3) - casdm3[m:n, i:j, k:l, k:l, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (4,5,2,3,0,1) - casdm3[k:l, k:l, m:n, i:j, i:j, m:n] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,4,5,0,1) - casdm3[k:l, k:l, i:j, m:n, m:n, i:j] = casdm3[i:j, m:n, k:l, k:l, m:n, i:j].transpose (2,3,0,1,4,5) + _transpose_dm3_(i,j, m,n, k,l, k,l, m,n, i,j) # Term 6- 29f d6sigma = (lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dma1r,dma2r,dma3r) +lib.einsum('r,rij,rkl,rmn->rilknmj',weights,dmb1r,dmb2r,dmb3r)) casdm3[i:j, k:l, k:l, m:n, m:n, i:j] += np.tensordot(weights, d6sigma, axes=1) - casdm3[i:j, k:l, m:n, i:j, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (0,1,4,5,2,3) - casdm3[m:n, i:j, i:j, k:l, k:l, m:n] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (4,5,0,1,2,3) - casdm3[m:n, i:j, k:l, m:n, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (4,5,2,3,0,1) - casdm3[k:l, m:n, m:n, i:j, i:j, k:l] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (2,3,4,5,0,1) - casdm3[k:l, m:n, i:j, k:l, m:n, i:j] = casdm3[i:j, k:l, k:l, m:n, m:n, i:j].transpose (2,3,0,1,4,5) + _transpose_dm3_(i,j, k,l, k,l, m,n, m,n, i,j) #Last 2 terms - combs of f1+f1+f2: dm1rs1=f1, dm2rs2=f2 - for (isub1,dm2rs2), (isub2,dm1rs1) in product(enumerate(casdm2frs), enumerate(casdm1frs)): - # isub1 must not be the same as isub2, else 1-rdm and 2-rdm will come from same - # fragment but they must come from different frags! - - if isub1 == isub2: continue + for isub1, isub2 in permutations (range (self.nfrags), 2): + dm2rs2 = casdm2frs[isub1] + dm1rs1 = casdm1frs[isub2] i = ncas_cum[isub1] j = ncas_cum[isub1+1] @@ -1747,8 +1729,9 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, +lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r) +lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot (weights, d4sigma, axes=1) - casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(0,1,4,5,2,3) - casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = casdm3[i:j, i:j, k:l, k:l, m:n, m:n].transpose(4,5,0,1,2,3) + d = casdm3[i:j, i:j, k:l, k:l, m:n, m:n] + casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = d.transpose(0,1,4,5,2,3) + casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = d.transpose(4,5,0,1,2,3) # Term 8 - 29h d5sigma = (lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmaa2r) @@ -1756,11 +1739,7 @@ def make_casdm3 (self, ci=None, ncas_sub=None, nelecas_sub=None, +lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmab2r) +lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmbb2r)) casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot (weights, d5sigma, axes=1) - casdm3[i:j, i:j, m:n, k:l, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(0,1,4,5,2,3) - casdm3[m:n, k:l, i:j, i:j, k:l, m:n] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,0,1,2,3) - casdm3[m:n, k:l, k:l, m:n, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(4,5,2,3,0,1) - casdm3[k:l, m:n, m:n, k:l, i:j, i:j] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,4,5,0,1) - casdm3[k:l, m:n, i:j, i:j, m:n, k:l] = casdm3[i:j, i:j, k:l, m:n, m:n, k:l].transpose(2,3,0,1,4,5) + _transpose_dm3_(i,j, i,j, k,l, m,n, m,n, k,l) return casdm3 From 3a8c4ce524c6f42f2c3bf00cdf2a834a153976ba Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Wed, 29 May 2024 11:29:37 -0500 Subject: [PATCH 48/87] cleanup/readability --- my_pyscf/mcscf/lasci.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 4447036d..870203a5 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -1714,10 +1714,8 @@ def _transpose_dm3_(i0, i1, i2, i3, j0, j1, j2, j3, k0, k1, k2, k3): i = ncas_cum[isub1] j = ncas_cum[isub1+1] - k = ncas_cum[isub1] - l = ncas_cum[isub1+1] - m = ncas_cum[isub2] - n = ncas_cum[isub2+1] + k = ncas_cum[isub2] + l = ncas_cum[isub2+1] dma1r, dmb1r = dm1rs1[:,0], dm1rs1[:,1] dm1r = dma1r + dmb1r @@ -1728,18 +1726,18 @@ def _transpose_dm3_(i0, i1, i2, i3, j0, j1, j2, j3, k0, k1, k2, k3): +lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r) +lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmab2r) +lib.einsum('r,rmn,rijkl->rijklmn',weights,dm1r,dmbb2r)) - casdm3[i:j, i:j, k:l, k:l, m:n, m:n] += np.tensordot (weights, d4sigma, axes=1) - d = casdm3[i:j, i:j, k:l, k:l, m:n, m:n] - casdm3[i:j, i:j, m:n, m:n, k:l, k:l] = d.transpose(0,1,4,5,2,3) - casdm3[m:n, m:n, i:j, i:j, k:l, k:l] = d.transpose(4,5,0,1,2,3) + casdm3[i:j, i:j, i:j, i:j, k:l, k:l] += np.tensordot (weights, d4sigma, axes=1) + d = casdm3[i:j, i:j, i:j, i:j, k:l, k:l] + casdm3[i:j, i:j, k:l, k:l, i:j, i:j] = d.transpose(0,1,4,5,2,3) + casdm3[k:l, k:l, i:j, i:j, i:j, i:j] = d.transpose(4,5,0,1,2,3) # Term 8 - 29h d5sigma = (lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmaa2r) +lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmab2r) +lib.einsum('r,rmn,rijkl->rijknml',weights,dma1r,dmab2r) +lib.einsum('r,rmn,rijkl->rijknml',weights,dmb1r,dmbb2r)) - casdm3[i:j, i:j, k:l, m:n, m:n, k:l] -= np.tensordot (weights, d5sigma, axes=1) - _transpose_dm3_(i,j, i,j, k,l, m,n, m,n, k,l) + casdm3[i:j, i:j, i:j, k:l, k:l, i:j] -= np.tensordot (weights, d5sigma, axes=1) + _transpose_dm3_(i,j, i,j, i,j, k,l, k,l, i,j) return casdm3 From 4734f521b44c8fdb21f6820c799dfb9fe3f5b46d Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Wed, 29 May 2024 12:05:16 -0500 Subject: [PATCH 49/87] move get_nelec_frs to LASCI layer Also add docstring to civec_spinless_repr --- my_pyscf/lassi/lassi.py | 9 ++------- my_pyscf/lassi/op_o0.py | 19 +++++++++++++++++++ my_pyscf/mcscf/lasci.py | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/my_pyscf/lassi/lassi.py b/my_pyscf/lassi/lassi.py index 4f553e52..a70faeb5 100644 --- a/my_pyscf/lassi/lassi.py +++ b/my_pyscf/lassi/lassi.py @@ -756,13 +756,8 @@ def ham_2q (self, mo_coeff=None, veff_c=None, h2eff_sub=None, soc=0): def get_nelec_frs (self, las=None): if las is None: las = self - nelec_frs = [] - for fcibox, nelec in zip (las.fciboxes, las.nelecas_sub): - nelec_rs = [] - for solver in fcibox.fcisolvers: - nelec_rs.append (_unpack_nelec (fcibox._get_nelec (solver, nelec))) - nelec_frs.append (nelec_rs) - return np.asarray (nelec_frs) + from mrh.my_pyscf.mcscf.lasci import get_nelec_frs + return get_nelec_frs (las) def get_lroots (self, ci=None): if ci is None: ci = self.ci diff --git a/my_pyscf/lassi/op_o0.py b/my_pyscf/lassi/op_o0.py index 92928a68..3ed074e0 100644 --- a/my_pyscf/lassi/op_o0.py +++ b/my_pyscf/lassi/op_o0.py @@ -144,6 +144,25 @@ def ci1_r_gen (buf=None): return ci1_r_gen, ss2spinless, spinless2ss def civec_spinless_repr (ci0_r, norb, nelec_r): + '''Put CI vectors in the spinless representation; i.e., map + norb -> 2 * norb + (neleca, nelecb) -> (neleca+nelecb, 0) + This permits linear combinations of CI vectors with different + M == neleca-nelecb at the price of higher memory cost. This function + does NOT change the datatype. + + Args: + ci0_r: sequence or generator of ndarray of length nprods + CAS-CI vectors in the spin-pure representation + norb: integer + Number of orbitals + nelec_r: sequence of tuple of length (2) + (neleca, nelecb) for each element of ci0_r + + Returns: + ci1_r: ndarray of shape (nprods, ndet_spinless) + spinless CAS-CI vectors + ''' ci1_r_gen, _, _ = civec_spinless_repr_generator (ci0_r, norb, nelec_r) ci1_r = np.stack ([x.copy () for x in ci1_r_gen ()], axis=0) return ci1_r diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index 870203a5..fd34a74c 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -862,6 +862,22 @@ def run_lasci (las, mo_coeff=None, ci0=None, lroots=None, lweights=None, verbose e_tot = np.dot (las.weights, e_states) return converged, e_tot, e_states, e_cas, e_lexc, ci1 + +def get_nelec_frs (las): + ''' Getter function for all electron occupancies in all fragments in all rootspaces. + + Returns: + nelec_frs: ndarray of shape (nfrags, nroots, 2) + number of electrons of each spin in each rootspace in each fragment + ''' + nelec_frs = [] + for fcibox, nelec in zip (las.fciboxes, las.nelecas_sub): + nelec_rs = [] + for solver in fcibox.fcisolvers: + nelec_rs.append (_unpack_nelec (fcibox._get_nelec (solver, nelec))) + nelec_frs.append (nelec_rs) + return np.asarray (nelec_frs) + class LASCINoSymm (casci.CASCI): def __init__(self, mf, ncas, nelecas, ncore=None, spin_sub=None, frozen=None, **kwargs): @@ -924,6 +940,7 @@ def get_mo_slice (self, idx, mo_coeff=None): mo = mo[:,:self.ncas_sub[idx]] return mo + get_nelec_frs = get_nelec_frs get_h1eff = get_h1las = h1e_for_las = h1e_for_las get_h2eff = ao2mo = las_ao2mo.get_h2eff get_h2cas = las_ao2mo.get_h2cas @@ -2047,6 +2064,7 @@ def converged (self, x): else: self.states_converged = [x,]*self.nroots + def dump_spaces (self, nroots=None, sort_energy=False): log = lib.logger.new_logger (self, self.verbose) log.info ("******** LAS space tables ********") From 1695e356fd9915992dd9a371fb2582f662b07bf2 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 31 May 2024 11:43:48 -0500 Subject: [PATCH 50/87] lassi analyze default kwarg fix & profile comment --- my_pyscf/lassi/lassi.py | 2 +- my_pyscf/lassi/op_o1.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/my_pyscf/lassi/lassi.py b/my_pyscf/lassi/lassi.py index a70faeb5..5c1c8f9c 100644 --- a/my_pyscf/lassi/lassi.py +++ b/my_pyscf/lassi/lassi.py @@ -799,7 +799,7 @@ def get_sivec_vacuum_shuffle (self, state=None, nelec_vac=None, si=None, ci=None nelec_frs = self.get_nelec_frs () return sivec_vacuum_shuffle (si, nelec_frs, lroots, nelec_vac=nelec_vac, state=state) - def analyze (self, state=None, **kwargs): + def analyze (self, state=0, **kwargs): from mrh.my_pyscf.lassi.sitools import analyze analyze (self, self.si, state=state, **kwargs) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 64c5d0e8..b3c4fb5b 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -1003,6 +1003,8 @@ def _get_spec_addr_ovlp (self, bra, ket, *inv): Overlap * permutation factors (cf. get_ovlp_fac) corresponding to the interactions bra_rng, ket_rng. ''' + # NOTE: from tests on triene 3frag LASSI[3,3], this function is 1/4 to 1/6 of the "put" + # runtime, and apparently it can sometimes multithread somehow??? rbra, rket = self.rootaddr[bra], self.rootaddr[ket] braenv = self.envaddr[bra] ketenv = self.envaddr[ket] From 2d7843af957d401e963b25526bdf854724949eb3 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 31 May 2024 13:24:45 -0500 Subject: [PATCH 51/87] lassi analyze print_wfn_leading --- my_pyscf/lassi/sitools.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/my_pyscf/lassi/sitools.py b/my_pyscf/lassi/sitools.py index d6b8535d..d40fced5 100644 --- a/my_pyscf/lassi/sitools.py +++ b/my_pyscf/lassi/sitools.py @@ -205,6 +205,8 @@ def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', no_coeff, no_ene, no_occ = las.canonicalize (natorb_casdm1=casdm1)[:3] log.info ("Analyzing LASSI vectors for states = %s",str(state)) + for st in states: + print_wfn_leading (las, state=st, ci=ci0, si=si) log.info ("Average quantum numbers:") space_weights, state_coeffs, idx_space = decompose_sivec_by_rootspace ( @@ -295,6 +297,25 @@ def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', else: return ci1, si1 +def print_wfn_leading (lsi, state=0, ci=None, si=None, ncoeffs=20): + if si is None: si = lsi.si + if ci is None: ci = lsi.ci + si = si[:,state] + log = lib.logger.new_logger (lsi, lsi.verbose) + rootaddr, envaddr = get_rootaddr_fragaddr (get_lroots (ci)) + idx = np.argsort (-si*si) + idx = idx[:ncoeffs] + si, rootaddr, envaddr = si[idx], rootaddr[idx], envaddr[:,idx].T + log.info ("Printing leading %d SI addresses and coeffs of state %d (wgt = %9.3e)", + ncoeffs, state, np.dot (si, si)) + if np.amax (envaddr) < 10: + env_fmt = ''.join (['{:1d}',]*lsi.nfrags) + else: + env_fmt = '.'.join (['{:1d}',]*lsi.nfrags) + envaddr = [env_fmt.format (*env) for env in envaddr] + for coeff, root, env in zip (si, rootaddr, envaddr): + log.info ("%5d|%s : %10.3e", root, env, coeff) + def analyze_basis (las, ci=None, space=0, frag=0, npr=10): '''Print out the many-electron wave function(s) in terms of CSFs for a specific fragment in a specific LAS rootspace''' From 95074af7e92e964b296b12a050d3a1e57349dfed Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 31 May 2024 14:09:48 -0500 Subject: [PATCH 52/87] LASSI UI fixes & more output in LASSI analyze --- my_pyscf/lassi/lassi.py | 14 +++++++++----- my_pyscf/lassi/sitools.py | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/my_pyscf/lassi/lassi.py b/my_pyscf/lassi/lassi.py index 5c1c8f9c..23f96e17 100644 --- a/my_pyscf/lassi/lassi.py +++ b/my_pyscf/lassi/lassi.py @@ -586,8 +586,10 @@ def roots_make_rdm12s (las, ci, si, orbsym=None, soc=None, break_symmetry=None, orbsym = las.label_symmetry_(las.mo_coeff).orbsym if orbsym is not None: orbsym = orbsym[las.ncore:las.ncore+las.ncas] - if soc is None: soc = si.soc - if break_symmetry is None: break_symmetry = si.break_symmetry + if soc is None: + soc = getattr (si, 'soc', getattr (las, 'soc', False)) + if break_symmetry is None: + break_symmetry = getattr (si, 'break_symmetry', getattr (las, 'break_symmetry', False)) o0_memcheck = op_o0.memcheck (las, ci, soc=soc) if opt == 0 and o0_memcheck == False: raise RuntimeError ('Insufficient memory to use o0 LASSI algorithm') @@ -685,8 +687,10 @@ def root_make_rdm12s (las, ci, si, state=0, orbsym=None, soc=None, break_symmetr ''' states = np.atleast_1d (state) si_column = si[:,states] - if soc is None: soc = si.soc - if break_symmetry is None: break_symmetry = si.break_symmetry + if soc is None: + soc = getattr (si, 'soc', getattr (las, 'soc', False)) + if break_symmetry is None: + break_symmetry = getattr (si, 'break_symmetry', getattr (las, 'break_symmetry', False)) if rootsym is None: rootsym = getattr (si, 'rootsym', getattr (las, 'rootsym', None)) rootsym = [rootsym[s] for s in states] @@ -801,7 +805,7 @@ def get_sivec_vacuum_shuffle (self, state=None, nelec_vac=None, si=None, ci=None def analyze (self, state=0, **kwargs): from mrh.my_pyscf.lassi.sitools import analyze - analyze (self, self.si, state=state, **kwargs) + return analyze (self, self.si, state=state, **kwargs) def reset (self, mol=None): if mol is not None: diff --git a/my_pyscf/lassi/sitools.py b/my_pyscf/lassi/sitools.py index d40fced5..5f30a2e2 100644 --- a/my_pyscf/lassi/sitools.py +++ b/my_pyscf/lassi/sitools.py @@ -222,13 +222,25 @@ def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', log.info ("Nelecb = %s", str (nb)) log.info ("Smult = %s", str (s)) + def log_qn_spread (qn_table, weights): + qns = np.unique (qn_table) + fmt_str = '{:4d} ' + ' '.join (['{:10.3e}',]*nfrags) + for qn in qns: + row = [np.sum (weights[qn_table[:,ifrag]==qn]) for ifrag in range (nfrags)] + log.info (fmt_str.format (qn, *row)) + + c, m, s, w = get_space_info (las) + avg_weights = space_weights[:,states].sum (1) / nstates + log.info ("Spread of charges:") + log_qn_spread (c, avg_weights) + log.info ("Spread of spin multiplicities:") + log_qn_spread (s, avg_weights) + log.info (("Analyzing rootspace fragment density matrices for LASSI " "states %s averaged together"), str (states)) log.info ("Continue until 1-%e of wave function(s) accounted for", print_all_but) - avg_weights = space_weights[:,states].sum (1) / nstates lroots = get_lroots (ci0).T running_weight = 1 - c, m, s, w = get_space_info (las) fmt_str = " {:4s} {:>7s} {:>4s} {:>3s} {:>6s} {:11s} {:>8s}" header = fmt_str.format ("Frag", "Nelec", "2S+1", "Ir", "", "Max(weight)", "Entropy") fmt_str = " {:4d} {:>7s} {:>4d} {:>3s} {:6.3f} {:>11.4f} {:8f}" From 9ce3b7eff42eae40202adb863e023f20b3e3921c Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 31 May 2024 16:09:11 -0500 Subject: [PATCH 53/87] typo --- my_pyscf/lassi/sitools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/my_pyscf/lassi/sitools.py b/my_pyscf/lassi/sitools.py index 5f30a2e2..e19b9cdf 100644 --- a/my_pyscf/lassi/sitools.py +++ b/my_pyscf/lassi/sitools.py @@ -224,9 +224,9 @@ def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', def log_qn_spread (qn_table, weights): qns = np.unique (qn_table) - fmt_str = '{:4d} ' + ' '.join (['{:10.3e}',]*nfrags) + fmt_str = '{:4d} ' + ' '.join (['{:10.3e}',]*las.nfrags) for qn in qns: - row = [np.sum (weights[qn_table[:,ifrag]==qn]) for ifrag in range (nfrags)] + row = [np.sum (weights[qn_table[:,ifrag]==qn]) for ifrag in range (las.nfrags)] log.info (fmt_str.format (qn, *row)) c, m, s, w = get_space_info (las) From e91883bfd65756a596ddeea510a18fef4817bc8a Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 3 Jun 2024 14:30:38 -0500 Subject: [PATCH 54/87] PySCF(-forge) compat check & version update --- pyscf-forge_version.txt | 2 +- pyscf_version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyscf-forge_version.txt b/pyscf-forge_version.txt index d5ddfa20..5e77e6b1 100644 --- a/pyscf-forge_version.txt +++ b/pyscf-forge_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf-forge.git@2ef4c002985d8d234e7e7720fad3affa0cfedce5 +git+https://github.com/pyscf/pyscf-forge.git@62e3e9fe0dd03d89d2632b7c1f6b4c9da6f625ee diff --git a/pyscf_version.txt b/pyscf_version.txt index a50f2dbc..84a214be 100644 --- a/pyscf_version.txt +++ b/pyscf_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf.git@a64f740c978eb6cc10eec53f0c50098fc4f543b2 +git+https://github.com/pyscf/pyscf.git@d892f8f110f8e783945d17b869094029d54065c6 From 2f7ea513d078562263595763421774e48cb1e13b Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Wed, 5 Jun 2024 12:50:05 -0500 Subject: [PATCH 55/87] mrh.util.chemistry.yamaguchi & lassi.get_smult_fr --- my_pyscf/lassi/lassi.py | 5 +++++ util/chemistry.py | 49 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 util/chemistry.py diff --git a/my_pyscf/lassi/lassi.py b/my_pyscf/lassi/lassi.py index 23f96e17..872e5111 100644 --- a/my_pyscf/lassi/lassi.py +++ b/my_pyscf/lassi/lassi.py @@ -763,6 +763,11 @@ def get_nelec_frs (self, las=None): from mrh.my_pyscf.mcscf.lasci import get_nelec_frs return get_nelec_frs (las) + def get_smult_fr (self, las=None): + if las is None: las = self + from mrh.my_pyscf.mcscf.lasci import get_space_info + return get_space_info (las)[2].T + def get_lroots (self, ci=None): if ci is None: ci = self.ci return get_lroots (ci) diff --git a/util/chemistry.py b/util/chemistry.py new file mode 100644 index 00000000..fcd12c9d --- /dev/null +++ b/util/chemistry.py @@ -0,0 +1,49 @@ +import numpy as np + +def yamaguchi (e_roots, s2, highsmult=None): + ''' Evaluate the Yamaguchi formula + + J = (ELS - EHS) / (HS - LS) + + in cm-1 + + Args: + e_roots: sequence + State energies + s2: sequence + Spin expectation values of states + + Kwargs: + highsmult: integer + High-spin spin multiplicity. Parity of the wave function + (i.e. whether low-spin is S=0 or S=1/2) is derived from this. + Defaults to maximum value of s2 + + Returns: + J: float + Magnetic coupling parameter in cm-1 + ''' + e_roots, s2 = np.asarray (e_roots), np.asarray (s2) + if highsmult is not None: + highs = (highsmult - 1) / 2 + highs2 = np.around (highs * (highs+1), 2) + else: + highs2 = np.around (np.amax (s2), 2) + lows2 = highs2 - np.floor (highs2) + idx = np.argsort (e_roots) + e_roots = e_roots[idx] + s2 = s2[idx] + idx_hs = (np.around (s2, 2) == highs2) + assert (np.count_nonzero (idx_hs)), 'high-spin ground state not found {} {}'.format (np.around (s2,2), highs2) + idx_hs = np.where (idx_hs)[0][0] + e_hs = e_roots[idx_hs] + idx_ls = (np.around (s2, 2) == lows2) + assert (np.count_nonzero (idx_ls)), 'low-spin ground state not found {} {}'.format (np.around (s2,2), lows2) + idx_ls = np.where (idx_ls)[0][0] + e_ls = e_roots[idx_ls] + j = (e_ls - e_hs) / 24 + from pyscf.data import nist + au2cm = nist.HARTREE2J / nist.PLANCK / nist.LIGHT_SPEED_SI * 1e-2 + return j*au2cm + + From f99f3e59e8aaf49aaead4e8a6fb88e2f1707b89d Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 6 Jun 2024 12:22:42 -0500 Subject: [PATCH 56/87] no full-dim "si_dm" in lassi op_o1 rdm12s build --- my_pyscf/lassi/op_o1.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index b3c4fb5b..e8c309ca 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -1545,18 +1545,18 @@ def __init__(self, ints, nlas, hopping_index, lroots, si, mask_bra_space=None, LSTDMint2.__init__(self, ints, nlas, hopping_index, lroots, mask_bra_space=mask_bra_space, mask_ket_space=mask_ket_space, log=log, dtype=dtype) self.nroots_si = si.shape[-1] - si = si.copy () - self._umat_linequiv_loop_(si) - self.si_dm = np.stack ([np.dot (si[:,i:i+1],si[:,i:i+1].conj ().T) - for i in range (self.nroots_si)], axis=-1) + self.si = si.copy () + self._umat_linequiv_loop_(self.si) def _put_SD1_(self, bra, ket, D1, wgt): - fac = np.dot (wgt, self.si_dm[bra,ket]) + si_dm = self.si[bra,:] * self.si[ket,:].conj () + fac = np.dot (wgt, si_dm) self.rdm1s[:] += np.multiply.outer (fac, D1) def _put_SD2_(self, bra, ket, D2, wgt): - wgt = np.dot (wgt, self.si_dm[bra,ket]) - self.rdm2s[:] += np.multiply.outer (wgt, D2) + si_dm = self.si[bra,:] * self.si[ket,:].conj () + fac = np.dot (wgt, si_dm) + self.rdm2s[:] += np.multiply.outer (fac, D2) def _add_transpose_(self): self.rdm1s += self.rdm1s.conj ().transpose (0,1,3,2) From fbd64323c85dd1a570229d19dac98155570ad0a4 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 7 Jun 2024 12:29:57 -0500 Subject: [PATCH 57/87] LASSI basic chkfile functionality Not optimized for LASSIS --- my_pyscf/lassi/chkfile.py | 7 ++-- my_pyscf/lassi/lassi.py | 5 +++ my_pyscf/mcscf/chkfile.py | 5 +++ tests/lassi/test_chkfile.py | 81 +++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 tests/lassi/test_chkfile.py diff --git a/my_pyscf/lassi/chkfile.py b/my_pyscf/lassi/chkfile.py index c4871cba..dd33dbf6 100644 --- a/my_pyscf/lassi/chkfile.py +++ b/my_pyscf/lassi/chkfile.py @@ -2,22 +2,23 @@ KEYS_CONFIG_LASSI = las_chkfile.KEYS_CONFIG_LASSCF + ['nfrags', 'break_symmetry', 'soc', 'opt'] KEYS_SACONSTR_LASSI = las_chkfile.KEYS_SACONSTR_LASSCF -KEYS_RESULTS_LASSI = ['e_roots', 'si', 's2', 's2_mat', 'nelec', 'wfnsym', 'rootsym'] +KEYS_RESULTS_LASSI = ['e_states', 'e_roots', 'si', 's2', 's2_mat', 'nelec', 'wfnsym', 'rootsym'] def load_lsi_(lsi, chkfile=None, method_key='lsi', keys_config=KEYS_CONFIG_LASSI, keys_saconstr=KEYS_SACONSTR_LASSI, keys_results=KEYS_RESULTS_LASSI): + lsi._las.load_chk (chkfile=chkfile) return las_chkfile.load_las_(lsi, chkfile=chkfile, method_key=method_key, keys_config=keys_config, keys_saconstr=keys_saconstr, keys_results=keys_results) - -def dump_lsi (lsi, chkfile='None', method_key='lsi', mo_coeff=None, ci=None, +def dump_lsi (lsi, chkfile=None, method_key='lsi', mo_coeff=None, ci=None, overwrite_mol=True, keys_config=KEYS_CONFIG_LASSI, keys_saconstr=KEYS_SACONSTR_LASSI, keys_results=KEYS_RESULTS_LASSI, **kwargs): + lsi._las.dump_chk (chkfile=chkfile) return las_chkfile.dump_las (lsi, chkfile=chkfile, method_key=method_key, mo_coeff=mo_coeff, ci=ci, overwrite_mol=overwrite_mol, keys_config=keys_config, keys_saconstr=keys_saconstr, keys_results=keys_results, **kwargs) diff --git a/my_pyscf/lassi/lassi.py b/my_pyscf/lassi/lassi.py index 872e5111..26d0df09 100644 --- a/my_pyscf/lassi/lassi.py +++ b/my_pyscf/lassi/lassi.py @@ -3,6 +3,7 @@ from scipy import linalg from mrh.my_pyscf.lassi import op_o0 from mrh.my_pyscf.lassi import op_o1 +from mrh.my_pyscf.lassi import chkfile from mrh.my_pyscf.lassi.citools import get_lroots from pyscf import lib, symm, ao2mo from pyscf.scf.addons import canonical_orth_ @@ -718,6 +719,7 @@ def __init__(self, las, mo_coeff=None, ci=None, soc=False, break_symmetry=False, self.ncore, self.ncas = las.ncore, las.ncas self.nfrags, self.nroots = las.nfrags, las.nroots self.ncas_sub, self.nelecas_sub, self.fciboxes = las.ncas_sub, las.nelecas_sub, las.fciboxes + self.nelecas = sum (self.nelecas_sub) self.weights, self.e_states, self.e_lexc = las.weights, las.e_states, las.e_lexc self.converged = las.converged # I/O data from las parent @@ -817,3 +819,6 @@ def reset (self, mol=None): self.mol = mol self._las.reset (mol) + dump_chk = chkfile.dump_lsi + load_chk = load_chk_ = chkfile.load_lsi_ + diff --git a/my_pyscf/mcscf/chkfile.py b/my_pyscf/mcscf/chkfile.py index d9067102..9942f581 100644 --- a/my_pyscf/mcscf/chkfile.py +++ b/my_pyscf/mcscf/chkfile.py @@ -28,6 +28,11 @@ def load_las_(mc, chkfile=None, method_key='las', except AttributeError as err: las = mc._las.state_average (**sakwargs) mc.fciboxes = las.fciboxes + for fcibox, norb, ne in zip (mc.fciboxes, mc.ncas_sub, mc.nelecas_sub): + for solver in fcibox.fcisolvers: + solver.norb = norb + solver.nelec = fcibox._get_nelec (solver, ne) + solver.check_transformer_cache () mc.nroots=las.nroots mc.weights=las.weights for key in keys_results: diff --git a/tests/lassi/test_chkfile.py b/tests/lassi/test_chkfile.py new file mode 100644 index 00000000..3da6d895 --- /dev/null +++ b/tests/lassi/test_chkfile.py @@ -0,0 +1,81 @@ +#!/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. + +import copy +import unittest +import tempfile +import numpy as np +from pyscf.tools import molden +from pyscf import gto, scf, lib, mcscf +from mrh.my_pyscf.mcscf.lasscf_o0 import LASSCF +from mrh.my_pyscf import lassi + +def setUpModule(): + global mf, lsi, lsi2 + xyz='''Li 0 0 0, + H 2 0 0, + Li 10 0 0, + H 12 0 0''' + mol = gto.M (atom=xyz, basis='6-31g', symmetry=None, verbose=0, output='/dev/null') + mf = scf.RHF (mol).run () + from pyscf.tools import molden + mc = mcscf.CASSCF (mf, 4, 4).run () + mc.analyze () + + las = LASSCF (mf, (2,2), (2,2)) + mo = las.localize_init_guess (([0,1],[2,3]), mc.mo_coeff, freeze_cas_spaces=True) + las.kernel (mo) + lsi = lassi.LASSIrq (las, r=2, q=2).run () + with tempfile.NamedTemporaryFile() as chkfile: + lsi.dump_chk (chkfile=chkfile.name) + lsi2 = lassi.LASSIrq (las, r=2, q=2) + lsi2.load_chk_(chkfile=chkfile.name) + +def tearDownModule(): + global mf, lsi, lsi2 + mf.mol.stdout.close () + del mf, lsi, lsi2 + +class KnownValues(unittest.TestCase): + def test_config (self): + self.assertEqual (lsi.ncore, lsi2.ncore) + self.assertEqual (lsi.nfrags, lsi2.nfrags) + self.assertEqual (lsi.nroots, lsi2.nroots) + self.assertEqual (lsi.break_symmetry, lsi2.break_symmetry) + self.assertEqual (lsi.soc, lsi2.soc) + self.assertEqual (lsi.opt, lsi2.opt) + self.assertListEqual (list(lsi.ncas_sub), list(lsi2.ncas_sub)) + self.assertListEqual (lsi.nelecas_sub.tolist(), lsi2.nelecas_sub.tolist ()) + self.assertListEqual (list(lsi.weights), list(lsi2.weights)) + + def test_results (self): + self.assertListEqual (list(lsi.e_states), list(lsi2.e_states)) + self.assertListEqual (list(lsi.e_roots), list(lsi2.e_roots)) + self.assertListEqual (list(lsi.s2), list(lsi2.s2)) + for ne, ne2 in zip (lsi.nelec, lsi2.nelec): + self.assertListEqual (list(ne), list(ne2)) + self.assertListEqual (list(lsi.wfnsym), list(lsi2.wfnsym)) + for r, r2 in zip (lsi.rootsym, lsi2.rootsym): + self.assertListEqual (list(r), list(r2)) + self.assertAlmostEqual (lib.fp (lsi.si), lib.fp (lsi2.si), 9) + self.assertAlmostEqual (lib.fp (lsi.s2_mat), lib.fp (lsi2.s2_mat), 9) + for i in range (2): + for j in range (lsi.nroots): + self.assertAlmostEqual (lib.fp (lsi.ci[i][j]), lib.fp (lsi2.ci[i][j]), 9) + +if __name__ == "__main__": + print("Full Tests for LASSCF chkfile") + unittest.main() + From b4c06efe135d1f3607f1cc39056f42f7f48fd4e7 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 7 Jun 2024 12:41:26 -0500 Subject: [PATCH 58/87] filename collision issue --- tests/lasscf/__init__.py | 1 + tests/lassi/__init__.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 tests/lasscf/__init__.py create mode 100644 tests/lassi/__init__.py diff --git a/tests/lasscf/__init__.py b/tests/lasscf/__init__.py new file mode 100644 index 00000000..1e0ca6a5 --- /dev/null +++ b/tests/lasscf/__init__.py @@ -0,0 +1 @@ +# diff --git a/tests/lassi/__init__.py b/tests/lassi/__init__.py new file mode 100644 index 00000000..1e0ca6a5 --- /dev/null +++ b/tests/lassi/__init__.py @@ -0,0 +1 @@ +# From 28df02becbcb0ebeb1c85b318a16455904b3ffeb Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 7 Jun 2024 13:37:13 -0500 Subject: [PATCH 59/87] filename collision issue --- tests/lasscf/__init__.py | 1 - tests/lassi/__init__.py | 1 - tests/lassi/{test_chkfile.py => test_chkfile2.py} | 0 3 files changed, 2 deletions(-) delete mode 100644 tests/lasscf/__init__.py delete mode 100644 tests/lassi/__init__.py rename tests/lassi/{test_chkfile.py => test_chkfile2.py} (100%) diff --git a/tests/lasscf/__init__.py b/tests/lasscf/__init__.py deleted file mode 100644 index 1e0ca6a5..00000000 --- a/tests/lasscf/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/tests/lassi/__init__.py b/tests/lassi/__init__.py deleted file mode 100644 index 1e0ca6a5..00000000 --- a/tests/lassi/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/tests/lassi/test_chkfile.py b/tests/lassi/test_chkfile2.py similarity index 100% rename from tests/lassi/test_chkfile.py rename to tests/lassi/test_chkfile2.py From aa117fbe6438230d8dbe5ce838d92457f34148a3 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 13 Jun 2024 12:15:14 -0500 Subject: [PATCH 60/87] Make natorb optional in lassi.sitools.analyze --- my_pyscf/lassi/sitools.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/my_pyscf/lassi/sitools.py b/my_pyscf/lassi/sitools.py index e19b9cdf..d4329e28 100644 --- a/my_pyscf/lassi/sitools.py +++ b/my_pyscf/lassi/sitools.py @@ -137,7 +137,7 @@ def _print_states (log, iroot, space_weights, state_coeffs, lroots, print_all_bu return def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', ncsf=10, - return_metrics=False): + return_metrics=False, do_natorb=True): '''Print out analysis of LASSI result in terms of average quantum numbers and density matrix analyses of the lroots in each rootspace @@ -163,6 +163,9 @@ def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', Number of leading CSFs of each basis function to print. return_metrics: logical If True, returns space_weights, navg, maxw, and entr arrays; see below. + do_natorb: logical + If True, include a natural-orbital analysis (which is sometimes very + slow due to poor optimization of LASSI op_o1 RDM build functions) Returns: ci1: list of list of ndarray @@ -194,15 +197,16 @@ def analyze (las, si, ci=None, state=0, print_all_but=1e-8, lbasis='primitive', nstates = len (states) log = lib.logger.new_logger (las, las.verbose) - log.info ("Natural-orbital analysis for state(s) %s", str (state)) - casdm1s = root_make_rdm12s (las, ci, si, state=state)[0] - if nstates > 1: - casdm1s = casdm1s.sum (0) / nstates - casdm1 = casdm1s.sum (0) - if isinstance (las, LASSI): - no_coeff, no_ene, no_occ = las._las.canonicalize (natorb_casdm1=casdm1)[:3] - else: - no_coeff, no_ene, no_occ = las.canonicalize (natorb_casdm1=casdm1)[:3] + if do_natorb: + log.info ("Natural-orbital analysis for state(s) %s", str (state)) + casdm1s = root_make_rdm12s (las, ci, si, state=state)[0] + if nstates > 1: + casdm1s = casdm1s.sum (0) / nstates + casdm1 = casdm1s.sum (0) + if isinstance (las, LASSI): + no_coeff, no_ene, no_occ = las._las.canonicalize (natorb_casdm1=casdm1)[:3] + else: + no_coeff, no_ene, no_occ = las.canonicalize (natorb_casdm1=casdm1)[:3] log.info ("Analyzing LASSI vectors for states = %s",str(state)) for st in states: From bb588209f69453541db92d4f941b591f8898187a Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 14 Jun 2024 11:42:06 -0500 Subject: [PATCH 61/87] lassirq prepare_states_ --- my_pyscf/lassi/lassirq.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/my_pyscf/lassi/lassirq.py b/my_pyscf/lassi/lassirq.py index f2efadea..83c98757 100644 --- a/my_pyscf/lassi/lassirq.py +++ b/my_pyscf/lassi/lassirq.py @@ -85,7 +85,7 @@ def __init__(self, las, r=0, q=1, opt=1, **kwargs): self.q = q LASSI.__init__(self, las, opt=opt, **kwargs) - def kernel (self, **kwargs): + def prepare_states_(self): self.converged, las = self.prepare_states () #self.__dict__.update(las.__dict__) # Unsafe self.fciboxes = las.fciboxes @@ -94,6 +94,9 @@ def kernel (self, **kwargs): self.weights = las.weights self.e_lexc = las.e_lexc self.e_states = las.e_states + + def kernel (self, **kwargs): + self.prepare_states_() return LASSI.kernel (self, **kwargs) def filter_spaces (self, las): @@ -103,6 +106,8 @@ def filter_spaces (self, las): make_lroots = make_lroots prepare_states = prepare_states as_scanner=as_scanner + + class LASSIrqCT (LASSIrq): def make_lroots (self, las, q=None): lroots = LASSIrq.make_lroots (self, las, q=q) From e1fa29754057a19d4d09da3fb266a2be8338ac09 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 17 Jun 2024 13:35:49 -0500 Subject: [PATCH 62/87] Improve lasscf_async set_fragments_ flexibility User can now pass lists of AOs as well as lists of atoms. --- my_pyscf/mcscf/lasscf_async/lasscf_async.py | 35 ++++++++++++++++----- my_pyscf/mcscf/lasscf_guess.py | 21 ++++++++----- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/my_pyscf/mcscf/lasscf_async/lasscf_async.py b/my_pyscf/mcscf/lasscf_async/lasscf_async.py index 67a0e7d2..76ec8696 100644 --- a/my_pyscf/mcscf/lasscf_async/lasscf_async.py +++ b/my_pyscf/mcscf/lasscf_async/lasscf_async.py @@ -3,6 +3,7 @@ from pyscf import lib from pyscf.mcscf import mc1step from mrh.my_pyscf.mcscf import lasci, lasscf_sync_o0 +from mrh.my_pyscf.mcscf.lasscf_guess import interpret_frags_atoms from mrh.my_pyscf.mcscf.lasscf_async.split import get_impurity_space_constructor from mrh.my_pyscf.mcscf.lasscf_async.crunch import get_impurity_casscf from mrh.my_pyscf.mcscf.lasscf_async.keyframe import LASKeyframe @@ -142,16 +143,34 @@ def get_keyframe (self, mo_coeff=None, ci=None): return LASKeyframe (self, mo_coeff, ci) as_scanner = mc1step.as_scanner def set_fragments_(self, frags_atoms=None, mo_coeff=None, localize_init_guess=True, - **kwargs): - # TODO: frags_orbs on input (requires refactoring localize_init_guess) - ao_offset = self.mol.offset_ao_by_atom () - frags_orbs = [[orb for atom in frag_atom - for orb in list (range (ao_offset[atom,2], ao_offset[atom,3]))] - for frag_atom in frags_atoms] - self.frags_orbs = frags_orbs + frags_by_AOs=False, **kwargs): + ''' Project active orbitals into sets of orthonormal "fragments" defined by frags_atoms, + cache the resulting frags_orbs member, and call localize_init_guess. Passes on unlisted + kwargs to localize_init_guess call. + + Kwargs: + frags_atoms: list of length nfrags + Contains either lists of integer atom indices, or lists of + strings which are passed to mol.search_ao_label, which define + fragments into which the active orbitals are to be localized + mo_coeff: ndarray of shape (nao, nmo) + Molecular orbital coefficients containing active orbitals + on columns ncore:ncore+ncas + localize_init_guess: logical + If false, this step is skipped and mo_coeff is returned unaltered + frags_by_AOs: logical + If True, interpret integer frags_atoms as AOs rather than atoms + + Returns: + mo_coeff: ndarray of shape (nao,nmo) + Orbital coefficients after localization of the active space; + columns in the order (inactive,las1,las2,...,lasn,external) + ''' + self.frags_orbs = interpret_frags_atoms (self.mol, frags_atoms, frags_by_AOs=frags_by_AOs) if mo_coeff is None: mo_coeff=self.mo_coeff if localize_init_guess: - mo_coeff = self.localize_init_guess (frags_atoms, mo_coeff=mo_coeff, **kwargs) + mo_coeff = self.localize_init_guess (self.frags_orbs, mo_coeff=mo_coeff, + frags_by_AOs=True, **kwargs) return mo_coeff def dump_flags (self, verbose=None, _method_name='LASSCF'): lasci.LASCINoSymm.dump_flags (self, verbose=verbose, _method_name=_method_name) diff --git a/my_pyscf/mcscf/lasscf_guess.py b/my_pyscf/mcscf/lasscf_guess.py index 7ff42b7a..18dc1e01 100644 --- a/my_pyscf/mcscf/lasscf_guess.py +++ b/my_pyscf/mcscf/lasscf_guess.py @@ -123,7 +123,7 @@ def _localize (las, frags_orbs, mo_coeff, spin, lo_coeff, fock, ao_ovlp, freeze_ return mo_coeff def localize_init_guess (las, frags_atoms, mo_coeff=None, spin=None, lo_coeff=None, fock=None, - freeze_cas_spaces=False): + freeze_cas_spaces=False, frags_by_AOs=False): ''' Project active orbitals into sets of orthonormal "fragments" defined by lo_coeff and frags_orbs, and orthonormalize inactive and virtual orbitals in the orthogonal complement space. Beware that unless freeze_cas_spaces=True, frozen orbitals will not be preserved. @@ -155,6 +155,8 @@ def localize_init_guess (las, frags_atoms, mo_coeff=None, spin=None, lo_coeff=No orbitals are projected into the localized-orbital space and the inactive and external orbitals are reconstructed as closely as possible using SVD. + frags_by_AOs: logical + If True, interpret integer frags_atoms as AOs rather than atoms Returns: mo_coeff: ndarray of shape (nao,nmo) @@ -169,20 +171,23 @@ def localize_init_guess (las, frags_atoms, mo_coeff=None, spin=None, lo_coeff=No spin = las.nelecas[0] - las.nelecas[1] assert (spin % 2 == sum (las.nelecas) % 2) assert (len (frags_atoms) == len (las.ncas_sub)) + frags_orbs = interpret_frags_atoms (las.mol, frags_atoms, frags_by_AOs=frags_by_AOs) + if fock is None: fock = las._scf.get_fock () + ao_ovlp = las._scf.get_ovlp () + return _localize (las, frags_orbs, mo_coeff, spin, lo_coeff, fock, ao_ovlp, freeze_cas_spaces=freeze_cas_spaces) + +def interpret_frags_atoms (mol, frags_atoms, frags_by_AOs=False): frags_atoms_int = all ([all ([isinstance (i, int) for i in j]) for j in frags_atoms]) frags_atoms_str = all ([all ([isinstance (i, str) for i in j]) for j in frags_atoms]) if frags_atoms_int: - ao_offset = las.mol.offset_ao_by_atom () + if frags_by_AOs: return frags_atoms + ao_offset = mol.offset_ao_by_atom () frags_orbs = [[orb for atom in frags_atoms for orb in list (range (ao_offset[atom,2], ao_offset[atom,3]))] for frags_atoms in frags_atoms] elif frags_atoms_str: - frags_orbs = [las.mol.search_ao_label (i) for i in frags_atoms] + frags_orbs = [mol.search_ao_label (i) for i in frags_atoms] else: raise RuntimeError ('localize_init_guess requires either all integers or all strings to identify fragments') - if fock is None: fock = las._scf.get_fock () - ao_ovlp = las._scf.get_ovlp () - return _localize (las, frags_orbs, mo_coeff, spin, lo_coeff, fock, ao_ovlp, freeze_cas_spaces=freeze_cas_spaces) - - + return frags_orbs From 53af01d9cc6fba5213e2aa6a709034c4580e4f79 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 17 Jun 2024 14:04:25 -0500 Subject: [PATCH 63/87] numpy datatype annoying --- my_pyscf/mcscf/lasscf_guess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/my_pyscf/mcscf/lasscf_guess.py b/my_pyscf/mcscf/lasscf_guess.py index 18dc1e01..577d27e6 100644 --- a/my_pyscf/mcscf/lasscf_guess.py +++ b/my_pyscf/mcscf/lasscf_guess.py @@ -177,7 +177,7 @@ def localize_init_guess (las, frags_atoms, mo_coeff=None, spin=None, lo_coeff=No return _localize (las, frags_orbs, mo_coeff, spin, lo_coeff, fock, ao_ovlp, freeze_cas_spaces=freeze_cas_spaces) def interpret_frags_atoms (mol, frags_atoms, frags_by_AOs=False): - frags_atoms_int = all ([all ([isinstance (i, int) for i in j]) for j in frags_atoms]) + frags_atoms_int = all ([all ([isinstance (i, (int,np.integer)) for i in j]) for j in frags_atoms]) frags_atoms_str = all ([all ([isinstance (i, str) for i in j]) for j in frags_atoms]) if frags_atoms_int: if frags_by_AOs: return frags_atoms @@ -186,7 +186,7 @@ def interpret_frags_atoms (mol, frags_atoms, frags_by_AOs=False): for orb in list (range (ao_offset[atom,2], ao_offset[atom,3]))] for frags_atoms in frags_atoms] elif frags_atoms_str: - frags_orbs = [mol.search_ao_label (i) for i in frags_atoms] + frags_orbs = [list (mol.search_ao_label (i)) for i in frags_atoms] else: raise RuntimeError ('localize_init_guess requires either all integers or all strings to identify fragments') return frags_orbs From 8aeb777701ac1897d151343e969bb25a855bc5e5 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 17 Jun 2024 14:33:20 -0500 Subject: [PATCH 64/87] lasscf_async edge case & numpy 2.0 compatibility lasscf_async edge case for zero inactive and zero external orbitals --- my_pyscf/fci/csfstring.py | 6 +- my_pyscf/mcscf/lasci.py | 2 +- my_pyscf/mcscf/lasci_sync.py | 2 +- my_pyscf/mcscf/lasscf_async/combine.py | 23 ++++--- my_pyscf/mcscf/lasscf_async/crunch.py | 84 ++++++++++++++------------ my_pyscf/mcscf/lasscf_async/split.py | 1 + my_pyscf/tools/molcas2pyscf.py | 2 +- 7 files changed, 64 insertions(+), 56 deletions(-) diff --git a/my_pyscf/fci/csfstring.py b/my_pyscf/fci/csfstring.py index 8ed694ae..758164a3 100644 --- a/my_pyscf/fci/csfstring.py +++ b/my_pyscf/fci/csfstring.py @@ -563,7 +563,7 @@ def _transform_det2csf (inparr, norb, neleca, nelecb, smult, reverse=False, csd_ ncol_out = (ncsf_all, ndet_all)[reverse or project] ncol_in = (ncsf_all, ndet_all)[~reverse or project] if not project: - outarr = np.ascontiguousarray (np.zeros ((nrow, ncol_out), dtype=np.float_)) + outarr = np.ascontiguousarray (np.zeros ((nrow, ncol_out), dtype=np.float64)) csf_addrs = np.zeros (ncsf_all, dtype=np.bool_) # Initialization is necessary because not all determinants have a csf for all spin states @@ -874,7 +874,7 @@ def get_spin_evecs (nspin, neleca, nelecb, smult): scstrs = addrs2str (nspin, smult, list (range (ncsf))) assert (len (scstrs) == ncsf), "should have {} coupling strings; have {} (nspin={}, s={})".format (ncsf, len (scstrs), nspin, s) - umat = np.ones ((ndet, ncsf), dtype=np.float_) + umat = np.ones ((ndet, ncsf), dtype=np.float64) twoS = smult-1 twoMS = neleca - nelecb @@ -904,7 +904,7 @@ def test_spin_evecs (nspin, neleca, nelecb, smult, S2mat=None): spinstrs = cistring.addrs2str (nspin, na, list (range (ndet))) if S2mat is None: - S2mat = np.zeros ((ndet, ndet), dtype=np.float_) + S2mat = np.zeros ((ndet, ndet), dtype=np.float64) twoS = smult - 1 twoMS = int (round (2 * ms)) diff --git a/my_pyscf/mcscf/lasci.py b/my_pyscf/mcscf/lasci.py index fd34a74c..2d11b823 100644 --- a/my_pyscf/mcscf/lasci.py +++ b/my_pyscf/mcscf/lasci.py @@ -661,7 +661,7 @@ def state_average_(las, weights=[0.5,0.5], charges=None, spins=None, wfnsyms = np.asarray (wfnsyms) spins = np.asarray (spins) smults = np.asarray (smults) - if np.issubsctype (wfnsyms.dtype, np.str_): + if np.issubdtype (wfnsyms.dtype, np.str_): wfnsyms_str = wfnsyms wfnsyms = np.zeros (wfnsyms_str.shape, dtype=np.int32) for ix, wfnsym in enumerate (wfnsyms_str.flat): diff --git a/my_pyscf/mcscf/lasci_sync.py b/my_pyscf/mcscf/lasci_sync.py index 85d42c94..4ab65fc5 100644 --- a/my_pyscf/mcscf/lasci_sync.py +++ b/my_pyscf/mcscf/lasci_sync.py @@ -271,7 +271,7 @@ def ci_cycle (las, mo, ci0, veff, h2eff_sub, casdm1frs, log): j = ncas_cum[isub+1] orbsym = orbsym[i:j] orbsym_io = orbsym.copy () - if np.issubsctype (orbsym.dtype, np.integer): + if np.issubdtype (orbsym.dtype, np.integer): orbsym_io = np.asarray ([symm.irrep_id2name (las.mol.groupname, x) for x in orbsym]) log.info ("LASCI subspace {} with orbsyms {}".format (isub, orbsym_io)) diff --git a/my_pyscf/mcscf/lasscf_async/combine.py b/my_pyscf/mcscf/lasscf_async/combine.py index 96d5ea91..a868b3de 100644 --- a/my_pyscf/mcscf/lasscf_async/combine.py +++ b/my_pyscf/mcscf/lasscf_async/combine.py @@ -62,16 +62,19 @@ def orth_orb (las, kf2_list): if errmax>1e-8: log.warn ('Non-orthogonal AOs in lasscf_async.combine.orth_orb: %e', errmax) mo1 = mo1[:,ncas:] - veff = sum ([kf2.veff for kf2 in kf2_list]) / nfrags - dm1s = sum ([kf2.dm1s for dm1s in kf2_list]) / nfrags - fock = las.get_hcore ()[None,:,:] + veff - fock = get_roothaan_fock (fock, dm1s, s0) - orbsym = None # TODO: symmetry - fock = mo1.conj ().T @ fock @ mo1 - ene, umat = las._eig (fock, 0, 0, orbsym) - mo_core = mo1 @ umat[:,:ncore] - mo_virt = mo1 @ umat[:,ncore:] - mo_coeff = np.concatenate ([mo_core, mo_cas, mo_virt], axis=1) + if mo1.size: + veff = sum ([kf2.veff for kf2 in kf2_list]) / nfrags + dm1s = sum ([kf2.dm1s for dm1s in kf2_list]) / nfrags + fock = las.get_hcore ()[None,:,:] + veff + fock = get_roothaan_fock (fock, dm1s, s0) + orbsym = None # TODO: symmetry + fock = mo1.conj ().T @ fock @ mo1 + ene, umat = las._eig (fock, 0, 0, orbsym) + mo_core = mo1 @ umat[:,:ncore] + mo_virt = mo1 @ umat[:,ncore:] + mo_coeff = np.concatenate ([mo_core, mo_cas, mo_virt], axis=1) + else: + mo_coeff = mo_cas return las.get_keyframe (mo_coeff, ci) diff --git a/my_pyscf/mcscf/lasscf_async/crunch.py b/my_pyscf/mcscf/lasscf_async/crunch.py index 651ba3f2..8e107c55 100644 --- a/my_pyscf/mcscf/lasscf_async/crunch.py +++ b/my_pyscf/mcscf/lasscf_async/crunch.py @@ -367,20 +367,21 @@ def _push_keyframe (self, kf1, mo_coeff=None, ci=None): kf2.mo_coeff[:,i:j] = mo_self[:,k:l] # Unentangled inactive orbitals + s0 = las._scf.get_ovlp () ncore_unent = las.ncore - self.ncore assert (ncore_unent>=0), '{} {}'.format (las.ncore, self.ncore) - mo_full_core = kf2.mo_coeff[:,:las.ncore] - s0 = las._scf.get_ovlp () - ovlp = mo_full_core.conj ().T @ s0 @ imporb_coeff - proj = ovlp @ ovlp.conj ().T - evals, u = linalg.eigh (-proj) - try: - assert (ncore_unent==0 or np.amax (np.abs (evals[-ncore_unent:]))<1e-4) - except AssertionError as err: - log.warn ("push_keyframe imporb problem: ncore_unent = %d but max |evals[-ncore_unent:]| = %e", - ncore_unent, np.amax (np.abs (evals[-ncore_unent:]))) - if ncore_unent>0: kf2.mo_coeff[:,:ncore_unent] = mo_full_core @ u[:,-ncore_unent:] - kf2.mo_coeff[:,ncore_unent:las.ncore] = mo_self[:,:self.ncore] + if las.ncore: + mo_full_core = kf2.mo_coeff[:,:las.ncore] + ovlp = mo_full_core.conj ().T @ s0 @ imporb_coeff + proj = ovlp @ ovlp.conj ().T + evals, u = linalg.eigh (-proj) + try: + assert (ncore_unent==0 or np.amax (np.abs (evals[-ncore_unent:]))<1e-4) + except AssertionError as err: + log.warn ("push_keyframe imporb problem: ncore_unent = %d but max |evals[-ncore_unent:]| = %e", + ncore_unent, np.amax (np.abs (evals[-ncore_unent:]))) + if ncore_unent>0: kf2.mo_coeff[:,:ncore_unent] = mo_full_core @ u[:,-ncore_unent:] + kf2.mo_coeff[:,ncore_unent:las.ncore] = mo_self[:,:self.ncore] # Canonicalize unentangled inactive orbitals # Be careful not to touch kf2.h2eff_sub or kf2.fock1 until we're done @@ -395,26 +396,27 @@ def _push_keyframe (self, kf1, mo_coeff=None, ci=None): nvirt_full = kf2.mo_coeff.shape[1] - las.ncore - las.ncas nvirt_self = mo_coeff.shape[1] - self.ncore - self.ncas nvirt_unent = nvirt_full - nvirt_self - assert (nvirt_unent>=0), '{} {}'.format (nvirt_full, nvirt_self) - mo_full_virt = kf2.mo_coeff[:,las.ncore+las.ncas:] - ovlp = mo_full_virt.conj ().T @ s0 @ imporb_coeff - proj = ovlp @ ovlp.conj ().T - evals, u = linalg.eigh (-proj) - try: - assert (nvirt_unent==0 or np.amax (np.abs (evals[-nvirt_unent:]))<1e-4) - except AssertionError as err: - log.warn ("push_keyframe imporb problem: nvirt_unent = %d but max |evals[-nvirt_unent:]| = %e", - nvirt_unent, np.amax (np.abs (evals[-nvirt_unent:]))) - if nvirt_unent>0: - kf2.mo_coeff[:,-nvirt_unent:] = mo_full_virt @ u[:,-nvirt_unent:] - kf2.mo_coeff[:,las.ncore+las.ncas:-nvirt_unent] = mo_self[:,self.ncore+self.ncas:] - # Canonicalize unentangled virtual orbitals - mo_a = kf2.mo_coeff[:,-nvirt_unent:] - f0_ab = mo_a.conj ().T @ f0 @ mo_a - w, u = linalg.eigh (f0_ab) - kf2.mo_coeff[:,-nvirt_unent:] = mo_a @ u - else: - kf2.mo_coeff[:,las.ncore+las.ncas:] = mo_self[:,self.ncore+self.ncas:] + if nvirt_full: + assert (nvirt_unent>=0), '{} {}'.format (nvirt_full, nvirt_self) + mo_full_virt = kf2.mo_coeff[:,las.ncore+las.ncas:] + ovlp = mo_full_virt.conj ().T @ s0 @ imporb_coeff + proj = ovlp @ ovlp.conj ().T + evals, u = linalg.eigh (-proj) + try: + assert (nvirt_unent==0 or np.amax (np.abs (evals[-nvirt_unent:]))<1e-4) + except AssertionError as err: + log.warn ("push_keyframe imporb problem: nvirt_unent = %d but max |evals[-nvirt_unent:]| = %e", + nvirt_unent, np.amax (np.abs (evals[-nvirt_unent:]))) + if nvirt_unent>0: + kf2.mo_coeff[:,-nvirt_unent:] = mo_full_virt @ u[:,-nvirt_unent:] + kf2.mo_coeff[:,las.ncore+las.ncas:-nvirt_unent] = mo_self[:,self.ncore+self.ncas:] + # Canonicalize unentangled virtual orbitals + mo_a = kf2.mo_coeff[:,-nvirt_unent:] + f0_ab = mo_a.conj ().T @ f0 @ mo_a + w, u = linalg.eigh (f0_ab) + kf2.mo_coeff[:,-nvirt_unent:] = mo_a @ u + else: + kf2.mo_coeff[:,las.ncore+las.ncas:] = mo_self[:,self.ncore+self.ncas:] return kf2 @@ -483,14 +485,16 @@ def _update_trial_state_(self, mo_coeff, ci, veff, dm1s): # Canonicalize core and virtual spaces fock = las.get_fock (veff=veff, dm1s=dm1s) fock = imporb_coeff.conj ().T @ fock @ imporb_coeff - mo_core = self.mo_coeff[:,:self.ncore] - fock_core = mo_core.conj ().T @ fock @ mo_core - w, c = linalg.eigh (fock_core) - self.mo_coeff[:,:self.ncore] = mo_core @ c - mo_virt = self.mo_coeff[:,nocc:] - fock_virt = mo_virt.conj ().T @ fock @ mo_virt - w, c = linalg.eigh (fock_virt) - self.mo_coeff[:,nocc:] = mo_virt @ c + if self.ncore: + mo_core = self.mo_coeff[:,:self.ncore] + fock_core = mo_core.conj ().T @ fock @ mo_core + w, c = linalg.eigh (fock_core) + self.mo_coeff[:,:self.ncore] = mo_core @ c + if (self.mo_coeff.shape[1] - nocc): + mo_virt = self.mo_coeff[:,nocc:] + fock_virt = mo_virt.conj ().T @ fock @ mo_virt + w, c = linalg.eigh (fock_virt) + self.mo_coeff[:,nocc:] = mo_virt @ c def _update_impurity_hamiltonian_(self, mo_coeff, ci, h2eff_sub=None, e_states=None, veff=None, dm1s=None): '''Update the Hamiltonian data contained within this impurity solver and all encapsulated diff --git a/my_pyscf/mcscf/lasscf_async/split.py b/my_pyscf/mcscf/lasscf_async/split.py index f921c178..2ef92e4b 100644 --- a/my_pyscf/mcscf/lasscf_async/split.py +++ b/my_pyscf/mcscf/lasscf_async/split.py @@ -181,6 +181,7 @@ def _get_orthnorm_frag (self, mo, ovlp_tol=1e-4): idx = np.ones (mo.shape[1], dtype=np.bool_) idx[self.ncore:self.nocc] = False uo = mo[:,idx] + if uo.size==0: return fo, uo s1 = uo.conj ().T @ self.frag_umat u, svals, vh = self.svd (s1, full_matrices=True) diff --git a/my_pyscf/tools/molcas2pyscf.py b/my_pyscf/tools/molcas2pyscf.py index c7420238..c65190fa 100644 --- a/my_pyscf/tools/molcas2pyscf.py +++ b/my_pyscf/tools/molcas2pyscf.py @@ -80,7 +80,7 @@ def get_mo_from_h5 (mol, h5fname, symmetry=None): nmo_irrep = np.asarray (nmo_irrep) usymm_irrep_offset = np.cumsum (nmo_irrep) - nmo_irrep coeff_irrep_offset = np.cumsum (nmo_irrep**2) - nmo_irrep**2 - mo_coeff = np.zeros ((nao, nao), dtype=np.float_) + mo_coeff = np.zeros ((nao, nao), dtype=np.float64) for m_ir, usymm_off, coeff_off in zip (nmo_irrep, usymm_irrep_offset, coeff_irrep_offset): i, j = usymm_off, usymm_off+m_ir u, v = coeff_off, coeff_off+(m_ir**2) From dfa47b6feeca2b5e7d26bb222bebf91a1365a97b Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 20 Jun 2024 12:53:36 -0500 Subject: [PATCH 65/87] interpret np.character dtype In lasscf_guess interpreting frags orbs --- my_pyscf/mcscf/lasscf_guess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/my_pyscf/mcscf/lasscf_guess.py b/my_pyscf/mcscf/lasscf_guess.py index 577d27e6..f2cbff14 100644 --- a/my_pyscf/mcscf/lasscf_guess.py +++ b/my_pyscf/mcscf/lasscf_guess.py @@ -178,7 +178,7 @@ def localize_init_guess (las, frags_atoms, mo_coeff=None, spin=None, lo_coeff=No def interpret_frags_atoms (mol, frags_atoms, frags_by_AOs=False): frags_atoms_int = all ([all ([isinstance (i, (int,np.integer)) for i in j]) for j in frags_atoms]) - frags_atoms_str = all ([all ([isinstance (i, str) for i in j]) for j in frags_atoms]) + frags_atoms_str = all ([all ([isinstance (i, (str,np.character)) for i in j]) for j in frags_atoms]) if frags_atoms_int: if frags_by_AOs: return frags_atoms ao_offset = mol.offset_ao_by_atom () From 7d0660fc8565feadb09a981c7a2937a270ce4bbf Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 21 Jun 2024 13:31:09 -0500 Subject: [PATCH 66/87] PySCF(-forge) version check & update --- pyscf-forge_version.txt | 2 +- pyscf_version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyscf-forge_version.txt b/pyscf-forge_version.txt index 5e77e6b1..9dd197fb 100644 --- a/pyscf-forge_version.txt +++ b/pyscf-forge_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf-forge.git@62e3e9fe0dd03d89d2632b7c1f6b4c9da6f625ee +git+https://github.com/pyscf/pyscf-forge.git@8d764a0868b80fbfa70c1a956eab23ec3fdc8494 diff --git a/pyscf_version.txt b/pyscf_version.txt index 84a214be..1f60d3ed 100644 --- a/pyscf_version.txt +++ b/pyscf_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf.git@d892f8f110f8e783945d17b869094029d54065c6 +git+https://github.com/pyscf/pyscf.git@6512c8b042139ac21355a2657f98535474ddabdc From 26bab549c35db1a4f63f99b903a24e9a5f457222 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 21 Jun 2024 14:29:19 -0500 Subject: [PATCH 67/87] numpy 2 compat --- exploratory/citools/fockspace.py | 2 +- my_pyscf/lassi/citools.py | 8 ++++---- my_pyscf/lassi/lassi.py | 2 +- my_pyscf/lassi/op_o0.py | 2 +- my_pyscf/lassi/sitools.py | 2 +- tests/lassi/test_citools.py | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/exploratory/citools/fockspace.py b/exploratory/citools/fockspace.py index e6ed9fc7..39096d3a 100644 --- a/exploratory/citools/fockspace.py +++ b/exploratory/citools/fockspace.py @@ -78,7 +78,7 @@ def fermion_spin_shuffle (norb, norb_f): nelec_f = np.zeros ((ndet, nfrag), dtype=np.int8) for ifrag, n in enumerate (ndet_f): addrs, fragaddrs = np.divmod (addrs, n) - nelec_f[:,ifrag] = ADDRS_NELEC[fragaddrs] + nelec_f[:,ifrag] = ADDRS_NELEC[fragaddrs.astype (int)] ne_f, ne_f_idx = np.unique (nelec_f, return_inverse=True, axis=0) for (ia, na_f), (ib, nb_f) in product (enumerate (ne_f), repeat=2): idx_a = ne_f_idx == ia diff --git a/my_pyscf/lassi/citools.py b/my_pyscf/lassi/citools.py index d18ab5af..cb38a2b3 100644 --- a/my_pyscf/lassi/citools.py +++ b/my_pyscf/lassi/citools.py @@ -48,7 +48,7 @@ def get_rootaddr_fragaddr (lroots): The ordinal designation local to each fragment of each LAS state. ''' nfrags, nroots = lroots.shape - nprods = np.product (lroots, axis=0) + nprods = np.prod (lroots, axis=0) fragaddr = np.zeros ((nfrags, sum(nprods)), dtype=int) rootaddr = np.zeros (sum(nprods), dtype=int) offs = np.cumsum (nprods) @@ -56,8 +56,8 @@ def get_rootaddr_fragaddr (lroots): j = offs[iroot] i = j - nprods[iroot] for ifrag in range (nfrags): - prods_before = np.product (lroots[:ifrag,iroot], axis=0) - prods_after = np.product (lroots[ifrag+1:,iroot], axis=0) + prods_before = np.prod (lroots[:ifrag,iroot], axis=0) + prods_after = np.prod (lroots[ifrag+1:,iroot], axis=0) addrs = np.repeat (np.arange (lroots[ifrag,iroot]), prods_before) addrs = np.tile (addrs, prods_after) fragaddr[ifrag,i:j] = addrs @@ -88,7 +88,7 @@ def umat_dot_1frag_(target, umat, lroots, ifrag, iroot, axis=0): Returns: target: same as input target After application of unitary transformation''' - nprods = np.product (lroots, axis=0) + nprods = np.prod (lroots, axis=0) offs = [0,] + list (np.cumsum (nprods)) i, j = offs[iroot], offs[iroot+1] newaxes = [axis,] + list (range (axis)) + list (range (axis+1, target.ndim)) diff --git a/my_pyscf/lassi/lassi.py b/my_pyscf/lassi/lassi.py index 26d0df09..8c5f964d 100644 --- a/my_pyscf/lassi/lassi.py +++ b/my_pyscf/lassi/lassi.py @@ -207,7 +207,7 @@ def __exit__(self, type, value, traceback): def iterate_subspace_blocks (las, ci, spacesym, subset=None): if subset is None: subset = set (spacesym) lroots = get_lroots (ci) - nprods_r = np.product (lroots, axis=0) + nprods_r = np.prod (lroots, axis=0) prod_off = np.cumsum (nprods_r) - nprods_r nprods = nprods_r.sum () for sym in subset: diff --git a/my_pyscf/lassi/op_o0.py b/my_pyscf/lassi/op_o0.py index 3ed074e0..65f4cc76 100644 --- a/my_pyscf/lassi/op_o0.py +++ b/my_pyscf/lassi/op_o0.py @@ -21,7 +21,7 @@ def memcheck (las, ci, soc=None): lroots_fr = np.array ([[1 if c.ndim<3 else c.shape[0] for c in ci_r] for ci_r in ci]) - lroots_r = np.product (lroots_fr, axis=0) + lroots_r = np.prod (lroots_fr, axis=0) nelec_frs = np.array ([[list (_unpack_nelec (fcibox._get_nelec (solver, nelecas))) for solver in fcibox.fcisolvers] for fcibox, nelecas in zip (las.fciboxes, las.nelecas_sub)]) diff --git a/my_pyscf/lassi/sitools.py b/my_pyscf/lassi/sitools.py index d4329e28..442e19e0 100644 --- a/my_pyscf/lassi/sitools.py +++ b/my_pyscf/lassi/sitools.py @@ -15,7 +15,7 @@ def decompose_sivec_by_rootspace (las, si, ci=None): if ci is None: ci=las.ci if si.ndim==1: si = si[:,None] lroots = get_lroots (ci) - nstates = np.product (lroots, axis=0) + nstates = np.prod (lroots, axis=0) jj = np.cumsum (nstates) ii = jj - nstates nspaces = las.nroots diff --git a/tests/lassi/test_citools.py b/tests/lassi/test_citools.py index ed59c664..42ed2b24 100644 --- a/tests/lassi/test_citools.py +++ b/tests/lassi/test_citools.py @@ -14,7 +14,7 @@ def tearDownModule(): pass def case_umat_dot_1frag (ks, rng, nroots, nfrags, nvecs, lroots): - nstates = np.product (lroots, axis=0).sum () + nstates = np.prod (lroots, axis=0).sum () if nvecs > nstates: return si = np.empty ((0,0)) for i in range (100): From 3d49535070f60d752abbe19792846ad5ffda27bc Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 21 Jun 2024 16:55:08 -0500 Subject: [PATCH 68/87] np.ix_, indexing, and dtype --- exploratory/citools/fockspace.py | 4 ++-- exploratory/unitary_cc/uccsd_sym1.py | 2 +- my_pyscf/lassi/op_o1.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exploratory/citools/fockspace.py b/exploratory/citools/fockspace.py index 39096d3a..4b60539e 100644 --- a/exploratory/citools/fockspace.py +++ b/exploratory/citools/fockspace.py @@ -81,8 +81,8 @@ def fermion_spin_shuffle (norb, norb_f): nelec_f[:,ifrag] = ADDRS_NELEC[fragaddrs.astype (int)] ne_f, ne_f_idx = np.unique (nelec_f, return_inverse=True, axis=0) for (ia, na_f), (ib, nb_f) in product (enumerate (ne_f), repeat=2): - idx_a = ne_f_idx == ia - idx_b = ne_f_idx == ib + idx_a = np.squeeze (ne_f_idx == ia) + idx_b = np.squeeze (ne_f_idx == ib) idx = np.ix_(idx_a,idx_b) sgn[idx] = _fss (na_f, nb_f) assert (np.count_nonzero (sgn==0) == 0) diff --git a/exploratory/unitary_cc/uccsd_sym1.py b/exploratory/unitary_cc/uccsd_sym1.py index d067da27..c9837f54 100644 --- a/exploratory/unitary_cc/uccsd_sym1.py +++ b/exploratory/unitary_cc/uccsd_sym1.py @@ -46,7 +46,7 @@ def spincases (p_idxs, norb): m = np.array ([0]) for ielec in range (nelec): q_idxs = p_idxs.copy () - q_idxs[:,ielec] += norb + q_idxs[:,ielec] += np.array ([norb], dtype=q_idxs.dtype) p_idxs = np.append (p_idxs, q_idxs, axis=0) m = np.append (m, m+1) p_sorted = np.stack ([np.sort (prow) for prow in p_idxs], axis=0) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index e8c309ca..8815d53b 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -880,7 +880,7 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): eqmap = idx[inv] for uniq_idx in idx: row_uniq = excp[uniq_idx] - braket_images = exc[:,:2][eqmap==uniq_idx] + braket_images = exc[:,:2][(eqmap==uniq_idx)[:,None]] self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) From a831a8e3780f382acd6cd88dcd937b6a7daa1f74 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 11:07:55 -0500 Subject: [PATCH 69/87] diagnose indexerror --- my_pyscf/lassi/op_o1.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 8815d53b..3a2b900e 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -880,7 +880,10 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): eqmap = idx[inv] for uniq_idx in idx: row_uniq = excp[uniq_idx] - braket_images = exc[:,:2][(eqmap==uniq_idx)[:,None]] + try: + braket_images = exc[:,:2][eqmap==uniq_idx] + except IndexError as err: + raise IndexError ('{} {} {} {}'.format (exc.shape, eqmap.shape, uniq_idx.shape, (eqmap==uniq_idx).shape)) self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) From 1bf2ae37a6df7835608b0d6c6c4bea4a824383ea Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 11:36:15 -0500 Subject: [PATCH 70/87] index dimension issue? --- my_pyscf/lassi/op_o1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 3a2b900e..4d0d75b6 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -877,13 +877,13 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): fprint = np.asarray (fprint) nexc = len (exc) _, idx, inv = np.unique (fprint, axis=0, return_index=True, return_inverse=True) - eqmap = idx[inv] + eqmap = np.squeeze (idx[inv]) for uniq_idx in idx: row_uniq = excp[uniq_idx] try: braket_images = exc[:,:2][eqmap==uniq_idx] except IndexError as err: - raise IndexError ('{} {} {} {}'.format (exc.shape, eqmap.shape, uniq_idx.shape, (eqmap==uniq_idx).shape)) + raise IndexError ('{} {} {} {} {}'.format (exc.shape, eqmap.shape, uniq_idx, (eqmap==uniq_idx).shape), np.count_nonzero (eqmap==uniq_idx)) self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) From ec8fd9bf12a0ec47915cbdc62fee125829acf7e8 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 12:19:00 -0500 Subject: [PATCH 71/87] trying to diagnose --- my_pyscf/lassi/op_o1.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 4d0d75b6..5fd8c489 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -877,13 +877,14 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): fprint = np.asarray (fprint) nexc = len (exc) _, idx, inv = np.unique (fprint, axis=0, return_index=True, return_inverse=True) - eqmap = np.squeeze (idx[inv]) + eqmap = idx[inv] for uniq_idx in idx: row_uniq = excp[uniq_idx] try: braket_images = exc[:,:2][eqmap==uniq_idx] except IndexError as err: - raise IndexError ('{} {} {} {} {}'.format (exc.shape, eqmap.shape, uniq_idx, (eqmap==uniq_idx).shape), np.count_nonzero (eqmap==uniq_idx)) + braket_images = exc[:,:2][np.squeeze (eqmap==uniq_idx)] + assert (braket_images.ndim==2) self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) From 8e3751bc111a4f9d666fde10ddfa9834335e9189 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 12:42:21 -0500 Subject: [PATCH 72/87] attempt to make this indexing work based on manual --- my_pyscf/lassi/op_o1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 5fd8c489..4ed26669 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -883,7 +883,7 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): try: braket_images = exc[:,:2][eqmap==uniq_idx] except IndexError as err: - braket_images = exc[:,:2][np.squeeze (eqmap==uniq_idx)] + braket_images = exc[:,:2][np.ix_(np.squeeze (eqmap==uniq_idx),[0,1])] assert (braket_images.ndim==2) self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] From 85f67aaa8fdbb85f00ec9823ea88e8e4a4104aca Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 13:10:28 -0500 Subject: [PATCH 73/87] try using np.tile --- my_pyscf/lassi/op_o1.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 4ed26669..ad825e3e 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -883,8 +883,9 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): try: braket_images = exc[:,:2][eqmap==uniq_idx] except IndexError as err: - braket_images = exc[:,:2][np.ix_(np.squeeze (eqmap==uniq_idx),[0,1])] - assert (braket_images.ndim==2) + uniq_idxs = np.tile (eqmap==uniq_idx, [1,2]) + braket_images = exc[:,:2][uniq_idxs].reshape (-1,2) + assert (braket_images.ndim==2), '{} {}'.format (np.count_nonzero (eqmap==uniq_idx), braket_images.shape) self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) From 6b81e5ff34c7e50a927adcf0914bdb61682a4f76 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 13:11:13 -0500 Subject: [PATCH 74/87] debug workflow --- .github/workflows/test.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.sh b/.github/workflows/test.sh index f4b355fb..ac008be5 100755 --- a/.github/workflows/test.sh +++ b/.github/workflows/test.sh @@ -4,4 +4,6 @@ set -e export PYTHONPATH=${PWD%/*}:$PYTHONPATH cd ./tests -pytest -k 'not _slow and not _dupe' +#pytest -k 'not _slow and not _dupe' +# debug +pytest -k 'c2h4n4 and not _slow and not _dupe' From c9886aacfa5f3f7f39e79f1a58c090a74a9f4f73 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 13:23:05 -0500 Subject: [PATCH 75/87] restore unittests --- .github/workflows/test.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.sh b/.github/workflows/test.sh index ac008be5..f4b355fb 100755 --- a/.github/workflows/test.sh +++ b/.github/workflows/test.sh @@ -4,6 +4,4 @@ set -e export PYTHONPATH=${PWD%/*}:$PYTHONPATH cd ./tests -#pytest -k 'not _slow and not _dupe' -# debug -pytest -k 'c2h4n4 and not _slow and not _dupe' +pytest -k 'not _slow and not _dupe' From 10a657a7087be5b0f87053ca2f325c4bdae76236 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 13:42:01 -0500 Subject: [PATCH 76/87] index as integer --- exploratory/citools/fockspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exploratory/citools/fockspace.py b/exploratory/citools/fockspace.py index 4b60539e..f3c3c916 100644 --- a/exploratory/citools/fockspace.py +++ b/exploratory/citools/fockspace.py @@ -127,7 +127,7 @@ def fermion_frag_shuffle (norb, i, j): # Lower orbital indices change faster than higher ones addrs_p = np.arange (ndet, dtype=np.uint64) // (2**i) addrs_q, addrs_p = np.divmod (addrs_p, 2**(j-i)) - nperms = ADDRS_NELEC[addrs_p] * ADDRS_NELEC[addrs_q] + nperms = ADDRS_NELEC[addrs_p.astype (int)] * ADDRS_NELEC[addrs_q.astype (int)] sgn = np.array ([1,-1], dtype=np.int8)[nperms%2] assert (sgn.size==ndet) return sgn From d2f6cbc1264bf763ec921b7ffd3cb23ea6ced29a Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 13:43:10 -0500 Subject: [PATCH 77/87] try simplify op_o1 update --- my_pyscf/lassi/op_o1.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index ad825e3e..3a99215c 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -880,11 +880,11 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): eqmap = idx[inv] for uniq_idx in idx: row_uniq = excp[uniq_idx] - try: - braket_images = exc[:,:2][eqmap==uniq_idx] - except IndexError as err: - uniq_idxs = np.tile (eqmap==uniq_idx, [1,2]) - braket_images = exc[:,:2][uniq_idxs].reshape (-1,2) + #try: + # braket_images = exc[:,:2][eqmap==uniq_idx] + #except IndexError as err: + uniq_idxs = np.tile (eqmap==uniq_idx, [1,2]) + braket_images = exc[:,:2][uniq_idxs].reshape (-1,2) assert (braket_images.ndim==2), '{} {}'.format (np.count_nonzero (eqmap==uniq_idx), braket_images.shape) self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] From 7e0509ec6eacf0e9770589573f3b42ed7d3a03ec Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 13:59:16 -0500 Subject: [PATCH 78/87] cleanup debugging --- my_pyscf/lassi/op_o1.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index 3a99215c..d1f39cc4 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -880,12 +880,8 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): eqmap = idx[inv] for uniq_idx in idx: row_uniq = excp[uniq_idx] - #try: - # braket_images = exc[:,:2][eqmap==uniq_idx] - #except IndexError as err: uniq_idxs = np.tile (eqmap==uniq_idx, [1,2]) braket_images = exc[:,:2][uniq_idxs].reshape (-1,2) - assert (braket_images.ndim==2), '{} {}'.format (np.count_nonzero (eqmap==uniq_idx), braket_images.shape) self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) From 1f6e5b23bf938926364314a8ba24dfa9306e255a Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 14:16:52 -0500 Subject: [PATCH 79/87] please work i'm so tired of this --- .github/workflows/test.sh | 3 ++- my_pyscf/lassi/op_o1.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.sh b/.github/workflows/test.sh index f4b355fb..0209fb1b 100755 --- a/.github/workflows/test.sh +++ b/.github/workflows/test.sh @@ -4,4 +4,5 @@ set -e export PYTHONPATH=${PWD%/*}:$PYTHONPATH cd ./tests -pytest -k 'not _slow and not _dupe' +#pytest -k 'not _slow and not _dupe' +pytest -k 'c2h4n4 and not _slow and not _dupe' diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index d1f39cc4..b753a042 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -880,7 +880,10 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): eqmap = idx[inv] for uniq_idx in idx: row_uniq = excp[uniq_idx] - uniq_idxs = np.tile (eqmap==uniq_idx, [1,2]) + # crazy numpy v1 vs v2 dimensionality issue here + uniq_idxs = eqmap==uniq_idx + if uniq_idxs.ndim==1: uniq_idxs = uniq_idxs[:,None] + uniq_idxs = np.tile (uniq_idxs, [1,2]) braket_images = exc[:,:2][uniq_idxs].reshape (-1,2) self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] From 7a4203b2858f0039163a35be94c0d2b6674d3d55 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 14:21:33 -0500 Subject: [PATCH 80/87] experiment with np.where --- my_pyscf/lassi/op_o1.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index b753a042..c9a264b1 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -877,14 +877,12 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): fprint = np.asarray (fprint) nexc = len (exc) _, idx, inv = np.unique (fprint, axis=0, return_index=True, return_inverse=True) - eqmap = idx[inv] + eqmap = np.squeeze (idx[inv]) for uniq_idx in idx: row_uniq = excp[uniq_idx] # crazy numpy v1 vs v2 dimensionality issue here - uniq_idxs = eqmap==uniq_idx - if uniq_idxs.ndim==1: uniq_idxs = uniq_idxs[:,None] - uniq_idxs = np.tile (uniq_idxs, [1,2]) - braket_images = exc[:,:2][uniq_idxs].reshape (-1,2) + uniq_idxs = np.where (eqmap==uniq_idx)[0] + braket_images = exc[np.ix_(uniq_idxs,[0,2])] self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) From 80d21e1cd7989faceedb053ecf96111f212312e0 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 14:22:16 -0500 Subject: [PATCH 81/87] restore all unittests again --- .github/workflows/test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.sh b/.github/workflows/test.sh index 0209fb1b..f4b355fb 100755 --- a/.github/workflows/test.sh +++ b/.github/workflows/test.sh @@ -4,5 +4,4 @@ set -e export PYTHONPATH=${PWD%/*}:$PYTHONPATH cd ./tests -#pytest -k 'not _slow and not _dupe' -pytest -k 'c2h4n4 and not _slow and not _dupe' +pytest -k 'not _slow and not _dupe' From b3734c9db176e1e995a0154dadf8b2b3c2468933 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Mon, 24 Jun 2024 14:23:47 -0500 Subject: [PATCH 82/87] wrong axes --- my_pyscf/lassi/op_o1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/my_pyscf/lassi/op_o1.py b/my_pyscf/lassi/op_o1.py index c9a264b1..b057ca0e 100644 --- a/my_pyscf/lassi/op_o1.py +++ b/my_pyscf/lassi/op_o1.py @@ -882,7 +882,7 @@ def mask_exc_table_(self, exc, lbl, mask_bra_space=None, mask_ket_space=None): row_uniq = excp[uniq_idx] # crazy numpy v1 vs v2 dimensionality issue here uniq_idxs = np.where (eqmap==uniq_idx)[0] - braket_images = exc[np.ix_(uniq_idxs,[0,2])] + braket_images = exc[np.ix_(uniq_idxs,[0,1])] self.nonuniq_exc[tuple(row_uniq)] = braket_images exc = exc[idx] nuniq = len (idx) From 0e2347c8b14988cadfddcb17c36b8ad0c1d6a31f Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Thu, 27 Jun 2024 13:07:59 -0500 Subject: [PATCH 83/87] compat check --- pyscf_version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscf_version.txt b/pyscf_version.txt index 1f60d3ed..bd0fe629 100644 --- a/pyscf_version.txt +++ b/pyscf_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf.git@6512c8b042139ac21355a2657f98535474ddabdc +git+https://github.com/pyscf/pyscf.git@d488cb7552130481407dbf698a9231459c21f291 From e7a24e40ef44907535608e79f4cbb2c44ef1f380 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 28 Jun 2024 14:59:49 -0500 Subject: [PATCH 84/87] lasscf_async keyframe comparison fns orbital_block_svd and count_common_orbitals --- my_pyscf/mcscf/lasscf_async/keyframe.py | 123 +++++++++++++++++++++--- 1 file changed, 108 insertions(+), 15 deletions(-) diff --git a/my_pyscf/mcscf/lasscf_async/keyframe.py b/my_pyscf/mcscf/lasscf_async/keyframe.py index e2ca8684..73548687 100644 --- a/my_pyscf/mcscf/lasscf_async/keyframe.py +++ b/my_pyscf/mcscf/lasscf_async/keyframe.py @@ -1,4 +1,5 @@ import numpy as np +from pyscf.lib import logger from scipy import linalg class LASKeyframe (object): @@ -75,6 +76,42 @@ def approx_keyframe_ovlp (las, kf1, kf2): if mo_ovlp deviates significantly from 1. ''' + u, svals, vh = orbital_block_svd (las, kf1, kf2) + mo_ovlp = np.prod (svals) + + ci_ovlp = [] + for ifrag, (fcibox, c1_r, c2_r) in enumerate (zip (las.fciboxes, kf1.ci, kf2.ci)): + nlas, nelelas = las.ncas_sub[ifrag], las.nelecas_sub[ifrag] + i = las.ncore + sum (las.ncas_sub[:ifrag]) + j = i + las.ncas_sub[ifrag] + umat = u[i:j,i:j] @ vh[i:j,i:j] + c1_r = fcibox.states_transform_ci_for_orbital_rotation (c1_r, nlas, nelelas, umat) + ci_ovlp.append ([abs (c1.conj ().ravel ().dot (c2.ravel ())) + for c1, c2 in zip (c1_r, c2_r)]) + + return mo_ovlp, ci_ovlp + +def orbital_block_svd (las, kf1, kf2): + '''Evaluate the block-SVD of the orbitals of two keyframes. Blocks are inactive (core), active + of each fragment, and virtual. + + Args: + las : object of :class:`LASCINoSymm` + kf1 : object of :class:`LASKeyframe` + kf2 : object of :class:`LASKeyframe` + + Returns: + u : array of shape (nao,nmo) + Block-diagonal unitary matrix of orbital rotations for kf1, keeping each subspace + unchanged but aligning the orbitals to identify the spaces the two keyframes have in + common, if any + svals : array of shape (nmo) + Singular values. + vh: array of shape (nmo,nao) + Transpose of block-diagonal unitary matrix of orbital rotations for kf2, keeping each + subspace unchanged but aligning the orbitals to identify the spaces the two keyframes + have in common, if any + ''' nao, nmo = kf1.mo_coeff.shape ncore, ncas = las.ncore, las.ncas nocc = ncore + ncas @@ -84,15 +121,11 @@ def approx_keyframe_ovlp (las, kf1, kf2): mo1 = kf1.mo_coeff[:,:ncore] mo2 = kf2.mo_coeff[:,:ncore] s1 = mo1.conj ().T @ s0 @ mo2 - u, svals, vh = linalg.svd (s1) - mo_ovlp = np.prod (svals) # inactive orbitals - mo1 = kf1.mo_coeff[:,nocc:] - mo2 = kf2.mo_coeff[:,nocc:] - s1 = mo1.conj ().T @ s0 @ mo2 - u, svals, vh = linalg.svd (s1) - mo_ovlp *= np.prod (svals) # virtual orbitals + u_core, svals_core, vh_core = linalg.svd (s1) - ci_ovlp = [] + u = [u_core,] + svals = [svals_core,] + vh = [vh_core,] for ifrag, (fcibox, c1_r, c2_r) in enumerate (zip (las.fciboxes, kf1.ci, kf2.ci)): nlas, nelelas = las.ncas_sub[ifrag], las.nelecas_sub[ifrag] i = ncore + sum (las.ncas_sub[:ifrag]) @@ -100,12 +133,72 @@ def approx_keyframe_ovlp (las, kf1, kf2): mo1 = kf1.mo_coeff[:,i:j] mo2 = kf2.mo_coeff[:,i:j] s1 = mo1.conj ().T @ s0 @ mo2 - u, svals, vh = linalg.svd (s1) - mo_ovlp *= np.prod (svals) # ifrag active orbitals - c1_r = fcibox.states_transform_ci_for_orbital_rotation (c1_r, nlas, nelelas, u @ vh) - ci_ovlp.append ([abs (c1.conj ().ravel ().dot (c2.ravel ())) - for c1, c2 in zip (c1_r, c2_r)]) + u_i, svals_i, vh_i = linalg.svd (s1) + u.append (u_i) + svals.append (svals_i) + vh.append (vh_i) + + mo1 = kf1.mo_coeff[:,nocc:] + mo2 = kf2.mo_coeff[:,nocc:] + s1 = mo1.conj ().T @ s0 @ mo2 + u_virt, svals_virt, vh_virt = linalg.svd (s1) + u.append (u_virt) + svals.append (svals_virt) + vh.append (vh_virt) + + u = linalg.block_diag (*u) + svals = np.concatenate (svals) + vh = linalg.block_diag (*vh) + + return u, svals, vh + +def count_common_orbitals (las, kf1, kf2, verbose=None): + '''Evaluate how many orbitals in each subspace two keyframes have in common + + Args: + las : object of :class:`LASCINoSymm` + kf1 : object of :class:`LASKeyframe` + kf2 : object of :class:`LASKeyframe` + + Kwargs: + verbose: integer or None + + Returns: + ncommon_core : int + ncommon_active : list of length nfrags + ncommon_virt : int + ''' + if verbose is None: verbose=las.verbose + ncore, ncas = las.ncore, las.ncas + nocc = ncore + ncas + nvirt = nmo - nocc + log = logger.new_logger (las, verbose) + + u, svals, vh = orbital_block_svd (las, kf1, kf2) + + fmt_str = '{:s} orbitals: {:d}/{:d} in common' + def _count (lbl, i, j): + ncommon = np.count_nonzero (np.isclose (svals[i:j], 1)) + log.info (fmt_string.format (lbl, ncommon, j-i)) + return ncommon + + ncommon_core = _count ('Inactive', 0, ncore) + ncommon_active = [] + j_list = np.cumsum (las.ncas_sub) + ncore + i_list = j_list - np.asarray (las.ncas_sub) + for ifrag, (i, j) in enumerate (zip (i_list, j_list)): + lbl = 'Active {:d}'.format (ifrag) + ncommon_active.append (_count (lbl, i, j)) + ncommon_virt = _count ('Virtual', nocc, nmo) + + return ncommon_core, ncommon_active, ncommon_virt + + + + + + + + - return mo_ovlp, ci_ovlp - From 271d1f22899ee49170169eb850547149779f0eda Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Fri, 28 Jun 2024 15:57:20 -0500 Subject: [PATCH 85/87] lasscf_async keyframe comparisons printout --- my_pyscf/mcscf/lasscf_async/keyframe.py | 3 ++- my_pyscf/mcscf/lasscf_async/lasscf_async.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/my_pyscf/mcscf/lasscf_async/keyframe.py b/my_pyscf/mcscf/lasscf_async/keyframe.py index 73548687..b2fb4fa0 100644 --- a/my_pyscf/mcscf/lasscf_async/keyframe.py +++ b/my_pyscf/mcscf/lasscf_async/keyframe.py @@ -169,6 +169,7 @@ def count_common_orbitals (las, kf1, kf2, verbose=None): ncommon_virt : int ''' if verbose is None: verbose=las.verbose + nao, nmo = kf1.mo_coeff.shape ncore, ncas = las.ncore, las.ncas nocc = ncore + ncas nvirt = nmo - nocc @@ -179,7 +180,7 @@ def count_common_orbitals (las, kf1, kf2, verbose=None): fmt_str = '{:s} orbitals: {:d}/{:d} in common' def _count (lbl, i, j): ncommon = np.count_nonzero (np.isclose (svals[i:j], 1)) - log.info (fmt_string.format (lbl, ncommon, j-i)) + log.info (fmt_str.format (lbl, ncommon, j-i)) return ncommon ncommon_core = _count ('Inactive', 0, ncore) diff --git a/my_pyscf/mcscf/lasscf_async/lasscf_async.py b/my_pyscf/mcscf/lasscf_async/lasscf_async.py index 76ec8696..d69e194e 100644 --- a/my_pyscf/mcscf/lasscf_async/lasscf_async.py +++ b/my_pyscf/mcscf/lasscf_async/lasscf_async.py @@ -1,12 +1,13 @@ +import itertools import numpy as np from scipy import linalg from pyscf import lib from pyscf.mcscf import mc1step from mrh.my_pyscf.mcscf import lasci, lasscf_sync_o0 from mrh.my_pyscf.mcscf.lasscf_guess import interpret_frags_atoms +from mrh.my_pyscf.mcscf.lasscf_async import keyframe from mrh.my_pyscf.mcscf.lasscf_async.split import get_impurity_space_constructor from mrh.my_pyscf.mcscf.lasscf_async.crunch import get_impurity_casscf -from mrh.my_pyscf.mcscf.lasscf_async.keyframe import LASKeyframe from mrh.my_pyscf.mcscf.lasscf_async.combine import combine_o0 def kernel (las, mo_coeff=None, ci0=None, conv_tol_grad=1e-4, @@ -56,6 +57,16 @@ def kernel (las, mo_coeff=None, ci0=None, conv_tol_grad=1e-4, impurity.kernel () kf2_list.append (impurity._push_keyframe (kf1)) + # EXPERIMENTAL: examining differences in keyframes + for i in range (len (kf2_list)): + kfi = kf2_list[i] + log.info ('Comparing reference keyframe to fragment %d', i) + keyframe.count_common_orbitals (las, kf1, kfi) + for i, j in itertools.combinations (range (len (kf2_list)), 2): + kfi, kfj = kf2_list[i], kf2_list[j] + log.info ('Comparing keyframes for fragments %d and %d:', i, j) + keyframe.count_common_orbitals (las, kfi, kfj) + # 3. Combine from fragments. TODO: smaller chunks instead of one whole-molecule function kf1 = combine_o0 (las, kf2_list) @@ -140,7 +151,7 @@ class LASSCFNoSymm (lasci.LASCINoSymm): def get_keyframe (self, mo_coeff=None, ci=None): if mo_coeff is None: mo_coeff=self.mo_coeff if ci is None: ci=self.ci - return LASKeyframe (self, mo_coeff, ci) + return keyframe.LASKeyframe (self, mo_coeff, ci) as_scanner = mc1step.as_scanner def set_fragments_(self, frags_atoms=None, mo_coeff=None, localize_init_guess=True, frags_by_AOs=False, **kwargs): From cc8f642ed164134f02828f4c86182e39ed086e14 Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 9 Jul 2024 11:27:56 -0500 Subject: [PATCH 86/87] issue #105 debug oversight compare eris correctly --- my_pyscf/mcscf/las_ao2mo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/my_pyscf/mcscf/las_ao2mo.py b/my_pyscf/mcscf/las_ao2mo.py index 6293c357..4855f251 100644 --- a/my_pyscf/mcscf/las_ao2mo.py +++ b/my_pyscf/mcscf/las_ao2mo.py @@ -56,7 +56,9 @@ def get_h2eff_df (las, mo_coeff): if mem_enough_int: eri = lib.tag_array (eri, bmPu=np.concatenate (bmuP, axis=-1).transpose (0,2,1)) if las.verbose > lib.logger.DEBUG: - eri_comp = las.with_df.ao2mo (mo, compact=True) + eri_comp = las.with_df.ao2mo (mo_coeff, compact=True) + eri_comp = eri_comp[:,ncore:nocc,ncore:nocc,ncore:nocc] + eri_comp = lib.pack_tril (eri_comp.reshape (nmo*ncas, ncas, ncas)).reshape (nmo, -1) lib.logger.debug(las,"CDERI two-step error: {}".format(linalg.norm(eri-eri_comp))) return eri From 97b913b41422dfbc8142f46e45f32b8328668f2f Mon Sep 17 00:00:00 2001 From: Matthew R Hermes Date: Tue, 9 Jul 2024 16:36:18 -0500 Subject: [PATCH 87/87] pyscf compatibility check --- pyscf_version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscf_version.txt b/pyscf_version.txt index bd0fe629..d45effe2 100644 --- a/pyscf_version.txt +++ b/pyscf_version.txt @@ -1 +1 @@ -git+https://github.com/pyscf/pyscf.git@d488cb7552130481407dbf698a9231459c21f291 +git+https://github.com/pyscf/pyscf.git@beb7b1bcb40dec578392322d20126826f2d3e6ad