Last active
July 12, 2020 19:00
-
-
Save shivupa/655e50a8193665663fb2681dcef94ec8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
rm -rf build | |
mkdir build | |
cd build | |
CC=clang \ | |
CXX=clang++ \ | |
cmake .. | |
make -j4 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
rm -rf build | |
mkdir build | |
cd build | |
CC=gcc \ | |
CXX=g++ \ | |
cmake .. | |
make -j4 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- The C compiler identification is Clang 10.0.0 | |
-- The CXX compiler identification is Clang 10.0.0 | |
-- Check for working C compiler: /usr/bin/clang | |
-- Check for working C compiler: /usr/bin/clang - works | |
-- Detecting C compiler ABI info | |
-- Detecting C compiler ABI info - done | |
-- Detecting C compile features | |
-- Detecting C compile features - done | |
-- Check for working CXX compiler: /usr/bin/clang++ | |
-- Check for working CXX compiler: /usr/bin/clang++ - works | |
-- Detecting CXX compiler ABI info | |
-- Detecting CXX compiler ABI info - done | |
-- Detecting CXX compile features | |
-- Detecting CXX compile features - done | |
-- STARTING BUILD HFTEST | |
-- Found PkgConfig: /usr/bin/pkg-config (found version "1.7.3") | |
-- Checking for module 'libint2' | |
-- Found libint2, version 2.6.0 | |
-- Found Libint2: /usr/include (found version "2.6.0") | |
-- Configuring done | |
-- Generating done | |
-- Build files have been written to: /home/shiv/software/libint_test/655e50a8193665663fb2681dcef94ec8/build | |
Scanning dependencies of target HFTEST | |
[ 50%] Building CXX object CMakeFiles/HFTEST.dir/hartree-fock.cc.o | |
In file included from /home/shiv/software/libint_test/655e50a8193665663fb2681dcef94ec8/hartree-fock.cc:37: | |
In file included from /usr/include/libint2.hpp:24: | |
In file included from /usr/include/libint2/cxxapi.h:41: | |
In file included from /usr/include/libint2/./engine.h:988: | |
In file included from /usr/include/libint2/./engine.impl.h:33: | |
/usr/include/libint2/boys.h:1844:23: error: redefinition of default argument | |
Real xmin = 0.0, | |
^ ~~~ | |
/usr/include/libint2/boys_fwd.h:68:23: note: previous definition is here | |
Real xmin = 0.0, | |
^ ~~~ | |
In file included from /home/shiv/software/libint_test/655e50a8193665663fb2681dcef94ec8/hartree-fock.cc:37: | |
In file included from /usr/include/libint2.hpp:24: | |
In file included from /usr/include/libint2/cxxapi.h:41: | |
In file included from /usr/include/libint2/./engine.h:988: | |
In file included from /usr/include/libint2/./engine.impl.h:33: | |
/usr/include/libint2/boys.h:1845:23: error: redefinition of default argument | |
Real xmax = 10.0, | |
^ ~~~~ | |
/usr/include/libint2/boys_fwd.h:69:23: note: previous definition is here | |
Real xmax = 10.0, | |
^ ~~~~ | |
In file included from /home/shiv/software/libint_test/655e50a8193665663fb2681dcef94ec8/hartree-fock.cc:37: | |
In file included from /usr/include/libint2.hpp:24: | |
In file included from /usr/include/libint2/cxxapi.h:41: | |
In file included from /usr/include/libint2/./engine.h:988: | |
In file included from /usr/include/libint2/./engine.impl.h:33: | |
/usr/include/libint2/boys.h:1846:31: error: redefinition of default argument | |
unsigned int npts = 1001) { | |
^ ~~~~ | |
/usr/include/libint2/boys_fwd.h:70:31: note: previous definition is here | |
unsigned int npts = 1001); | |
^ ~~~~ | |
3 errors generated. | |
make[2]: *** [CMakeFiles/HFTEST.dir/build.make:83: CMakeFiles/HFTEST.dir/hartree-fock.cc.o] Error 1 | |
make[1]: *** [CMakeFiles/Makefile2:96: CMakeFiles/HFTEST.dir/all] Error 2 | |
make: *** [Makefile:150: all] Error 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cmake_minimum_required(VERSION 3.6.0 FATAL_ERROR) | |
project(HFTEST) | |
set(CMAKE_CXX_STANDARD 20) | |
set(CMAKE_CXX_STANDARD_REQUIRED ON) | |
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}" ${CMAKE_MODULE_PATH}) | |
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY | |
${HFTEST_BINARY_DIR}/lib | |
CACHE PATH "Single output directory for building all libraries.") | |
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY | |
${HFTEST_BINARY_DIR}/bin | |
CACHE PATH "Single output directory for building all executables.") | |
message(STATUS "STARTING BUILD HFTEST") | |
# Minimal packages | |
find_package(Libint2 REQUIRED) | |
find_package(Eigen3 3.3 REQUIRED NO_MODULE) | |
add_executable(HFTEST hartree-fock.cc) | |
target_link_libraries(HFTEST Libint2::cxx Eigen3::Eigen) | |
install(TARGETS HFTEST DESTINATION bin) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# FindLibint2.cmake | |
# | |
# Finds the Libint2 header and library using the pkg-config program. | |
# | |
# Use this module by invoking find_package() as follows: | |
# find_package(Libint2 | |
# [version] [EXACT] # Minimum or EXACT version e.g. 2.5.0 | |
# [REQUIRED] # Fail with error if Libint2 is not found | |
# ) | |
# | |
# The behavior can be controlled by setting the following variables | |
# | |
# LIBINT2_SHARED_LIBRARY_ONLY if true, will look for shared lib only; may be needed for some platforms | |
# where linking errors or worse, e.g. duplicate static data, occur if | |
# linking shared libraries against static Libint2 library. | |
# PKG_CONFIG_PATH (environment variable) Add the libint2 install prefix directory (e.g. /usr/local) | |
# to specify where to look for libint2 | |
# CMAKE_MODULE_PATH Add the libint2 install prefix directory (e.g. /usr/local) | |
# to specify where to look for libint2 | |
# | |
# This will define the following CMake cache variables | |
# | |
# LIBINT2_FOUND - true if libint2.h header and libint2 library were found | |
# LIBINT2_VERSION - the libint2 version | |
# LIBINT2_INCLUDE_DIRS - (deprecated: use the CMake IMPORTED targets listed below) list of libint2 include directories | |
# LIBINT2_LIBRARIES - (deprecated: use the CMake IMPORTED targets listed below) list of libint2 libraries | |
# | |
# and the following imported targets | |
# | |
# Libint2::int2 - library only | |
# Libint2::cxx - library + C++11 API; may need to add dependence on Eigen3 and/or Boost.Preprocessor if | |
# was not found by Libint at configure time | |
# | |
# Author: Eduard Valeyev - libint@valeyev.net | |
# need cmake 3.8 for cxx_std_11 compile feature | |
if(CMAKE_VERSION VERSION_LESS 3.8.0) | |
message(FATAL_ERROR "This file relies on consumers using CMake 3.8.0 or greater.") | |
endif() | |
find_package(PkgConfig) | |
if (PKG_CONFIG_FOUND) | |
# point pkg-config to the location of this tree's libint2.pc | |
set(ENV{PKG_CONFIG_PATH} "${CMAKE_CURRENT_LIST_DIR}/../../pkgconfig:$ENV{PKG_CONFIG_PATH}") | |
if(LIBINT2_FIND_QUIETLY) | |
pkg_check_modules(PC_LIBINT2 QUIET libint2) | |
else() | |
pkg_check_modules(PC_LIBINT2 libint2) | |
endif() | |
set(LIBINT2_VERSION ${PC_LIBINT2_VERSION}) | |
find_path(LIBINT2_INCLUDE_DIR | |
NAMES libint2.h | |
PATHS ${PC_LIBINT2_INCLUDE_DIRS} | |
PATH_SUFFIXES libint2 | |
) | |
if (LIBINT2_SHARED_LIBRARY_ONLY) | |
set(_LIBINT2_LIB_NAMES "libint2.so" "libint2.dylib") | |
else (LIBINT2_SHARED_LIBRARY_ONLY) | |
set(_LIBINT2_LIB_NAMES "int2") | |
endif(LIBINT2_SHARED_LIBRARY_ONLY) | |
find_library(LIBINT2_LIBRARY NAMES ${_LIBINT2_LIB_NAMES} HINTS ${PC_LIBINT2_LIBRARY_DIRS}) | |
mark_as_advanced(LIBINT2_FOUND LIBINT2_INCLUDE_DIR LIBINT2_LIBRARY LIBINT2_VERSION) | |
include(FindPackageHandleStandardArgs) | |
find_package_handle_standard_args(Libint2 | |
FOUND_VAR LIBINT2_FOUND | |
REQUIRED_VARS LIBINT2_INCLUDE_DIR | |
VERSION_VAR LIBINT2_VERSION | |
) | |
if(LIBINT2_FOUND) | |
set(LIBINT2_LIBRARIES ${LIBINT2_LIBRARY}) | |
set(LIBINT2_INCLUDE_DIRS ${LIBINT2_INCLUDE_DIR} ${LIBINT2_INCLUDE_DIR}/libint2 ${PC_LIBINT2_INCLUDE_DIRS}) | |
# sanitize LIBINT2_INCLUDE_DIRS: remove duplicates and non-existent entries | |
list(REMOVE_DUPLICATES LIBINT2_INCLUDE_DIRS) | |
set(LIBINT2_INCLUDE_DIRS_SANITIZED ) | |
foreach(DIR IN LISTS LIBINT2_INCLUDE_DIRS) | |
if (EXISTS ${DIR}) | |
list(APPEND LIBINT2_INCLUDE_DIRS_SANITIZED ${DIR}) | |
endif() | |
endforeach() | |
set(LIBINT2_INCLUDE_DIRS ${LIBINT2_INCLUDE_DIRS_SANITIZED}) | |
endif() | |
if(LIBINT2_FOUND AND NOT TARGET Libint2::Libint) | |
add_library(Libint2::int2 INTERFACE IMPORTED) | |
set_target_properties(Libint2::int2 PROPERTIES | |
INTERFACE_INCLUDE_DIRECTORIES "${LIBINT2_INCLUDE_DIR};${LIBINT2_INCLUDE_DIR}/libint2" | |
) | |
set_target_properties(Libint2::int2 PROPERTIES | |
INTERFACE_LINK_LIBRARIES ${LIBINT2_LIBRARY} | |
) | |
set_target_properties(Libint2::int2 PROPERTIES | |
INTERFACE_COMPILE_FEATURES "cxx_std_11" | |
) | |
endif() | |
if(LIBINT2_FOUND AND NOT TARGET Libint2::cxx) | |
add_library(Libint2::cxx INTERFACE IMPORTED) | |
set_target_properties(Libint2::cxx PROPERTIES | |
INTERFACE_INCLUDE_DIRECTORIES "${LIBINT2_INCLUDE_DIRS}" | |
) | |
target_link_libraries(Libint2::cxx INTERFACE Libint2::int2) | |
endif() | |
else(PKG_CONFIG_FOUND) | |
message(FATAL_ERROR "Could not find the required pkg-config executable") | |
endif(PKG_CONFIG_FOUND) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- The C compiler identification is GNU 10.1.0 | |
-- The CXX compiler identification is GNU 10.1.0 | |
-- Check for working C compiler: /usr/bin/gcc | |
-- Check for working C compiler: /usr/bin/gcc - works | |
-- Detecting C compiler ABI info | |
-- Detecting C compiler ABI info - done | |
-- Detecting C compile features | |
-- Detecting C compile features - done | |
-- Check for working CXX compiler: /usr/bin/g++ | |
-- Check for working CXX compiler: /usr/bin/g++ - works | |
-- Detecting CXX compiler ABI info | |
-- Detecting CXX compiler ABI info - done | |
-- Detecting CXX compile features | |
-- Detecting CXX compile features - done | |
-- STARTING BUILD HFTEST | |
-- Found PkgConfig: /usr/bin/pkg-config (found version "1.7.3") | |
-- Checking for module 'libint2' | |
-- Found libint2, version 2.6.0 | |
-- Found Libint2: /usr/include (found version "2.6.0") | |
-- Configuring done | |
-- Generating done | |
-- Build files have been written to: /home/shiv/software/libint_test/655e50a8193665663fb2681dcef94ec8/build | |
Scanning dependencies of target HFTEST | |
[ 50%] Building CXX object CMakeFiles/HFTEST.dir/hartree-fock.cc.o | |
In file included from /usr/include/libint2/engine.impl.h:33, | |
from /usr/include/libint2/engine.h:988, | |
from /usr/include/libint2/cxxapi.h:41, | |
from /usr/include/libint2.hpp:24, | |
from /home/shiv/software/libint_test/655e50a8193665663fb2681dcef94ec8/hartree-fock.cc:37: | |
/usr/include/libint2/boys.h:1841:8: error: redeclaration of ‘template<class Real> void libint2::stg_ng_fit(unsigned int, Real, std::vector<std::pair<_FIter, _FIter> >&, Real, Real, unsigned int)’ may not have default arguments [-fpermissive] | |
1841 | void stg_ng_fit(unsigned int n, | |
| ^~~~~~~~~~ | |
make[2]: *** [CMakeFiles/HFTEST.dir/build.make:83: CMakeFiles/HFTEST.dir/hartree-fock.cc.o] Error 1 | |
make[1]: *** [CMakeFiles/Makefile2:96: CMakeFiles/HFTEST.dir/all] Error 2 | |
make: *** [Makefile:150: all] Error 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 | |
O 0.00000 -0.07579 0.00000 | |
H 0.86681 0.60144 0.00000 | |
H -0.86681 0.60144 0.00000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2004-2020 Edward F. Valeev | |
* | |
* This file is part of Libint. | |
* | |
* Libint is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* Libint is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with Libint. If not, see <http://www.gnu.org/licenses/>. | |
* | |
*/ | |
#define HAVE_LAPACK 1 | |
// standard C++ headers | |
#include <cmath> | |
#include <iostream> | |
#include <fstream> | |
#include <sstream> | |
#include <iomanip> | |
#include <vector> | |
#include <chrono> | |
// Eigen matrix algebra library | |
#include <Eigen/Dense> | |
#include <Eigen/Eigenvalues> | |
// Libint Gaussian integrals library | |
#include <libint2.hpp> | |
#if !LIBINT2_CONSTEXPR_STATICS | |
# include <libint2/statics_definition.h> | |
#endif | |
using real_t = libint2::scalar_type; | |
typedef Eigen::Matrix<real_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> | |
Matrix; // import dense, dynamically sized Matrix type from Eigen; | |
// this is a matrix with row-major storage (http://en.wikipedia.org/wiki/Row-major_order) | |
// to meet the layout of the integrals returned by the Libint integral library | |
struct Atom { | |
int atomic_number; | |
double x, y, z; | |
}; | |
std::vector<Atom> read_geometry(const std::string& filename); | |
std::vector<libint2::Shell> make_sto3g_basis(const std::vector<Atom>& atoms); | |
size_t nbasis(const std::vector<libint2::Shell>& shells); | |
std::vector<size_t> map_shell_to_basis_function(const std::vector<libint2::Shell>& shells); | |
Matrix compute_soad(const std::vector<Atom>& atoms); | |
Matrix compute_1body_ints(const std::vector<libint2::Shell>& shells, | |
libint2::Operator t, | |
const std::vector<Atom>& atoms = std::vector<Atom>()); | |
// simple-to-read, but inefficient Fock builder; computes ~16 times as many ints as possible | |
Matrix compute_2body_fock_simple(const std::vector<libint2::Shell>& shells, | |
const Matrix& D); | |
// an efficient Fock builder; *integral-driven* hence computes permutationally-unique ints once | |
Matrix compute_2body_fock(const std::vector<libint2::Shell>& shells, | |
const Matrix& D); | |
int main(int argc, char *argv[]) { | |
using std::cout; | |
using std::cerr; | |
using std::endl; | |
using libint2::Shell; | |
using libint2::Engine; | |
using libint2::Operator; | |
try { | |
/*** =========================== ***/ | |
/*** initialize molecule ***/ | |
/*** =========================== ***/ | |
// read geometry from a file; by default read from h2o.xyz, else take filename (.xyz) from the command line | |
const auto filename = (argc > 1) ? argv[1] : "h2o.xyz"; | |
std::vector<Atom> atoms = read_geometry(filename); | |
// count the number of electrons | |
auto nelectron = 0; | |
for (auto i = 0; i < atoms.size(); ++i) | |
nelectron += atoms[i].atomic_number; | |
const auto ndocc = nelectron / 2; | |
// compute the nuclear repulsion energy | |
auto enuc = 0.0; | |
for (auto i = 0; i < atoms.size(); i++) | |
for (auto j = i + 1; j < atoms.size(); j++) { | |
auto xij = atoms[i].x - atoms[j].x; | |
auto yij = atoms[i].y - atoms[j].y; | |
auto zij = atoms[i].z - atoms[j].z; | |
auto r2 = xij*xij + yij*yij + zij*zij; | |
auto r = sqrt(r2); | |
enuc += atoms[i].atomic_number * atoms[j].atomic_number / r; | |
} | |
cout << "\tNuclear repulsion energy = " << enuc << endl; | |
/*** =========================== ***/ | |
/*** create basis set ***/ | |
/*** =========================== ***/ | |
auto shells = make_sto3g_basis(atoms); | |
size_t nao = 0; | |
for (auto s=0; s<shells.size(); ++s) | |
nao += shells[s].size(); | |
/*** =========================== ***/ | |
/*** compute 1-e integrals ***/ | |
/*** =========================== ***/ | |
// initializes the Libint integrals library ... now ready to compute | |
libint2::initialize(); | |
// compute overlap integrals | |
auto S = compute_1body_ints(shells, Operator::overlap); | |
cout << "\n\tOverlap Integrals:\n"; | |
cout << S << endl; | |
// compute kinetic-energy integrals | |
auto T = compute_1body_ints(shells, Operator::kinetic); | |
cout << "\n\tKinetic-Energy Integrals:\n"; | |
cout << T << endl; | |
// compute nuclear-attraction integrals | |
Matrix V = compute_1body_ints(shells, Operator::nuclear, atoms); | |
cout << "\n\tNuclear Attraction Integrals:\n"; | |
cout << V << endl; | |
// Core Hamiltonian = T + V | |
Matrix H = T + V; | |
cout << "\n\tCore Hamiltonian:\n"; | |
cout << H << endl; | |
// T and V no longer needed, free up the memory | |
T.resize(0,0); | |
V.resize(0,0); | |
/*** =========================== ***/ | |
/*** build initial-guess density ***/ | |
/*** =========================== ***/ | |
const auto use_hcore_guess = false; // use core Hamiltonian eigenstates to guess density? | |
// set to true to match the result of versions 0, 1, and 2 of the code | |
// HOWEVER !!! even for medium-size molecules hcore will usually fail !!! | |
// thus set to false to use Superposition-Of-Atomic-Densities (SOAD) guess | |
Matrix D; | |
if (use_hcore_guess) { // hcore guess | |
// solve H C = e S C | |
Eigen::GeneralizedSelfAdjointEigenSolver<Matrix> gen_eig_solver(H, S); | |
auto eps = gen_eig_solver.eigenvalues(); | |
auto C = gen_eig_solver.eigenvectors(); | |
cout << "\n\tInitial C Matrix:\n"; | |
cout << C << endl; | |
// compute density, D = C(occ) . C(occ)T | |
auto C_occ = C.leftCols(ndocc); | |
D = C_occ * C_occ.transpose(); | |
} | |
else { // SOAD as the guess density, assumes STO-nG basis | |
D = compute_soad(atoms); | |
} | |
cout << "\n\tInitial Density Matrix:\n"; | |
cout << D << endl; | |
/*** =========================== ***/ | |
/*** main iterative loop ***/ | |
/*** =========================== ***/ | |
const auto maxiter = 100; | |
const real_t conv = 1e-12; | |
auto iter = 0; | |
real_t rmsd = 0.0; | |
real_t ediff = 0.0; | |
real_t ehf = 0.0; | |
do { | |
const auto tstart = std::chrono::high_resolution_clock::now(); | |
++iter; | |
// Save a copy of the energy and the density | |
auto ehf_last = ehf; | |
auto D_last = D; | |
// build a new Fock matrix | |
auto F = H; | |
//F += compute_2body_fock_simple(shells, D); | |
F += compute_2body_fock(shells, D); | |
if (iter == 1) { | |
cout << "\n\tFock Matrix:\n"; | |
cout << F << endl; | |
} | |
// solve F C = e S C | |
Eigen::GeneralizedSelfAdjointEigenSolver<Matrix> gen_eig_solver(F, S); | |
auto eps = gen_eig_solver.eigenvalues(); | |
auto C = gen_eig_solver.eigenvectors(); | |
// compute density, D = C(occ) . C(occ)T | |
auto C_occ = C.leftCols(ndocc); | |
D = C_occ * C_occ.transpose(); | |
// compute HF energy | |
ehf = 0.0; | |
for (auto i = 0; i < nao; i++) | |
for (auto j = 0; j < nao; j++) | |
ehf += D(i,j) * (H(i,j) + F(i,j)); | |
// compute difference with last iteration | |
ediff = ehf - ehf_last; | |
rmsd = (D - D_last).norm(); | |
const auto tstop = std::chrono::high_resolution_clock::now(); | |
const std::chrono::duration<double> time_elapsed = tstop - tstart; | |
if (iter == 1) | |
std::cout << | |
"\n\n Iter E(elec) E(tot) Delta(E) RMS(D) Time(s)\n"; | |
printf(" %02d %20.12f %20.12f %20.12f %20.12f %10.5lf\n", iter, ehf, ehf + enuc, | |
ediff, rmsd, time_elapsed.count()); | |
} while (((fabs(ediff) > conv) || (fabs(rmsd) > conv)) && (iter < maxiter)); | |
printf("** Hartree-Fock energy = %20.12f\n", ehf + enuc); | |
libint2::finalize(); // done with libint | |
} // end of try block; if any exceptions occurred, report them and exit cleanly | |
catch (const char* ex) { | |
cerr << "caught exception: " << ex << endl; | |
return 1; | |
} | |
catch (std::string& ex) { | |
cerr << "caught exception: " << ex << endl; | |
return 1; | |
} | |
catch (std::exception& ex) { | |
cerr << ex.what() << endl; | |
return 1; | |
} | |
catch (...) { | |
cerr << "caught unknown exception\n"; | |
return 1; | |
} | |
return 0; | |
} | |
// this reads the geometry in the standard xyz format supported by most chemistry software | |
std::vector<Atom> read_dotxyz(std::istream& is) { | |
// line 1 = # of atoms | |
size_t natom; | |
is >> natom; | |
// read off the rest of line 1 and discard | |
std::string rest_of_line; | |
std::getline(is, rest_of_line); | |
// line 2 = comment (possibly empty) | |
std::string comment; | |
std::getline(is, comment); | |
std::vector<Atom> atoms(natom); | |
for (auto i = 0; i < natom; i++) { | |
std::string element_label; | |
double x, y, z; | |
is >> element_label >> x >> y >> z; | |
// .xyz files report element labels, hence convert to atomic numbers | |
int Z; | |
if (element_label == "H") | |
Z = 1; | |
else if (element_label == "C") | |
Z = 6; | |
else if (element_label == "N") | |
Z = 7; | |
else if (element_label == "O") | |
Z = 8; | |
else if (element_label == "F") | |
Z = 9; | |
else if (element_label == "S") | |
Z = 16; | |
else if (element_label == "Cl") | |
Z = 17; | |
else { | |
std::cerr << "read_dotxyz: element label \"" << element_label << "\" is not recognized" << std::endl; | |
throw std::invalid_argument("Did not recognize element label in .xyz file"); | |
} | |
atoms[i].atomic_number = Z; | |
// .xyz files report Cartesian coordinates in angstroms; convert to bohr | |
const auto angstrom_to_bohr = 1 / 0.52917721092; // 2010 CODATA value | |
atoms[i].x = x * angstrom_to_bohr; | |
atoms[i].y = y * angstrom_to_bohr; | |
atoms[i].z = z * angstrom_to_bohr; | |
} | |
return atoms; | |
} | |
std::vector<Atom> read_geometry(const std::string& filename) { | |
std::cout << "Will read geometry from " << filename << std::endl; | |
std::ifstream is(filename); | |
assert(is.good()); | |
// to prepare for MPI parallelization, we will read the entire file into a string that can be | |
// broadcast to everyone, then converted to an std::istringstream object that can be used just like std::ifstream | |
std::ostringstream oss; | |
oss << is.rdbuf(); | |
// use ss.str() to get the entire contents of the file as an std::string | |
// broadcast | |
// then make an std::istringstream in each process | |
std::istringstream iss(oss.str()); | |
// check the extension: if .xyz, assume the standard XYZ format, otherwise throw an exception | |
if ( filename.rfind(".xyz") != std::string::npos) | |
return read_dotxyz(iss); | |
else | |
throw std::invalid_argument("only .xyz files are accepted"); | |
} | |
std::vector<libint2::Shell> make_sto3g_basis(const std::vector<Atom>& atoms) { | |
using libint2::Shell; | |
std::vector<Shell> shells; | |
for(auto a=0; a<atoms.size(); ++a) { | |
// STO-3G basis set | |
// cite: W. J. Hehre, R. F. Stewart, and J. A. Pople, The Journal of Chemical Physics 51, 2657 (1969) | |
// doi: 10.1063/1.1672392 | |
// obtained from https://bse.pnl.gov/bse/portal | |
switch (atoms[a].atomic_number) { | |
case 1: // Z=1: hydrogen | |
shells.push_back( | |
{ | |
{3.425250910, 0.623913730, 0.168855400}, // exponents of primitive Gaussians | |
{ // contraction 0: s shell (l=0), spherical=false, contraction coefficients | |
{0, false, {0.15432897, 0.53532814, 0.44463454}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} // origin coordinates | |
} | |
); | |
break; | |
case 6: // Z=6: carbon | |
shells.push_back( | |
{ | |
{71.616837000, 13.045096000, 3.530512200}, | |
{ | |
{0, false, {0.15432897, 0.53532814, 0.44463454}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
shells.push_back( | |
{ | |
{2.941249400, 0.683483100, 0.222289900}, | |
{ | |
{0, false, {-0.09996723, 0.39951283, 0.70011547}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
shells.push_back( | |
{ | |
{2.941249400, 0.683483100, 0.222289900}, | |
{ // contraction 0: p shell (l=1), spherical=false | |
{1, false, {0.15591627, 0.60768372, 0.39195739}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
break; | |
case 7: // Z=7: nitrogen | |
shells.push_back( | |
{ | |
{99.106169000, 18.052312000, 4.885660200}, | |
{ | |
{0, false, {0.15432897, 0.53532814, 0.44463454}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
shells.push_back( | |
{ | |
{3.780455900, 0.878496600, 0.285714400}, | |
{ | |
{0, false, {-0.09996723, 0.39951283, 0.70011547}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
shells.push_back( | |
{ | |
{3.780455900, 0.878496600, 0.285714400}, | |
{ // contraction 0: p shell (l=1), spherical=false | |
{1, false, {0.15591627, 0.60768372, 0.39195739}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
break; | |
case 8: // Z=8: oxygen | |
shells.push_back( | |
{ | |
{130.709320000, 23.808861000, 6.443608300}, | |
{ | |
{0, false, {0.15432897, 0.53532814, 0.44463454}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
shells.push_back( | |
{ | |
{5.033151300, 1.169596100, 0.380389000}, | |
{ | |
{0, false, {-0.09996723, 0.39951283, 0.70011547}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
shells.push_back( | |
{ | |
{5.033151300, 1.169596100, 0.380389000}, | |
{ // contraction 0: p shell (l=1), spherical=false | |
{1, false, {0.15591627, 0.60768372, 0.39195739}} | |
}, | |
{{atoms[a].x, atoms[a].y, atoms[a].z}} | |
} | |
); | |
break; | |
default: | |
throw "do not know STO-3G basis for this Z"; | |
} | |
} | |
return shells; | |
} | |
size_t nbasis(const std::vector<libint2::Shell>& shells) { | |
size_t n = 0; | |
for (const auto& shell: shells) | |
n += shell.size(); | |
return n; | |
} | |
size_t max_nprim(const std::vector<libint2::Shell>& shells) { | |
size_t n = 0; | |
for (auto shell: shells) | |
n = std::max(shell.nprim(), n); | |
return n; | |
} | |
int max_l(const std::vector<libint2::Shell>& shells) { | |
int l = 0; | |
for (auto shell: shells) | |
for (auto c: shell.contr) | |
l = std::max(c.l, l); | |
return l; | |
} | |
std::vector<size_t> map_shell_to_basis_function(const std::vector<libint2::Shell>& shells) { | |
std::vector<size_t> result; | |
result.reserve(shells.size()); | |
size_t n = 0; | |
for (auto shell: shells) { | |
result.push_back(n); | |
n += shell.size(); | |
} | |
return result; | |
} | |
// computes Superposition-Of-Atomic-Densities guess for the molecular density matrix | |
// in minimal basis; occupies subshells by smearing electrons evenly over the orbitals | |
Matrix compute_soad(const std::vector<Atom>& atoms) { | |
// compute number of atomic orbitals | |
size_t nao = 0; | |
for(const auto& atom: atoms) { | |
const auto Z = atom.atomic_number; | |
if (Z == 1 || Z == 2) // H, He | |
nao += 1; | |
else if (Z <= 10) // Li - Ne | |
nao += 5; | |
else | |
throw "SOAD with Z > 10 is not yet supported"; | |
} | |
// compute the minimal basis density | |
Matrix D = Matrix::Zero(nao, nao); | |
size_t ao_offset = 0; // first AO of this atom | |
for(const auto& atom: atoms) { | |
const auto Z = atom.atomic_number; | |
if (Z == 1 || Z == 2) { // H, He | |
D(ao_offset, ao_offset) = Z; // all electrons go to the 1s | |
ao_offset += 1; | |
} | |
else if (Z <= 10) { | |
D(ao_offset, ao_offset) = 2; // 2 electrons go to the 1s | |
D(ao_offset+1, ao_offset+1) = (Z == 3) ? 1 : 2; // Li? only 1 electron in 2s, else 2 electrons | |
// smear the remaining electrons in 2p orbitals | |
const double num_electrons_per_2p = (Z > 4) ? (double)(Z - 4)/3 : 0; | |
for(auto xyz=0; xyz!=3; ++xyz) | |
D(ao_offset+2+xyz, ao_offset+2+xyz) = num_electrons_per_2p; | |
ao_offset += 5; | |
} | |
} | |
return D * 0.5; // we use densities normalized to # of electrons/2 | |
} | |
Matrix compute_1body_ints(const std::vector<libint2::Shell>& shells, | |
libint2::Operator obtype, | |
const std::vector<Atom>& atoms) | |
{ | |
using libint2::Shell; | |
using libint2::Engine; | |
using libint2::Operator; | |
const auto n = nbasis(shells); | |
Matrix result(n,n); | |
// construct the overlap integrals engine | |
Engine engine(obtype, max_nprim(shells), max_l(shells), 0); | |
// nuclear attraction ints engine needs to know where the charges sit ... | |
// the nuclei are charges in this case; in QM/MM there will also be classical charges | |
if (obtype == Operator::nuclear) { | |
std::vector<std::pair<real_t,std::array<real_t,3>>> q; | |
for(const auto& atom : atoms) { | |
q.push_back( {static_cast<real_t>(atom.atomic_number), {{atom.x, atom.y, atom.z}}} ); | |
} | |
engine.set_params(q); | |
} | |
auto shell2bf = map_shell_to_basis_function(shells); | |
// buf[0] points to the target shell set after every call to engine.compute() | |
const auto& buf = engine.results(); | |
// loop over unique shell pairs, {s1,s2} such that s1 >= s2 | |
// this is due to the permutational symmetry of the real integrals over Hermitian operators: (1|2) = (2|1) | |
for(auto s1=0; s1!=shells.size(); ++s1) { | |
auto bf1 = shell2bf[s1]; // first basis function in this shell | |
auto n1 = shells[s1].size(); | |
for(auto s2=0; s2<=s1; ++s2) { | |
auto bf2 = shell2bf[s2]; | |
auto n2 = shells[s2].size(); | |
// compute shell pair | |
engine.compute(shells[s1], shells[s2]); | |
// "map" buffer to a const Eigen Matrix, and copy it to the corresponding blocks of the result | |
Eigen::Map<const Matrix> buf_mat(buf[0], n1, n2); | |
result.block(bf1, bf2, n1, n2) = buf_mat; | |
if (s1 != s2) // if s1 >= s2, copy {s1,s2} to the corresponding {s2,s1} block, note the transpose! | |
result.block(bf2, bf1, n2, n1) = buf_mat.transpose(); | |
} | |
} | |
return result; | |
} | |
Matrix compute_2body_fock_simple(const std::vector<libint2::Shell>& shells, | |
const Matrix& D) { | |
using libint2::Shell; | |
using libint2::Engine; | |
using libint2::Operator; | |
const auto n = nbasis(shells); | |
Matrix G = Matrix::Zero(n,n); | |
// construct the electron repulsion integrals engine | |
Engine engine(Operator::coulomb, max_nprim(shells), max_l(shells), 0); | |
auto shell2bf = map_shell_to_basis_function(shells); | |
// buf[0] points to the target shell set after every call to engine.compute() | |
const auto& buf = engine.results(); | |
// loop over shell pairs of the Fock matrix, {s1,s2} | |
// Fock matrix is symmetric, but skipping it here for simplicity (see compute_2body_fock) | |
for(auto s1=0; s1!=shells.size(); ++s1) { | |
auto bf1_first = shell2bf[s1]; // first basis function in this shell | |
auto n1 = shells[s1].size(); | |
for(auto s2=0; s2!=shells.size(); ++s2) { | |
auto bf2_first = shell2bf[s2]; | |
auto n2 = shells[s2].size(); | |
// loop over shell pairs of the density matrix, {s3,s4} | |
// again symmetry is not used for simplicity | |
for(auto s3=0; s3!=shells.size(); ++s3) { | |
auto bf3_first = shell2bf[s3]; | |
auto n3 = shells[s3].size(); | |
for(auto s4=0; s4!=shells.size(); ++s4) { | |
auto bf4_first = shell2bf[s4]; | |
auto n4 = shells[s4].size(); | |
// Coulomb contribution to the Fock matrix is from {s1,s2,s3,s4} integrals | |
engine.compute(shells[s1], shells[s2], shells[s3], shells[s4]); | |
const auto* buf_1234 = buf[0]; | |
if (buf_1234 == nullptr) | |
continue; // if all integrals screened out, skip to next quartet | |
// we don't have an analog of Eigen for tensors (yet ... see github.com/BTAS/BTAS, under development) | |
// hence some manual labor here: | |
// 1) loop over every integral in the shell set (= nested loops over basis functions in each shell) | |
// and 2) add contribution from each integral | |
for(auto f1=0, f1234=0; f1!=n1; ++f1) { | |
const auto bf1 = f1 + bf1_first; | |
for(auto f2=0; f2!=n2; ++f2) { | |
const auto bf2 = f2 + bf2_first; | |
for(auto f3=0; f3!=n3; ++f3) { | |
const auto bf3 = f3 + bf3_first; | |
for(auto f4=0; f4!=n4; ++f4, ++f1234) { | |
const auto bf4 = f4 + bf4_first; | |
G(bf1,bf2) += D(bf3,bf4) * 2.0 * buf_1234[f1234]; | |
} | |
} | |
} | |
} | |
// exchange contribution to the Fock matrix is from {s1,s3,s2,s4} integrals | |
engine.compute(shells[s1], shells[s3], shells[s2], shells[s4]); | |
const auto* buf_1324 = buf[0]; | |
for(auto f1=0, f1324=0; f1!=n1; ++f1) { | |
const auto bf1 = f1 + bf1_first; | |
for(auto f3=0; f3!=n3; ++f3) { | |
const auto bf3 = f3 + bf3_first; | |
for(auto f2=0; f2!=n2; ++f2) { | |
const auto bf2 = f2 + bf2_first; | |
for(auto f4=0; f4!=n4; ++f4, ++f1324) { | |
const auto bf4 = f4 + bf4_first; | |
G(bf1,bf2) -= D(bf3,bf4) * buf_1324[f1324]; | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
return G; | |
} | |
Matrix compute_2body_fock(const std::vector<libint2::Shell>& shells, | |
const Matrix& D) { | |
using libint2::Shell; | |
using libint2::Engine; | |
using libint2::Operator; | |
auto time_elapsed = std::chrono::duration<double>::zero(); | |
const auto n = nbasis(shells); | |
Matrix G = Matrix::Zero(n,n); | |
// construct the 2-electron repulsion integrals engine | |
Engine engine(Operator::coulomb, max_nprim(shells), max_l(shells), 0); | |
auto shell2bf = map_shell_to_basis_function(shells); | |
const auto& buf = engine.results(); | |
// The problem with the simple Fock builder is that permutational symmetries of the Fock, | |
// density, and two-electron integrals are not taken into account to reduce the cost. | |
// To make the simple Fock builder efficient we must rearrange our computation. | |
// The most expensive step in Fock matrix construction is the evaluation of 2-e integrals; | |
// hence we must minimize the number of computed integrals by taking advantage of their permutational | |
// symmetry. Due to the multiplicative and Hermitian nature of the Coulomb kernel (and realness | |
// of the Gaussians) the permutational symmetry of the 2-e ints is given by the following relations: | |
// | |
// (12|34) = (21|34) = (12|43) = (21|43) = (34|12) = (43|12) = (34|21) = (43|21) | |
// | |
// (here we use chemists' notation for the integrals, i.e in (ab|cd) a and b correspond to | |
// electron 1, and c and d -- to electron 2). | |
// | |
// It is easy to verify that the following set of nested loops produces a permutationally-unique | |
// set of integrals: | |
// foreach a = 0 .. n-1 | |
// foreach b = 0 .. a | |
// foreach c = 0 .. a | |
// foreach d = 0 .. (a == c ? b : c) | |
// compute (ab|cd) | |
// | |
// The only complication is that we must compute integrals over shells. But it's not that complicated ... | |
// | |
// The real trick is figuring out to which matrix elements of the Fock matrix each permutationally-unique | |
// (ab|cd) contributes. STOP READING and try to figure it out yourself. (to check your answer see below) | |
// loop over permutationally-unique set of shells | |
for(auto s1=0; s1!=shells.size(); ++s1) { | |
auto bf1_first = shell2bf[s1]; // first basis function in this shell | |
auto n1 = shells[s1].size(); // number of basis functions in this shell | |
for(auto s2=0; s2<=s1; ++s2) { | |
auto bf2_first = shell2bf[s2]; | |
auto n2 = shells[s2].size(); | |
for(auto s3=0; s3<=s1; ++s3) { | |
auto bf3_first = shell2bf[s3]; | |
auto n3 = shells[s3].size(); | |
const auto s4_max = (s1 == s3) ? s2 : s3; | |
for(auto s4=0; s4<=s4_max; ++s4) { | |
auto bf4_first = shell2bf[s4]; | |
auto n4 = shells[s4].size(); | |
// compute the permutational degeneracy (i.e. # of equivalents) of the given shell set | |
auto s12_deg = (s1 == s2) ? 1.0 : 2.0; | |
auto s34_deg = (s3 == s4) ? 1.0 : 2.0; | |
auto s12_34_deg = (s1 == s3) ? (s2 == s4 ? 1.0 : 2.0) : 2.0; | |
auto s1234_deg = s12_deg * s34_deg * s12_34_deg; | |
const auto tstart = std::chrono::high_resolution_clock::now(); | |
engine.compute(shells[s1], shells[s2], shells[s3], shells[s4]); | |
const auto* buf_1234 = buf[0]; | |
if (buf_1234 == nullptr) | |
continue; // if all integrals screened out, skip to next quartet | |
const auto tstop = std::chrono::high_resolution_clock::now(); | |
time_elapsed += tstop - tstart; | |
// ANSWER | |
// 1) each shell set of integrals contributes up to 6 shell sets of the Fock matrix: | |
// F(a,b) += (ab|cd) * D(c,d) | |
// F(c,d) += (ab|cd) * D(a,b) | |
// F(b,d) -= 1/4 * (ab|cd) * D(a,c) | |
// F(b,c) -= 1/4 * (ab|cd) * D(a,d) | |
// F(a,c) -= 1/4 * (ab|cd) * D(b,d) | |
// F(a,d) -= 1/4 * (ab|cd) * D(b,c) | |
// 2) each permutationally-unique integral (shell set) must be scaled by its degeneracy, | |
// i.e. the number of the integrals/sets equivalent to it | |
// 3) the end result must be symmetrized | |
for(auto f1=0, f1234=0; f1!=n1; ++f1) { | |
const auto bf1 = f1 + bf1_first; | |
for(auto f2=0; f2!=n2; ++f2) { | |
const auto bf2 = f2 + bf2_first; | |
for(auto f3=0; f3!=n3; ++f3) { | |
const auto bf3 = f3 + bf3_first; | |
for(auto f4=0; f4!=n4; ++f4, ++f1234) { | |
const auto bf4 = f4 + bf4_first; | |
const auto value = buf_1234[f1234]; | |
const auto value_scal_by_deg = value * s1234_deg; | |
G(bf1,bf2) += D(bf3,bf4) * value_scal_by_deg; | |
G(bf3,bf4) += D(bf1,bf2) * value_scal_by_deg; | |
G(bf1,bf3) -= 0.25 * D(bf2,bf4) * value_scal_by_deg; | |
G(bf2,bf4) -= 0.25 * D(bf1,bf3) * value_scal_by_deg; | |
G(bf1,bf4) -= 0.25 * D(bf2,bf3) * value_scal_by_deg; | |
G(bf2,bf3) -= 0.25 * D(bf1,bf4) * value_scal_by_deg; | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
// symmetrize the result and return | |
Matrix Gt = G.transpose(); | |
return 0.5 * (G + Gt); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment