diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0067fc..548e50b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,17 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install run: | - sudo apt-get install -y libopenblas-dev - pip install . - pip install pytest + sudo add-apt-repository ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt install gcc-10 g++-10 gfortran-10 + sudo update-alternatives \ + --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 \ + --slave /usr/bin/gfortran gfortran /usr/bin/gfortran-10 \ + --slave /usr/bin/gcov gcov /usr/bin/gcov-10 + sudo apt-get install -y libopenblas-dev + pip install . + pip install pytest - name: Test run: | - export PYSCF_EXT_PATH=$(pwd):$PYSCF_EXT_PATH - pytest -s pyscf/dispersion + export PYSCF_EXT_PATH=$(pwd):$PYSCF_EXT_PATH + pytest -s pyscf/dispersion diff --git a/.gitignore b/.gitignore index 401702d..3414c5b 100644 --- a/.gitignore +++ b/.gitignore @@ -62,8 +62,12 @@ Makefile cmake_install.cmake pyscf/lib/deps pyscf/lib/config.h - +pyscf/lib/cmake/ +pyscf/lib/pkgconfig/ +pyscf/lib/libdftd4.* +pyscf/lib/libs-dftd3.* settings.py +*.cmake # Memoization and caching tmp/ diff --git a/pyscf/dispersion/dftd4.py b/pyscf/dispersion/dftd4.py index d2e9f32..400e2f2 100644 --- a/pyscf/dispersion/dftd4.py +++ b/pyscf/dispersion/dftd4.py @@ -28,7 +28,9 @@ class _d4_restype(ctypes.Structure): libdftd4.dftd4_new_error.restype = _d4_p libdftd4.dftd4_new_structure.restype = _d4_p libdftd4.dftd4_new_d4_model.restype = _d4_p +libdftd4.dftd4_new_d4s_model.restype = _d4_p libdftd4.dftd4_custom_d4_model.restype = _d4_p +libdftd4.dftd4_custom_d4s_model.restype = _d4_p libdftd4.dftd4_load_rational_damping.restype = _d4_p libdftd4.dftd4_new_rational_damping.restype = _d4_p @@ -40,18 +42,22 @@ def error_check(err): raise RuntimeError(message.value.decode()) class DFTD4Dispersion(lib.StreamObject): - def __init__(self, mol, xc, ga=None, gc=None, wf=None, atm=False): + def __init__(self, mol, xc, version='d4', ga=None, gc=None, wf=None, atm=False): xc_lc = xc.lower().encode() self._disp = None self._mol = None self._param = None + log = lib.logger.new_logger(mol) + # https://github.com/dftd4/dftd4/pull/276 + if xc_lc == 'wb97x': + log.warn('The previous wb97x is renamed as wb97x-2008. Now D4 dispersion for wb97x is the replacement of vv10 in wb97x-v. See https://github.com/dftd4/dftd4/blob/main/README.md') coords = np.asarray(mol.atom_coords(), dtype=np.double, order='C') charge = np.array([mol.charge], dtype=np.double) nuc_types = [gto.charge(mol.atom_symbol(ia)) for ia in range(mol.natm)] nuc_types = np.asarray(nuc_types, dtype=np.int32) - + self.natm = mol.natm if isinstance(mol, gto.Mole): lattice = lib.c_null_ptr() @@ -71,20 +77,29 @@ def __init__(self, mol, xc, ga=None, gc=None, wf=None, atm=False): lattice, periodic, ) error_check(err) - if ga is None and gc is None and wf is None: - self._disp = libdftd4.dftd4_new_d4_model(err, self._mol) + if version.lower() == 'd4': + if ga is None and gc is None and wf is None: + self._disp = libdftd4.dftd4_new_d4_model(err, self._mol) + else: + # Default from DFTD4 repo, https://github.com/dftd4/dftd4/blob/main/python/dftd4/interface.py#L290 + if ga is None: ga = 3.0 + if gc is None: gc = 2.0 + if wf is None: wf = 6.0 + self._disp = libdftd4.dftd4_custom_d4_model(err, self._mol, + ctypes.c_double(ga), + ctypes.c_double(gc), + ctypes.c_double(wf)) + elif version.lower() == 'd4s': + if ga is None and gc is None: + self._disp = libdftd4.dftd4_new_d4s_model(err, self._mol) + else: + if ga is None: ga = 3.0 + if gc is None: gc = 2.0 + self._disp = libdftd4.dftd4_custom_d4s_model(err, self._mol, + ctypes.c_double(ga), + ctypes.c_double(gc)) else: - # Default from DFTD4 repo, https://github.com/dftd4/dftd4/blob/main/python/dftd4/interface.py#L290 - if ga is None: - ga = 3.0 - if gc is None: - gc = 2.0 - if wf is None: - wf = 6.0 - self._disp = libdftd4.dftd4_custom_d4_model(err, self._mol, - ctypes.c_double(ga), - ctypes.c_double(gc), - ctypes.c_double(wf)) + raise ValueError('version must be d4 or d4s') error_check(err) self._param = libdftd4.dftd4_load_rational_damping( err, @@ -107,10 +122,10 @@ def __del__(self): def set_param(self, s8, a1, a2, s6=1.0, s9=1.0, alp=16.0): self._param = libdftd4.dftd4_new_rational_damping( ctypes.c_double(s6), - ctypes.c_double(s8), - ctypes.c_double(s9), - ctypes.c_double(a1), - ctypes.c_double(a2), + ctypes.c_double(s8), + ctypes.c_double(s9), + ctypes.c_double(a1), + ctypes.c_double(a2), ctypes.c_double(alp)) return diff --git a/pyscf/dispersion/tests/test_d4.py b/pyscf/dispersion/tests/test_d4.py index 0c407e7..65926ad 100644 --- a/pyscf/dispersion/tests/test_d4.py +++ b/pyscf/dispersion/tests/test_d4.py @@ -25,10 +25,16 @@ def test_d4_unknown_xc(): def test_d4_energy(): mol = pyscf.M(atom='H 0 0 0; H 0 0 1') - model = DFTD4Dispersion(mol, xc='WB97X') + model = DFTD4Dispersion(mol, xc='WB97X-2008') out = model.get_dispersion() assert abs(out['energy'] - -2.21334459527e-05) < 1e-10 +def test_wb97x_d4_energy(): + mol = pyscf.M(atom='H 0 0 0; H 0 0 1') + model = DFTD4Dispersion(mol, xc='WB97X') + out = model.get_dispersion() + assert abs(out['energy'] - -0.00027002) < 1e-8 + def test_d4_gradients(): mol = pyscf.M(atom='H 0 0 0; H 0 0 1') model = DFTD4Dispersion(mol, xc='HF') @@ -39,6 +45,82 @@ def test_d4_gradients(): def test_d4_with_pbc(): mol = pyscf.M(atom='H 0 0 0; H 0 0 1', a=np.eye(3)*2) - model = DFTD4Dispersion(mol, xc='WB97X') + model = DFTD4Dispersion(mol, xc='WB97X-2008') out = model.get_dispersion() assert abs(out['energy'] - -0.002715970438476524) < 1e-10 + +def test_d4s_energy(): + ''' Test copied from DFTD4 + ''' + mol = pyscf.M( + atom=""" + Na -1.855282634 3.586705153 -2.417637293 + H 4.401780235 0.023388444 -4.954577493 + O -2.987060334 4.762520654 1.270433015 + H 0.799808860 1.411034556 -5.046553216 + F -4.206474694 1.842757675 4.550380848 + H -3.543561218 -3.188356651 1.462400217 + H 2.700321601 1.068184525 -1.732346503 + O 3.731140888 -2.070015433 2.231609376 + N -1.753068192 0.359514171 1.053234061 + H 5.417557885 -1.578818300 1.753940027 + H -2.234628682 -2.138565050 4.109222857 + Cl 1.015658662 -3.219521545 -3.360509630 + B 2.421192557 0.266264350 -3.918624743 + B -3.025260988 2.536678890 2.316649847 + N -2.004389486 -2.292351369 2.197828073 + Al 1.122265541 -1.369420070 0.484550554 + """ + ) + model = DFTD4Dispersion(mol, xc="TPSS", version="d4s") + out = model.get_dispersion() + assert abs(out['energy'] - -0.016049411775539424) < 1.0e-7 + +def test_d4s_gradient(): + ''' Test copied from DFTD4 + ''' + mol = pyscf.M( + atom=""" + H -1.795376258 -3.778664229 -1.078835583 + S -2.682788333 0.388926662 1.662148652 + B 0.114846497 1.488579332 3.656603965 + O -1.079988795 -0.162591216 -4.557030658 + Mg 0.603028329 4.088161496 -0.025893731 + H -1.225340893 -1.799813824 -3.707731733 + H -1.334609820 -4.248190824 2.727919027 + H -0.162780825 2.412679941 5.690306951 + Si 2.878024440 -0.331205250 1.883113735 + H 0.684893279 0.327902040 -4.205476937 + B -1.209197735 -2.872537625 0.940642042 + Li -3.255726045 2.212410929 -2.867155493 + F -1.831474682 5.205272937 -2.269762706 + H 4.908858657 -1.925765619 2.990699194 + H 1.268062422 -2.604093417 0.551628052 + S 4.119569763 1.598928667 -1.391174777 + """, + spin=1 + ) + ref = np.array( + [ + [-1.04361222e-04, -1.65054791e-04, -1.36662175e-04], + [-1.41500522e-03, +1.89282651e-04, +2.16639105e-04], + [-1.18067839e-04, +4.50543787e-04, +1.50087553e-03], + [+3.37690080e-04, -4.10348598e-04, -3.02311767e-04], + [+4.39892308e-04, +1.54862493e-03, +1.33655085e-04], + [+1.31259180e-06, -7.51721105e-05, -1.39848135e-04], + [-4.61111364e-05, -1.65382677e-04, +1.81820530e-04], + [-1.94292825e-05, +7.21791149e-05, +1.79879351e-04], + [+1.14226323e-03, -6.08455689e-04, +6.24007890e-04], + [+6.95738570e-05, -1.86718359e-05, -1.25837081e-04], + [-1.66091884e-04, -1.03519307e-03, -1.71797180e-04], + [-1.29925668e-03, +6.18658801e-05, -6.30138324e-04], + [-1.58991399e-04, +5.73306273e-04, -2.35799582e-04], + [+2.90056077e-04, -2.14985916e-04, +1.62430848e-04], + [+6.43808246e-05, -3.35585457e-04, -2.45131168e-04], + [+9.82145702e-04, +1.33047503e-04, -1.01178292e-03], + ] + ) + + model = DFTD4Dispersion(mol, xc="BLYP", version="d4s") + out = model.get_dispersion(grad=True) + assert np.linalg.norm(out['gradient'] - ref) < 1.0e-7 diff --git a/pyscf/lib/CMakeLists.txt b/pyscf/lib/CMakeLists.txt index 3556e1d..23d4338 100644 --- a/pyscf/lib/CMakeLists.txt +++ b/pyscf/lib/CMakeLists.txt @@ -15,6 +15,8 @@ cmake_minimum_required (VERSION 3.5) project (pyscf-dispersion) +set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -cpp -ffree-line-length-none -Wall") + include(ExternalProject) # statically compile deps mctc mstore ExternalProject_Add(dftd3_static @@ -46,23 +48,25 @@ add_dependencies(dftd3 dftd3_static) # statically compile deps mctc mstore ExternalProject_Add(dftd4_static GIT_REPOSITORY "https://github.com/dftd4/dftd4" - GIT_TAG v3.7.0 + GIT_TAG cf1af36a0f89fdaa2faa15def912148db9f7cb07 GIT_SHALLOW FALSE GIT_PROGRESS TRUE PREFIX ${PROJECT_BINARY_DIR}/deps INSTALL_DIR ${PROJECT_SOURCE_DIR}/deps CMAKE_ARGS -DWITH_OpenMP=OFF + -DCMAKE_Fortran_FLAGS=-cpp\ -ffree-line-length-none\ -Wall -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_INSTALL_LIBDIR:PATH=lib ) ExternalProject_Add(dftd4 GIT_REPOSITORY "https://github.com/dftd4/dftd4" - GIT_TAG v3.7.0 + GIT_TAG cf1af36a0f89fdaa2faa15def912148db9f7cb07 GIT_SHALLOW FALSE GIT_PROGRESS TRUE PREFIX ${PROJECT_BINARY_DIR}/deps INSTALL_DIR ${PROJECT_SOURCE_DIR}/deps CMAKE_ARGS -DWITH_OpenMP=OFF -DBUILD_SHARED_LIBS=ON + -DCMAKE_Fortran_FLAGS=-cpp\ -ffree-line-length-none\ -Wall -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_INSTALL_LIBDIR:PATH=${PROJECT_SOURCE_DIR} )