From 3a4475416cd04e69680345d2951755d942a5dd8b Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Mon, 25 Nov 2024 20:34:15 -0500 Subject: [PATCH 01/43] introduced quemb namespace --- README.md | 8 ++++---- docs/source/usage.rst | 4 ++-- example/kbe_polyacetylene.py | 2 +- example/molbe_dmrg_block2.py | 2 +- example/molbe_h8_chemical_potential.py | 2 +- example/molbe_h8_density_matching.py | 2 +- example/molbe_hexene_oneshot_uccsd.py | 2 +- example/molbe_io_fcidump.py | 4 ++-- example/molbe_octane.py | 2 +- example/molbe_octane_get_rdms.py | 2 +- example/molbe_oneshot_rbe_hcore.py | 2 +- example/molbe_oneshot_rbe_qmmm-fromchk.py | 2 +- example/molbe_oneshot_ube_qmmm.py | 2 +- example/molbe_ppp.py | 2 +- src/{molbe/external => }/__init__.py | 0 src/kbe/__init__.py | 4 ---- src/molbe/__init__.py | 5 ----- src/quemb/__init__.py | 3 +++ src/quemb/kbe/__init__.py | 4 ++++ src/{ => quemb}/kbe/_opt.py | 4 ++-- src/{ => quemb}/kbe/autofrag.py | 4 ++-- src/{ => quemb}/kbe/chain.py | 0 src/{ => quemb}/kbe/fragment.py | 6 +++--- src/{ => quemb}/kbe/helper.py | 2 +- src/{ => quemb}/kbe/lo.py | 6 +++--- src/{ => quemb}/kbe/lo_k.py | 2 +- src/{ => quemb}/kbe/misc.py | 0 src/{ => quemb}/kbe/pbe.py | 12 ++++++------ src/{ => quemb}/kbe/pfrag.py | 8 ++++---- src/{ => quemb}/kbe/solver.py | 2 +- src/{ => quemb}/molbe/.DS_Store | Bin src/quemb/molbe/__init__.py | 5 +++++ src/{ => quemb}/molbe/_opt.py | 8 ++++---- src/{ => quemb}/molbe/autofrag.py | 2 +- src/{ => quemb}/molbe/be_parallel.py | 14 ++++++++++---- src/{ => quemb}/molbe/eri_onthefly.py | 2 +- src/quemb/molbe/external/__init__.py | 0 src/{ => quemb}/molbe/external/ccsd_rdm.py | 0 src/{ => quemb}/molbe/external/cphf_utils.py | 0 src/{ => quemb}/molbe/external/cpmp2_utils.py | 4 ++-- src/{ => quemb}/molbe/external/jac_utils.py | 4 ++-- src/{ => quemb}/molbe/external/lo_helper.py | 0 src/{ => quemb}/molbe/external/optqn.py | 10 +++++----- .../molbe/external/restore_eri_addition.c | 0 src/{ => quemb}/molbe/external/uccsd_eri.py | 0 .../molbe/external/unrestricted_utils.py | 0 src/{ => quemb}/molbe/fragment.py | 6 +++--- src/{ => quemb}/molbe/helper.py | 0 src/{ => quemb}/molbe/lchain.py | 0 src/{ => quemb}/molbe/lo.py | 4 ++-- src/{ => quemb}/molbe/mbe.py | 18 +++++++++--------- src/{ => quemb}/molbe/misc.py | 6 +++--- src/{ => quemb}/molbe/pfrag.py | 4 ++-- src/{ => quemb}/molbe/rdm.py | 0 src/{ => quemb}/molbe/solver.py | 10 +++++----- src/{ => quemb}/molbe/ube.py | 12 ++++++------ src/quemb/shared/__init__.py | 0 src/{molbe => quemb/shared}/be_var.py | 0 tests/chem_dm_kBE_test.py | 2 +- tests/chempot_molBE_test.py | 2 +- tests/dm_molBE_test.py | 2 +- tests/dmrg_molBE_test.py | 2 +- tests/eri_onthefly_test.py | 2 +- tests/hf-in-hf_BE_test.py | 2 +- tests/kbe_polyacetylene_test.py | 2 +- tests/molbe_h8_test.py | 2 +- tests/molbe_io_fcidump_test.py | 4 ++-- tests/molbe_octane_get_rdms_test.py | 2 +- tests/molbe_octane_test.py | 2 +- tests/molbe_oneshot_rbe_hcore_test.py | 2 +- tests/molbe_oneshot_rbe_qmmm-fromchk_test.py | 2 +- tests/ube-oneshot_test.py | 2 +- 72 files changed, 124 insertions(+), 115 deletions(-) rename src/{molbe/external => }/__init__.py (100%) delete mode 100644 src/kbe/__init__.py delete mode 100644 src/molbe/__init__.py create mode 100644 src/quemb/__init__.py create mode 100644 src/quemb/kbe/__init__.py rename src/{ => quemb}/kbe/_opt.py (97%) rename src/{ => quemb}/kbe/autofrag.py (99%) rename src/{ => quemb}/kbe/chain.py (100%) rename src/{ => quemb}/kbe/fragment.py (97%) rename src/{ => quemb}/kbe/helper.py (97%) rename src/{ => quemb}/kbe/lo.py (99%) rename src/{ => quemb}/kbe/lo_k.py (99%) rename src/{ => quemb}/kbe/misc.py (100%) rename src/{ => quemb}/kbe/pbe.py (98%) rename src/{ => quemb}/kbe/pfrag.py (98%) rename src/{ => quemb}/kbe/solver.py (97%) rename src/{ => quemb}/molbe/.DS_Store (100%) create mode 100644 src/quemb/molbe/__init__.py rename src/{ => quemb}/molbe/_opt.py (98%) rename src/{ => quemb}/molbe/autofrag.py (99%) rename src/{ => quemb}/molbe/be_parallel.py (98%) rename src/{ => quemb}/molbe/eri_onthefly.py (99%) create mode 100644 src/quemb/molbe/external/__init__.py rename src/{ => quemb}/molbe/external/ccsd_rdm.py (100%) rename src/{ => quemb}/molbe/external/cphf_utils.py (100%) rename src/{ => quemb}/molbe/external/cpmp2_utils.py (98%) rename src/{ => quemb}/molbe/external/jac_utils.py (97%) rename src/{ => quemb}/molbe/external/lo_helper.py (100%) rename src/{ => quemb}/molbe/external/optqn.py (98%) rename src/{ => quemb}/molbe/external/restore_eri_addition.c (100%) rename src/{ => quemb}/molbe/external/uccsd_eri.py (100%) rename src/{ => quemb}/molbe/external/unrestricted_utils.py (100%) rename src/{ => quemb}/molbe/fragment.py (98%) rename src/{ => quemb}/molbe/helper.py (100%) rename src/{ => quemb}/molbe/lchain.py (100%) rename src/{ => quemb}/molbe/lo.py (99%) rename src/{ => quemb}/molbe/mbe.py (97%) rename src/{ => quemb}/molbe/misc.py (99%) rename src/{ => quemb}/molbe/pfrag.py (99%) rename src/{ => quemb}/molbe/rdm.py (100%) rename src/{ => quemb}/molbe/solver.py (99%) rename src/{ => quemb}/molbe/ube.py (98%) create mode 100644 src/quemb/shared/__init__.py rename src/{molbe => quemb/shared}/be_var.py (100%) diff --git a/README.md b/README.md index b903f99f..5908b31c 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,12 @@ processors. ```bash # Molecular -from molbe import fragpart -from molbe import BE +from quemb.molbe import fragpart +from quemb.molbe import BE # Periodic -#from kbe import fragpart -#from kbe import BE +#from quemb.kbe import fragpart +#from quemb.kbe import BE # Perform pyscf HF/KHF calculations # get mol: pyscf.gto.M or pyscf.pbc.gto.Cell diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 4237bee0..04fdf439 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -11,7 +11,7 @@ QuEmb requires molecular & Hartree-Fock calculation objects from pyscf. Refer to Simple example of BE calculation on molecular system:: - from molbe import fragpart, BE + from quemb.molbe import fragpart, BE # Perform pyscf calculations to get mol, mf objects # See quemb/example/molbe_h8_density_matching.py @@ -30,7 +30,7 @@ Simple example of BE calculation on molecular system:: Simple example of periodic BE calculation on 1D periodic system:: - from kbe import fragpart, BE + from quemb.kbe import fragpart, BE # Perform pyscf pbc calculations to get cell, kmf, kpts # See quemb/example/kbe_polyacetylene.py diff --git a/example/kbe_polyacetylene.py b/example/kbe_polyacetylene.py index fe350e38..1ce7dbb2 100644 --- a/example/kbe_polyacetylene.py +++ b/example/kbe_polyacetylene.py @@ -5,7 +5,7 @@ import numpy from pyscf.pbc import df, gto, scf -from kbe import BE, fragpart +from quemb.kbe import BE, fragpart kpt = [1, 1, 3] cell = gto.Cell() diff --git a/example/molbe_dmrg_block2.py b/example/molbe_dmrg_block2.py index 8e6a6df9..8660ead2 100644 --- a/example/molbe_dmrg_block2.py +++ b/example/molbe_dmrg_block2.py @@ -8,7 +8,7 @@ import numpy from pyscf import cc, fci, gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart # We'll consider the dissociation curve for a 1D chain of 8 H-atoms: num_points = 3 diff --git a/example/molbe_h8_chemical_potential.py b/example/molbe_h8_chemical_potential.py index 638995e9..456f8016 100644 --- a/example/molbe_h8_chemical_potential.py +++ b/example/molbe_h8_chemical_potential.py @@ -3,7 +3,7 @@ from pyscf import fci, gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart # PySCF HF generated mol & mf (molecular desciption & HF object) mol = gto.M( diff --git a/example/molbe_h8_density_matching.py b/example/molbe_h8_density_matching.py index 69b7c9fb..a14414b7 100644 --- a/example/molbe_h8_density_matching.py +++ b/example/molbe_h8_density_matching.py @@ -3,7 +3,7 @@ from pyscf import fci, gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart # PySCF HF generated mol & mf (molecular desciption & HF object) mol = gto.M( diff --git a/example/molbe_hexene_oneshot_uccsd.py b/example/molbe_hexene_oneshot_uccsd.py index 17497abc..70e82c02 100644 --- a/example/molbe_hexene_oneshot_uccsd.py +++ b/example/molbe_hexene_oneshot_uccsd.py @@ -4,7 +4,7 @@ from pyscf import gto, scf -from molbe import UBE, fragpart +from quemb.molbe import UBE, fragpart # Set up scratch directory settings # be_var.SCRATCH='{scratch location}' diff --git a/example/molbe_io_fcidump.py b/example/molbe_io_fcidump.py index 312b83b5..3c774d54 100644 --- a/example/molbe_io_fcidump.py +++ b/example/molbe_io_fcidump.py @@ -1,8 +1,8 @@ # Illustrates how fcidump file containing fragment hamiltonian # can be generated using be2fcidump -from molbe import BE, be_var, fragpart -from molbe.misc import be2fcidump, libint2pyscf +from quemb.molbe import BE, be_var, fragpart +from quemb.molbe.misc import be2fcidump, libint2pyscf be_var.PRINT_LEVEL = 3 diff --git a/example/molbe_octane.py b/example/molbe_octane.py index ebb26284..eef4cf5b 100644 --- a/example/molbe_octane.py +++ b/example/molbe_octane.py @@ -2,7 +2,7 @@ from pyscf import cc, gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart # Perform pyscf HF calculation to get mol & mf objects mol = gto.M( diff --git a/example/molbe_octane_get_rdms.py b/example/molbe_octane_get_rdms.py index d7b54b1e..e1466c31 100644 --- a/example/molbe_octane_get_rdms.py +++ b/example/molbe_octane_get_rdms.py @@ -2,7 +2,7 @@ from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart # Perform pyscf HF calculation to get mol & mf objects mol = gto.M( diff --git a/example/molbe_oneshot_rbe_hcore.py b/example/molbe_oneshot_rbe_hcore.py index 6653d441..a80f2a01 100644 --- a/example/molbe_oneshot_rbe_hcore.py +++ b/example/molbe_oneshot_rbe_hcore.py @@ -7,7 +7,7 @@ import numpy from pyscf import gto, qmmm, scf -from molbe.misc import be2puffin +from quemb.molbe.misc import be2puffin # variables for scratch handling # pbe_var.SCRATCH = '{}' diff --git a/example/molbe_oneshot_rbe_qmmm-fromchk.py b/example/molbe_oneshot_rbe_qmmm-fromchk.py index 08fe9752..4a2536a8 100644 --- a/example/molbe_oneshot_rbe_qmmm-fromchk.py +++ b/example/molbe_oneshot_rbe_qmmm-fromchk.py @@ -2,7 +2,7 @@ # using the be2puffin functionality, starting from a checkfile. # Returns BE CCSD energy for the system -from molbe.misc import be2puffin +from quemb.molbe.misc import be2puffin # variables for scratch handling # pbe_var.SCRATCH = '{}' diff --git a/example/molbe_oneshot_ube_qmmm.py b/example/molbe_oneshot_ube_qmmm.py index 63aa4dc3..ff7835f7 100644 --- a/example/molbe_oneshot_ube_qmmm.py +++ b/example/molbe_oneshot_ube_qmmm.py @@ -2,7 +2,7 @@ # using the be2puffin functionality. # Returns UBE UCCSD energy for the system -from molbe.misc import be2puffin +from quemb.molbe.misc import be2puffin # variables for scratch handling # pbe_var.SCRATCH = '{}' diff --git a/example/molbe_ppp.py b/example/molbe_ppp.py index 97c3bec3..0e129127 100644 --- a/example/molbe_ppp.py +++ b/example/molbe_ppp.py @@ -2,7 +2,7 @@ from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart # Perform pyscf HF calculation to get mol & mf objects mol = gto.M( diff --git a/src/molbe/external/__init__.py b/src/__init__.py similarity index 100% rename from src/molbe/external/__init__.py rename to src/__init__.py diff --git a/src/kbe/__init__.py b/src/kbe/__init__.py deleted file mode 100644 index 186920fd..00000000 --- a/src/kbe/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from kbe.fragment import fragpart -from kbe.pbe import BE - -__all__ = ["fragpart", "BE"] diff --git a/src/molbe/__init__.py b/src/molbe/__init__.py deleted file mode 100644 index b5daa904..00000000 --- a/src/molbe/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from molbe.fragment import fragpart -from molbe.mbe import BE -from molbe.ube import UBE - -__all__ = ["fragpart", "BE", "UBE"] diff --git a/src/quemb/__init__.py b/src/quemb/__init__.py new file mode 100644 index 00000000..158875b3 --- /dev/null +++ b/src/quemb/__init__.py @@ -0,0 +1,3 @@ +import quemb.kbe +import quemb.molbe +import quemb.shared diff --git a/src/quemb/kbe/__init__.py b/src/quemb/kbe/__init__.py new file mode 100644 index 00000000..e1e48e92 --- /dev/null +++ b/src/quemb/kbe/__init__.py @@ -0,0 +1,4 @@ +from quemb.kbe.fragment import fragpart +from quemb.kbe.pbe import BE + +__all__ = ["fragpart", "BE"] diff --git a/src/kbe/_opt.py b/src/quemb/kbe/_opt.py similarity index 97% rename from src/kbe/_opt.py rename to src/quemb/kbe/_opt.py index 2b622d94..645f05a4 100644 --- a/src/kbe/_opt.py +++ b/src/quemb/kbe/_opt.py @@ -2,8 +2,8 @@ import sys -from kbe.misc import print_energy -from molbe._opt import BEOPT +from quemb.kbe.misc import print_energy +from quemb.molbe._opt import BEOPT def optimize( diff --git a/src/kbe/autofrag.py b/src/quemb/kbe/autofrag.py similarity index 99% rename from src/kbe/autofrag.py rename to src/quemb/kbe/autofrag.py index 7fe8e289..6a1f2373 100644 --- a/src/kbe/autofrag.py +++ b/src/quemb/kbe/autofrag.py @@ -5,8 +5,8 @@ import numpy from pyscf import lib -from kbe.misc import sgeom -from molbe.helper import get_core +from quemb.kbe.misc import sgeom +from quemb.molbe.helper import get_core def warn_large_fragment(): diff --git a/src/kbe/chain.py b/src/quemb/kbe/chain.py similarity index 100% rename from src/kbe/chain.py rename to src/quemb/kbe/chain.py diff --git a/src/kbe/fragment.py b/src/quemb/kbe/fragment.py similarity index 97% rename from src/kbe/fragment.py rename to src/quemb/kbe/fragment.py index 2f659a7d..d47c0969 100644 --- a/src/kbe/fragment.py +++ b/src/quemb/kbe/fragment.py @@ -2,8 +2,8 @@ import sys -from kbe.autofrag import autogen -from molbe.helper import get_core +from quemb.kbe.autofrag import autogen +from quemb.molbe.helper import get_core def print_mol_missing(): @@ -157,4 +157,4 @@ def __init__( # This import makes polychain a method of the class and # cannot be moved to the top of the file - from kbe.chain import polychain # noqa: PLC0415 + from quemb.kbe.chain import polychain # noqa: PLC0415 diff --git a/src/kbe/helper.py b/src/quemb/kbe/helper.py similarity index 97% rename from src/kbe/helper.py rename to src/quemb/kbe/helper.py index 01b408d6..aa898236 100644 --- a/src/kbe/helper.py +++ b/src/quemb/kbe/helper.py @@ -5,7 +5,7 @@ import numpy from pyscf import scf -from molbe.helper import unused +from quemb.molbe.helper import unused def get_veff(eri_, dm, S, TA, hf_veff, return_veff0=False): diff --git a/src/kbe/lo.py b/src/quemb/kbe/lo.py similarity index 99% rename from src/kbe/lo.py rename to src/quemb/kbe/lo.py index 7193a78d..67bea0a2 100644 --- a/src/kbe/lo.py +++ b/src/quemb/kbe/lo.py @@ -10,15 +10,15 @@ from libdmet.lo import pywannier90 from numpy.linalg import eigh, svd -from kbe.lo_k import ( +from quemb.kbe.lo_k import ( get_iao_k, get_pao_native_k, get_xovlp_k, remove_core_mo_k, symm_orth_k, ) -from molbe.external.lo_helper import get_aoind_by_atom, reorder_by_atom_ -from molbe.helper import ncore_, unused +from quemb.molbe.external.lo_helper import get_aoind_by_atom, reorder_by_atom_ +from quemb.molbe.helper import ncore_, unused class KMF: diff --git a/src/kbe/lo_k.py b/src/quemb/kbe/lo_k.py similarity index 99% rename from src/kbe/lo_k.py rename to src/quemb/kbe/lo_k.py index 68d1133a..060e8de6 100644 --- a/src/kbe/lo_k.py +++ b/src/quemb/kbe/lo_k.py @@ -8,7 +8,7 @@ import scipy from pyscf.pbc import gto as pgto -from molbe.helper import unused +from quemb.molbe.helper import unused def dot_gen(A, B, ovlp): diff --git a/src/kbe/misc.py b/src/quemb/kbe/misc.py similarity index 100% rename from src/kbe/misc.py rename to src/quemb/kbe/misc.py diff --git a/src/kbe/pbe.py b/src/quemb/kbe/pbe.py similarity index 98% rename from src/kbe/pbe.py rename to src/quemb/kbe/pbe.py index 86503fe5..d3492e9c 100644 --- a/src/kbe/pbe.py +++ b/src/quemb/kbe/pbe.py @@ -12,9 +12,9 @@ from pyscf.pbc import df, gto from pyscf.pbc.df.df_jk import _ewald_exxdiv_for_G0 -import molbe.be_var as be_var -from kbe.misc import storePBE -from kbe.pfrag import Frags +from quemb.shared import be_var +from quemb.kbe.misc import storePBE +from quemb.kbe.pfrag import Frags class BE: @@ -325,9 +325,9 @@ def __init__( # The following import of these functions turns them into # proper methods of the class. - from kbe._opt import optimize # noqa: PLC0415 - from kbe.lo import localize # noqa: PLC0415 - from molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 + from quemb.kbe._opt import optimize # noqa: PLC0415 + from quemb.kbe.lo import localize # noqa: PLC0415 + from quemb.molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 def print_ini(self): """ diff --git a/src/kbe/pfrag.py b/src/quemb/kbe/pfrag.py similarity index 98% rename from src/kbe/pfrag.py rename to src/quemb/kbe/pfrag.py index 2c3942ee..9f43beca 100644 --- a/src/kbe/pfrag.py +++ b/src/quemb/kbe/pfrag.py @@ -6,10 +6,10 @@ import h5py import numpy -from kbe.helper import get_veff -from kbe.misc import get_phase, get_phase1 -from kbe.solver import schmidt_decomp_svd -from molbe.helper import get_eri, get_scfObj, unused +from quemb.kbe.helper import get_veff +from quemb.kbe.misc import get_phase, get_phase1 +from quemb.kbe.solver import schmidt_decomp_svd +from quemb.molbe.helper import get_eri, get_scfObj, unused class Frags: diff --git a/src/kbe/solver.py b/src/quemb/kbe/solver.py similarity index 97% rename from src/kbe/solver.py rename to src/quemb/kbe/solver.py index 9b632738..da517f3c 100644 --- a/src/kbe/solver.py +++ b/src/quemb/kbe/solver.py @@ -3,7 +3,7 @@ import numpy import scipy.linalg -from molbe.helper import unused +from quemb.molbe.helper import unused def schmidt_decomp_svd(rdm, Frag_sites): diff --git a/src/molbe/.DS_Store b/src/quemb/molbe/.DS_Store similarity index 100% rename from src/molbe/.DS_Store rename to src/quemb/molbe/.DS_Store diff --git a/src/quemb/molbe/__init__.py b/src/quemb/molbe/__init__.py new file mode 100644 index 00000000..ffe6c654 --- /dev/null +++ b/src/quemb/molbe/__init__.py @@ -0,0 +1,5 @@ +from quemb.molbe.fragment import fragpart +from quemb.molbe.mbe import BE +from quemb.molbe.ube import UBE + +__all__ = ["fragpart", "BE", "UBE"] diff --git a/src/molbe/_opt.py b/src/quemb/molbe/_opt.py similarity index 98% rename from src/molbe/_opt.py rename to src/quemb/molbe/_opt.py index 16308be3..e23ddd89 100644 --- a/src/molbe/_opt.py +++ b/src/quemb/molbe/_opt.py @@ -4,10 +4,10 @@ import numpy -from molbe.be_parallel import be_func_parallel -from molbe.external.optqn import FrankQN -from molbe.misc import print_energy -from molbe.solver import be_func +from quemb.molbe.be_parallel import be_func_parallel +from quemb.molbe.external.optqn import FrankQN +from quemb.molbe.misc import print_energy +from quemb.molbe.solver import be_func class BEOPT: diff --git a/src/molbe/autofrag.py b/src/quemb/molbe/autofrag.py similarity index 99% rename from src/molbe/autofrag.py rename to src/quemb/molbe/autofrag.py index 811988cd..fcecdcb1 100644 --- a/src/molbe/autofrag.py +++ b/src/quemb/molbe/autofrag.py @@ -4,7 +4,7 @@ import numpy -from molbe.helper import get_core, unused +from quemb.molbe.helper import get_core, unused def autogen( diff --git a/src/molbe/be_parallel.py b/src/quemb/molbe/be_parallel.py similarity index 98% rename from src/molbe/be_parallel.py rename to src/quemb/molbe/be_parallel.py index f37ca656..9813fb02 100644 --- a/src/molbe/be_parallel.py +++ b/src/quemb/molbe/be_parallel.py @@ -8,10 +8,16 @@ import numpy from pyscf import ao2mo, fci, mcscf -from molbe.external.ccsd_rdm import make_rdm1_uccsd, make_rdm2_uccsd -from molbe.external.unrestricted_utils import make_uhf_obj -from molbe.helper import get_eri, get_frag_energy, get_frag_energy_u, get_scfObj, unused -from molbe.solver import ( +from quemb.molbe.external.ccsd_rdm import make_rdm1_uccsd, make_rdm2_uccsd +from quemb.molbe.external.unrestricted_utils import make_uhf_obj +from quemb.molbe.helper import ( + get_eri, + get_frag_energy, + get_frag_energy_u, + get_scfObj, + unused, +) +from quemb.molbe.solver import ( make_rdm1_ccsd_t1, make_rdm2_urlx, solve_ccsd, diff --git a/src/molbe/eri_onthefly.py b/src/quemb/molbe/eri_onthefly.py similarity index 99% rename from src/molbe/eri_onthefly.py rename to src/quemb/molbe/eri_onthefly.py index c5cfa8f5..ab52f456 100644 --- a/src/molbe/eri_onthefly.py +++ b/src/quemb/molbe/eri_onthefly.py @@ -8,7 +8,7 @@ from pyscf.gto.moleintor import getints3c, make_cintopt, make_loc from scipy.linalg import cholesky, solve_triangular -from molbe import be_var +from quemb.shared import be_var def integral_direct_DF(mf, Fobjs, file_eri, auxbasis=None): diff --git a/src/quemb/molbe/external/__init__.py b/src/quemb/molbe/external/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/molbe/external/ccsd_rdm.py b/src/quemb/molbe/external/ccsd_rdm.py similarity index 100% rename from src/molbe/external/ccsd_rdm.py rename to src/quemb/molbe/external/ccsd_rdm.py diff --git a/src/molbe/external/cphf_utils.py b/src/quemb/molbe/external/cphf_utils.py similarity index 100% rename from src/molbe/external/cphf_utils.py rename to src/quemb/molbe/external/cphf_utils.py diff --git a/src/molbe/external/cpmp2_utils.py b/src/quemb/molbe/external/cpmp2_utils.py similarity index 98% rename from src/molbe/external/cpmp2_utils.py rename to src/quemb/molbe/external/cpmp2_utils.py index 65915554..6499991f 100644 --- a/src/molbe/external/cpmp2_utils.py +++ b/src/quemb/molbe/external/cpmp2_utils.py @@ -7,8 +7,8 @@ import scipy.linalg as slg from pyscf import ao2mo, scf -from molbe.external.cphf_utils import cphf_kernel_batch as cphf_kernel -from molbe.external.cphf_utils import get_cpuhf_u_batch as cpuhf_kernel +from quemb.molbe.external.cphf_utils import cphf_kernel_batch as cphf_kernel +from quemb.molbe.external.cphf_utils import get_cpuhf_u_batch as cpuhf_kernel """ RMP2 implementation """ diff --git a/src/molbe/external/jac_utils.py b/src/quemb/molbe/external/jac_utils.py similarity index 97% rename from src/molbe/external/jac_utils.py rename to src/quemb/molbe/external/jac_utils.py index 1cf8b18a..16988f2d 100644 --- a/src/molbe/external/jac_utils.py +++ b/src/quemb/molbe/external/jac_utils.py @@ -6,8 +6,8 @@ import numpy as np from pyscf import ao2mo -from molbe.external.cphf_utils import cphf_kernel_batch -from molbe.external.cpmp2_utils import get_dF_r, get_Diajb_r, get_dmoe_F_r +from quemb.molbe.external.cphf_utils import cphf_kernel_batch +from quemb.molbe.external.cpmp2_utils import get_dF_r, get_Diajb_r, get_dmoe_F_r """ Derivative of approximate t1 amplitudes t_ia = ((2*t2-t2)_ibjc g_cjba - g_ikbj (2*t2-t2)_jbka) / (e_i - e_a) diff --git a/src/molbe/external/lo_helper.py b/src/quemb/molbe/external/lo_helper.py similarity index 100% rename from src/molbe/external/lo_helper.py rename to src/quemb/molbe/external/lo_helper.py diff --git a/src/molbe/external/optqn.py b/src/quemb/molbe/external/optqn.py similarity index 98% rename from src/molbe/external/optqn.py rename to src/quemb/molbe/external/optqn.py index b25d47a6..a6ff9fea 100644 --- a/src/molbe/external/optqn.py +++ b/src/quemb/molbe/external/optqn.py @@ -8,11 +8,11 @@ import numpy -from molbe import be_var -from molbe.external.cphf_utils import cphf_kernel_batch, get_rhf_dP_from_u -from molbe.external.cpmp2_utils import get_dPmp2_batch_r -from molbe.external.jac_utils import get_dPccsdurlx_batch_u -from molbe.helper import get_eri, get_scfObj +from quemb.shared import be_var +from quemb.molbe.external.cphf_utils import cphf_kernel_batch, get_rhf_dP_from_u +from quemb.molbe.external.cpmp2_utils import get_dPmp2_batch_r +from quemb.molbe.external.jac_utils import get_dPccsdurlx_batch_u +from quemb.molbe.helper import get_eri, get_scfObj def line_search_LF(func, xold, fold, dx, iter_): diff --git a/src/molbe/external/restore_eri_addition.c b/src/quemb/molbe/external/restore_eri_addition.c similarity index 100% rename from src/molbe/external/restore_eri_addition.c rename to src/quemb/molbe/external/restore_eri_addition.c diff --git a/src/molbe/external/uccsd_eri.py b/src/quemb/molbe/external/uccsd_eri.py similarity index 100% rename from src/molbe/external/uccsd_eri.py rename to src/quemb/molbe/external/uccsd_eri.py diff --git a/src/molbe/external/unrestricted_utils.py b/src/quemb/molbe/external/unrestricted_utils.py similarity index 100% rename from src/molbe/external/unrestricted_utils.py rename to src/quemb/molbe/external/unrestricted_utils.py diff --git a/src/molbe/fragment.py b/src/quemb/molbe/fragment.py similarity index 98% rename from src/molbe/fragment.py rename to src/quemb/molbe/fragment.py index 1cd74dd2..7307da13 100644 --- a/src/molbe/fragment.py +++ b/src/quemb/molbe/fragment.py @@ -2,8 +2,8 @@ import sys -from molbe.autofrag import autogen -from molbe.helper import get_core +from quemb.molbe.autofrag import autogen +from quemb.molbe.helper import get_core class fragpart: @@ -134,7 +134,7 @@ def __init__( sys.exit() # importing the function here turns it into a proper method - from molbe.lchain import chain # noqa: PLC0415 + from quemb.molbe.lchain import chain # noqa: PLC0415 def hchain_simple(self): """Hard coded fragmentation feature""" diff --git a/src/molbe/helper.py b/src/quemb/molbe/helper.py similarity index 100% rename from src/molbe/helper.py rename to src/quemb/molbe/helper.py diff --git a/src/molbe/lchain.py b/src/quemb/molbe/lchain.py similarity index 100% rename from src/molbe/lchain.py rename to src/quemb/molbe/lchain.py diff --git a/src/molbe/lo.py b/src/quemb/molbe/lo.py similarity index 99% rename from src/molbe/lo.py rename to src/quemb/molbe/lo.py index 8d05735a..23f35194 100644 --- a/src/molbe/lo.py +++ b/src/quemb/molbe/lo.py @@ -7,11 +7,11 @@ from numpy.linalg import eigh from pyscf.gto import intor_cross -from molbe.external.lo_helper import ( +from quemb.molbe.external.lo_helper import ( get_aoind_by_atom, reorder_by_atom_, ) -from molbe.helper import ncore_, unused +from quemb.molbe.helper import ncore_, unused def dot_gen(A, B, ovlp): diff --git a/src/molbe/mbe.py b/src/quemb/molbe/mbe.py similarity index 97% rename from src/molbe/mbe.py rename to src/quemb/molbe/mbe.py index 94a5d924..b42d1619 100644 --- a/src/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -7,11 +7,11 @@ import numpy from pyscf import ao2mo -import molbe.be_var as be_var -from molbe.be_parallel import be_func_parallel -from molbe.eri_onthefly import integral_direct_DF -from molbe.pfrag import Frags -from molbe.solver import be_func +from quemb.shared import be_var +from quemb.molbe.be_parallel import be_func_parallel +from quemb.molbe.eri_onthefly import integral_direct_DF +from quemb.molbe.pfrag import Frags +from quemb.molbe.solver import be_func class storeBE: @@ -302,10 +302,10 @@ def __init__( # The following imports turn the imported functions into proper methods # cannot be moved to head of file. - from molbe._opt import optimize # noqa: PLC0415 - from molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 - from molbe.lo import localize # noqa: PLC0415 - from molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 + from quemb.molbe._opt import optimize # noqa: PLC0415 + from quemb.molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 + from quemb.molbe.lo import localize # noqa: PLC0415 + from quemb.molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 def print_ini(self): """ diff --git a/src/molbe/misc.py b/src/quemb/molbe/misc.py similarity index 99% rename from src/molbe/misc.py rename to src/quemb/molbe/misc.py index b7bb509b..6ca2c322 100644 --- a/src/molbe/misc.py +++ b/src/quemb/molbe/misc.py @@ -10,7 +10,7 @@ from pyscf.lib import chkfile from pyscf.tools import fcidump -from molbe.fragment import fragpart +from quemb.molbe.fragment import fragpart def libint2pyscf( @@ -320,8 +320,8 @@ def be2puffin( """ # The following imports have to happen here to avoid # circular dependencies. - from molbe.mbe import BE # noqa: PLC0415 - from molbe.ube import UBE # noqa: PLC0415 + from quemb.molbe.mbe import BE # noqa: PLC0415 + from quemb.molbe.ube import UBE # noqa: PLC0415 # Check input validity assert os.path.exists(xyzfile), "Input xyz file does not exist" diff --git a/src/molbe/pfrag.py b/src/quemb/molbe/pfrag.py similarity index 99% rename from src/molbe/pfrag.py rename to src/quemb/molbe/pfrag.py index 4372ace4..4f1e286e 100644 --- a/src/molbe/pfrag.py +++ b/src/quemb/molbe/pfrag.py @@ -6,8 +6,8 @@ import numpy import scipy.linalg -from molbe.helper import get_eri, get_scfObj, get_veff -from molbe.solver import schmidt_decomposition +from quemb.molbe.helper import get_eri, get_scfObj, get_veff +from quemb.molbe.solver import schmidt_decomposition class Frags: diff --git a/src/molbe/rdm.py b/src/quemb/molbe/rdm.py similarity index 100% rename from src/molbe/rdm.py rename to src/quemb/molbe/rdm.py diff --git a/src/molbe/solver.py b/src/quemb/molbe/solver.py similarity index 99% rename from src/molbe/solver.py rename to src/quemb/molbe/solver.py index 1da1d392..ebb0e2ee 100644 --- a/src/molbe/solver.py +++ b/src/quemb/molbe/solver.py @@ -8,16 +8,16 @@ from pyscf import ao2mo, cc, fci, mcscf, mp from pyscf.cc.ccsd_rdm import make_rdm2 -from molbe import be_var -from molbe.external.ccsd_rdm import ( +from quemb.shared import be_var +from quemb.molbe.external.ccsd_rdm import ( make_rdm1_ccsd_t1, make_rdm1_uccsd, make_rdm2_uccsd, make_rdm2_urlx, ) -from molbe.external.uccsd_eri import make_eris_incore -from molbe.external.unrestricted_utils import make_uhf_obj -from molbe.helper import get_frag_energy, get_frag_energy_u, unused +from quemb.molbe.external.uccsd_eri import make_eris_incore +from quemb.molbe.external.unrestricted_utils import make_uhf_obj +from quemb.molbe.helper import get_frag_energy, get_frag_energy_u, unused def be_func( diff --git a/src/molbe/ube.py b/src/quemb/molbe/ube.py similarity index 98% rename from src/molbe/ube.py rename to src/quemb/molbe/ube.py index b622e822..55c5b85f 100644 --- a/src/molbe/ube.py +++ b/src/quemb/molbe/ube.py @@ -18,12 +18,12 @@ import numpy from pyscf import ao2mo -import molbe.be_var as be_var -from molbe.be_parallel import be_func_parallel_u -from molbe.helper import unused -from molbe.mbe import BE -from molbe.pfrag import Frags -from molbe.solver import be_func_u +from quemb.shared import be_var +from quemb.molbe.be_parallel import be_func_parallel_u +from quemb.molbe.helper import unused +from quemb.molbe.mbe import BE +from quemb.molbe.pfrag import Frags +from quemb.molbe.solver import be_func_u class UBE(BE): # 🍠 diff --git a/src/quemb/shared/__init__.py b/src/quemb/shared/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/molbe/be_var.py b/src/quemb/shared/be_var.py similarity index 100% rename from src/molbe/be_var.py rename to src/quemb/shared/be_var.py diff --git a/tests/chem_dm_kBE_test.py b/tests/chem_dm_kBE_test.py index b26dab7e..076ae4c0 100644 --- a/tests/chem_dm_kBE_test.py +++ b/tests/chem_dm_kBE_test.py @@ -11,7 +11,7 @@ import numpy from pyscf.pbc import df, gto, scf -from kbe import BE, fragpart +from quemb.kbe import BE, fragpart try: import libdmet diff --git a/tests/chempot_molBE_test.py b/tests/chempot_molBE_test.py index a5cb5164..79a1ba7c 100644 --- a/tests/chempot_molBE_test.py +++ b/tests/chempot_molBE_test.py @@ -10,7 +10,7 @@ from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart class TestBE_restricted(unittest.TestCase): diff --git a/tests/dm_molBE_test.py b/tests/dm_molBE_test.py index ef150817..b5ee2e11 100644 --- a/tests/dm_molBE_test.py +++ b/tests/dm_molBE_test.py @@ -9,7 +9,7 @@ from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart class TestBE_restricted(unittest.TestCase): diff --git a/tests/dmrg_molBE_test.py b/tests/dmrg_molBE_test.py index 038a344b..11020e32 100644 --- a/tests/dmrg_molBE_test.py +++ b/tests/dmrg_molBE_test.py @@ -9,7 +9,7 @@ from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart try: from pyscf import dmrgscf diff --git a/tests/eri_onthefly_test.py b/tests/eri_onthefly_test.py index 7850185e..5c09d25e 100644 --- a/tests/eri_onthefly_test.py +++ b/tests/eri_onthefly_test.py @@ -10,7 +10,7 @@ from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart class TestDF_ontheflyERI(unittest.TestCase): diff --git a/tests/hf-in-hf_BE_test.py b/tests/hf-in-hf_BE_test.py index 24166ea7..3b08b053 100644 --- a/tests/hf-in-hf_BE_test.py +++ b/tests/hf-in-hf_BE_test.py @@ -10,7 +10,7 @@ from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart class TestHFinHF_restricted(unittest.TestCase): diff --git a/tests/kbe_polyacetylene_test.py b/tests/kbe_polyacetylene_test.py index e66c5e4b..d3faa992 100644 --- a/tests/kbe_polyacetylene_test.py +++ b/tests/kbe_polyacetylene_test.py @@ -5,7 +5,7 @@ import numpy as np from pyscf.pbc import df, gto, scf -from kbe import BE, fragpart +from quemb.kbe import BE, fragpart def test_polyacetylene(): diff --git a/tests/molbe_h8_test.py b/tests/molbe_h8_test.py index d3cdb86f..6e65a0cc 100644 --- a/tests/molbe_h8_test.py +++ b/tests/molbe_h8_test.py @@ -5,7 +5,7 @@ import numpy as np from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart def prepare_system(): diff --git a/tests/molbe_io_fcidump_test.py b/tests/molbe_io_fcidump_test.py index 98b9e091..3845efe8 100644 --- a/tests/molbe_io_fcidump_test.py +++ b/tests/molbe_io_fcidump_test.py @@ -11,8 +11,8 @@ from pyscf.lib.misc import with_omp_threads from pyscf.tools import fcidump -from molbe import BE, fragpart -from molbe.misc import be2fcidump, libint2pyscf +from quemb.molbe import BE, fragpart +from quemb.molbe.misc import be2fcidump, libint2pyscf def prepare_system(): diff --git a/tests/molbe_octane_get_rdms_test.py b/tests/molbe_octane_get_rdms_test.py index 01a2c0b3..12cd1929 100644 --- a/tests/molbe_octane_get_rdms_test.py +++ b/tests/molbe_octane_get_rdms_test.py @@ -5,7 +5,7 @@ import pytest from pyscf import gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart # TODO: actually add meaningful tests for RDM elements, # energies etc. diff --git a/tests/molbe_octane_test.py b/tests/molbe_octane_test.py index 83db9839..6aebe93d 100644 --- a/tests/molbe_octane_test.py +++ b/tests/molbe_octane_test.py @@ -6,7 +6,7 @@ import pytest from pyscf import cc, gto, scf -from molbe import BE, fragpart +from quemb.molbe import BE, fragpart # TODO: actually add meaningful tests for energies etc. # At the moment the test fails already for technical reasons. diff --git a/tests/molbe_oneshot_rbe_hcore_test.py b/tests/molbe_oneshot_rbe_hcore_test.py index 076d67eb..fd12b0a2 100644 --- a/tests/molbe_oneshot_rbe_hcore_test.py +++ b/tests/molbe_oneshot_rbe_hcore_test.py @@ -7,7 +7,7 @@ import numpy as np from pyscf import gto, qmmm, scf -from molbe.misc import be2puffin +from quemb.molbe.misc import be2puffin # variables for scratch handling # pbe_var.SCRATCH = '{}' diff --git a/tests/molbe_oneshot_rbe_qmmm-fromchk_test.py b/tests/molbe_oneshot_rbe_qmmm-fromchk_test.py index 0940a538..60c1ad3a 100644 --- a/tests/molbe_oneshot_rbe_qmmm-fromchk_test.py +++ b/tests/molbe_oneshot_rbe_qmmm-fromchk_test.py @@ -6,7 +6,7 @@ import pytest -from molbe.misc import be2puffin +from quemb.molbe.misc import be2puffin # variables for scratch handling # pbe_var.SCRATCH = '{}' diff --git a/tests/ube-oneshot_test.py b/tests/ube-oneshot_test.py index 3eda36ad..452efaeb 100644 --- a/tests/ube-oneshot_test.py +++ b/tests/ube-oneshot_test.py @@ -11,7 +11,7 @@ from pyscf import gto, scf -from molbe import UBE, fragpart +from quemb.molbe import UBE, fragpart class TestOneShot_Unrestricted(unittest.TestCase): From 2c138940d6fce079b4a5d2901fceb6d5527219a3 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Mon, 25 Nov 2024 20:36:07 -0500 Subject: [PATCH 02/43] updated docs with quemb namespace --- docs/source/fragment.rst | 8 ++++---- docs/source/index.rst | 6 +++--- docs/source/kernel.rst | 8 ++++---- docs/source/misc.rst | 8 ++++---- docs/source/optimize.rst | 4 ++-- docs/source/pfrag.rst | 4 ++-- docs/source/solvers.rst | 30 +++++++++++++++--------------- docs/source/usage.rst | 8 ++++---- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/source/fragment.rst b/docs/source/fragment.rst index 3b333e6d..57c0f926 100644 --- a/docs/source/fragment.rst +++ b/docs/source/fragment.rst @@ -6,12 +6,12 @@ Fragments for molecular systems .. toctree:: :maxdepth: 4 -.. autoclass:: molbe.fragment.fragpart +.. autoclass:: quemb.molbe.fragment.fragpart :members: :show-inheritance: :undoc-members: -.. automodule:: molbe.autofrag +.. automodule:: quemb.molbe.autofrag :members: Fragments for perioric systems @@ -19,9 +19,9 @@ Fragments for perioric systems .. toctree:: :maxdepth: 4 -.. autoclass:: kbe.fragment.fragpart +.. autoclass:: quemb.kbe.fragment.fragpart :members: :show-inheritance: :undoc-members: -.. autofunction:: kbe.autofrag.autogen +.. autofunction:: quemb.kbe.autofrag.autogen diff --git a/docs/source/index.rst b/docs/source/index.rst index 3247dabe..c7844c9d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -13,9 +13,9 @@ the Python implementation of the BE methods, including periodic bootstrap embedd The code leverages `PySCF `_ library for quantum chemistry calculations and utlizes Python's multiprocessing module to enable parallel computations in high-performance computing environments. -QuEmb includes two libraries: ``molbe`` and ``kbe``. -The ``molbe`` library implements BE for molecules and supramolecular complexes, -while the ``kbe`` library is designed to handle periodic systems such as surfaces and solids using periodic BE. +QuEmb includes two libraries: ``quemb.molbe`` and ``quemb.kbe``. +The ``quemb.molbe`` library implements BE for molecules and supramolecular complexes, +while the ``quemb.kbe`` library is designed to handle periodic systems such as surfaces and solids using periodic BE. References ========== diff --git a/docs/source/kernel.rst b/docs/source/kernel.rst index ff30b78d..e3a6a986 100644 --- a/docs/source/kernel.rst +++ b/docs/source/kernel.rst @@ -6,7 +6,7 @@ Molecular BE kernel .. toctree:: :maxdepth: 4 -.. automodule:: molbe.mbe +.. automodule:: quemb.molbe.mbe :members: Periodic BE kernel @@ -14,7 +14,7 @@ Periodic BE kernel .. toctree:: :maxdepth: 4 -.. automodule:: kbe.pbe +.. automodule:: quemb.kbe.pbe :members: Parallel BE Solver @@ -22,7 +22,7 @@ Parallel BE Solver .. toctree:: :maxdepth: 4 -.. automodule:: molbe.be_parallel +.. automodule:: quemb.molbe.be_parallel :members: Serial BE Solver @@ -30,6 +30,6 @@ Serial BE Solver .. toctree:: :maxdepth: 4 -.. autofunction:: molbe.solver.be_func +.. autofunction:: quemb.molbe.solver.be_func diff --git a/docs/source/misc.rst b/docs/source/misc.rst index b1967c57..b120f715 100644 --- a/docs/source/misc.rst +++ b/docs/source/misc.rst @@ -3,7 +3,7 @@ Misc. functionalities .. toctree:: :maxdepth: 4 -.. autofunction:: molbe.misc.libint2pyscf -.. autofunction:: molbe.misc.be2fcidump -.. autofunction:: molbe.misc.ube2fcidump -.. autofunction:: molbe.misc.be2puffin +.. autofunction:: quemb.molbe.misc.libint2pyscf +.. autofunction:: quemb.molbe.misc.be2fcidump +.. autofunction:: quemb.molbe.misc.ube2fcidump +.. autofunction:: quemb.molbe.misc.be2puffin diff --git a/docs/source/optimize.rst b/docs/source/optimize.rst index 00efcc1c..09919db5 100644 --- a/docs/source/optimize.rst +++ b/docs/source/optimize.rst @@ -6,10 +6,10 @@ Main BE optimization .. toctree:: :maxdepth: 4 -.. autoclass:: molbe._opt.BEOPT +.. autoclass:: quemb.molbe._opt.BEOPT :members: Quasi-Newton optimization ========================= -.. autoclass:: molbe.external.optqn.FrankQN +.. autoclass:: quemb.molbe.external.optqn.FrankQN diff --git a/docs/source/pfrag.rst b/docs/source/pfrag.rst index 941469ea..a95b07a5 100644 --- a/docs/source/pfrag.rst +++ b/docs/source/pfrag.rst @@ -6,7 +6,7 @@ Molecular fragments .. toctree:: :maxdepth: 4 -.. automodule:: molbe.pfrag +.. automodule:: quemb.molbe.pfrag :members: @@ -15,6 +15,6 @@ Periodic fragments .. toctree:: :maxdepth: 4 -.. automodule:: kbe.pfrag +.. automodule:: quemb.kbe.pfrag :members: diff --git a/docs/source/solvers.rst b/docs/source/solvers.rst index db50ecc3..e2cacc6d 100644 --- a/docs/source/solvers.rst +++ b/docs/source/solvers.rst @@ -8,26 +8,26 @@ Orbital Localization Molecular orbital localization ------------------------------ -.. autofunction:: molbe.lo.localize +.. autofunction:: quemb.molbe.lo.localize Crystalline orbital localization -------------------------------- -.. autofunction:: kbe.lo.localize +.. autofunction:: quemb.kbe.lo.localize Density Matching Error ====================== -.. autofunction:: molbe.solver.solve_error +.. autofunction:: quemb.molbe.solver.solve_error Interface to Quantum Chemistry Methods ====================================== -.. autofunction:: molbe.solver.solve_mp2 +.. autofunction:: quemb.molbe.solver.solve_mp2 -.. autofunction:: molbe.solver.solve_ccsd +.. autofunction:: quemb.molbe.solver.solve_ccsd -.. autofunction:: molbe.helper.get_scfObj +.. autofunction:: quemb.molbe.helper.get_scfObj Schmidt Decomposition ===================== @@ -35,40 +35,40 @@ Schmidt Decomposition Molecular Schmidt decomposition ------------------------------- -.. autofunction:: molbe.solver.schmidt_decomposition +.. autofunction:: quemb.molbe.solver.schmidt_decomposition Periodic Schmidt decomposition ------------------------------ -.. autofunction:: kbe.solver.schmidt_decomp_svd +.. autofunction:: quemb.kbe.solver.schmidt_decomp_svd Handling Hamiltonian ==================== -.. autofunction:: molbe.helper.get_eri +.. autofunction:: quemb.molbe.helper.get_eri -.. autofunction:: molbe.helper.get_core +.. autofunction:: quemb.molbe.helper.get_core Build molecular HF potential ---------------------------- -.. autofunction:: molbe.helper.get_veff +.. autofunction:: quemb.molbe.helper.get_veff Build perioidic HF potential ---------------------------- -.. autofunction:: kbe.helper.get_veff +.. autofunction:: quemb.kbe.helper.get_veff Handling Energies ================= -.. autofunction:: molbe.helper.get_frag_energy +.. autofunction:: quemb.molbe.helper.get_frag_energy -.. autofunction:: molbe.rdm.compute_energy_full +.. autofunction:: quemb.molbe.rdm.compute_energy_full Handling Densities ================== -.. autofunction:: molbe.rdm.rdm1_fullbasis +.. autofunction:: quemb.molbe.rdm.rdm1_fullbasis diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 04fdf439..7f15a92a 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -11,10 +11,10 @@ QuEmb requires molecular & Hartree-Fock calculation objects from pyscf. Refer to Simple example of BE calculation on molecular system:: - from quemb.molbe import fragpart, BE + from quemb.quemb.molbe import fragpart, BE # Perform pyscf calculations to get mol, mf objects - # See quemb/example/molbe_h8_density_matching.py + # See quemb/example/quemb.molbe_h8_density_matching.py # get mol: pyscf.gto.M # get mf: pyscf.scf.RHF @@ -30,10 +30,10 @@ Simple example of BE calculation on molecular system:: Simple example of periodic BE calculation on 1D periodic system:: - from quemb.kbe import fragpart, BE + from quemb.quemb.kbe import fragpart, BE # Perform pyscf pbc calculations to get cell, kmf, kpts - # See quemb/example/kbe_polyacetylene.py + # See quemb/example/quemb.kbe_polyacetylene.py # get cell: pyscf.pbc.gto.Cell # get kmf: pyscf.pbc.scf.KRHF # get kpts: 2D array of k-points From 19b547471e5f4f389eb641ce14bb76a3452e044c Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Mon, 25 Nov 2024 20:41:14 -0500 Subject: [PATCH 03/43] removed wrong __init__.py --- src/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/__init__.py diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29b..00000000 From 7e5e2fd0907347f7172e38d0a4a9e9d2ea0ecba7 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Mon, 25 Nov 2024 20:42:00 -0500 Subject: [PATCH 04/43] updated mypy settings --- mypy.ini | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index bd1183d5..dc75914b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,9 +1,20 @@ [mypy] no_implicit_optional = True + disallow_untyped_defs = True + check_untyped_defs = True + +[mypy-quemb.molbe.*] + disallow_untyped_defs = False + check_untyped_defs = False + + +[mypy-quemb.kbe.*] + disallow_untyped_defs = False + check_untyped_defs = False + # TODO: whenever the following packages have stubs available, # stop ignoring them. - [mypy-pyscf.*] ignore_missing_imports = True From fc3e5c0e97c6e318f1d548e72c611f503dd3e7d5 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Mon, 25 Nov 2024 21:18:24 -0500 Subject: [PATCH 05/43] add check for assumed failing function --- src/quemb/molbe/rdm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quemb/molbe/rdm.py b/src/quemb/molbe/rdm.py index 9ccfc723..18987a9d 100644 --- a/src/quemb/molbe/rdm.py +++ b/src/quemb/molbe/rdm.py @@ -235,6 +235,7 @@ def compute_energy_full( approximate or true cumulants, and to return the reduced density matrices (RDMs). The energy components are printed as part of the function's output. """ + raise ValueError("This should fail.") # Compute the one-particle reduced density matrix (RDM1) and the cumulant (Kumul) # in the full basis rdm1f, Kumul, rdm1_lo, rdm2_lo = self.rdm1_fullbasis( From beda895514997bb150ad428b53f5ee5a0226982d Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Mon, 25 Nov 2024 21:23:32 -0500 Subject: [PATCH 06/43] improved docstring adder --- src/quemb/shared/helpers.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/quemb/shared/helpers.py diff --git a/src/quemb/shared/helpers.py b/src/quemb/shared/helpers.py new file mode 100644 index 00000000..b39a5142 --- /dev/null +++ b/src/quemb/shared/helpers.py @@ -0,0 +1,35 @@ +from typing import Callable, TypeVar + +Function = TypeVar("Function", bound=Callable) + + +# Note that we have once Callable and once Function. +# This is **intentional**. +# The inner function `update_doc` takes a function +# and returns a function **with** the exact same signature. +def add_docstring(doc: str) -> Callable[[Function], Function]: + """Add a docstring to a function. + + Is useful for programmatically generating docstrings. + + Parameters + ---------- + doc: str + A docstring. + + Example + ---------- + >>> @add_docstring("Returns 'asdf'") + >>> def f(): + >>> return 'asdf' + is equivalent to + >>> def f(): + >>> "Returns 'asdf'" + >>> return 'asdf' + """ + + def update_doc(f: Function) -> Function: + f.__doc__ = doc + return f + + return update_doc From 3788b0ba12c35da1a0bf1aaed2fb16bf90090d94 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 11:37:52 -0500 Subject: [PATCH 07/43] small reformatting --- src/quemb/kbe/pbe.py | 2 +- src/quemb/molbe/external/optqn.py | 2 +- src/quemb/molbe/mbe.py | 2 +- src/quemb/molbe/solver.py | 2 +- src/quemb/molbe/ube.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/quemb/kbe/pbe.py b/src/quemb/kbe/pbe.py index d3492e9c..aa877deb 100644 --- a/src/quemb/kbe/pbe.py +++ b/src/quemb/kbe/pbe.py @@ -12,9 +12,9 @@ from pyscf.pbc import df, gto from pyscf.pbc.df.df_jk import _ewald_exxdiv_for_G0 -from quemb.shared import be_var from quemb.kbe.misc import storePBE from quemb.kbe.pfrag import Frags +from quemb.shared import be_var class BE: diff --git a/src/quemb/molbe/external/optqn.py b/src/quemb/molbe/external/optqn.py index a6ff9fea..b4b1a62a 100644 --- a/src/quemb/molbe/external/optqn.py +++ b/src/quemb/molbe/external/optqn.py @@ -8,11 +8,11 @@ import numpy -from quemb.shared import be_var from quemb.molbe.external.cphf_utils import cphf_kernel_batch, get_rhf_dP_from_u from quemb.molbe.external.cpmp2_utils import get_dPmp2_batch_r from quemb.molbe.external.jac_utils import get_dPccsdurlx_batch_u from quemb.molbe.helper import get_eri, get_scfObj +from quemb.shared import be_var def line_search_LF(func, xold, fold, dx, iter_): diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index b42d1619..a3345468 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -7,11 +7,11 @@ import numpy from pyscf import ao2mo -from quemb.shared import be_var from quemb.molbe.be_parallel import be_func_parallel from quemb.molbe.eri_onthefly import integral_direct_DF from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func +from quemb.shared import be_var class storeBE: diff --git a/src/quemb/molbe/solver.py b/src/quemb/molbe/solver.py index ebb0e2ee..d06a7480 100644 --- a/src/quemb/molbe/solver.py +++ b/src/quemb/molbe/solver.py @@ -8,7 +8,6 @@ from pyscf import ao2mo, cc, fci, mcscf, mp from pyscf.cc.ccsd_rdm import make_rdm2 -from quemb.shared import be_var from quemb.molbe.external.ccsd_rdm import ( make_rdm1_ccsd_t1, make_rdm1_uccsd, @@ -18,6 +17,7 @@ from quemb.molbe.external.uccsd_eri import make_eris_incore from quemb.molbe.external.unrestricted_utils import make_uhf_obj from quemb.molbe.helper import get_frag_energy, get_frag_energy_u, unused +from quemb.shared import be_var def be_func( diff --git a/src/quemb/molbe/ube.py b/src/quemb/molbe/ube.py index 55c5b85f..71431618 100644 --- a/src/quemb/molbe/ube.py +++ b/src/quemb/molbe/ube.py @@ -18,12 +18,12 @@ import numpy from pyscf import ao2mo -from quemb.shared import be_var from quemb.molbe.be_parallel import be_func_parallel_u from quemb.molbe.helper import unused from quemb.molbe.mbe import BE from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func_u +from quemb.shared import be_var class UBE(BE): # 🍠 From d3869fe819dbb76594733d87de5abc1227d4250f Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 12:05:29 -0500 Subject: [PATCH 08/43] fixed rdm test --- src/quemb/molbe/rdm.py | 1 - tests/molbe_octane_get_rdms_test.py | 21 +++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/quemb/molbe/rdm.py b/src/quemb/molbe/rdm.py index 18987a9d..9ccfc723 100644 --- a/src/quemb/molbe/rdm.py +++ b/src/quemb/molbe/rdm.py @@ -235,7 +235,6 @@ def compute_energy_full( approximate or true cumulants, and to return the reduced density matrices (RDMs). The energy components are printed as part of the function's output. """ - raise ValueError("This should fail.") # Compute the one-particle reduced density matrix (RDM1) and the cumulant (Kumul) # in the full basis rdm1f, Kumul, rdm1_lo, rdm2_lo = self.rdm1_fullbasis( diff --git a/tests/molbe_octane_get_rdms_test.py b/tests/molbe_octane_get_rdms_test.py index 12cd1929..a220eeb9 100644 --- a/tests/molbe_octane_get_rdms_test.py +++ b/tests/molbe_octane_get_rdms_test.py @@ -1,21 +1,19 @@ -# Illustrates parallelized BE computation on octane +# Illustrates BE computation on octane with RDMs import os +import numpy as np import pytest from pyscf import gto, scf from quemb.molbe import BE, fragpart +from quemb.shared import be_var # TODO: actually add meaningful tests for RDM elements, # energies etc. # At the moment the test fails already for technical reasons. -@pytest.mark.skipif( - not os.getenv("QUEMB_DO_KNOWN_TO_FAIL_TESTS") == "true", - reason="This test is known to fail.", -) def test_rdm(): # Perform pyscf HF calculation to get mol & mf objects mol = gto.M( @@ -61,8 +59,15 @@ def test_rdm(): mybe = BE(mf, fobj) # Perform BE density matching. - # Uses 20 procs, each fragment calculation assigned OMP_NUM_THREADS to 4 - # effectively running 5 fragment calculations in parallel - mybe.optimize(solver="CCSD", nproc=20, ompnum=4) + mybe.optimize(solver="CCSD", nproc=1, ompnum=1) rdm1_ao, rdm2_ao = mybe.rdm1_fullbasis(return_ao=True) + + assert np.isclose(mybe.ebe_tot, -310.3311676424482) + + rdm1, rdm2 = mybe.compute_energy_full(approx_cumulant=False, return_rdm=True) + + assert np.isclose(mybe.ebe_tot, -310.3311676424482) + + +test_rdm() From f917b9f5dd15c4dde0af72d4f943988ce89c2366 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 12:19:34 -0500 Subject: [PATCH 09/43] changed molbe.BE.localize to be properly defined as method --- src/quemb/molbe/lo.py | 273 --------------------------------------- src/quemb/molbe/mbe.py | 283 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 282 insertions(+), 274 deletions(-) diff --git a/src/quemb/molbe/lo.py b/src/quemb/molbe/lo.py index 23f35194..11e8e554 100644 --- a/src/quemb/molbe/lo.py +++ b/src/quemb/molbe/lo.py @@ -1,18 +1,8 @@ # Author(s): Henry Tran, Oinam Meitei, Shaun Weatherly # -import functools -import sys - import numpy -from numpy.linalg import eigh from pyscf.gto import intor_cross -from quemb.molbe.external.lo_helper import ( - get_aoind_by_atom, - reorder_by_atom_, -) -from quemb.molbe.helper import ncore_, unused - def dot_gen(A, B, ovlp): return A.T @ B if ovlp is None else A.T @ ovlp @ B @@ -218,266 +208,3 @@ def get_loc(mol, C, method, pop_method=None, init_guess=None): C_ = mlo.kernel() return C_ - - -def localize( - self, - lo_method, - mol=None, - valence_basis="sto-3g", - hstack=False, - pop_method=None, - init_guess=None, - valence_only=False, - nosave=False, -): - """Molecular orbital localization - - Performs molecular orbital localization computations. For large basis, - IAO is recommended augmented with PAO orbitals. - - Parameters - ---------- - lo_method : str - Localization method in quantum chemistry. 'lowdin', 'boys', and 'iao' - are supported. - mol : pyscf.gto.Molecule - pyscf.gto.Molecule object. - valence_basis: str - Name of minimal basis set for IAO scheme. 'sto-3g' suffice for most cases. - valence_only: bool - If this option is set to True, all calculation will be performed in the valence - basis in the IAO partitioning. - This is an experimental feature. - """ - if lo_method == "lowdin": - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - if self.unrestricted: - P_core = [ - numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core[s], self.S) - for s in [0, 1] - ] - C_ = numpy.dot(P_core, self.W) - Cpop = [ - functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) - for s in [0, 1] - ] - Cpop = [numpy.diag(Cpop[s]) for s in [0, 1]] - no_core_idx = [numpy.where(Cpop[s] > 0.7)[0] for s in [0, 1]] - C_ = [C_[s][:, no_core_idx[s]] for s in [0, 1]] - S_ = [ - functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) - for s in [0, 1] - ] - W_ = [] - for s in [0, 1]: - es_, vs_ = eigh(S_[s]) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_.append(functools.reduce(numpy.dot, (vs_, s_, vs_.T))) - self.W = [numpy.dot(C_[s], W_[s]) for s in [0, 1]] - else: - P_core = numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, self.W) - # NOTE: PYSCF has basis in 1s2s3s2p2p2p3p3p3p format - # fix no_core_idx - use population for now - Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.7)[0] - C_ = C_[:, no_core_idx] - S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - es_, vs_ = eigh(S_) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - self.W = numpy.dot(C_, W_) - - if self.unrestricted: - if self.frozen_core: - self.lmo_coeff_a = functools.reduce( - numpy.dot, (self.W[0].T, self.S, self.C_a[:, self.ncore :]) - ) - self.lmo_coeff_b = functools.reduce( - numpy.dot, (self.W[1].T, self.S, self.C_b[:, self.ncore :]) - ) - else: - self.lmo_coeff_a = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C_a) - ) - self.lmo_coeff_b = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C_b) - ) - else: - if self.frozen_core: - self.lmo_coeff = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C[:, self.ncore :]) - ) - else: - self.lmo_coeff = functools.reduce(numpy.dot, (self.W.T, self.S, self.C)) - - elif lo_method in ["pipek-mezey", "pipek", "PM"]: - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, W_) - Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.55)[0] - C_ = C_[:, no_core_idx] - S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - es_, vs_ = eigh(S_) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - W_ = numpy.dot(C_, W_) - - self.W = get_loc( - self.mol, W_, "PM", pop_method=pop_method, init_guess=init_guess - ) - - if not self.frozen_core: - self.lmo_coeff = self.W.T @ self.S @ self.C - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - - elif lo_method == "iao": - loc_type = "SO" - val_basis = "sto-3g" - - # Occupied mo_coeff (with core) - Co = self.C[:, : self.Nocc] - # Get necessary overlaps, second arg is IAO basis - S12, S2 = get_xovlp(self.mol, basis=val_basis) - # Use these to get IAOs - Ciao = get_iao(Co, S12, self.S, S2=S2) - - if not valence_only: - # Now get PAOs - if loc_type.upper() != "SO": - Cpao = get_pao(Ciao, self.S, S12, S2, self.mol) - elif loc_type.upper() == "SO": - Cpao = get_pao_native(Ciao, self.S, self.mol, valence_basis=val_basis) - - # rearrange by atom - aoind_by_atom = get_aoind_by_atom(self.mol) - Ciao, iaoind_by_atom = reorder_by_atom_(Ciao, aoind_by_atom, self.S) - - if not valence_only: - Cpao, paoind_by_atom = reorder_by_atom_(Cpao, aoind_by_atom, self.S) - - if self.frozen_core: - # Remove core MOs - Cc = self.C[:, : self.ncore] # Assumes core are first - Ciao = remove_core_mo(Ciao, Cc, self.S) - - # Localize orbitals beyond symm orth - if loc_type.upper() != "SO": - Ciao = get_loc(self.mol, Ciao, loc_type) - if not valence_only: - Cpao = get_loc(self.mol, Cpao, loc_type) - - shift = 0 - ncore = 0 - if not valence_only: - Wstack = numpy.zeros( - (Ciao.shape[0], Ciao.shape[1] + Cpao.shape[1]) - ) # -self.ncore)) - else: - Wstack = numpy.zeros((Ciao.shape[0], Ciao.shape[1])) - - if self.frozen_core: - for ix in range(self.mol.natm): - nc = ncore_(self.mol.atom_charge(ix)) - ncore += nc - niao = len(iaoind_by_atom[ix]) - iaoind_ix = [i_ - ncore for i_ in iaoind_by_atom[ix][nc:]] - Wstack[:, shift : shift + niao - nc] = Ciao[:, iaoind_ix] - shift += niao - nc - if not valence_only: - npao = len(paoind_by_atom[ix]) - Wstack[:, shift : shift + npao] = Cpao[:, paoind_by_atom[ix]] - shift += npao - else: - if not hstack: - for ix in range(self.mol.natm): - niao = len(iaoind_by_atom[ix]) - Wstack[:, shift : shift + niao] = Ciao[:, iaoind_by_atom[ix]] - shift += niao - if not valence_only: - npao = len(paoind_by_atom[ix]) - Wstack[:, shift : shift + npao] = Cpao[:, paoind_by_atom[ix]] - shift += npao - else: - Wstack = numpy.hstack((Ciao, Cpao)) - if not nosave: - self.W = Wstack - assert numpy.allclose( - self.W.T @ self.S @ self.W, numpy.eye(self.W.shape[1]) - ) - else: - assert numpy.allclose( - Wstack.T @ self.S @ Wstack, numpy.eye(Wstack.shape[1]) - ) - return Wstack - nmo = self.C.shape[1] - self.ncore - nlo = self.W.shape[1] - - if not valence_only: - if nmo > nlo: - Co_nocore = self.C[:, self.ncore : self.Nocc] - Cv = self.C[:, self.Nocc :] - # Ensure that the LOs span the occupied space - assert numpy.allclose( - numpy.sum((self.W.T @ self.S @ Co_nocore) ** 2.0), - self.Nocc - self.ncore, - ) - # Find virtual orbitals that lie in the span of LOs - u, l, vt = numpy.linalg.svd(self.W.T @ self.S @ Cv, full_matrices=False) - unused(u) - nvlo = nlo - self.Nocc - self.ncore - assert numpy.allclose(numpy.sum(l[:nvlo]), nvlo) - C_ = numpy.hstack([Co_nocore, Cv @ vt[:nvlo].T]) - self.lmo_coeff = self.W.T @ self.S @ C_ - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - - elif lo_method == "boys": - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, W_) - Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.55)[0] - C_ = C_[:, no_core_idx] - S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - es_, vs_ = eigh(S_) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - W_ = numpy.dot(C_, W_) - - self.W = get_loc(self.mol, W_, "BOYS") - - if not self.frozen_core: - self.lmo_coeff = self.W.T @ self.S @ self.C - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - - else: - print("lo_method = ", lo_method, " not implemented!", flush=True) - print("exiting", flush=True) - sys.exit() diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index a3345468..198aa137 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -1,14 +1,30 @@ # Author(s): Oinam Romesh Meitei +import functools import os import pickle +import sys import h5py import numpy +from numpy.linalg import eigh, multi_dot, svd from pyscf import ao2mo from quemb.molbe.be_parallel import be_func_parallel from quemb.molbe.eri_onthefly import integral_direct_DF +from quemb.molbe.external.lo_helper import ( + get_aoind_by_atom, + reorder_by_atom_, +) +from quemb.molbe.helper import ncore_, unused +from quemb.molbe.lo import ( + get_iao, + get_loc, + get_pao, + get_pao_native, + get_xovlp, + remove_core_mo, +) from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func from quemb.shared import be_var @@ -304,9 +320,274 @@ def __init__( # cannot be moved to head of file. from quemb.molbe._opt import optimize # noqa: PLC0415 from quemb.molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 - from quemb.molbe.lo import localize # noqa: PLC0415 from quemb.molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 + def localize( + self, + lo_method, + mol=None, + valence_basis="sto-3g", + hstack=False, + pop_method=None, + init_guess=None, + valence_only=False, + nosave=False, + ): + """Molecular orbital localization + + Performs molecular orbital localization computations. For large basis, + IAO is recommended augmented with PAO orbitals. + + Parameters + ---------- + lo_method : str + Localization method in quantum chemistry. 'lowdin', 'boys', and 'iao' + are supported. + mol : pyscf.gto.Molecule + pyscf.gto.Molecule object. + valence_basis: str + Name of minimal basis set for IAO scheme. 'sto-3g' suffice for most cases. + valence_only: bool + If this option is set to True, all calculation will be performed in the valence + basis in the IAO partitioning. + This is an experimental feature. + """ + if lo_method == "lowdin": + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + if self.unrestricted: + P_core = [ + numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core[s], self.S) + for s in [0, 1] + ] + C_ = numpy.dot(P_core, self.W) + Cpop = [ + functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) + for s in [0, 1] + ] + Cpop = [numpy.diag(Cpop[s]) for s in [0, 1]] + no_core_idx = [numpy.where(Cpop[s] > 0.7)[0] for s in [0, 1]] + C_ = [C_[s][:, no_core_idx[s]] for s in [0, 1]] + S_ = [ + functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) + for s in [0, 1] + ] + W_ = [] + for s in [0, 1]: + es_, vs_ = eigh(S_[s]) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_.append(functools.reduce(numpy.dot, (vs_, s_, vs_.T))) + self.W = [numpy.dot(C_[s], W_[s]) for s in [0, 1]] + else: + P_core = numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, self.W) + # NOTE: PYSCF has basis in 1s2s3s2p2p2p3p3p3p format + # fix no_core_idx - use population for now + Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + Cpop = numpy.diag(Cpop) + no_core_idx = numpy.where(Cpop > 0.7)[0] + C_ = C_[:, no_core_idx] + S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + es_, vs_ = eigh(S_) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) + self.W = numpy.dot(C_, W_) + + if self.unrestricted: + if self.frozen_core: + self.lmo_coeff_a = functools.reduce( + numpy.dot, (self.W[0].T, self.S, self.C_a[:, self.ncore :]) + ) + self.lmo_coeff_b = functools.reduce( + numpy.dot, (self.W[1].T, self.S, self.C_b[:, self.ncore :]) + ) + else: + self.lmo_coeff_a = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C_a) + ) + self.lmo_coeff_b = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C_b) + ) + else: + if self.frozen_core: + self.lmo_coeff = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C[:, self.ncore :]) + ) + else: + self.lmo_coeff = multi_dot((self.W.T, self.S, self.C)) + + elif lo_method in ["pipek-mezey", "pipek", "PM"]: + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, W_) + Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + Cpop = numpy.diag(Cpop) + no_core_idx = numpy.where(Cpop > 0.55)[0] + C_ = C_[:, no_core_idx] + S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + es_, vs_ = eigh(S_) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) + W_ = numpy.dot(C_, W_) + + self.W = get_loc( + self.mol, W_, "PM", pop_method=pop_method, init_guess=init_guess + ) + + if not self.frozen_core: + self.lmo_coeff = self.W.T @ self.S @ self.C + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + + elif lo_method == "iao": + loc_type = "SO" + val_basis = "sto-3g" + + # Occupied mo_coeff (with core) + Co = self.C[:, : self.Nocc] + # Get necessary overlaps, second arg is IAO basis + S12, S2 = get_xovlp(self.mol, basis=val_basis) + # Use these to get IAOs + Ciao = get_iao(Co, S12, self.S, S2=S2) + + if not valence_only: + # Now get PAOs + if loc_type.upper() != "SO": + Cpao = get_pao(Ciao, self.S, S12, S2, self.mol) + elif loc_type.upper() == "SO": + Cpao = get_pao_native( + Ciao, self.S, self.mol, valence_basis=val_basis + ) + + # rearrange by atom + aoind_by_atom = get_aoind_by_atom(self.mol) + Ciao, iaoind_by_atom = reorder_by_atom_(Ciao, aoind_by_atom, self.S) + + if not valence_only: + Cpao, paoind_by_atom = reorder_by_atom_(Cpao, aoind_by_atom, self.S) + + if self.frozen_core: + # Remove core MOs + Cc = self.C[:, : self.ncore] # Assumes core are first + Ciao = remove_core_mo(Ciao, Cc, self.S) + + # Localize orbitals beyond symm orth + if loc_type.upper() != "SO": + Ciao = get_loc(self.mol, Ciao, loc_type) + if not valence_only: + Cpao = get_loc(self.mol, Cpao, loc_type) + + shift = 0 + ncore = 0 + if not valence_only: + Wstack = numpy.zeros( + (Ciao.shape[0], Ciao.shape[1] + Cpao.shape[1]) + ) # -self.ncore)) + else: + Wstack = numpy.zeros((Ciao.shape[0], Ciao.shape[1])) + + if self.frozen_core: + for ix in range(self.mol.natm): + nc = ncore_(self.mol.atom_charge(ix)) + ncore += nc + niao = len(iaoind_by_atom[ix]) + iaoind_ix = [i_ - ncore for i_ in iaoind_by_atom[ix][nc:]] + Wstack[:, shift : shift + niao - nc] = Ciao[:, iaoind_ix] + shift += niao - nc + if not valence_only: + npao = len(paoind_by_atom[ix]) + Wstack[:, shift : shift + npao] = Cpao[:, paoind_by_atom[ix]] + shift += npao + else: + if not hstack: + for ix in range(self.mol.natm): + niao = len(iaoind_by_atom[ix]) + Wstack[:, shift : shift + niao] = Ciao[:, iaoind_by_atom[ix]] + shift += niao + if not valence_only: + npao = len(paoind_by_atom[ix]) + Wstack[:, shift : shift + npao] = Cpao[ + :, paoind_by_atom[ix] + ] + shift += npao + else: + Wstack = numpy.hstack((Ciao, Cpao)) + if not nosave: + self.W = Wstack + assert numpy.allclose( + self.W.T @ self.S @ self.W, numpy.eye(self.W.shape[1]) + ) + else: + assert numpy.allclose( + Wstack.T @ self.S @ Wstack, numpy.eye(Wstack.shape[1]) + ) + return Wstack + nmo = self.C.shape[1] - self.ncore + nlo = self.W.shape[1] + + if not valence_only: + if nmo > nlo: + Co_nocore = self.C[:, self.ncore : self.Nocc] + Cv = self.C[:, self.Nocc :] + # Ensure that the LOs span the occupied space + assert numpy.allclose( + numpy.sum((self.W.T @ self.S @ Co_nocore) ** 2.0), + self.Nocc - self.ncore, + ) + # Find virtual orbitals that lie in the span of LOs + u, l, vt = svd(self.W.T @ self.S @ Cv, full_matrices=False) + unused(u) + nvlo = nlo - self.Nocc - self.ncore + assert numpy.allclose(numpy.sum(l[:nvlo]), nvlo) + C_ = numpy.hstack([Co_nocore, Cv @ vt[:nvlo].T]) + self.lmo_coeff = self.W.T @ self.S @ C_ + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + + elif lo_method == "boys": + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, W_) + Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + Cpop = numpy.diag(Cpop) + no_core_idx = numpy.where(Cpop > 0.55)[0] + C_ = C_[:, no_core_idx] + S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + es_, vs_ = eigh(S_) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) + W_ = numpy.dot(C_, W_) + + self.W = get_loc(self.mol, W_, "BOYS") + + if not self.frozen_core: + self.lmo_coeff = self.W.T @ self.S @ self.C + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + + else: + print("lo_method = ", lo_method, " not implemented!", flush=True) + print("exiting", flush=True) + sys.exit() + def print_ini(self): """ Print initialization banner for the MOLBE calculation. From b17b2225099c38236b2b201d5a848b3f5a55149a Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 12:33:46 -0500 Subject: [PATCH 10/43] turned kbe localization into mixin --- TODO | 1 + src/quemb/kbe/lo.py | 796 ++++++++++++++++++++++--------------------- src/quemb/kbe/pbe.py | 4 +- 3 files changed, 419 insertions(+), 382 deletions(-) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 00000000..d8a54832 --- /dev/null +++ b/TODO @@ -0,0 +1 @@ +turn quemb.pbe.lo.KMF into dataclass (what it is) diff --git a/src/quemb/kbe/lo.py b/src/quemb/kbe/lo.py index 67bea0a2..c2282dff 100644 --- a/src/quemb/kbe/lo.py +++ b/src/quemb/kbe/lo.py @@ -31,445 +31,481 @@ def __init__(self, cell, kpts=None, mo_coeff=None, mo_energy=None): self.mo_coeff_kpts = mo_coeff.copy() -def localize( - self, - lo_method, - mol=None, - valence_basis="sto-3g", - iao_wannier=True, - valence_only=False, - iao_val_core=True, -): - """Orbital localization - - Performs orbital localization computations for periodic systems. For large basis, - IAO is recommended augmented with PAO orbitals. - - Parameters - ---------- - lo_method : str - Localization method in quantum chemistry. 'lowdin', 'boys','iao', and 'wannier' - are supported. - mol : pyscf.gto.Molecule - pyscf.gto.Molecule object. - valence_basis: str - Name of minimal basis set for IAO scheme. 'sto-3g' suffice for most cases. - valence_only: bool - If this option is set to True, all calculation will be performed in the valence - basis in the IAO partitioning. - This is an experimental feature. - iao_wannier : bool - Whether to perform Wannier localization in the IAO space - """ - if lo_method == "iao": - if valence_basis == "sto-3g": - from .basis_sto3g_core_val import core_basis, val_basis # noqa: PLC0415 - elif valence_basis == "minao": - from .basis_minao_core_val import core_basis, val_basis # noqa: PLC0415 - elif iao_val_core: - sys.exit( - "valence_basis=" - + valence_basis - + " not supported for iao_val_core=True" - ) +class Mixin_k_Localize: + def localize( + self, + lo_method, + mol=None, + valence_basis="sto-3g", + iao_wannier=True, + valence_only=False, + iao_val_core=True, + ): + """Orbital localization + + Performs orbital localization computations for periodic systems. For large + basis, IAO is recommended augmented with PAO orbitals. + + Parameters + ---------- + lo_method : str + Localization method in quantum chemistry. 'lowdin', 'boys','iao', and 'wannier' + are supported. + mol : pyscf.gto.Molecule + pyscf.gto.Molecule object. + valence_basis: str + Name of minimal basis set for IAO scheme. 'sto-3g' suffice for most cases. + valence_only: bool + If this option is set to True, all calculation will be performed in the valence + basis in the IAO partitioning. + This is an experimental feature. + iao_wannier : bool + Whether to perform Wannier localization in the IAO space + """ + if lo_method == "iao": + if valence_basis == "sto-3g": + from .basis_sto3g_core_val import core_basis, val_basis # noqa: PLC0415 + elif valence_basis == "minao": + from .basis_minao_core_val import core_basis, val_basis # noqa: PLC0415 + elif iao_val_core: + sys.exit( + "valence_basis=" + + valence_basis + + " not supported for iao_val_core=True" + ) - if lo_method == "lowdin": - # Lowdin orthogonalization with k-points - W = numpy.zeros_like(self.S) - nk, nao, nmo = self.C.shape - if self.frozen_core: - W_nocore = numpy.zeros_like(self.S[:, :, self.ncore :]) - lmo_coeff = numpy.zeros_like(self.C[:, self.ncore :, self.ncore :]) - cinv_ = numpy.zeros((nk, nmo - self.ncore, nao), dtype=numpy.complex128) - else: - lmo_coeff = numpy.zeros_like(self.C) - cinv_ = numpy.zeros((nk, nmo, nao), dtype=numpy.complex128) + if lo_method == "lowdin": + # Lowdin orthogonalization with k-points + W = numpy.zeros_like(self.S) + nk, nao, nmo = self.C.shape + if self.frozen_core: + W_nocore = numpy.zeros_like(self.S[:, :, self.ncore :]) + lmo_coeff = numpy.zeros_like(self.C[:, self.ncore :, self.ncore :]) + cinv_ = numpy.zeros((nk, nmo - self.ncore, nao), dtype=numpy.complex128) + else: + lmo_coeff = numpy.zeros_like(self.C) + cinv_ = numpy.zeros((nk, nmo, nao), dtype=numpy.complex128) + + for k in range(self.nkpt): + es_, vs_ = eigh(self.S[k]) + edx = es_ > 1.0e-14 - for k in range(self.nkpt): - es_, vs_ = eigh(self.S[k]) - edx = es_ > 1.0e-14 + W[k] = (vs_[:, edx] / numpy.sqrt(es_[edx])) @ vs_[:, edx].conj().T + for i in range(W[k].shape[1]): + if W[k][i, i] < 0: + W[:, i] *= -1 + if self.frozen_core: + pcore = numpy.eye(W[k].shape[0]) - (self.P_core[k] @ self.S[k]) + C_ = numpy.dot(pcore, W[k]) + + # PYSCF has basis in 1s2s3s2p2p2p3p3p3p format + # fix no_core_idx - use population for now + # C_ = C_[:,self.no_core_idx] + Cpop = functools.reduce(numpy.dot, (C_.conj().T, self.S[k], C_)) + Cpop = numpy.diag(Cpop.real) + + no_core_idx = numpy.where(Cpop > 0.7)[0] + C_ = C_[:, no_core_idx] + + S_ = functools.reduce(numpy.dot, (C_.conj().T, self.S[k], C_)) + + es_, vs_ = eigh(S_) + edx = es_ > 1.0e-14 + W_ = (vs_[:, edx] / numpy.sqrt(es_[edx])) @ vs_[:, edx].conj().T + W_nocore[k] = numpy.dot(C_, W_) + + lmo_coeff[k] = functools.reduce( + numpy.dot, + (W_nocore[k].conj().T, self.S[k], self.C[k][:, self.ncore :]), + ) + cinv_[k] = numpy.dot(W_nocore[k].conj().T, self.S[k]) - W[k] = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].conj().T) - for i in range(W[k].shape[1]): - if W[k][i, i] < 0: - W[:, i] *= -1 + else: + lmo_coeff[k] = functools.reduce( + numpy.dot, (W[k].conj().T, self.S[k], self.C[k]) + ) + cinv_[k] = numpy.dot(W[k].conj().T, self.S[k]) if self.frozen_core: - pcore = numpy.eye(W[k].shape[0]) - numpy.dot(self.P_core[k], self.S[k]) - C_ = numpy.dot(pcore, W[k]) + self.W = W_nocore + else: + self.W = W + self.lmo_coeff = lmo_coeff + self.cinv = cinv_ + + elif lo_method == "iao": + if not iao_val_core or not self.frozen_core: + Co = self.C[:, :, : self.Nocc].copy() + S12, S2 = get_xovlp_k(self.cell, self.kpts, basis=valence_basis) + ciao_ = get_iao_k(Co, S12, self.S, S2=S2) + + arrange_by_atom = True + # tmp - aos are not rearrange and so below is not necessary + if arrange_by_atom: + nk, nao, nlo = ciao_.shape + Ciao_ = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) + for k in range(self.nkpt): + aoind_by_atom = get_aoind_by_atom(self.cell) + ctmp, iaoind_by_atom = reorder_by_atom_( + ciao_[k], aoind_by_atom, self.S[k] + ) + Ciao_[k] = ctmp + else: + Ciao_ = ciao_.copy() - # PYSCF has basis in 1s2s3s2p2p2p3p3p3p format - # fix no_core_idx - use population for now - # C_ = C_[:,self.no_core_idx] - Cpop = functools.reduce(numpy.dot, (C_.conj().T, self.S[k], C_)) - Cpop = numpy.diag(Cpop.real) + # get_pao_k returns canonical orthogonalized orbitals + # Cpao = get_pao_k(Ciao, self.S, S12, S2, self.cell) + # get_pao_native_k returns symm orthogonalized orbitals + cpao_ = get_pao_native_k( + Ciao_, self.S, self.cell, valence_basis, self.kpts + ) - no_core_idx = numpy.where(Cpop > 0.7)[0] - C_ = C_[:, no_core_idx] + if arrange_by_atom: + nk, nao, nlo = cpao_.shape + Cpao_ = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) + for k in range(self.nkpt): + aoind_by_atom = get_aoind_by_atom(self.cell) + ctmp, paoind_by_atom = reorder_by_atom_( + cpao_[k], aoind_by_atom, self.S[k] + ) + Cpao_[k] = ctmp + else: + Cpao_ = cpao_.copy() - S_ = functools.reduce(numpy.dot, (C_.conj().T, self.S[k], C_)) + nk, nao, nlo = Ciao_.shape + if self.frozen_core: + nk, nao, nlo = Ciao_.shape + Ciao_nocore = numpy.zeros( + (nk, nao, nlo - self.ncore), dtype=numpy.complex128 + ) + for k in range(nk): + Ccore = self.C[k][:, : self.ncore] + Ciao_nocore[k] = remove_core_mo_k(Ciao_[k], Ccore, self.S[k]) + Ciao_ = Ciao_nocore - es_, vs_ = eigh(S_) - edx = es_ > 1.0e-14 - W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].conj().T) - W_nocore[k] = numpy.dot(C_, W_) + else: + # Construct seperate IAOs for the core and valence + + # Begin core + s12_core_, s2_core = get_xovlp_k(self.cell, self.kpts, basis=core_basis) + C_core_ = self.C[:, :, : self.ncore].copy() + nk_, nao_, nmo_ = C_core_.shape + s1_core = numpy.zeros((nk_, nmo_, nmo_), dtype=self.S.dtype) + s12_core = numpy.zeros( + (nk_, nmo_, s12_core_.shape[-1]), dtype=s12_core_.dtype + ) + C_core = numpy.zeros((nk_, self.ncore, self.ncore), dtype=C_core_.dtype) + for k in range(nk_): + C_core[k] = C_core_[k].conj().T @ self.S[k] @ C_core_[k] + s1_core[k] = C_core_[k].conj().T @ self.S[k] @ C_core_[k] + s12_core[k] = C_core_[k].conj().T @ s12_core_[k] + ciao_core_ = get_iao_k(C_core, s12_core, s1_core, s2_core, ortho=False) + ciao_core = numpy.zeros( + (nk_, nao_, ciao_core_.shape[-1]), dtype=ciao_core_.dtype + ) + for k in range(nk_): + ciao_core[k] = C_core_[k] @ ciao_core_[k] + ciao_core[k] = symm_orth_k(ciao_core[k], ovlp=self.S[k]) + + # Begin valence + s12_val_, s2_val = get_xovlp_k(self.cell, self.kpts, basis=val_basis) + C_nocore = self.C[:, :, self.ncore :].copy() + C_nocore_occ_ = C_nocore[:, :, : self.Nocc].copy() + nk_, nao_, nmo_ = C_nocore.shape + s1_val = numpy.zeros((nk_, nmo_, nmo_), dtype=self.S.dtype) + s12_val = numpy.zeros( + (nk_, nmo_, s12_val_.shape[-1]), dtype=s12_val_.dtype + ) + C_nocore_occ = numpy.zeros( + (nk_, nao_ - self.ncore, C_nocore_occ_.shape[-1]), + dtype=C_nocore_occ_.dtype, + ) + for k in range(nk_): + C_nocore_occ[k] = ( + C_nocore[k].conj().T @ self.S[k] @ C_nocore_occ_[k] + ) + s1_val[k] = C_nocore[k].conj().T @ self.S[k] @ C_nocore[k] + s12_val[k] = C_nocore[k].conj().T @ s12_val_[k] + ciao_val_ = get_iao_k( + C_nocore_occ, s12_val, s1_val, s2_val, ortho=False + ) + Ciao_ = numpy.zeros( + (nk_, nao_, ciao_val_.shape[-1]), dtype=ciao_val_.dtype + ) + for k in range(nk_): + Ciao_[k] = C_nocore[k] @ ciao_val_[k] + Ciao_[k] = symm_orth_k(Ciao_[k], ovlp=self.S[k]) + + # stack core|val + nao = self.S.shape[-1] + c_core_val = numpy.zeros( + (nk_, nao, Ciao_.shape[-1] + self.ncore), dtype=Ciao_.dtype + ) + for k in range(nk_): + c_core_val[k] = numpy.hstack((ciao_core[k], Ciao_[k])) + + arrange_by_atom = True + # tmp - aos are not rearrange and so below is not necessary + # (iaoind_by_atom is used to stack iao|pao later) + if arrange_by_atom: + nk, nao, nlo = c_core_val.shape + for k in range(self.nkpt): + aoind_by_atom = get_aoind_by_atom(self.cell) + ctmp, iaoind_by_atom = reorder_by_atom_( + c_core_val[k], aoind_by_atom, self.S[k] + ) + + cpao_ = get_pao_native_k( + c_core_val, self.S, self.cell, valence_basis, self.kpts, ortho=True + ) + if arrange_by_atom: + nk, nao, nlo = cpao_.shape + Cpao_ = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) + for k in range(self.nkpt): + aoind_by_atom = get_aoind_by_atom(self.cell) + ctmp, paoind_by_atom = reorder_by_atom_( + cpao_[k], aoind_by_atom, self.S[k] + ) + Cpao_[k] = ctmp + + Cpao = Cpao_.copy() + Ciao = Ciao_.copy() + + if iao_wannier: + mo_energy_ = [] + for k in range(nk): + fock_iao = reduce( + numpy.dot, (Ciao_[k].conj().T, self.FOCK[k], Ciao_[k]) + ) + S_iao = reduce(numpy.dot, (Ciao_[k].conj().T, self.S[k], Ciao_[k])) + e_iao, v_iao = eigh(fock_iao, S_iao) + unused(v_iao) + mo_energy_.append(e_iao) + iaomf = KMF( + self.mol, kpts=self.kpts, mo_coeff=Ciao_, mo_energy=mo_energy_ + ) - lmo_coeff[k] = functools.reduce( - numpy.dot, - (W_nocore[k].conj().T, self.S[k], self.C[k][:, self.ncore :]), + num_wann = numpy.asarray(iaomf.mo_coeff).shape[2] + keywords = """ + num_iter = 5000 + dis_num_iter = 0 + conv_noise_amp = -2.0 + conv_window = 100 + conv_tol = 1.0E-09 + iprint = 3 + kmesh_tol = 0.00001 + """ + # set conv window + # dis_num_iter=0 + w90 = pywannier90.W90( + iaomf, self.kmesh, num_wann, other_keywords=keywords ) - cinv_[k] = numpy.dot(W_nocore[k].conj().T, self.S[k]) - else: - lmo_coeff[k] = functools.reduce( - numpy.dot, (W[k].conj().T, self.S[k], self.C[k]) + A_matrix = numpy.zeros( + (self.nkpt, num_wann, num_wann), dtype=numpy.complex128 ) - cinv_[k] = numpy.dot(W[k].conj().T, self.S[k]) - if self.frozen_core: - self.W = W_nocore - else: - self.W = W - self.lmo_coeff = lmo_coeff - self.cinv = cinv_ - - elif lo_method == "iao": - if not iao_val_core or not self.frozen_core: - Co = self.C[:, :, : self.Nocc].copy() - S12, S2 = get_xovlp_k(self.cell, self.kpts, basis=valence_basis) - ciao_ = get_iao_k(Co, S12, self.S, S2=S2) - - arrange_by_atom = True - # tmp - aos are not rearrange and so below is not necessary - if arrange_by_atom: - nk, nao, nlo = ciao_.shape - Ciao_ = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) + + i_init = True for k in range(self.nkpt): - aoind_by_atom = get_aoind_by_atom(self.cell) - ctmp, iaoind_by_atom = reorder_by_atom_( - ciao_[k], aoind_by_atom, self.S[k] - ) - Ciao_[k] = ctmp - else: - Ciao_ = ciao_.copy() + if i_init: + A_matrix[k] = numpy.eye(num_wann, dtype=numpy.complex128) + else: + ovlp_ciao = uciao[k].conj().T @ self.S[k] @ Ciao[k] + A_matrix[k] = ovlp_ciao + A_matrix = A_matrix.transpose(1, 2, 0) - # get_pao_k returns canonical orthogonalized orbitals - # Cpao = get_pao_k(Ciao, self.S, S12, S2, self.cell) - # get_pao_native_k returns symm orthogonalized orbitals - cpao_ = get_pao_native_k(Ciao_, self.S, self.cell, valence_basis, self.kpts) + w90.kernel(A_matrix=A_matrix) - if arrange_by_atom: - nk, nao, nlo = cpao_.shape - Cpao_ = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) - for k in range(self.nkpt): - aoind_by_atom = get_aoind_by_atom(self.cell) - ctmp, paoind_by_atom = reorder_by_atom_( - cpao_[k], aoind_by_atom, self.S[k] - ) - Cpao_[k] = ctmp - else: - Cpao_ = cpao_.copy() + u_mat = numpy.array( + w90.U_matrix.transpose(2, 0, 1), order="C", dtype=numpy.complex128 + ) + + os.system("cp wannier90.wout wannier90_iao.wout") + os.system("rm wannier90.*") - nk, nao, nlo = Ciao_.shape - if self.frozen_core: nk, nao, nlo = Ciao_.shape - Ciao_nocore = numpy.zeros( - (nk, nao, nlo - self.ncore), dtype=numpy.complex128 - ) - for k in range(nk): - Ccore = self.C[k][:, : self.ncore] - Ciao_nocore[k] = remove_core_mo_k(Ciao_[k], Ccore, self.S[k]) - Ciao_ = Ciao_nocore + Ciao = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) - else: - # Construct seperate IAOs for the core and valence - - # Begin core - s12_core_, s2_core = get_xovlp_k(self.cell, self.kpts, basis=core_basis) - C_core_ = self.C[:, :, : self.ncore].copy() - nk_, nao_, nmo_ = C_core_.shape - s1_core = numpy.zeros((nk_, nmo_, nmo_), dtype=self.S.dtype) - s12_core = numpy.zeros( - (nk_, nmo_, s12_core_.shape[-1]), dtype=s12_core_.dtype - ) - C_core = numpy.zeros((nk_, self.ncore, self.ncore), dtype=C_core_.dtype) - for k in range(nk_): - C_core[k] = C_core_[k].conj().T @ self.S[k] @ C_core_[k] - s1_core[k] = C_core_[k].conj().T @ self.S[k] @ C_core_[k] - s12_core[k] = C_core_[k].conj().T @ s12_core_[k] - ciao_core_ = get_iao_k(C_core, s12_core, s1_core, s2_core, ortho=False) - ciao_core = numpy.zeros( - (nk_, nao_, ciao_core_.shape[-1]), dtype=ciao_core_.dtype - ) - for k in range(nk_): - ciao_core[k] = C_core_[k] @ ciao_core_[k] - ciao_core[k] = symm_orth_k(ciao_core[k], ovlp=self.S[k]) - - # Begin valence - s12_val_, s2_val = get_xovlp_k(self.cell, self.kpts, basis=val_basis) - C_nocore = self.C[:, :, self.ncore :].copy() - C_nocore_occ_ = C_nocore[:, :, : self.Nocc].copy() - nk_, nao_, nmo_ = C_nocore.shape - s1_val = numpy.zeros((nk_, nmo_, nmo_), dtype=self.S.dtype) - s12_val = numpy.zeros((nk_, nmo_, s12_val_.shape[-1]), dtype=s12_val_.dtype) - C_nocore_occ = numpy.zeros( - (nk_, nao_ - self.ncore, C_nocore_occ_.shape[-1]), - dtype=C_nocore_occ_.dtype, - ) - for k in range(nk_): - C_nocore_occ[k] = C_nocore[k].conj().T @ self.S[k] @ C_nocore_occ_[k] - s1_val[k] = C_nocore[k].conj().T @ self.S[k] @ C_nocore[k] - s12_val[k] = C_nocore[k].conj().T @ s12_val_[k] - ciao_val_ = get_iao_k(C_nocore_occ, s12_val, s1_val, s2_val, ortho=False) - Ciao_ = numpy.zeros((nk_, nao_, ciao_val_.shape[-1]), dtype=ciao_val_.dtype) - for k in range(nk_): - Ciao_[k] = C_nocore[k] @ ciao_val_[k] - Ciao_[k] = symm_orth_k(Ciao_[k], ovlp=self.S[k]) - - # stack core|val - nao = self.S.shape[-1] - c_core_val = numpy.zeros( - (nk_, nao, Ciao_.shape[-1] + self.ncore), dtype=Ciao_.dtype - ) - for k in range(nk_): - c_core_val[k] = numpy.hstack((ciao_core[k], Ciao_[k])) - - arrange_by_atom = True - # tmp - aos are not rearrange and so below is not necessary - # (iaoind_by_atom is used to stack iao|pao later) - if arrange_by_atom: - nk, nao, nlo = c_core_val.shape for k in range(self.nkpt): - aoind_by_atom = get_aoind_by_atom(self.cell) - ctmp, iaoind_by_atom = reorder_by_atom_( - c_core_val[k], aoind_by_atom, self.S[k] - ) + Ciao[k] = numpy.dot(Ciao_[k], u_mat[k]) - cpao_ = get_pao_native_k( - c_core_val, self.S, self.cell, valence_basis, self.kpts, ortho=True + # Stack Ciao|Cpao + Wstack = numpy.zeros( + (self.nkpt, Ciao.shape[1], Ciao.shape[2] + Cpao.shape[2]), + dtype=numpy.complex128, ) - if arrange_by_atom: - nk, nao, nlo = cpao_.shape - Cpao_ = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) + if self.frozen_core: for k in range(self.nkpt): - aoind_by_atom = get_aoind_by_atom(self.cell) - ctmp, paoind_by_atom = reorder_by_atom_( - cpao_[k], aoind_by_atom, self.S[k] + shift = 0 + ncore = 0 + for ix in range(self.cell.natm): + nc = ncore_(self.cell.atom_charge(ix)) + ncore += nc + niao = len(iaoind_by_atom[ix]) + iaoind_ix = [i_ - ncore for i_ in iaoind_by_atom[ix][nc:]] + Wstack[k][:, shift : shift + niao - nc] = Ciao[k][:, iaoind_ix] + shift += niao - nc + npao = len(paoind_by_atom[ix]) + + Wstack[k][:, shift : shift + npao] = Cpao[k][ + :, paoind_by_atom[ix] + ] + shift += npao + else: + for k in range(self.nkpt): + shift = 0 + for ix in range(self.cell.natm): + niao = len(iaoind_by_atom[ix]) + Wstack[k][:, shift : shift + niao] = Ciao[k][ + :, iaoind_by_atom[ix] + ] + shift += niao + npao = len(paoind_by_atom[ix]) + Wstack[k][:, shift : shift + npao] = Cpao[k][ + :, paoind_by_atom[ix] + ] + shift += npao + self.W = Wstack + + nmo = self.C.shape[2] - self.ncore + nlo = self.W.shape[2] + nao = self.S.shape[2] + + lmo_coeff = numpy.zeros((self.nkpt, nlo, nmo), dtype=numpy.complex128) + cinv_ = numpy.zeros((self.nkpt, nlo, nao), dtype=numpy.complex128) + + if nmo > nlo: + Co_nocore = self.C[:, :, self.ncore : self.Nocc] + Cv = self.C[:, :, self.Nocc :] + # Ensure that the LOs span the occupied space + for k in range(self.nkpt): + assert numpy.allclose( + numpy.sum( + (self.W[k].conj().T @ self.S[k] @ Co_nocore[k]) ** 2.0 + ), + self.Nocc - self.ncore, + ) + # Find virtual orbitals that lie in the span of LOs + u, l, vt = svd( + self.W[k].conj().T @ self.S[k] @ Cv[k], full_matrices=False + ) + unused(u) + nvlo = nlo - self.Nocc - self.ncore + assert numpy.allclose(numpy.sum(l[:nvlo]), nvlo) + C_ = numpy.hstack([Co_nocore[k], Cv[k] @ vt[:nvlo].conj().T]) + lmo_ = self.W[k].conj().T @ self.S[k] @ C_ + assert numpy.allclose( + lmo_.conj().T @ lmo_, numpy.eye(lmo_.shape[1]) + ) + lmo_coeff.append(lmo_) + else: + for k in range(self.nkpt): + lmo_coeff[k] = reduce( + numpy.dot, + (self.W[k].conj().T, self.S[k], self.C[k][:, self.ncore :]), + ) + cinv_[k] = numpy.dot(self.W[k].conj().T, self.S[k]) + + assert numpy.allclose( + lmo_coeff[k].conj().T @ lmo_coeff[k], + numpy.eye(lmo_coeff[k].shape[1]), ) - Cpao_[k] = ctmp - Cpao = Cpao_.copy() - Ciao = Ciao_.copy() + self.lmo_coeff = lmo_coeff + self.cinv = cinv_ - if iao_wannier: - mo_energy_ = [] + elif lo_method == "wannier": + nk, nao, nmo = self.C.shape + lorb = numpy.zeros((nk, nao, nmo), dtype=numpy.complex128) + lorb_nocore = numpy.zeros( + (nk, nao, nmo - self.ncore), dtype=numpy.complex128 + ) for k in range(nk): - fock_iao = reduce( - numpy.dot, (Ciao_[k].conj().T, self.FOCK[k], Ciao_[k]) + es_, vs_ = eigh(self.S[k]) + edx = es_ > 1.0e-14 + lorb[k] = numpy.dot( + vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].conj().T ) - S_iao = reduce(numpy.dot, (Ciao_[k].conj().T, self.S[k], Ciao_[k])) - e_iao, v_iao = eigh(fock_iao, S_iao) - unused(v_iao) - mo_energy_.append(e_iao) - iaomf = KMF(self.mol, kpts=self.kpts, mo_coeff=Ciao_, mo_energy=mo_energy_) - num_wann = numpy.asarray(iaomf.mo_coeff).shape[2] + if self.frozen_core: + Ccore = self.C[k][:, : self.ncore] + lorb_nocore[k] = remove_core_mo_k(lorb[k], Ccore, self.S[k]) + + if not self.frozen_core: + lmf = KMF( + self.mol, kpts=self.kpts, mo_coeff=lorb, mo_energy=self.mo_energy + ) + else: + mo_energy_nc = [] + for k in range(nk): + fock_lnc = reduce( + numpy.dot, + (lorb_nocore[k].conj().T, self.FOCK[k], lorb_nocore[k]), + ) + S_lnc = reduce( + numpy.dot, (lorb_nocore[k].conj().T, self.S[k], lorb_nocore[k]) + ) + e__, v__ = eigh(fock_lnc, S_lnc) + unused(v__) + mo_energy_nc.append(e__) + lmf = KMF( + self.mol, + kpts=self.kpts, + mo_coeff=lorb_nocore, + mo_energy=mo_energy_nc, + ) + + num_wann = lmf.mo_coeff.shape[2] keywords = """ - num_iter = 5000 + num_iter = 10000 dis_num_iter = 0 - conv_noise_amp = -2.0 - conv_window = 100 + conv_window = 10 conv_tol = 1.0E-09 iprint = 3 kmesh_tol = 0.00001 """ - # set conv window - # dis_num_iter=0 - w90 = pywannier90.W90(iaomf, self.kmesh, num_wann, other_keywords=keywords) + w90 = pywannier90.W90(lmf, self.kmesh, num_wann, other_keywords=keywords) A_matrix = numpy.zeros( (self.nkpt, num_wann, num_wann), dtype=numpy.complex128 ) - + # Using A=I + lowdin orbital and A= + |psi> is the same i_init = True for k in range(self.nkpt): if i_init: A_matrix[k] = numpy.eye(num_wann, dtype=numpy.complex128) - else: - ovlp_ciao = uciao[k].conj().T @ self.S[k] @ Ciao[k] - A_matrix[k] = ovlp_ciao + A_matrix = A_matrix.transpose(1, 2, 0) w90.kernel(A_matrix=A_matrix) - u_mat = numpy.array( w90.U_matrix.transpose(2, 0, 1), order="C", dtype=numpy.complex128 ) - os.system("cp wannier90.wout wannier90_iao.wout") - os.system("rm wannier90.*") + nk, nao, nlo = lmf.mo_coeff.shape + W = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) + for k in range(nk): + W[k] = numpy.dot(lmf.mo_coeff[k], u_mat[k]) - nk, nao, nlo = Ciao_.shape - Ciao = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) + self.W = W + lmo_coeff = numpy.zeros( + (self.nkpt, nlo, nmo - self.ncore), dtype=numpy.complex128 + ) + cinv_ = numpy.zeros((self.nkpt, nlo, nao), dtype=numpy.complex128) - for k in range(self.nkpt): - Ciao[k] = numpy.dot(Ciao_[k], u_mat[k]) - - # Stack Ciao|Cpao - Wstack = numpy.zeros( - (self.nkpt, Ciao.shape[1], Ciao.shape[2] + Cpao.shape[2]), - dtype=numpy.complex128, - ) - if self.frozen_core: - for k in range(self.nkpt): - shift = 0 - ncore = 0 - for ix in range(self.cell.natm): - nc = ncore_(self.cell.atom_charge(ix)) - ncore += nc - niao = len(iaoind_by_atom[ix]) - iaoind_ix = [i_ - ncore for i_ in iaoind_by_atom[ix][nc:]] - Wstack[k][:, shift : shift + niao - nc] = Ciao[k][:, iaoind_ix] - shift += niao - nc - npao = len(paoind_by_atom[ix]) - - Wstack[k][:, shift : shift + npao] = Cpao[k][:, paoind_by_atom[ix]] - shift += npao - else: - for k in range(self.nkpt): - shift = 0 - for ix in range(self.cell.natm): - niao = len(iaoind_by_atom[ix]) - Wstack[k][:, shift : shift + niao] = Ciao[k][:, iaoind_by_atom[ix]] - shift += niao - npao = len(paoind_by_atom[ix]) - Wstack[k][:, shift : shift + npao] = Cpao[k][:, paoind_by_atom[ix]] - shift += npao - self.W = Wstack - - nmo = self.C.shape[2] - self.ncore - nlo = self.W.shape[2] - nao = self.S.shape[2] - - lmo_coeff = numpy.zeros((self.nkpt, nlo, nmo), dtype=numpy.complex128) - cinv_ = numpy.zeros((self.nkpt, nlo, nao), dtype=numpy.complex128) - - if nmo > nlo: - Co_nocore = self.C[:, :, self.ncore : self.Nocc] - Cv = self.C[:, :, self.Nocc :] - # Ensure that the LOs span the occupied space - for k in range(self.nkpt): - assert numpy.allclose( - numpy.sum((self.W[k].conj().T @ self.S[k] @ Co_nocore[k]) ** 2.0), - self.Nocc - self.ncore, - ) - # Find virtual orbitals that lie in the span of LOs - u, l, vt = svd( - self.W[k].conj().T @ self.S[k] @ Cv[k], full_matrices=False - ) - unused(u) - nvlo = nlo - self.Nocc - self.ncore - assert numpy.allclose(numpy.sum(l[:nvlo]), nvlo) - C_ = numpy.hstack([Co_nocore[k], Cv[k] @ vt[:nvlo].conj().T]) - lmo_ = self.W[k].conj().T @ self.S[k] @ C_ - assert numpy.allclose(lmo_.conj().T @ lmo_, numpy.eye(lmo_.shape[1])) - lmo_coeff.append(lmo_) - else: - for k in range(self.nkpt): + for k in range(nk): lmo_coeff[k] = reduce( numpy.dot, (self.W[k].conj().T, self.S[k], self.C[k][:, self.ncore :]), ) cinv_[k] = numpy.dot(self.W[k].conj().T, self.S[k]) - assert numpy.allclose( lmo_coeff[k].conj().T @ lmo_coeff[k], numpy.eye(lmo_coeff[k].shape[1]), ) + self.lmo_coeff = lmo_coeff + self.cinv = cinv_ - self.lmo_coeff = lmo_coeff - self.cinv = cinv_ - - elif lo_method == "wannier": - nk, nao, nmo = self.C.shape - lorb = numpy.zeros((nk, nao, nmo), dtype=numpy.complex128) - lorb_nocore = numpy.zeros((nk, nao, nmo - self.ncore), dtype=numpy.complex128) - for k in range(nk): - es_, vs_ = eigh(self.S[k]) - edx = es_ > 1.0e-14 - lorb[k] = numpy.dot( - vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].conj().T - ) - - if self.frozen_core: - Ccore = self.C[k][:, : self.ncore] - lorb_nocore[k] = remove_core_mo_k(lorb[k], Ccore, self.S[k]) - - if not self.frozen_core: - lmf = KMF(self.mol, kpts=self.kpts, mo_coeff=lorb, mo_energy=self.mo_energy) else: - mo_energy_nc = [] - for k in range(nk): - fock_lnc = reduce( - numpy.dot, (lorb_nocore[k].conj().T, self.FOCK[k], lorb_nocore[k]) - ) - S_lnc = reduce( - numpy.dot, (lorb_nocore[k].conj().T, self.S[k], lorb_nocore[k]) - ) - e__, v__ = eigh(fock_lnc, S_lnc) - unused(v__) - mo_energy_nc.append(e__) - lmf = KMF( - self.mol, kpts=self.kpts, mo_coeff=lorb_nocore, mo_energy=mo_energy_nc - ) - - num_wann = lmf.mo_coeff.shape[2] - keywords = """ - num_iter = 10000 - dis_num_iter = 0 - conv_window = 10 - conv_tol = 1.0E-09 - iprint = 3 - kmesh_tol = 0.00001 - """ - - w90 = pywannier90.W90(lmf, self.kmesh, num_wann, other_keywords=keywords) - A_matrix = numpy.zeros((self.nkpt, num_wann, num_wann), dtype=numpy.complex128) - i_init = ( - True # Using A=I + lowdin orbital and A= + |psi> is the same - ) - for k in range(self.nkpt): - if i_init: - A_matrix[k] = numpy.eye(num_wann, dtype=numpy.complex128) - - A_matrix = A_matrix.transpose(1, 2, 0) - - w90.kernel(A_matrix=A_matrix) - u_mat = numpy.array( - w90.U_matrix.transpose(2, 0, 1), order="C", dtype=numpy.complex128 - ) - - nk, nao, nlo = lmf.mo_coeff.shape - W = numpy.zeros((nk, nao, nlo), dtype=numpy.complex128) - for k in range(nk): - W[k] = numpy.dot(lmf.mo_coeff[k], u_mat[k]) - - self.W = W - lmo_coeff = numpy.zeros( - (self.nkpt, nlo, nmo - self.ncore), dtype=numpy.complex128 - ) - cinv_ = numpy.zeros((self.nkpt, nlo, nao), dtype=numpy.complex128) - - for k in range(nk): - lmo_coeff[k] = reduce( - numpy.dot, (self.W[k].conj().T, self.S[k], self.C[k][:, self.ncore :]) - ) - cinv_[k] = numpy.dot(self.W[k].conj().T, self.S[k]) - assert numpy.allclose( - lmo_coeff[k].conj().T @ lmo_coeff[k], numpy.eye(lmo_coeff[k].shape[1]) - ) - self.lmo_coeff = lmo_coeff - self.cinv = cinv_ - - else: - print("lo_method = ", lo_method, " not implemented!", flush=True) - print("exiting", flush=True) - sys.exit() + print("lo_method = ", lo_method, " not implemented!", flush=True) + print("exiting", flush=True) + sys.exit() diff --git a/src/quemb/kbe/pbe.py b/src/quemb/kbe/pbe.py index aa877deb..17321bdc 100644 --- a/src/quemb/kbe/pbe.py +++ b/src/quemb/kbe/pbe.py @@ -12,12 +12,13 @@ from pyscf.pbc import df, gto from pyscf.pbc.df.df_jk import _ewald_exxdiv_for_G0 +from quemb.kbe.lo import Mixin_k_Localize from quemb.kbe.misc import storePBE from quemb.kbe.pfrag import Frags from quemb.shared import be_var -class BE: +class BE(Mixin_k_Localize): """ Class for handling periodic bootstrap embedding (BE) calculations. @@ -326,7 +327,6 @@ def __init__( # The following import of these functions turns them into # proper methods of the class. from quemb.kbe._opt import optimize # noqa: PLC0415 - from quemb.kbe.lo import localize # noqa: PLC0415 from quemb.molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 def print_ini(self): From 4adb13e70c55cc85c86885340239f5c46542c05c Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 12:34:07 -0500 Subject: [PATCH 11/43] Revert "changed molbe.BE.localize to be properly defined as method" This reverts commit f917b9f5dd15c4dde0af72d4f943988ce89c2366. --- src/quemb/molbe/lo.py | 273 +++++++++++++++++++++++++++++++++++++++ src/quemb/molbe/mbe.py | 283 +---------------------------------------- 2 files changed, 274 insertions(+), 282 deletions(-) diff --git a/src/quemb/molbe/lo.py b/src/quemb/molbe/lo.py index 11e8e554..23f35194 100644 --- a/src/quemb/molbe/lo.py +++ b/src/quemb/molbe/lo.py @@ -1,8 +1,18 @@ # Author(s): Henry Tran, Oinam Meitei, Shaun Weatherly # +import functools +import sys + import numpy +from numpy.linalg import eigh from pyscf.gto import intor_cross +from quemb.molbe.external.lo_helper import ( + get_aoind_by_atom, + reorder_by_atom_, +) +from quemb.molbe.helper import ncore_, unused + def dot_gen(A, B, ovlp): return A.T @ B if ovlp is None else A.T @ ovlp @ B @@ -208,3 +218,266 @@ def get_loc(mol, C, method, pop_method=None, init_guess=None): C_ = mlo.kernel() return C_ + + +def localize( + self, + lo_method, + mol=None, + valence_basis="sto-3g", + hstack=False, + pop_method=None, + init_guess=None, + valence_only=False, + nosave=False, +): + """Molecular orbital localization + + Performs molecular orbital localization computations. For large basis, + IAO is recommended augmented with PAO orbitals. + + Parameters + ---------- + lo_method : str + Localization method in quantum chemistry. 'lowdin', 'boys', and 'iao' + are supported. + mol : pyscf.gto.Molecule + pyscf.gto.Molecule object. + valence_basis: str + Name of minimal basis set for IAO scheme. 'sto-3g' suffice for most cases. + valence_only: bool + If this option is set to True, all calculation will be performed in the valence + basis in the IAO partitioning. + This is an experimental feature. + """ + if lo_method == "lowdin": + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + if self.unrestricted: + P_core = [ + numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core[s], self.S) + for s in [0, 1] + ] + C_ = numpy.dot(P_core, self.W) + Cpop = [ + functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) + for s in [0, 1] + ] + Cpop = [numpy.diag(Cpop[s]) for s in [0, 1]] + no_core_idx = [numpy.where(Cpop[s] > 0.7)[0] for s in [0, 1]] + C_ = [C_[s][:, no_core_idx[s]] for s in [0, 1]] + S_ = [ + functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) + for s in [0, 1] + ] + W_ = [] + for s in [0, 1]: + es_, vs_ = eigh(S_[s]) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_.append(functools.reduce(numpy.dot, (vs_, s_, vs_.T))) + self.W = [numpy.dot(C_[s], W_[s]) for s in [0, 1]] + else: + P_core = numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, self.W) + # NOTE: PYSCF has basis in 1s2s3s2p2p2p3p3p3p format + # fix no_core_idx - use population for now + Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + Cpop = numpy.diag(Cpop) + no_core_idx = numpy.where(Cpop > 0.7)[0] + C_ = C_[:, no_core_idx] + S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + es_, vs_ = eigh(S_) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) + self.W = numpy.dot(C_, W_) + + if self.unrestricted: + if self.frozen_core: + self.lmo_coeff_a = functools.reduce( + numpy.dot, (self.W[0].T, self.S, self.C_a[:, self.ncore :]) + ) + self.lmo_coeff_b = functools.reduce( + numpy.dot, (self.W[1].T, self.S, self.C_b[:, self.ncore :]) + ) + else: + self.lmo_coeff_a = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C_a) + ) + self.lmo_coeff_b = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C_b) + ) + else: + if self.frozen_core: + self.lmo_coeff = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C[:, self.ncore :]) + ) + else: + self.lmo_coeff = functools.reduce(numpy.dot, (self.W.T, self.S, self.C)) + + elif lo_method in ["pipek-mezey", "pipek", "PM"]: + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, W_) + Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + Cpop = numpy.diag(Cpop) + no_core_idx = numpy.where(Cpop > 0.55)[0] + C_ = C_[:, no_core_idx] + S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + es_, vs_ = eigh(S_) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) + W_ = numpy.dot(C_, W_) + + self.W = get_loc( + self.mol, W_, "PM", pop_method=pop_method, init_guess=init_guess + ) + + if not self.frozen_core: + self.lmo_coeff = self.W.T @ self.S @ self.C + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + + elif lo_method == "iao": + loc_type = "SO" + val_basis = "sto-3g" + + # Occupied mo_coeff (with core) + Co = self.C[:, : self.Nocc] + # Get necessary overlaps, second arg is IAO basis + S12, S2 = get_xovlp(self.mol, basis=val_basis) + # Use these to get IAOs + Ciao = get_iao(Co, S12, self.S, S2=S2) + + if not valence_only: + # Now get PAOs + if loc_type.upper() != "SO": + Cpao = get_pao(Ciao, self.S, S12, S2, self.mol) + elif loc_type.upper() == "SO": + Cpao = get_pao_native(Ciao, self.S, self.mol, valence_basis=val_basis) + + # rearrange by atom + aoind_by_atom = get_aoind_by_atom(self.mol) + Ciao, iaoind_by_atom = reorder_by_atom_(Ciao, aoind_by_atom, self.S) + + if not valence_only: + Cpao, paoind_by_atom = reorder_by_atom_(Cpao, aoind_by_atom, self.S) + + if self.frozen_core: + # Remove core MOs + Cc = self.C[:, : self.ncore] # Assumes core are first + Ciao = remove_core_mo(Ciao, Cc, self.S) + + # Localize orbitals beyond symm orth + if loc_type.upper() != "SO": + Ciao = get_loc(self.mol, Ciao, loc_type) + if not valence_only: + Cpao = get_loc(self.mol, Cpao, loc_type) + + shift = 0 + ncore = 0 + if not valence_only: + Wstack = numpy.zeros( + (Ciao.shape[0], Ciao.shape[1] + Cpao.shape[1]) + ) # -self.ncore)) + else: + Wstack = numpy.zeros((Ciao.shape[0], Ciao.shape[1])) + + if self.frozen_core: + for ix in range(self.mol.natm): + nc = ncore_(self.mol.atom_charge(ix)) + ncore += nc + niao = len(iaoind_by_atom[ix]) + iaoind_ix = [i_ - ncore for i_ in iaoind_by_atom[ix][nc:]] + Wstack[:, shift : shift + niao - nc] = Ciao[:, iaoind_ix] + shift += niao - nc + if not valence_only: + npao = len(paoind_by_atom[ix]) + Wstack[:, shift : shift + npao] = Cpao[:, paoind_by_atom[ix]] + shift += npao + else: + if not hstack: + for ix in range(self.mol.natm): + niao = len(iaoind_by_atom[ix]) + Wstack[:, shift : shift + niao] = Ciao[:, iaoind_by_atom[ix]] + shift += niao + if not valence_only: + npao = len(paoind_by_atom[ix]) + Wstack[:, shift : shift + npao] = Cpao[:, paoind_by_atom[ix]] + shift += npao + else: + Wstack = numpy.hstack((Ciao, Cpao)) + if not nosave: + self.W = Wstack + assert numpy.allclose( + self.W.T @ self.S @ self.W, numpy.eye(self.W.shape[1]) + ) + else: + assert numpy.allclose( + Wstack.T @ self.S @ Wstack, numpy.eye(Wstack.shape[1]) + ) + return Wstack + nmo = self.C.shape[1] - self.ncore + nlo = self.W.shape[1] + + if not valence_only: + if nmo > nlo: + Co_nocore = self.C[:, self.ncore : self.Nocc] + Cv = self.C[:, self.Nocc :] + # Ensure that the LOs span the occupied space + assert numpy.allclose( + numpy.sum((self.W.T @ self.S @ Co_nocore) ** 2.0), + self.Nocc - self.ncore, + ) + # Find virtual orbitals that lie in the span of LOs + u, l, vt = numpy.linalg.svd(self.W.T @ self.S @ Cv, full_matrices=False) + unused(u) + nvlo = nlo - self.Nocc - self.ncore + assert numpy.allclose(numpy.sum(l[:nvlo]), nvlo) + C_ = numpy.hstack([Co_nocore, Cv @ vt[:nvlo].T]) + self.lmo_coeff = self.W.T @ self.S @ C_ + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + + elif lo_method == "boys": + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, W_) + Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + Cpop = numpy.diag(Cpop) + no_core_idx = numpy.where(Cpop > 0.55)[0] + C_ = C_[:, no_core_idx] + S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + es_, vs_ = eigh(S_) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) + W_ = numpy.dot(C_, W_) + + self.W = get_loc(self.mol, W_, "BOYS") + + if not self.frozen_core: + self.lmo_coeff = self.W.T @ self.S @ self.C + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + + else: + print("lo_method = ", lo_method, " not implemented!", flush=True) + print("exiting", flush=True) + sys.exit() diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index 198aa137..a3345468 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -1,30 +1,14 @@ # Author(s): Oinam Romesh Meitei -import functools import os import pickle -import sys import h5py import numpy -from numpy.linalg import eigh, multi_dot, svd from pyscf import ao2mo from quemb.molbe.be_parallel import be_func_parallel from quemb.molbe.eri_onthefly import integral_direct_DF -from quemb.molbe.external.lo_helper import ( - get_aoind_by_atom, - reorder_by_atom_, -) -from quemb.molbe.helper import ncore_, unused -from quemb.molbe.lo import ( - get_iao, - get_loc, - get_pao, - get_pao_native, - get_xovlp, - remove_core_mo, -) from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func from quemb.shared import be_var @@ -320,274 +304,9 @@ def __init__( # cannot be moved to head of file. from quemb.molbe._opt import optimize # noqa: PLC0415 from quemb.molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 + from quemb.molbe.lo import localize # noqa: PLC0415 from quemb.molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 - def localize( - self, - lo_method, - mol=None, - valence_basis="sto-3g", - hstack=False, - pop_method=None, - init_guess=None, - valence_only=False, - nosave=False, - ): - """Molecular orbital localization - - Performs molecular orbital localization computations. For large basis, - IAO is recommended augmented with PAO orbitals. - - Parameters - ---------- - lo_method : str - Localization method in quantum chemistry. 'lowdin', 'boys', and 'iao' - are supported. - mol : pyscf.gto.Molecule - pyscf.gto.Molecule object. - valence_basis: str - Name of minimal basis set for IAO scheme. 'sto-3g' suffice for most cases. - valence_only: bool - If this option is set to True, all calculation will be performed in the valence - basis in the IAO partitioning. - This is an experimental feature. - """ - if lo_method == "lowdin": - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - if self.unrestricted: - P_core = [ - numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core[s], self.S) - for s in [0, 1] - ] - C_ = numpy.dot(P_core, self.W) - Cpop = [ - functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) - for s in [0, 1] - ] - Cpop = [numpy.diag(Cpop[s]) for s in [0, 1]] - no_core_idx = [numpy.where(Cpop[s] > 0.7)[0] for s in [0, 1]] - C_ = [C_[s][:, no_core_idx[s]] for s in [0, 1]] - S_ = [ - functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) - for s in [0, 1] - ] - W_ = [] - for s in [0, 1]: - es_, vs_ = eigh(S_[s]) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_.append(functools.reduce(numpy.dot, (vs_, s_, vs_.T))) - self.W = [numpy.dot(C_[s], W_[s]) for s in [0, 1]] - else: - P_core = numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, self.W) - # NOTE: PYSCF has basis in 1s2s3s2p2p2p3p3p3p format - # fix no_core_idx - use population for now - Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.7)[0] - C_ = C_[:, no_core_idx] - S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - es_, vs_ = eigh(S_) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - self.W = numpy.dot(C_, W_) - - if self.unrestricted: - if self.frozen_core: - self.lmo_coeff_a = functools.reduce( - numpy.dot, (self.W[0].T, self.S, self.C_a[:, self.ncore :]) - ) - self.lmo_coeff_b = functools.reduce( - numpy.dot, (self.W[1].T, self.S, self.C_b[:, self.ncore :]) - ) - else: - self.lmo_coeff_a = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C_a) - ) - self.lmo_coeff_b = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C_b) - ) - else: - if self.frozen_core: - self.lmo_coeff = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C[:, self.ncore :]) - ) - else: - self.lmo_coeff = multi_dot((self.W.T, self.S, self.C)) - - elif lo_method in ["pipek-mezey", "pipek", "PM"]: - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, W_) - Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.55)[0] - C_ = C_[:, no_core_idx] - S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - es_, vs_ = eigh(S_) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - W_ = numpy.dot(C_, W_) - - self.W = get_loc( - self.mol, W_, "PM", pop_method=pop_method, init_guess=init_guess - ) - - if not self.frozen_core: - self.lmo_coeff = self.W.T @ self.S @ self.C - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - - elif lo_method == "iao": - loc_type = "SO" - val_basis = "sto-3g" - - # Occupied mo_coeff (with core) - Co = self.C[:, : self.Nocc] - # Get necessary overlaps, second arg is IAO basis - S12, S2 = get_xovlp(self.mol, basis=val_basis) - # Use these to get IAOs - Ciao = get_iao(Co, S12, self.S, S2=S2) - - if not valence_only: - # Now get PAOs - if loc_type.upper() != "SO": - Cpao = get_pao(Ciao, self.S, S12, S2, self.mol) - elif loc_type.upper() == "SO": - Cpao = get_pao_native( - Ciao, self.S, self.mol, valence_basis=val_basis - ) - - # rearrange by atom - aoind_by_atom = get_aoind_by_atom(self.mol) - Ciao, iaoind_by_atom = reorder_by_atom_(Ciao, aoind_by_atom, self.S) - - if not valence_only: - Cpao, paoind_by_atom = reorder_by_atom_(Cpao, aoind_by_atom, self.S) - - if self.frozen_core: - # Remove core MOs - Cc = self.C[:, : self.ncore] # Assumes core are first - Ciao = remove_core_mo(Ciao, Cc, self.S) - - # Localize orbitals beyond symm orth - if loc_type.upper() != "SO": - Ciao = get_loc(self.mol, Ciao, loc_type) - if not valence_only: - Cpao = get_loc(self.mol, Cpao, loc_type) - - shift = 0 - ncore = 0 - if not valence_only: - Wstack = numpy.zeros( - (Ciao.shape[0], Ciao.shape[1] + Cpao.shape[1]) - ) # -self.ncore)) - else: - Wstack = numpy.zeros((Ciao.shape[0], Ciao.shape[1])) - - if self.frozen_core: - for ix in range(self.mol.natm): - nc = ncore_(self.mol.atom_charge(ix)) - ncore += nc - niao = len(iaoind_by_atom[ix]) - iaoind_ix = [i_ - ncore for i_ in iaoind_by_atom[ix][nc:]] - Wstack[:, shift : shift + niao - nc] = Ciao[:, iaoind_ix] - shift += niao - nc - if not valence_only: - npao = len(paoind_by_atom[ix]) - Wstack[:, shift : shift + npao] = Cpao[:, paoind_by_atom[ix]] - shift += npao - else: - if not hstack: - for ix in range(self.mol.natm): - niao = len(iaoind_by_atom[ix]) - Wstack[:, shift : shift + niao] = Ciao[:, iaoind_by_atom[ix]] - shift += niao - if not valence_only: - npao = len(paoind_by_atom[ix]) - Wstack[:, shift : shift + npao] = Cpao[ - :, paoind_by_atom[ix] - ] - shift += npao - else: - Wstack = numpy.hstack((Ciao, Cpao)) - if not nosave: - self.W = Wstack - assert numpy.allclose( - self.W.T @ self.S @ self.W, numpy.eye(self.W.shape[1]) - ) - else: - assert numpy.allclose( - Wstack.T @ self.S @ Wstack, numpy.eye(Wstack.shape[1]) - ) - return Wstack - nmo = self.C.shape[1] - self.ncore - nlo = self.W.shape[1] - - if not valence_only: - if nmo > nlo: - Co_nocore = self.C[:, self.ncore : self.Nocc] - Cv = self.C[:, self.Nocc :] - # Ensure that the LOs span the occupied space - assert numpy.allclose( - numpy.sum((self.W.T @ self.S @ Co_nocore) ** 2.0), - self.Nocc - self.ncore, - ) - # Find virtual orbitals that lie in the span of LOs - u, l, vt = svd(self.W.T @ self.S @ Cv, full_matrices=False) - unused(u) - nvlo = nlo - self.Nocc - self.ncore - assert numpy.allclose(numpy.sum(l[:nvlo]), nvlo) - C_ = numpy.hstack([Co_nocore, Cv @ vt[:nvlo].T]) - self.lmo_coeff = self.W.T @ self.S @ C_ - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - - elif lo_method == "boys": - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, W_) - Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.55)[0] - C_ = C_[:, no_core_idx] - S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - es_, vs_ = eigh(S_) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - W_ = numpy.dot(C_, W_) - - self.W = get_loc(self.mol, W_, "BOYS") - - if not self.frozen_core: - self.lmo_coeff = self.W.T @ self.S @ self.C - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - - else: - print("lo_method = ", lo_method, " not implemented!", flush=True) - print("exiting", flush=True) - sys.exit() - def print_ini(self): """ Print initialization banner for the MOLBE calculation. From a8fcf44dfa9f93254c06476dff963fcaaf442241 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 12:37:46 -0500 Subject: [PATCH 12/43] turned molBE localize into method via mixin --- src/quemb/molbe/lo.py | 463 +++++++++++++++++++++-------------------- src/quemb/molbe/mbe.py | 4 +- 2 files changed, 236 insertions(+), 231 deletions(-) diff --git a/src/quemb/molbe/lo.py b/src/quemb/molbe/lo.py index 23f35194..4d8fba6e 100644 --- a/src/quemb/molbe/lo.py +++ b/src/quemb/molbe/lo.py @@ -4,7 +4,7 @@ import sys import numpy -from numpy.linalg import eigh +from numpy.linalg import eigh, multi_dot, svd from pyscf.gto import intor_cross from quemb.molbe.external.lo_helper import ( @@ -220,264 +220,269 @@ def get_loc(mol, C, method, pop_method=None, init_guess=None): return C_ -def localize( - self, - lo_method, - mol=None, - valence_basis="sto-3g", - hstack=False, - pop_method=None, - init_guess=None, - valence_only=False, - nosave=False, -): - """Molecular orbital localization - - Performs molecular orbital localization computations. For large basis, - IAO is recommended augmented with PAO orbitals. - - Parameters - ---------- - lo_method : str - Localization method in quantum chemistry. 'lowdin', 'boys', and 'iao' - are supported. - mol : pyscf.gto.Molecule - pyscf.gto.Molecule object. - valence_basis: str - Name of minimal basis set for IAO scheme. 'sto-3g' suffice for most cases. - valence_only: bool - If this option is set to True, all calculation will be performed in the valence - basis in the IAO partitioning. - This is an experimental feature. - """ - if lo_method == "lowdin": - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - if self.unrestricted: - P_core = [ - numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core[s], self.S) - for s in [0, 1] - ] - C_ = numpy.dot(P_core, self.W) - Cpop = [ - functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) - for s in [0, 1] - ] - Cpop = [numpy.diag(Cpop[s]) for s in [0, 1]] - no_core_idx = [numpy.where(Cpop[s] > 0.7)[0] for s in [0, 1]] - C_ = [C_[s][:, no_core_idx[s]] for s in [0, 1]] - S_ = [ - functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) - for s in [0, 1] - ] - W_ = [] - for s in [0, 1]: - es_, vs_ = eigh(S_[s]) +class MixinLocalize: + def localize( + self, + lo_method, + mol=None, + valence_basis="sto-3g", + hstack=False, + pop_method=None, + init_guess=None, + valence_only=False, + nosave=False, + ): + """Molecular orbital localization + + Performs molecular orbital localization computations. For large basis, + IAO is recommended augmented with PAO orbitals. + + Parameters + ---------- + lo_method : str + Localization method in quantum chemistry. 'lowdin', 'boys', and 'iao' + are supported. + mol : pyscf.gto.Molecule + pyscf.gto.Molecule object. + valence_basis: str + Name of minimal basis set for IAO scheme. 'sto-3g' suffice for most cases. + valence_only: bool + If this option is set to True, all calculation will be performed in the valence + basis in the IAO partitioning. + This is an experimental feature. + """ + if lo_method == "lowdin": + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + if self.unrestricted: + P_core = [ + numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core[s], self.S) + for s in [0, 1] + ] + C_ = numpy.dot(P_core, self.W) + Cpop = [ + functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) + for s in [0, 1] + ] + Cpop = [numpy.diag(Cpop[s]) for s in [0, 1]] + no_core_idx = [numpy.where(Cpop[s] > 0.7)[0] for s in [0, 1]] + C_ = [C_[s][:, no_core_idx[s]] for s in [0, 1]] + S_ = [ + functools.reduce(numpy.dot, (C_[s].T, self.S, C_[s])) + for s in [0, 1] + ] + W_ = [] + for s in [0, 1]: + es_, vs_ = eigh(S_[s]) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_.append(functools.reduce(numpy.dot, (vs_, s_, vs_.T))) + self.W = [numpy.dot(C_[s], W_[s]) for s in [0, 1]] + else: + P_core = numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, self.W) + # NOTE: PYSCF has basis in 1s2s3s2p2p2p3p3p3p format + # fix no_core_idx - use population for now + Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + Cpop = numpy.diag(Cpop) + no_core_idx = numpy.where(Cpop > 0.7)[0] + C_ = C_[:, no_core_idx] + S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + es_, vs_ = eigh(S_) s_ = numpy.sqrt(es_) s_ = numpy.diag(1.0 / s_) - W_.append(functools.reduce(numpy.dot, (vs_, s_, vs_.T))) - self.W = [numpy.dot(C_[s], W_[s]) for s in [0, 1]] + W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) + self.W = numpy.dot(C_, W_) + + if self.unrestricted: + if self.frozen_core: + self.lmo_coeff_a = functools.reduce( + numpy.dot, (self.W[0].T, self.S, self.C_a[:, self.ncore :]) + ) + self.lmo_coeff_b = functools.reduce( + numpy.dot, (self.W[1].T, self.S, self.C_b[:, self.ncore :]) + ) + else: + self.lmo_coeff_a = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C_a) + ) + self.lmo_coeff_b = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C_b) + ) else: - P_core = numpy.eye(self.W.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, self.W) - # NOTE: PYSCF has basis in 1s2s3s2p2p2p3p3p3p format - # fix no_core_idx - use population for now + if self.frozen_core: + self.lmo_coeff = functools.reduce( + numpy.dot, (self.W.T, self.S, self.C[:, self.ncore :]) + ) + else: + self.lmo_coeff = multi_dot((self.W.T, self.S, self.C)) + + elif lo_method in ["pipek-mezey", "pipek", "PM"]: + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, W_) Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.7)[0] + no_core_idx = numpy.where(Cpop > 0.55)[0] C_ = C_[:, no_core_idx] S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) es_, vs_ = eigh(S_) s_ = numpy.sqrt(es_) s_ = numpy.diag(1.0 / s_) W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - self.W = numpy.dot(C_, W_) + W_ = numpy.dot(C_, W_) - if self.unrestricted: - if self.frozen_core: - self.lmo_coeff_a = functools.reduce( - numpy.dot, (self.W[0].T, self.S, self.C_a[:, self.ncore :]) - ) - self.lmo_coeff_b = functools.reduce( - numpy.dot, (self.W[1].T, self.S, self.C_b[:, self.ncore :]) - ) - else: - self.lmo_coeff_a = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C_a) - ) - self.lmo_coeff_b = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C_b) - ) - else: - if self.frozen_core: - self.lmo_coeff = functools.reduce( - numpy.dot, (self.W.T, self.S, self.C[:, self.ncore :]) - ) - else: - self.lmo_coeff = functools.reduce(numpy.dot, (self.W.T, self.S, self.C)) - - elif lo_method in ["pipek-mezey", "pipek", "PM"]: - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - self.W = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, W_) - Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.55)[0] - C_ = C_[:, no_core_idx] - S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - es_, vs_ = eigh(S_) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - W_ = numpy.dot(C_, W_) - - self.W = get_loc( - self.mol, W_, "PM", pop_method=pop_method, init_guess=init_guess - ) + self.W = get_loc( + self.mol, W_, "PM", pop_method=pop_method, init_guess=init_guess + ) - if not self.frozen_core: - self.lmo_coeff = self.W.T @ self.S @ self.C - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + if not self.frozen_core: + self.lmo_coeff = self.W.T @ self.S @ self.C + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - elif lo_method == "iao": - loc_type = "SO" - val_basis = "sto-3g" + elif lo_method == "iao": + loc_type = "SO" + val_basis = "sto-3g" - # Occupied mo_coeff (with core) - Co = self.C[:, : self.Nocc] - # Get necessary overlaps, second arg is IAO basis - S12, S2 = get_xovlp(self.mol, basis=val_basis) - # Use these to get IAOs - Ciao = get_iao(Co, S12, self.S, S2=S2) + # Occupied mo_coeff (with core) + Co = self.C[:, : self.Nocc] + # Get necessary overlaps, second arg is IAO basis + S12, S2 = get_xovlp(self.mol, basis=val_basis) + # Use these to get IAOs + Ciao = get_iao(Co, S12, self.S, S2=S2) - if not valence_only: - # Now get PAOs - if loc_type.upper() != "SO": - Cpao = get_pao(Ciao, self.S, S12, S2, self.mol) - elif loc_type.upper() == "SO": - Cpao = get_pao_native(Ciao, self.S, self.mol, valence_basis=val_basis) + if not valence_only: + # Now get PAOs + if loc_type.upper() != "SO": + Cpao = get_pao(Ciao, self.S, S12, S2, self.mol) + elif loc_type.upper() == "SO": + Cpao = get_pao_native( + Ciao, self.S, self.mol, valence_basis=val_basis + ) + + # rearrange by atom + aoind_by_atom = get_aoind_by_atom(self.mol) + Ciao, iaoind_by_atom = reorder_by_atom_(Ciao, aoind_by_atom, self.S) - # rearrange by atom - aoind_by_atom = get_aoind_by_atom(self.mol) - Ciao, iaoind_by_atom = reorder_by_atom_(Ciao, aoind_by_atom, self.S) + if not valence_only: + Cpao, paoind_by_atom = reorder_by_atom_(Cpao, aoind_by_atom, self.S) - if not valence_only: - Cpao, paoind_by_atom = reorder_by_atom_(Cpao, aoind_by_atom, self.S) + if self.frozen_core: + # Remove core MOs + Cc = self.C[:, : self.ncore] # Assumes core are first + Ciao = remove_core_mo(Ciao, Cc, self.S) - if self.frozen_core: - # Remove core MOs - Cc = self.C[:, : self.ncore] # Assumes core are first - Ciao = remove_core_mo(Ciao, Cc, self.S) + # Localize orbitals beyond symm orth + if loc_type.upper() != "SO": + Ciao = get_loc(self.mol, Ciao, loc_type) + if not valence_only: + Cpao = get_loc(self.mol, Cpao, loc_type) - # Localize orbitals beyond symm orth - if loc_type.upper() != "SO": - Ciao = get_loc(self.mol, Ciao, loc_type) + shift = 0 + ncore = 0 if not valence_only: - Cpao = get_loc(self.mol, Cpao, loc_type) - - shift = 0 - ncore = 0 - if not valence_only: - Wstack = numpy.zeros( - (Ciao.shape[0], Ciao.shape[1] + Cpao.shape[1]) - ) # -self.ncore)) - else: - Wstack = numpy.zeros((Ciao.shape[0], Ciao.shape[1])) - - if self.frozen_core: - for ix in range(self.mol.natm): - nc = ncore_(self.mol.atom_charge(ix)) - ncore += nc - niao = len(iaoind_by_atom[ix]) - iaoind_ix = [i_ - ncore for i_ in iaoind_by_atom[ix][nc:]] - Wstack[:, shift : shift + niao - nc] = Ciao[:, iaoind_ix] - shift += niao - nc - if not valence_only: - npao = len(paoind_by_atom[ix]) - Wstack[:, shift : shift + npao] = Cpao[:, paoind_by_atom[ix]] - shift += npao - else: - if not hstack: + Wstack = numpy.zeros( + (Ciao.shape[0], Ciao.shape[1] + Cpao.shape[1]) + ) # -self.ncore)) + else: + Wstack = numpy.zeros((Ciao.shape[0], Ciao.shape[1])) + + if self.frozen_core: for ix in range(self.mol.natm): + nc = ncore_(self.mol.atom_charge(ix)) + ncore += nc niao = len(iaoind_by_atom[ix]) - Wstack[:, shift : shift + niao] = Ciao[:, iaoind_by_atom[ix]] - shift += niao + iaoind_ix = [i_ - ncore for i_ in iaoind_by_atom[ix][nc:]] + Wstack[:, shift : shift + niao - nc] = Ciao[:, iaoind_ix] + shift += niao - nc if not valence_only: npao = len(paoind_by_atom[ix]) Wstack[:, shift : shift + npao] = Cpao[:, paoind_by_atom[ix]] shift += npao else: - Wstack = numpy.hstack((Ciao, Cpao)) - if not nosave: - self.W = Wstack - assert numpy.allclose( - self.W.T @ self.S @ self.W, numpy.eye(self.W.shape[1]) - ) - else: - assert numpy.allclose( - Wstack.T @ self.S @ Wstack, numpy.eye(Wstack.shape[1]) - ) - return Wstack - nmo = self.C.shape[1] - self.ncore - nlo = self.W.shape[1] - - if not valence_only: - if nmo > nlo: - Co_nocore = self.C[:, self.ncore : self.Nocc] - Cv = self.C[:, self.Nocc :] - # Ensure that the LOs span the occupied space + if not hstack: + for ix in range(self.mol.natm): + niao = len(iaoind_by_atom[ix]) + Wstack[:, shift : shift + niao] = Ciao[:, iaoind_by_atom[ix]] + shift += niao + if not valence_only: + npao = len(paoind_by_atom[ix]) + Wstack[:, shift : shift + npao] = Cpao[ + :, paoind_by_atom[ix] + ] + shift += npao + else: + Wstack = numpy.hstack((Ciao, Cpao)) + if not nosave: + self.W = Wstack assert numpy.allclose( - numpy.sum((self.W.T @ self.S @ Co_nocore) ** 2.0), - self.Nocc - self.ncore, + self.W.T @ self.S @ self.W, numpy.eye(self.W.shape[1]) ) - # Find virtual orbitals that lie in the span of LOs - u, l, vt = numpy.linalg.svd(self.W.T @ self.S @ Cv, full_matrices=False) - unused(u) - nvlo = nlo - self.Nocc - self.ncore - assert numpy.allclose(numpy.sum(l[:nvlo]), nvlo) - C_ = numpy.hstack([Co_nocore, Cv @ vt[:nvlo].T]) - self.lmo_coeff = self.W.T @ self.S @ C_ + else: + assert numpy.allclose( + Wstack.T @ self.S @ Wstack, numpy.eye(Wstack.shape[1]) + ) + return Wstack + nmo = self.C.shape[1] - self.ncore + nlo = self.W.shape[1] + + if not valence_only: + if nmo > nlo: + Co_nocore = self.C[:, self.ncore : self.Nocc] + Cv = self.C[:, self.Nocc :] + # Ensure that the LOs span the occupied space + assert numpy.allclose( + numpy.sum((self.W.T @ self.S @ Co_nocore) ** 2.0), + self.Nocc - self.ncore, + ) + # Find virtual orbitals that lie in the span of LOs + u, l, vt = svd(self.W.T @ self.S @ Cv, full_matrices=False) + unused(u) + nvlo = nlo - self.Nocc - self.ncore + assert numpy.allclose(numpy.sum(l[:nvlo]), nvlo) + C_ = numpy.hstack([Co_nocore, Cv @ vt[:nvlo].T]) + self.lmo_coeff = self.W.T @ self.S @ C_ + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] else: self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - - elif lo_method == "boys": - es_, vs_ = eigh(self.S) - edx = es_ > 1.0e-15 - W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) - if self.frozen_core: - P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) - C_ = numpy.dot(P_core, W_) - Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - Cpop = numpy.diag(Cpop) - no_core_idx = numpy.where(Cpop > 0.55)[0] - C_ = C_[:, no_core_idx] - S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) - es_, vs_ = eigh(S_) - s_ = numpy.sqrt(es_) - s_ = numpy.diag(1.0 / s_) - W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) - W_ = numpy.dot(C_, W_) - - self.W = get_loc(self.mol, W_, "BOYS") - - if not self.frozen_core: - self.lmo_coeff = self.W.T @ self.S @ self.C - else: - self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] - else: - print("lo_method = ", lo_method, " not implemented!", flush=True) - print("exiting", flush=True) - sys.exit() + elif lo_method == "boys": + es_, vs_ = eigh(self.S) + edx = es_ > 1.0e-15 + W_ = numpy.dot(vs_[:, edx] / numpy.sqrt(es_[edx]), vs_[:, edx].T) + if self.frozen_core: + P_core = numpy.eye(W_.shape[0]) - numpy.dot(self.P_core, self.S) + C_ = numpy.dot(P_core, W_) + Cpop = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + Cpop = numpy.diag(Cpop) + no_core_idx = numpy.where(Cpop > 0.55)[0] + C_ = C_[:, no_core_idx] + S_ = functools.reduce(numpy.dot, (C_.T, self.S, C_)) + es_, vs_ = eigh(S_) + s_ = numpy.sqrt(es_) + s_ = numpy.diag(1.0 / s_) + W_ = functools.reduce(numpy.dot, (vs_, s_, vs_.T)) + W_ = numpy.dot(C_, W_) + + self.W = get_loc(self.mol, W_, "BOYS") + + if not self.frozen_core: + self.lmo_coeff = self.W.T @ self.S @ self.C + else: + self.lmo_coeff = self.W.T @ self.S @ self.C[:, self.ncore :] + + else: + print("lo_method = ", lo_method, " not implemented!", flush=True) + print("exiting", flush=True) + sys.exit() diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index a3345468..ccaa8dd7 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -9,6 +9,7 @@ from quemb.molbe.be_parallel import be_func_parallel from quemb.molbe.eri_onthefly import integral_direct_DF +from quemb.molbe.lo import MixinLocalize from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func from quemb.shared import be_var @@ -50,7 +51,7 @@ def __init__( self.mo_energy = mo_energy -class BE: +class BE(MixinLocalize): """ Class for handling bootstrap embedding (BE) calculations. @@ -304,7 +305,6 @@ def __init__( # cannot be moved to head of file. from quemb.molbe._opt import optimize # noqa: PLC0415 from quemb.molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 - from quemb.molbe.lo import localize # noqa: PLC0415 from quemb.molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 def print_ini(self): From d6a615f4a64d706db6636de51ce22ec04540bb08 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 12:40:07 -0500 Subject: [PATCH 13/43] moved molbe.external to shared.external --- docs/source/optimize.rst | 2 +- src/quemb/kbe/lo.py | 2 +- src/quemb/kbe/pbe.py | 2 +- src/quemb/molbe/_opt.py | 2 +- src/quemb/molbe/be_parallel.py | 4 ++-- src/quemb/molbe/lo.py | 2 +- src/quemb/molbe/mbe.py | 2 +- src/quemb/molbe/solver.py | 6 +++--- src/quemb/{molbe => shared}/external/__init__.py | 0 src/quemb/{molbe => shared}/external/ccsd_rdm.py | 0 src/quemb/{molbe => shared}/external/cphf_utils.py | 0 src/quemb/{molbe => shared}/external/cpmp2_utils.py | 4 ++-- src/quemb/{molbe => shared}/external/jac_utils.py | 4 ++-- src/quemb/{molbe => shared}/external/lo_helper.py | 0 src/quemb/{molbe => shared}/external/optqn.py | 6 +++--- src/quemb/{molbe => shared}/external/restore_eri_addition.c | 0 src/quemb/{molbe => shared}/external/uccsd_eri.py | 0 src/quemb/{molbe => shared}/external/unrestricted_utils.py | 0 18 files changed, 18 insertions(+), 18 deletions(-) rename src/quemb/{molbe => shared}/external/__init__.py (100%) rename src/quemb/{molbe => shared}/external/ccsd_rdm.py (100%) rename src/quemb/{molbe => shared}/external/cphf_utils.py (100%) rename src/quemb/{molbe => shared}/external/cpmp2_utils.py (98%) rename src/quemb/{molbe => shared}/external/jac_utils.py (97%) rename src/quemb/{molbe => shared}/external/lo_helper.py (100%) rename src/quemb/{molbe => shared}/external/optqn.py (98%) rename src/quemb/{molbe => shared}/external/restore_eri_addition.c (100%) rename src/quemb/{molbe => shared}/external/uccsd_eri.py (100%) rename src/quemb/{molbe => shared}/external/unrestricted_utils.py (100%) diff --git a/docs/source/optimize.rst b/docs/source/optimize.rst index 09919db5..14f46426 100644 --- a/docs/source/optimize.rst +++ b/docs/source/optimize.rst @@ -12,4 +12,4 @@ Main BE optimization Quasi-Newton optimization ========================= -.. autoclass:: quemb.molbe.external.optqn.FrankQN +.. autoclass:: quemb.shared.external.optqn.FrankQN diff --git a/src/quemb/kbe/lo.py b/src/quemb/kbe/lo.py index c2282dff..038e2cef 100644 --- a/src/quemb/kbe/lo.py +++ b/src/quemb/kbe/lo.py @@ -17,7 +17,7 @@ remove_core_mo_k, symm_orth_k, ) -from quemb.molbe.external.lo_helper import get_aoind_by_atom, reorder_by_atom_ +from quemb.shared.external.lo_helper import get_aoind_by_atom, reorder_by_atom_ from quemb.molbe.helper import ncore_, unused diff --git a/src/quemb/kbe/pbe.py b/src/quemb/kbe/pbe.py index 17321bdc..fc46f52d 100644 --- a/src/quemb/kbe/pbe.py +++ b/src/quemb/kbe/pbe.py @@ -327,7 +327,7 @@ def __init__( # The following import of these functions turns them into # proper methods of the class. from quemb.kbe._opt import optimize # noqa: PLC0415 - from quemb.molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 + from quemb.shared.external.optqn import get_be_error_jacobian # noqa: PLC0415 def print_ini(self): """ diff --git a/src/quemb/molbe/_opt.py b/src/quemb/molbe/_opt.py index e23ddd89..0b7def59 100644 --- a/src/quemb/molbe/_opt.py +++ b/src/quemb/molbe/_opt.py @@ -5,7 +5,7 @@ import numpy from quemb.molbe.be_parallel import be_func_parallel -from quemb.molbe.external.optqn import FrankQN +from quemb.shared.external.optqn import FrankQN from quemb.molbe.misc import print_energy from quemb.molbe.solver import be_func diff --git a/src/quemb/molbe/be_parallel.py b/src/quemb/molbe/be_parallel.py index 9813fb02..16a6b5fc 100644 --- a/src/quemb/molbe/be_parallel.py +++ b/src/quemb/molbe/be_parallel.py @@ -8,8 +8,8 @@ import numpy from pyscf import ao2mo, fci, mcscf -from quemb.molbe.external.ccsd_rdm import make_rdm1_uccsd, make_rdm2_uccsd -from quemb.molbe.external.unrestricted_utils import make_uhf_obj +from quemb.shared.external.ccsd_rdm import make_rdm1_uccsd, make_rdm2_uccsd +from quemb.shared.external.unrestricted_utils import make_uhf_obj from quemb.molbe.helper import ( get_eri, get_frag_energy, diff --git a/src/quemb/molbe/lo.py b/src/quemb/molbe/lo.py index 4d8fba6e..5b047cd9 100644 --- a/src/quemb/molbe/lo.py +++ b/src/quemb/molbe/lo.py @@ -7,7 +7,7 @@ from numpy.linalg import eigh, multi_dot, svd from pyscf.gto import intor_cross -from quemb.molbe.external.lo_helper import ( +from quemb.shared.external.lo_helper import ( get_aoind_by_atom, reorder_by_atom_, ) diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index ccaa8dd7..2c348b4f 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -304,7 +304,7 @@ def __init__( # The following imports turn the imported functions into proper methods # cannot be moved to head of file. from quemb.molbe._opt import optimize # noqa: PLC0415 - from quemb.molbe.external.optqn import get_be_error_jacobian # noqa: PLC0415 + from quemb.shared.external.optqn import get_be_error_jacobian # noqa: PLC0415 from quemb.molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 def print_ini(self): diff --git a/src/quemb/molbe/solver.py b/src/quemb/molbe/solver.py index d06a7480..98bbdb8c 100644 --- a/src/quemb/molbe/solver.py +++ b/src/quemb/molbe/solver.py @@ -8,14 +8,14 @@ from pyscf import ao2mo, cc, fci, mcscf, mp from pyscf.cc.ccsd_rdm import make_rdm2 -from quemb.molbe.external.ccsd_rdm import ( +from quemb.shared.external.ccsd_rdm import ( make_rdm1_ccsd_t1, make_rdm1_uccsd, make_rdm2_uccsd, make_rdm2_urlx, ) -from quemb.molbe.external.uccsd_eri import make_eris_incore -from quemb.molbe.external.unrestricted_utils import make_uhf_obj +from quemb.shared.external.uccsd_eri import make_eris_incore +from quemb.shared.external.unrestricted_utils import make_uhf_obj from quemb.molbe.helper import get_frag_energy, get_frag_energy_u, unused from quemb.shared import be_var diff --git a/src/quemb/molbe/external/__init__.py b/src/quemb/shared/external/__init__.py similarity index 100% rename from src/quemb/molbe/external/__init__.py rename to src/quemb/shared/external/__init__.py diff --git a/src/quemb/molbe/external/ccsd_rdm.py b/src/quemb/shared/external/ccsd_rdm.py similarity index 100% rename from src/quemb/molbe/external/ccsd_rdm.py rename to src/quemb/shared/external/ccsd_rdm.py diff --git a/src/quemb/molbe/external/cphf_utils.py b/src/quemb/shared/external/cphf_utils.py similarity index 100% rename from src/quemb/molbe/external/cphf_utils.py rename to src/quemb/shared/external/cphf_utils.py diff --git a/src/quemb/molbe/external/cpmp2_utils.py b/src/quemb/shared/external/cpmp2_utils.py similarity index 98% rename from src/quemb/molbe/external/cpmp2_utils.py rename to src/quemb/shared/external/cpmp2_utils.py index 6499991f..fb8c149b 100644 --- a/src/quemb/molbe/external/cpmp2_utils.py +++ b/src/quemb/shared/external/cpmp2_utils.py @@ -7,8 +7,8 @@ import scipy.linalg as slg from pyscf import ao2mo, scf -from quemb.molbe.external.cphf_utils import cphf_kernel_batch as cphf_kernel -from quemb.molbe.external.cphf_utils import get_cpuhf_u_batch as cpuhf_kernel +from quemb.shared.external.cphf_utils import cphf_kernel_batch as cphf_kernel +from quemb.shared.external.cphf_utils import get_cpuhf_u_batch as cpuhf_kernel """ RMP2 implementation """ diff --git a/src/quemb/molbe/external/jac_utils.py b/src/quemb/shared/external/jac_utils.py similarity index 97% rename from src/quemb/molbe/external/jac_utils.py rename to src/quemb/shared/external/jac_utils.py index 16988f2d..05028f47 100644 --- a/src/quemb/molbe/external/jac_utils.py +++ b/src/quemb/shared/external/jac_utils.py @@ -6,8 +6,8 @@ import numpy as np from pyscf import ao2mo -from quemb.molbe.external.cphf_utils import cphf_kernel_batch -from quemb.molbe.external.cpmp2_utils import get_dF_r, get_Diajb_r, get_dmoe_F_r +from quemb.shared.external.cphf_utils import cphf_kernel_batch +from quemb.shared.external.cpmp2_utils import get_dF_r, get_Diajb_r, get_dmoe_F_r """ Derivative of approximate t1 amplitudes t_ia = ((2*t2-t2)_ibjc g_cjba - g_ikbj (2*t2-t2)_jbka) / (e_i - e_a) diff --git a/src/quemb/molbe/external/lo_helper.py b/src/quemb/shared/external/lo_helper.py similarity index 100% rename from src/quemb/molbe/external/lo_helper.py rename to src/quemb/shared/external/lo_helper.py diff --git a/src/quemb/molbe/external/optqn.py b/src/quemb/shared/external/optqn.py similarity index 98% rename from src/quemb/molbe/external/optqn.py rename to src/quemb/shared/external/optqn.py index b4b1a62a..a383fab4 100644 --- a/src/quemb/molbe/external/optqn.py +++ b/src/quemb/shared/external/optqn.py @@ -8,9 +8,9 @@ import numpy -from quemb.molbe.external.cphf_utils import cphf_kernel_batch, get_rhf_dP_from_u -from quemb.molbe.external.cpmp2_utils import get_dPmp2_batch_r -from quemb.molbe.external.jac_utils import get_dPccsdurlx_batch_u +from quemb.shared.external.cphf_utils import cphf_kernel_batch, get_rhf_dP_from_u +from quemb.shared.external.cpmp2_utils import get_dPmp2_batch_r +from quemb.shared.external.jac_utils import get_dPccsdurlx_batch_u from quemb.molbe.helper import get_eri, get_scfObj from quemb.shared import be_var diff --git a/src/quemb/molbe/external/restore_eri_addition.c b/src/quemb/shared/external/restore_eri_addition.c similarity index 100% rename from src/quemb/molbe/external/restore_eri_addition.c rename to src/quemb/shared/external/restore_eri_addition.c diff --git a/src/quemb/molbe/external/uccsd_eri.py b/src/quemb/shared/external/uccsd_eri.py similarity index 100% rename from src/quemb/molbe/external/uccsd_eri.py rename to src/quemb/shared/external/uccsd_eri.py diff --git a/src/quemb/molbe/external/unrestricted_utils.py b/src/quemb/shared/external/unrestricted_utils.py similarity index 100% rename from src/quemb/molbe/external/unrestricted_utils.py rename to src/quemb/shared/external/unrestricted_utils.py From 5ab89e83dd4984ef80e148855a4d72b129dd92ae Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 12:59:24 -0500 Subject: [PATCH 14/43] turned get_be_error_jacobian into free function --- TODO | 2 ++ src/quemb/kbe/lo.py | 2 +- src/quemb/kbe/pbe.py | 5 ++++- src/quemb/molbe/_opt.py | 2 +- src/quemb/molbe/be_parallel.py | 4 ++-- src/quemb/molbe/lo.py | 2 +- src/quemb/molbe/mbe.py | 5 ++++- src/quemb/molbe/solver.py | 4 ++-- src/quemb/shared/external/optqn.py | 26 +++++++++++++------------- 9 files changed, 30 insertions(+), 22 deletions(-) diff --git a/TODO b/TODO index d8a54832..acd003cd 100644 --- a/TODO +++ b/TODO @@ -1 +1,3 @@ turn quemb.pbe.lo.KMF into dataclass (what it is) + +unify kbe and be pfrags.Frags class diff --git a/src/quemb/kbe/lo.py b/src/quemb/kbe/lo.py index 038e2cef..c35ea946 100644 --- a/src/quemb/kbe/lo.py +++ b/src/quemb/kbe/lo.py @@ -17,8 +17,8 @@ remove_core_mo_k, symm_orth_k, ) -from quemb.shared.external.lo_helper import get_aoind_by_atom, reorder_by_atom_ from quemb.molbe.helper import ncore_, unused +from quemb.shared.external.lo_helper import get_aoind_by_atom, reorder_by_atom_ class KMF: diff --git a/src/quemb/kbe/pbe.py b/src/quemb/kbe/pbe.py index fc46f52d..298d8909 100644 --- a/src/quemb/kbe/pbe.py +++ b/src/quemb/kbe/pbe.py @@ -16,6 +16,7 @@ from quemb.kbe.misc import storePBE from quemb.kbe.pfrag import Frags from quemb.shared import be_var +from quemb.shared.external.optqn import get_be_error_jacobian class BE(Mixin_k_Localize): @@ -327,7 +328,9 @@ def __init__( # The following import of these functions turns them into # proper methods of the class. from quemb.kbe._opt import optimize # noqa: PLC0415 - from quemb.shared.external.optqn import get_be_error_jacobian # noqa: PLC0415 + + def get_be_error_jacobian(self, jac_solver="HF"): + return get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) def print_ini(self): """ diff --git a/src/quemb/molbe/_opt.py b/src/quemb/molbe/_opt.py index 0b7def59..989548c7 100644 --- a/src/quemb/molbe/_opt.py +++ b/src/quemb/molbe/_opt.py @@ -5,9 +5,9 @@ import numpy from quemb.molbe.be_parallel import be_func_parallel -from quemb.shared.external.optqn import FrankQN from quemb.molbe.misc import print_energy from quemb.molbe.solver import be_func +from quemb.shared.external.optqn import FrankQN class BEOPT: diff --git a/src/quemb/molbe/be_parallel.py b/src/quemb/molbe/be_parallel.py index 16a6b5fc..22f2738f 100644 --- a/src/quemb/molbe/be_parallel.py +++ b/src/quemb/molbe/be_parallel.py @@ -8,8 +8,6 @@ import numpy from pyscf import ao2mo, fci, mcscf -from quemb.shared.external.ccsd_rdm import make_rdm1_uccsd, make_rdm2_uccsd -from quemb.shared.external.unrestricted_utils import make_uhf_obj from quemb.molbe.helper import ( get_eri, get_frag_energy, @@ -25,6 +23,8 @@ solve_mp2, solve_uccsd, ) +from quemb.shared.external.ccsd_rdm import make_rdm1_uccsd, make_rdm2_uccsd +from quemb.shared.external.unrestricted_utils import make_uhf_obj def run_solver( diff --git a/src/quemb/molbe/lo.py b/src/quemb/molbe/lo.py index 5b047cd9..9f37b6e3 100644 --- a/src/quemb/molbe/lo.py +++ b/src/quemb/molbe/lo.py @@ -7,11 +7,11 @@ from numpy.linalg import eigh, multi_dot, svd from pyscf.gto import intor_cross +from quemb.molbe.helper import ncore_, unused from quemb.shared.external.lo_helper import ( get_aoind_by_atom, reorder_by_atom_, ) -from quemb.molbe.helper import ncore_, unused def dot_gen(A, B, ovlp): diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index 2c348b4f..610d7d8a 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -13,6 +13,7 @@ from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func from quemb.shared import be_var +from quemb.shared.external.optqn import get_be_error_jacobian class storeBE: @@ -304,9 +305,11 @@ def __init__( # The following imports turn the imported functions into proper methods # cannot be moved to head of file. from quemb.molbe._opt import optimize # noqa: PLC0415 - from quemb.shared.external.optqn import get_be_error_jacobian # noqa: PLC0415 from quemb.molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 + def get_be_error_jacobian(self, jac_solver="HF"): + return get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) + def print_ini(self): """ Print initialization banner for the MOLBE calculation. diff --git a/src/quemb/molbe/solver.py b/src/quemb/molbe/solver.py index 98bbdb8c..c34470db 100644 --- a/src/quemb/molbe/solver.py +++ b/src/quemb/molbe/solver.py @@ -8,6 +8,8 @@ from pyscf import ao2mo, cc, fci, mcscf, mp from pyscf.cc.ccsd_rdm import make_rdm2 +from quemb.molbe.helper import get_frag_energy, get_frag_energy_u, unused +from quemb.shared import be_var from quemb.shared.external.ccsd_rdm import ( make_rdm1_ccsd_t1, make_rdm1_uccsd, @@ -16,8 +18,6 @@ ) from quemb.shared.external.uccsd_eri import make_eris_incore from quemb.shared.external.unrestricted_utils import make_uhf_obj -from quemb.molbe.helper import get_frag_energy, get_frag_energy_u, unused -from quemb.shared import be_var def be_func( diff --git a/src/quemb/shared/external/optqn.py b/src/quemb/shared/external/optqn.py index a383fab4..323f29c4 100644 --- a/src/quemb/shared/external/optqn.py +++ b/src/quemb/shared/external/optqn.py @@ -8,11 +8,11 @@ import numpy +from quemb.molbe.helper import get_eri, get_scfObj +from quemb.shared import be_var from quemb.shared.external.cphf_utils import cphf_kernel_batch, get_rhf_dP_from_u from quemb.shared.external.cpmp2_utils import get_dPmp2_batch_r from quemb.shared.external.jac_utils import get_dPccsdurlx_batch_u -from quemb.molbe.helper import get_eri, get_scfObj -from quemb.shared import be_var def line_search_LF(func, xold, fold, dx, iter_): @@ -250,13 +250,13 @@ def get_Bnfn(self, n): return vs[0] -def get_be_error_jacobian(self, jac_solver="HF"): - Jes = [None] * self.Nfrag - Jcs = [None] * self.Nfrag - xes = [None] * self.Nfrag - xcs = [None] * self.Nfrag - ys = [None] * self.Nfrag - alphas = [None] * self.Nfrag +def get_be_error_jacobian(Nfrag: int, Fobjs, jac_solver: str = "HF"): + Jes = [None] * Nfrag + Jcs = [None] * Nfrag + xes = [None] * Nfrag + xcs = [None] * Nfrag + ys = [None] * Nfrag + alphas = [None] * Nfrag if jac_solver == "MP2": res_func = mp2res_func @@ -265,10 +265,10 @@ def get_be_error_jacobian(self, jac_solver="HF"): elif jac_solver == "HF": res_func = hfres_func - Ncout = [None] * self.Nfrag - for A in range(self.Nfrag): + Ncout = [None] * Nfrag + for A in range(Nfrag): Jes[A], Jcs[A], xes[A], xcs[A], ys[A], alphas[A], Ncout[A] = ( - get_atbe_Jblock_frag(self.Fobjs[A], res_func) + get_atbe_Jblock_frag(Fobjs[A], res_func) ) alpha = sum(alphas) @@ -288,7 +288,7 @@ def get_be_error_jacobian(self, jac_solver="HF"): J = numpy.zeros((N_ + 1, N_ + 1)) cout = 0 - for findx, fobj in enumerate(self.Fobjs): + for findx, fobj in enumerate(Fobjs): J[cout : Ncout[findx] + cout, cout : Ncout[findx] + cout] = Jes[findx] J[cout : Ncout[findx] + cout, N_:] = numpy.array(xes[findx]).reshape(-1, 1) J[N_:, cout : Ncout[findx] + cout] = ys[findx] From 727e061b02651b3375e6df328681ced54dc7aaa2 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 13:05:07 -0500 Subject: [PATCH 15/43] moved definition of molbe.optimize --- src/quemb/molbe/_opt.py | 110 --------------------------------------- src/quemb/molbe/mbe.py | 112 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 111 deletions(-) diff --git a/src/quemb/molbe/_opt.py b/src/quemb/molbe/_opt.py index 989548c7..b4c4552e 100644 --- a/src/quemb/molbe/_opt.py +++ b/src/quemb/molbe/_opt.py @@ -5,7 +5,6 @@ import numpy from quemb.molbe.be_parallel import be_func_parallel -from quemb.molbe.misc import print_energy from quemb.molbe.solver import be_func from quemb.shared.external.optqn import FrankQN @@ -232,112 +231,3 @@ def optimize(self, method, J0=None, trust_region=False): else: print("This optimization method for BE is not supported") sys.exit() - - -def optimize( - self, - solver="MP2", - method="QN", - only_chem=False, - conv_tol=1.0e-6, - relax_density=False, - use_cumulant=True, - J0=None, - nproc=1, - ompnum=4, - max_iter=500, - scratch_dir=None, - trust_region=False, - **solver_kwargs, -): - """BE optimization function - - Interfaces BEOPT to perform bootstrap embedding optimization. - - Parameters - ---------- - solver : str, optional - High-level solver for the fragment, by default 'MP2' - method : str, optional - Optimization method, by default 'QN' - only_chem : bool, optional - If true, density matching is not performed -- only global chemical potential - is optimized, by default False - conv_tol : _type_, optional - Convergence tolerance, by default 1.e-6 - relax_density : bool, optional - Whether to use relaxed or unrelaxed densities, by default False - This option is for using CCSD as solver. Relaxed density here uses - Lambda amplitudes, whereas unrelaxed density only uses T amplitudes. - c.f. See http://classic.chem.msu.su/cgi-bin/ceilidh.exe/gran/gamess/forum/?C34df668afbHW-7216-1405+00.htm - for the distinction between the two - use_cumulant : bool, optional - Use cumulant-based energy expression, by default True - max_iter : int, optional - Maximum number of optimization steps, by default 500 - nproc : int - Total number of processors assigned for the optimization. Defaults to 1. - When nproc > 1, Python multithreading - is invoked. - ompnum : int - If nproc > 1, ompnum sets the number of cores for OpenMP parallelization. - Defaults to 4 - J0 : list of list of float - Initial Jacobian. - trust_region : bool, optional - Use trust-region based QN optimization, by default False - """ - # Check if only chemical potential optimization is required - if not only_chem: - pot = self.pot - if self.be_type == "be1": - raise ValueError( - "BE1 only works with chemical potential optimization. " - "Set only_chem=True" - ) - else: - pot = [0.0] - - # Initialize the BEOPT object - be_ = BEOPT( - pot, - self.Fobjs, - self.Nocc, - self.enuc, - hf_veff=self.hf_veff, - nproc=nproc, - ompnum=ompnum, - scratch_dir=scratch_dir, - max_space=max_iter, - conv_tol=conv_tol, - only_chem=only_chem, - hci_cutoff=self.hci_cutoff, - ci_coeff_cutoff=self.ci_coeff_cutoff, - relax_density=relax_density, - select_cutoff=self.select_cutoff, - hci_pt=self.hci_pt, - solver=solver, - ecore=self.E_core, - ebe_hf=self.ebe_hf, - **solver_kwargs, - ) - - if method == "QN": - # Prepare the initial Jacobian matrix - if only_chem: - J0 = [[0.0]] - J0 = self.get_be_error_jacobian(jac_solver="HF") - J0 = [[J0[-1, -1]]] - else: - J0 = self.get_be_error_jacobian(jac_solver="HF") - - # Perform the optimization - be_.optimize(method, J0=J0, trust_region=trust_region) - self.ebe_tot = self.ebe_hf + be_.Ebe[0] - # Print the energy components - print_energy( - be_.Ebe[0], be_.Ebe[1][1], be_.Ebe[1][0] + be_.Ebe[1][2], self.ebe_hf - ) - else: - print("This optimization method for BE is not supported") - sys.exit() diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index 610d7d8a..54459d3b 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -2,14 +2,17 @@ import os import pickle +import sys import h5py import numpy from pyscf import ao2mo +from quemb.molbe._opt import BEOPT from quemb.molbe.be_parallel import be_func_parallel from quemb.molbe.eri_onthefly import integral_direct_DF from quemb.molbe.lo import MixinLocalize +from quemb.molbe.misc import print_energy from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func from quemb.shared import be_var @@ -304,9 +307,116 @@ def __init__( # The following imports turn the imported functions into proper methods # cannot be moved to head of file. - from quemb.molbe._opt import optimize # noqa: PLC0415 from quemb.molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 + def optimize( + self, + solver="MP2", + method="QN", + only_chem=False, + conv_tol=1.0e-6, + relax_density=False, + use_cumulant=True, + J0=None, + nproc=1, + ompnum=4, + max_iter=500, + scratch_dir=None, + trust_region=False, + **solver_kwargs, + ): + """BE optimization function + + Interfaces BEOPT to perform bootstrap embedding optimization. + + Parameters + ---------- + solver : str, optional + High-level solver for the fragment, by default 'MP2' + method : str, optional + Optimization method, by default 'QN' + only_chem : bool, optional + If true, density matching is not performed -- only global chemical potential + is optimized, by default False + conv_tol : _type_, optional + Convergence tolerance, by default 1.e-6 + relax_density : bool, optional + Whether to use relaxed or unrelaxed densities, by default False + This option is for using CCSD as solver. Relaxed density here uses + Lambda amplitudes, whereas unrelaxed density only uses T amplitudes. + c.f. See http://classic.chem.msu.su/cgi-bin/ceilidh.exe/gran/gamess/forum/?C34df668afbHW-7216-1405+00.htm + for the distinction between the two + use_cumulant : bool, optional + Use cumulant-based energy expression, by default True + max_iter : int, optional + Maximum number of optimization steps, by default 500 + nproc : int + Total number of processors assigned for the optimization. Defaults to 1. + When nproc > 1, Python multithreading + is invoked. + ompnum : int + If nproc > 1, ompnum sets the number of cores for OpenMP parallelization. + Defaults to 4 + J0 : list of list of float + Initial Jacobian. + trust_region : bool, optional + Use trust-region based QN optimization, by default False + """ + # Check if only chemical potential optimization is required + if not only_chem: + pot = self.pot + if self.be_type == "be1": + raise ValueError( + "BE1 only works with chemical potential optimization. " + "Set only_chem=True" + ) + else: + pot = [0.0] + + # Initialize the BEOPT object + be_ = BEOPT( + pot, + self.Fobjs, + self.Nocc, + self.enuc, + hf_veff=self.hf_veff, + nproc=nproc, + ompnum=ompnum, + scratch_dir=scratch_dir, + max_space=max_iter, + conv_tol=conv_tol, + only_chem=only_chem, + hci_cutoff=self.hci_cutoff, + ci_coeff_cutoff=self.ci_coeff_cutoff, + relax_density=relax_density, + select_cutoff=self.select_cutoff, + hci_pt=self.hci_pt, + solver=solver, + ecore=self.E_core, + ebe_hf=self.ebe_hf, + **solver_kwargs, + ) + + if method == "QN": + # Prepare the initial Jacobian matrix + if only_chem: + J0 = [[0.0]] + J0 = self.get_be_error_jacobian(jac_solver="HF") + J0 = [[J0[-1, -1]]] + else: + J0 = self.get_be_error_jacobian(jac_solver="HF") + + # Perform the optimization + be_.optimize(method, J0=J0, trust_region=trust_region) + self.ebe_tot = self.ebe_hf + be_.Ebe[0] + # Print the energy components + print_energy( + be_.Ebe[0], be_.Ebe[1][1], be_.Ebe[1][0] + be_.Ebe[1][2], self.ebe_hf + ) + else: + print("This optimization method for BE is not supported") + sys.exit() + def get_be_error_jacobian(self, jac_solver="HF"): return get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) From f6198ce2c030f8ef81d1544ec63e90a25acbf95e Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 13:14:43 -0500 Subject: [PATCH 16/43] added ISSUES and TODOs --- ISSUES | 1 + TODO | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 ISSUES diff --git a/ISSUES b/ISSUES new file mode 100644 index 00000000..0a7f39ed --- /dev/null +++ b/ISSUES @@ -0,0 +1 @@ +kbe compute_energy_full is called, but never defined diff --git a/TODO b/TODO index acd003cd..b67578fe 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ turn quemb.pbe.lo.KMF into dataclass (what it is) unify kbe and be pfrags.Frags class + +add non-trivial test for RDM elements to molbe_octane_get_rdms_test.py From 187ffce2a4ae13f499e52844acbacb6c1871b586 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 13:17:16 -0500 Subject: [PATCH 17/43] moved molbe RDM methods into the molbe class --- src/quemb/molbe/mbe.py | 362 ++++++++++++++++++++++++++++++++++++++++- src/quemb/molbe/rdm.py | 345 --------------------------------------- 2 files changed, 356 insertions(+), 351 deletions(-) delete mode 100644 src/quemb/molbe/rdm.py diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index 54459d3b..e74a4841 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -6,7 +6,7 @@ import h5py import numpy -from pyscf import ao2mo +from pyscf import ao2mo, scf from quemb.molbe._opt import BEOPT from quemb.molbe.be_parallel import be_func_parallel @@ -16,7 +16,9 @@ from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func from quemb.shared import be_var -from quemb.shared.external.optqn import get_be_error_jacobian +from quemb.shared.external.optqn import ( + get_be_error_jacobian as _ext_get_be_error_jacobian, +) class storeBE: @@ -305,9 +307,357 @@ def __init__( else: self.initialize(None, compute_hf, restart=True) - # The following imports turn the imported functions into proper methods - # cannot be moved to head of file. - from quemb.molbe.rdm import compute_energy_full, rdm1_fullbasis # noqa: PLC0415 + def rdm1_fullbasis( + self, + return_ao=True, + only_rdm1=False, + only_rdm2=False, + return_lo=False, + return_RDM2=True, + print_energy=False, + ): + """ + Compute the one-particle and two-particle reduced density matrices + (RDM1 and RDM2). + + Parameters: + ----------- + return_ao : bool, optional + Whether to return the RDMs in the AO basis. Default is True. + only_rdm1 : bool, optional + Whether to compute only the RDM1. Default is False. + only_rdm2 : bool, optional + Whether to compute only the RDM2. Default is False. + return_lo : bool, optional + Whether to return the RDMs in the localized orbital (LO) basis. + Default is False. + return_RDM2 : bool, optional + Whether to return the two-particle RDM (RDM2). Default is True. + print_energy : bool, optional + Whether to print the energy contributions. Default is False. + + Returns: + -------- + rdm1AO : numpy.ndarray + The one-particle RDM in the AO basis. + rdm2AO : numpy.ndarray + The two-particle RDM in the AO basis (if return_RDM2 is True). + rdm1LO : numpy.ndarray + The one-particle RDM in the LO basis (if return_lo is True). + rdm2LO : numpy.ndarray + The two-particle RDM in the LO basis + (if return_lo and return_RDM2 are True). + rdm1MO : numpy.ndarray + The one-particle RDM in the molecular orbital (MO) basis + (if return_ao is False). + rdm2MO : numpy.ndarray + The two-particle RDM in the MO basis + (if return_ao is False and return_RDM2 is True). + """ + # Copy the molecular orbital coefficients + C_mo = self.C.copy() + nao = C_mo.shape[0] + + # Initialize density matrices for atomic orbitals (AO) + rdm1AO = numpy.zeros((nao, nao)) + rdm2AO = numpy.zeros((nao, nao, nao, nao)) + + for fobjs in self.Fobjs: + if return_RDM2: + # Adjust the one-particle reduced density matrix (RDM1) + drdm1 = fobjs.__rdm1.copy() + drdm1[numpy.diag_indices(fobjs.nsocc)] -= 2.0 + + # Compute the two-particle reduced density matrix (RDM2) and subtract + # non-connected component + dm_nc = numpy.einsum( + "ij,kl->ijkl", drdm1, drdm1, dtype=numpy.float64, optimize=True + ) - 0.5 * numpy.einsum( + "ij,kl->iklj", drdm1, drdm1, dtype=numpy.float64, optimize=True + ) + fobjs.__rdm2 -= dm_nc + + # Generate the projection matrix + cind = [fobjs.fsites[i] for i in fobjs.efac[1]] + Pc_ = ( + fobjs.TA.T + @ self.S + @ self.W[:, cind] + @ self.W[:, cind].T + @ self.S + @ fobjs.TA + ) + + if not only_rdm2: + # Compute RDM1 in the localized orbital (LO) basis + # and transform to AO basis + rdm1_eo = fobjs.mo_coeffs @ fobjs.__rdm1 @ fobjs.mo_coeffs.T + rdm1_center = Pc_ @ rdm1_eo + rdm1_ao = fobjs.TA @ rdm1_center @ fobjs.TA.T + rdm1AO += rdm1_ao + + if not only_rdm1: + # Transform RDM2 to AO basis + rdm2s = numpy.einsum( + "ijkl,pi,qj,rk,sl->pqrs", + fobjs.__rdm2, + *([fobjs.mo_coeffs] * 4), + optimize=True, + ) + rdm2_ao = numpy.einsum( + "xi,ijkl,px,qj,rk,sl->pqrs", + Pc_, + rdm2s, + fobjs.TA, + fobjs.TA, + fobjs.TA, + fobjs.TA, + optimize=True, + ) + rdm2AO += rdm2_ao + + if not only_rdm1: + # Symmetrize RDM2 and add the non-cumulant part if requested + rdm2AO = (rdm2AO + rdm2AO.T) / 2.0 + if return_RDM2: + nc_AO = ( + numpy.einsum( + "ij,kl->ijkl", + rdm1AO, + rdm1AO, + dtype=numpy.float64, + optimize=True, + ) + - numpy.einsum( + "ij,kl->iklj", + rdm1AO, + rdm1AO, + dtype=numpy.float64, + optimize=True, + ) + * 0.5 + ) + rdm2AO = nc_AO + rdm2AO + + # Transform RDM2 to the molecular orbital (MO) basis if needed + if not return_ao: + CmoT_S = self.C.T @ self.S + rdm2MO = numpy.einsum( + "ijkl,pi,qj,rk,sl->pqrs", + rdm2AO, + CmoT_S, + CmoT_S, + CmoT_S, + CmoT_S, + optimize=True, + ) + + # Transform RDM2 to the localized orbital (LO) basis if needed + if return_lo: + CloT_S = self.W.T @ self.S + rdm2LO = numpy.einsum( + "ijkl,pi,qj,rk,sl->pqrs", + rdm2AO, + CloT_S, + CloT_S, + CloT_S, + CloT_S, + optimize=True, + ) + + if not only_rdm2: + # Symmetrize RDM1 + rdm1AO = (rdm1AO + rdm1AO.T) / 2.0 + + # Transform RDM1 to the MO basis if needed + if not return_ao: + rdm1MO = self.C.T @ self.S @ rdm1AO @ self.S @ self.C + + # Transform RDM1 to the LO basis if needed + if return_lo: + rdm1LO = self.W.T @ self.S @ rdm1AO @ self.S @ self.W + + if return_RDM2 and print_energy: + # Compute and print energy contributions + Eh1 = numpy.einsum("ij,ij", self.hcore, rdm1AO, optimize=True) + eri = ao2mo.restore(1, self.mf._eri, self.mf.mo_coeff.shape[1]) + E2 = 0.5 * numpy.einsum("pqrs,pqrs", eri, rdm2AO, optimize=True) + print(flush=True) + print("-----------------------------------------------------", flush=True) + print(" BE ENERGIES with cumulant-based expression", flush=True) + + print("-----------------------------------------------------", flush=True) + + print(" 1-elec E : {:>15.8f} Ha".format(Eh1), flush=True) + print(" 2-elec E : {:>15.8f} Ha".format(E2), flush=True) + E_tot = Eh1 + E2 + self.E_core + self.enuc + print(" E_BE : {:>15.8f} Ha".format(E_tot), flush=True) + print( + " Ecorr BE : {:>15.8f} Ha".format((E_tot) - self.ebe_hf), + flush=True, + ) + print("-----------------------------------------------------", flush=True) + print(flush=True) + + if only_rdm1: + if return_ao: + return rdm1AO + else: + return rdm1MO + if only_rdm2: + if return_ao: + return rdm2AO + else: + return rdm2MO + + if return_lo and return_ao: + return (rdm1AO, rdm2AO, rdm1LO, rdm2LO) + if return_lo and not return_ao: + return (rdm1MO, rdm2MO, rdm1LO, rdm2LO) + + if return_ao: + return rdm1AO, rdm2AO + if not return_ao: + return rdm1MO, rdm2MO + + +def compute_energy_full( + self, approx_cumulant=False, use_full_rdm=False, return_rdm=True +): + """ + Compute the total energy using rdms in the full basis. + + Parameters + ---------- + approx_cumulant : bool, optional + If True, use an approximate cumulant for the energy computation. + Default is False. + use_full_rdm : bool, optional + If True, use the full two-particle RDM for energy computation. Default is False. + return_rdm : bool, optional + If True, return the computed reduced density matrices (RDMs). Default is True. + + Returns + ------- + tuple of numpy.ndarray or None + If `return_rdm` is True, returns a tuple containing the one-particle + and two-particle reduced density matrices (RDM1 and RDM2). + Otherwise, returns None. + + Notes + ----- + This function computes the total energy in the full basis, with options to use + approximate or true cumulants, and to return the reduced density matrices (RDMs). + The energy components are printed as part of the function's output. + """ + # Compute the one-particle reduced density matrix (RDM1) and the cumulant (Kumul) + # in the full basis + rdm1f, Kumul, rdm1_lo, rdm2_lo = self.rdm1_fullbasis( + return_lo=True, return_RDM2=False + ) + + if not approx_cumulant: + # Compute the true two-particle reduced density matrix (RDM2) if not using + # approximate cumulant + Kumul_T = self.rdm1_fullbasis(only_rdm2=True) + + if return_rdm: + # Construct the full RDM2 from RDM1 + RDM2_full = ( + numpy.einsum( + "ij,kl->ijkl", rdm1f, rdm1f, dtype=numpy.float64, optimize=True + ) + - numpy.einsum( + "ij,kl->iklj", rdm1f, rdm1f, dtype=numpy.float64, optimize=True + ) + * 0.5 + ) + + # Add the cumulant part to RDM2 + if not approx_cumulant: + RDM2_full += Kumul_T + else: + RDM2_full += Kumul + + # Compute the change in the one-particle density matrix (delta_gamma) + del_gamma = rdm1f - self.hf_dm + + # Compute the effective potential + veff = scf.hf.get_veff(self.mol, rdm1f, hermi=0) + + # Compute the one-electron energy + Eh1 = numpy.einsum("ij,ij", self.hcore, rdm1f, optimize=True) + + # Compute the energy due to the effective potential + EVeff = numpy.einsum("ij,ij", veff, rdm1f, optimize=True) + + # Compute the change in the one-electron energy + Eh1_dg = numpy.einsum("ij,ij", self.hcore, del_gamma, optimize=True) + + # Compute the change in the effective potential energy + Eveff_dg = numpy.einsum("ij,ij", self.hf_veff, del_gamma, optimize=True) + + # Restore the electron repulsion integrals (ERI) + eri = ao2mo.restore(1, self.mf._eri, self.mf.mo_coeff.shape[1]) + + # Compute the cumulant part of the two-electron energy + EKumul = numpy.einsum("pqrs,pqrs", eri, Kumul, optimize=True) + + if not approx_cumulant: + # Compute the true two-electron energy if not using approximate cumulant + EKumul_T = numpy.einsum("pqrs,pqrs", eri, Kumul_T, optimize=True) + + if use_full_rdm and return_rdm: + # Compute the full two-electron energy using the full RDM2 + E2 = numpy.einsum("pqrs,pqrs", eri, RDM2_full, optimize=True) + + # Compute the approximate BE total energy + EKapprox = self.ebe_hf + Eh1_dg + Eveff_dg + EKumul / 2.0 + self.ebe_tot = EKapprox + + if not approx_cumulant: + # Compute the true BE total energy if not using approximate cumulant + EKtrue = Eh1 + EVeff / 2.0 + EKumul_T / 2.0 + self.enuc + self.E_core + self.ebe_tot = EKtrue + + # Print energy results + print("-----------------------------------------------------", flush=True) + print(" BE ENERGIES with cumulant-based expression", flush=True) + + print("-----------------------------------------------------", flush=True) + print(" E_BE = E_HF + Tr(F del g) + Tr(V K_approx)", flush=True) + print(" E_HF : {:>14.8f} Ha".format(self.ebe_hf), flush=True) + print(" Tr(F del g) : {:>14.8f} Ha".format(Eh1_dg + Eveff_dg), flush=True) + print(" Tr(V K_aprrox) : {:>14.8f} Ha".format(EKumul / 2.0), flush=True) + print(" E_BE : {:>14.8f} Ha".format(EKapprox), flush=True) + print(" Ecorr BE : {:>14.8f} Ha".format(EKapprox - self.ebe_hf), flush=True) + + if not approx_cumulant: + print(flush=True) + print(" E_BE = Tr(F[g] g) + Tr(V K_true)", flush=True) + print(" Tr(h1 g) : {:>14.8f} Ha".format(Eh1), flush=True) + print(" Tr(Veff[g] g) : {:>14.8f} Ha".format(EVeff / 2.0), flush=True) + print(" Tr(V K_true) : {:>14.8f} Ha".format(EKumul_T / 2.0), flush=True) + print(" E_BE : {:>14.8f} Ha".format(EKtrue), flush=True) + if use_full_rdm and return_rdm: + print( + " E(g+G) : {:>14.8f} Ha".format( + Eh1 + 0.5 * E2 + self.E_core + self.enuc + ), + flush=True, + ) + print( + " Ecorr BE : {:>14.8f} Ha".format(EKtrue - self.ebe_hf), flush=True + ) + print(flush=True) + print(" True - approx : {:>14.4e} Ha".format(EKtrue - EKapprox)) + print("-----------------------------------------------------", flush=True) + + print(flush=True) + + # Return the RDMs if requested + if return_rdm: + return (rdm1f, RDM2_full) def optimize( self, @@ -418,7 +768,7 @@ def optimize( sys.exit() def get_be_error_jacobian(self, jac_solver="HF"): - return get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) + return _ext_get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) def print_ini(self): """ diff --git a/src/quemb/molbe/rdm.py b/src/quemb/molbe/rdm.py deleted file mode 100644 index 9ccfc723..00000000 --- a/src/quemb/molbe/rdm.py +++ /dev/null @@ -1,345 +0,0 @@ -# Author(s): Oinam Romesh Meitei - -import numpy -from pyscf import ao2mo, scf - - -def rdm1_fullbasis( - self, - return_ao=True, - only_rdm1=False, - only_rdm2=False, - return_lo=False, - return_RDM2=True, - print_energy=False, -): - """ - Compute the one-particle and two-particle reduced density matrices (RDM1 and RDM2). - - Parameters: - ----------- - return_ao : bool, optional - Whether to return the RDMs in the AO basis. Default is True. - only_rdm1 : bool, optional - Whether to compute only the RDM1. Default is False. - only_rdm2 : bool, optional - Whether to compute only the RDM2. Default is False. - return_lo : bool, optional - Whether to return the RDMs in the localized orbital (LO) basis. - Default is False. - return_RDM2 : bool, optional - Whether to return the two-particle RDM (RDM2). Default is True. - print_energy : bool, optional - Whether to print the energy contributions. Default is False. - - Returns: - -------- - rdm1AO : numpy.ndarray - The one-particle RDM in the AO basis. - rdm2AO : numpy.ndarray - The two-particle RDM in the AO basis (if return_RDM2 is True). - rdm1LO : numpy.ndarray - The one-particle RDM in the LO basis (if return_lo is True). - rdm2LO : numpy.ndarray - The two-particle RDM in the LO basis (if return_lo and return_RDM2 are True). - rdm1MO : numpy.ndarray - The one-particle RDM in the molecular orbital (MO) basis - (if return_ao is False). - rdm2MO : numpy.ndarray - The two-particle RDM in the MO basis - (if return_ao is False and return_RDM2 is True). - """ - # Copy the molecular orbital coefficients - C_mo = self.C.copy() - nao = C_mo.shape[0] - - # Initialize density matrices for atomic orbitals (AO) - rdm1AO = numpy.zeros((nao, nao)) - rdm2AO = numpy.zeros((nao, nao, nao, nao)) - - for fobjs in self.Fobjs: - if return_RDM2: - # Adjust the one-particle reduced density matrix (RDM1) - drdm1 = fobjs.__rdm1.copy() - drdm1[numpy.diag_indices(fobjs.nsocc)] -= 2.0 - - # Compute the two-particle reduced density matrix (RDM2) and subtract - # non-connected component - dm_nc = numpy.einsum( - "ij,kl->ijkl", drdm1, drdm1, dtype=numpy.float64, optimize=True - ) - 0.5 * numpy.einsum( - "ij,kl->iklj", drdm1, drdm1, dtype=numpy.float64, optimize=True - ) - fobjs.__rdm2 -= dm_nc - - # Generate the projection matrix - cind = [fobjs.fsites[i] for i in fobjs.efac[1]] - Pc_ = ( - fobjs.TA.T - @ self.S - @ self.W[:, cind] - @ self.W[:, cind].T - @ self.S - @ fobjs.TA - ) - - if not only_rdm2: - # Compute RDM1 in the localized orbital (LO) basis and transform to AO basis - rdm1_eo = fobjs.mo_coeffs @ fobjs.__rdm1 @ fobjs.mo_coeffs.T - rdm1_center = Pc_ @ rdm1_eo - rdm1_ao = fobjs.TA @ rdm1_center @ fobjs.TA.T - rdm1AO += rdm1_ao - - if not only_rdm1: - # Transform RDM2 to AO basis - rdm2s = numpy.einsum( - "ijkl,pi,qj,rk,sl->pqrs", - fobjs.__rdm2, - *([fobjs.mo_coeffs] * 4), - optimize=True, - ) - rdm2_ao = numpy.einsum( - "xi,ijkl,px,qj,rk,sl->pqrs", - Pc_, - rdm2s, - fobjs.TA, - fobjs.TA, - fobjs.TA, - fobjs.TA, - optimize=True, - ) - rdm2AO += rdm2_ao - - if not only_rdm1: - # Symmetrize RDM2 and add the non-cumulant part if requested - rdm2AO = (rdm2AO + rdm2AO.T) / 2.0 - if return_RDM2: - nc_AO = ( - numpy.einsum( - "ij,kl->ijkl", rdm1AO, rdm1AO, dtype=numpy.float64, optimize=True - ) - - numpy.einsum( - "ij,kl->iklj", rdm1AO, rdm1AO, dtype=numpy.float64, optimize=True - ) - * 0.5 - ) - rdm2AO = nc_AO + rdm2AO - - # Transform RDM2 to the molecular orbital (MO) basis if needed - if not return_ao: - CmoT_S = self.C.T @ self.S - rdm2MO = numpy.einsum( - "ijkl,pi,qj,rk,sl->pqrs", - rdm2AO, - CmoT_S, - CmoT_S, - CmoT_S, - CmoT_S, - optimize=True, - ) - - # Transform RDM2 to the localized orbital (LO) basis if needed - if return_lo: - CloT_S = self.W.T @ self.S - rdm2LO = numpy.einsum( - "ijkl,pi,qj,rk,sl->pqrs", - rdm2AO, - CloT_S, - CloT_S, - CloT_S, - CloT_S, - optimize=True, - ) - - if not only_rdm2: - # Symmetrize RDM1 - rdm1AO = (rdm1AO + rdm1AO.T) / 2.0 - - # Transform RDM1 to the MO basis if needed - if not return_ao: - rdm1MO = self.C.T @ self.S @ rdm1AO @ self.S @ self.C - - # Transform RDM1 to the LO basis if needed - if return_lo: - rdm1LO = self.W.T @ self.S @ rdm1AO @ self.S @ self.W - - if return_RDM2 and print_energy: - # Compute and print energy contributions - Eh1 = numpy.einsum("ij,ij", self.hcore, rdm1AO, optimize=True) - eri = ao2mo.restore(1, self.mf._eri, self.mf.mo_coeff.shape[1]) - E2 = 0.5 * numpy.einsum("pqrs,pqrs", eri, rdm2AO, optimize=True) - print(flush=True) - print("-----------------------------------------------------", flush=True) - print(" BE ENERGIES with cumulant-based expression", flush=True) - - print("-----------------------------------------------------", flush=True) - - print(" 1-elec E : {:>15.8f} Ha".format(Eh1), flush=True) - print(" 2-elec E : {:>15.8f} Ha".format(E2), flush=True) - E_tot = Eh1 + E2 + self.E_core + self.enuc - print(" E_BE : {:>15.8f} Ha".format(E_tot), flush=True) - print( - " Ecorr BE : {:>15.8f} Ha".format((E_tot) - self.ebe_hf), flush=True - ) - print("-----------------------------------------------------", flush=True) - print(flush=True) - - if only_rdm1: - if return_ao: - return rdm1AO - else: - return rdm1MO - if only_rdm2: - if return_ao: - return rdm2AO - else: - return rdm2MO - - if return_lo and return_ao: - return (rdm1AO, rdm2AO, rdm1LO, rdm2LO) - if return_lo and not return_ao: - return (rdm1MO, rdm2MO, rdm1LO, rdm2LO) - - if return_ao: - return rdm1AO, rdm2AO - if not return_ao: - return rdm1MO, rdm2MO - - -def compute_energy_full( - self, approx_cumulant=False, use_full_rdm=False, return_rdm=True -): - """ - Compute the total energy using rdms in the full basis. - - Parameters - ---------- - approx_cumulant : bool, optional - If True, use an approximate cumulant for the energy computation. - Default is False. - use_full_rdm : bool, optional - If True, use the full two-particle RDM for energy computation. Default is False. - return_rdm : bool, optional - If True, return the computed reduced density matrices (RDMs). Default is True. - - Returns - ------- - tuple of numpy.ndarray or None - If `return_rdm` is True, returns a tuple containing the one-particle - and two-particle reduced density matrices (RDM1 and RDM2). - Otherwise, returns None. - - Notes - ----- - This function computes the total energy in the full basis, with options to use - approximate or true cumulants, and to return the reduced density matrices (RDMs). - The energy components are printed as part of the function's output. - """ - # Compute the one-particle reduced density matrix (RDM1) and the cumulant (Kumul) - # in the full basis - rdm1f, Kumul, rdm1_lo, rdm2_lo = self.rdm1_fullbasis( - return_lo=True, return_RDM2=False - ) - - if not approx_cumulant: - # Compute the true two-particle reduced density matrix (RDM2) if not using - # approximate cumulant - Kumul_T = self.rdm1_fullbasis(only_rdm2=True) - - if return_rdm: - # Construct the full RDM2 from RDM1 - RDM2_full = ( - numpy.einsum( - "ij,kl->ijkl", rdm1f, rdm1f, dtype=numpy.float64, optimize=True - ) - - numpy.einsum( - "ij,kl->iklj", rdm1f, rdm1f, dtype=numpy.float64, optimize=True - ) - * 0.5 - ) - - # Add the cumulant part to RDM2 - if not approx_cumulant: - RDM2_full += Kumul_T - else: - RDM2_full += Kumul - - # Compute the change in the one-particle density matrix (delta_gamma) - del_gamma = rdm1f - self.hf_dm - - # Compute the effective potential - veff = scf.hf.get_veff(self.mol, rdm1f, hermi=0) - - # Compute the one-electron energy - Eh1 = numpy.einsum("ij,ij", self.hcore, rdm1f, optimize=True) - - # Compute the energy due to the effective potential - EVeff = numpy.einsum("ij,ij", veff, rdm1f, optimize=True) - - # Compute the change in the one-electron energy - Eh1_dg = numpy.einsum("ij,ij", self.hcore, del_gamma, optimize=True) - - # Compute the change in the effective potential energy - Eveff_dg = numpy.einsum("ij,ij", self.hf_veff, del_gamma, optimize=True) - - # Restore the electron repulsion integrals (ERI) - eri = ao2mo.restore(1, self.mf._eri, self.mf.mo_coeff.shape[1]) - - # Compute the cumulant part of the two-electron energy - EKumul = numpy.einsum("pqrs,pqrs", eri, Kumul, optimize=True) - - if not approx_cumulant: - # Compute the true two-electron energy if not using approximate cumulant - EKumul_T = numpy.einsum("pqrs,pqrs", eri, Kumul_T, optimize=True) - - if use_full_rdm and return_rdm: - # Compute the full two-electron energy using the full RDM2 - E2 = numpy.einsum("pqrs,pqrs", eri, RDM2_full, optimize=True) - - # Compute the approximate BE total energy - EKapprox = self.ebe_hf + Eh1_dg + Eveff_dg + EKumul / 2.0 - self.ebe_tot = EKapprox - - if not approx_cumulant: - # Compute the true BE total energy if not using approximate cumulant - EKtrue = Eh1 + EVeff / 2.0 + EKumul_T / 2.0 + self.enuc + self.E_core - self.ebe_tot = EKtrue - - # Print energy results - print("-----------------------------------------------------", flush=True) - print(" BE ENERGIES with cumulant-based expression", flush=True) - - print("-----------------------------------------------------", flush=True) - print(" E_BE = E_HF + Tr(F del g) + Tr(V K_approx)", flush=True) - print(" E_HF : {:>14.8f} Ha".format(self.ebe_hf), flush=True) - print(" Tr(F del g) : {:>14.8f} Ha".format(Eh1_dg + Eveff_dg), flush=True) - print(" Tr(V K_aprrox) : {:>14.8f} Ha".format(EKumul / 2.0), flush=True) - print(" E_BE : {:>14.8f} Ha".format(EKapprox), flush=True) - print(" Ecorr BE : {:>14.8f} Ha".format(EKapprox - self.ebe_hf), flush=True) - - if not approx_cumulant: - print(flush=True) - print(" E_BE = Tr(F[g] g) + Tr(V K_true)", flush=True) - print(" Tr(h1 g) : {:>14.8f} Ha".format(Eh1), flush=True) - print(" Tr(Veff[g] g) : {:>14.8f} Ha".format(EVeff / 2.0), flush=True) - print(" Tr(V K_true) : {:>14.8f} Ha".format(EKumul_T / 2.0), flush=True) - print(" E_BE : {:>14.8f} Ha".format(EKtrue), flush=True) - if use_full_rdm and return_rdm: - print( - " E(g+G) : {:>14.8f} Ha".format( - Eh1 + 0.5 * E2 + self.E_core + self.enuc - ), - flush=True, - ) - print( - " Ecorr BE : {:>14.8f} Ha".format(EKtrue - self.ebe_hf), flush=True - ) - print(flush=True) - print(" True - approx : {:>14.4e} Ha".format(EKtrue - EKapprox)) - print("-----------------------------------------------------", flush=True) - - print(flush=True) - - # Return the RDMs if requested - if return_rdm: - return (rdm1f, RDM2_full) From c5a993ef85b4192865e77f6090eaa877be2c2bd9 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 15:22:15 -0500 Subject: [PATCH 18/43] moved kbe.BE.optimize into the class definition --- mypy.ini | 4 + src/quemb/kbe/_opt.py | 111 -------------------- src/quemb/kbe/pbe.py | 157 +++++++++++++++++++++++----- tests/molbe_octane_get_rdms_test.py | 3 - 4 files changed, 137 insertions(+), 138 deletions(-) delete mode 100644 src/quemb/kbe/_opt.py diff --git a/mypy.ini b/mypy.ini index dc75914b..dbb78019 100644 --- a/mypy.ini +++ b/mypy.ini @@ -12,6 +12,10 @@ disallow_untyped_defs = False check_untyped_defs = False +[mypy-quemb.shared.external.*] + disallow_untyped_defs = False + check_untyped_defs = False + # TODO: whenever the following packages have stubs available, # stop ignoring them. diff --git a/src/quemb/kbe/_opt.py b/src/quemb/kbe/_opt.py deleted file mode 100644 index 645f05a4..00000000 --- a/src/quemb/kbe/_opt.py +++ /dev/null @@ -1,111 +0,0 @@ -# Author(s): Oinam Romesh Meitei - -import sys - -from quemb.kbe.misc import print_energy -from quemb.molbe._opt import BEOPT - - -def optimize( - self, - solver="MP2", - method="QN", - only_chem=False, - conv_tol=1.0e-6, - relax_density=False, - use_cumulant=True, - J0=None, - nproc=1, - ompnum=4, - max_iter=500, -): - """BE optimization function - - Interfaces BEOPT to perform bootstrap embedding optimization. - - Parameters - ---------- - solver : str, optional - High-level solver for the fragment, by default 'MP2' - method : str, optional - Optimization method, by default 'QN' - only_chem : bool, optional - If true, density matching is not performed -- only global chemical potential is - optimized, by default False - conv_tol : _type_, optional - Convergence tolerance, by default 1.e-6 - relax_density : bool, optional - Whether to use relaxed or unrelaxed densities, by default False - This option is for using CCSD as solver. Relaxed density here uses - Lambda amplitudes, whereas unrelaxed density only uses T amplitudes. - c.f. See http://classic.chem.msu.su/cgi-bin/ceilidh.exe/gran/gamess/forum/?C34df668afbHW-7216-1405+00.htm - for the distinction between the two - use_cumulant : bool, optional - Use cumulant-based energy expression, by default True - max_iter : int, optional - Maximum number of optimization steps, by default 500 - nproc : int - Total number of processors assigned for the optimization. Defaults to 1. - When nproc > 1, Python multithreading - is invoked. - ompnum : int - If nproc > 1, ompnum sets the number of cores for OpenMP parallelization. - Defaults to 4 - J0 : list of list of float - Initial Jacobian. - """ - # Check if only chemical potential optimization is required - if not only_chem: - pot = self.pot - if self.be_type == "be1": - sys.exit( - "BE1 only works with chemical potential optimization. " - "Set only_chem=True" - ) - else: - pot = [0.0] - - # Initialize the BEOPT object - be_ = BEOPT( - pot, - self.Fobjs, - self.Nocc, - self.enuc, - hf_veff=self.hf_veff, - nproc=nproc, - ompnum=ompnum, - max_space=max_iter, - conv_tol=conv_tol, - only_chem=only_chem, - hci_cutoff=self.hci_cutoff, - ci_coeff_cutoff=self.ci_coeff_cutoff, - relax_density=relax_density, - select_cutoff=self.select_cutoff, - solver=solver, - ecore=self.E_core, - ebe_hf=self.ebe_hf, - ) - - if method == "QN": - # Prepare the initial Jacobian matrix - if only_chem: - J0 = [[0.0]] - J0 = self.get_be_error_jacobian(jac_solver="HF") - J0 = [[J0[-1, -1]]] - else: - J0 = self.get_be_error_jacobian(jac_solver="HF") - - # Perform the optimization - be_.optimize(method, J0=J0) - self.ebe_tot = self.ebe_hf + be_.Ebe[0] - # Print the energy components - print_energy( - be_.Ebe[0], - be_.Ebe[1][1], - be_.Ebe[1][0] + be_.Ebe[1][2], - self.ebe_hf, - self.unitcell_nkpt, - ) - else: - print("This optimization method for BE is not supported") - sys.exit() diff --git a/src/quemb/kbe/pbe.py b/src/quemb/kbe/pbe.py index 298d8909..b3cf6437 100644 --- a/src/quemb/kbe/pbe.py +++ b/src/quemb/kbe/pbe.py @@ -13,10 +13,13 @@ from pyscf.pbc.df.df_jk import _ewald_exxdiv_for_G0 from quemb.kbe.lo import Mixin_k_Localize -from quemb.kbe.misc import storePBE +from quemb.kbe.misc import print_energy, storePBE from quemb.kbe.pfrag import Frags +from quemb.molbe._opt import BEOPT from quemb.shared import be_var -from quemb.shared.external.optqn import get_be_error_jacobian +from quemb.shared.external.optqn import ( + get_be_error_jacobian as _ext_get_be_error_jacobian, +) class BE(Mixin_k_Localize): @@ -325,32 +328,138 @@ def __init__( if not restart: self.initialize(mf._eri, compute_hf) - # The following import of these functions turns them into - # proper methods of the class. - from quemb.kbe._opt import optimize # noqa: PLC0415 + def optimize( + self, + solver="MP2", + method="QN", + only_chem=False, + conv_tol=1.0e-6, + relax_density=False, + use_cumulant=True, + J0=None, + nproc=1, + ompnum=4, + max_iter=500, + ): + """BE optimization function - def get_be_error_jacobian(self, jac_solver="HF"): - return get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) + Interfaces BEOPT to perform bootstrap embedding optimization. - def print_ini(self): - """ - Print initialization banner for the kBE calculation. + Parameters + ---------- + solver : str, optional + High-level solver for the fragment, by default 'MP2' + method : str, optional + Optimization method, by default 'QN' + only_chem : bool, optional + If true, density matching is not performed -- + only global chemical potential is optimized, by default False + conv_tol : _type_, optional + Convergence tolerance, by default 1.e-6 + relax_density : bool, optional + Whether to use relaxed or unrelaxed densities, by default False + This option is for using CCSD as solver. Relaxed density here uses + Lambda amplitudes, whereas unrelaxed density only uses T amplitudes. + c.f. See http://classic.chem.msu.su/cgi-bin/ceilidh.exe/gran/gamess/forum/?C34df668afbHW-7216-1405+00.htm + for the distinction between the two + use_cumulant : bool, optional + Use cumulant-based energy expression, by default True + max_iter : int, optional + Maximum number of optimization steps, by default 500 + nproc : int + Total number of processors assigned for the optimization. Defaults to 1. + When nproc > 1, Python multithreading + is invoked. + ompnum : int + If nproc > 1, ompnum sets the number of cores for OpenMP parallelization. + Defaults to 4 + J0 : list of list of float + Initial Jacobian. """ - print("-----------------------------------------------------------", flush=True) - - print(" BBBBBBB EEEEEEE ", flush=True) - print(" BB B EE ", flush=True) - print(" PP PP BB B EE ", flush=True) - print(" PP PP BBBBBBB EEEEEEE ", flush=True) - print(" PPPP BB B EE ", flush=True) - print(" PP PP BB B EE ", flush=True) - print(" PP PP BBBBBBB EEEEEEE ", flush=True) - print(flush=True) + # Check if only chemical potential optimization is required + if not only_chem: + pot = self.pot + if self.be_type == "be1": + sys.exit( + "BE1 only works with chemical potential optimization. " + "Set only_chem=True" + ) + else: + pot = [0.0] + + # Initialize the BEOPT object + be_ = BEOPT( + pot, + self.Fobjs, + self.Nocc, + self.enuc, + hf_veff=self.hf_veff, + nproc=nproc, + ompnum=ompnum, + max_space=max_iter, + conv_tol=conv_tol, + only_chem=only_chem, + hci_cutoff=self.hci_cutoff, + ci_coeff_cutoff=self.ci_coeff_cutoff, + relax_density=relax_density, + select_cutoff=self.select_cutoff, + solver=solver, + ecore=self.E_core, + ebe_hf=self.ebe_hf, + ) - print(" PERIODIC BOOTSTRAP EMBEDDING", flush=True) - print(" BEn = ", self.be_type, flush=True) - print("-----------------------------------------------------------", flush=True) - print(flush=True) + if method == "QN": + # Prepare the initial Jacobian matrix + if only_chem: + J0 = [[0.0]] + J0 = self.get_be_error_jacobian(jac_solver="HF") + J0 = [[J0[-1, -1]]] + else: + J0 = self.get_be_error_jacobian(jac_solver="HF") + + # Perform the optimization + be_.optimize(method, J0=J0) + self.ebe_tot = self.ebe_hf + be_.Ebe[0] + # Print the energy components + print_energy( + be_.Ebe[0], + be_.Ebe[1][1], + be_.Ebe[1][0] + be_.Ebe[1][2], + self.ebe_hf, + self.unitcell_nkpt, + ) + else: + print("This optimization method for BE is not supported") + sys.exit() + + def get_be_error_jacobian(self, jac_solver="HF"): + return _ext_get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) + + def print_ini(self): + """ + Print initialization banner for the kBE calculation. + """ + print( + "-----------------------------------------------------------", + flush=True, + ) + + print(" BBBBBBB EEEEEEE ", flush=True) + print(" BB B EE ", flush=True) + print(" PP PP BB B EE ", flush=True) + print(" PP PP BBBBBBB EEEEEEE ", flush=True) + print(" PPPP BB B EE ", flush=True) + print(" PP PP BB B EE ", flush=True) + print(" PP PP BBBBBBB EEEEEEE ", flush=True) + print(flush=True) + + print(" PERIODIC BOOTSTRAP EMBEDDING", flush=True) + print(" BEn = ", self.be_type, flush=True) + print( + "-----------------------------------------------------------", + flush=True, + ) + print(flush=True) def ewald_sum(self, kpts=None): dm_ = self.mf.make_rdm1() diff --git a/tests/molbe_octane_get_rdms_test.py b/tests/molbe_octane_get_rdms_test.py index a220eeb9..f1f33a5e 100644 --- a/tests/molbe_octane_get_rdms_test.py +++ b/tests/molbe_octane_get_rdms_test.py @@ -1,13 +1,10 @@ # Illustrates BE computation on octane with RDMs -import os import numpy as np -import pytest from pyscf import gto, scf from quemb.molbe import BE, fragpart -from quemb.shared import be_var # TODO: actually add meaningful tests for RDM elements, # energies etc. From 00efcbf870456db43969470ec7af28ea1df2699f Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 15:41:11 -0500 Subject: [PATCH 19/43] no more imports in class definitions --- src/quemb/kbe/fragment.py | 8 +- src/quemb/kbe/pbe.py | 2 + src/quemb/molbe/fragment.py | 7 +- src/quemb/molbe/mbe.py | 232 ++++++++++++++++++------------------ 4 files changed, 130 insertions(+), 119 deletions(-) diff --git a/src/quemb/kbe/fragment.py b/src/quemb/kbe/fragment.py index d47c0969..6a26a074 100644 --- a/src/quemb/kbe/fragment.py +++ b/src/quemb/kbe/fragment.py @@ -1,8 +1,10 @@ # Author(s): Oinam Romesh Meitei import sys +from functools import wraps from quemb.kbe.autofrag import autogen +from quemb.kbe.chain import polychain as _ext_polychain from quemb.molbe.helper import get_core @@ -155,6 +157,6 @@ def __init__( print("exiting", flush=True) sys.exit() - # This import makes polychain a method of the class and - # cannot be moved to the top of the file - from quemb.kbe.chain import polychain # noqa: PLC0415 + @wraps(_ext_polychain) + def polychain(self, mol, frozen_core=False, unitcell=1): + return _ext_polychain(self, mol, frozen_core=frozen_core, unitcell=unitcell) diff --git a/src/quemb/kbe/pbe.py b/src/quemb/kbe/pbe.py index b3cf6437..0c70cee3 100644 --- a/src/quemb/kbe/pbe.py +++ b/src/quemb/kbe/pbe.py @@ -3,6 +3,7 @@ import os import pickle import sys +from functools import wraps from multiprocessing import Pool import h5py @@ -432,6 +433,7 @@ def optimize( print("This optimization method for BE is not supported") sys.exit() + @wraps(_ext_get_be_error_jacobian) def get_be_error_jacobian(self, jac_solver="HF"): return _ext_get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) diff --git a/src/quemb/molbe/fragment.py b/src/quemb/molbe/fragment.py index 7307da13..490461d3 100644 --- a/src/quemb/molbe/fragment.py +++ b/src/quemb/molbe/fragment.py @@ -1,9 +1,11 @@ # Author: Oinam Romesh Meitei import sys +from functools import wraps from quemb.molbe.autofrag import autogen from quemb.molbe.helper import get_core +from quemb.molbe.lchain import chain as _ext_chain class fragpart: @@ -133,8 +135,9 @@ def __init__( print("exiting", flush=True) sys.exit() - # importing the function here turns it into a proper method - from quemb.molbe.lchain import chain # noqa: PLC0415 + @wraps(_ext_chain) + def chain(self, mol, frozen_core=False, closed=False): + return _ext_chain(self, mol, frozen_core=frozen_core, closed=closed) def hchain_simple(self): """Hard coded fragmentation feature""" diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index e74a4841..158da64b 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -3,6 +3,7 @@ import os import pickle import sys +from functools import wraps import h5py import numpy @@ -520,144 +521,146 @@ def rdm1_fullbasis( if not return_ao: return rdm1MO, rdm2MO + def compute_energy_full( + self, approx_cumulant=False, use_full_rdm=False, return_rdm=True + ): + """ + Compute the total energy using rdms in the full basis. -def compute_energy_full( - self, approx_cumulant=False, use_full_rdm=False, return_rdm=True -): - """ - Compute the total energy using rdms in the full basis. - - Parameters - ---------- - approx_cumulant : bool, optional - If True, use an approximate cumulant for the energy computation. - Default is False. - use_full_rdm : bool, optional - If True, use the full two-particle RDM for energy computation. Default is False. - return_rdm : bool, optional - If True, return the computed reduced density matrices (RDMs). Default is True. - - Returns - ------- - tuple of numpy.ndarray or None - If `return_rdm` is True, returns a tuple containing the one-particle - and two-particle reduced density matrices (RDM1 and RDM2). - Otherwise, returns None. - - Notes - ----- - This function computes the total energy in the full basis, with options to use - approximate or true cumulants, and to return the reduced density matrices (RDMs). - The energy components are printed as part of the function's output. - """ - # Compute the one-particle reduced density matrix (RDM1) and the cumulant (Kumul) - # in the full basis - rdm1f, Kumul, rdm1_lo, rdm2_lo = self.rdm1_fullbasis( - return_lo=True, return_RDM2=False - ) - - if not approx_cumulant: - # Compute the true two-particle reduced density matrix (RDM2) if not using - # approximate cumulant - Kumul_T = self.rdm1_fullbasis(only_rdm2=True) - - if return_rdm: - # Construct the full RDM2 from RDM1 - RDM2_full = ( - numpy.einsum( - "ij,kl->ijkl", rdm1f, rdm1f, dtype=numpy.float64, optimize=True - ) - - numpy.einsum( - "ij,kl->iklj", rdm1f, rdm1f, dtype=numpy.float64, optimize=True - ) - * 0.5 + Parameters + ---------- + approx_cumulant : bool, optional + If True, use an approximate cumulant for the energy computation. + Default is False. + use_full_rdm : bool, optional + If True, use the full two-particle RDM for energy computation. Default is False. + return_rdm : bool, optional + If True, return the computed reduced density matrices (RDMs). Default is True. + + Returns + ------- + tuple of numpy.ndarray or None + If `return_rdm` is True, returns a tuple containing the one-particle + and two-particle reduced density matrices (RDM1 and RDM2). + Otherwise, returns None. + + Notes + ----- + This function computes the total energy in the full basis, with options to use + approximate or true cumulants, and to return the reduced density matrices (RDMs). + The energy components are printed as part of the function's output. + """ + # Compute the one-particle reduced density matrix (RDM1) and the cumulant (Kumul) + # in the full basis + rdm1f, Kumul, rdm1_lo, rdm2_lo = self.rdm1_fullbasis( + return_lo=True, return_RDM2=False ) - # Add the cumulant part to RDM2 if not approx_cumulant: - RDM2_full += Kumul_T - else: - RDM2_full += Kumul + # Compute the true two-particle reduced density matrix (RDM2) if not using + # approximate cumulant + Kumul_T = self.rdm1_fullbasis(only_rdm2=True) + + if return_rdm: + # Construct the full RDM2 from RDM1 + RDM2_full = ( + numpy.einsum( + "ij,kl->ijkl", rdm1f, rdm1f, dtype=numpy.float64, optimize=True + ) + - numpy.einsum( + "ij,kl->iklj", rdm1f, rdm1f, dtype=numpy.float64, optimize=True + ) + * 0.5 + ) - # Compute the change in the one-particle density matrix (delta_gamma) - del_gamma = rdm1f - self.hf_dm + # Add the cumulant part to RDM2 + if not approx_cumulant: + RDM2_full += Kumul_T + else: + RDM2_full += Kumul - # Compute the effective potential - veff = scf.hf.get_veff(self.mol, rdm1f, hermi=0) + # Compute the change in the one-particle density matrix (delta_gamma) + del_gamma = rdm1f - self.hf_dm - # Compute the one-electron energy - Eh1 = numpy.einsum("ij,ij", self.hcore, rdm1f, optimize=True) + # Compute the effective potential + veff = scf.hf.get_veff(self.mol, rdm1f, hermi=0) - # Compute the energy due to the effective potential - EVeff = numpy.einsum("ij,ij", veff, rdm1f, optimize=True) + # Compute the one-electron energy + Eh1 = numpy.einsum("ij,ij", self.hcore, rdm1f, optimize=True) - # Compute the change in the one-electron energy - Eh1_dg = numpy.einsum("ij,ij", self.hcore, del_gamma, optimize=True) + # Compute the energy due to the effective potential + EVeff = numpy.einsum("ij,ij", veff, rdm1f, optimize=True) - # Compute the change in the effective potential energy - Eveff_dg = numpy.einsum("ij,ij", self.hf_veff, del_gamma, optimize=True) + # Compute the change in the one-electron energy + Eh1_dg = numpy.einsum("ij,ij", self.hcore, del_gamma, optimize=True) - # Restore the electron repulsion integrals (ERI) - eri = ao2mo.restore(1, self.mf._eri, self.mf.mo_coeff.shape[1]) + # Compute the change in the effective potential energy + Eveff_dg = numpy.einsum("ij,ij", self.hf_veff, del_gamma, optimize=True) - # Compute the cumulant part of the two-electron energy - EKumul = numpy.einsum("pqrs,pqrs", eri, Kumul, optimize=True) + # Restore the electron repulsion integrals (ERI) + eri = ao2mo.restore(1, self.mf._eri, self.mf.mo_coeff.shape[1]) - if not approx_cumulant: - # Compute the true two-electron energy if not using approximate cumulant - EKumul_T = numpy.einsum("pqrs,pqrs", eri, Kumul_T, optimize=True) + # Compute the cumulant part of the two-electron energy + EKumul = numpy.einsum("pqrs,pqrs", eri, Kumul, optimize=True) - if use_full_rdm and return_rdm: - # Compute the full two-electron energy using the full RDM2 - E2 = numpy.einsum("pqrs,pqrs", eri, RDM2_full, optimize=True) + if not approx_cumulant: + # Compute the true two-electron energy if not using approximate cumulant + EKumul_T = numpy.einsum("pqrs,pqrs", eri, Kumul_T, optimize=True) - # Compute the approximate BE total energy - EKapprox = self.ebe_hf + Eh1_dg + Eveff_dg + EKumul / 2.0 - self.ebe_tot = EKapprox + if use_full_rdm and return_rdm: + # Compute the full two-electron energy using the full RDM2 + E2 = numpy.einsum("pqrs,pqrs", eri, RDM2_full, optimize=True) - if not approx_cumulant: - # Compute the true BE total energy if not using approximate cumulant - EKtrue = Eh1 + EVeff / 2.0 + EKumul_T / 2.0 + self.enuc + self.E_core - self.ebe_tot = EKtrue + # Compute the approximate BE total energy + EKapprox = self.ebe_hf + Eh1_dg + Eveff_dg + EKumul / 2.0 + self.ebe_tot = EKapprox - # Print energy results - print("-----------------------------------------------------", flush=True) - print(" BE ENERGIES with cumulant-based expression", flush=True) + if not approx_cumulant: + # Compute the true BE total energy if not using approximate cumulant + EKtrue = Eh1 + EVeff / 2.0 + EKumul_T / 2.0 + self.enuc + self.E_core + self.ebe_tot = EKtrue - print("-----------------------------------------------------", flush=True) - print(" E_BE = E_HF + Tr(F del g) + Tr(V K_approx)", flush=True) - print(" E_HF : {:>14.8f} Ha".format(self.ebe_hf), flush=True) - print(" Tr(F del g) : {:>14.8f} Ha".format(Eh1_dg + Eveff_dg), flush=True) - print(" Tr(V K_aprrox) : {:>14.8f} Ha".format(EKumul / 2.0), flush=True) - print(" E_BE : {:>14.8f} Ha".format(EKapprox), flush=True) - print(" Ecorr BE : {:>14.8f} Ha".format(EKapprox - self.ebe_hf), flush=True) + # Print energy results + print("-----------------------------------------------------", flush=True) + print(" BE ENERGIES with cumulant-based expression", flush=True) - if not approx_cumulant: - print(flush=True) - print(" E_BE = Tr(F[g] g) + Tr(V K_true)", flush=True) - print(" Tr(h1 g) : {:>14.8f} Ha".format(Eh1), flush=True) - print(" Tr(Veff[g] g) : {:>14.8f} Ha".format(EVeff / 2.0), flush=True) - print(" Tr(V K_true) : {:>14.8f} Ha".format(EKumul_T / 2.0), flush=True) - print(" E_BE : {:>14.8f} Ha".format(EKtrue), flush=True) - if use_full_rdm and return_rdm: + print("-----------------------------------------------------", flush=True) + print(" E_BE = E_HF + Tr(F del g) + Tr(V K_approx)", flush=True) + print(" E_HF : {:>14.8f} Ha".format(self.ebe_hf), flush=True) + print(" Tr(F del g) : {:>14.8f} Ha".format(Eh1_dg + Eveff_dg), flush=True) + print(" Tr(V K_aprrox) : {:>14.8f} Ha".format(EKumul / 2.0), flush=True) + print(" E_BE : {:>14.8f} Ha".format(EKapprox), flush=True) + print( + " Ecorr BE : {:>14.8f} Ha".format(EKapprox - self.ebe_hf), flush=True + ) + + if not approx_cumulant: + print(flush=True) + print(" E_BE = Tr(F[g] g) + Tr(V K_true)", flush=True) + print(" Tr(h1 g) : {:>14.8f} Ha".format(Eh1), flush=True) + print(" Tr(Veff[g] g) : {:>14.8f} Ha".format(EVeff / 2.0), flush=True) + print(" Tr(V K_true) : {:>14.8f} Ha".format(EKumul_T / 2.0), flush=True) + print(" E_BE : {:>14.8f} Ha".format(EKtrue), flush=True) + if use_full_rdm and return_rdm: + print( + " E(g+G) : {:>14.8f} Ha".format( + Eh1 + 0.5 * E2 + self.E_core + self.enuc + ), + flush=True, + ) print( - " E(g+G) : {:>14.8f} Ha".format( - Eh1 + 0.5 * E2 + self.E_core + self.enuc - ), + " Ecorr BE : {:>14.8f} Ha".format(EKtrue - self.ebe_hf), flush=True, ) - print( - " Ecorr BE : {:>14.8f} Ha".format(EKtrue - self.ebe_hf), flush=True - ) - print(flush=True) - print(" True - approx : {:>14.4e} Ha".format(EKtrue - EKapprox)) - print("-----------------------------------------------------", flush=True) + print(flush=True) + print(" True - approx : {:>14.4e} Ha".format(EKtrue - EKapprox)) + print("-----------------------------------------------------", flush=True) - print(flush=True) + print(flush=True) - # Return the RDMs if requested - if return_rdm: - return (rdm1f, RDM2_full) + # Return the RDMs if requested + if return_rdm: + return (rdm1f, RDM2_full) def optimize( self, @@ -767,6 +770,7 @@ def optimize( print("This optimization method for BE is not supported") sys.exit() + @wraps(_ext_get_be_error_jacobian) def get_be_error_jacobian(self, jac_solver="HF"): return _ext_get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) From 0af761e7e238fb097e3b592f49b5eb7dffec1e5c Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Tue, 26 Nov 2024 15:51:38 -0500 Subject: [PATCH 20/43] Replace dangerous double leading underscores with trailing ones __variable has a special meaning in python and **SHOULD NOT** be used to denote if a variable is per fragment --- src/quemb/molbe/be_parallel.py | 12 ++++++------ src/quemb/molbe/mbe.py | 8 ++++---- src/quemb/molbe/pfrag.py | 4 ++-- src/quemb/molbe/solver.py | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/quemb/molbe/be_parallel.py b/src/quemb/molbe/be_parallel.py index 22f2738f..62d07a2d 100644 --- a/src/quemb/molbe/be_parallel.py +++ b/src/quemb/molbe/be_parallel.py @@ -342,7 +342,7 @@ def run_solver_u( raise NotImplementedError("Only UCCSD Solver implemented") # Compute RDM1 - fobj_a.__rdm1 = rdm1_tmp[0].copy() + fobj_a.rdm1__ = rdm1_tmp[0].copy() fobj_a._rdm1 = ( functools.reduce( numpy.dot, (fobj_a._mf.mo_coeff, rdm1_tmp[0], fobj_a._mf.mo_coeff.T) @@ -350,7 +350,7 @@ def run_solver_u( * 0.5 ) - fobj_b.__rdm1 = rdm1_tmp[1].copy() + fobj_b.rdm1__ = rdm1_tmp[1].copy() fobj_b._rdm1 = ( functools.reduce( numpy.dot, (fobj_b._mf.mo_coeff, rdm1_tmp[1], fobj_b._mf.mo_coeff.T) @@ -368,8 +368,8 @@ def run_solver_u( else: raise NotImplementedError("RDM Return not Implemented") - fobj_a.__rdm2 = rdm2s[0].copy() - fobj_b.__rdm2 = rdm2s[1].copy() + fobj_a.rdm2__ = rdm2s[0].copy() + fobj_b.rdm2__ = rdm2s[1].copy() # Calculate energy on a per-fragment basis if frag_energy: @@ -570,8 +570,8 @@ def be_func_parallel( e_c += rdms[idx][0][2] fobj.mo_coeffs = rdms[idx][1] fobj._rdm1 = rdms[idx][2] - fobj.__rdm2 = rdms[idx][3] - fobj.__rdm1 = rdms[idx][4] + fobj.rdm2__ = rdms[idx][3] + fobj.rdm1__ = rdms[idx][4] del rdms ernorm, ervec = solve_error(Fobjs, Nocc, only_chem=only_chem) diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index 158da64b..d663f9af 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -366,7 +366,7 @@ def rdm1_fullbasis( for fobjs in self.Fobjs: if return_RDM2: # Adjust the one-particle reduced density matrix (RDM1) - drdm1 = fobjs.__rdm1.copy() + drdm1 = fobjs.rdm1__.copy() drdm1[numpy.diag_indices(fobjs.nsocc)] -= 2.0 # Compute the two-particle reduced density matrix (RDM2) and subtract @@ -376,7 +376,7 @@ def rdm1_fullbasis( ) - 0.5 * numpy.einsum( "ij,kl->iklj", drdm1, drdm1, dtype=numpy.float64, optimize=True ) - fobjs.__rdm2 -= dm_nc + fobjs.rdm2__ -= dm_nc # Generate the projection matrix cind = [fobjs.fsites[i] for i in fobjs.efac[1]] @@ -392,7 +392,7 @@ def rdm1_fullbasis( if not only_rdm2: # Compute RDM1 in the localized orbital (LO) basis # and transform to AO basis - rdm1_eo = fobjs.mo_coeffs @ fobjs.__rdm1 @ fobjs.mo_coeffs.T + rdm1_eo = fobjs.mo_coeffs @ fobjs.rdm1__ @ fobjs.mo_coeffs.T rdm1_center = Pc_ @ rdm1_eo rdm1_ao = fobjs.TA @ rdm1_center @ fobjs.TA.T rdm1AO += rdm1_ao @@ -401,7 +401,7 @@ def rdm1_fullbasis( # Transform RDM2 to AO basis rdm2s = numpy.einsum( "ijkl,pi,qj,rk,sl->pqrs", - fobjs.__rdm2, + fobjs.rdm2__, *([fobjs.mo_coeffs] * 4), optimize=True, ) diff --git a/src/quemb/molbe/pfrag.py b/src/quemb/molbe/pfrag.py index 4f1e286e..29c878ca 100644 --- a/src/quemb/molbe/pfrag.py +++ b/src/quemb/molbe/pfrag.py @@ -95,8 +95,8 @@ def __init__( self.udim = None self._rdm1 = None - self.__rdm1 = None - self.__rdm2 = None + self.rdm1__ = None + self.rdm2__ = None self.rdm1 = None self.genvs = None self.ebe = 0.0 diff --git a/src/quemb/molbe/solver.py b/src/quemb/molbe/solver.py index c34470db..c9bbd4d2 100644 --- a/src/quemb/molbe/solver.py +++ b/src/quemb/molbe/solver.py @@ -255,7 +255,7 @@ def be_func( if solver == "MP2": rdm1_tmp = fobj._mc.make_rdm1() - fobj.__rdm1 = rdm1_tmp.copy() + fobj.rdm1__ = rdm1_tmp.copy() fobj._rdm1 = ( functools.reduce( numpy.dot, @@ -295,7 +295,7 @@ def be_func( + numpy.einsum("ij,kl->iklj", del_rdm1, hf_dm) ) * 0.5 rdm2s -= nc - fobj.__rdm2 = rdm2s.copy() + fobj.rdm2__ = rdm2s.copy() if frag_energy or eeval: # Find the energy of a given fragment, with the cumulant definition. # Return [e1, e2, ec] as e_f and add to the running total_e. @@ -428,7 +428,7 @@ def be_func_u( print("exiting", flush=True) sys.exit() - fobj_a.__rdm1 = rdm1_tmp[0].copy() + fobj_a.rdm1__ = rdm1_tmp[0].copy() fobj_b._rdm1 = ( functools.reduce( numpy.dot, (fobj_a._mf.mo_coeff, rdm1_tmp[0], fobj_a._mf.mo_coeff.T) @@ -436,7 +436,7 @@ def be_func_u( * 0.5 ) - fobj_b.__rdm1 = rdm1_tmp[1].copy() + fobj_b.rdm1__ = rdm1_tmp[1].copy() fobj_b._rdm1 = ( functools.reduce( numpy.dot, (fobj_b._mf.mo_coeff, rdm1_tmp[1], fobj_b._mf.mo_coeff.T) @@ -450,8 +450,8 @@ def be_func_u( if use_cumulant: with_dm1 = False rdm2s = make_rdm2_uccsd(ucc, with_dm1=with_dm1) - fobj_a.__rdm2 = rdm2s[0].copy() - fobj_b.__rdm2 = rdm2s[1].copy() + fobj_a.rdm2__ = rdm2s[0].copy() + fobj_b.rdm2__ = rdm2s[1].copy() if frag_energy: if frozen: h1_ab = [ From 57ac99aafe41f8d2fba3ae6bb87ca1e34e646567 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 09:45:51 -0500 Subject: [PATCH 21/43] fixed indentation --- src/quemb/kbe/pbe.py | 54 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/quemb/kbe/pbe.py b/src/quemb/kbe/pbe.py index 0c70cee3..c041c42b 100644 --- a/src/quemb/kbe/pbe.py +++ b/src/quemb/kbe/pbe.py @@ -433,35 +433,35 @@ def optimize( print("This optimization method for BE is not supported") sys.exit() - @wraps(_ext_get_be_error_jacobian) - def get_be_error_jacobian(self, jac_solver="HF"): - return _ext_get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) - - def print_ini(self): - """ - Print initialization banner for the kBE calculation. - """ - print( - "-----------------------------------------------------------", - flush=True, - ) + @wraps(_ext_get_be_error_jacobian) + def get_be_error_jacobian(self, jac_solver="HF"): + return _ext_get_be_error_jacobian(self.Nfrag, self.Fobjs, jac_solver) - print(" BBBBBBB EEEEEEE ", flush=True) - print(" BB B EE ", flush=True) - print(" PP PP BB B EE ", flush=True) - print(" PP PP BBBBBBB EEEEEEE ", flush=True) - print(" PPPP BB B EE ", flush=True) - print(" PP PP BB B EE ", flush=True) - print(" PP PP BBBBBBB EEEEEEE ", flush=True) - print(flush=True) + def print_ini(self): + """ + Print initialization banner for the kBE calculation. + """ + print( + "-----------------------------------------------------------", + flush=True, + ) - print(" PERIODIC BOOTSTRAP EMBEDDING", flush=True) - print(" BEn = ", self.be_type, flush=True) - print( - "-----------------------------------------------------------", - flush=True, - ) - print(flush=True) + print(" BBBBBBB EEEEEEE ", flush=True) + print(" BB B EE ", flush=True) + print(" PP PP BB B EE ", flush=True) + print(" PP PP BBBBBBB EEEEEEE ", flush=True) + print(" PPPP BB B EE ", flush=True) + print(" PP PP BB B EE ", flush=True) + print(" PP PP BBBBBBB EEEEEEE ", flush=True) + print(flush=True) + + print(" PERIODIC BOOTSTRAP EMBEDDING", flush=True) + print(" BEn = ", self.be_type, flush=True) + print( + "-----------------------------------------------------------", + flush=True, + ) + print(flush=True) def ewald_sum(self, kpts=None): dm_ = self.mf.make_rdm1() From 359de78841abf618748493cfb3b9efa36b5d50ea Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 09:47:38 -0500 Subject: [PATCH 22/43] fixed formatting --- src/quemb/molbe/mbe.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/quemb/molbe/mbe.py b/src/quemb/molbe/mbe.py index d663f9af..1660f6eb 100644 --- a/src/quemb/molbe/mbe.py +++ b/src/quemb/molbe/mbe.py @@ -533,9 +533,11 @@ def compute_energy_full( If True, use an approximate cumulant for the energy computation. Default is False. use_full_rdm : bool, optional - If True, use the full two-particle RDM for energy computation. Default is False. + If True, use the full two-particle RDM for energy computation. + Default is False. return_rdm : bool, optional - If True, return the computed reduced density matrices (RDMs). Default is True. + If True, return the computed reduced density matrices (RDMs). + Default is True. Returns ------- @@ -547,11 +549,12 @@ def compute_energy_full( Notes ----- This function computes the total energy in the full basis, with options to use - approximate or true cumulants, and to return the reduced density matrices (RDMs). - The energy components are printed as part of the function's output. + approximate or true cumulants, and to return the + reduced density matrices (RDMs). The energy components are printed as part + of the function's output. """ - # Compute the one-particle reduced density matrix (RDM1) and the cumulant (Kumul) - # in the full basis + # Compute the one-particle reduced density matrix (RDM1) and the cumulant + # (Kumul) in the full basis rdm1f, Kumul, rdm1_lo, rdm2_lo = self.rdm1_fullbasis( return_lo=True, return_RDM2=False ) From 31456c0776e02fc0ce3eaeeead2b48c07043843a Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 09:48:31 -0500 Subject: [PATCH 23/43] added py.typed file to quemb --- src/quemb/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/quemb/py.typed diff --git a/src/quemb/py.typed b/src/quemb/py.typed new file mode 100644 index 00000000..e69de29b From 63f7d3cfbccc78fe073819bb920fc2aea919e493 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 09:59:03 -0500 Subject: [PATCH 24/43] added TODO --- TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO b/TODO index b67578fe..9b07b648 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ +switch to pyprojects.toml + turn quemb.pbe.lo.KMF into dataclass (what it is) unify kbe and be pfrags.Frags class From d6655aa91079cc44284e4d3268ed6a4163b4ae25 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 10:02:12 -0500 Subject: [PATCH 25/43] use approx cumulant in molbe octane rdm test --- tests/molbe_octane_get_rdms_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/molbe_octane_get_rdms_test.py b/tests/molbe_octane_get_rdms_test.py index f1f33a5e..effe21a4 100644 --- a/tests/molbe_octane_get_rdms_test.py +++ b/tests/molbe_octane_get_rdms_test.py @@ -62,7 +62,7 @@ def test_rdm(): assert np.isclose(mybe.ebe_tot, -310.3311676424482) - rdm1, rdm2 = mybe.compute_energy_full(approx_cumulant=False, return_rdm=True) + rdm1, rdm2 = mybe.compute_energy_full(approx_cumulant=True, return_rdm=True) assert np.isclose(mybe.ebe_tot, -310.3311676424482) From b344df1c176f53e2f6f6fab0af033088e3444dcc Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 10:16:30 -0500 Subject: [PATCH 26/43] added correct Optional specifier --- src/quemb/molbe/solver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/quemb/molbe/solver.py b/src/quemb/molbe/solver.py index c9bbd4d2..454f3854 100644 --- a/src/quemb/molbe/solver.py +++ b/src/quemb/molbe/solver.py @@ -3,6 +3,7 @@ import functools import os import sys +from typing import Optional import numpy from pyscf import ao2mo, cc, fci, mcscf, mp @@ -727,7 +728,9 @@ def solve_ccsd( return (t1, t2) -def solve_block2(mf: object, nocc: int, frag_scratch: str = None, **solver_kwargs): +def solve_block2( + mf: object, nocc: int, frag_scratch: Optional[str] = None, **solver_kwargs +): """DMRG fragment solver using the pyscf.dmrgscf wrapper. Parameters From fdf25ebf578dcb110c313496c74e16c5342860af Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 10:24:26 -0500 Subject: [PATCH 27/43] moved ncore_ from molbe.helper to shared.helper --- src/quemb/kbe/helper.py | 2 +- src/quemb/kbe/lo.py | 2 +- src/quemb/kbe/lo_k.py | 2 +- src/quemb/kbe/solver.py | 2 +- src/quemb/molbe/helper.py | 31 ++-------------------- src/quemb/molbe/lo.py | 2 +- src/quemb/molbe/ube.py | 2 +- src/quemb/shared/{helpers.py => helper.py} | 30 ++++++++++++++++++++- 8 files changed, 37 insertions(+), 36 deletions(-) rename src/quemb/shared/{helpers.py => helper.py} (57%) diff --git a/src/quemb/kbe/helper.py b/src/quemb/kbe/helper.py index aa898236..f4f96df8 100644 --- a/src/quemb/kbe/helper.py +++ b/src/quemb/kbe/helper.py @@ -5,7 +5,7 @@ import numpy from pyscf import scf -from quemb.molbe.helper import unused +from quemb.shared.helper import unused def get_veff(eri_, dm, S, TA, hf_veff, return_veff0=False): diff --git a/src/quemb/kbe/lo.py b/src/quemb/kbe/lo.py index c35ea946..00f86b18 100644 --- a/src/quemb/kbe/lo.py +++ b/src/quemb/kbe/lo.py @@ -17,8 +17,8 @@ remove_core_mo_k, symm_orth_k, ) -from quemb.molbe.helper import ncore_, unused from quemb.shared.external.lo_helper import get_aoind_by_atom, reorder_by_atom_ +from quemb.shared.helper import ncore_, unused class KMF: diff --git a/src/quemb/kbe/lo_k.py b/src/quemb/kbe/lo_k.py index 060e8de6..0236a74c 100644 --- a/src/quemb/kbe/lo_k.py +++ b/src/quemb/kbe/lo_k.py @@ -8,7 +8,7 @@ import scipy from pyscf.pbc import gto as pgto -from quemb.molbe.helper import unused +from quemb.shared.helper import unused def dot_gen(A, B, ovlp): diff --git a/src/quemb/kbe/solver.py b/src/quemb/kbe/solver.py index da517f3c..26f9bd7e 100644 --- a/src/quemb/kbe/solver.py +++ b/src/quemb/kbe/solver.py @@ -3,7 +3,7 @@ import numpy import scipy.linalg -from quemb.molbe.helper import unused +from quemb.shared.helper import unused def schmidt_decomp_svd(rdm, Frag_sites): diff --git a/src/quemb/molbe/helper.py b/src/quemb/molbe/helper.py index e272c96d..815c6980 100644 --- a/src/quemb/molbe/helper.py +++ b/src/quemb/molbe/helper.py @@ -2,12 +2,13 @@ # Leah Weisburn import functools -import sys import h5py import numpy from pyscf import ao2mo, gto, lib, scf +from quemb.shared.helper import ncore_ + def get_veff(eri_, dm, S, TA, hf_veff): """ @@ -184,29 +185,6 @@ def get_eri(i_frag, Nao, symm=8, ignore_symm=False, eri_file="eri_file.h5"): return eri__ -def ncore_(z): - if 1 <= z <= 2: - nc = 0 - elif 2 <= z <= 5: - nc = 1 - elif 5 <= z <= 12: - nc = 1 - elif 12 <= z <= 30: - nc = 5 - elif 31 <= z <= 38: - nc = 9 - elif 39 <= z <= 48: - nc = 14 - elif 49 <= z <= 56: - nc = 18 - else: - print("Ncore not computed in helper.ncore(), add it yourself!", flush=True) - print("exiting", flush=True) - sys.exit() - - return nc - - def get_core(mol): """ Calculate the number of cores for each atom in the molecule. @@ -517,8 +495,3 @@ def contract_2e(jmaxs, rdm2_, V_, s, sym): ec_tmp += efac[s][0] * ec[s][i] return [e1_tmp, e2_tmp, ec_tmp] - - -def unused(*args) -> None: - for arg in args: - del arg diff --git a/src/quemb/molbe/lo.py b/src/quemb/molbe/lo.py index 9f37b6e3..e21f5e42 100644 --- a/src/quemb/molbe/lo.py +++ b/src/quemb/molbe/lo.py @@ -7,11 +7,11 @@ from numpy.linalg import eigh, multi_dot, svd from pyscf.gto import intor_cross -from quemb.molbe.helper import ncore_, unused from quemb.shared.external.lo_helper import ( get_aoind_by_atom, reorder_by_atom_, ) +from quemb.shared.helper import ncore_, unused def dot_gen(A, B, ovlp): diff --git a/src/quemb/molbe/ube.py b/src/quemb/molbe/ube.py index 71431618..db9cb876 100644 --- a/src/quemb/molbe/ube.py +++ b/src/quemb/molbe/ube.py @@ -19,7 +19,7 @@ from pyscf import ao2mo from quemb.molbe.be_parallel import be_func_parallel_u -from quemb.molbe.helper import unused +from quemb.shared.helper import unused from quemb.molbe.mbe import BE from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func_u diff --git a/src/quemb/shared/helpers.py b/src/quemb/shared/helper.py similarity index 57% rename from src/quemb/shared/helpers.py rename to src/quemb/shared/helper.py index b39a5142..463977fd 100644 --- a/src/quemb/shared/helpers.py +++ b/src/quemb/shared/helper.py @@ -1,4 +1,5 @@ -from typing import Callable, TypeVar +import sys +from typing import Any, Callable, TypeVar Function = TypeVar("Function", bound=Callable) @@ -33,3 +34,30 @@ def update_doc(f: Function) -> Function: return f return update_doc + + +def unused(*args: Any) -> None: + for arg in args: + del arg + + +def ncore_(z: int) -> int: + if 1 <= z <= 2: + nc = 0 + elif 2 <= z <= 5: + nc = 1 + elif 5 <= z <= 12: + nc = 1 + elif 12 <= z <= 30: + nc = 5 + elif 31 <= z <= 38: + nc = 9 + elif 39 <= z <= 48: + nc = 14 + elif 49 <= z <= 56: + nc = 18 + else: + print("Ncore not computed in helper.ncore(), add it yourself!", flush=True) + print("exiting", flush=True) + sys.exit() + return nc From 002477ddc5451b935d6cdb0412978abb49460cb3 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 12:32:14 -0500 Subject: [PATCH 28/43] fixed imports --- src/quemb/kbe/pfrag.py | 3 ++- src/quemb/molbe/autofrag.py | 3 ++- src/quemb/molbe/be_parallel.py | 2 +- src/quemb/molbe/solver.py | 3 ++- src/quemb/molbe/ube.py | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/quemb/kbe/pfrag.py b/src/quemb/kbe/pfrag.py index 9f43beca..214a6816 100644 --- a/src/quemb/kbe/pfrag.py +++ b/src/quemb/kbe/pfrag.py @@ -9,7 +9,8 @@ from quemb.kbe.helper import get_veff from quemb.kbe.misc import get_phase, get_phase1 from quemb.kbe.solver import schmidt_decomp_svd -from quemb.molbe.helper import get_eri, get_scfObj, unused +from quemb.molbe.helper import get_eri, get_scfObj +from quemb.shared.helper import unused class Frags: diff --git a/src/quemb/molbe/autofrag.py b/src/quemb/molbe/autofrag.py index fcecdcb1..cbd43f38 100644 --- a/src/quemb/molbe/autofrag.py +++ b/src/quemb/molbe/autofrag.py @@ -4,7 +4,8 @@ import numpy -from quemb.molbe.helper import get_core, unused +from quemb.molbe.helper import get_core +from quemb.shared.helper import unused def autogen( diff --git a/src/quemb/molbe/be_parallel.py b/src/quemb/molbe/be_parallel.py index 62d07a2d..91c88be6 100644 --- a/src/quemb/molbe/be_parallel.py +++ b/src/quemb/molbe/be_parallel.py @@ -13,7 +13,6 @@ get_frag_energy, get_frag_energy_u, get_scfObj, - unused, ) from quemb.molbe.solver import ( make_rdm1_ccsd_t1, @@ -25,6 +24,7 @@ ) from quemb.shared.external.ccsd_rdm import make_rdm1_uccsd, make_rdm2_uccsd from quemb.shared.external.unrestricted_utils import make_uhf_obj +from quemb.shared.helper import unused def run_solver( diff --git a/src/quemb/molbe/solver.py b/src/quemb/molbe/solver.py index 454f3854..9ad5db7b 100644 --- a/src/quemb/molbe/solver.py +++ b/src/quemb/molbe/solver.py @@ -9,7 +9,7 @@ from pyscf import ao2mo, cc, fci, mcscf, mp from pyscf.cc.ccsd_rdm import make_rdm2 -from quemb.molbe.helper import get_frag_energy, get_frag_energy_u, unused +from quemb.molbe.helper import get_frag_energy, get_frag_energy_u from quemb.shared import be_var from quemb.shared.external.ccsd_rdm import ( make_rdm1_ccsd_t1, @@ -19,6 +19,7 @@ ) from quemb.shared.external.uccsd_eri import make_eris_incore from quemb.shared.external.unrestricted_utils import make_uhf_obj +from quemb.shared.helper import unused def be_func( diff --git a/src/quemb/molbe/ube.py b/src/quemb/molbe/ube.py index db9cb876..4939cc95 100644 --- a/src/quemb/molbe/ube.py +++ b/src/quemb/molbe/ube.py @@ -19,11 +19,11 @@ from pyscf import ao2mo from quemb.molbe.be_parallel import be_func_parallel_u -from quemb.shared.helper import unused from quemb.molbe.mbe import BE from quemb.molbe.pfrag import Frags from quemb.molbe.solver import be_func_u from quemb.shared import be_var +from quemb.shared.helper import unused class UBE(BE): # 🍠 From 2b58871efdf1cf979389061c2bf65664887be30b Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 12:37:04 -0500 Subject: [PATCH 29/43] added check for trailing whitespace to CI pipeline --- .github/workflows/quemb_unittest.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/quemb_unittest.yml b/.github/workflows/quemb_unittest.yml index 591f245d..a6b6ec15 100644 --- a/.github/workflows/quemb_unittest.yml +++ b/.github/workflows/quemb_unittest.yml @@ -44,6 +44,11 @@ jobs: pip install . + - name: Check trailing whitespace + run: | + [ "`git diff --check --cached | wc -c`" -eq 0 ] + + - name: Check formatting run: | ruff format --diff From f33dda20825a09423c8bc29e02e118d78d6db4ab Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 12:38:08 -0500 Subject: [PATCH 30/43] check whitespace test on server side --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e0b4ccfe..e244e81e 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,4 @@ eri_file.h5 # MacOS DS_Store files .DS_Store + From 574c01491c0a2569d6f75c33ef0157d593cb46a2 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 13:51:50 -0500 Subject: [PATCH 31/43] play around with whitespace rules --- .gitignore | 1 - docs/make.bat | 35 ----------------------------------- 2 files changed, 36 deletions(-) delete mode 100644 docs/make.bat diff --git a/.gitignore b/.gitignore index e244e81e..e0b4ccfe 100644 --- a/.gitignore +++ b/.gitignore @@ -170,4 +170,3 @@ eri_file.h5 # MacOS DS_Store files .DS_Store - diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 747ffb7b..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd From ba74d580032a0d420b9f388ec349361694696c17 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 13:53:03 -0500 Subject: [PATCH 32/43] fixed trailing newlines at EOF --- docs/source/index.rst | 2 -- docs/source/kernel.rst | 2 -- docs/source/pfrag.rst | 1 - docs/source/usage.rst | 1 - example/data/octane.xyz | 1 - tests/data/octane.xyz | 1 - 6 files changed, 8 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index c7844c9d..9222d94f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -36,5 +36,3 @@ References optimize solvers misc - - diff --git a/docs/source/kernel.rst b/docs/source/kernel.rst index e3a6a986..c0705f32 100644 --- a/docs/source/kernel.rst +++ b/docs/source/kernel.rst @@ -31,5 +31,3 @@ Serial BE Solver .. toctree:: :maxdepth: 4 .. autofunction:: quemb.molbe.solver.be_func - - diff --git a/docs/source/pfrag.rst b/docs/source/pfrag.rst index a95b07a5..9a35c063 100644 --- a/docs/source/pfrag.rst +++ b/docs/source/pfrag.rst @@ -17,4 +17,3 @@ Periodic fragments :maxdepth: 4 .. automodule:: quemb.kbe.pfrag :members: - diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 7f15a92a..c9a931c0 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -47,4 +47,3 @@ Simple example of periodic BE calculation on 1D periodic system:: # Perform density matching in BE mybe.optimize(solver='CCSD') - diff --git a/example/data/octane.xyz b/example/data/octane.xyz index 4c07588d..56140bd3 100644 --- a/example/data/octane.xyz +++ b/example/data/octane.xyz @@ -26,4 +26,3 @@ H 0.9171145792 4.5073104916 -0.8797333088 H -0.9171145792 -4.5073104916 0.8797333088 H 0.3671153250 -5.3316378285 0.0000000000 H -0.3671153250 5.3316378285 0.0000000000 - diff --git a/tests/data/octane.xyz b/tests/data/octane.xyz index 4c07588d..56140bd3 100644 --- a/tests/data/octane.xyz +++ b/tests/data/octane.xyz @@ -26,4 +26,3 @@ H 0.9171145792 4.5073104916 -0.8797333088 H -0.9171145792 -4.5073104916 0.8797333088 H 0.3671153250 -5.3316378285 0.0000000000 H -0.3671153250 5.3316378285 0.0000000000 - From 93ba15bd803c11493292c5a9817455b590ae3781 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 13:57:03 -0500 Subject: [PATCH 33/43] changed docs/make.bat to UNIX format ask Minsik if it still works under windows --- docs/make.bat | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/make.bat diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..dc1312ab --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd From 62ee6fde17a5930ae6446681894e1941df3ba2cb Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 13:58:43 -0500 Subject: [PATCH 34/43] do the whitespace test earlier --- .github/workflows/quemb_unittest.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/quemb_unittest.yml b/.github/workflows/quemb_unittest.yml index a6b6ec15..ee0bdfd7 100644 --- a/.github/workflows/quemb_unittest.yml +++ b/.github/workflows/quemb_unittest.yml @@ -23,6 +23,12 @@ jobs: steps: - uses: actions/checkout@v4 + + - name: Check trailing whitespace + run: | + git diff-tree --check $(git hash-object -t tree /dev/null) HEAD + + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -44,11 +50,6 @@ jobs: pip install . - - name: Check trailing whitespace - run: | - [ "`git diff --check --cached | wc -c`" -eq 0 ] - - - name: Check formatting run: | ruff format --diff From 966ea270e8106fb6ae56db6905bb5422256e1cdc Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 13:59:01 -0500 Subject: [PATCH 35/43] check the enforcement of whitespace rules --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e0b4ccfe..6bf49199 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,5 @@ eri_file.h5 # MacOS DS_Store files .DS_Store + + From 87b6c38eaa3a2957364f98ccee28eaa019530d49 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 13:59:53 -0500 Subject: [PATCH 36/43] fixed trailing line at EOF of gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6bf49199..e0b4ccfe 100644 --- a/.gitignore +++ b/.gitignore @@ -170,5 +170,3 @@ eri_file.h5 # MacOS DS_Store files .DS_Store - - From 43d6acbefcd31686150f1d0d0d11c2722d1cb516 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 14:06:44 -0500 Subject: [PATCH 37/43] recommit docs/make.bat with Windows newline chars --- docs/make.bat | 70 +++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/make.bat b/docs/make.bat index dc1312ab..747ffb7b 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,35 +1,35 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd From 81298723e8467326012d26e03bbfa34be2359fc3 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 14:10:25 -0500 Subject: [PATCH 38/43] cleanly ignore windows file for whitespace check --- .github/workflows/quemb_unittest.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/quemb_unittest.yml b/.github/workflows/quemb_unittest.yml index ee0bdfd7..17964874 100644 --- a/.github/workflows/quemb_unittest.yml +++ b/.github/workflows/quemb_unittest.yml @@ -25,8 +25,10 @@ jobs: - name: Check trailing whitespace + # Idea taken from https://peter.eisentraut.org/blog/2014/11/04/checking-whitespace-with-git + # Windows uses different new line convention, thats why we exclude docs/make.bat run: | - git diff-tree --check $(git hash-object -t tree /dev/null) HEAD + git diff-tree --check $(git hash-object -t tree /dev/null) HEAD -- '(exclude)docs/make.bat' - name: Set up Python ${{ matrix.python-version }} @@ -56,16 +58,17 @@ jobs: - name: Static analysis with ruff + # for the moment we want to report always report success run: | - ruff check . || true # for the moment we want to report always report success + ruff check . || true - name: Static analysis with pylint + # ruff does nearly everything that we want from pylint + # except for detecting unresolved imports + # TODO: if they add it to ruff as well https://github.com/astral-sh/ruff/issues/9103 + # remove pylint. run: | - # ruff does nearly everything that we want from pylint - # except for detecting unresolved imports - # TODO: if they add it to ruff as well https://github.com/astral-sh/ruff/issues/9103 - # remove pylint. pylint --disable=all --enable=E0401,R0401,E0611 . || true # for the moment we want to report always report success From 3575c9614fc1041413ad98851f7fd24a841def7a Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 14:20:50 -0500 Subject: [PATCH 39/43] removed ISSUES and TODO files (just needed something to note down problems when having no internet) --- ISSUES | 1 - TODO | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 ISSUES delete mode 100644 TODO diff --git a/ISSUES b/ISSUES deleted file mode 100644 index 0a7f39ed..00000000 --- a/ISSUES +++ /dev/null @@ -1 +0,0 @@ -kbe compute_energy_full is called, but never defined diff --git a/TODO b/TODO deleted file mode 100644 index 9b07b648..00000000 --- a/TODO +++ /dev/null @@ -1,7 +0,0 @@ -switch to pyprojects.toml - -turn quemb.pbe.lo.KMF into dataclass (what it is) - -unify kbe and be pfrags.Frags class - -add non-trivial test for RDM elements to molbe_octane_get_rdms_test.py From 99daeca2dff937812ca8330e6989d5a82c82c71b Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 27 Nov 2024 14:32:20 -0500 Subject: [PATCH 40/43] added a bit of functionality for numpy typing --- src/quemb/shared/typing.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/quemb/shared/typing.py diff --git a/src/quemb/shared/typing.py b/src/quemb/shared/typing.py new file mode 100644 index 00000000..643eeb97 --- /dev/null +++ b/src/quemb/shared/typing.py @@ -0,0 +1,22 @@ +"""Enable barebone typechecking for the shape of numpy arrays + +Inspired by +https://stackoverflow.com/questions/75495212/type-hinting-numpy-arrays-and-batches + +Note that most numpy functions return `ndarray[Any, Any]` +i.e. the type is mostly useful to document intent to the developer. +""" + +from typing import Tuple, TypeVar + +import numpy as np + +T_co = TypeVar("T_co", bound=np.generic, covariant=True) + +Vector = np.ndarray[Tuple[int], np.dtype[T_co]] +Matrix = np.ndarray[Tuple[int, int], np.dtype[T_co]] +Tensor3D = np.ndarray[Tuple[int, int, int], np.dtype[T_co]] +Tensor4D = np.ndarray[Tuple[int, int, int, int], np.dtype[T_co]] +Tensor5D = np.ndarray[Tuple[int, int, int, int, int], np.dtype[T_co]] +Tensor6D = np.ndarray[Tuple[int, int, int, int, int, int], np.dtype[T_co]] +Tensor = np.ndarray[Tuple[int, ...], np.dtype[T_co]] From 91a8a97c85fb6272406673848aa0046cc543eeed Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 4 Dec 2024 13:35:14 -0500 Subject: [PATCH 41/43] deleted .DS_Store --- src/quemb/molbe/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/quemb/molbe/.DS_Store diff --git a/src/quemb/molbe/.DS_Store b/src/quemb/molbe/.DS_Store deleted file mode 100644 index 68cd1d5f967150f4cdd492b3f9d56f14d66bdeca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO-=$a82t)F21pD`Hzsoe5^pdfE?j#69biO~kwHZxyV-jOuV6fkhjFFv`&!J< zDJv6W%=eM@bvp0$=QYz(A~M5==@C(%h$1A$Ru@?y_&ujuG+fO-&`29|TG0i516tPD zTA>c81Aol{{&suv&CY2-)$ezGmKT$8UW~!u?co?{@F{`PMzy3hO(;dWri|P1RIi?@ z4NigGVI_Nn*M~Xu*b&(GX#Ejw^Pb}KpKE*`M~ZnYg8YvfKcgp%C8IkiQp&eS-+1@< zJ-Pxjr5W8ovk1?kdE%)$;!Te6b3cK3G~YZ$8|}|A^GS#;EAdiUiO4y68=f`s8dUjR zjAV?qXBba`o&qD1m8fd=M6-Jps@w?NjZdMrc@?Vs4rW?VE~^~$r!n&sc+~i@130re zqMk*~)d6)t9r$*D_lFpXF>zR0l&u4qTmgVBR7;@De->C1J4_sw7V!pQtQ2Ua#$7Rt zm7_j(yu@K?(aOoV%ZG728+Su7(mUFZg-#~1sJS|z4s1Knw2ys0|4(<{|F=c@qz Date: Wed, 4 Dec 2024 13:55:07 -0500 Subject: [PATCH 42/43] added comment to explain reason for covariant type --- src/quemb/shared/typing.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/quemb/shared/typing.py b/src/quemb/shared/typing.py index 643eeb97..8457420d 100644 --- a/src/quemb/shared/typing.py +++ b/src/quemb/shared/typing.py @@ -11,12 +11,18 @@ import numpy as np -T_co = TypeVar("T_co", bound=np.generic, covariant=True) +# We want the dtype to behave covariant, i.e. if a +# Vector[float] is allowed, then the more specific +# Vector[float64] should also be allowed. +# +# Also see here: +# https://stackoverflow.com/questions/61568462/what-does-typevara-b-covariant-true-mean +T_dtype_co = TypeVar("T_dtype_co", bound=np.generic, covariant=True) -Vector = np.ndarray[Tuple[int], np.dtype[T_co]] -Matrix = np.ndarray[Tuple[int, int], np.dtype[T_co]] -Tensor3D = np.ndarray[Tuple[int, int, int], np.dtype[T_co]] -Tensor4D = np.ndarray[Tuple[int, int, int, int], np.dtype[T_co]] -Tensor5D = np.ndarray[Tuple[int, int, int, int, int], np.dtype[T_co]] -Tensor6D = np.ndarray[Tuple[int, int, int, int, int, int], np.dtype[T_co]] -Tensor = np.ndarray[Tuple[int, ...], np.dtype[T_co]] +Vector = np.ndarray[Tuple[int], np.dtype[T_dtype_co]] +Matrix = np.ndarray[Tuple[int, int], np.dtype[T_dtype_co]] +Tensor3D = np.ndarray[Tuple[int, int, int], np.dtype[T_dtype_co]] +Tensor4D = np.ndarray[Tuple[int, int, int, int], np.dtype[T_dtype_co]] +Tensor5D = np.ndarray[Tuple[int, int, int, int, int], np.dtype[T_dtype_co]] +Tensor6D = np.ndarray[Tuple[int, int, int, int, int, int], np.dtype[T_dtype_co]] +Tensor = np.ndarray[Tuple[int, ...], np.dtype[T_dtype_co]] From 1e4fbafb163626d13c8bc7658ba4cf01900a99f8 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Wed, 4 Dec 2024 14:05:52 -0500 Subject: [PATCH 43/43] trigger pipeline --- src/quemb/shared/typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/quemb/shared/typing.py b/src/quemb/shared/typing.py index 8457420d..5f40d527 100644 --- a/src/quemb/shared/typing.py +++ b/src/quemb/shared/typing.py @@ -14,7 +14,6 @@ # We want the dtype to behave covariant, i.e. if a # Vector[float] is allowed, then the more specific # Vector[float64] should also be allowed. -# # Also see here: # https://stackoverflow.com/questions/61568462/what-does-typevara-b-covariant-true-mean T_dtype_co = TypeVar("T_dtype_co", bound=np.generic, covariant=True)