From 9e91fb76d776ea4c97a604ae061f8a33b7cd9dde Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Thu, 5 Feb 2026 14:36:57 -0500 Subject: [PATCH 01/64] Set-up preprocessor macro for defining RKB ERIs --- CMakeLists.txt | 203 ++++++++++++++++-------------- cmake/modules/int_am.cmake | 3 +- include/libint2/config.h.cmake.in | 19 +++ include/libint2/cxxapi.h | 2 +- src/bin/libint/build_libint.cc | 5 + 5 files changed, 134 insertions(+), 98 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d767bdcc..5b88cf73b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,28 +11,28 @@ endif () list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(DynamicVersion) dynamic_version( - PROJECT_PREFIX Libint2Compiler_ - GIT_ARCHIVAL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/.git_archival.txt - VERSION_FULL_MODE POST - OUTPUT_COMMIT LibintRepository_COMMIT - OUTPUT_VERSION LibintRepository_VERSION - OUTPUT_DESCRIBE LibintRepository_DESCRIBE - OUTPUT_DISTANCE LibintRepository_DISTANCE - OUTPUT_SHORT_HASH LibintRepository_SHORT_HASH - OUTPUT_VERSION_FULL LibintRepository_VERSION_FULL - ) + PROJECT_PREFIX Libint2Compiler_ + GIT_ARCHIVAL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/.git_archival.txt + VERSION_FULL_MODE POST + OUTPUT_COMMIT LibintRepository_COMMIT + OUTPUT_VERSION LibintRepository_VERSION + OUTPUT_DESCRIBE LibintRepository_DESCRIBE + OUTPUT_DISTANCE LibintRepository_DISTANCE + OUTPUT_SHORT_HASH LibintRepository_SHORT_HASH + OUTPUT_VERSION_FULL LibintRepository_VERSION_FULL +) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build.") # foil Ninja Debug on Windows project( - Libint2Compiler - VERSION ${LibintRepository_VERSION} - DESCRIPTION - "A library for the evaluation of molecular integrals of many-body operators over Gaussian functions" - HOMEPAGE_URL "http://libint.valeyev.net" - LANGUAGES CXX - ) - # * http://libint.valeyev.net/ redirects to https://github.com/evaleev/libint + Libint2Compiler + VERSION ${LibintRepository_VERSION} + DESCRIPTION + "A library for the evaluation of molecular integrals of many-body operators over Gaussian functions" + HOMEPAGE_URL "http://libint.valeyev.net" + LANGUAGES CXX +) +# * http://libint.valeyev.net/ redirects to https://github.com/evaleev/libint set(${PROJECT_NAME}_AUTHORS "Edward F. Valeev") set(${PROJECT_NAME}_LICENSE "GPL-3.0-only for generator; LGPL-3.0-only for generated library") @@ -109,64 +109,67 @@ option_with_default(CMAKE_BUILD_TYPE "Build type (Release or Debug)" Release) ### compiler-only option_with_print(LIBINT2_BUILD_LIBRARY_AS_SUBPROJECT - "[EXPERT] Build generated library as a subproject: if FALSE will configure and build separately" OFF) + "[EXPERT] Build generated library as a subproject: if FALSE will configure and build separately" OFF) ### library-only option_with_print(LIBINT2_REQUIRE_CXX_API - "C++11 Libint API: define library targets + test (requires Eigen3, Boost is optional but strongly recommended)" ON) + "C++11 Libint API: define library targets + test (requires Eigen3, Boost is optional but strongly recommended)" ON) option_with_print(LIBINT2_REQUIRE_CXX_API_COMPILED - "Build C++11 Compiled (not just header-only) targets (requires Eigen3, Boost strongly recommended)" ON) + "Build C++11 Compiled (not just header-only) targets (requires Eigen3, Boost strongly recommended)" ON) option_with_print(LIBINT2_ENABLE_FORTRAN - "Build Fortran03+ Libint interface (requires Fortran)" OFF) + "Build Fortran03+ Libint interface (requires Fortran)" OFF) option_with_print(LIBINT2_ENABLE_PYTHON - "Build Python bindings (requires Python and Pybind11 and Eigen3)" OFF) + "Build Python bindings (requires Python and Pybind11 and Eigen3)" OFF) option_with_print(LIBINT2_PREFIX_PYTHON_INSTALL - "For LIBINT2_ENABLE_PYTHON=ON, whether to install the Python module in the Linux manner to CMAKE_INSTALL_PREFIX or to not install it. See target libint2-python-wheel for alternate installation in the Python manner to Python_EXECUTABLE's site-packages." OFF) + "For LIBINT2_ENABLE_PYTHON=ON, whether to install the Python module in the Linux manner to CMAKE_INSTALL_PREFIX or to not install it. See target libint2-python-wheel for alternate installation in the Python manner to Python_EXECUTABLE's site-packages." OFF) option_with_print(BUILD_SHARED_LIBS - "Build Libint library as shared, not static" OFF) + "Build Libint library as shared, not static" OFF) option_with_print(LIBINT2_BUILD_SHARED_AND_STATIC_LIBS - "Build both shared and static Libint libraries in one shot. Uses -fPIC." OFF) + "Build both shared and static Libint libraries in one shot. Uses -fPIC." OFF) option_with_print(LIBINT2_ENABLE_MPFR - "Use GNU MPFR library for high-precision testing (EXPERTS ONLY). Consumed at library build-time." OFF) + "Use GNU MPFR library for high-precision testing (EXPERTS ONLY). Consumed at library build-time." OFF) # <<< Which Integrals Classes, Which Derivative Levels >>> option_with_default(LIBINT2_ENABLE_ONEBODY - "Compile with support for up to N-th derivatives of 1-body integrals (-1 for OFF)" 0) + "Compile with support for up to N-th derivatives of 1-body integrals (-1 for OFF)" 0) option_with_default(LIBINT2_ENABLE_ERI - "Compile with support for up to N-th derivatives of 4-center electron repulsion integrals (-1 for OFF)" 0) + "Compile with support for up to N-th derivatives of 4-center electron repulsion integrals (-1 for OFF)" 0) option_with_default(LIBINT2_ENABLE_ERI3 - "Compile with support for up to N-th derivatives of 3-center electron repulsion integrals (-1 for OFF)" -1) + "Compile with support for up to N-th derivatives of 3-center electron repulsion integrals (-1 for OFF)" -1) option_with_default(LIBINT2_ENABLE_ERI2 - "Compile with support for up to N-th derivatives of 2-center electron repulsion integrals (-1 for OFF)" -1) + "Compile with support for up to N-th derivatives of 2-center electron repulsion integrals (-1 for OFF)" -1) +option_with_default(LIBINT2_ENABLE_RKB_ERI + "Compile with support for up to N-th derivatives of relativistic restricted kinetic + balance (RKB) 4-center electron repulsion integrals (-1 for OFF)" 0) option_with_default(LIBINT2_ENABLE_G12 - "Compile with support for N-th derivatives of MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) + "Compile with support for N-th derivatives of MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) option_with_default(LIBINT2_ENABLE_G12DKH - "Compile with support for N-th derivatives of DKH-MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) + "Compile with support for N-th derivatives of DKH-MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) option_with_print(LIBINT2_DISABLE_ONEBODY_PROPERTY_DERIVS - "Disable geometric derivatives of 1-body property integrals (all but overlap, kinetic, elecpot). + "Disable geometric derivatives of 1-body property integrals (all but overlap, kinetic, elecpot). These derivatives are disabled by default to save compile time. (enable with OFF) Note that the libtool build won't enable this- if forcibly enabled, build_libint balks." ON) option_with_print(LIBINT2_ENABLE_T1G12 - "Enable [Ti,G12] integrals when G12 integrals are enabled. Irrelevant when `LIBINT2_ENABLE_G12=OFF`. (disable with OFF)" ON) + "Enable [Ti,G12] integrals when G12 integrals are enabled. Irrelevant when `LIBINT2_ENABLE_G12=OFF`. (disable with OFF)" ON) # <<< Ordering Conventions >>> option_with_default(LIBINT2_SHGAUSS_ORDERING - "Ordering for shells of solid harmonic Gaussians: + "Ordering for shells of solid harmonic Gaussians: standard -- standard ordering (-l, -l+1 ... l) gaussian -- the Gaussian ordering (0, 1, -1, 2, -2, ... l, -l) See https://github.com/evaleev/libint/blob/master/INSTALL.md#solid-harmonic-ordering-scope-and-history ." standard) option_with_default(LIBINT2_CARTGAUSS_ORDERING - "Orderings for shells of cartesian Gaussians: + "Orderings for shells of cartesian Gaussians: standard -- standard ordering (xxx, xxy, xxz, xyy, xyz, xzz, yyy, ...) intv3 -- intv3 ordering (yyy, yyz, yzz, zzz, xyy, xyz, xzz, xxy, xxz, xxx) gamess -- GAMESS ordering (xxx, yyy, zzz, xxy, xxz, yyx, yyz, zzx, zzy, xyz) orca -- ORCA ordering (hydrid between GAMESS and standard) bagel -- axis-permuted version of intv3 (xxx, xxy, xyy, yyy, xxz, xyz, yyz, xzz, yzz, zzz)" standard) option_with_default(LIBINT2_SHELL_SET - "Support computation of shell sets sets subject to these restrictions: + "Support computation of shell sets sets subject to these restrictions: standard -- standard ordering: for (ab|cd): l(a) >= l(b), @@ -195,99 +198,107 @@ option_with_default(LIBINT2_SHELL_SET # `export CMAKE_BUILD_PARALLEL_LEVEL=N`. option_with_default(LIBINT2_MAX_AM - "Support Gaussians of angular momentum up to N. + "Support Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. If ERI3 ints are enabled, this option also controls the AM of the paired centers." 4) option_with_default(LIBINT2_OPT_AM - "Optimize maximally for up to angular momentum N (N <= max-am). + "Optimize maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. (default: (libint_max_am/2)+1)" -1) option_with_default(LIBINT2_MULTIPOLE_MAX_ORDER - "Maximum order of spherical multipole integrals. There is no maximum" 4) + "Maximum order of spherical multipole integrals. There is no maximum" 4) option_with_default(LIBINT2_ONEBODY_MAX_AM - "Support 1-body ints for Gaussians of angular momentum up to N. + "Support 1-body ints for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am)" -1) option_with_default(LIBINT2_ONEBODY_OPT_AM - "Optimize 1-body ints maximally for up to angular momentum N (N <= max-am). + "Optimize 1-body ints maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) option_with_default(LIBINT2_ERI_MAX_AM - "Support 4-center ERIs for Gaussians of angular momentum up to N. + "Support 4-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am)" -1) option_with_default(LIBINT2_ERI_OPT_AM - "Optimize 4-center ERIs maximally for up to angular momentum N (N <= max-am). + "Optimize 4-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) +option_with_default(LIBINT2_RKB_ERI_MAX_AM + "Support relativistic restricted kinetic balance (RKB) 4-center ERIs for Gaussians of angular momentum up to N. + Can specify values for each derivative level as a semicolon-separated string. (default: max_am)" -1) +option_with_default(LIBINT2_RKB_ERI_OPT_AM + "Optimize relativistic restricted kinetic balance (RKB) 4-center ERIs maximally for up to angular momentum N (N <= max-am). + Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) + + option_with_default(LIBINT2_ERI3_MAX_AM - "Support 3-center ERIs for Gaussians of angular momentum up to N. + "Support 3-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am) This option controls only the single fitting center. The paired centers use LIBINT2_MAX_AM." -1) option_with_default(LIBINT2_ERI3_OPT_AM - "Optimize 3-center ERIs maximally for up to angular momentum N (N <= max-am). + "Optimize 3-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. (default: (max_am/2)+1)" -1) option_with_print(LIBINT2_ERI3_PURE_SH - "Assume the 'unpaired' center of 3-center ERIs will be transformed to pure solid harmonics" OFF) + "Assume the 'unpaired' center of 3-center ERIs will be transformed to pure solid harmonics" OFF) option_with_default(LIBINT2_ERI2_MAX_AM - "Support 2-center ERIs for Gaussians of angular momentum up to N. + "Support 2-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am)" -1) option_with_default(LIBINT2_ERI2_OPT_AM - "Optimize 2-center ERIs maximally for up to angular momentum N (N <= max-am). + "Optimize 2-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. (default: (max_am/2)+1)" -1) option_with_print(LIBINT2_ERI2_PURE_SH - "Assume the 2-center ERIs will be transformed to pure solid harmonics" OFF) + "Assume the 2-center ERIs will be transformed to pure solid harmonics" OFF) option_with_default(LIBINT2_G12_MAX_AM - "Support integrals for G12 methods of angular momentum up to N. (default: max_am)" -1) + "Support integrals for G12 methods of angular momentum up to N. (default: max_am)" -1) option_with_default(LIBINT2_G12_OPT_AM - "Optimize G12 integrals for up to angular momentum N (N <= max-am). (default: (max_am/2)+1)" -1) + "Optimize G12 integrals for up to angular momentum N (N <= max-am). (default: (max_am/2)+1)" -1) option_with_default(LIBINT2_G12DKH_MAX_AM - "Support integrals for relativistic G12 methods of angular momentum up to N. (default: max_am)" -1) + "Support integrals for relativistic G12 methods of angular momentum up to N. (default: max_am)" -1) option_with_default(LIBINT2_G12DKH_OPT_AM - "Optimize G12DKH integrals for up to angular momentum N (N <= max-am). (default: (max_am/2)+1)" -1) + "Optimize G12DKH integrals for up to angular momentum N (N <= max-am). (default: (max_am/2)+1)" -1) # <<< Miscellaneous >>> option_with_print(LIBINT2_CONTRACTED_INTS - "Turn on support for contracted integrals." ON) + "Turn on support for contracted integrals." ON) option_with_default(LIBINT2_ERI_STRATEGY - "(EXPERT) Compute ERIs using the following strategy. (0 for OS, 1 for HGP, 2 for HL)" 1) + "(EXPERT) Compute ERIs using the following strategy. (0 for OS, 1 for HGP, 2 for HL)" 1) option_with_print(LIBINT2_USE_COMPOSITE_EVALUATORS - "Libint will use composite evaluators (i.e. every evaluator will compute one integral type only)" ON) + "Libint will use composite evaluators (i.e. every evaluator will compute one integral type only)" ON) option_with_print(LIBINT2_SINGLE_EVALTYPE - "Generate single evaluator type (i.e. all tasks use the same evaluator). OFF is NYI" ON) + "Generate single evaluator type (i.e. all tasks use the same evaluator). OFF is NYI" ON) option_with_default(LIBINT2_ENABLE_UNROLLING - "Unroll shell sets into integrals (will unroll shell sets larger than N) (0 for never, N for N, 1000000000 for always)" 100) + "Unroll shell sets into integrals (will unroll shell sets larger than N) (0 for never, N for N, 1000000000 for always)" 100) option_with_default(LIBINT2_ALIGN_SIZE - "(EXPERT) if posix_memalign is available, this will specify alignment of Libint data, in units of + "(EXPERT) if posix_memalign is available, this will specify alignment of Libint data, in units of sizeof(LIBINT2_REALTYPE). Default is to use built-in heuristics: system-determined for vectorization off (default) or veclen * sizeof(LIBINT2_REALTYPE) for vectorization on." 0) mark_as_advanced(LIBINT2_ALIGN_SIZE) option_with_default(LIBINT2_REALTYPE - "Specifies the floating-point data type used by the library. Consumed at library build-time." double) + "Specifies the floating-point data type used by the library. Consumed at library build-time." double) option_with_print(LIBINT2_USER_DEFINED_REAL_INCLUDES - "Additional #includes necessary to use the real type." OFF) + "Additional #includes necessary to use the real type." OFF) include(int_userreal) option_with_print(LIBINT2_GENERATE_FMA - "Generate FMA (fused multiply-add) instructions (to benefit must have FMA-capable hardware and compiler)" OFF) + "Generate FMA (fused multiply-add) instructions (to benefit must have FMA-capable hardware and compiler)" OFF) option_with_print(LIBINT2_ENABLE_GENERIC_CODE - "Use manually-written generic code" OFF) + "Use manually-written generic code" OFF) option_with_print(LIBINT2_API_PREFIX - "Prepend this string to every name in the library API (except for the types)." OFF) + "Prepend this string to every name in the library API (except for the types)." OFF) option_with_print(LIBINT2_VECTOR_LENGTH - "Compute integrals in vectors of length N." OFF) + "Compute integrals in vectors of length N." OFF) option_with_default(LIBINT2_VECTOR_METHOD - "Specifies how to vectorize integrals. Irrelevant when `LIBINT2_VECTOR_LENGTH=OFF. Allowed values are 'block' (default), and 'line'." block) + "Specifies how to vectorize integrals. Irrelevant when `LIBINT2_VECTOR_LENGTH=OFF. Allowed values are 'block' (default), and 'line'." block) option_with_print(LIBINT2_ACCUM_INTS - "Accumulate integrals to the buffer, rather than copy (OFF for copy, ON for accum)." OFF) + "Accumulate integrals to the buffer, rather than copy (OFF for copy, ON for accum)." OFF) option_with_print(LIBINT2_FLOP_COUNT - "Support (approximate) FLOP counting by the library. (Generated code will require C++11!)" OFF) + "Support (approximate) FLOP counting by the library. (Generated code will require C++11!)" OFF) option_with_print(LIBINT2_PROFILE - "Turn on profiling instrumentation of the library. (Generated code will require C++11!)" OFF) + "Turn on profiling instrumentation of the library. (Generated code will require C++11!)" OFF) option_with_print(LIBINT2_ENABLE_MPFR - "Use GNU MPFR library for high-precision testing (EXPERTS ONLY). Consumed at library build-time." OFF) + "Use GNU MPFR library for high-precision testing (EXPERTS ONLY). Consumed at library build-time." OFF) option_with_default(LIBINT2_EXPORT_COMPRESSOR - "Export tarball with compression gzip or bzip2" gzip) + "Export tarball with compression gzip or bzip2" gzip) # next one defined by `include(CTest)` message(STATUS "Showing option BUILD_TESTING: ${BUILD_TESTING}") @@ -304,13 +315,13 @@ include(int_am) check_function_exists(posix_memalign HAVE_POSIX_MEMALIGN) if (NOT HAVE_POSIX_MEMALIGN) message(FATAL_ERROR "did not find posix_memalign ... this SHOULD NOT happen. Cannot proceed.") -endif() +endif () check_include_file_cxx(stdint.h HAVE_STDINT_H) # limits.h? if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES) set(LIBINT_HAS_CXX11 1) -endif() +endif () booleanize01(LIBINT2_ERI3_PURE_SH) booleanize01(LIBINT2_ERI2_PURE_SH) @@ -331,9 +342,9 @@ if (LIBINT2_EXPORT_COMPRESSOR STREQUAL "gzip") elseif (LIBINT2_EXPORT_COMPRESSOR STREQUAL "bzip2") set(LIBINT_EXPORT_COMPRESSOR_CMD "jcf") set(LIBINT_EXPORT_COMPRESSOR_EXT "tbz2") -else() +else () message(FATAL_ERROR "No valid compressor; invoke CMake with -DLIBINT2_EXPORT_COMPRESSOR=gzip|bzip2") -endif() +endif () ################################## Dependencies ################################# @@ -344,9 +355,9 @@ if (LIBINT2_ENABLE_MPFR) # mpfr detected in CMakeLists.txt.export at appropriate time for library, but prechecking here find_package(Multiprecision MODULE REQUIRED COMPONENTS gmpxx mpfr) set(LIBINT_HAS_MPFR 1) -else() +else () find_package(Multiprecision MODULE REQUIRED COMPONENTS gmpxx) -endif() +endif () get_property(_loc TARGET Multiprecision::gmp PROPERTY LOCATION) message(VERBOSE "${Cyan}Found GMP${ColourReset}: ${_loc}") @@ -355,12 +366,12 @@ message(VERBOSE "${Cyan}Found GMPXX${ColourReset}: ${_loc}") if (TARGET Multiprecision::mpfr) get_property(_loc TARGET Multiprecision::mpfr PROPERTY LOCATION) message(VERBOSE "${Cyan}Found MPFR${ColourReset}: ${_loc} (found version ${MPFR_VERSION})") -endif() +endif () find_package(Boost 1.57 REQUIRED) if (TARGET Boost::headers) set(LIBINT_HAS_SYSTEM_BOOST_PREPROCESSOR_VARIADICS 1) -endif() +endif () # deferring find_package(Eigen3) to library (CMakeLists.txt.export) @@ -370,9 +381,9 @@ endif() set(EXPORT_STAGE_DIR ${PROJECT_BINARY_DIR}/libint-${LIBINT_EXT_VERSION}) configure_file( - cmake/modules/int_computed.cmake.in - cmake/modules/int_computed.cmake - @ONLY) + cmake/modules/int_computed.cmake.in + cmake/modules/int_computed.cmake + @ONLY) # CMake data transmitted to C++ via config.h for generator/compiler (_EXPORT_MODE=0). # Same info is positioned for the library export, but _EXPORT_MODE=1 turns on @@ -380,28 +391,28 @@ configure_file( # library build time. set(_EXPORT_MODE 0) # convert user-facing LIBINT2_ variables to LIBINT_ internal variables -foreach(_var API_PREFIX;ERI3_PURE_SH;ERI2_PURE_SH;DISABLE_ONEBODY_PROPERTY_DERIVS;ENABLE_UNROLLING;ENABLE_GENERIC_CODE;VECTOR_LENGTH;VECTOR_METHOD;ALIGN_SIZE;USER_DEFINED_REAL;USER_DEFINED_REAL_INCLUDES;GENERATE_FMA;ACCUM_INTS;FLOP_COUNT;PROFILE;CONTRACTED_INTS;SINGLE_EVALTYPE;USE_COMPOSITE_EVALUATORS;ERI_STRATEGY;MULTIPOLE_MAX_ORDER) +foreach (_var API_PREFIX;ERI3_PURE_SH;ERI2_PURE_SH;DISABLE_ONEBODY_PROPERTY_DERIVS;ENABLE_UNROLLING;ENABLE_GENERIC_CODE;VECTOR_LENGTH;VECTOR_METHOD;ALIGN_SIZE;USER_DEFINED_REAL;USER_DEFINED_REAL_INCLUDES;GENERATE_FMA;ACCUM_INTS;FLOP_COUNT;PROFILE;CONTRACTED_INTS;SINGLE_EVALTYPE;USE_COMPOSITE_EVALUATORS;ERI_STRATEGY;MULTIPOLE_MAX_ORDER) if (DEFINED LIBINT2_${_var}) if (DEFINED LIBINT_${_var}) message(FATAL_ERROR "renaming user-facing LIBINT2_${_var} variable but internal variable LIBINT_${_var} already exists") else () set(LIBINT_${_var} ${LIBINT2_${_var}}) - endif() - endif() + endif () + endif () endforeach () configure_file( - include/libint2/config.h.cmake.in - include/libint2/config.h - @ONLY) + include/libint2/config.h.cmake.in + include/libint2/config.h + @ONLY) set(_EXPORT_MODE 1) configure_file( - include/libint2/config.h.cmake.in - ${EXPORT_STAGE_DIR}/include/libint2/config.h - @ONLY) + include/libint2/config.h.cmake.in + ${EXPORT_STAGE_DIR}/include/libint2/config.h + @ONLY) configure_file( - include/libint2/config2.h.cmake.in - ${EXPORT_STAGE_DIR}/include/libint2/config2.h.cmake.in - COPYONLY) + include/libint2/config2.h.cmake.in + ${EXPORT_STAGE_DIR}/include/libint2/config2.h.cmake.in + COPYONLY) add_subdirectory(src) diff --git a/cmake/modules/int_am.cmake b/cmake/modules/int_am.cmake index c1d61cd55..782048aaf 100644 --- a/cmake/modules/int_am.cmake +++ b/cmake/modules/int_am.cmake @@ -357,6 +357,7 @@ endmacro() process_integrals_class(ONEBODY) process_integrals_class(ERI) +process_integrals_class(RKB_ERI) process_integrals_class(ERI3) process_integrals_class(ERI2) # unlike above, these classes (1) don't do AM_LIST and (2) require value in config.h if enabled @@ -396,7 +397,7 @@ list(REVERSE _amlist) list(APPEND Libint2_ERI_COMPONENTS "${_amlist}") message(VERBOSE "setting components ${_amlist}") -foreach(_cls ONEBODY;ERI;ERI3;ERI2;G12;G12DKH) +foreach(_cls ONEBODY;ERI;RKB_ERI;ERI3;ERI2;G12;G12DKH) if((_cls STREQUAL G12) OR (_cls STREQUAL G12DKH)) add_feature_info( "integral class ${_cls}" diff --git a/include/libint2/config.h.cmake.in b/include/libint2/config.h.cmake.in index 640e68099..6018873e5 100644 --- a/include/libint2/config.h.cmake.in +++ b/include/libint2/config.h.cmake.in @@ -71,6 +71,13 @@ #undef LIBINT_INCLUDE_ERI #endif +/* Support ERI derivatives up to this order */ +#define LIBINT_INCLUDE_RKB_ERI @LIBINT_INCLUDE_RKB_ERI@ +#if @LIBINT_INCLUDE_RKB_ERI@ == -1 +#undef LIBINT_INCLUDE_RKB_ERI +#endif + + /* Support 3-center ERI derivatives up to this order */ #define LIBINT_INCLUDE_ERI3 @LIBINT_INCLUDE_ERI3@ #if @LIBINT_INCLUDE_ERI3@ == -1 @@ -122,6 +129,18 @@ /* Max optimized AM for ERI and its derivatives */ #cmakedefine LIBINT_ERI_OPT_AM_LIST "@LIBINT_ERI_OPT_AM_LIST@" +/* Max AM for RKB_ERI (same for all derivatives; if not defined see LIBINT_ERI_MAX_AM_LIST) */ +#cmakedefine LIBINT_RKB_ERI_MAX_AM @LIBINT_RKB_ERI_MAX_AM@ + +/* Max AM for RKB_ERI and its derivatives */ +#cmakedefine LIBINT_RKB_ERI_MAX_AM_LIST "@LIBINT_RKB_ERI_MAX_AM_LIST@" + +/* Max optimized AM for ERI (same for all derivatives; if not defined see LIBINT_ERI_OPT_AM_LIST) */ +#cmakedefine LIBINT_RKB_ERI_OPT_AM @LIBINT_RKB_ERI_OPT_AM@ + +/* Max optimized AM for ERI and its derivatives */ +#cmakedefine LIBINT_RKB_ERI_OPT_AM_LIST "@LIBINT_RKB_ERI_OPT_AM_LIST@" + /* Max AM for 3-center ERI (same for all derivatives; if not defined see LIBINT_ERI3_MAX_AM_LIST) */ #cmakedefine LIBINT_ERI3_MAX_AM @LIBINT_ERI3_MAX_AM@ diff --git a/include/libint2/cxxapi.h b/include/libint2/cxxapi.h index 0aceb509b..a54802817 100644 --- a/include/libint2/cxxapi.h +++ b/include/libint2/cxxapi.h @@ -35,7 +35,7 @@ #if !defined(LIBINT_INCLUDE_ONEBODY) || \ !(defined(LIBINT_INCLUDE_ERI) || defined(LIBINT_INCLUDE_ERI3) || \ - defined(LIBINT_INCLUDE_ERI2)) + defined(LIBINT_INCLUDE_ERI2) || defined(LIBINT_INCLUDE_RKB_ERI)) #error \ "C++ API is only supported if both 1-body and some (eri, eri3, eri2) 2-body integrals are enabled" #endif diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 7baef7f1c..c0a69c6bd 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -567,6 +567,7 @@ void try_main(int argc, char* argv[]) { taskmgr.add(task_label("eri", d)); } #endif + #ifdef LIBINT_INCLUDE_ERI3 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI3; ++d) { taskmgr.add(task_label("3eri", d)); @@ -985,8 +986,12 @@ void print_config(std::ostream& os) { #ifdef LIBINT_INCLUDE_G12DKH os << "Will support G12DKH" << endl; #endif +#ifdef LIBINT_INCLUDE_RKB_ERI + os << "RKB works " << std::endl; +#endif } + #ifdef LIBINT_INCLUDE_ERI void build_TwoPRep_2b_2k(std::ostream& os, const std::shared_ptr& cparams, From 7941816503701ba47473d9660e9380bac7297c9a Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sat, 7 Feb 2026 14:32:26 -0500 Subject: [PATCH 02/64] =?UTF-8?q?Can=20generate=20code=20for=20`(LL|SS)`?= =?UTF-8?q?=20type=20integral,=20i.e.,=20`(=CE=BC=20=CE=BD=20|=20(=CF=83.p?= =?UTF-8?q?)=CE=BA=20(=CF=83.p)=CE=BB)`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bin/libint/build_libint.cc | 186 ++++++++++++++---- .../comp_11_Coulomb\317\203p\317\203p_11.h" | 167 ++++++++++++++++ .../libint/comp_1_\317\203pV\317\203p_1.h" | 4 +- src/bin/libint/master_ints_list.h | 6 +- src/bin/libint/master_rrs_list.h | 3 + src/bin/libint/oper.h | 58 +++++- src/bin/libint/strategy.cc | 9 + 7 files changed, 383 insertions(+), 50 deletions(-) create mode 100644 "src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index c0a69c6bd..9ff8f5710 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -193,12 +193,16 @@ static void config_to_api(const std::shared_ptr& cparams, #ifdef LIBINT_INCLUDE_ERI #define USE_GENERIC_ERI_BUILD 1 #if !USE_GENERIC_ERI_BUILD +template static void build_TwoPRep_2b_2k( - std::ostream& os, const std::shared_ptr& cparams, + std::ostream& os, std::string label, + const std::shared_ptr& cparams, std::shared_ptr& iface); #else +template static void build_TwoPRep_2b_2k( - std::ostream& os, const std::shared_ptr& cparams, + std::ostream& os, std::string label, + const std::shared_ptr& cparams, std::shared_ptr& iface, unsigned int deriv_level); #endif #endif @@ -568,6 +572,23 @@ void try_main(int argc, char* argv[]) { } #endif +#ifdef LIBINT_INCLUDE_RKB_ERI +#define BOOST_PP_RKB_ERI_TASK_TUPLE (coulomb_opop) +#define BOOST_PP_RKB_ERI_TASK_OPER_TUPLE (CoulombσpσpOper) +#define BOOST_PP_RKB_ERI_TASK_LIST \ + BOOST_PP_TUPLE_TO_LIST(BOOST_PP_RKB_ERI_TASK_TUPLE) +#define BOOST_PP_RKB_ERI_TASK_OPER_LIST \ + BOOST_PP_TUPLE_TO_LIST(BOOST_PP_RKB_ERI_TASK_OPER_TUPLE) + + for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI; ++d) { +#define BOOST_PP_RKB_ERI_MCR1(r, data, elem) \ + taskmgr.add(task_label(BOOST_PP_STRINGIZE(elem), d)); + + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI_MCR1, _, BOOST_PP_RKB_ERI_TASK_LIST) +#undef BOOST_PP_RKB_ERI_MCR1 + } +#endif + #ifdef LIBINT_INCLUDE_ERI3 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI3; ++d) { taskmgr.add(task_label("3eri", d)); @@ -669,6 +690,46 @@ void try_main(int argc, char* argv[]) { cparams->num_bf(task_label("eri", d), 4); } #endif + +#ifdef LIBINT_INCLUDE_RKB_ERI + for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI; ++d) { +#if defined(LIBINT_RKB_ERI_MAX_AM_LIST) +#define BOOST_PP_RKB_ERI_MCR2(r, data, elem) \ + cparams->max_am( \ + task_label(BOOST_PP_STRINGIZE(elem), d), \ + token(LIBINT_RKB_ERI_MAX_AM_LIST, ',', d)); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI_MCR2, _, BOOST_PP_RKB_ERI_TASK_LIST) +#undef BOOST_PP_RKB_ERI_MCR2 +#elif defined(LIBINT_RKB_ERI_MAX_AM) +#define BOOST_PP_RKB_ERI_MCR3(r, data, elem) \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + LIBINT_RKB_ERI_MAX_AM); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI_MCR3, _, BOOST_PP_RKB_ERI_TASK_LIST) +#undef BOOST_PP_RKB_ERI_MCR3 +#endif +#if defined(LIBINT_RKB_ERI_OPT_AM_LIST) +#define BOOST_PP_RKB_ERI_MCR4(r, data, elem) \ + cparams->max_am_opt( \ + task_label(BOOST_PP_STRINGIZE(elem), d), \ + token(LIBINT_RKB_ERI_OPT_AM_LIST, ',', d)); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI_MCR4, _, BOOST_PP_RKB_ERI_TASK_LIST) +#undef BOOST_PP_RKB_ERI_MCR4 +#elif defined(LIBINT_RKB_ERI_OPT_AM) +#define BOOST_PP_RKB_ERI_MCR5(r, data, elem) \ + cparams->max_am_opt(task_label(BOOST_PP_STRINGIZE(elem), d), \ + LIBINT_RKB_ERI_OPT_AM); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI_MCR5, _, BOOST_PP_RKB_ERI_TASK_LIST) +#undef BOOST_PP_RKB_ERI_MCR5 +#endif + } + for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI; ++d) { +#define BOOST_PP_RKB_ERI_MCR6(r, data, elem) \ + cparams->num_bf(task_label(BOOST_PP_STRINGIZE(elem), d), 4); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI_MCR6, _, BOOST_PP_RKB_ERI_TASK_LIST) +#undef BOOST_PP_RKB_ERI_MCR6 + } +#endif // LIBINT_INCLUDE_RKB_ERI + #ifdef LIBINT_INCLUDE_ERI3 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI3; ++d) { #if defined(LIBINT_ERI3_MAX_AM_LIST) @@ -853,6 +914,9 @@ void try_main(int argc, char* argv[]) { #ifdef LIBINT_INCLUDE_ERI max_deriv = std::max(LIBINT_INCLUDE_ERI, max_deriv); #endif +#ifdef LIBINT_INCLUDE_RKB_ERI + max_deriv = std::max(LIBINT_INCLUDE_RKB_ERI, max_deriv); +#endif #ifdef LIBINT_INCLUDE_ERI3 max_deriv = std::max(LIBINT_INCLUDE_ERI3, max_deriv); #endif @@ -880,13 +944,25 @@ void try_main(int argc, char* argv[]) { #endif #ifdef LIBINT_INCLUDE_ERI #if !USE_GENERIC_ERI_BUILD - build_TwoPRep_2b_2k(os, cparams, iface); + build_TwoPRep_2b_2k(os, "eri", cparams, iface); #else for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI; ++d) { - build_TwoPRep_2b_2k(os, cparams, iface, d); + build_TwoPRep_2b_2k(os, "eri", cparams, iface, d); } #endif #endif + +#ifdef LIBINT_INCLUDE_RKB_ERI + for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI; ++d) { +#define BOOST_PP_RKB_ERI_MCR7(r, data, i, elem) \ + build_TwoPRep_2b_2k( \ + os, BOOST_PP_STRINGIZE(elem), cparams, iface, d); + BOOST_PP_LIST_FOR_EACH_I(BOOST_PP_RKB_ERI_MCR7, _, + BOOST_PP_RKB_ERI_TASK_LIST) +#undef BOOST_PP_RKB_ERI_MCR7 + } +#endif + #ifdef LIBINT_INCLUDE_ERI3 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI3; ++d) { build_TwoPRep_1b_2k(os, cparams, iface, d); @@ -987,18 +1063,22 @@ void print_config(std::ostream& os) { os << "Will support G12DKH" << endl; #endif #ifdef LIBINT_INCLUDE_RKB_ERI - os << "RKB works " << std::endl; + os << "Will support restricted kinetically balance (RKB) 4-center ERIs " + << std::endl; #endif } - #ifdef LIBINT_INCLUDE_ERI -void build_TwoPRep_2b_2k(std::ostream& os, +template +void build_TwoPRep_2b_2k(std::ostream& os, std::string label, const std::shared_ptr& cparams, std::shared_ptr& iface, unsigned int deriv_level) { - const std::string task = task_label("eri", deriv_level); - typedef TwoPRep_11_11_sq TwoPRep_sh_11_11; + typedef GenIntegralSet_11_11 TwoBody_sh_11_11; + typedef typename OperType::Descriptor OperDescrType; + + const std::string task = task_label(label, deriv_level); + vector shells; unsigned int lmax = cparams->max_am(task); for (unsigned int l = 0; l <= lmax; l++) { @@ -1010,6 +1090,7 @@ void build_TwoPRep_2b_2k(std::ostream& os, taskmgr.current(task); iface->to_params(iface->macro_define(std::string("MAX_AM_") + task, lmax)); + const auto nullaux = typename TwoBody_sh_11_11::AuxIndexType(0u); // // Construct graphs for each desired target integral and // 1) generate source code for the found traversal path @@ -1041,9 +1122,25 @@ void build_TwoPRep_2b_2k(std::ostream& os, const int lim = 1; if (!(la == lim && lb == lim && lc == lim && ld == lim)) continue; #endif + // this will hold all target shell sets + std::vector> targets; + + ///////////////////////////////// + // loop over operator components + ///////////////////////////////// + // most important operators have 1 component ... + std::vector descrs(1); // operator descriptors + // important EXCEPTION: multipole moments + if (std::is_same::value) { + // reset descriptors array + descrs.resize(0); + // iterate over quaternion components + for (int p = 0; p != 4; ++p) { + descrs.emplace_back(make_descr(p)); + } + } - // unroll only if max_am <= cparams->max_am_opt(task) - using std::max; + // unroll only if max_am <= cparams->max_am_opt(task) using std::max; const unsigned int max_am = max(max(la, lb), max(lc, ld)); const bool need_to_optimize = (max_am <= cparams->max_am_opt(task)); const bool need_to_unroll = @@ -1072,7 +1169,7 @@ void build_TwoPRep_2b_2k(std::ostream& os, //////////// // NB translational invariance is now handled by CR_DerivGauss CartesianDerivIterator<4> diter(deriv_level); - std::vector> targets; + bool last_deriv = false; do { CGShell a(la); @@ -1089,18 +1186,22 @@ void build_TwoPRep_2b_2k(std::ostream& os, } } - std::shared_ptr abcd = - TwoPRep_sh_11_11::Instance(a, b, c, d, mType(0u)); - targets.push_back(abcd); + // operator component loop + for (unsigned int op = 0; op != descrs.size(); ++op) { + OperType oper(descrs[op]); + + std::shared_ptr abcd = + TwoBody_sh_11_11::Instance(a, b, c, d, nullaux, oper); + targets.push_back(abcd); + } + last_deriv = diter.last(); if (!last_deriv) diter.next(); } while (!last_deriv); // append all derivatives as targets to the graph - for (std::vector>::const_iterator - t = targets.begin(); - t != targets.end(); ++t) { + for (auto it = targets.begin(); it != targets.end(); ++it) { std::shared_ptr t_ptr = - std::dynamic_pointer_cast(*t); + std::dynamic_pointer_cast(*it); dg_xxxx->append_target(t_ptr); } @@ -1112,23 +1213,35 @@ void build_TwoPRep_2b_2k(std::ostream& os, CGShell b(lb); CGShell c(lc); CGShell d(ld); - std::shared_ptr abcd = - TwoPRep_sh_11_11::Instance(a, b, c, d, mType(0u)); - abcd_label = abcd->label(); + + if constexpr (!std::is_same::value) { + OperType oper; + oper = OperType(descrs[0]); + std::shared_ptr abcd = + TwoBody_sh_11_11::Instance(a, b, c, d, nullaux, oper); + abcd_label = abcd->label(); + } else { + std::ostringstream oss; + oss << cparams->api_prefix(); + oss << "_" << a.label() << "_" << b.label(); + oss << "_" << label; + oss << "_" << c.label() << "_" << d.label(); + abcd_label = oss.str(); + } } // + derivative level (if deriv_level > 0) - std::string label; + std::string eval_label; { - label = cparams->api_prefix(); + eval_label = cparams->api_prefix(); if (deriv_level != 0) { std::ostringstream oss; oss << "deriv" << deriv_level; - label += oss.str(); + eval_label += oss.str(); } - label += abcd_label; + eval_label += abcd_label; } - std::cout << "working on " << label << " ... "; + std::cout << "working on " << eval_label << " ... "; std::cout.flush(); std::string prefix(cparams->source_directory()); @@ -1138,7 +1251,8 @@ void build_TwoPRep_2b_2k(std::ostream& os, // this will generate code for these targets, and potentially generate // code for its prerequisites GenerateCode(dg_xxxx, context, cparams, strat, tactic, memman, - decl_filenames, def_filenames, prefix, label, false); + decl_filenames, def_filenames, prefix, eval_label, + false); // update max stack size and # of targets const std::shared_ptr& tparams = @@ -1153,7 +1267,7 @@ void build_TwoPRep_2b_2k(std::ostream& os, oss << context->label_to_name(cparams->api_prefix()) << "libint2_build_" << task << "[" << la << "][" << lb << "][" << lc << "][" << ld - << "] = " << context->label_to_name(label_to_funcname(label)) + << "] = " << context->label_to_name(label_to_funcname(eval_label)) << context->end_of_stat() << endl; iface->to_static_init(oss.str()); @@ -1369,10 +1483,9 @@ void build_TwoPRep_1b_2k(std::ostream& os, iface->to_static_init(oss.str()); // need to declare this function internally - for (std::deque::const_iterator i = decl_filenames.begin(); - i != decl_filenames.end(); ++i) { + for (auto& decl_filename : decl_filenames) { oss.str(""); - oss << "#include <" << *i << ">" << endl; + oss << "#include <" << decl_filename << ">" << endl; iface->to_int_iface(oss.str()); } @@ -1381,7 +1494,7 @@ void build_TwoPRep_1b_2k(std::ostream& os, #endif dg_xxx->reset(); memman->reset(); - + std::cout << "done" << std::endl; } // end of d loop } // end of c loop } // end of bra loop @@ -1566,10 +1679,9 @@ void build_TwoPRep_1b_1k(std::ostream& os, iface->to_static_init(oss.str()); // need to declare this function internally - for (std::deque::const_iterator i = decl_filenames.begin(); - i != decl_filenames.end(); ++i) { + for (auto& decl_filename : decl_filenames) { oss.str(""); - oss << "#include <" << *i << ">" << endl; + oss << "#include <" << decl_filename << ">" << endl; iface->to_int_iface(oss.str()); } @@ -1578,7 +1690,7 @@ void build_TwoPRep_1b_1k(std::ostream& os, #endif dg_xxx->reset(); memman->reset(); - + std::cout << "done" << std::endl; } // end of ket loop } // end of bra loop } diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" new file mode 100644 index 000000000..252eabf5d --- /dev/null +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2004-2024 Edward F. Valeev + * + * This file is part of Libint compiler. + * + * Libint compiler 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 compiler 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 compiler. If not, see . + * + */ + +#ifndef LIBINT_COMP_11_COULOMBΣPΣP_11_H +#define LIBINT_COMP_11_COULOMBΣPΣP_11_H + +#include +#include +#include + +namespace libint2 { + +/** + * this computes integral of + * \f$ \frac{1}{r_{ij}} \sigma \cdot \hat{p}_1 \sigma \cdot \hat{p}_2 \f$ over + * CGShell/CGF by rewriting it as a linear combination of integrals over + * derivatives of \frac{1}{r_{ij}} + * @tparam F basis function type. valid choices are CGShell or CGF + */ +template +class CR_11_Coulombσpσp_11 + : public GenericRecurrenceRelation< + CR_11_Coulombσpσp_11, F, + GenIntegralSet_11_11> { + public: + typedef CR_11_Coulombσpσp_11 ThisType; + typedef F BasisFunctionType; + typedef CoulombσpσpOper OperType; + typedef GenIntegralSet_11_11 TargetType; + typedef GenericRecurrenceRelation + ParentType; + friend class GenericRecurrenceRelation; + static const unsigned int max_nchildren = 100; // TODO figure out + + using ParentType::Instance; + + static bool directional() { return false; } + + private: + using ParentType::is_simple; + using ParentType::target_; + using ParentType::RecurrenceRelation::expr_; + using ParentType::RecurrenceRelation::nflops_; + + /// Constructor is private, used by ParentType::Instance that maintains + /// registry of these objects + CR_11_Coulombσpσp_11(const std::shared_ptr &, unsigned int = 0); + + static std::string descr() { return "CR"; } +}; + +template +CR_11_Coulombσpσp_11::CR_11_Coulombσpσp_11( + const std::shared_ptr &Tint, unsigned int) + : ParentType(Tint, 0) { + assert(Tint->num_func_bra(/* particle */ 0) == 1); + assert(Tint->num_func_bra(/* particle */ 1) == 1); + assert(Tint->num_func_ket(/* particle */ 0) == 1); + assert(Tint->num_func_ket(/* particle */ 1) == 1); + + F a(Tint->bra(0, 0)); + F b(Tint->ket(0, 0)); + F c(Tint->bra(1, 0)); + F d(Tint->ket(1, 0)); + + const auto &oper = Tint->oper(); + + // can express integrals of Coulombσpσp in terms of + // derivative integrals of 1/r12 for primitive Gaussians + // only + if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) + return; + + using namespace libint2::algebra; + using namespace libint2::prefactor; + using libint2::algebra::operator*; + + const mType zero_m(0u); + + ChildFactory> + factory(this); + + constexpr auto x = 0; + constexpr auto y = 1; + constexpr auto z = 2; + + F c_x{c}; + c_x.deriv().inc(x); // d(c)/dx = c_x + F c_y{c}; + c_y.deriv().inc(y); // d(c)/dy = c_y + F c_z{c}; + c_z.deriv().inc(z); // d(c)/dz = c_z + + F d_x{d}; + d_x.deriv().inc(x); // d(d)/dx = d_x + F d_y{d}; + d_y.deriv().inc(y); // d(d)/dy = d_y + F d_z{d}; + d_z.deriv().inc(z); // d(d)/dz = d_z + + // Component wise generation for quaternion ( a b | 1/r12 | (σ.p) c (σ.p) d ) + switch (oper->descr().quaternion_index()) { + case 0: { + // zeroth component = (a b | c_x d_x) + (a b | c_y d_y) + (a b | c_z d_z) + auto a_b_cx_dx = factory.make_child(a, b, c_x, d_x, zero_m); + auto a_b_cy_dy = factory.make_child(a, b, c_y, d_y, zero_m); + auto a_b_cz_dz = factory.make_child(a, b, c_z, d_z, zero_m); + if (is_simple()) { + expr_ = a_b_cx_dx + a_b_cy_dy + a_b_cz_dz; + nflops_ += 2; + } + } break; + case 1: { + // x component = (a b | c_y d_z) - (a b | c_z d_y) + auto a_b_cy_dz = factory.make_child(a, b, c_y, d_z, zero_m); + auto a_b_cz_dy = factory.make_child(a, b, c_z, d_y, zero_m); + if (is_simple()) { + expr_ = a_b_cy_dz - a_b_cz_dy; + nflops_ += 1; + } + } break; + case 2: { + // y component = (a b | c_z d_x) - (a b | c_x d_z) + auto a_b_cz_dx = factory.make_child(a, b, c_z, d_x, zero_m); + auto a_b_cx_dz = factory.make_child(a, b, c_x, d_z, zero_m); + if (is_simple()) { + expr_ = a_b_cz_dx - a_b_cx_dz; + nflops_ += 1; + } + } break; + case 3: { + // z component = (a b | c_x d_y) - (a b | c_y d_x) + auto a_b_cx_dy = factory.make_child(a, b, c_x, d_y, zero_m); + auto a_b_cy_dx = factory.make_child(a, b, c_y, d_x, zero_m); + if (is_simple()) { + expr_ = a_b_cx_dy - a_b_cy_dx; + nflops_ += 1; + } + } break; + default: + throw std::runtime_error( + "CR_11_Coulombσpσp_11: invalid quaternionic index"); + } + +} // CR_11_Coulombσpσp_11::CR_11_Coulombσpσp_11 +}; // namespace libint2 + +#endif // LIBINT_COMP_11_COULOMBΣPΣP_11_H diff --git "a/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" "b/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" index cb131ebb8..9fbdef361 100644 --- "a/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" +++ "b/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" @@ -107,7 +107,7 @@ CR_1_σpVσp_1::CR_1_σpVσp_1(const std::shared_ptr &Tint, // (a|W0|b) = (d a/dAx | V | d b/dBx) + (d a/dAy | V | d b/dBy) + (d a/dAz | V // | d b/dBz) - switch (oper->descr().pauli_index()) { + switch (oper->descr().quaternion_index()) { case 0: { auto Dx_a_V_Dx_b = factory.make_child(Dx_a, Dx_b, zero_m); auto Dy_a_V_Dy_b = factory.make_child(Dy_a, Dy_b, zero_m); @@ -146,7 +146,7 @@ CR_1_σpVσp_1::CR_1_σpVσp_1(const std::shared_ptr &Tint, } } break; default: - throw std::runtime_error("CR_1_σpVσp_1: invalid Pauli index"); + throw std::runtime_error("CR_1_σpVσp_1: invalid quaternionic index"); } } // CR_1_σpVσp_1::CR_1_σpVσp_1 diff --git a/src/bin/libint/master_ints_list.h b/src/bin/libint/master_ints_list.h index 1aa8c3e64..c5a6f4655 100644 --- a/src/bin/libint/master_ints_list.h +++ b/src/bin/libint/master_ints_list.h @@ -106,6 +106,9 @@ typedef GenIntegralSet_1_1, CartesianMultipoleOper<1u>, ////////////////////////// typedef GenIntegralSet_11_11 TwoPRep_11_11_sq; typedef GenIntegralSet_11_11 TwoPRep_11_11_int; +typedef GenIntegralSet_11_11 + Coulombσpσp_11_11_sq; +typedef GenIntegralSet_11_11 Coulombσpσp_11_11_int; typedef GenIntegralSet_11_11 R12kG12_11_11_sq; typedef GenIntegralSet_11_11 R12kG12_11_11_int; typedef GenIntegralSet_11_11 @@ -144,7 +147,8 @@ typedef boost::mpl::list< CMultipole_1_1_int_y, CMultipole_1_1_int_z, SMultipole_1_1_sh, SMultipole_1_1_int, #endif - TwoPRep_11_11_sq, TwoPRep_11_11_int, R12kG12_11_11_sq, R12kG12_11_11_int, + TwoPRep_11_11_sq, TwoPRep_11_11_int, Coulombσpσp_11_11_sq, + Coulombσpσp_11_11_int, R12kG12_11_11_sq, R12kG12_11_11_int, R12kR12lG12_11_11_sq, R12kR12lG12_11_11_int, TiG12_11_11_sq, TiG12_11_11_int, G12TiG12_11_11_sq, G12TiG12_11_11_int, DivG12prime_xTx_11_11_sq, DivG12prime_xTx_11_11_int, diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index f3ec4e2d2..32e56878f 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -21,6 +21,7 @@ #ifndef _libint2_src_bin_libint_masterrrslist_h_ #define _libint2_src_bin_libint_masterrrslist_h_ +#include #include #include #include @@ -266,6 +267,8 @@ typedef CR_DerivGauss Deriv_d_11_TwoPRep_11_int; +typedef CR_11_Coulombσpσp_11 CR_11_Coulombσpσp_11_sh; +typedef CR_11_Coulombσpσp_11 CR_11_Coulombσpσp_11_int; }; // namespace libint2 #endif // header guard diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index 091df3ac0..daa6aa644 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -289,22 +289,22 @@ BOOST_PP_LIST_FOR_EACH(BOOST_PP_DECLARE_HERMITIAN_ONEBODY_DESCRIPTOR, struct σpVσp_Descr : public Contractable<σpVσp_Descr> { typedef MultiplicativeODep1Body_Props Properties; - σpVσp_Descr() : pauli_index_(0) {} - σpVσp_Descr(int pauli_index) : pauli_index_(pauli_index) { - assert(pauli_index <= 3); + σpVσp_Descr() : quaternion_index_(0) {} + σpVσp_Descr(int quaternion_index) : quaternion_index_(quaternion_index) { + assert(quaternion_index <= 3); } static const unsigned int max_key = 4; - unsigned int key() const { return pauli_index(); } + unsigned int key() const { return quaternion_index(); } std::string description() const { std::string descr("opVop["); - if (pauli_index() == 0) + if (quaternion_index() == 0) descr += "0"; - else if (pauli_index() == 1) + else if (quaternion_index() == 1) descr += "Z"; - else if (pauli_index() == 2) + else if (quaternion_index() == 2) descr += "X"; - else if (pauli_index() == 3) + else if (quaternion_index() == 3) descr += "Y"; else abort(); @@ -314,10 +314,10 @@ struct σpVσp_Descr : public Contractable<σpVσp_Descr> { int psymm(int i, int j) const { abort(); } int hermitian(int i) const { return +1; } - int pauli_index() const { return pauli_index_; } + int quaternion_index() const { return quaternion_index_; } private: - const int pauli_index_ = -1; + const int quaternion_index_ = -1; }; typedef GenOper<σpVσp_Descr> σpVσpOper; @@ -399,6 +399,44 @@ struct TwoPRep_Descr : public Contractable { }; typedef GenOper TwoPRep; +/** Coulombσpσp is the two-body repulsion operator. + */ +struct Coulombσpσp_Descr : public Contractable { + typedef MultiplicativeSymm2Body_Props Properties; + + Coulombσpσp_Descr() : quaternion_index_(0) {} + Coulombσpσp_Descr(int quaternion_index) + : quaternion_index_(quaternion_index) { + assert(quaternion_index <= 3); + } + + static const unsigned int max_key = 4; + unsigned int key() const { return quaternion_index(); } + std::string description() const { + std::string descr("coulomb_opop["); + if (quaternion_index() == 0) + descr += "0"; + else if (quaternion_index() == 1) + descr += "Z"; + else if (quaternion_index() == 2) + descr += "X"; + else if (quaternion_index() == 3) + descr += "Y"; + else + abort(); + return descr + "]"; + } + std::string label() const { return description(); } + int psymm(int i, int j) const { abort(); } + int hermitian(int i) const { return +1; } + + int quaternion_index() const { return quaternion_index_; } + + private: + const int quaternion_index_ = -1; +}; +typedef GenOper CoulombσpσpOper; + /** GTG_1d is the two-body 1-dimensional Gaussian geminal */ struct GTG_1d_Descr : public Contractable { diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index bbfc3fe21..bcab9dcad 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -115,6 +115,15 @@ struct MasterStrategy { }; #endif +template <> +struct MasterStrategy { + typedef boost::mpl::list value; +}; +template <> +struct MasterStrategy { + typedef boost::mpl::list value; +}; + #if LIBINT_SHELLQUARTET_STRATEGY == LIBINT_SHELLQUARTET_STRATEGY_A0C0 template <> struct MasterStrategy { From 05e9ff5c31bc4f3670f6b165f4851ef9169a2112 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Mon, 9 Feb 2026 11:24:33 -0500 Subject: [PATCH 03/64] =?UTF-8?q?bugfix:=20added=20the=20missing=20`make?= =?UTF-8?q?=5Fdescr`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmake/modules/int_am.cmake | 2 + include/libint2/cxxapi.h | 2 +- include/libint2/engine.h | 12 ++++++ include/libint2/engine.impl.h | 74 +++++++++++++++++++--------------- src/bin/libint/build_libint.cc | 6 +++ 5 files changed, 62 insertions(+), 34 deletions(-) diff --git a/cmake/modules/int_am.cmake b/cmake/modules/int_am.cmake index 782048aaf..cc86b7aa7 100644 --- a/cmake/modules/int_am.cmake +++ b/cmake/modules/int_am.cmake @@ -434,6 +434,8 @@ foreach(_cls ONEBODY;ERI;RKB_ERI;ERI3;ERI2;G12;G12DKH) list(APPEND _amlist "onebody_${_am${_l}}${_am${_l}}_d${_d}") elseif (_cls STREQUAL "G12") list(APPEND _amlist "g12_${_am${_l}}${_am${_l}}${_am${_l}}${_am${_l}}_d${_d}") + elseif (_cls STREQUAL "RKB_ERI") + list(APPEND _amlist "rkb_eri_${_am${_l}}${_am${_l}}${_am${_l}}${_am${_l}}_d${_d}") endif() endforeach() if (_cls STREQUAL "ERI3") diff --git a/include/libint2/cxxapi.h b/include/libint2/cxxapi.h index a54802817..22686f958 100644 --- a/include/libint2/cxxapi.h +++ b/include/libint2/cxxapi.h @@ -37,7 +37,7 @@ !(defined(LIBINT_INCLUDE_ERI) || defined(LIBINT_INCLUDE_ERI3) || \ defined(LIBINT_INCLUDE_ERI2) || defined(LIBINT_INCLUDE_RKB_ERI)) #error \ - "C++ API is only supported if both 1-body and some (eri, eri3, eri2) 2-body integrals are enabled" + "C++ API is only supported if both 1-body and some (eri, eri3, eri2, rkb_eri) 2-body integrals are enabled" #endif #include diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 7e29eb710..81048b049 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -153,6 +153,9 @@ enum class Operator { coulomb, /// alias for Operator::coulomb r12_m1 = coulomb, + /// (2-body) \f$ r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ where k1 & k2 are + /// centers of ket1 and ket2, respectively + coulomb_opop, /// contracted Gaussian geminal cgtg, /// contracted Gaussian geminal times Coulomb @@ -246,6 +249,7 @@ struct operator_traits typedef const libint2::FmEval_Reference core_eval_type; #endif }; + template <> struct operator_traits : public operator_traits { @@ -346,6 +350,14 @@ struct operator_traits typedef const libint2::FmEval_Reference core_eval_type; #endif }; + +template <> +struct operator_traits + : public operator_traits { + static constexpr auto nopers = 4; + static constexpr auto intrinsic_deriv_order = 2; +}; + namespace detail { template struct cgtg_operator_traits : public detail::default_operator_traits { diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 05d5808ea..8e107449b 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -70,30 +70,31 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { /// These MUST appear in the same order as in Operator. /// You must also update BOOST_PP_NBODY_OPERATOR_LAST_ONEBODY_INDEX when you add /// one-body ints -#define BOOST_PP_NBODY_OPERATOR_LIST \ - (overlap, /* overlap */ \ - (kinetic, /* kinetic */ \ - (elecpot, /* nuclear */ \ - (elecpot, /* erf_nuclear */ \ - (elecpot, /* erfc_nuclear */ \ - (elecpot, /* erfx_nuclear */ \ - (1emultipole, /* emultipole1 */ \ - (2emultipole, /* emultipole2 */ \ - (3emultipole, /* emultipole3 */ \ - (sphemultipole, /* sphemultipole */ \ - (opVop, /* opVop */ \ - (eri, /* delta */ \ - (eri, /* coulomb */ \ - (eri, /* cgtg */ \ - (eri, /* cgtg_x_coulomb */ \ - (eri, /* delcgtg2 */ \ - (eri, /* r12 */ \ - (eri, /* erf_coulomb */ \ - (eri, /* erfc_coulomb */ \ - (eri, /* erfx_coulomb */ \ - (eri, /* stg */ \ - (eri, /* yukawa */ \ - BOOST_PP_NIL)))))))))))))))))))))) +#define BOOST_PP_NBODY_OPERATOR_LIST \ + (overlap, /* overlap */ \ + (kinetic, /* kinetic */ \ + (elecpot, /* nuclear */ \ + (elecpot, /* erf_nuclear */ \ + (elecpot, /* erfc_nuclear */ \ + (elecpot, /* erfx_nuclear */ \ + (1emultipole, /* emultipole1 */ \ + (2emultipole, /* emultipole2 */ \ + (3emultipole, /* emultipole3 */ \ + (sphemultipole, /* sphemultipole */ \ + (opVop, /* opVop */ \ + (eri, /* delta */ \ + (eri, /* coulomb */ \ + (eri, /* coulomb_opop */ \ + (eri, /* cgtg */ \ + (eri, /* cgtg_x_coulomb */ \ + (eri, /* delcgtg2 */ \ + (eri, /* r12 */ \ + (eri, /* erf_coulomb */ \ + (eri, /* erfc_coulomb */ \ + (eri, /* erfx_coulomb */ \ + (eri, /* stg */ \ + (eri, /* yukawa */ \ + BOOST_PP_NIL))))))))))))))))))))))) #define BOOST_PP_NBODY_OPERATOR_INDEX_TUPLE \ BOOST_PP_MAKE_TUPLE(BOOST_PP_LIST_SIZE(BOOST_PP_NBODY_OPERATOR_LIST)) @@ -663,23 +664,23 @@ __libint2_engine_inline void Engine::initialize(size_t max_nprim) { // validate braket #ifndef LIBINT_INCLUDE_ONEBODY assert(braket_ != BraKet::x_x && - "this braket type not supported by the library; give --enable-1body " - "to configure"); + "this braket type not supported by the library; configure with " + "-DLIBINT_INCLUDE_ONEBODY >= 0"); #endif #ifndef LIBINT_INCLUDE_ERI assert(braket_ != BraKet::xx_xx && - "this braket type not supported by the library; give --enable-eri to " - "configure"); + "this braket type not supported by the library; configure with " + "-DLIBINT_INCLUDE_ERI >= 0"); #endif #ifndef LIBINT_INCLUDE_ERI3 assert((braket_ != BraKet::xs_xx && braket_ != BraKet::xx_xs) && - "this braket type not supported by the library; give --enable-eri3 to " - "configure"); + "this braket type not supported by the library; configure with " + "-DLIBINT_INCLUDE_ERI3 >= 0"); #endif #ifndef LIBINT_INCLUDE_ERI2 assert(braket_ != BraKet::xs_xs && - "this braket type not supported by the library; give --enable-eri2 to " - "configure"); + "this braket type not supported by the library; configure with " + "-DLIBINT_INCLUDE_ERI2 >= 0"); #endif // make sure it's no default initialized @@ -1421,7 +1422,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const scalar_type rho = gammap * gammaq * oogammapq; const scalar_type T = PQ2 * rho; auto* gm_ptr = &(primdata.LIBINT_T_SS_EREP_SS(0)[0]); - const auto mmax = l + deriv_order_; + const auto mmax = l + deriv_order_ + intrinsic_deriv_order(); if (!skip_core_ints) { switch (oper_) { @@ -1432,6 +1433,13 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( .first(); core_eval_ptr->eval(gm_ptr, T, mmax); } break; + case Operator::coulomb_opop: { + const auto& core_eval_ptr = + any_cast&>(core_eval_pack_) + .first(); + core_eval_ptr->eval(gm_ptr, T, mmax); + } break; case Operator::cgtg_x_coulomb: { const auto& core_eval_ptr = any_cast σpVσp_Descr make_descr<σpVσp_Descr>(int p, int, int) { return σpVσp_Descr(p); } + +template <> +Coulombσpσp_Descr make_descr(int p, int, int) { + return Coulombσpσp_Descr(p); +} + } // namespace template From f3274a99e927bfaa40837831d64056e3762c2e55 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 10 Feb 2026 12:03:00 -0500 Subject: [PATCH 04/64] `Engine` can initialize for coulomb_opop operator --- include/libint2/engine.impl.h | 4 ++-- src/bin/libint/build_libint.cc | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 8e107449b..40d50763b 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -84,7 +84,7 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { (opVop, /* opVop */ \ (eri, /* delta */ \ (eri, /* coulomb */ \ - (eri, /* coulomb_opop */ \ + (coulomb_opop, /* coulomb_opop */ \ (eri, /* cgtg */ \ (eri, /* cgtg_x_coulomb */ \ (eri, /* delcgtg2 */ \ @@ -1462,7 +1462,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const detail::core_eval_pack_type&>( core_eval_pack_) .first(); - const auto& core_ints_params = + ` const auto& core_ints_params = any_cast::oper_params_type&>( core_ints_params_); diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 1a3a93b95..8acb3814d 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -1071,6 +1071,9 @@ void print_config(std::ostream& os) { #ifdef LIBINT_INCLUDE_RKB_ERI os << "Will support restricted kinetically balance (RKB) 4-center ERIs " << std::endl; + if (LIBINT_INCLUDE_RKB_ERI > 0) + os << "(deriv order = " << LIBINT_INCLUDE_RKB_ERI << ")"; + os << endl; #endif } @@ -2262,6 +2265,12 @@ void config_to_api(const std::shared_ptr& cparams, iface->to_params(iface->macro_define("DERIV_ERI_ORDER", LIBINT_INCLUDE_ERI)); max_deriv_order = std::max(max_deriv_order, LIBINT_INCLUDE_ERI); #endif +#ifdef LIBINT_INCLUDE_RKB_ERI + iface->to_params(iface->macro_define("SUPPORT_RKB_ERI", 1)); + iface->to_params( + iface->macro_define("DERIV_RKB_ERI_ORDER", LIBINT_INCLUDE_RKB_ERI)); + max_deriv_order = std::max(max_deriv_order, LIBINT_INCLUDE_RKB_ERI); +#endif #ifdef LIBINT_INCLUDE_ERI3 iface->to_params(iface->macro_define("SUPPORT_ERI3", 1)); iface->to_params( @@ -2317,8 +2326,9 @@ void config_to_api(const std::shared_ptr& cparams, { // 2-body ints -#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ - ("eri", "r12kg12", "r12_0_g12", "r12_2_g12", "g12_T1_g12", "g12dkh") +#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ + ("eri", "coulomb_opop", "r12kg12", "r12_0_g12", "r12_2_g12", "g12_T1_g12", \ + "g12dkh") #define BOOST_PP_TWOBODY_TASKOPER_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_TWOBODY_TASKOPER_TUPLE) From 2bf62f641d175110b367c7ed0217cfc748b96fbb Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 10 Feb 2026 21:20:58 -0500 Subject: [PATCH 05/64] use and invoke correct `buildfnptr`s if engine is initialized with`Operator::coulomb_opop` --- include/libint2/engine.impl.h | 7 ++++--- "src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 40d50763b..3f5669d96 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -1462,7 +1462,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const detail::core_eval_pack_type&>( core_eval_pack_) .first(); - ` const auto& core_ints_params = + const auto& core_ints_params = any_cast::oper_params_type&>( core_ints_params_); @@ -1754,7 +1754,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( #endif // prefactors for derivative ERI relations - if (deriv_order_ > 0) { + if (deriv_order_ + intrinsic_deriv_order() > 0) { #if LIBINT2_DEFINED(eri, alpha1_rho_over_zeta2) primdata.alpha1_rho_over_zeta2[0] = alpha0 * (oogammap * gammaq_o_gammapgammaq); @@ -1837,7 +1837,8 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( } // compute directly (ss|ss) - const auto compute_directly = lmax == 0 && deriv_order_ == 0; + const auto compute_directly = + lmax == 0 && deriv_order_ == 0 & intrinsic_deriv_order() == 0; if (compute_directly) { #ifdef LIBINT2_ENGINE_TIMERS diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index 252eabf5d..82ee512ef 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004-2024 Edward F. Valeev + * Copyright (C) 2004-2026 Edward F. Valeev * * This file is part of Libint compiler. * From b6c376be3812b1195b323c3c895c9230f40be1eb Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Wed, 11 Feb 2026 13:18:47 -0500 Subject: [PATCH 06/64] Apply @loriab 's review suggestions --- include/libint2/config.h.cmake.in | 4 ++-- include/libint2/engine.impl.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/libint2/config.h.cmake.in b/include/libint2/config.h.cmake.in index 6018873e5..3eb32e2f0 100644 --- a/include/libint2/config.h.cmake.in +++ b/include/libint2/config.h.cmake.in @@ -129,13 +129,13 @@ /* Max optimized AM for ERI and its derivatives */ #cmakedefine LIBINT_ERI_OPT_AM_LIST "@LIBINT_ERI_OPT_AM_LIST@" -/* Max AM for RKB_ERI (same for all derivatives; if not defined see LIBINT_ERI_MAX_AM_LIST) */ +/* Max AM for RKB_ERI (same for all derivatives; if not defined see LIBINT_RKB_ERI_MAX_AM_LIST) */ #cmakedefine LIBINT_RKB_ERI_MAX_AM @LIBINT_RKB_ERI_MAX_AM@ /* Max AM for RKB_ERI and its derivatives */ #cmakedefine LIBINT_RKB_ERI_MAX_AM_LIST "@LIBINT_RKB_ERI_MAX_AM_LIST@" -/* Max optimized AM for ERI (same for all derivatives; if not defined see LIBINT_ERI_OPT_AM_LIST) */ +/* Max optimized AM for ERI (same for all derivatives; if not defined see LIBINT_RKB_ERI_OPT_AM_LIST) */ #cmakedefine LIBINT_RKB_ERI_OPT_AM @LIBINT_RKB_ERI_OPT_AM@ /* Max optimized AM for ERI and its derivatives */ diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 3f5669d96..9381b972a 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -665,22 +665,22 @@ __libint2_engine_inline void Engine::initialize(size_t max_nprim) { #ifndef LIBINT_INCLUDE_ONEBODY assert(braket_ != BraKet::x_x && "this braket type not supported by the library; configure with " - "-DLIBINT_INCLUDE_ONEBODY >= 0"); + "-DLIBINT2_ENABLE_ONEBODY >= 0"); #endif #ifndef LIBINT_INCLUDE_ERI assert(braket_ != BraKet::xx_xx && "this braket type not supported by the library; configure with " - "-DLIBINT_INCLUDE_ERI >= 0"); + "-DLIBINT2_ENABLE_ERI >= 0"); #endif #ifndef LIBINT_INCLUDE_ERI3 assert((braket_ != BraKet::xs_xx && braket_ != BraKet::xx_xs) && "this braket type not supported by the library; configure with " - "-DLIBINT_INCLUDE_ERI3 >= 0"); + "-DLIBINT2_ENABLE_ERI3 >= 0"); #endif #ifndef LIBINT_INCLUDE_ERI2 assert(braket_ != BraKet::xs_xs && "this braket type not supported by the library; configure with " - "-DLIBINT_INCLUDE_ERI2 >= 0"); + "-DLIBINT2_ENABLE_ERI2 >= 0"); #endif // make sure it's no default initialized From 0aa7b83f6116d8140563eace3d8119fa777dc5f5 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 13 Feb 2026 19:16:53 -0500 Subject: [PATCH 07/64] Added unittest for `Operator::coulomb_opop` and fixed logic errors in unique am shell sets and phase change for this operator --- export/tests/unit/test-2body.cc | 167 ++++++++++++++++++++++++++++++-- include/libint2/engine.impl.h | 38 +++++--- src/bin/libint/build_libint.cc | 15 ++- src/bin/libint/oper.h | 12 +-- 4 files changed, 198 insertions(+), 34 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index fd602a910..ee08ed972 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004-2024 Edward F. Valeev + * Copyright (C) 2004-2026 Edward F. Valeev * * This file is part of Libint library. * @@ -344,6 +344,153 @@ TEST_CASE("eri geometric derivatives", "[engine][2-body]") { } } +TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { + std::vector obs{ + // pseudorandom s + Shell{{1.0, 0.3}, {{0, false, {0.9, 0.3}}}, {{0.0, 0.0, 0.0}}}, + // pseudorandom p + Shell{{2.0, 0.4}, {{1, false, {0.8, -0.2}}}, {{1.0, 1.0, 1.0}}}}; + + const auto max_nprim = libint2::max_nprim(obs); + const auto max_l = libint2::max_l(obs); + typedef std::array der_idx; + + SECTION("Coulombσpσp") { + Engine engine; + try { + engine = Engine(Operator::coulomb_opop, max_nprim, max_l, 0); + // TODO: need another unit test for derivatives of RKB ERIs + } catch ( + Engine::lmax_exceeded &) { // skip the test if lmax exceeded or libint2 + // not configured with RKB support + return; + } + + const auto nshell = obs.size(); + for (int s0 = 0; s0 != nshell; ++s0) { + for (int s1 = 0; s1 != nshell; ++s1) { + for (int s2 = 0; s2 != nshell; ++s2) { + for (int s3 = 0; s3 != nshell; ++s3) { + const auto &results = + engine.compute(obs[s0], obs[s1], obs[s2], obs[s3]); + assert(results.size() == + 4); // we get 4 buffers for each quaternion component + + LIBINT2_REF_REALTYPE Aref[3]; + for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; + LIBINT2_REF_REALTYPE Bref[3]; + for (int i = 0; i < 3; ++i) Bref[i] = obs[s1].O[i]; + LIBINT2_REF_REALTYPE Cref[3]; + for (int i = 0; i < 3; ++i) Cref[i] = obs[s2].O[i]; + LIBINT2_REF_REALTYPE Dref[3]; + for (int i = 0; i < 3; ++i) Dref[i] = obs[s3].O[i]; + + int ijkl = 0; + + int l0, m0, n0; + FOR_CART(l0, m0, n0, obs[s0].contr[0].l) + + int l1, m1, n1; + FOR_CART(l1, m1, n1, obs[s1].contr[0].l) + + int l2, m2, n2; + FOR_CART(l2, m2, n2, obs[s2].contr[0].l) + + int l3, m3, n3; + FOR_CART(l3, m3, n3, obs[s3].contr[0].l) + + std::array ref_coulomb_opop{0.0, 0.0, 0.0, + 0.0}; + uint p0123 = 0; + for (uint p0 = 0; p0 < obs[s0].nprim(); p0++) { + for (uint p1 = 0; p1 < obs[s1].nprim(); p1++) { + for (uint p2 = 0; p2 < obs[s2].nprim(); p2++) { + for (uint p3 = 0; p3 < obs[s3].nprim(); p3++, p0123++) { + const LIBINT2_REF_REALTYPE alpha0 = obs[s0].alpha[p0]; + const LIBINT2_REF_REALTYPE alpha1 = obs[s1].alpha[p1]; + const LIBINT2_REF_REALTYPE alpha2 = obs[s2].alpha[p2]; + const LIBINT2_REF_REALTYPE alpha3 = obs[s3].alpha[p3]; + + const LIBINT2_REF_REALTYPE c0 = obs[s0].contr[0].coeff[p0]; + const LIBINT2_REF_REALTYPE c1 = obs[s1].contr[0].coeff[p1]; + const LIBINT2_REF_REALTYPE c2 = obs[s2].contr[0].coeff[p2]; + const LIBINT2_REF_REALTYPE c3 = obs[s3].contr[0].coeff[p3]; + const LIBINT2_REF_REALTYPE c0123 = c0 * c1 * c2 * c3; + + auto eri_drr = [&](der_idx d_rr) { + return eri(d_rr.data(), l0, m0, n0, alpha0, Aref, l1, m1, + n1, alpha1, Bref, l2, m2, n2, alpha2, Cref, l3, + m3, n3, alpha3, Dref, 0); + }; + + // e.g. d_xx maps the derivative index of derivative w.r.t x + // coord of ket1 and x coord of ket2 in Chemist notation. + der_idx d_xx = {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0}; + der_idx d_yy = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}; + der_idx d_zz = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1}; + ref_coulomb_opop[0] += + c0123 * (eri_drr(d_xx) + eri_drr(d_yy) + eri_drr(d_zz)); + + der_idx d_yz = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1}; + der_idx d_zy = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0}; + ref_coulomb_opop[1] += + c0123 * (eri_drr(d_yz) - eri_drr(d_zy)); + + der_idx d_zx = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0}; + der_idx d_xz = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; + ref_coulomb_opop[2] += + c0123 * (eri_drr(d_zx) - eri_drr(d_xz)); + + der_idx d_xy = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0}; + der_idx d_yx = {0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0}; + ref_coulomb_opop[3] += + c0123 * (eri_drr(d_xy) - eri_drr(d_yx)); + } + } + } + } + + const double ABSOLUTE_DEVIATION_THRESHOLD = 5.0E-14; + const double RELATIVE_DEVIATION_THRESHOLD = + 1.0E-9; // For more detail on choice of these thresholds, see + // the comments in the TEST_CASE "eri geometric + // derivatives" + + std::array abs_errs; + std::array rel_abs_errs; + + for (auto comp = 0; comp < 4; ++comp) { + abs_errs[comp] = + abs(ref_coulomb_opop[comp] - results[comp][ijkl]); + rel_abs_errs[comp] = abs(abs_errs[comp] / ref_coulomb_opop[comp]); + bool not_ok = rel_abs_errs[comp] > RELATIVE_DEVIATION_THRESHOLD && + abs_errs[comp] > ABSOLUTE_DEVIATION_THRESHOLD; + // no 3^n prefactor here since the intrinsic deriv order is 2 + if (not_ok) { + std::cout << "(l0 l1| l2 l3) = " + << "(" << s0 << " " << s1 << " | " << s2 << " " << s3 + << ") " + << "Elem " << ijkl << " comp= " << comp + << " : ref = " << ref_coulomb_opop[comp] + << " libint = " << results[comp][ijkl] + << " relabs_error = " << rel_abs_errs[comp] + << " abs_error = " << abs_errs[comp] << std::endl; + } + REQUIRE(!not_ok); + } + + ++ijkl; + END_FOR_CART + END_FOR_CART + END_FOR_CART + END_FOR_CART + } + } + } + } + } +} + TEST_CASE("Erfx_Coulomb integrals", "[engine][2-body]") { // pseudorandom s shells std::vector obs{ @@ -374,12 +521,12 @@ TEST_CASE("Erfx_Coulomb integrals", "[engine][2-body]") { REQUIRE(results[0] != nullptr); switch (k) { /* VALIDATION WOLFRAM CODE: -(* Integral of Coulomb kernel damped by (\[Lambda] Erf[\[Omega] r] + \ -\[Sigma] Erfc[\[Omega] r]), over unit-normalized s functions, \ -see Eq 52 in DOI 10.1039/b605188j *) -F0[T_] := If[T == 0, 1, Sqrt[\[Pi]/T]*Erf[Sqrt[T]]/2]; -sN[a_] := ((2 a)/\[Pi])^(3/4); -VVeeErfx[\[Alpha]1_, A1_List, \[Alpha]2_, A2_List, \[Beta]1_, + (* Integral of Coulomb kernel damped by (\[Lambda] Erf[\[Omega] r] + \ + \[Sigma] Erfc[\[Omega] r]), over unit-normalized s functions, \ + see Eq 52 in DOI 10.1039/b605188j *) + F0[T_] := If[T == 0, 1, Sqrt[\[Pi]/T]*Erf[Sqrt[T]]/2]; + sN[a_] := ((2 a)/\[Pi])^(3/4); + VVeeErfx[\[Alpha]1_, A1_List, \[Alpha]2_, A2_List, \[Beta]1_, B1_List, \[Beta]2_, B2_List, \[Omega]_, \[Lambda]_, \[Sigma]_] := Module[{\[Gamma]1, \[Gamma]2, P1, P2, K1, K2, T, result, \[Rho]}, \[Gamma]1 = \[Alpha]1 + \[Beta]1; @@ -397,13 +544,13 @@ VVeeErfx[\[Alpha]1_, A1_List, \[Alpha]2_, A2_List, \[Beta]1_, T]) sN[\[Alpha]1] sN[\[Alpha]2] sN[\[Beta]1] sN[\[Beta]2]; Return[result]; ]; -Print[CForm[ + Print[CForm[ N[VVeeErfx[1, {0, 0, 0}, 3, {2, 2, 2}, 2, {1, 1, 1}, 4, {3, 3, 3}, 1.1, 1, 0], 20]]] -Print[CForm[ + Print[CForm[ N[VVeeErfx[1, {0, 0, 0}, 3, {2, 2, 2}, 2, {1, 1, 1}, 4, {3, 3, 3}, 1.1, 0, 1], 20]]] -Print[CForm[ + Print[CForm[ N[VVeeErfx[1, {0, 0, 0}, 3, {2, 2, 2}, 2, {1, 1, 1}, 4, {3, 3, 3}, 1.1, 2, 3], 20]]] */ diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 9381b972a..ec570c045 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -1215,17 +1215,21 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( LIBINT2_SHELLQUARTET_SET_STANDARD // standard angular momentum ordering const auto swap_tbra = (tbra1.contr[0].l < tbra2.contr[0].l); const auto swap_tket = (tket1.contr[0].l < tket2.contr[0].l); - const auto swap_braket = - ((braket_ == BraKet::xx_xx) && (tbra1.contr[0].l + tbra2.contr[0].l > - tket1.contr[0].l + tket2.contr[0].l)) || - braket_ == BraKet::xx_xs; + const auto swap_braket = ((braket_ == BraKet::xx_xx) && + (tbra1.contr[0].l + tbra2.contr[0].l < + tket1.contr[0].l + tket2.contr[0].l) && + (oper_ != Operator::coulomb_opop)) || + braket_ == BraKet::xx_xs; + // N.B. cannot swap bra and ket for coulomb_opop since the ket is mutated by + // this operator #else // orca angular momentum ordering const auto swap_tbra = (tbra1.contr[0].l > tbra2.contr[0].l); const auto swap_tket = (tket1.contr[0].l > tket2.contr[0].l); - const auto swap_braket = - ((braket_ == BraKet::xx_xx) && (tbra1.contr[0].l + tbra2.contr[0].l < - tket1.contr[0].l + tket2.contr[0].l)) || - braket_ == BraKet::xx_xs; + const auto swap_braket = ((braket_ == BraKet::xx_xx) && + (tbra1.contr[0].l + tbra2.contr[0].l < + tket1.contr[0].l + tket2.contr[0].l) && + (oper_ != Operator::coulomb_opop)) || + braket_ == BraKet::xx_xs; assert(false && "feature not implemented"); abort(); #endif @@ -1659,7 +1663,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const auto Wz = (gammap_o_gammapgammaq * P[2] + gammaq_o_gammapgammaq * Q[2]); - if (deriv_order_ > 0 || lmax_bra > 0) { + if (deriv_order_ + intrinsic_deriv_order() > 0 || lmax_bra > 0) { #if LIBINT2_DEFINED(eri, WP_x) primdata.WP_x[0] = Wx - P[0]; #endif @@ -1670,7 +1674,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( primdata.WP_z[0] = Wz - P[2]; #endif } - if (deriv_order_ > 0 || lmax_ket > 0) { + if (deriv_order_ + intrinsic_deriv_order() > 0 || lmax_ket > 0) { #if LIBINT2_DEFINED(eri, WQ_x) primdata.WQ_x[0] = Wx - Q[0]; #endif @@ -1916,8 +1920,10 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( "the angular momentum limit is exceeded"); assert(ket2.contr[0].l <= ket_lmax && "the angular momentum limit is exceeded"); + buildfnidx = (bra1.contr[0].l * ket_lmax + ket1.contr[0].l) * ket_lmax + ket2.contr[0].l; + #ifdef LIBINT_ERI3_PURE_SH if (bra1.contr[0].l > 1) assert(bra1.contr[0].pure && @@ -2110,9 +2116,15 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const auto tgt_row_idx = !swap_tbra ? r1 * nr2 + r2 : r2 * nr1 + r1; Map tgt_blk_mat(tgt_ptr + tgt_row_idx * ncol, nc1_tgt, nc2_tgt); - if (swap_tket) - tgt_blk_mat = src_blk_mat.transpose(); - else + if (swap_tket) { + Shell::real_t oper_cart_component_phase = 1.0; + if (oper_ == Operator::coulomb_opop && s > 0) + oper_cart_component_phase = + -1.0; // x,y,z quaternion components flip sign on + // swapping ket for coulomb_opop + tgt_blk_mat = + oper_cart_component_phase * src_blk_mat.transpose(); + } else tgt_blk_mat = src_blk_mat; } } // end of loop diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 8acb3814d..dfb48fbd0 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -1118,10 +1118,13 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, for (unsigned int lb = 0; lb <= lmax; lb++) { for (unsigned int lc = 0; lc <= lmax; lc++) { for (unsigned int ld = 0; ld <= lmax; ld++) { - if (!ShellQuartetSetPredicate( - LIBINT_SHELL_SET)>::value(la, lb, lc, ld)) - continue; - + if (std::is_same::value) { + if (!(la >= lb && lc >= ld)) continue; + } else { + if (!ShellQuartetSetPredicate( + LIBINT_SHELL_SET)>::value(la, lb, lc, ld)) + continue; + } // std::shared_ptr tactic(new ParticleDirectionTactic(la+lb > // lc+ld ? false : true)); std::shared_ptr tactic( @@ -1223,7 +1226,7 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, CGShell c(lc); CGShell d(ld); - if constexpr (!std::is_same::value) { + if constexpr (std::is_same::value) { OperType oper; oper = OperType(descrs[0]); std::shared_ptr abcd = @@ -1236,6 +1239,8 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, oss << "_" << label; oss << "_" << c.label() << "_" << d.label(); abcd_label = oss.str(); + std::cout << "(" << a.label() << " " << b.label() << " | " + << c.label() << " " << d.label() << ") "; } } // + derivative level (if deriv_level > 0) diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index daa6aa644..36292613a 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -301,11 +301,11 @@ struct σpVσp_Descr : public Contractable<σpVσp_Descr> { if (quaternion_index() == 0) descr += "0"; else if (quaternion_index() == 1) - descr += "Z"; - else if (quaternion_index() == 2) descr += "X"; - else if (quaternion_index() == 3) + else if (quaternion_index() == 2) descr += "Y"; + else if (quaternion_index() == 3) + descr += "Z"; else abort(); return descr + "]"; @@ -417,11 +417,11 @@ struct Coulombσpσp_Descr : public Contractable { if (quaternion_index() == 0) descr += "0"; else if (quaternion_index() == 1) - descr += "Z"; - else if (quaternion_index() == 2) descr += "X"; - else if (quaternion_index() == 3) + else if (quaternion_index() == 2) descr += "Y"; + else if (quaternion_index() == 3) + descr += "Z"; else abort(); return descr + "]"; From 62142d9671e87c397e7938f6535eff0972e127ee Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sat, 14 Feb 2026 10:54:02 -0500 Subject: [PATCH 08/64] bugfix: revert incorrect braket swapping criteria for `Operator::coulomb` --- include/libint2/engine.impl.h | 2 +- src/bin/libint/build_libint.cc | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index ec570c045..834fd9c6d 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -1216,7 +1216,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const auto swap_tbra = (tbra1.contr[0].l < tbra2.contr[0].l); const auto swap_tket = (tket1.contr[0].l < tket2.contr[0].l); const auto swap_braket = ((braket_ == BraKet::xx_xx) && - (tbra1.contr[0].l + tbra2.contr[0].l < + (tbra1.contr[0].l + tbra2.contr[0].l > tket1.contr[0].l + tket2.contr[0].l) && (oper_ != Operator::coulomb_opop)) || braket_ == BraKet::xx_xs; diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index dfb48fbd0..f70d16644 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -77,8 +77,9 @@ struct ShellQuartetSetPredicate { }; template <> struct ShellQuartetSetPredicate { - static bool value(int la, int lb, int lc, int ld) { - return la >= lb && lc >= ld && la + lb <= lc + ld; + static bool value(int la, int lb, int lc, int ld, + bool p1p2_swappable = true) { + return la >= lb && lc >= ld && (!p1p2_swappable || la + lb <= lc + ld); } }; template <> @@ -1118,7 +1119,7 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, for (unsigned int lb = 0; lb <= lmax; lb++) { for (unsigned int lc = 0; lc <= lmax; lc++) { for (unsigned int ld = 0; ld <= lmax; ld++) { - if (std::is_same::value) { + if constexpr (std::is_same::value) { if (!(la >= lb && lc >= ld)) continue; } else { if (!ShellQuartetSetPredicate( @@ -1239,8 +1240,6 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, oss << "_" << label; oss << "_" << c.label() << "_" << d.label(); abcd_label = oss.str(); - std::cout << "(" << a.label() << " " << b.label() << " | " - << c.label() << " " << d.label() << ") "; } } // + derivative level (if deriv_level > 0) From b2b79ab24c02f9405614e28d4262f86eaacdc0e0 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Wed, 18 Feb 2026 17:19:34 -0500 Subject: [PATCH 09/64] generate code for missing contracted kernels for `deriv(>0)-eri` + cleanup --- src/bin/libint/build_libint.cc | 34 +++++++++---------- .../comp_11_Coulomb\317\203p\317\203p_11.h" | 8 ++--- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index f70d16644..c892dd618 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -73,7 +73,7 @@ enum ShellSetType { template struct ShellQuartetSetPredicate { // return true if this set of angular momenta is included - static bool value(int la, int lb, int lc, int ld); + static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true); }; template <> struct ShellQuartetSetPredicate { @@ -84,8 +84,10 @@ struct ShellQuartetSetPredicate { }; template <> struct ShellQuartetSetPredicate { - static bool value(int la, int lb, int lc, int ld) { - return la <= lb && lc <= ld && (la < lc || (la == lc && lb <= ld)); + static bool value(int la, int lb, int lc, int ld, + bool p1p2_swappable = true) { + return la <= lb && lc <= ld && + (!p1p2_swappable || (la < lc || (la == lc && lb <= ld))); } }; template @@ -1115,17 +1117,16 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, std::shared_ptr context(new CppCodeContext(cparams)); std::shared_ptr memman(new WorstFitMemoryManager()); + bool p1_p2_swappable = !std::is_same::value; + for (unsigned int la = 0; la <= lmax; la++) { for (unsigned int lb = 0; lb <= lmax; lb++) { for (unsigned int lc = 0; lc <= lmax; lc++) { for (unsigned int ld = 0; ld <= lmax; ld++) { - if constexpr (std::is_same::value) { - if (!(la >= lb && lc >= ld)) continue; - } else { - if (!ShellQuartetSetPredicate( - LIBINT_SHELL_SET)>::value(la, lb, lc, ld)) - continue; - } + if (!ShellQuartetSetPredicate( + LIBINT_SHELL_SET)>::value(la, lb, lc, ld, p1_p2_swappable)) + continue; + // std::shared_ptr tactic(new ParticleDirectionTactic(la+lb > // lc+ld ? false : true)); std::shared_ptr tactic( @@ -1141,9 +1142,7 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, ///////////////////////////////// // loop over operator components ///////////////////////////////// - // most important operators have 1 component ... - std::vector descrs(1); // operator descriptors - // important EXCEPTION: multipole moments + std::vector descrs(1); if (std::is_same::value) { // reset descriptors array descrs.resize(0); @@ -1156,8 +1155,9 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, // unroll only if max_am <= cparams->max_am_opt(task) using std::max; const unsigned int max_am = max(max(la, lb), max(lc, ld)); const bool need_to_optimize = (max_am <= cparams->max_am_opt(task)); + const auto nopers = descrs.size(); const bool need_to_unroll = - l_to_cgshellsize(la) * l_to_cgshellsize(lb) * + nopers * l_to_cgshellsize(la) * l_to_cgshellsize(lb) * l_to_cgshellsize(lc) * l_to_cgshellsize(ld) <= cparams->unroll_threshold(); const unsigned int unroll_threshold = @@ -1285,11 +1285,9 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, iface->to_static_init(oss.str()); // need to declare this function internally - for (std::deque::const_iterator i = - decl_filenames.begin(); - i != decl_filenames.end(); ++i) { + for (auto& decl_filename : decl_filenames) { oss.str(""); - oss << "#include <" << *i << ">" << endl; + oss << "#include <" << decl_filename << ">" << endl; iface->to_int_iface(oss.str()); } diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index 82ee512ef..14621936b 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -83,11 +83,9 @@ CR_11_Coulombσpσp_11::CR_11_Coulombσpσp_11( const auto &oper = Tint->oper(); - // can express integrals of Coulombσpσp in terms of - // derivative integrals of 1/r12 for primitive Gaussians - // only - if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) - return; + // TODO: need to do this only for uncontracted gaussians + // if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) + // return; using namespace libint2::algebra; using namespace libint2::prefactor; From d4247b0563b541a2b4ffce48fb16578c550a3e5f Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 20 Feb 2026 16:56:32 -0500 Subject: [PATCH 10/64] =?UTF-8?q?reverted=20allowing=20contracted=20shells?= =?UTF-8?q?=20for=20Coulomb=CF=83p=CF=83p=20and=20use=20additional=20diffe?= =?UTF-8?q?rentiator=20when=20on=20MacOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/libint2.h | 17 ++++++++------- .../comp_11_Coulomb\317\203p\317\203p_11.h" | 5 ++--- src/bin/libint/gauss.cc | 21 ++++++++++++++++--- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/include/libint2.h b/include/libint2.h index 59d17ef65..25a231be7 100644 --- a/include/libint2.h +++ b/include/libint2.h @@ -22,18 +22,19 @@ #define _libint2_header_ #define LIBINT_T_SS_EREP_SS(mValue) \ - _aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_##mValue + _aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_##mValue #define LIBINT_T_SS_Km1G12_SS(mValue) \ - _aB_s___0__s___1___r12_minus_1_g12_s___0__s___1___Ab__up_##mValue + _aB_s____0__s____1___r12_minus_1_g12_s____0__s____1___Ab__up_##mValue #define LIBINT_T_SS_K0G12_SS_0 \ - _aB_s___0__s___1___r12_0_g12_s___0__s___1___Ab__up_0 + _aB_s____0__s____1___r12_0_g12_s____0__s____1___Ab__up_0 #define LIBINT_T_SS_K2G12_SS_0 \ - _aB_s___0__s___1___r12_2_g12_s___0__s___1___Ab__up_0 + _aB_s____0__s____1___r12_2_g12_s____0__s____1___Ab__up_0 #define LIBINT_T_SS_K4G12_SS_0 \ - _aB_s___0__s___1___r12_4_g12_s___0__s___1___Ab__up_0 -#define LIBINT_T_S_OVERLAP_S _aB_s___0___Overlap_s___0___Ab__up_ -#define LIBINT_T_S_KINETIC_S _aB_s___0___Kinetic_s___0___Ab__up_ -#define LIBINT_T_S_ELECPOT_S(mValue) _aB_s___0___ElecPot_s___0___Ab__up_##mValue + _aB_s____0__s____1___r12_4_g12_s____0__s____1___Ab__up_0 +#define LIBINT_T_S_OVERLAP_S _aB_s____0___Overlap_s____0___Ab__up_ +#define LIBINT_T_S_KINETIC_S _aB_s____0___Kinetic_s____0___Ab__up_ +#define LIBINT_T_S_ELECPOT_S(mValue) \ + _aB_s____0___ElecPot_s____0___Ab__up_##mValue #include #include diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index 14621936b..315135994 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -83,9 +83,8 @@ CR_11_Coulombσpσp_11::CR_11_Coulombσpσp_11( const auto &oper = Tint->oper(); - // TODO: need to do this only for uncontracted gaussians - // if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) - // return; + if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) + return; using namespace libint2::algebra; using namespace libint2::prefactor; diff --git a/src/bin/libint/gauss.cc b/src/bin/libint/gauss.cc index 3899283ab..aa60b1de7 100644 --- a/src/bin/libint/gauss.cc +++ b/src/bin/libint/gauss.cc @@ -115,8 +115,15 @@ std::string CGF::label() const { unsigned int am = qn_[0] + qn_[1] + qn_[2]; std::string deriv_label; if (!deriv_.zero()) deriv_label = deriv_.label(); - const std::string am_string = am_to_symbol(am, contracted()); + std::string am_string = am_to_symbol(am, contracted()); std::ostringstream oss; + + // Some OSs can have case-insensitive filesystem e.g., MacOS. So here we add + // additional identifier for primitive function labels + if (!this->contracted()) { + am_string += "_"; + } + oss << (pure_sh_ && am > 0 ? "W" : "") << am_string << deriv_label << "_"; if (am == 0) return oss.str(); @@ -223,8 +230,16 @@ CGShell::~CGShell() {} std::string CGShell::label() const { if (is_unit()) return "unit"; - std::string result = std::string(pure_sh_ && qn_[0] > 0 ? "W" : "") + - am_to_symbol(qn_[0], contracted()); + std::string am_symbol = am_to_symbol(qn_[0], contracted()); + + // Some OSs can have case-insensitive filesystem e.g., MacOS. So here we add + // additional identifier for primitive shell labels + if (!this->contracted()) { + am_symbol += "_"; + } + + std::string result = + std::string(pure_sh_ && qn_[0] > 0 ? "W" : "") + am_symbol; if (!deriv_.zero()) result += deriv_.label(); return result; } From 054b8eba4a03140eeb3ceb66be90d4f3d355da5a Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sat, 21 Feb 2026 11:51:43 -0500 Subject: [PATCH 11/64] can generate code for all small component RKB ERIs i.e.,`(SS|SS)` --- src/bin/libint/build_libint.cc | 15 +- ...3p\317\203pCoulomb\317\203p\317\203p_11.h" | 264 ++++++++++++++++++ src/bin/libint/master_ints_list.h | 12 +- src/bin/libint/master_rrs_list.h | 4 + src/bin/libint/oper.h | 36 +++ src/bin/libint/strategy.cc | 8 + 6 files changed, 330 insertions(+), 9 deletions(-) create mode 100644 "src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index c892dd618..8712580a5 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -287,6 +287,11 @@ Coulombσpσp_Descr make_descr(int p, int, int) { return Coulombσpσp_Descr(p); } +template <> +σpσpCoulombσpσp_Descr make_descr<σpσpCoulombσpσp_Descr>(int p, int, int) { + return σpσpCoulombσpσp_Descr(p); +} + } // namespace template @@ -582,8 +587,8 @@ void try_main(int argc, char* argv[]) { #endif #ifdef LIBINT_INCLUDE_RKB_ERI -#define BOOST_PP_RKB_ERI_TASK_TUPLE (coulomb_opop) -#define BOOST_PP_RKB_ERI_TASK_OPER_TUPLE (CoulombσpσpOper) +#define BOOST_PP_RKB_ERI_TASK_TUPLE (coulomb_opop, opop_coulomb_opop) +#define BOOST_PP_RKB_ERI_TASK_OPER_TUPLE (CoulombσpσpOper, σpσpCoulombσpσpOper) #define BOOST_PP_RKB_ERI_TASK_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_RKB_ERI_TASK_TUPLE) #define BOOST_PP_RKB_ERI_TASK_OPER_LIST \ @@ -2328,9 +2333,9 @@ void config_to_api(const std::shared_ptr& cparams, { // 2-body ints -#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ - ("eri", "coulomb_opop", "r12kg12", "r12_0_g12", "r12_2_g12", "g12_T1_g12", \ - "g12dkh") +#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ + ("eri", "coulomb_opop", "opop_coulomb_opop", "r12kg12", "r12_0_g12", \ + "r12_2_g12", "g12_T1_g12", "g12dkh") #define BOOST_PP_TWOBODY_TASKOPER_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_TWOBODY_TASKOPER_TUPLE) diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" new file mode 100644 index 000000000..1cacc7b3a --- /dev/null +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2004-2026 Edward F. Valeev + * + * This file is part of Libint compiler. + * + * Libint compiler 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 compiler 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 compiler. If not, see . + * + */ + +#ifndef LIBINT_COMP_11_ΣPΣPCOULOMBΣPΣP_11_H +#define LIBINT_COMP_11_ΣPΣPCOULOMBΣPΣP_11_H + +#include +#include +#include + +namespace libint2 { + +/** + * this computes integral of + * \sigma \cdot \hat{p}_1 \sigma \cdot \hat{p}_2 \f$ \frac{1}{r_{ij}} \sigma + * \cdot \hat{p}_3 \sigma \cdot \hat{p}_4 \f$ over CGShell/CGF by rewriting it + * as a linear combination of integrals over derivatives of \frac{1}{r_{ij}} + * @tparam F basis function type. valid choices are CGShell or CGF + */ +template +class CR_11_σpσpCoulombσpσp_11 + : public GenericRecurrenceRelation< + CR_11_σpσpCoulombσpσp_11, F, + GenIntegralSet_11_11> { + public: + typedef CR_11_σpσpCoulombσpσp_11 ThisType; + typedef F BasisFunctionType; + typedef σpσpCoulombσpσpOper OperType; + typedef GenIntegralSet_11_11 TargetType; + typedef GenericRecurrenceRelation + ParentType; + friend class GenericRecurrenceRelation; + static const unsigned int max_nchildren = 100; // TODO figure out + + using ParentType::Instance; + + static bool directional() { return false; } + + private: + using ParentType::is_simple; + using ParentType::target_; + using ParentType::RecurrenceRelation::expr_; + using ParentType::RecurrenceRelation::nflops_; + + /// Constructor is private, used by ParentType::Instance that maintains + /// registry of these objects + CR_11_σpσpCoulombσpσp_11(const std::shared_ptr &, + unsigned int = 0); + + static std::string descr() { return "CR"; } +}; + +template +CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11( + const std::shared_ptr &Tint, unsigned int) + : ParentType(Tint, 0) { + assert(Tint->num_func_bra(/* particle */ 0) == 1); + assert(Tint->num_func_bra(/* particle */ 1) == 1); + assert(Tint->num_func_ket(/* particle */ 0) == 1); + assert(Tint->num_func_ket(/* particle */ 1) == 1); + + F a(Tint->bra(0, 0)); + F b(Tint->ket(0, 0)); + F c(Tint->bra(1, 0)); + F d(Tint->ket(1, 0)); + + const auto &oper = Tint->oper(); + + if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) + return; + + using namespace libint2::algebra; + using namespace libint2::prefactor; + using libint2::algebra::operator*; + + const mType zero_m(0u); + + ChildFactory> + factory(this); + + constexpr auto x = 0; + constexpr auto y = 1; + constexpr auto z = 2; + + auto mc = [&](const int r1, const int r2, const int r3, const int r4) { + F a_r1{a}; + a_r1.deriv().inc(r1); + F b_r2{b}; + b_r2.deriv().inc(r2); + F c_r3{c}; + c_r3.deriv().inc(r3); + F d_r4{d}; + d_r4.deriv().inc(r4); + return factory.make_child(a_r1, b_r2, c_r3, d_r4, zero_m); + }; + + // Component wise generation for quaternion : + // ( (σ.p) a (σ.p)b | 1/r12 | (σ.p) c (σ.p) d ) + switch (oper->descr().quaternion_index()) { + case 0: { + // zeroth component = + // x1 x2 x3 x4 + y1 y2 x3 x4 - y1 x2 y3 x4 + x1 y2 y3 x4 + y1 x2 x3 y4 - + // x1 y2 x3 y4 + x1 x2 y3 y4 + y1 y2 y3 y4 + z1 z2 x3 x4 + z1 z2 y3 y4 - + // z1 x2 z3 x4 - z1 y2 z3 y4 + x1 z2 z3 x4 + y1 z2 z3 y4 + z1 x2 x3 z4 + + // z1 y2 y3 z4 - x1 z2 x3 z4 - y1 z2 y3 z4 + x1 x2 z3 z4 + y1 y2 z3 z4 + + // z1 z2 z3 z4 + auto xxxx = mc(x, x, x, x); + auto yyxx = mc(y, y, x, x); + auto yxyx = mc(y, x, y, x); + auto xyyx = mc(x, y, y, x); + auto yxxy = mc(y, x, x, y); + auto xyxy = mc(x, y, x, y); + auto xxyy = mc(x, x, y, y); + auto yyyy = mc(y, y, y, y); + auto zzxx = mc(z, z, x, x); + auto zzyy = mc(z, z, y, y); + auto zxzx = mc(z, x, z, x); + auto zyzy = mc(z, y, z, y); + auto xzzx = mc(x, z, z, x); + auto yzzy = mc(y, z, z, y); + auto zxxz = mc(z, x, x, z); + auto zyyz = mc(z, y, y, z); + auto xzxz = mc(x, z, x, z); + auto yzyz = mc(y, z, y, z); + auto xxzz = mc(x, x, z, z); + auto yyzz = mc(y, y, z, z); + auto zzzz = mc(z, z, z, z); + if (is_simple()) { + expr_ = xxxx + yyxx - yxyx + xyyx + yxxy - xyxy + xxyy + yyyy + zzxx + + zzyy - zxzx - zyzy + xzzx + yzzy + zxxz + zyyz - xzxz - yzyz + + xxzz + yyzz + zzzz; + nflops_ += 20; + } + } break; + case 1: { + // x component = + // - z1 y2 x3 x4 + z1 x2 y3 x4 - z1 x2 x3 y4 - z1 y2 y3 y4 + y1 z2 x3 x4 - + // x1 z2 y3 x4 + x1 z2 x3 y4 + y1 z2 y3 y4 - y1 x2 z3 x4 + x1 y2 z3 x4 - + // x1 x2 z3 y4 - y1 y2 z3 y4 - z1 z2 z3 y4 + y1 x2 x3 z4 - x1 y2 x3 z4 + + // x1 x2 y3 z4 + y1 y2 y3 z4 + z1 z2 y3 z4 - z1 y2 z3 z4 + y1 z2 z3 z4 + auto zyxx = mc(z, y, x, x); + auto zxyx = mc(z, x, y, x); + auto zxxy = mc(z, x, x, y); + auto zyyy = mc(z, y, y, y); + auto yzxx = mc(y, z, x, x); + auto xzyx = mc(x, z, y, x); + auto xzxy = mc(x, z, x, y); + auto yzyy = mc(y, z, y, y); + auto yxzx = mc(y, x, z, x); + auto xyzx = mc(x, y, z, x); + auto xxzy = mc(x, x, z, y); + auto yyzy = mc(y, y, z, y); + auto zzzy = mc(z, z, z, y); + auto yxxz = mc(y, x, x, z); + auto xyxz = mc(x, y, x, z); + auto xxyz = mc(x, x, y, z); + auto yyyz = mc(y, y, y, z); + auto zzyz = mc(z, z, y, z); + auto zyzz = mc(z, y, z, z); + auto yzzz = mc(y, z, z, z); + if (is_simple()) { + // swapped order of first two terms compiler does not like negative sign + // in front of first term + expr_ = zxyx - zyxx - zxxy - zyyy + yzxx - xzyx + xzxy + yzyy - yxzx + + xyzx - xxzy - yyzy - zzzy + yxxz - xyxz + xxyz + yyyz + zzyz - + zyzz + yzzz; + nflops_ += 19; + } + } break; + case 2: { + // y component = + // z1 x2 x3 x4 + z1 y2 y3 x4 - z1 y2 x3 y4 + z1 x2 y3 y4 - x1 z2 x3 x4 - + // y1 z2 y3 x4 + y1 z2 x3 y4 - x1 z2 y3 y4 + x1 x2 z3 x4 + y1 y2 z3 x4 - + // y1 x2 z3 y4 + x1 y2 z3 y4 + z1 z2 z3 x4 - x1 x2 x3 z4 - y1 y2 x3 z4 + + // y1 x2 y3 z4 - x1 y2 y3 z4 - z1 z2 x3 z4 + z1 x2 z3 z4 - x1 z2 z3 z4 + auto zxxx = mc(z, x, x, x); + auto zyyx = mc(z, y, y, x); + auto zyxy = mc(z, y, x, y); + auto zxyy = mc(z, x, y, y); + auto xzxx = mc(x, z, x, x); + auto yzyx = mc(y, z, y, x); + auto yzxy = mc(y, z, x, y); + auto xzyy = mc(x, z, y, y); + auto xxzx = mc(x, x, z, x); + auto yyzx = mc(y, y, z, x); + auto yxzy = mc(y, x, z, y); + auto xyzy = mc(x, y, z, y); + auto zzzx = mc(z, z, z, x); + auto xxxz = mc(x, x, x, z); + auto yyxz = mc(y, y, x, z); + auto yxyz = mc(y, x, y, z); + auto xyyz = mc(x, y, y, z); + auto zzxz = mc(z, z, x, z); + auto zxzz = mc(z, x, z, z); + auto xzzz = mc(x, z, z, z); + + if (is_simple()) { + expr_ = zxxx + zyyx - zyxy + zxyy - xzxx - yzyx + yzxy - xzyy + xxzx + + yyzx - yxzy + xyzy + zzzx - xxxz - yyxz + yxyz - xyyz - zzxz + + zxzz - xzzz; + nflops_ += 19; + } + } break; + case 3: { + // z component = + // - y1 x2 x3 x4 + x1 y2 x3 x4 - x1 x2 y3 x4 - y1 y2 y3 x4 + x1 x2 x3 y4 + + // y1 y2 x3 y4 - y1 x2 y3 y4 + x1 y2 y3 y4 - z1 z2 y3 x4 + z1 z2 x3 y4 + + // z1 y2 z3 x4 - z1 x2 z3 y4 - y1 z2 z3 x4 + x1 z2 z3 y4 - z1 y2 x3 z4 + + // z1 x2 y3 z4 + y1 z2 x3 z4 - x1 z2 y3 z4 - y1 x2 z3 z4 + x1 y2 z3 z4 + auto yxxx = mc(y, x, x, x); + auto xyxx = mc(x, y, x, x); + auto xxyx = mc(x, x, y, x); + auto yyyx = mc(y, y, y, x); + auto xxxy = mc(x, x, x, y); + auto yyxy = mc(y, y, x, y); + auto yxyy = mc(y, x, y, y); + auto xyyy = mc(x, y, y, y); + auto zzyx = mc(z, z, y, x); + auto zzxy = mc(z, z, x, y); + auto zyzx = mc(z, y, z, x); + auto zxzy = mc(z, x, z, y); + auto yzzx = mc(y, z, z, x); + auto xzzy = mc(x, z, z, y); + auto zyxz = mc(z, y, x, z); + auto zxyz = mc(z, x, y, z); + auto yzxz = mc(y, z, x, z); + auto xzyz = mc(x, z, y, z); + auto yxzz = mc(y, x, z, z); + auto xyzz = mc(x, y, z, z); + if (is_simple()) { + expr_ = xyxx - yxxx - xxyx - yyyx + xxxy + yyxy - yxyy + xyyy - zzyx + + zzxy + zyzx - zxzy - yzzx + xzzy - zyxz + zxyz + yzxz - xzyz - + yxzz + xyzz; + nflops_ += 19; + } + } break; + default: + throw std::runtime_error( + "CR_11_σpσpCoulombσpσp_11: invalid quaternionic index"); + } + +} // CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11 +}; // namespace libint2 + +#endif // LIBINT_COMP_11_ΣPΣPCOULOMBΣPΣP_11_H diff --git a/src/bin/libint/master_ints_list.h b/src/bin/libint/master_ints_list.h index c5a6f4655..37bfa29a7 100644 --- a/src/bin/libint/master_ints_list.h +++ b/src/bin/libint/master_ints_list.h @@ -109,6 +109,10 @@ typedef GenIntegralSet_11_11 TwoPRep_11_11_int; typedef GenIntegralSet_11_11 Coulombσpσp_11_11_sq; typedef GenIntegralSet_11_11 Coulombσpσp_11_11_int; +typedef GenIntegralSet_11_11 + σpσpCoulombσpσp_11_11_sq; +typedef GenIntegralSet_11_11 + σpσpCoulombσpσp_11_11_int; typedef GenIntegralSet_11_11 R12kG12_11_11_sq; typedef GenIntegralSet_11_11 R12kG12_11_11_int; typedef GenIntegralSet_11_11 @@ -148,10 +152,10 @@ typedef boost::mpl::list< SMultipole_1_1_int, #endif TwoPRep_11_11_sq, TwoPRep_11_11_int, Coulombσpσp_11_11_sq, - Coulombσpσp_11_11_int, R12kG12_11_11_sq, R12kG12_11_11_int, - R12kR12lG12_11_11_sq, R12kR12lG12_11_11_int, TiG12_11_11_sq, - TiG12_11_11_int, G12TiG12_11_11_sq, G12TiG12_11_11_int, - DivG12prime_xTx_11_11_sq, DivG12prime_xTx_11_11_int, + Coulombσpσp_11_11_int, σpσpCoulombσpσp_11_11_sq, σpσpCoulombσpσp_11_11_int, + R12kG12_11_11_sq, R12kG12_11_11_int, R12kR12lG12_11_11_sq, + R12kR12lG12_11_11_int, TiG12_11_11_sq, TiG12_11_11_int, G12TiG12_11_11_sq, + G12TiG12_11_11_int, DivG12prime_xTx_11_11_sq, DivG12prime_xTx_11_11_int, DummySymmIntegral_11_11_sq, DummySymmIntegral_11_11_int> MasterIntegralTypeList; diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index 32e56878f..d55cfa301 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -269,6 +270,9 @@ typedef CR_DerivGauss CR_11_Coulombσpσp_11_sh; typedef CR_11_Coulombσpσp_11 CR_11_Coulombσpσp_11_int; + +typedef CR_11_σpσpCoulombσpσp_11 CR_11_σpσpCoulombσpσp_11_sh; +typedef CR_11_σpσpCoulombσpσp_11 CR_11_σpσpCoulombσpσp_11_int; }; // namespace libint2 #endif // header guard diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index 36292613a..cecbda72b 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -437,6 +437,42 @@ struct Coulombσpσp_Descr : public Contractable { }; typedef GenOper CoulombσpσpOper; +struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr> { + typedef MultiplicativeSymm2Body_Props Properties; + + σpσpCoulombσpσp_Descr() : quaternion_index_(0) {} + σpσpCoulombσpσp_Descr(int quaternion_index) + : quaternion_index_(quaternion_index) { + assert(quaternion_index <= 3); + } + + static const unsigned int max_key = 4; + unsigned int key() const { return quaternion_index(); } + std::string description() const { + std::string descr("opop_coulomb_opop["); + if (quaternion_index() == 0) + descr += "0"; + else if (quaternion_index() == 1) + descr += "X"; + else if (quaternion_index() == 2) + descr += "Y"; + else if (quaternion_index() == 3) + descr += "Z"; + else + abort(); + return descr + "]"; + } + std::string label() const { return description(); } + int psymm(int i, int j) const { abort(); } + int hermitian(int i) const { return +1; } + + int quaternion_index() const { return quaternion_index_; } + + private: + const int quaternion_index_ = -1; +}; +typedef GenOper<σpσpCoulombσpσp_Descr> σpσpCoulombσpσpOper; + /** GTG_1d is the two-body 1-dimensional Gaussian geminal */ struct GTG_1d_Descr : public Contractable { diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index bcab9dcad..58fb8d2bd 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -123,6 +123,14 @@ template <> struct MasterStrategy { typedef boost::mpl::list value; }; +template <> +struct MasterStrategy<σpσpCoulombσpσp_11_11_sq> { + typedef boost::mpl::list value; +}; +template <> +struct MasterStrategy<σpσpCoulombσpσp_11_11_int> { + typedef boost::mpl::list value; +}; #if LIBINT_SHELLQUARTET_STRATEGY == LIBINT_SHELLQUARTET_STRATEGY_A0C0 template <> From 60e2117db25cbdc062a930c1dcb9e83837dfb21f Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sat, 28 Feb 2026 21:38:37 -0500 Subject: [PATCH 12/64] Can evaluate `(SS|SS)` integrals. Only for `STANDARD` shell quartet for now. --- export/tests/unit/test-2body.cc | 346 ++++++++++++------ include/libint2/engine.h | 30 +- include/libint2/engine.impl.h | 75 ++-- src/bin/libint/build_libint.cc | 80 ++-- ...3p\317\203pCoulomb\317\203p\317\203p_11.h" | 132 ++----- 5 files changed, 383 insertions(+), 280 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index ee08ed972..bf3b6a60d 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -345,20 +345,83 @@ TEST_CASE("eri geometric derivatives", "[engine][2-body]") { } TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { - std::vector obs{ - // pseudorandom s - Shell{{1.0, 0.3}, {{0, false, {0.9, 0.3}}}, {{0.0, 0.0, 0.0}}}, - // pseudorandom p - Shell{{2.0, 0.4}, {{1, false, {0.8, -0.2}}}, {{1.0, 1.0, 1.0}}}}; + std::vector obs{// pseudorandom s + Shell{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}, + // pseudorandom p + Shell{{2.0}, {{1, false, {1.0}}}, {{1.0, 1.0, 1.0}}}}; const auto max_nprim = libint2::max_nprim(obs); const auto max_l = libint2::max_l(obs); typedef std::array der_idx; - SECTION("Coulombσpσp") { - Engine engine; + // e.g. d_xx maps the derivative index of derivative w.r.t x + // coord of ket1 and x coord of ket2 in Chemist notation. + // deriv indices for (LL|SS) + der_idx d_xx = {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0}; + der_idx d_yy = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}; + der_idx d_zz = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1}; + der_idx d_yz = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1}; + der_idx d_zy = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0}; + der_idx d_zx = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0}; + der_idx d_xz = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; + der_idx d_xy = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0}; + der_idx d_yx = {0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0}; + + // deriv indices for (SS|SS) + // 0th component + der_idx xxxx = {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; + der_idx yyxx = {0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0}; + der_idx zzxx = {0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0}; + der_idx yxyx = {0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0}; + der_idx xyyx = {1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0}; + der_idx yxxy = {0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0}; + der_idx xyxy = {1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0}; + der_idx xxyy = {1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0}; + der_idx yyyy = {0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}; + der_idx zzyy = {0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0}; + der_idx xxzz = {1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1}; + der_idx yyzz = {0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1}; + der_idx zzzz = {0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1}; + + // x-component + der_idx zxzx = {0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0}; + der_idx xzzx = {1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0}; + der_idx zyzy = {0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0}; + der_idx yzzy = {0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0}; + der_idx zxxz = {0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1}; + der_idx xzxz = {1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1}; + der_idx zyyz = {0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1}; + der_idx yzyz = {0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1}; + + // y-component + der_idx zyzx = {0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0}; + der_idx yzzx = {0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0}; + der_idx zxzy = {0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0}; + der_idx xzzy = {1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0}; + der_idx zyxz = {0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1}; + der_idx yzxz = {0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1}; + der_idx zxyz = {0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1}; + der_idx xzyz = {1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1}; + + // z-component + der_idx yxxx = {0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; + der_idx xyxx = {1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0}; + der_idx xxyx = {1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0}; + der_idx yyyx = {0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0}; + der_idx zzyx = {0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0}; + der_idx xxxy = {1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0}; + der_idx yyxy = {0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0}; + der_idx zzxy = {0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0}; + der_idx yxyy = {0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0}; + der_idx xyyy = {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}; + der_idx yxzz = {0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1}; + der_idx xyzz = {1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1}; + + SECTION("Coulombσpσp and σpσpCoulombσpσp") { + Engine engine_llss, engine_ssss; try { - engine = Engine(Operator::coulomb_opop, max_nprim, max_l, 0); + engine_llss = Engine(Operator::coulomb_opop, max_nprim, max_l, 0); + engine_ssss = Engine(Operator::opop_coulomb_opop, max_nprim, max_l, 0); // TODO: need another unit test for derivatives of RKB ERIs } catch ( Engine::lmax_exceeded &) { // skip the test if lmax exceeded or libint2 @@ -371,119 +434,172 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { for (int s1 = 0; s1 != nshell; ++s1) { for (int s2 = 0; s2 != nshell; ++s2) { for (int s3 = 0; s3 != nshell; ++s3) { - const auto &results = - engine.compute(obs[s0], obs[s1], obs[s2], obs[s3]); - assert(results.size() == - 4); // we get 4 buffers for each quaternion component - - LIBINT2_REF_REALTYPE Aref[3]; - for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; - LIBINT2_REF_REALTYPE Bref[3]; - for (int i = 0; i < 3; ++i) Bref[i] = obs[s1].O[i]; - LIBINT2_REF_REALTYPE Cref[3]; - for (int i = 0; i < 3; ++i) Cref[i] = obs[s2].O[i]; - LIBINT2_REF_REALTYPE Dref[3]; - for (int i = 0; i < 3; ++i) Dref[i] = obs[s3].O[i]; - - int ijkl = 0; - - int l0, m0, n0; - FOR_CART(l0, m0, n0, obs[s0].contr[0].l) - - int l1, m1, n1; - FOR_CART(l1, m1, n1, obs[s1].contr[0].l) - - int l2, m2, n2; - FOR_CART(l2, m2, n2, obs[s2].contr[0].l) - - int l3, m3, n3; - FOR_CART(l3, m3, n3, obs[s3].contr[0].l) - - std::array ref_coulomb_opop{0.0, 0.0, 0.0, - 0.0}; - uint p0123 = 0; - for (uint p0 = 0; p0 < obs[s0].nprim(); p0++) { - for (uint p1 = 0; p1 < obs[s1].nprim(); p1++) { - for (uint p2 = 0; p2 < obs[s2].nprim(); p2++) { - for (uint p3 = 0; p3 < obs[s3].nprim(); p3++, p0123++) { - const LIBINT2_REF_REALTYPE alpha0 = obs[s0].alpha[p0]; - const LIBINT2_REF_REALTYPE alpha1 = obs[s1].alpha[p1]; - const LIBINT2_REF_REALTYPE alpha2 = obs[s2].alpha[p2]; - const LIBINT2_REF_REALTYPE alpha3 = obs[s3].alpha[p3]; - - const LIBINT2_REF_REALTYPE c0 = obs[s0].contr[0].coeff[p0]; - const LIBINT2_REF_REALTYPE c1 = obs[s1].contr[0].coeff[p1]; - const LIBINT2_REF_REALTYPE c2 = obs[s2].contr[0].coeff[p2]; - const LIBINT2_REF_REALTYPE c3 = obs[s3].contr[0].coeff[p3]; - const LIBINT2_REF_REALTYPE c0123 = c0 * c1 * c2 * c3; - - auto eri_drr = [&](der_idx d_rr) { - return eri(d_rr.data(), l0, m0, n0, alpha0, Aref, l1, m1, - n1, alpha1, Bref, l2, m2, n2, alpha2, Cref, l3, - m3, n3, alpha3, Dref, 0); - }; - - // e.g. d_xx maps the derivative index of derivative w.r.t x - // coord of ket1 and x coord of ket2 in Chemist notation. - der_idx d_xx = {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0}; - der_idx d_yy = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}; - der_idx d_zz = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1}; - ref_coulomb_opop[0] += - c0123 * (eri_drr(d_xx) + eri_drr(d_yy) + eri_drr(d_zz)); - - der_idx d_yz = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1}; - der_idx d_zy = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0}; - ref_coulomb_opop[1] += - c0123 * (eri_drr(d_yz) - eri_drr(d_zy)); - - der_idx d_zx = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0}; - der_idx d_xz = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; - ref_coulomb_opop[2] += - c0123 * (eri_drr(d_zx) - eri_drr(d_xz)); - - der_idx d_xy = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0}; - der_idx d_yx = {0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0}; - ref_coulomb_opop[3] += - c0123 * (eri_drr(d_xy) - eri_drr(d_yx)); + if (s0 == 0 && s1 == 1 && s2 == 0 && s3 == 1) { + const auto &results_llss = + engine_llss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); + const auto &results_ssss = + engine_ssss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); + assert(results_llss.size() == + 4); // we get 4 buffers for each quaternion component + + LIBINT2_REF_REALTYPE Aref[3]; + for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; + LIBINT2_REF_REALTYPE Bref[3]; + for (int i = 0; i < 3; ++i) Bref[i] = obs[s1].O[i]; + LIBINT2_REF_REALTYPE Cref[3]; + for (int i = 0; i < 3; ++i) Cref[i] = obs[s2].O[i]; + LIBINT2_REF_REALTYPE Dref[3]; + for (int i = 0; i < 3; ++i) Dref[i] = obs[s3].O[i]; + + int ijkl = 0; + + int l0, m0, n0; + FOR_CART(l0, m0, n0, obs[s0].contr[0].l) + + int l1, m1, n1; + FOR_CART(l1, m1, n1, obs[s1].contr[0].l) + + int l2, m2, n2; + FOR_CART(l2, m2, n2, obs[s2].contr[0].l) + + int l3, m3, n3; + FOR_CART(l3, m3, n3, obs[s3].contr[0].l) + + std::array ref_coulomb_opop{0.0, 0.0, + 0.0, 0.0}; + std::array ref_opop_coulomb_opop{ + 0.0, 0.0, 0.0, 0.0}; + uint p0123 = 0; + for (uint p0 = 0; p0 < obs[s0].nprim(); p0++) { + for (uint p1 = 0; p1 < obs[s1].nprim(); p1++) { + for (uint p2 = 0; p2 < obs[s2].nprim(); p2++) { + for (uint p3 = 0; p3 < obs[s3].nprim(); p3++, p0123++) { + const LIBINT2_REF_REALTYPE alpha0 = obs[s0].alpha[p0]; + const LIBINT2_REF_REALTYPE alpha1 = obs[s1].alpha[p1]; + const LIBINT2_REF_REALTYPE alpha2 = obs[s2].alpha[p2]; + const LIBINT2_REF_REALTYPE alpha3 = obs[s3].alpha[p3]; + + const LIBINT2_REF_REALTYPE c0 = + obs[s0].contr[0].coeff[p0]; + const LIBINT2_REF_REALTYPE c1 = + obs[s1].contr[0].coeff[p1]; + const LIBINT2_REF_REALTYPE c2 = + obs[s2].contr[0].coeff[p2]; + const LIBINT2_REF_REALTYPE c3 = + obs[s3].contr[0].coeff[p3]; + const LIBINT2_REF_REALTYPE c0123 = c0 * c1 * c2 * c3; + + auto eri_drrrr = [&](der_idx d_rrrr) { + return eri(d_rrrr.data(), l0, m0, n0, alpha0, Aref, l1, + m1, n1, alpha1, Bref, l2, m2, n2, alpha2, + Cref, l3, m3, n3, alpha3, Dref, 0); + }; + + // (LL|SS) + ref_coulomb_opop[0] += + c0123 * + (eri_drrrr(d_xx) + eri_drrrr(d_yy) + eri_drrrr(d_zz)); + ref_coulomb_opop[1] += + c0123 * (eri_drrrr(d_yz) - eri_drrrr(d_zy)); + ref_coulomb_opop[2] += + c0123 * (eri_drrrr(d_zx) - eri_drrrr(d_xz)); + ref_coulomb_opop[3] += + c0123 * (eri_drrrr(d_xy) - eri_drrrr(d_yx)); + + // (SS|SS) + ref_opop_coulomb_opop[0] += + c0123 * + (eri_drrrr(xxxx) + eri_drrrr(yyxx) + eri_drrrr(zzxx) - + eri_drrrr(yxyx) + eri_drrrr(xyyx) + eri_drrrr(yxxy) - + eri_drrrr(xyxy) + eri_drrrr(xxyy) + eri_drrrr(yyyy) + + eri_drrrr(zzyy) + eri_drrrr(xxzz) + eri_drrrr(yyzz) + + eri_drrrr(zzzz)); + ref_opop_coulomb_opop[1] += + c0123 * + (eri_drrrr(zxzx) - eri_drrrr(xzzx) - eri_drrrr(zyzy) + + eri_drrrr(yzzy) - eri_drrrr(zxxz) + eri_drrrr(xzxz) + + eri_drrrr(zyyz) - eri_drrrr(yzyz)); + ref_opop_coulomb_opop[2] += + c0123 * + (-eri_drrrr(zyzx) + eri_drrrr(yzzx) - + eri_drrrr(zxzy) + eri_drrrr(xzzy) + eri_drrrr(zyxz) - + eri_drrrr(yzxz) + eri_drrrr(zxyz) - eri_drrrr(xzyz)); + ref_opop_coulomb_opop[3] += + c0123 * + (-eri_drrrr(yxxx) + eri_drrrr(xyxx) - + eri_drrrr(xxyx) - eri_drrrr(yyyx) - eri_drrrr(zzyx) + + eri_drrrr(xxxy) + eri_drrrr(yyxy) + eri_drrrr(zzxy) - + eri_drrrr(yxyy) + eri_drrrr(xyyy) - eri_drrrr(yxzz) + + eri_drrrr(xyzz)); + } } } } - } - const double ABSOLUTE_DEVIATION_THRESHOLD = 5.0E-14; - const double RELATIVE_DEVIATION_THRESHOLD = - 1.0E-9; // For more detail on choice of these thresholds, see - // the comments in the TEST_CASE "eri geometric - // derivatives" - - std::array abs_errs; - std::array rel_abs_errs; - - for (auto comp = 0; comp < 4; ++comp) { - abs_errs[comp] = - abs(ref_coulomb_opop[comp] - results[comp][ijkl]); - rel_abs_errs[comp] = abs(abs_errs[comp] / ref_coulomb_opop[comp]); - bool not_ok = rel_abs_errs[comp] > RELATIVE_DEVIATION_THRESHOLD && - abs_errs[comp] > ABSOLUTE_DEVIATION_THRESHOLD; - // no 3^n prefactor here since the intrinsic deriv order is 2 - if (not_ok) { - std::cout << "(l0 l1| l2 l3) = " - << "(" << s0 << " " << s1 << " | " << s2 << " " << s3 - << ") " - << "Elem " << ijkl << " comp= " << comp - << " : ref = " << ref_coulomb_opop[comp] - << " libint = " << results[comp][ijkl] - << " relabs_error = " << rel_abs_errs[comp] - << " abs_error = " << abs_errs[comp] << std::endl; + const double ABSOLUTE_DEVIATION_THRESHOLD = 5.0E-14; + const double RELATIVE_DEVIATION_THRESHOLD = + 1.0E-9; // For more detail on choice of these thresholds, see + // the comments in the TEST_CASE "eri geometric + // derivatives" + + std::array abs_errs_llss; + std::array rel_abs_errs_llss; + + std::array abs_errs_ssss; + std::array rel_abs_errs_ssss; + + for (auto comp = 0; comp < 4; ++comp) { + abs_errs_llss[comp] = + abs(ref_coulomb_opop[comp] - results_llss[comp][ijkl]); + rel_abs_errs_llss[comp] = + abs(abs_errs_llss[comp] / ref_coulomb_opop[comp]); + + abs_errs_ssss[comp] = + abs(ref_opop_coulomb_opop[comp] - results_ssss[comp][ijkl]); + rel_abs_errs_ssss[comp] = + abs(abs_errs_ssss[comp] / ref_opop_coulomb_opop[comp]); + + bool llss_not_ok = + rel_abs_errs_llss[comp] > RELATIVE_DEVIATION_THRESHOLD && + abs_errs_llss[comp] > ABSOLUTE_DEVIATION_THRESHOLD; + + bool ssss_not_ok = + rel_abs_errs_ssss[comp] > RELATIVE_DEVIATION_THRESHOLD && + abs_errs_ssss[comp] > ABSOLUTE_DEVIATION_THRESHOLD; + + // no 3^n prefactor here since the intrinsic deriv order is 2 + if (llss_not_ok) { + std::cout << "(l0 l1| l2 l3) = " + << "(" << s0 << " " << s1 << " | " << s2 << " " + << s3 << ") " + << "Elem " << ijkl << " comp= " << comp + << " : ref = " << ref_coulomb_opop[comp] + << " libint = " << results_llss[comp][ijkl] + << " relabs_error = " << rel_abs_errs_llss[comp] + << " abs_error = " << abs_errs_llss[comp] + << std::endl; + } + if (ssss_not_ok) { + std::cout << "(l0 l1| l2 l3) = " + << "(" << s0 << " " << s1 << " | " << s2 << " " + << s3 << ") " + << "Elem " << ijkl << " comp= " << comp + << " : ref = " << ref_opop_coulomb_opop[comp] + << " libint = " << results_ssss[comp][ijkl] + << " relabs_error = " << rel_abs_errs_ssss[comp] + << " abs_error = " << abs_errs_ssss[comp] + << std::endl; + } + REQUIRE(!llss_not_ok); + REQUIRE(!ssss_not_ok); } - REQUIRE(!not_ok); - } - ++ijkl; - END_FOR_CART - END_FOR_CART - END_FOR_CART - END_FOR_CART + ++ijkl; + END_FOR_CART + END_FOR_CART + END_FOR_CART + END_FOR_CART + } } } } diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 81048b049..7253ce417 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -156,6 +156,10 @@ enum class Operator { /// (2-body) \f$ r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ where k1 & k2 are /// centers of ket1 and ket2, respectively coulomb_opop, + /// (2-body) \f$ (σ.p_{b1})(σ.p_{b2}) r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ + /// where b1 & b2 are centers of bra1 and bra2 and k1 & k2 are centers of + /// ket1 and ket2, respectively + opop_coulomb_opop, /// contracted Gaussian geminal cgtg, /// contracted Gaussian geminal times Coulomb @@ -357,6 +361,12 @@ struct operator_traits static constexpr auto nopers = 4; static constexpr auto intrinsic_deriv_order = 2; }; +template <> +struct operator_traits + : public operator_traits { + static constexpr auto nopers = 4; + static constexpr auto intrinsic_deriv_order = 4; +}; namespace detail { template @@ -851,16 +861,16 @@ class Engine { const Shell& ket2, const ShellPair* spbra, const ShellPair* spket); // clang-format off - /** this specifies target precision for computing the integrals, i.e. - * the target absolute (i.e., not relative) error of the integrals. - * It is used to screen out primitive integrals. For some screening - * methods precision can be almost guaranteed (due to finite precision - * of the precomputed interpolation tables used to evaluate the core integrals - * it is not in general possible to guarantee precision rigorously). - * - * @param[in] prec the target precision - * @sa ScreeningMethod - */ + /** this specifies target precision for computing the integrals, i.e. + * the target absolute (i.e., not relative) error of the integrals. + * It is used to screen out primitive integrals. For some screening + * methods precision can be almost guaranteed (due to finite precision + * of the precomputed interpolation tables used to evaluate the core integrals + * it is not in general possible to guarantee precision rigorously). + * + * @param[in] prec the target precision + * @sa ScreeningMethod + */ // clang-format on Engine& set_precision(scalar_type prec) { if (prec <= 0.) { diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 834fd9c6d..da5275c4c 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -70,31 +70,32 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { /// These MUST appear in the same order as in Operator. /// You must also update BOOST_PP_NBODY_OPERATOR_LAST_ONEBODY_INDEX when you add /// one-body ints -#define BOOST_PP_NBODY_OPERATOR_LIST \ - (overlap, /* overlap */ \ - (kinetic, /* kinetic */ \ - (elecpot, /* nuclear */ \ - (elecpot, /* erf_nuclear */ \ - (elecpot, /* erfc_nuclear */ \ - (elecpot, /* erfx_nuclear */ \ - (1emultipole, /* emultipole1 */ \ - (2emultipole, /* emultipole2 */ \ - (3emultipole, /* emultipole3 */ \ - (sphemultipole, /* sphemultipole */ \ - (opVop, /* opVop */ \ - (eri, /* delta */ \ - (eri, /* coulomb */ \ - (coulomb_opop, /* coulomb_opop */ \ - (eri, /* cgtg */ \ - (eri, /* cgtg_x_coulomb */ \ - (eri, /* delcgtg2 */ \ - (eri, /* r12 */ \ - (eri, /* erf_coulomb */ \ - (eri, /* erfc_coulomb */ \ - (eri, /* erfx_coulomb */ \ - (eri, /* stg */ \ - (eri, /* yukawa */ \ - BOOST_PP_NIL))))))))))))))))))))))) +#define BOOST_PP_NBODY_OPERATOR_LIST \ + (overlap, /* overlap */ \ + (kinetic, /* kinetic */ \ + (elecpot, /* nuclear */ \ + (elecpot, /* erf_nuclear */ \ + (elecpot, /* erfc_nuclear */ \ + (elecpot, /* erfx_nuclear */ \ + (1emultipole, /* emultipole1 */ \ + (2emultipole, /* emultipole2 */ \ + (3emultipole, /* emultipole3 */ \ + (sphemultipole, /* sphemultipole */ \ + (opVop, /* opVop */ \ + (eri, /* delta */ \ + (eri, /* coulomb */ \ + (coulomb_opop, /* coulomb_opop */ \ + (opop_coulomb_opop, /* coulomb_opop */ \ + (eri, /* cgtg */ \ + (eri, /* cgtg_x_coulomb */ \ + (eri, /* delcgtg2 */ \ + (eri, /* r12 */ \ + (eri, /* erf_coulomb */ \ + (eri, /* erfc_coulomb */ \ + (eri, /* erfx_coulomb */ \ + (eri, /* stg */ \ + (eri, /* yukawa */ \ + BOOST_PP_NIL)))))))))))))))))))))))) #define BOOST_PP_NBODY_OPERATOR_INDEX_TUPLE \ BOOST_PP_MAKE_TUPLE(BOOST_PP_LIST_SIZE(BOOST_PP_NBODY_OPERATOR_LIST)) @@ -702,6 +703,7 @@ __libint2_engine_inline void Engine::initialize(size_t max_nprim) { // target indices. const auto permutable_targets = deriv_order_ > 0 && + (braket_ == BraKet::xx_xx || braket_ == BraKet::xs_xx || braket_ == BraKet::xx_xs); if (permutable_targets) @@ -1213,13 +1215,21 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( #if LIBINT2_SHELLQUARTET_SET == \ LIBINT2_SHELLQUARTET_SET_STANDARD // standard angular momentum ordering - const auto swap_tbra = (tbra1.contr[0].l < tbra2.contr[0].l); - const auto swap_tket = (tket1.contr[0].l < tket2.contr[0].l); const auto swap_braket = ((braket_ == BraKet::xx_xx) && (tbra1.contr[0].l + tbra2.contr[0].l > tket1.contr[0].l + tket2.contr[0].l) && (oper_ != Operator::coulomb_opop)) || braket_ == BraKet::xx_xs; + bool swap_tbra, swap_tket; + if (oper_ == Operator::opop_coulomb_opop) { + bool swap_p1p2 = swap_braket ? (tbra1.contr[0].l < tbra2.contr[0].l) + : (tket1.contr[0].l < tket2.contr[0].l); + swap_tbra = swap_tket = swap_p1p2; + } else { + swap_tbra = (tbra1.contr[0].l < tbra2.contr[0].l); + swap_tket = (tket1.contr[0].l < tket2.contr[0].l); + } + // N.B. cannot swap bra and ket for coulomb_opop since the ket is mutated by // this operator #else // orca angular momentum ordering @@ -1444,6 +1454,13 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( .first(); core_eval_ptr->eval(gm_ptr, T, mmax); } break; + case Operator::opop_coulomb_opop: { + const auto& core_eval_ptr = + any_cast&>(core_eval_pack_) + .first(); + core_eval_ptr->eval(gm_ptr, T, mmax); + } break; case Operator::cgtg_x_coulomb: { const auto& core_eval_ptr = any_cast 0) oper_cart_component_phase = -1.0; // x,y,z quaternion components flip sign on diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 8712580a5..278e2668a 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -75,19 +75,35 @@ struct ShellQuartetSetPredicate { // return true if this set of angular momenta is included static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true); }; + +/** + * standard ordering for angular momenta la, lb, lc, ld + * @param p1p2_swappable whether operator allows swaps of particle 1 and 2 + * functions (e.g., not allowed for Coulombσpσp but allowed + * for Coulomb (TwoPRep)). + * @param bra_ket_coswappable whether need to swap within both bra and ket. + * Not individually swapping of either ket of bra allowed + * ( e.g., for σpσpCoulombσpσp) + */ template <> struct ShellQuartetSetPredicate { - static bool value(int la, int lb, int lc, int ld, - bool p1p2_swappable = true) { - return la >= lb && lc >= ld && (!p1p2_swappable || la + lb <= lc + ld); + static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true, + bool bra_ket_coswappable = false) { + if (bra_ket_coswappable) + return (la + lb <= lc + ld) && lc >= ld; + else + return la >= lb && lc >= ld && (!p1p2_swappable || la + lb <= lc + ld); } }; template <> struct ShellQuartetSetPredicate { - static bool value(int la, int lb, int lc, int ld, - bool p1p2_swappable = true) { - return la <= lb && lc <= ld && - (!p1p2_swappable || (la < lc || (la == lc && lb <= ld))); + static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true, + bool bra_ket_coswappable = false) { + if (bra_ket_coswappable) + return (la < lc || (la == lc && lb <= ld)); + else + return la <= lb && lc <= ld && + (!p1p2_swappable || (la < lc || (la == lc && lb <= ld))); } }; template @@ -1123,13 +1139,17 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, std::shared_ptr memman(new WorstFitMemoryManager()); bool p1_p2_swappable = !std::is_same::value; + bool bra_ket_coswappable = std::is_same::value; + // Note: la, lb, lc, ld generate code for chemist notation (ab|O|cd), where O + // is a two-body operator. for (unsigned int la = 0; la <= lmax; la++) { for (unsigned int lb = 0; lb <= lmax; lb++) { for (unsigned int lc = 0; lc <= lmax; lc++) { for (unsigned int ld = 0; ld <= lmax; ld++) { if (!ShellQuartetSetPredicate( - LIBINT_SHELL_SET)>::value(la, lb, lc, ld, p1_p2_swappable)) + LIBINT_SHELL_SET)>::value(la, lb, lc, ld, p1_p2_swappable, + bra_ket_coswappable)) continue; // std::shared_ptr tactic(new ParticleDirectionTactic(la+lb > @@ -1148,7 +1168,8 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, // loop over operator components ///////////////////////////////// std::vector descrs(1); - if (std::is_same::value) { + if (std::is_same::value || + std::is_same::value) { // reset descriptors array descrs.resize(0); // iterate over quaternion components @@ -1157,7 +1178,8 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, } } - // unroll only if max_am <= cparams->max_am_opt(task) using std::max; + // unroll only if max_am <= cparams->max_am_opt(task) using + // std::max; const unsigned int max_am = max(max(la, lb), max(lc, ld)); const bool need_to_optimize = (max_am <= cparams->max_am_opt(task)); const auto nopers = descrs.size(); @@ -1266,8 +1288,8 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, std::deque decl_filenames; std::deque def_filenames; - // this will generate code for these targets, and potentially generate - // code for its prerequisites + // this will generate code for these targets, and potentially + // generate code for its prerequisites GenerateCode(dg_xxxx, context, cparams, strat, tactic, memman, decl_filenames, def_filenames, prefix, eval_label, false); @@ -1359,9 +1381,9 @@ void build_TwoPRep_1b_2k(std::ostream& os, LIBINT_SHELL_SET)>::value(lbra, lc, ld)) continue; - // I will use 4-center recurrence relations and integrals, and have one - // center carry an s function unfortunately, depending on the direction - // in which the build goes it must be A(0) or B(1) + // I will use 4-center recurrence relations and integrals, and have + // one center carry an s function unfortunately, depending on the + // direction in which the build goes it must be A(0) or B(1) const unsigned int dummy_center = (LIBINT_SHELL_SET == LIBINT_SHELL_SET_ORCA) ? 0 : 1; @@ -1560,8 +1582,8 @@ void build_TwoPRep_1b_1k(std::ostream& os, const unsigned int dummy_center2 = (LIBINT_SHELL_SET == LIBINT_SHELL_SET_ORCA) ? 2 : 3; - // std::shared_ptr tactic(new ParticleDirectionTactic(lbra > lket - // ? false : true)); + // std::shared_ptr tactic(new ParticleDirectionTactic(lbra > + // lket ? false : true)); std::shared_ptr tactic(new FourCenter_OS_Tactic( dummy_center1 == 0 ? 0 : lbra, dummy_center1 == 1 ? 0 : lbra, dummy_center2 == 2 ? 0 : lket, dummy_center2 == 3 ? 0 : lket)); @@ -1674,8 +1696,8 @@ void build_TwoPRep_1b_1k(std::ostream& os, std::deque decl_filenames; std::deque def_filenames; - // this will generate code for this targets, and potentially generate code - // for its prerequisites + // this will generate code for this targets, and potentially generate + // code for its prerequisites GenerateCode(dg_xxx, context, cparams, strat, tactic, memman, decl_filenames, def_filenames, prefix, label, false); @@ -1684,7 +1706,8 @@ void build_TwoPRep_1b_1k(std::ostream& os, taskmgr.current().params(); tparams->max_stack_size(max_am, memman->max_memory_used()); tparams->max_ntarget(targets.size()); - // os << " Max memory used = " << memman->max_memory_used() << std::endl; + // os << " Max memory used = " << memman->max_memory_used() << + // std::endl; // set pointer to the top-level evaluator function ostringstream oss; @@ -1875,8 +1898,8 @@ void build_R12kG12_2b_2k(std::ostream& os, std::deque decl_filenames; std::deque def_filenames; - // this will generate code for this targets, and potentially generate - // code for its prerequisites + // this will generate code for this targets, and potentially + // generate code for its prerequisites GenerateCode(dg_xxxx, context, cparams, strat, tactic, memman, decl_filenames, def_filenames, prefix, label, false); @@ -2230,11 +2253,11 @@ void build_G12DKH_2b_2k(std::ostream& os, oss << "#include <" << decl_filename << ">" << endl; iface->to_int_iface(oss.str()); - // For the most expensive (i.e. presumably complete) graph extract all - // precomputed quantities -- these will be members of the evaluator - // structure also extract all RRs -- need to keep track of these to - // figure out which external symbols appearing in RR code belong to - // this task also + // For the most expensive (i.e. presumably complete) graph extract + // all precomputed quantities -- these will be members of the + // evaluator structure also extract all RRs -- need to keep track of + // these to figure out which external symbols appearing in RR code + // belong to this task also if (la == lmax && lb == lmax && lc == lmax && ld == lmax) extract_symbols(dg_xxxx); @@ -2307,7 +2330,8 @@ void config_to_api(const std::shared_ptr& cparams, // generated tasks declare all tasks in a range of valid tasks as defined or // not LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - // the range is defined by max # of centers, max deriv order, and operator set + // the range is defined by max # of centers, max deriv order, and operator + // set const size_t max_ncenter = 4; for (unsigned int ncenter = 0; ncenter <= max_ncenter; ++ncenter) { std::stringstream oss; diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" index 1cacc7b3a..7bf0a4b9a 100644 --- "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -114,143 +114,75 @@ CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11( }; // Component wise generation for quaternion : - // ( (σ.p) a (σ.p)b | 1/r12 | (σ.p) c (σ.p) d ) + // ( (σ.p) a (σ.p) b | 1/r12 | (σ.p) c (σ.p) d ) switch (oper->descr().quaternion_index()) { case 0: { - // zeroth component = - // x1 x2 x3 x4 + y1 y2 x3 x4 - y1 x2 y3 x4 + x1 y2 y3 x4 + y1 x2 x3 y4 - - // x1 y2 x3 y4 + x1 x2 y3 y4 + y1 y2 y3 y4 + z1 z2 x3 x4 + z1 z2 y3 y4 - - // z1 x2 z3 x4 - z1 y2 z3 y4 + x1 z2 z3 x4 + y1 z2 z3 y4 + z1 x2 x3 z4 + - // z1 y2 y3 z4 - x1 z2 x3 z4 - y1 z2 y3 z4 + x1 x2 z3 z4 + y1 y2 z3 z4 + - // z1 z2 z3 z4 auto xxxx = mc(x, x, x, x); auto yyxx = mc(y, y, x, x); + auto zzxx = mc(z, z, x, x); auto yxyx = mc(y, x, y, x); auto xyyx = mc(x, y, y, x); auto yxxy = mc(y, x, x, y); auto xyxy = mc(x, y, x, y); auto xxyy = mc(x, x, y, y); auto yyyy = mc(y, y, y, y); - auto zzxx = mc(z, z, x, x); auto zzyy = mc(z, z, y, y); - auto zxzx = mc(z, x, z, x); - auto zyzy = mc(z, y, z, y); - auto xzzx = mc(x, z, z, x); - auto yzzy = mc(y, z, z, y); - auto zxxz = mc(z, x, x, z); - auto zyyz = mc(z, y, y, z); - auto xzxz = mc(x, z, x, z); - auto yzyz = mc(y, z, y, z); auto xxzz = mc(x, x, z, z); auto yyzz = mc(y, y, z, z); auto zzzz = mc(z, z, z, z); if (is_simple()) { - expr_ = xxxx + yyxx - yxyx + xyyx + yxxy - xyxy + xxyy + yyyy + zzxx + - zzyy - zxzx - zyzy + xzzx + yzzy + zxxz + zyyz - xzxz - yzyz + - xxzz + yyzz + zzzz; - nflops_ += 20; + expr_ = xxxx + yyxx + zzxx - yxyx + xyyx + yxxy - xyxy + xxyy + yyyy + + zzyy + xxzz + yyzz + zzzz; + nflops_ += 12; } } break; case 1: { - // x component = - // - z1 y2 x3 x4 + z1 x2 y3 x4 - z1 x2 x3 y4 - z1 y2 y3 y4 + y1 z2 x3 x4 - - // x1 z2 y3 x4 + x1 z2 x3 y4 + y1 z2 y3 y4 - y1 x2 z3 x4 + x1 y2 z3 x4 - - // x1 x2 z3 y4 - y1 y2 z3 y4 - z1 z2 z3 y4 + y1 x2 x3 z4 - x1 y2 x3 z4 + - // x1 x2 y3 z4 + y1 y2 y3 z4 + z1 z2 y3 z4 - z1 y2 z3 z4 + y1 z2 z3 z4 - auto zyxx = mc(z, y, x, x); - auto zxyx = mc(z, x, y, x); - auto zxxy = mc(z, x, x, y); - auto zyyy = mc(z, y, y, y); - auto yzxx = mc(y, z, x, x); - auto xzyx = mc(x, z, y, x); - auto xzxy = mc(x, z, x, y); - auto yzyy = mc(y, z, y, y); - auto yxzx = mc(y, x, z, x); - auto xyzx = mc(x, y, z, x); - auto xxzy = mc(x, x, z, y); - auto yyzy = mc(y, y, z, y); - auto zzzy = mc(z, z, z, y); - auto yxxz = mc(y, x, x, z); - auto xyxz = mc(x, y, x, z); - auto xxyz = mc(x, x, y, z); - auto yyyz = mc(y, y, y, z); - auto zzyz = mc(z, z, y, z); - auto zyzz = mc(z, y, z, z); - auto yzzz = mc(y, z, z, z); + auto zxzx = mc(z, x, z, x); + auto xzzx = mc(x, z, z, x); + auto zyzy = mc(z, y, z, y); + auto yzzy = mc(y, z, z, y); + auto zxxz = mc(z, x, x, z); + auto xzxz = mc(x, z, x, z); + auto zyyz = mc(z, y, y, z); + auto yzyz = mc(y, z, y, z); if (is_simple()) { - // swapped order of first two terms compiler does not like negative sign - // in front of first term - expr_ = zxyx - zyxx - zxxy - zyyy + yzxx - xzyx + xzxy + yzyy - yxzx + - xyzx - xxzy - yyzy - zzzy + yxxz - xyxz + xxyz + yyyz + zzyz - - zyzz + yzzz; - nflops_ += 19; + expr_ = zxzx - xzzx - zyzy + yzzy - zxxz + xzxz + zyyz - yzyz; + nflops_ += 7; } } break; case 2: { - // y component = - // z1 x2 x3 x4 + z1 y2 y3 x4 - z1 y2 x3 y4 + z1 x2 y3 y4 - x1 z2 x3 x4 - - // y1 z2 y3 x4 + y1 z2 x3 y4 - x1 z2 y3 y4 + x1 x2 z3 x4 + y1 y2 z3 x4 - - // y1 x2 z3 y4 + x1 y2 z3 y4 + z1 z2 z3 x4 - x1 x2 x3 z4 - y1 y2 x3 z4 + - // y1 x2 y3 z4 - x1 y2 y3 z4 - z1 z2 x3 z4 + z1 x2 z3 z4 - x1 z2 z3 z4 - auto zxxx = mc(z, x, x, x); - auto zyyx = mc(z, y, y, x); - auto zyxy = mc(z, y, x, y); - auto zxyy = mc(z, x, y, y); - auto xzxx = mc(x, z, x, x); - auto yzyx = mc(y, z, y, x); - auto yzxy = mc(y, z, x, y); - auto xzyy = mc(x, z, y, y); - auto xxzx = mc(x, x, z, x); - auto yyzx = mc(y, y, z, x); - auto yxzy = mc(y, x, z, y); - auto xyzy = mc(x, y, z, y); - auto zzzx = mc(z, z, z, x); - auto xxxz = mc(x, x, x, z); - auto yyxz = mc(y, y, x, z); - auto yxyz = mc(y, x, y, z); - auto xyyz = mc(x, y, y, z); - auto zzxz = mc(z, z, x, z); - auto zxzz = mc(z, x, z, z); - auto xzzz = mc(x, z, z, z); - + auto zyzx = mc(z, y, z, x); + auto yzzx = mc(y, z, z, x); + auto zxzy = mc(z, x, z, y); + auto xzzy = mc(x, z, z, y); + auto zyxz = mc(z, y, x, z); + auto yzxz = mc(y, z, x, z); + auto zxyz = mc(z, x, y, z); + auto xzyz = mc(x, z, y, z); if (is_simple()) { - expr_ = zxxx + zyyx - zyxy + zxyy - xzxx - yzyx + yzxy - xzyy + xxzx + - yyzx - yxzy + xyzy + zzzx - xxxz - yyxz + yxyz - xyyz - zzxz + - zxzz - xzzz; - nflops_ += 19; + // swapped order of first two terms compiler does not like negative sign + // in front of first term + expr_ = yzzx - zyzx - zxzy + xzzy + zyxz - yzxz + zxyz - xzyz; + nflops_ += 7; } } break; case 3: { - // z component = - // - y1 x2 x3 x4 + x1 y2 x3 x4 - x1 x2 y3 x4 - y1 y2 y3 x4 + x1 x2 x3 y4 + - // y1 y2 x3 y4 - y1 x2 y3 y4 + x1 y2 y3 y4 - z1 z2 y3 x4 + z1 z2 x3 y4 + - // z1 y2 z3 x4 - z1 x2 z3 y4 - y1 z2 z3 x4 + x1 z2 z3 y4 - z1 y2 x3 z4 + - // z1 x2 y3 z4 + y1 z2 x3 z4 - x1 z2 y3 z4 - y1 x2 z3 z4 + x1 y2 z3 z4 auto yxxx = mc(y, x, x, x); auto xyxx = mc(x, y, x, x); auto xxyx = mc(x, x, y, x); auto yyyx = mc(y, y, y, x); + auto zzyx = mc(z, z, y, x); auto xxxy = mc(x, x, x, y); auto yyxy = mc(y, y, x, y); + auto zzxy = mc(z, z, x, y); auto yxyy = mc(y, x, y, y); auto xyyy = mc(x, y, y, y); - auto zzyx = mc(z, z, y, x); - auto zzxy = mc(z, z, x, y); - auto zyzx = mc(z, y, z, x); - auto zxzy = mc(z, x, z, y); - auto yzzx = mc(y, z, z, x); - auto xzzy = mc(x, z, z, y); - auto zyxz = mc(z, y, x, z); - auto zxyz = mc(z, x, y, z); - auto yzxz = mc(y, z, x, z); - auto xzyz = mc(x, z, y, z); auto yxzz = mc(y, x, z, z); auto xyzz = mc(x, y, z, z); if (is_simple()) { - expr_ = xyxx - yxxx - xxyx - yyyx + xxxy + yyxy - yxyy + xyyy - zzyx + - zzxy + zyzx - zxzy - yzzx + xzzy - zyxz + zxyz + yzxz - xzyz - - yxzz + xyzz; - nflops_ += 19; + expr_ = xyxx - yxxx - xxyx - yyyx - zzyx + xxxy + yyxy + zzxy - yxyy + + xyyy - yxzz + xyzz; + nflops_ += 11; } } break; default: From e29428ab8005654eb789860c1be9b666b946bba1 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Mon, 9 Mar 2026 17:29:27 -0400 Subject: [PATCH 13/64] cleanup: Remove debugging statements from `RKB Coulomb integrals` test and more cleanup --- export/tests/unit/test-2body.cc | 313 ++++++++++++++++---------------- src/bin/libint/build_libint.cc | 17 +- 2 files changed, 161 insertions(+), 169 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index bf3b6a60d..2f0383fc1 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -434,172 +434,165 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { for (int s1 = 0; s1 != nshell; ++s1) { for (int s2 = 0; s2 != nshell; ++s2) { for (int s3 = 0; s3 != nshell; ++s3) { - if (s0 == 0 && s1 == 1 && s2 == 0 && s3 == 1) { - const auto &results_llss = - engine_llss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); - const auto &results_ssss = - engine_ssss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); - assert(results_llss.size() == - 4); // we get 4 buffers for each quaternion component - - LIBINT2_REF_REALTYPE Aref[3]; - for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; - LIBINT2_REF_REALTYPE Bref[3]; - for (int i = 0; i < 3; ++i) Bref[i] = obs[s1].O[i]; - LIBINT2_REF_REALTYPE Cref[3]; - for (int i = 0; i < 3; ++i) Cref[i] = obs[s2].O[i]; - LIBINT2_REF_REALTYPE Dref[3]; - for (int i = 0; i < 3; ++i) Dref[i] = obs[s3].O[i]; - - int ijkl = 0; - - int l0, m0, n0; - FOR_CART(l0, m0, n0, obs[s0].contr[0].l) - - int l1, m1, n1; - FOR_CART(l1, m1, n1, obs[s1].contr[0].l) - - int l2, m2, n2; - FOR_CART(l2, m2, n2, obs[s2].contr[0].l) - - int l3, m3, n3; - FOR_CART(l3, m3, n3, obs[s3].contr[0].l) - - std::array ref_coulomb_opop{0.0, 0.0, - 0.0, 0.0}; - std::array ref_opop_coulomb_opop{ - 0.0, 0.0, 0.0, 0.0}; - uint p0123 = 0; - for (uint p0 = 0; p0 < obs[s0].nprim(); p0++) { - for (uint p1 = 0; p1 < obs[s1].nprim(); p1++) { - for (uint p2 = 0; p2 < obs[s2].nprim(); p2++) { - for (uint p3 = 0; p3 < obs[s3].nprim(); p3++, p0123++) { - const LIBINT2_REF_REALTYPE alpha0 = obs[s0].alpha[p0]; - const LIBINT2_REF_REALTYPE alpha1 = obs[s1].alpha[p1]; - const LIBINT2_REF_REALTYPE alpha2 = obs[s2].alpha[p2]; - const LIBINT2_REF_REALTYPE alpha3 = obs[s3].alpha[p3]; - - const LIBINT2_REF_REALTYPE c0 = - obs[s0].contr[0].coeff[p0]; - const LIBINT2_REF_REALTYPE c1 = - obs[s1].contr[0].coeff[p1]; - const LIBINT2_REF_REALTYPE c2 = - obs[s2].contr[0].coeff[p2]; - const LIBINT2_REF_REALTYPE c3 = - obs[s3].contr[0].coeff[p3]; - const LIBINT2_REF_REALTYPE c0123 = c0 * c1 * c2 * c3; - - auto eri_drrrr = [&](der_idx d_rrrr) { - return eri(d_rrrr.data(), l0, m0, n0, alpha0, Aref, l1, - m1, n1, alpha1, Bref, l2, m2, n2, alpha2, - Cref, l3, m3, n3, alpha3, Dref, 0); - }; - - // (LL|SS) - ref_coulomb_opop[0] += - c0123 * - (eri_drrrr(d_xx) + eri_drrrr(d_yy) + eri_drrrr(d_zz)); - ref_coulomb_opop[1] += - c0123 * (eri_drrrr(d_yz) - eri_drrrr(d_zy)); - ref_coulomb_opop[2] += - c0123 * (eri_drrrr(d_zx) - eri_drrrr(d_xz)); - ref_coulomb_opop[3] += - c0123 * (eri_drrrr(d_xy) - eri_drrrr(d_yx)); - - // (SS|SS) - ref_opop_coulomb_opop[0] += - c0123 * - (eri_drrrr(xxxx) + eri_drrrr(yyxx) + eri_drrrr(zzxx) - - eri_drrrr(yxyx) + eri_drrrr(xyyx) + eri_drrrr(yxxy) - - eri_drrrr(xyxy) + eri_drrrr(xxyy) + eri_drrrr(yyyy) + - eri_drrrr(zzyy) + eri_drrrr(xxzz) + eri_drrrr(yyzz) + - eri_drrrr(zzzz)); - ref_opop_coulomb_opop[1] += - c0123 * - (eri_drrrr(zxzx) - eri_drrrr(xzzx) - eri_drrrr(zyzy) + - eri_drrrr(yzzy) - eri_drrrr(zxxz) + eri_drrrr(xzxz) + - eri_drrrr(zyyz) - eri_drrrr(yzyz)); - ref_opop_coulomb_opop[2] += - c0123 * - (-eri_drrrr(zyzx) + eri_drrrr(yzzx) - - eri_drrrr(zxzy) + eri_drrrr(xzzy) + eri_drrrr(zyxz) - - eri_drrrr(yzxz) + eri_drrrr(zxyz) - eri_drrrr(xzyz)); - ref_opop_coulomb_opop[3] += - c0123 * - (-eri_drrrr(yxxx) + eri_drrrr(xyxx) - - eri_drrrr(xxyx) - eri_drrrr(yyyx) - eri_drrrr(zzyx) + - eri_drrrr(xxxy) + eri_drrrr(yyxy) + eri_drrrr(zzxy) - - eri_drrrr(yxyy) + eri_drrrr(xyyy) - eri_drrrr(yxzz) + - eri_drrrr(xyzz)); - } + const auto &results_llss = + engine_llss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); + const auto &results_ssss = + engine_ssss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); + assert(results_llss.size() == + 4); // we get 4 buffers for each quaternion component + + LIBINT2_REF_REALTYPE Aref[3]; + for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; + LIBINT2_REF_REALTYPE Bref[3]; + for (int i = 0; i < 3; ++i) Bref[i] = obs[s1].O[i]; + LIBINT2_REF_REALTYPE Cref[3]; + for (int i = 0; i < 3; ++i) Cref[i] = obs[s2].O[i]; + LIBINT2_REF_REALTYPE Dref[3]; + for (int i = 0; i < 3; ++i) Dref[i] = obs[s3].O[i]; + + int ijkl = 0; + + int l0, m0, n0; + FOR_CART(l0, m0, n0, obs[s0].contr[0].l) + + int l1, m1, n1; + FOR_CART(l1, m1, n1, obs[s1].contr[0].l) + + int l2, m2, n2; + FOR_CART(l2, m2, n2, obs[s2].contr[0].l) + + int l3, m3, n3; + FOR_CART(l3, m3, n3, obs[s3].contr[0].l) + + std::array ref_coulomb_opop{0.0, 0.0, 0.0, + 0.0}; + std::array ref_opop_coulomb_opop{0.0, 0.0, + 0.0, 0.0}; + uint p0123 = 0; + for (uint p0 = 0; p0 < obs[s0].nprim(); p0++) { + for (uint p1 = 0; p1 < obs[s1].nprim(); p1++) { + for (uint p2 = 0; p2 < obs[s2].nprim(); p2++) { + for (uint p3 = 0; p3 < obs[s3].nprim(); p3++, p0123++) { + const LIBINT2_REF_REALTYPE alpha0 = obs[s0].alpha[p0]; + const LIBINT2_REF_REALTYPE alpha1 = obs[s1].alpha[p1]; + const LIBINT2_REF_REALTYPE alpha2 = obs[s2].alpha[p2]; + const LIBINT2_REF_REALTYPE alpha3 = obs[s3].alpha[p3]; + + const LIBINT2_REF_REALTYPE c0 = obs[s0].contr[0].coeff[p0]; + const LIBINT2_REF_REALTYPE c1 = obs[s1].contr[0].coeff[p1]; + const LIBINT2_REF_REALTYPE c2 = obs[s2].contr[0].coeff[p2]; + const LIBINT2_REF_REALTYPE c3 = obs[s3].contr[0].coeff[p3]; + const LIBINT2_REF_REALTYPE c0123 = c0 * c1 * c2 * c3; + + auto eri_drrrr = [&](der_idx d_rrrr) { + return eri(d_rrrr.data(), l0, m0, n0, alpha0, Aref, l1, + m1, n1, alpha1, Bref, l2, m2, n2, alpha2, Cref, + l3, m3, n3, alpha3, Dref, 0); + }; + + // (LL|SS) + ref_coulomb_opop[0] += + c0123 * + (eri_drrrr(d_xx) + eri_drrrr(d_yy) + eri_drrrr(d_zz)); + ref_coulomb_opop[1] += + c0123 * (eri_drrrr(d_yz) - eri_drrrr(d_zy)); + ref_coulomb_opop[2] += + c0123 * (eri_drrrr(d_zx) - eri_drrrr(d_xz)); + ref_coulomb_opop[3] += + c0123 * (eri_drrrr(d_xy) - eri_drrrr(d_yx)); + + // (SS|SS) + ref_opop_coulomb_opop[0] += + c0123 * + (eri_drrrr(xxxx) + eri_drrrr(yyxx) + eri_drrrr(zzxx) - + eri_drrrr(yxyx) + eri_drrrr(xyyx) + eri_drrrr(yxxy) - + eri_drrrr(xyxy) + eri_drrrr(xxyy) + eri_drrrr(yyyy) + + eri_drrrr(zzyy) + eri_drrrr(xxzz) + eri_drrrr(yyzz) + + eri_drrrr(zzzz)); + ref_opop_coulomb_opop[1] += + c0123 * + (eri_drrrr(zxzx) - eri_drrrr(xzzx) - eri_drrrr(zyzy) + + eri_drrrr(yzzy) - eri_drrrr(zxxz) + eri_drrrr(xzxz) + + eri_drrrr(zyyz) - eri_drrrr(yzyz)); + ref_opop_coulomb_opop[2] += + c0123 * + (-eri_drrrr(zyzx) + eri_drrrr(yzzx) - eri_drrrr(zxzy) + + eri_drrrr(xzzy) + eri_drrrr(zyxz) - eri_drrrr(yzxz) + + eri_drrrr(zxyz) - eri_drrrr(xzyz)); + ref_opop_coulomb_opop[3] += + c0123 * + (-eri_drrrr(yxxx) + eri_drrrr(xyxx) - eri_drrrr(xxyx) - + eri_drrrr(yyyx) - eri_drrrr(zzyx) + eri_drrrr(xxxy) + + eri_drrrr(yyxy) + eri_drrrr(zzxy) - eri_drrrr(yxyy) + + eri_drrrr(xyyy) - eri_drrrr(yxzz) + eri_drrrr(xyzz)); } } } + } - const double ABSOLUTE_DEVIATION_THRESHOLD = 5.0E-14; - const double RELATIVE_DEVIATION_THRESHOLD = - 1.0E-9; // For more detail on choice of these thresholds, see - // the comments in the TEST_CASE "eri geometric - // derivatives" - - std::array abs_errs_llss; - std::array rel_abs_errs_llss; - - std::array abs_errs_ssss; - std::array rel_abs_errs_ssss; - - for (auto comp = 0; comp < 4; ++comp) { - abs_errs_llss[comp] = - abs(ref_coulomb_opop[comp] - results_llss[comp][ijkl]); - rel_abs_errs_llss[comp] = - abs(abs_errs_llss[comp] / ref_coulomb_opop[comp]); - - abs_errs_ssss[comp] = - abs(ref_opop_coulomb_opop[comp] - results_ssss[comp][ijkl]); - rel_abs_errs_ssss[comp] = - abs(abs_errs_ssss[comp] / ref_opop_coulomb_opop[comp]); - - bool llss_not_ok = - rel_abs_errs_llss[comp] > RELATIVE_DEVIATION_THRESHOLD && - abs_errs_llss[comp] > ABSOLUTE_DEVIATION_THRESHOLD; - - bool ssss_not_ok = - rel_abs_errs_ssss[comp] > RELATIVE_DEVIATION_THRESHOLD && - abs_errs_ssss[comp] > ABSOLUTE_DEVIATION_THRESHOLD; - - // no 3^n prefactor here since the intrinsic deriv order is 2 - if (llss_not_ok) { - std::cout << "(l0 l1| l2 l3) = " - << "(" << s0 << " " << s1 << " | " << s2 << " " - << s3 << ") " - << "Elem " << ijkl << " comp= " << comp - << " : ref = " << ref_coulomb_opop[comp] - << " libint = " << results_llss[comp][ijkl] - << " relabs_error = " << rel_abs_errs_llss[comp] - << " abs_error = " << abs_errs_llss[comp] - << std::endl; - } - if (ssss_not_ok) { - std::cout << "(l0 l1| l2 l3) = " - << "(" << s0 << " " << s1 << " | " << s2 << " " - << s3 << ") " - << "Elem " << ijkl << " comp= " << comp - << " : ref = " << ref_opop_coulomb_opop[comp] - << " libint = " << results_ssss[comp][ijkl] - << " relabs_error = " << rel_abs_errs_ssss[comp] - << " abs_error = " << abs_errs_ssss[comp] - << std::endl; - } - REQUIRE(!llss_not_ok); - REQUIRE(!ssss_not_ok); + const double ABSOLUTE_DEVIATION_THRESHOLD = 5.0E-14; + const double RELATIVE_DEVIATION_THRESHOLD = + 1.0E-9; // For more detail on choice of these thresholds, see + // the comments in the TEST_CASE "eri geometric + // derivatives" + + std::array abs_errs_llss; + std::array rel_abs_errs_llss; + + std::array abs_errs_ssss; + std::array rel_abs_errs_ssss; + + for (auto comp = 0; comp < 4; ++comp) { + abs_errs_llss[comp] = + abs(ref_coulomb_opop[comp] - results_llss[comp][ijkl]); + rel_abs_errs_llss[comp] = + abs(abs_errs_llss[comp] / ref_coulomb_opop[comp]); + + abs_errs_ssss[comp] = + abs(ref_opop_coulomb_opop[comp] - results_ssss[comp][ijkl]); + rel_abs_errs_ssss[comp] = + abs(abs_errs_ssss[comp] / ref_opop_coulomb_opop[comp]); + + bool llss_not_ok = + rel_abs_errs_llss[comp] > RELATIVE_DEVIATION_THRESHOLD && + abs_errs_llss[comp] > ABSOLUTE_DEVIATION_THRESHOLD; + + bool ssss_not_ok = + rel_abs_errs_ssss[comp] > RELATIVE_DEVIATION_THRESHOLD && + abs_errs_ssss[comp] > ABSOLUTE_DEVIATION_THRESHOLD; + + // no 3^n prefactor here since the intrinsic deriv order is 2 + if (llss_not_ok) { + std::cout << "(l0 l1| l2 l3) = " + << "(" << s0 << " " << s1 << " | " << s2 << " " << s3 + << ") " + << "Elem " << ijkl << " comp= " << comp + << " : ref = " << ref_coulomb_opop[comp] + << " libint = " << results_llss[comp][ijkl] + << " relabs_error = " << rel_abs_errs_llss[comp] + << " abs_error = " << abs_errs_llss[comp] + << std::endl; } - - ++ijkl; - END_FOR_CART - END_FOR_CART - END_FOR_CART - END_FOR_CART + if (ssss_not_ok) { + std::cout << "(l0 l1| l2 l3) = " + << "(" << s0 << " " << s1 << " | " << s2 << " " << s3 + << ") " + << "Elem " << ijkl << " comp= " << comp + << " : ref = " << ref_opop_coulomb_opop[comp] + << " libint = " << results_ssss[comp][ijkl] + << " relabs_error = " << rel_abs_errs_ssss[comp] + << " abs_error = " << abs_errs_ssss[comp] + << std::endl; + } + REQUIRE(!llss_not_ok); + REQUIRE(!ssss_not_ok); } + + ++ijkl; + END_FOR_CART + END_FOR_CART + END_FOR_CART + END_FOR_CART } } } diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 4bf4bf383..c2a9de6cd 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -1178,8 +1178,8 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, } } - // unroll only if max_am <= cparams->max_am_opt(task) using - // std::max; + // unroll only if max_am <= cparams->max_am_opt(task) + using std::max; const unsigned int max_am = max(max(la, lb), max(lc, ld)); const bool need_to_optimize = (max_am <= cparams->max_am_opt(task)); const auto nopers = descrs.size(); @@ -1262,7 +1262,6 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, abcd_label = abcd->label(); } else { std::ostringstream oss; - oss << cparams->api_prefix(); oss << "_" << a.label() << "_" << b.label(); oss << "_" << label; oss << "_" << c.label() << "_" << d.label(); @@ -1272,7 +1271,7 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, // + derivative level (if deriv_level > 0) std::string eval_label; { - eval_label = cparams->api_prefix(); + eval_label = ""; if (deriv_level != 0) { std::ostringstream oss; oss << "deriv" << deriv_level; @@ -1380,9 +1379,9 @@ void build_TwoPRep_1b_2k(std::ostream& os, LIBINT_SHELL_SET)>::value(lbra, lc, ld)) continue; - // I will use 4-center recurrence relations and integrals, and have - // one center carry an s function unfortunately, depending on the - // direction in which the build goes it must be A(0) or B(1) + // I will use 4-center recurrence relations and integrals, and have one + // center carry an s function unfortunately, depending on the direction + // in which the build goes it must be A(0) or B(1) const unsigned int dummy_center = (LIBINT_SHELL_SET == LIBINT_SHELL_SET_ORCA) ? 0 : 1; @@ -1581,8 +1580,8 @@ void build_TwoPRep_1b_1k(std::ostream& os, const unsigned int dummy_center2 = (LIBINT_SHELL_SET == LIBINT_SHELL_SET_ORCA) ? 2 : 3; - // std::shared_ptr tactic(new ParticleDirectionTactic(lbra > - // lket ? false : true)); + // std::shared_ptr tactic(new ParticleDirectionTactic(lbra > lket + // ? false : true)); std::shared_ptr tactic(new FourCenter_OS_Tactic( dummy_center1 == 0 ? 0 : lbra, dummy_center1 == 1 ? 0 : lbra, dummy_center2 == 2 ? 0 : lket, dummy_center2 == 3 ? 0 : lket)); From 921da582fe6c830bcd142f778d313ad5132a2c04 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sat, 21 Mar 2026 14:00:30 -0400 Subject: [PATCH 14/64] Optimize RKB integral code generation: braket symmetry + disable CSE + progress bar + sign fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ShellQuartetSetPredicate: add braket-swap tiebreaker for bra_ket_coswappable operators (σpσpCoulombσpσp). When la+lb == lc+ld, use max(la,lb) <= lc to pick one canonical representative, reducing duplicate quartet generation. - Engine (engine.impl.h): update swap_braket logic for opop_coulomb_opop to match the new predicate tiebreaker. Add coupled-swap sign correction in the swap_braket branch (was missing — exposed by d-shell testing). - build_libint.cc: disable CSE (do_cse/condense_expr) for multi-component operators since their 16 components share no intermediates at the expression level. This eliminates the superlinear optimize_rr_out bottleneck (e.g., 8.8s → 71ms for (ss|ds) prerequisite DAG). - build_libint.cc: fix compilation when only LIBINT_INCLUDE_RKB_ERI is defined (without LIBINT_INCLUDE_ERI): extend #ifdef guards for build_TwoPRep_2b_2k, add forward declaration, move make_descr to detail namespace, use if constexpr for component descriptor construction. - buildtest.h: add CodeGenProgress spinner showing elapsed time, function count, and current task name on stderr during code generation. - int_am.cmake: fix typo in OPT_AM variable reference. --- cmake/modules/int_am.cmake | 2 +- export/tests/unit/test-2body.cc | 126 ++++++--- include/libint2/engine.h | 4 +- include/libint2/engine.impl.h | 117 ++++++-- src/bin/libint/build_libint.cc | 101 ++++--- src/bin/libint/buildtest.h | 50 +++- ...3p\317\203pCoulomb\317\203p\317\203p_11.h" | 259 ++++++++++++++---- src/bin/libint/oper.h | 31 ++- 8 files changed, 522 insertions(+), 168 deletions(-) diff --git a/cmake/modules/int_am.cmake b/cmake/modules/int_am.cmake index cc86b7aa7..350924f49 100644 --- a/cmake/modules/int_am.cmake +++ b/cmake/modules/int_am.cmake @@ -262,7 +262,7 @@ macro(process_integrals_class class) if (LIBINT2_${class}_OPT_AM EQUAL -1) set(LIBINT_${class}_OPT_AM "") else() - set($LIBINT_{class}_OPT_AM ${LIBINT2_${class}_OPT_AM}) + set(LIBINT_${class}_OPT_AM ${LIBINT2_${class}_OPT_AM}) endif() endif() if (LIBINT_OPT_AM_LIST) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 2f0383fc1..708190b90 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -439,7 +439,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { const auto &results_ssss = engine_ssss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); assert(results_llss.size() == - 4); // we get 4 buffers for each quaternion component + 4); // 4 buffers for single-spin quaternion components LIBINT2_REF_REALTYPE Aref[3]; for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; @@ -466,8 +466,8 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { std::array ref_coulomb_opop{0.0, 0.0, 0.0, 0.0}; - std::array ref_opop_coulomb_opop{0.0, 0.0, - 0.0, 0.0}; + std::array ref_opop_coulomb_opop{}; + ref_opop_coulomb_opop.fill(0.0); uint p0123 = 0; for (uint p0 = 0; p0 < obs[s0].nprim(); p0++) { for (uint p1 = 0; p1 < obs[s1].nprim(); p1++) { @@ -490,6 +490,22 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { l3, m3, n3, alpha3, Dref, 0); }; + // helper: build der_idx from 4 derivative directions + // (0=x, 1=y, 2=z) for centers A, B, C, D + constexpr int X = 0, Y = 1, Z = 2; + auto didx = [](int a, int b, int c, int d) -> der_idx { + der_idx r = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + r[a] = 1; + r[3 + b] = 1; + r[6 + c] = 1; + r[9 + d] = 1; + return r; + }; + // shorthand: evaluate derivative ERI from 4 directions + auto D = [&](int a, int b, int c, int d) { + return eri_drrrr(didx(a, b, c, d)); + }; + // (LL|SS) ref_coulomb_opop[0] += c0123 * @@ -501,30 +517,60 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { ref_coulomb_opop[3] += c0123 * (eri_drrrr(d_xy) - eri_drrrr(d_yx)); - // (SS|SS) + // (SS|SS) — 16 components, Option A: index = 4*bra + ket + // 0:SS 1:SX 2:SY 3:SZ ref_opop_coulomb_opop[0] += - c0123 * - (eri_drrrr(xxxx) + eri_drrrr(yyxx) + eri_drrrr(zzxx) - - eri_drrrr(yxyx) + eri_drrrr(xyyx) + eri_drrrr(yxxy) - - eri_drrrr(xyxy) + eri_drrrr(xxyy) + eri_drrrr(yyyy) + - eri_drrrr(zzyy) + eri_drrrr(xxzz) + eri_drrrr(yyzz) + - eri_drrrr(zzzz)); + c0123 * (D(X, X, X, X) + D(X, X, Y, Y) + D(X, X, Z, Z) + + D(Y, Y, X, X) + D(Y, Y, Y, Y) + D(Y, Y, Z, Z) + + D(Z, Z, X, X) + D(Z, Z, Y, Y) + D(Z, Z, Z, Z)); ref_opop_coulomb_opop[1] += - c0123 * - (eri_drrrr(zxzx) - eri_drrrr(xzzx) - eri_drrrr(zyzy) + - eri_drrrr(yzzy) - eri_drrrr(zxxz) + eri_drrrr(xzxz) + - eri_drrrr(zyyz) - eri_drrrr(yzyz)); + c0123 * (D(X, X, Y, Z) - D(X, X, Z, Y) + D(Y, Y, Y, Z) - + D(Y, Y, Z, Y) + D(Z, Z, Y, Z) - D(Z, Z, Z, Y)); ref_opop_coulomb_opop[2] += - c0123 * - (-eri_drrrr(zyzx) + eri_drrrr(yzzx) - eri_drrrr(zxzy) + - eri_drrrr(xzzy) + eri_drrrr(zyxz) - eri_drrrr(yzxz) + - eri_drrrr(zxyz) - eri_drrrr(xzyz)); + c0123 * (D(X, X, Z, X) - D(X, X, X, Z) + D(Y, Y, Z, X) - + D(Y, Y, X, Z) + D(Z, Z, Z, X) - D(Z, Z, X, Z)); ref_opop_coulomb_opop[3] += - c0123 * - (-eri_drrrr(yxxx) + eri_drrrr(xyxx) - eri_drrrr(xxyx) - - eri_drrrr(yyyx) - eri_drrrr(zzyx) + eri_drrrr(xxxy) + - eri_drrrr(yyxy) + eri_drrrr(zzxy) - eri_drrrr(yxyy) + - eri_drrrr(xyyy) - eri_drrrr(yxzz) + eri_drrrr(xyzz)); + c0123 * (D(X, X, X, Y) - D(X, X, Y, X) + D(Y, Y, X, Y) - + D(Y, Y, Y, X) + D(Z, Z, X, Y) - D(Z, Z, Y, X)); + // 4:XS 5:XX 6:XY 7:XZ + ref_opop_coulomb_opop[4] += + c0123 * (D(Y, Z, X, X) - D(Z, Y, X, X) + D(Y, Z, Y, Y) - + D(Z, Y, Y, Y) + D(Y, Z, Z, Z) - D(Z, Y, Z, Z)); + ref_opop_coulomb_opop[5] += + c0123 * (-D(Y, Z, Y, Z) + D(Y, Z, Z, Y) + + D(Z, Y, Y, Z) - D(Z, Y, Z, Y)); + ref_opop_coulomb_opop[6] += + c0123 * (-D(Y, Z, Z, X) + D(Y, Z, X, Z) + + D(Z, Y, Z, X) - D(Z, Y, X, Z)); + ref_opop_coulomb_opop[7] += + c0123 * (-D(Y, Z, X, Y) + D(Y, Z, Y, X) + + D(Z, Y, X, Y) - D(Z, Y, Y, X)); + // 8:YS 9:YX 10:YY 11:YZ + ref_opop_coulomb_opop[8] += + c0123 * (D(Z, X, X, X) - D(X, Z, X, X) + D(Z, X, Y, Y) - + D(X, Z, Y, Y) + D(Z, X, Z, Z) - D(X, Z, Z, Z)); + ref_opop_coulomb_opop[9] += + c0123 * (-D(Z, X, Y, Z) + D(Z, X, Z, Y) + + D(X, Z, Y, Z) - D(X, Z, Z, Y)); + ref_opop_coulomb_opop[10] += + c0123 * (-D(Z, X, Z, X) + D(Z, X, X, Z) + + D(X, Z, Z, X) - D(X, Z, X, Z)); + ref_opop_coulomb_opop[11] += + c0123 * (-D(Z, X, X, Y) + D(Z, X, Y, X) + + D(X, Z, X, Y) - D(X, Z, Y, X)); + // 12:ZS 13:ZX 14:ZY 15:ZZ + ref_opop_coulomb_opop[12] += + c0123 * (D(X, Y, X, X) - D(Y, X, X, X) + D(X, Y, Y, Y) - + D(Y, X, Y, Y) + D(X, Y, Z, Z) - D(Y, X, Z, Z)); + ref_opop_coulomb_opop[13] += + c0123 * (-D(X, Y, Y, Z) + D(X, Y, Z, Y) + + D(Y, X, Y, Z) - D(Y, X, Z, Y)); + ref_opop_coulomb_opop[14] += + c0123 * (-D(X, Y, Z, X) + D(X, Y, X, Z) + + D(Y, X, Z, X) - D(Y, X, X, Z)); + ref_opop_coulomb_opop[15] += + c0123 * (-D(X, Y, X, Y) + D(X, Y, Y, X) + + D(Y, X, X, Y) - D(Y, X, Y, X)); } } } @@ -539,29 +585,17 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { std::array abs_errs_llss; std::array rel_abs_errs_llss; - std::array abs_errs_ssss; - std::array rel_abs_errs_ssss; - + // (LL|SS) has 4 components for (auto comp = 0; comp < 4; ++comp) { abs_errs_llss[comp] = abs(ref_coulomb_opop[comp] - results_llss[comp][ijkl]); rel_abs_errs_llss[comp] = abs(abs_errs_llss[comp] / ref_coulomb_opop[comp]); - abs_errs_ssss[comp] = - abs(ref_opop_coulomb_opop[comp] - results_ssss[comp][ijkl]); - rel_abs_errs_ssss[comp] = - abs(abs_errs_ssss[comp] / ref_opop_coulomb_opop[comp]); - bool llss_not_ok = rel_abs_errs_llss[comp] > RELATIVE_DEVIATION_THRESHOLD && abs_errs_llss[comp] > ABSOLUTE_DEVIATION_THRESHOLD; - bool ssss_not_ok = - rel_abs_errs_ssss[comp] > RELATIVE_DEVIATION_THRESHOLD && - abs_errs_ssss[comp] > ABSOLUTE_DEVIATION_THRESHOLD; - - // no 3^n prefactor here since the intrinsic deriv order is 2 if (llss_not_ok) { std::cout << "(l0 l1| l2 l3) = " << "(" << s0 << " " << s1 << " | " << s2 << " " << s3 @@ -573,6 +607,20 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { << " abs_error = " << abs_errs_llss[comp] << std::endl; } + REQUIRE(!llss_not_ok); + } + + // (SS|SS) has 16 components (two independent spin spaces) + for (auto comp = 0; comp < 16; ++comp) { + auto abs_err_ssss = + abs(ref_opop_coulomb_opop[comp] - results_ssss[comp][ijkl]); + auto rel_abs_err_ssss = + abs(abs_err_ssss / ref_opop_coulomb_opop[comp]); + + bool ssss_not_ok = + rel_abs_err_ssss > RELATIVE_DEVIATION_THRESHOLD && + abs_err_ssss > ABSOLUTE_DEVIATION_THRESHOLD; + if (ssss_not_ok) { std::cout << "(l0 l1| l2 l3) = " << "(" << s0 << " " << s1 << " | " << s2 << " " << s3 @@ -580,11 +628,9 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { << "Elem " << ijkl << " comp= " << comp << " : ref = " << ref_opop_coulomb_opop[comp] << " libint = " << results_ssss[comp][ijkl] - << " relabs_error = " << rel_abs_errs_ssss[comp] - << " abs_error = " << abs_errs_ssss[comp] - << std::endl; + << " relabs_error = " << rel_abs_err_ssss + << " abs_error = " << abs_err_ssss << std::endl; } - REQUIRE(!llss_not_ok); REQUIRE(!ssss_not_ok); } diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 94077da63..fae7d1785 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -364,7 +364,9 @@ struct operator_traits template <> struct operator_traits : public operator_traits { - static constexpr auto nopers = 4; + /// 16 components: tensor product of two independent spin-space quaternions + /// index = 4 * bra_spin + ket_spin, where spin in {S=0, X=1, Y=2, Z=3} + static constexpr auto nopers = 16; static constexpr auto intrinsic_deriv_order = 4; }; diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index a39244e7e..ff143d254 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -1216,17 +1216,39 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( #if LIBINT2_SHELLQUARTET_SET == \ LIBINT2_SHELLQUARTET_SET_STANDARD // standard angular momentum ordering - const auto swap_braket = ((braket_ == BraKet::xx_xx) && - (tbra1.contr[0].l + tbra2.contr[0].l > - tket1.contr[0].l + tket2.contr[0].l) && - (oper_ != Operator::coulomb_opop)) || - braket_ == BraKet::xx_xs; + bool swap_braket; bool swap_tbra, swap_tket; if (oper_ == Operator::opop_coulomb_opop) { - bool swap_p1p2 = swap_braket ? (tbra1.contr[0].l < tbra2.contr[0].l) - : (tket1.contr[0].l < tket2.contr[0].l); - swap_tbra = swap_tket = swap_p1p2; + // For σpσpCoulombσpσp: (ab|cd) = (cd|ab) = (ba|dc)* = (dc|ba)* + // Canonical form: lc >= ld (or la >= lb when lc == ld), + // la+lb <= lc+ld (or max(la,lb) <= lc when sums equal) + const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; + const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; + const auto bra_max = std::max(tbra1.contr[0].l, tbra2.contr[0].l); + const auto ket_max = std::max(tket1.contr[0].l, tket2.contr[0].l); + swap_braket = ((braket_ == BraKet::xx_xx) && + (bra_total > ket_total || + (bra_total == ket_total && bra_max > ket_max))) || + braket_ == BraKet::xx_xs; + // Coupled swap: after braket swap, sort the pair that ends up in ket + // position to ensure lc >= ld; when lc == ld, also sort bra (la >= lb) + if (swap_braket) { + // After braket swap: new ket = original bra, new bra = original ket. + // Coupled swap sorts new ket (ensure lc >= ld). + const bool swap_p1p2 = (tbra1.contr[0].l < tbra2.contr[0].l); + swap_tbra = swap_tket = swap_p1p2; + } else { + // No braket swap: ket stays as original ket. + // Coupled swap sorts ket (ensure lc >= ld). + const bool swap_p1p2 = (tket1.contr[0].l < tket2.contr[0].l); + swap_tbra = swap_tket = swap_p1p2; + } } else { + swap_braket = ((braket_ == BraKet::xx_xx) && + (tbra1.contr[0].l + tbra2.contr[0].l > + tket1.contr[0].l + tket2.contr[0].l) && + (oper_ != Operator::coulomb_opop)) || + braket_ == BraKet::xx_xs; swap_tbra = (tbra1.contr[0].l < tbra2.contr[0].l); swap_tket = (tket1.contr[0].l < tket2.contr[0].l); } @@ -1234,15 +1256,35 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( // N.B. cannot swap bra and ket for coulomb_opop since the ket is mutated by // this operator #else // orca angular momentum ordering - const auto swap_tbra = (tbra1.contr[0].l > tbra2.contr[0].l); - const auto swap_tket = (tket1.contr[0].l > tket2.contr[0].l); - const auto swap_braket = ((braket_ == BraKet::xx_xx) && - (tbra1.contr[0].l + tbra2.contr[0].l < - tket1.contr[0].l + tket2.contr[0].l) && - (oper_ != Operator::coulomb_opop)) || - braket_ == BraKet::xx_xs; - assert(false && "feature not implemented"); - abort(); + bool swap_braket; + bool swap_tbra, swap_tket; + if (oper_ == Operator::opop_coulomb_opop) { + // ORCA canonical for σpσpCoulombσpσp: lc <= ld (or la <= lb when lc == ld), + // la+lb >= lc+ld (or min(la,lb) >= lc when sums equal) + const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; + const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; + const auto bra_min = std::min(tbra1.contr[0].l, tbra2.contr[0].l); + const auto ket_min = std::min(tket1.contr[0].l, tket2.contr[0].l); + swap_braket = ((braket_ == BraKet::xx_xx) && + (bra_total < ket_total || + (bra_total == ket_total && bra_min < ket_min))) || + braket_ == BraKet::xx_xs; + if (swap_braket) { + const bool swap_p1p2 = (tbra1.contr[0].l > tbra2.contr[0].l); + swap_tbra = swap_tket = swap_p1p2; + } else { + const bool swap_p1p2 = (tket1.contr[0].l > tket2.contr[0].l); + swap_tbra = swap_tket = swap_p1p2; + } + } else { + swap_tbra = (tbra1.contr[0].l > tbra2.contr[0].l); + swap_tket = (tket1.contr[0].l > tket2.contr[0].l); + swap_braket = ((braket_ == BraKet::xx_xx) && + (tbra1.contr[0].l + tbra2.contr[0].l < + tket1.contr[0].l + tket2.contr[0].l) && + (oper_ != Operator::coulomb_opop)) || + braket_ == BraKet::xx_xs; + } #endif const auto& bra1 = swap_braket ? (swap_tket ? tket2 : tket1) : (swap_tbra ? tbra2 : tbra1); @@ -2124,10 +2166,21 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( tgt_ptr + tgt_col_idx, nr1_tgt, nr2_tgt, Eigen::Stride( nr2_tgt * ncol_tgt, ncol_tgt)); + // Coupled swap sign correction for multi-component operators + Shell::real_t oper_cart_component_phase = 1.0; + if (swap_tket && oper_ == Operator::opop_coulomb_opop) { + const bool bra_is_spin = (s / 4) > 0; + const bool ket_is_spin = (s % 4) > 0; + if (bra_is_spin != ket_is_spin) + oper_cart_component_phase = -1.0; + } + if (swap_tket && oper_ == Operator::coulomb_opop && s > 0) + oper_cart_component_phase = -1.0; if (swap_tbra) - tgt_blk_mat = src_blk_mat.transpose(); + tgt_blk_mat = + oper_cart_component_phase * src_blk_mat.transpose(); else - tgt_blk_mat = src_blk_mat; + tgt_blk_mat = oper_cart_component_phase * src_blk_mat; } else { // source row {r1,r2} is mapped to target row {r1,r2} if // !swap_tbra, else to {r2,r1} @@ -2136,10 +2189,16 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( Map tgt_blk_mat(tgt_ptr + tgt_row_idx * ncol, nc1_tgt, nc2_tgt); if (swap_tket) { Shell::real_t oper_cart_component_phase = 1.0; - if (oper_ == Operator::opop_coulomb_opop && s == 3) - oper_cart_component_phase = - -1.0; // z quaternion components flip sign on - // swapping ket + if (oper_ == Operator::opop_coulomb_opop) { + // Option A ordering: index = 4*bra + ket + // Coupled swap (a<->b AND c<->d) flips sign when exactly + // one of bra/ket is a cross product (spin != S): + // bra_spin = s/4, ket_spin = s%4 (0=S, 1-3=X/Y/Z) + const bool bra_is_spin = (s / 4) > 0; + const bool ket_is_spin = (s % 4) > 0; + if (bra_is_spin != ket_is_spin) + oper_cart_component_phase = -1.0; + } if (oper_ == Operator::coulomb_opop && s > 0) oper_cart_component_phase = -1.0; // x,y,z quaternion components flip sign on @@ -2171,7 +2230,17 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( // to primdata_[0].targets targets_[s_target] = source; } - } // loop over shellsets + } // loop over shellsets + + // For opop_coulomb_opop with swap_braket: swapping particles remaps + // component (α,β) → (β,α). With Option A ordering (index=4*bra+ket), + // this is a matrix transpose: s_new = 4*(s%4) + (s/4). + if (permute && oper_ == Operator::opop_coulomb_opop && swap_braket) { + std::array temp; + for (auto s = 0; s != ntargets; ++s) temp[s] = targets_[s]; + for (auto s = 0; s != ntargets; ++s) + targets_[4 * (s % 4) + (s / 4)] = temp[s]; + } } // if need_scratch => needed to transpose and/or tform else { // did not use scratch? may still need to update targets_ if (set_targets_) { diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index c2a9de6cd..4e9b10152 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -31,7 +31,9 @@ */ #include +#include #include +#include #include #include #include @@ -66,6 +68,8 @@ using namespace std; using namespace libint2; +CodeGenProgress g_progress; + enum ShellSetType { ShellSetType_Standard = LIBINT_SHELL_SET_STANDARD, ShellSetType_ORCA = LIBINT_SHELL_SET_ORCA @@ -90,7 +94,8 @@ struct ShellQuartetSetPredicate { static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true, bool bra_ket_coswappable = false) { if (bra_ket_coswappable) - return (la + lb <= lc + ld) && lc >= ld; + return lc >= ld && (la + lb < lc + ld || + (la + lb == lc + ld && std::max(la, lb) <= lc)); else return la >= lb && lc >= ld && (!p1p2_swappable || la + lb <= lc + ld); } @@ -100,7 +105,8 @@ struct ShellQuartetSetPredicate { static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true, bool bra_ket_coswappable = false) { if (bra_ket_coswappable) - return (la < lc || (la == lc && lb <= ld)); + return lc <= ld && (la + lb > lc + ld || + (la + lb == lc + ld && std::min(la, lb) >= lc)); else return la <= lb && lc <= ld && (!p1p2_swappable || (la < lc || (la == lc && lb <= ld))); @@ -211,7 +217,9 @@ static void config_to_api(const std::shared_ptr& cparams, #ifdef LIBINT_INCLUDE_ERI #define USE_GENERIC_ERI_BUILD 1 -#if !USE_GENERIC_ERI_BUILD +#endif +#if defined(LIBINT_INCLUDE_ERI) || defined(LIBINT_INCLUDE_RKB_ERI) +#if defined(USE_GENERIC_ERI_BUILD) && !USE_GENERIC_ERI_BUILD template static void build_TwoPRep_2b_2k( std::ostream& os, std::string label, @@ -222,7 +230,7 @@ template static void build_TwoPRep_2b_2k( std::ostream& os, std::string label, const std::shared_ptr& cparams, - std::shared_ptr& iface, unsigned int deriv_level); + std::shared_ptr& iface, unsigned int deriv_level = 0); #endif #endif @@ -272,6 +280,8 @@ struct AuxQuantaType { typedef EmptySet type; }; +} // namespace + template OperDescrType make_descr(int, int = 0, int = 0) { return OperDescrType(); @@ -308,8 +318,6 @@ template <> return σpσpCoulombσpσp_Descr(p); } -} // namespace - template void build_onebody_1b_1k(std::ostream& os, std::string label, const std::shared_ptr& cparams, @@ -514,8 +522,8 @@ void build_onebody_1b_1k(std::ostream& os, std::string label, eval_label = oss.str(); } - std::cout << "working on " << eval_label << " ... "; - std::cout.flush(); + g_progress.current_task = eval_label; + g_progress.print(); std::string prefix(cparams->source_directory()); std::deque decl_filenames; @@ -555,8 +563,6 @@ void build_onebody_1b_1k(std::ostream& os, std::string label, dg->reset(); memman->reset(); - std::cout << "done" << std::endl; - } // end of b loop } // end of a loop } @@ -962,6 +968,8 @@ void try_main(int argc, char* argv[]) { #endif cparams->print(os); + g_progress.start(); + #ifdef LIBINT_INCLUDE_ONEBODY for (unsigned int d = 0; d <= LIBINT_INCLUDE_ONEBODY; ++d) { #define BOOST_PP_ONEBODY_MCR7(r, data, i, elem) \ @@ -1020,6 +1028,8 @@ void try_main(int argc, char* argv[]) { build_G12DKH_2b_2k(os, cparams, iface); #endif + g_progress.finish(); + // Generate code for the set-level RRs std::deque decl_filenames, def_filenames; generate_rr_code(os, cparams, decl_filenames, def_filenames); @@ -1101,12 +1111,12 @@ void print_config(std::ostream& os) { #endif } -#ifdef LIBINT_INCLUDE_ERI +#if defined(LIBINT_INCLUDE_ERI) || defined(LIBINT_INCLUDE_RKB_ERI) template -void build_TwoPRep_2b_2k(std::ostream& os, std::string label, - const std::shared_ptr& cparams, - std::shared_ptr& iface, - unsigned int deriv_level) { +static void build_TwoPRep_2b_2k( + std::ostream& os, std::string label, + const std::shared_ptr& cparams, + std::shared_ptr& iface, unsigned int deriv_level) { typedef GenIntegralSet_11_11 TwoBody_sh_11_11; typedef typename OperType::Descriptor OperDescrType; @@ -1168,13 +1178,20 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, // loop over operator components ///////////////////////////////// std::vector descrs(1); - if (std::is_same::value || - std::is_same::value) { + if constexpr (std::is_same::value) { // reset descriptors array descrs.resize(0); - // iterate over quaternion components + // iterate over 4 quaternion components (single spin space) for (int p = 0; p != 4; ++p) { - descrs.emplace_back(make_descr(p)); + descrs.emplace_back(OperDescrType(p)); + } + } + if constexpr (std::is_same::value) { + // reset descriptors array + descrs.resize(0); + // iterate over 16 components (tensor product of two spin spaces) + for (int p = 0; p != 16; ++p) { + descrs.emplace_back(OperDescrType(p)); } } @@ -1192,10 +1209,14 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, ? std::numeric_limits::max() : 0; dg_xxxx->registry()->unroll_threshold(unroll_threshold); - dg_xxxx->registry()->do_cse(need_to_optimize); - dg_xxxx->registry()->condense_expr(condense_expr( - cparams->unroll_threshold(), cparams->max_vector_length() > 1)); - // dg_xxxx->registry()->condense_expr(true); + // For multi-component operators (RKB), components share no + // intermediates, so CSE/condense_expr is pure overhead — disable. + const bool do_optimize = (nopers > 1) ? false : need_to_optimize; + dg_xxxx->registry()->do_cse(do_optimize); + dg_xxxx->registry()->condense_expr( + do_optimize ? condense_expr(cparams->unroll_threshold(), + cparams->max_vector_length() > 1) + : false); // Need to accumulate integrals? dg_xxxx->registry()->accumulate_targets( cparams->accumulate_targets()); @@ -1238,13 +1259,6 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, last_deriv = diter.last(); if (!last_deriv) diter.next(); } while (!last_deriv); - // append all derivatives as targets to the graph - for (auto it = targets.begin(); it != targets.end(); ++it) { - std::shared_ptr t_ptr = - std::dynamic_pointer_cast(*it); - dg_xxxx->append_target(t_ptr); - } - // make label that characterizes this set of targets // use the label of the nondifferentiated integral as a base std::string abcd_label; @@ -1280,13 +1294,20 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, eval_label += abcd_label; } - std::cout << "working on " << eval_label << " ... "; - std::cout.flush(); + g_progress.current_task = eval_label; + g_progress.print(); std::string prefix(cparams->source_directory()); std::deque decl_filenames; std::deque def_filenames; + // append all targets to the graph + for (auto it = targets.begin(); it != targets.end(); ++it) { + std::shared_ptr t_ptr = + std::dynamic_pointer_cast(*it); + dg_xxxx->append_target(t_ptr); + } + // this will generate code for these targets, and potentially // generate code for its prerequisites GenerateCode(dg_xxxx, context, cparams, strat, tactic, memman, @@ -1322,7 +1343,8 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, dg_xxxx->reset(); memman->reset(); - std::cout << "done" << std::endl; + ++g_progress.done; + g_progress.print(); } // end of d loop } // end of c loop @@ -1330,7 +1352,7 @@ void build_TwoPRep_2b_2k(std::ostream& os, std::string label, } // end of a loop } -#endif // LIBINT_INCLUDE_ERI +#endif // LIBINT_INCLUDE_ERI || LIBINT_INCLUDE_RKB_ERI #ifdef LIBINT_INCLUDE_ERI3 @@ -1490,8 +1512,8 @@ void build_TwoPRep_1b_2k(std::ostream& os, label += abcd_label; } - std::cout << "working on " << label << " ... "; - std::cout.flush(); + g_progress.current_task = label; + g_progress.print(); std::string prefix(cparams->source_directory()); std::deque decl_filenames; @@ -1530,7 +1552,8 @@ void build_TwoPRep_1b_2k(std::ostream& os, #endif dg_xxx->reset(); memman->reset(); - std::cout << "done" << std::endl; + ++g_progress.done; + g_progress.print(); } // end of d loop } // end of c loop } // end of bra loop @@ -1687,7 +1710,8 @@ void build_TwoPRep_1b_1k(std::ostream& os, label += abcd_label; } - std::cout << "working on " << label << " ... "; + g_progress.current_task = label; + g_progress.print(); std::cout.flush(); std::string prefix(cparams->source_directory()); @@ -1727,7 +1751,6 @@ void build_TwoPRep_1b_1k(std::ostream& os, #endif dg_xxx->reset(); memman->reset(); - std::cout << "done" << std::endl; } // end of ket loop } // end of bra loop } diff --git a/src/bin/libint/buildtest.h b/src/bin/libint/buildtest.h index a4923c022..0ea5b571e 100644 --- a/src/bin/libint/buildtest.h +++ b/src/bin/libint/buildtest.h @@ -30,13 +30,57 @@ #include #include +#include #include #include +#include #include #include #include #include +/// Progress tracker for code generation. +struct CodeGenProgress { + unsigned int done = 0; + std::string current_task; + std::chrono::steady_clock::time_point start_time; + bool started = false; + + void start() { + start_time = std::chrono::steady_clock::now(); + started = true; + } + + void print() const { + if (!started) return; + static const char spinner[] = "|/-\\"; + const auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time) + .count(); + const auto mins = elapsed / 60; + const auto secs = elapsed % 60; + std::cerr << "\r " << spinner[done % 4] << " " << std::setfill('0') + << std::setw(2) << mins << ":" << std::setw(2) << secs + << std::setfill(' ') << " [" << done << " functions generated] " + << current_task << " " << std::flush; + } + + void finish() { + if (!started) return; + const auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time) + .count(); + const auto mins = elapsed / 60; + const auto secs = elapsed % 60; + std::cerr << "\r done " << std::setfill('0') << std::setw(2) << mins + << ":" << std::setw(2) << secs << std::setfill(' ') << " [" + << done << " functions generated]" + << " " << std::endl; + started = false; + } +}; +extern CodeGenProgress g_progress; + namespace libint2 { // defined in buildtest.cc @@ -263,7 +307,6 @@ void GenerateCode(const std::shared_ptr& dg, // if there are missing prerequisites -- make a list of them PrerequisitesExtractor pe; if (dg->missing_prerequisites()) { - // std::cout << "missing some prerequisites!" << std::endl; dg->foreach (pe); } std::deque > prereq_list = pe.vertices; @@ -296,6 +339,11 @@ void GenerateCode(const std::shared_ptr& dg, // extract all external symbols extract_symbols(dg); + // Update progress + ++g_progress.done; + g_progress.current_task = label; + g_progress.print(); + #if PRINT_DAG_GRAPHVIZ { std::basic_ofstream dotfile(dg->label() + ".symb.dot"); diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" index 7bf0a4b9a..606418d0b 100644 --- "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -28,10 +28,38 @@ namespace libint2 { /** - * this computes integral of - * \sigma \cdot \hat{p}_1 \sigma \cdot \hat{p}_2 \f$ \frac{1}{r_{ij}} \sigma - * \cdot \hat{p}_3 \sigma \cdot \hat{p}_4 \f$ over CGShell/CGF by rewriting it - * as a linear combination of integrals over derivatives of \frac{1}{r_{ij}} + * Computes integral of + * \f$ (\sigma_1 \cdot \hat{p}_a)(\sigma_1 \cdot \hat{p}_b) + * \frac{1}{r_{12}} + * (\sigma_2 \cdot \hat{p}_c)(\sigma_2 \cdot \hat{p}_d) \f$ + * over CGShell/CGF by rewriting it as a linear combination of integrals + * over derivatives of \f$ \frac{1}{r_{12}} \f$. + * + * The two sigma operators act on independent spin spaces (electron 1 and + * electron 2). Using the Dirac identity (see e.g. Eq. 1.27 of I. P. Grant, + * "Relativistic Quantum Theory of Atoms and Molecules", Springer, 2007): + * \f$ (\sigma \cdot a)(\sigma \cdot b) = (a \cdot b)I + * + i\sigma \cdot (a \times b) \f$ + * applied independently to each particle's spin space gives a tensor product + * of two quaternions with \f$ 4 \times 4 = 16 \f$ components: + * + * index = 4 * bra_spin_index + ket_spin_index + * + * where spin indices are: 0=S (scalar/dot product), 1=X, 2=Y, 3=Z + * (cross product components). + * + * The 16 components map to: + * T1 (index 0): SS = (a.b)(c.d) [scalar x scalar] + * T2 (indices 1-3): SX,SY,SZ = (a.b)(cxd)_{x,y,z} [scalar x spin] + * T3 (indices 4-6): XS,YS,ZS = (axb)_{x,y,z}(c.d) [spin x scalar] + * T4 (indices 7-15): XX..ZZ = -(axb)_i(cxd)_j [spin x spin] + * + * Sign convention: T4 components include the minus sign from \f$ i^2 = -1 \f$, + * arising from the product of two \f$ i \f$ factors in the Dirac identity: + * \f$ [i\sigma_1 \cdot (a \times b)] \otimes [i\sigma_2 \cdot (c \times d)] + * = -\sigma_{1,i} \otimes \sigma_{2,j}\; (a \times b)_i\, (c \times d)_j + * \f$ + * * @tparam F basis function type. valid choices are CGShell or CGF */ template @@ -113,81 +141,216 @@ CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11( return factory.make_child(a_r1, b_r2, c_r3, d_r4, zero_m); }; - // Component wise generation for quaternion : - // ( (σ.p) a (σ.p) b | 1/r12 | (σ.p) c (σ.p) d ) + // 16-component generation for two independent spin spaces: + // ( (σ₁.p) a (σ₁.p) b | 1/r12 | (σ₂.p) c (σ₂.p) d ) + // + // Option A (tensor product) ordering: index = 4 * bra_spin + ket_spin + // bra_spin = index / 4, ket_spin = index % 4 + // spin indices: S=0, X=1, Y=2, Z=3 + // + // Row bra=S: 0=SS, 1=SX, 2=SY, 3=SZ (T1 + T2) + // Row bra=X: 4=XS, 5=XX, 6=XY, 7=XZ (T3 + T4) + // Row bra=Y: 8=YS, 9=YX, 10=YY, 11=YZ (T3 + T4) + // Row bra=Z: 12=ZS, 13=ZX, 14=ZY, 15=ZZ (T3 + T4) + // + // T4 components include minus sign from i^2 = -1. switch (oper->descr().quaternion_index()) { + // ===== 0: SS = (a.b)(c.d) ===== case 0: { auto xxxx = mc(x, x, x, x); - auto yyxx = mc(y, y, x, x); - auto zzxx = mc(z, z, x, x); - auto yxyx = mc(y, x, y, x); - auto xyyx = mc(x, y, y, x); - auto yxxy = mc(y, x, x, y); - auto xyxy = mc(x, y, x, y); auto xxyy = mc(x, x, y, y); - auto yyyy = mc(y, y, y, y); - auto zzyy = mc(z, z, y, y); auto xxzz = mc(x, x, z, z); + auto yyxx = mc(y, y, x, x); + auto yyyy = mc(y, y, y, y); auto yyzz = mc(y, y, z, z); + auto zzxx = mc(z, z, x, x); + auto zzyy = mc(z, z, y, y); auto zzzz = mc(z, z, z, z); if (is_simple()) { - expr_ = xxxx + yyxx + zzxx - yxyx + xyyx + yxxy - xyxy + xxyy + yyyy + - zzyy + xxzz + yyzz + zzzz; - nflops_ += 12; + expr_ = xxxx + xxyy + xxzz + yyxx + yyyy + yyzz + zzxx + zzyy + zzzz; + nflops_ += 8; } } break; + // ===== 1: SX = (a.b)(c×d)_x ===== case 1: { - auto zxzx = mc(z, x, z, x); - auto xzzx = mc(x, z, z, x); - auto zyzy = mc(z, y, z, y); + auto xxyz = mc(x, x, y, z); + auto xxzy = mc(x, x, z, y); + auto yyyz = mc(y, y, y, z); + auto yyzy = mc(y, y, z, y); + auto zzyz = mc(z, z, y, z); + auto zzzy = mc(z, z, z, y); + if (is_simple()) { + expr_ = xxyz - xxzy + yyyz - yyzy + zzyz - zzzy; + nflops_ += 5; + } + } break; + // ===== 2: SY = (a.b)(c×d)_y ===== + case 2: { + auto xxzx = mc(x, x, z, x); + auto xxxz = mc(x, x, x, z); + auto yyzx = mc(y, y, z, x); + auto yyxz = mc(y, y, x, z); + auto zzzx = mc(z, z, z, x); + auto zzxz = mc(z, z, x, z); + if (is_simple()) { + expr_ = xxzx - xxxz + yyzx - yyxz + zzzx - zzxz; + nflops_ += 5; + } + } break; + // ===== 3: SZ = (a.b)(c×d)_z ===== + case 3: { + auto xxxy = mc(x, x, x, y); + auto xxyx = mc(x, x, y, x); + auto yyxy = mc(y, y, x, y); + auto yyyx = mc(y, y, y, x); + auto zzxy = mc(z, z, x, y); + auto zzyx = mc(z, z, y, x); + if (is_simple()) { + expr_ = xxxy - xxyx + yyxy - yyyx + zzxy - zzyx; + nflops_ += 5; + } + } break; + // ===== 4: XS = (a×b)_x(c.d) ===== + case 4: { + auto yzxx = mc(y, z, x, x); + auto zyxx = mc(z, y, x, x); + auto yzyy = mc(y, z, y, y); + auto zyyy = mc(z, y, y, y); + auto yzzz = mc(y, z, z, z); + auto zyzz = mc(z, y, z, z); + if (is_simple()) { + expr_ = yzxx - zyxx + yzyy - zyyy + yzzz - zyzz; + nflops_ += 5; + } + } break; + // ===== 5: XX = -(a×b)_x(c×d)_x (minus from i²=-1) ===== + case 5: { + auto yzyz = mc(y, z, y, z); auto yzzy = mc(y, z, z, y); - auto zxxz = mc(z, x, x, z); - auto xzxz = mc(x, z, x, z); auto zyyz = mc(z, y, y, z); - auto yzyz = mc(y, z, y, z); + auto zyzy = mc(z, y, z, y); if (is_simple()) { - expr_ = zxzx - xzzx - zyzy + yzzy - zxxz + xzxz + zyyz - yzyz; - nflops_ += 7; + expr_ = yzzy - yzyz + zyyz - zyzy; + nflops_ += 3; } } break; - case 2: { - auto zyzx = mc(z, y, z, x); + // ===== 6: XY = -(a×b)_x(c×d)_y ===== + case 6: { auto yzzx = mc(y, z, z, x); - auto zxzy = mc(z, x, z, y); - auto xzzy = mc(x, z, z, y); - auto zyxz = mc(z, y, x, z); auto yzxz = mc(y, z, x, z); + auto zyzx = mc(z, y, z, x); + auto zyxz = mc(z, y, x, z); + if (is_simple()) { + expr_ = yzxz - yzzx + zyzx - zyxz; + nflops_ += 3; + } + } break; + // ===== 7: XZ = -(a×b)_x(c×d)_z ===== + case 7: { + auto yzxy = mc(y, z, x, y); + auto yzyx = mc(y, z, y, x); + auto zyxy = mc(z, y, x, y); + auto zyyx = mc(z, y, y, x); + if (is_simple()) { + expr_ = yzyx - yzxy + zyxy - zyyx; + nflops_ += 3; + } + } break; + // ===== 8: YS = (a×b)_y(c.d) ===== + case 8: { + auto zxxx = mc(z, x, x, x); + auto xzxx = mc(x, z, x, x); + auto zxyy = mc(z, x, y, y); + auto xzyy = mc(x, z, y, y); + auto zxzz = mc(z, x, z, z); + auto xzzz = mc(x, z, z, z); + if (is_simple()) { + expr_ = zxxx - xzxx + zxyy - xzyy + zxzz - xzzz; + nflops_ += 5; + } + } break; + // ===== 9: YX = -(a×b)_y(c×d)_x ===== + case 9: { auto zxyz = mc(z, x, y, z); + auto zxzy = mc(z, x, z, y); auto xzyz = mc(x, z, y, z); + auto xzzy = mc(x, z, z, y); if (is_simple()) { - // swapped order of first two terms compiler does not like negative sign - // in front of first term - expr_ = yzzx - zyzx - zxzy + xzzy + zyxz - yzxz + zxyz - xzyz; - nflops_ += 7; + expr_ = zxzy - zxyz + xzyz - xzzy; + nflops_ += 3; } } break; - case 3: { - auto yxxx = mc(y, x, x, x); + // ===== 10: YY = -(a×b)_y(c×d)_y ===== + case 10: { + auto zxzx = mc(z, x, z, x); + auto zxxz = mc(z, x, x, z); + auto xzzx = mc(x, z, z, x); + auto xzxz = mc(x, z, x, z); + if (is_simple()) { + expr_ = zxxz - zxzx + xzzx - xzxz; + nflops_ += 3; + } + } break; + // ===== 11: YZ = -(a×b)_y(c×d)_z ===== + case 11: { + auto zxxy = mc(z, x, x, y); + auto zxyx = mc(z, x, y, x); + auto xzxy = mc(x, z, x, y); + auto xzyx = mc(x, z, y, x); + if (is_simple()) { + expr_ = zxyx - zxxy + xzxy - xzyx; + nflops_ += 3; + } + } break; + // ===== 12: ZS = (a×b)_z(c.d) ===== + case 12: { auto xyxx = mc(x, y, x, x); - auto xxyx = mc(x, x, y, x); - auto yyyx = mc(y, y, y, x); - auto zzyx = mc(z, z, y, x); - auto xxxy = mc(x, x, x, y); - auto yyxy = mc(y, y, x, y); - auto zzxy = mc(z, z, x, y); - auto yxyy = mc(y, x, y, y); + auto yxxx = mc(y, x, x, x); auto xyyy = mc(x, y, y, y); - auto yxzz = mc(y, x, z, z); + auto yxyy = mc(y, x, y, y); auto xyzz = mc(x, y, z, z); + auto yxzz = mc(y, x, z, z); + if (is_simple()) { + expr_ = xyxx - yxxx + xyyy - yxyy + xyzz - yxzz; + nflops_ += 5; + } + } break; + // ===== 13: ZX = -(a×b)_z(c×d)_x ===== + case 13: { + auto xyyz = mc(x, y, y, z); + auto xyzy = mc(x, y, z, y); + auto yxyz = mc(y, x, y, z); + auto yxzy = mc(y, x, z, y); + if (is_simple()) { + expr_ = xyzy - xyyz + yxyz - yxzy; + nflops_ += 3; + } + } break; + // ===== 14: ZY = -(a×b)_z(c×d)_y ===== + case 14: { + auto xyzx = mc(x, y, z, x); + auto xyxz = mc(x, y, x, z); + auto yxzx = mc(y, x, z, x); + auto yxxz = mc(y, x, x, z); + if (is_simple()) { + expr_ = xyxz - xyzx + yxzx - yxxz; + nflops_ += 3; + } + } break; + // ===== 15: ZZ = -(a×b)_z(c×d)_z ===== + case 15: { + auto xyxy = mc(x, y, x, y); + auto xyyx = mc(x, y, y, x); + auto yxxy = mc(y, x, x, y); + auto yxyx = mc(y, x, y, x); if (is_simple()) { - expr_ = xyxx - yxxx - xxyx - yyyx - zzyx + xxxy + yyxy + zzxy - yxyy + - xyyy - yxzz + xyzz; - nflops_ += 11; + expr_ = xyyx - xyxy + yxxy - yxyx; + nflops_ += 3; } } break; default: throw std::runtime_error( - "CR_11_σpσpCoulombσpσp_11: invalid quaternionic index"); + "CR_11_σpσpCoulombσpσp_11: invalid component index (expected 0-15)"); } } // CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11 diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index cecbda72b..678180c28 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -443,24 +443,27 @@ struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr σpσpCoulombσpσp_Descr() : quaternion_index_(0) {} σpσpCoulombσpσp_Descr(int quaternion_index) : quaternion_index_(quaternion_index) { - assert(quaternion_index <= 3); + assert(quaternion_index >= 0 && quaternion_index <= 15); } - static const unsigned int max_key = 4; + /// 16 components from tensor product of two independent spin spaces: + /// index = 4 * bra_spin_index + ket_spin_index + /// where spin indices are: 0=S (scalar), 1=X, 2=Y, 3=Z (cross product) + static const unsigned int max_key = 16; unsigned int key() const { return quaternion_index(); } std::string description() const { - std::string descr("opop_coulomb_opop["); - if (quaternion_index() == 0) - descr += "0"; - else if (quaternion_index() == 1) - descr += "X"; - else if (quaternion_index() == 2) - descr += "Y"; - else if (quaternion_index() == 3) - descr += "Z"; - else - abort(); - return descr + "]"; + // clang-format off + // Option A (tensor product order): index = 4 * bra_spin + ket_spin + static const char* labels[] = { + "SS", "SX", "SY", "SZ", + "XS", "XX", "XY", "XZ", + "YS", "YX", "YY", "YZ", + "ZS", "ZX", "ZY", "ZZ" + }; + // clang-format on + const auto qi = quaternion_index(); + if (qi > 15) abort(); + return std::string("opop_coulomb_opop[") + labels[qi] + "]"; } std::string label() const { return description(); } int psymm(int i, int j) const { abort(); } From 98d7d375a0e9bd040a14236f3e9a79a798bd42e1 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Thu, 2 Apr 2026 19:28:27 -0400 Subject: [PATCH 15/64] DerivGaussV2: HRR-like code-sharing for derivative Gaussians Apply the same optimization as HRR: since differentiation of a Gaussian depends only on that shell's quanta (not spectators), generate code once per unique differentiated shell and pass spectator dims at the call site. Verified: 295,944 assertions pass (Coulomb + ERI derivs + RKB integrals). --- src/bin/libint/comp_deriv_gauss_v2.h | 547 +++++++++++++++++++++++++++ src/bin/libint/master_rrs_list.h | 46 +++ src/bin/libint/strategy.cc | 62 +-- 3 files changed, 631 insertions(+), 24 deletions(-) create mode 100644 src/bin/libint/comp_deriv_gauss_v2.h diff --git a/src/bin/libint/comp_deriv_gauss_v2.h b/src/bin/libint/comp_deriv_gauss_v2.h new file mode 100644 index 000000000..970bab0ae --- /dev/null +++ b/src/bin/libint/comp_deriv_gauss_v2.h @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2004-2026 Edward F. Valeev + * + * This file is part of Libint compiler. + * + * Libint compiler 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 compiler 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 compiler. If not, see . + * + */ + +#ifndef _libint2_src_bin_libint_compderivgaussv2_h_ +#define _libint2_src_bin_libint_compderivgaussv2_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace libint2 { + +/** Optimized compute relation for (geometric) derivative Gaussian integrals. + * + * Like CR_DerivGauss, this expands derivative Gaussians via: + * d/dr G(a) = 2*alpha * G(a+1) - a_i * G(a-1) + * + * Unlike CR_DerivGauss, this uses the HRR-like code-sharing optimization: + * since differentiation of a Gaussian at position (part, where) depends only + * on that shell's quanta (not spectator shells), we generate code once per + * unique differentiated shell and pass spectator dimensions at the call site. + * + * @tparam IntType integral type + * @tparam part particle index of the function to be differentiated + * @tparam where position of the function to be differentiated (InBra/InKet) + * @tparam trans_inv_part if non-negative, specifies the particle index for + * translational invariance + * @tparam trans_inv_where position for translational invariance + */ +template +class DerivGaussV2 : public RecurrenceRelation { + private: + static constexpr auto trans_inv_oper = + not IntType::OperType::Properties::odep; + static constexpr auto using_trans_inv = + trans_inv_oper && (part == trans_inv_part) && (where == trans_inv_where); + + public: + typedef RecurrenceRelation ParentType; + typedef typename IntType::BasisFunctionType BasisFunctionType; + typedef DerivGaussV2 ThisType; + typedef IntType TargetType; + typedef IntType ChildType; + typedef RecurrenceRelation::ExprType ExprType; + + static const unsigned int max_nchildren_ = + using_trans_inv ? (IntType::num_bf - 1) : 2u; + + static std::shared_ptr Instance( + const std::shared_ptr& Tint, unsigned int dir = 0); + virtual ~DerivGaussV2() {} + + /// always directional + static bool directional() { return true; } + + unsigned int num_children() const override { return nchildren_; } + std::shared_ptr rr_target() const override { + return std::static_pointer_cast(target_); + } + std::shared_ptr rr_child(unsigned int i) const override { + return children_.at(i); + } + bool is_simple() const override { + return TrivialBFSet::result; + } + + std::string spfunction_call( + const std::shared_ptr& context, + const std::shared_ptr& dims) const override; + + private: + DerivGaussV2(const std::shared_ptr& Tint, unsigned int dir); + + unsigned int dir_; + std::shared_ptr target_; + std::vector> children_; + unsigned int nchildren_; + + std::string generate_label() const override; + std::shared_ptr adapt_dims_( + const std::shared_ptr& dims) const override; + bool register_with_rrstack() const; + bool expl_high_dim() const; + bool expl_low_dim() const; + + /// add child, deduplicating + const std::shared_ptr& add_child( + const std::shared_ptr& child) { + for (auto& c : children_) { + if (c == child) return c; + } + children_.push_back(child); + ++nchildren_; + return children_.back(); + } +}; + +// +// Implementation +// + +template +std::shared_ptr< + DerivGaussV2> +DerivGaussV2::Instance( + const std::shared_ptr& Tint, unsigned int dir) { + std::shared_ptr this_ptr(new ThisType(Tint, dir)); + if (this_ptr->num_children() != 0) { + this_ptr->register_with_rrstack(); + return this_ptr; + } + return std::shared_ptr(); +} + +template +DerivGaussV2:: + DerivGaussV2(const std::shared_ptr& Tint, unsigned int dir) + : dir_(dir), target_(Tint), nchildren_(0) { + using namespace libint2::algebra; + using namespace libint2::prefactor; + using namespace libint2::braket; + typedef BasisFunctionType F; + const F& _1 = unit(is_simple() ? dir : 0); + + const typename IntType::AuxQuantaType& aux = Tint->aux(); + const typename IntType::OperType& oper = Tint->oper(); + + children_.reserve(max_nchildren_); + + // the Gaussian must be differentiated in direction dir + { + if (where == InBra && Tint->bra(part, 0).deriv().d(dir) == 0) return; + if (where == InKet && Tint->ket(part, 0).deriv().d(dir) == 0) return; + } + + // if not using translational invariance, can only expand primitives + if (not using_trans_inv) { + if (where == InBra && Tint->bra(part, 0).contracted()) return; + if (where == InKet && Tint->ket(part, 0).contracted()) return; + } + + typedef typename IntType::BraType IBraType; + typedef typename IntType::KetType IKetType; + IBraType* bra = new IBraType(Tint->bra()); + IKetType* ket = new IKetType(Tint->ket()); + + if (not using_trans_inv) { // differentiate + + if (where == InBra) { + F a(bra->member(part, 0)); + + // add a+1 + F ap1(bra->member(part, 0) + _1); + ap1.deriv().dec(dir); + bra->set_member(ap1, part, 0); + auto int_ap1 = add_child(IntType::Instance(*bra, *ket, aux, oper)); + bra->set_member(a, part, 0); + if (is_simple()) { + std::ostringstream oss; + oss << "two_alpha" << part << "_bra"; + expr_ = Scalar(oss.str()) * int_ap1; + nflops_ += 1; + } + + // See if a-1 exists + F am1(bra->member(part, 0) - _1); + if (exists(am1)) { + am1.deriv().dec(dir); + bra->set_member(am1, part, 0); + auto int_am1 = add_child(IntType::Instance(*bra, *ket, aux, oper)); + bra->set_member(a, part, 0); + if (is_simple()) { + expr_ -= Scalar(a[dir]) * int_am1; + nflops_ += 2; + } + } + delete bra; + delete ket; + return; + } + + if (where == InKet) { + F a(ket->member(part, 0)); + + // add a+1 + F ap1(ket->member(part, 0) + _1); + ap1.deriv().dec(dir); + ket->set_member(ap1, part, 0); + auto int_ap1 = add_child(IntType::Instance(*bra, *ket, aux, oper)); + ket->set_member(a, part, 0); + if (is_simple()) { + std::ostringstream oss; + oss << "two_alpha" << part << "_ket"; + expr_ = Scalar(oss.str()) * int_ap1; + nflops_ += 1; + } + + // See if a-1 exists + F am1(ket->member(part, 0) - _1); + if (exists(am1)) { + am1.deriv().dec(dir); + ket->set_member(am1, part, 0); + auto int_am1 = add_child(IntType::Instance(*bra, *ket, aux, oper)); + ket->set_member(a, part, 0); + if (is_simple()) { + expr_ -= Scalar(a[dir]) * int_am1; + nflops_ += 2; + } + } + delete bra; + delete ket; + return; + } + + } else { // use translational invariance + + // remove one deriv quantum from the target function + if (where == InBra) bra->member(part, 0).deriv().dec(dir); + if (where == InKet) ket->member(part, 0).deriv().dec(dir); + + int term_count = 0; + for (int p = 0; p != IntType::num_particles; ++p) { + typedef BasisFunctionType F; + if (p != trans_inv_part || trans_inv_where != InBra) { + F a(bra->member(p, 0)); + if (not a.is_unit()) { + F da(a); + da.deriv().inc(dir); + bra->set_member(da, p, 0); + auto int_da = add_child(IntType::Instance(*bra, *ket, aux, oper)); + bra->set_member(a, p, 0); + if (is_simple()) { + if (term_count == 0) + expr_ = Scalar(-1) * int_da; + else + expr_ -= int_da; + ++term_count; + nflops_ += 1; + } + } + } + if (p != trans_inv_part || trans_inv_where != InKet) { + F a(ket->member(p, 0)); + if (not a.is_unit()) { + F da(a); + da.deriv().inc(dir); + ket->set_member(da, p, 0); + auto int_da = add_child(IntType::Instance(*bra, *ket, aux, oper)); + ket->set_member(a, p, 0); + if (is_simple()) { + if (term_count == 0) + expr_ = Scalar(-1) * int_da; + else + expr_ -= int_da; + ++term_count; + nflops_ += 1; + } + } + } + } + } + + delete bra; + delete ket; +} + +template +bool DerivGaussV2::register_with_rrstack() const { + using std::swap; + + // only register RRs for shell sets (not individual integrals) + if (TrivialBFSet::result) return false; + + // translational invariance path not optimized yet — register as-is + if (using_trans_inv) { + std::shared_ptr rrstack = RRStack::Instance(); + std::shared_ptr this_ptr = + std::const_pointer_cast( + std::static_pointer_cast( + std::enable_shared_from_this::shared_from_this())); + rrstack->find(this_ptr); + return true; + } + + typedef typename IntType::BraType IBraType; + typedef typename IntType::KetType IKetType; + const IBraType& bra = target_->bra(); + const IKetType& ket = target_->ket(); + + // check if all spectator shells already have zero quanta + bool nonzero_quanta = false; + unsigned const int npart = IntType::OperatorType::Properties::np; + for (unsigned int p = 0; p < npart; p++) { + int nfbra = bra.num_members(p); + for (int f = 0; f < nfbra; f++) { + // skip the differentiated position + if (static_cast(p) == part && where == InBra) continue; + if (!bra.member(p, f).zero() || !bra.member(p, f).deriv().zero()) + nonzero_quanta = true; + } + int nfket = ket.num_members(p); + for (int f = 0; f < nfket; f++) { + if (static_cast(p) == part && where == InKet) continue; + if (!ket.member(p, f).zero() || !ket.member(p, f).deriv().zero()) + nonzero_quanta = true; + } + } + + // if all spectators are zero, register this instance directly + if (!nonzero_quanta) { + std::shared_ptr rrstack = RRStack::Instance(); + std::shared_ptr this_ptr = + std::const_pointer_cast( + std::static_pointer_cast( + std::enable_shared_from_this::shared_from_this())); + rrstack->find(this_ptr); + return true; + } + + // Otherwise, zero out all spectator shells and register a dummy + IBraType bra_zero(bra); + IKetType ket_zero(ket); + for (unsigned int p = 0; p < npart; p++) { + int nfbra = bra_zero.num_members(p); + for (int f = 0; f < nfbra; f++) { + if (static_cast(p) == part && where == InBra) continue; + typedef typename IBraType::bfs_type bfs_type; + typedef typename IBraType::bfs_ref bfs_ref; + bfs_ref bfs = bra_zero.member(p, f); + if (!bfs.zero() || !bfs.deriv().zero()) { + bfs_type null_bfs; + swap(bfs, null_bfs); + } + } + int nfket = ket_zero.num_members(p); + for (int f = 0; f < nfket; f++) { + if (static_cast(p) == part && where == InKet) continue; + typedef typename IKetType::bfs_type bfs_type; + typedef typename IKetType::bfs_ref bfs_ref; + bfs_ref bfs = ket_zero.member(p, f); + if (!bfs.zero() || !bfs.deriv().zero()) { + bfs_type null_bfs; + swap(bfs, null_bfs); + } + } + } + + // create a generic integral with a dummy operator + typedef GenOper> + DummyOper; + typedef EmptySet DummyQuanta; + typedef GenIntegralSet + DummyIntegral; + DummyOper dummy_oper; + DummyQuanta dummy_quanta(std::vector(0, 0)); + std::shared_ptr dummy_integral = + DummyIntegral::Instance(bra_zero, ket_zero, dummy_quanta, dummy_oper); + + // construct a DerivGaussV2 over the dummy integral and register it + typedef DerivGaussV2 DummyDerivGaussV2; + std::shared_ptr dummy_rr = + DummyDerivGaussV2::Instance(dummy_integral, dir_); + std::shared_ptr rrstack = RRStack::Instance(); + rrstack->find(dummy_rr); + return true; +} + +template +std::string DerivGaussV2::generate_label() const { + std::ostringstream os; + + // For translational invariance, children depend on ALL shells, so + // the label must include full integral info (no code sharing). + // For direct differentiation, only the differentiated shell matters. + if constexpr (using_trans_inv) { + typedef typename TargetType::AuxIndexType mType; + static std::shared_ptr aux0(new mType(0u)); + os << "CR_DerivGauss" + << "P" << part << to_string(where) + << genintegralset_label(target_->bra(), target_->ket(), aux0, + target_->oper()); + return os.str(); + } + + os << "DerivGaussV2 P" << part << " " << to_string(where) << " "; + + // Only encode the differentiated shell — not spectators + if (where == InBra) { + BasisFunctionType sh(target_->bra(part, 0)); + sh.uncontract(); + os << sh.label(); + } else { + BasisFunctionType sh(target_->ket(part, 0)); + sh.uncontract(); + os << sh.label(); + } + + return os.str(); +} + +template +std::string +DerivGaussV2:: + spfunction_call(const std::shared_ptr& context, + const std::shared_ptr& dims) const { + std::ostringstream os; + os << context->label_to_function_name(label()) << "(inteval, " + << context->value_to_pointer(rr_target()->symbol()); + + const unsigned int nc = num_children(); + for (unsigned int c = 0; c < nc; c++) { + os << ", " << context->value_to_pointer(rr_child(c)->symbol()); + } + + // compute hsr and lsr — dimensions of spectator shells + // canonical order: for each particle p, bra then ket + // hsr = product of dims before (part, where) + // lsr = product of dims after (part, where) + unsigned int hsr = 1; + unsigned int lsr = 1; + const unsigned int np = IntType::OperType::Properties::np; + for (int p = 0; p < static_cast(np); p++) { + unsigned int nbra = target_->bra().num_members(p); + assert(nbra == 1); + for (unsigned int i = 0; i < nbra; i++) { + SubIterator* iter = target_->bra().member_subiter(p, i); + if (p < part || (p == part && where == InKet)) hsr *= iter->num_iter(); + // skip p == part && where == InBra (the differentiated shell) + if (p > part) lsr *= iter->num_iter(); + delete iter; + } + unsigned int nket = target_->ket().num_members(p); + assert(nket == 1); + for (unsigned int i = 0; i < nket; i++) { + SubIterator* iter = target_->ket().member_subiter(p, i); + if (p < part) hsr *= iter->num_iter(); + // skip p == part && where == InKet (the differentiated shell) + if (p > part || (p == part && where == InBra)) lsr *= iter->num_iter(); + delete iter; + } + } + + // Use TaskParameters to keep track of maximum ranks + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + taskmgr.current().params()->max_hrr_hsrank(hsr); + + if (expl_high_dim()) os << "," << hsr; + if (expl_low_dim()) os << "," << lsr; + os << ")" << context->end_of_stat() << std::endl; + return os.str(); +} + +template +bool DerivGaussV2::expl_high_dim() const { + // translational invariance: no code sharing, no explicit dims + if (using_trans_inv) return false; + // need explicit high dim unless this is the first position + if (part == 0 && where == InBra) return false; + return true; +} + +template +bool DerivGaussV2::expl_low_dim() const { + // translational invariance: no code sharing, no explicit dims + if (using_trans_inv) return false; + unsigned int np = IntType::OperType::Properties::np; + // need explicit low dim unless this is the last position + if (static_cast(np) - 1 == part && where == InKet) return false; + // corner case: 1-particle operator + if (np == 1) return true; + return true; +} + +template +std::shared_ptr +DerivGaussV2:: + adapt_dims_(const std::shared_ptr& dims) const { + bool high_rank = expl_high_dim(); + bool low_rank = expl_low_dim(); + + std::shared_ptr high_dim, low_dim; + if (high_rank) { + high_dim = + std::shared_ptr(new RTimeEntity("highdim")); + } else { + high_dim = dims->high(); + } + if (low_rank) { + low_dim = + std::shared_ptr(new RTimeEntity("lowdim")); + } else { + low_dim = dims->low(); + } + + std::shared_ptr localdims( + new ImplicitDimensions(high_dim, low_dim, dims->vecdim())); + return localdims; +} + +}; // namespace libint2 + +#endif diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index d55cfa301..5517d7c5c 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -268,6 +269,51 @@ typedef CR_DerivGauss Deriv_d_11_TwoPRep_11_int; +// DerivGaussV2 for TwoPRep (shell sets) +typedef DerivGaussV2 + DerivV2_a_11_TwoPRep_11_sh; +typedef DerivGaussV2 + DerivV2_b_11_TwoPRep_11_sh; +typedef DerivGaussV2 + DerivV2_c_11_TwoPRep_11_sh; +typedef DerivGaussV2 + DerivV2_d_11_TwoPRep_11_sh; +// DerivGaussV2 for TwoPRep (individual integrals) +typedef DerivGaussV2 + DerivV2_a_11_TwoPRep_11_int; +typedef DerivGaussV2 + DerivV2_b_11_TwoPRep_11_int; +typedef DerivGaussV2 + DerivV2_c_11_TwoPRep_11_int; +typedef DerivGaussV2 + DerivV2_d_11_TwoPRep_11_int; + +// DerivGaussV2 for DummySymmIntegral (used by register_with_rrstack) +typedef DerivGaussV2 + DerivV2_a_11_Dummy_11_sh; +typedef DerivGaussV2 + DerivV2_b_11_Dummy_11_sh; +typedef DerivGaussV2 + DerivV2_c_11_Dummy_11_sh; +typedef DerivGaussV2 + DerivV2_d_11_Dummy_11_sh; +typedef DerivGaussV2 + DerivV2_a_11_Dummy_11_int; +typedef DerivGaussV2 + DerivV2_b_11_Dummy_11_int; +typedef DerivGaussV2 + DerivV2_c_11_Dummy_11_int; +typedef DerivGaussV2 + DerivV2_d_11_Dummy_11_int; + typedef CR_11_Coulombσpσp_11 CR_11_Coulombσpσp_11_sh; typedef CR_11_Coulombσpσp_11 CR_11_Coulombσpσp_11_int; diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index 58fb8d2bd..4e4804da7 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -70,47 +70,51 @@ struct MasterStrategy; #if LIBINT_SHELLQUARTET_STRATEGY == LIBINT_SHELLQUARTET_STRATEGY_A0C0 template <> struct MasterStrategy { - typedef boost::mpl::list + VRR_a_11_TwoPRep_11_sh, VRR_c_11_TwoPRep_11_sh> value; }; template <> struct MasterStrategy { - typedef boost::mpl::list + VRR_a_11_TwoPRep_11_int, VRR_c_11_TwoPRep_11_int> value; }; #else // 0B0D strategy template <> struct MasterStrategy { - typedef boost::mpl::list + VRR_b_11_TwoPRep_11_sh, VRR_d_11_TwoPRep_11_sh> value; }; template <> struct MasterStrategy { - typedef boost::mpl::list + VRR_b_11_TwoPRep_11_int, VRR_d_11_TwoPRep_11_int> value; }; #endif @@ -214,21 +218,31 @@ struct MasterStrategy { #if LIBINT_SHELLQUARTET_STRATEGY == LIBINT_SHELLQUARTET_STRATEGY_A0C0 template <> struct MasterStrategy { - typedef boost::mpl::list value; + typedef boost::mpl::list + value; }; template <> struct MasterStrategy { - typedef boost::mpl::list + typedef boost::mpl::list value; }; #else // 0B0D strategy template <> struct MasterStrategy { - typedef boost::mpl::list value; + typedef boost::mpl::list + value; }; template <> struct MasterStrategy { - typedef boost::mpl::list + typedef boost::mpl::list value; }; #endif From 9b50b0b46010916acb168d8f6bf77b1d3b7fa070 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 10 Apr 2026 14:02:01 -0400 Subject: [PATCH 16/64] Fix DerivGaussV2 target indexing for size-1 integral sets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handle_trivial_nodes() used default_dims() (hardcoded "1") before adapt_dims_() provided correct runtime dims ("lowdim"/"highdim"). Pass localdims through optimize_rr_out → handle_trivial_nodes. --- src/bin/libint/dg.cc | 10 +++++----- src/bin/libint/dg.h | 9 +++++++-- src/bin/libint/rr.cc | 6 ++++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/bin/libint/dg.cc b/src/bin/libint/dg.cc index eded6a54f..9e03277ae 100644 --- a/src/bin/libint/dg.cc +++ b/src/bin/libint/dg.cc @@ -499,10 +499,11 @@ void DirectedGraph::apply_to(const std::shared_ptr& vertex, // Optimize out simple recurrence relations void DirectedGraph::optimize_rr_out( - const std::shared_ptr& context) { + const std::shared_ptr& context, + const std::shared_ptr& dims) { replace_rr_with_expr(); remove_trivial_arithmetics(); - handle_trivial_nodes(context); + handle_trivial_nodes(context, dims); remove_disconnected_vertices(); find_subtrees(); } @@ -797,7 +798,8 @@ inline std::string to_vector_symbol(const std::shared_ptr& v) { // refer to another node so that no code is generated for it. // void DirectedGraph::handle_trivial_nodes( - const std::shared_ptr& context) { + const std::shared_ptr& context, + const std::shared_ptr& dims) { typedef vertices::iterator iter; for (iter v = stack_.begin(); v != stack_.end(); ++v) { const ver_ptr& vptr = vertex_ptr(*v); @@ -821,8 +823,6 @@ void DirectedGraph::handle_trivial_nodes( // if (child->symbol_set() == false) { const std::string stack_name("stack"); - const std::shared_ptr& dims = - ImplicitDimensions::default_dims(); std::string low_rank = dims->low_label(); std::string veclen = dims->vecdim_label(); diff --git a/src/bin/libint/dg.h b/src/bin/libint/dg.h index 0f85abc8f..19e96074c 100644 --- a/src/bin/libint/dg.h +++ b/src/bin/libint/dg.h @@ -22,6 +22,7 @@ #define _libint2_src_bin_libint_dg_h_ #include +#include #include #include #include @@ -253,7 +254,9 @@ class DirectedGraph : public std::enable_shared_from_this { optimized away. optimize_rr_out() will replace all simple recurrence relations with code representing them. */ - void optimize_rr_out(const std::shared_ptr& context); + void optimize_rr_out(const std::shared_ptr& context, + const std::shared_ptr& dims = + ImplicitDimensions::default_dims()); /** after all apply's have been called, traverse() construct a heuristic order of traversal for the graph. @@ -438,7 +441,9 @@ class DirectedGraph : public std::enable_shared_from_this { to their equivalents (such as (ss|ss) shell quartet can only be connected to (ss|ss) integral) */ - void handle_trivial_nodes(const std::shared_ptr& context); + void handle_trivial_nodes(const std::shared_ptr& context, + const std::shared_ptr& dims = + ImplicitDimensions::default_dims()); /// This functions removes vertices not connected to other vertices void remove_disconnected_vertices(); /** Finds (binary) subtrees. The subtrees correspond to a single-line code (no diff --git a/src/bin/libint/rr.cc b/src/bin/libint/rr.cc index 32bbb9df9..a653bf4b2 100644 --- a/src/bin/libint/rr.cc +++ b/src/bin/libint/rr.cc @@ -127,8 +127,11 @@ void RecurrenceRelation::generate_code( // Assign symbols for the target and source integral sets std::shared_ptr symbols(new CodeSymbols); assign_symbols_(symbols); + // Compute local dimensions before optimize_rr_out so that + // handle_trivial_nodes uses the correct dims (e.g., "lowdim" instead of "1") + std::shared_ptr localdims = adapt_dims_(dims); // Traverse the graph - dg->optimize_rr_out(context); + dg->optimize_rr_out(context, localdims); dg->traverse(); #if PRINT_DAG_GRAPHVIZ { @@ -138,7 +141,6 @@ void RecurrenceRelation::generate_code( #endif // Generate code std::shared_ptr memman(new WorstFitMemoryManager()); - std::shared_ptr localdims = adapt_dims_(dims); dg->generate_code(context, memman, localdims, symbols, funcname, decl, def); // extract all external symbols -- these will be members of the evaluator From 1db978df470b632c98039c19484d2b77249ac696 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 10 Apr 2026 14:49:46 -0400 Subject: [PATCH 17/64] =?UTF-8?q?RKB=20CR=20code=20sharing:=20deduplicate?= =?UTF-8?q?=20generated=20Coulomb=CF=83p=CF=83p/=CF=83p=CF=83pCoulomb?= =?UTF-8?q?=CF=83p=CF=83p=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add code-sharing overrides (generate_label, spfunction_call, adapt_dims_, generate_code) to CR_11_Coulombσpσp_11 and CR_11_σpσpCoulombσpσp_11. Shell quartets with the same quaternion component share a single function parameterized by highdim. Hand-emits element-wise loops to avoid TwoPRep particle-swap child deduplication issues in DAG-based codegen. --- .../comp_11_Coulomb\317\203p\317\203p_11.h" | 103 ++++++++++++++- ...3p\317\203pCoulomb\317\203p\317\203p_11.h" | 120 +++++++++++++++++- 2 files changed, 215 insertions(+), 8 deletions(-) diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index 315135994..db28cb7ea 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -21,8 +21,11 @@ #ifndef LIBINT_COMP_11_COULOMBΣPΣP_11_H #define LIBINT_COMP_11_COULOMBΣPΣP_11_H +#include +#include #include #include +#include #include namespace libint2 { @@ -60,16 +63,108 @@ class CR_11_Coulombσpσp_11 using ParentType::RecurrenceRelation::expr_; using ParentType::RecurrenceRelation::nflops_; - /// Constructor is private, used by ParentType::Instance that maintains + /// Constructor is private, used by Instance that maintains /// registry of these objects - CR_11_Coulombσpσp_11(const std::shared_ptr &, unsigned int = 0); + CR_11_Coulombσpσp_11(const std::shared_ptr&, unsigned int = 0); static std::string descr() { return "CR"; } + + // --- Code sharing overrides --- + // All shell combos with the same quaternion component share one function. + + std::string generate_label() const override { + return "CR_Coulombopop_" + + std::to_string(target_->oper()->descr().quaternion_index()); + } + + std::string spfunction_call( + const std::shared_ptr& context, + const std::shared_ptr& dims) const override { + std::ostringstream os; + os << context->label_to_function_name(this->label()) << "(inteval, " + << context->value_to_pointer(this->rr_target()->symbol()); + const unsigned int nc = this->num_children(); + for (unsigned int c = 0; c < nc; c++) { + os << ", " << context->value_to_pointer(this->rr_child(c)->symbol()); + } + // total_dim = product of all shell dims (all 4 shells are spectators) + unsigned int total_dim = 1; + for (unsigned int p = 0; p < 2; p++) { + SubIterator* si = target_->bra().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + si = target_->ket().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + } + os << "," << total_dim; + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + taskmgr.current().params()->max_hrr_hsrank(total_dim); + os << ")" << context->end_of_stat() << std::endl; + return os.str(); + } + + std::shared_ptr adapt_dims_( + const std::shared_ptr& dims) const override { + auto high_dim = std::make_shared>("highdim"); + return std::make_shared(high_dim, dims->low(), + dims->vecdim()); + } + + /// Hand-emit a simple element-wise loop function. + /// comp 0: target = src0 + src1 + src2 (dot product) + /// comp 1-3: target = src0 - src1 (cross product components) + void generate_code(const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::string& funcname, std::ostream& decl, + std::ostream& def) override { + // declare_function lives in dg.cc + extern std::string declare_function( + const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::shared_ptr& args, const std::string& tlabel, + const std::string& function_descr, std::ostream& decl); + + std::shared_ptr localdims = adapt_dims_(dims); + // inline assign_symbols_: set symbol names on target/children and + // populate CodeSymbols + std::shared_ptr symbols(new CodeSymbols); + this->rr_target()->set_symbol("target"); + symbols->append_symbol("target"); + for (unsigned int c = 0; c < this->num_children(); c++) { + std::string symb = "src" + std::to_string(c); + this->rr_child(c)->set_symbol(symb); + symbols->append_symbol(symb); + } + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + const std::string tlabel = taskmgr.current().label(); + const std::string func_decl = + declare_function(context, localdims, symbols, tlabel, funcname, decl); + def << context->std_header(); + def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; + def << context->code_prefix(); + def << func_decl << context->open_block() << std::endl; + def << context->std_function_header(); + const unsigned int nc = this->num_children(); + def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; + def << "for(int hsi = 0; hsi 1) ? nc - 1 : 0; + def << "/** Number of flops = " << nflops << " */\n"; + def << context->close_block() << std::endl; + def << context->code_postfix(); + } }; template CR_11_Coulombσpσp_11::CR_11_Coulombσpσp_11( - const std::shared_ptr &Tint, unsigned int) + const std::shared_ptr& Tint, unsigned int) : ParentType(Tint, 0) { assert(Tint->num_func_bra(/* particle */ 0) == 1); assert(Tint->num_func_bra(/* particle */ 1) == 1); @@ -81,7 +176,7 @@ CR_11_Coulombσpσp_11::CR_11_Coulombσpσp_11( F c(Tint->bra(1, 0)); F d(Tint->ket(1, 0)); - const auto &oper = Tint->oper(); + const auto& oper = Tint->oper(); if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) return; diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" index 606418d0b..64d1fdee6 100644 --- "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -21,8 +21,11 @@ #ifndef LIBINT_COMP_11_ΣPΣPCOULOMBΣPΣP_11_H #define LIBINT_COMP_11_ΣPΣPCOULOMBΣPΣP_11_H +#include +#include #include #include +#include #include namespace libint2 { @@ -88,17 +91,126 @@ class CR_11_σpσpCoulombσpσp_11 using ParentType::RecurrenceRelation::expr_; using ParentType::RecurrenceRelation::nflops_; - /// Constructor is private, used by ParentType::Instance that maintains + /// Constructor is private, used by Instance that maintains /// registry of these objects - CR_11_σpσpCoulombσpσp_11(const std::shared_ptr &, + CR_11_σpσpCoulombσpσp_11(const std::shared_ptr&, unsigned int = 0); static std::string descr() { return "CR"; } + + // --- Code sharing overrides --- + // All shell combos with the same quaternion component share one function. + + std::string generate_label() const override { + return "CR_opopCoulombopop_" + + std::to_string(target_->oper()->descr().quaternion_index()); + } + + std::string spfunction_call( + const std::shared_ptr& context, + const std::shared_ptr& dims) const override { + std::ostringstream os; + os << context->label_to_function_name(this->label()) << "(inteval, " + << context->value_to_pointer(this->rr_target()->symbol()); + const unsigned int nc = this->num_children(); + for (unsigned int c = 0; c < nc; c++) { + os << ", " << context->value_to_pointer(this->rr_child(c)->symbol()); + } + // total_dim = product of all shell dims (all 4 shells are spectators) + unsigned int total_dim = 1; + for (unsigned int p = 0; p < 2; p++) { + SubIterator* si = target_->bra().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + si = target_->ket().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + } + os << "," << total_dim; + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + taskmgr.current().params()->max_hrr_hsrank(total_dim); + os << ")" << context->end_of_stat() << std::endl; + return os.str(); + } + + std::shared_ptr adapt_dims_( + const std::shared_ptr& dims) const override { + auto high_dim = std::make_shared>("highdim"); + return std::make_shared(high_dim, dims->low(), + dims->vecdim()); + } + + /// Hand-emit a simple element-wise loop function. + /// Cannot use S-shell dummy because TwoPRep particle-swap canonicalization + /// deduplicates children (e.g., (S_x S_x|S_y S_y) = (S_y S_y|S_x S_x)), + /// giving fewer children than the real instance. + void generate_code(const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::string& funcname, std::ostream& decl, + std::ostream& def) override { + // declare_function lives in dg.cc + extern std::string declare_function( + const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::shared_ptr& args, const std::string& tlabel, + const std::string& function_descr, std::ostream& decl); + + std::shared_ptr localdims = adapt_dims_(dims); + // inline assign_symbols_: set symbol names on target/children and + // populate CodeSymbols + std::shared_ptr symbols(new CodeSymbols); + this->rr_target()->set_symbol("target"); + symbols->append_symbol("target"); + for (unsigned int c = 0; c < this->num_children(); c++) { + std::string symb = "src" + std::to_string(c); + this->rr_child(c)->set_symbol(symb); + symbols->append_symbol(symb); + } + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + const std::string tlabel = taskmgr.current().label(); + const std::string func_decl = + declare_function(context, localdims, symbols, tlabel, funcname, decl); + def << context->std_header(); + def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; + def << context->code_prefix(); + def << func_decl << context->open_block() << std::endl; + def << context->std_function_header(); + // Sign patterns for each component, indexed by child order in constructor. + // comp 0 (SS): 9 children, all +1 + // comp 1-3 (SX,SY,SZ): 6 children, alternating +1,-1 + // comp 4,8,12 (XS,YS,ZS): 6 children, alternating +1,-1 + // comp 5-7,9-11,13-15 (XX..ZZ): 4 children, pattern -1,+1,+1,-1 + const unsigned int nc = this->num_children(); + def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; + def << "for(int hsi = 0; hsi 1) ? nc - 1 : 0; + def << "/** Number of flops = " << nflops << " */\n"; + def << context->close_block() << std::endl; + def << context->code_postfix(); + } }; template CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11( - const std::shared_ptr &Tint, unsigned int) + const std::shared_ptr& Tint, unsigned int) : ParentType(Tint, 0) { assert(Tint->num_func_bra(/* particle */ 0) == 1); assert(Tint->num_func_bra(/* particle */ 1) == 1); @@ -110,7 +222,7 @@ CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11( F c(Tint->bra(1, 0)); F d(Tint->ket(1, 0)); - const auto &oper = Tint->oper(); + const auto& oper = Tint->oper(); if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) return; From 5104e816d9ac650f83f5031dba9ac3a41bf63323 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 17 Apr 2026 16:20:02 -0400 Subject: [PATCH 18/64] Add op_coulomb_op: 9-component Gaunt LS bilinear integral MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (μ ∂_a ν | 1/r12 | κ ∂_b λ) for a, b ∈ {x,y,z}. Needed for Gaunt LS Fock where coulomb_opop's 4 folded outputs are rank-deficient. Only 2-fold bra↔ket symmetry (p1_p2_swappable, no within-side swap); uses dedicated predicate la+lb <= lc+ld. Also: LIBINT2_SIMPLE_CORE_EVAL_CASE macro to compactify Coulomb-family dispatch in engine.impl.h. --- export/tests/unit/test-2body.cc | 103 +++++++++++ include/libint2/engine.h | 16 ++ include/libint2/engine.impl.h | 119 +++++++------ src/bin/libint/build_libint.cc | 51 ++++-- src/bin/libint/comp_11_opCoulombop_11.h | 216 ++++++++++++++++++++++++ src/bin/libint/master_ints_list.h | 10 +- src/bin/libint/master_rrs_list.h | 4 + src/bin/libint/oper.h | 49 ++++++ src/bin/libint/strategy.cc | 8 + 9 files changed, 516 insertions(+), 60 deletions(-) create mode 100644 src/bin/libint/comp_11_opCoulombop_11.h diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 708190b90..cee9b0800 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -644,6 +644,109 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } } + + SECTION("op_coulomb_op") { + Engine engine_opCop; + try { + engine_opCop = Engine(Operator::op_coulomb_op, max_nprim, max_l, 0); + } catch (Engine::lmax_exceeded &) { + return; + } + + const auto nshell = obs.size(); + for (int s0 = 0; s0 != nshell; ++s0) { + for (int s1 = 0; s1 != nshell; ++s1) { + for (int s2 = 0; s2 != nshell; ++s2) { + for (int s3 = 0; s3 != nshell; ++s3) { + const auto &results = + engine_opCop.compute(obs[s0], obs[s1], obs[s2], obs[s3]); + assert(results.size() == 9); + + LIBINT2_REF_REALTYPE Aref[3], Bref[3], Cref[3], Dref[3]; + for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; + for (int i = 0; i < 3; ++i) Bref[i] = obs[s1].O[i]; + for (int i = 0; i < 3; ++i) Cref[i] = obs[s2].O[i]; + for (int i = 0; i < 3; ++i) Dref[i] = obs[s3].O[i]; + + int ijkl = 0; + + int l0, m0, n0; + FOR_CART(l0, m0, n0, obs[s0].contr[0].l) + int l1, m1, n1; + FOR_CART(l1, m1, n1, obs[s1].contr[0].l) + int l2, m2, n2; + FOR_CART(l2, m2, n2, obs[s2].contr[0].l) + int l3, m3, n3; + FOR_CART(l3, m3, n3, obs[s3].contr[0].l) + + std::array ref_op_coulomb_op{}; + ref_op_coulomb_op.fill(0.0); + + for (uint p0 = 0; p0 < obs[s0].nprim(); p0++) { + for (uint p1 = 0; p1 < obs[s1].nprim(); p1++) { + for (uint p2 = 0; p2 < obs[s2].nprim(); p2++) { + for (uint p3 = 0; p3 < obs[s3].nprim(); p3++) { + const LIBINT2_REF_REALTYPE alpha0 = obs[s0].alpha[p0]; + const LIBINT2_REF_REALTYPE alpha1 = obs[s1].alpha[p1]; + const LIBINT2_REF_REALTYPE alpha2 = obs[s2].alpha[p2]; + const LIBINT2_REF_REALTYPE alpha3 = obs[s3].alpha[p3]; + const LIBINT2_REF_REALTYPE c0 = obs[s0].contr[0].coeff[p0]; + const LIBINT2_REF_REALTYPE c1 = obs[s1].contr[0].coeff[p1]; + const LIBINT2_REF_REALTYPE c2 = obs[s2].contr[0].coeff[p2]; + const LIBINT2_REF_REALTYPE c3 = obs[s3].contr[0].coeff[p3]; + const LIBINT2_REF_REALTYPE c0123 = c0 * c1 * c2 * c3; + + // Deriv on ν (center B, index 1) and on λ (center D, idx + // 3). + auto didx_bd = [](int a, int b) -> der_idx { + der_idx r = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + r[3 + a] = 1; + r[9 + b] = 1; + return r; + }; + auto D = [&](int a, int b) { + auto di = didx_bd(a, b); + return eri(di.data(), l0, m0, n0, alpha0, Aref, l1, m1, + n1, alpha1, Bref, l2, m2, n2, alpha2, Cref, l3, + m3, n3, alpha3, Dref, 0); + }; + for (int a = 0; a < 3; ++a) + for (int b = 0; b < 3; ++b) + ref_op_coulomb_op[3 * a + b] += c0123 * D(a, b); + } + } + } + } + + const double ABSOLUTE_DEVIATION_THRESHOLD = 5.0E-14; + const double RELATIVE_DEVIATION_THRESHOLD = 1.0E-9; + for (auto comp = 0; comp < 9; ++comp) { + auto abs_err = abs(ref_op_coulomb_op[comp] - results[comp][ijkl]); + auto rel_abs_err = abs(abs_err / ref_op_coulomb_op[comp]); + bool not_ok = rel_abs_err > RELATIVE_DEVIATION_THRESHOLD && + abs_err > ABSOLUTE_DEVIATION_THRESHOLD; + if (not_ok) { + std::cout << "(l0 l1| l2 l3) = (" << s0 << " " << s1 << " | " + << s2 << " " << s3 << ") Elem " << ijkl + << " comp= " << comp + << " : ref = " << ref_op_coulomb_op[comp] + << " libint = " << results[comp][ijkl] + << " relabs_error = " << rel_abs_err + << " abs_error = " << abs_err << std::endl; + } + REQUIRE(!not_ok); + } + + ++ijkl; + END_FOR_CART + END_FOR_CART + END_FOR_CART + END_FOR_CART + } + } + } + } + } } TEST_CASE("Erfx_Coulomb integrals", "[engine][2-body]") { diff --git a/include/libint2/engine.h b/include/libint2/engine.h index fae7d1785..f99c6a898 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -160,6 +160,13 @@ enum class Operator { /// where b1 & b2 are centers of bra1 and bra2 and k1 & k2 are centers of /// ket1 and ket2, respectively opop_coulomb_opop, + /// (2-body) \f$ (σ.p_{b2}) r_{12}^{-1} (σ.p_{k2}) \f$ where b2 is the center + /// of bra2 and k2 is the center of ket2; Gaunt LS "bilinear" integral. + /// Produces 9 components (outer product of two Cartesian directions), + /// indexed as `3*a + b` with `a` = bra-side direction, `b` = ket-side + /// direction, and `a,b ∈ {x=0, y=1, z=2}`. Unlike coulomb_opop, the 9 + /// components are NOT contracted via σ·σ — all are kept independent. + op_coulomb_op, /// contracted Gaussian geminal cgtg, /// contracted Gaussian geminal times Coulomb @@ -369,6 +376,15 @@ struct operator_traits static constexpr auto nopers = 16; static constexpr auto intrinsic_deriv_order = 4; }; +template <> +struct operator_traits + : public operator_traits { + /// 9 components: Cartesian dyadic of the two (σ·p) directions. + /// index = 3 * a + b, with a = bra-side direction, b = ket-side direction, + /// a,b ∈ {x=0, y=1, z=2}. + static constexpr auto nopers = 9; + static constexpr auto intrinsic_deriv_order = 2; +}; namespace detail { template diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index ff143d254..27187cbcc 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -70,32 +70,33 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { /// These MUST appear in the same order as in Operator. /// You must also update BOOST_PP_NBODY_OPERATOR_LAST_ONEBODY_INDEX when you add /// one-body ints -#define BOOST_PP_NBODY_OPERATOR_LIST \ - (overlap, /* overlap */ \ - (kinetic, /* kinetic */ \ - (elecpot, /* nuclear */ \ - (elecpot, /* erf_nuclear */ \ - (elecpot, /* erfc_nuclear */ \ - (elecpot, /* erfx_nuclear */ \ - (1emultipole, /* emultipole1 */ \ - (2emultipole, /* emultipole2 */ \ - (3emultipole, /* emultipole3 */ \ - (sphemultipole, /* sphemultipole */ \ - (opVop, /* opVop */ \ - (eri, /* delta */ \ - (eri, /* coulomb */ \ - (coulomb_opop, /* coulomb_opop */ \ - (opop_coulomb_opop, /* coulomb_opop */ \ - (eri, /* cgtg */ \ - (eri, /* cgtg_x_coulomb */ \ - (eri, /* delcgtg2 */ \ - (eri, /* r12 */ \ - (eri, /* erf_coulomb */ \ - (eri, /* erfc_coulomb */ \ - (eri, /* erfx_coulomb */ \ - (eri, /* stg */ \ - (eri, /* yukawa */ \ - BOOST_PP_NIL)))))))))))))))))))))))) +#define BOOST_PP_NBODY_OPERATOR_LIST \ + (overlap, /* overlap */ \ + (kinetic, /* kinetic */ \ + (elecpot, /* nuclear */ \ + (elecpot, /* erf_nuclear */ \ + (elecpot, /* erfc_nuclear */ \ + (elecpot, /* erfx_nuclear */ \ + (1emultipole, /* emultipole1 */ \ + (2emultipole, /* emultipole2 */ \ + (3emultipole, /* emultipole3 */ \ + (sphemultipole, /* sphemultipole */ \ + (opVop, /* opVop */ \ + (eri, /* delta */ \ + (eri, /* coulomb */ \ + (coulomb_opop, /* coulomb_opop */ \ + (opop_coulomb_opop, /* opop_coulomb_opop */ \ + (op_coulomb_op, /* op_coulomb_op */ \ + (eri, /* cgtg */ \ + (eri, /* cgtg_x_coulomb */ \ + (eri, /* delcgtg2 */ \ + (eri, /* r12 */ \ + (eri, /* erf_coulomb */ \ + (eri, /* erfc_coulomb */ \ + (eri, /* erfx_coulomb */ \ + (eri, /* stg */ \ + (eri, /* yukawa */ \ + BOOST_PP_NIL))))))))))))))))))))))))) #define BOOST_PP_NBODY_OPERATOR_INDEX_TUPLE \ BOOST_PP_MAKE_TUPLE(BOOST_PP_LIST_SIZE(BOOST_PP_NBODY_OPERATOR_LIST)) @@ -1243,6 +1244,18 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const bool swap_p1p2 = (tket1.contr[0].l < tket2.contr[0].l); swap_tbra = swap_tket = swap_p1p2; } + } else if (oper_ == Operator::op_coulomb_op) { + // opCoulombop: only bra↔ket (particle 1↔2) swap is a symmetry (with + // (a,b)↔(b,a) component remap). Within-side swap is NOT a symmetry + // because σ·p attaches to one specific function per side; moving it to + // the other function changes the integral in a way IBP cannot recover + // across electrons. Canonical form: la+lb <= lc+ld only. + const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; + const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; + swap_braket = ((braket_ == BraKet::xx_xx) && (bra_total > ket_total)) || + braket_ == BraKet::xx_xs; + swap_tbra = false; + swap_tket = false; } else { swap_braket = ((braket_ == BraKet::xx_xx) && (tbra1.contr[0].l + tbra2.contr[0].l > @@ -1276,6 +1289,15 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const bool swap_p1p2 = (tket1.contr[0].l > tket2.contr[0].l); swap_tbra = swap_tket = swap_p1p2; } + } else if (oper_ == Operator::op_coulomb_op) { + // opCoulombop: only bra↔ket swap is a symmetry (with (a,b)↔(b,a) remap). + // ORCA canonical form: la+lb >= lc+ld only. + const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; + const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; + swap_braket = ((braket_ == BraKet::xx_xx) && (bra_total < ket_total)) || + braket_ == BraKet::xx_xs; + swap_tbra = false; + swap_tket = false; } else { swap_tbra = (tbra1.contr[0].l > tbra2.contr[0].l); swap_tket = (tket1.contr[0].l > tket2.contr[0].l); @@ -1482,28 +1504,21 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const auto mmax = l + deriv_order_ + intrinsic_deriv_order(); if (!skip_core_ints) { +// Simple core-eval dispatch: just `core_eval_ptr->eval(gm_ptr, T, mmax)`. +// Applies to Coulomb-family operators whose core integral is the bare Fm. +#define LIBINT2_SIMPLE_CORE_EVAL_CASE(OP) \ + case Operator::OP: { \ + const auto& core_eval_ptr = \ + any_cast&>( \ + core_eval_pack_) \ + .first(); \ + core_eval_ptr->eval(gm_ptr, T, mmax); \ + } break switch (oper_) { - case Operator::coulomb: { - const auto& core_eval_ptr = - any_cast&>(core_eval_pack_) - .first(); - core_eval_ptr->eval(gm_ptr, T, mmax); - } break; - case Operator::coulomb_opop: { - const auto& core_eval_ptr = - any_cast&>(core_eval_pack_) - .first(); - core_eval_ptr->eval(gm_ptr, T, mmax); - } break; - case Operator::opop_coulomb_opop: { - const auto& core_eval_ptr = - any_cast&>(core_eval_pack_) - .first(); - core_eval_ptr->eval(gm_ptr, T, mmax); - } break; + LIBINT2_SIMPLE_CORE_EVAL_CASE(coulomb); + LIBINT2_SIMPLE_CORE_EVAL_CASE(coulomb_opop); + LIBINT2_SIMPLE_CORE_EVAL_CASE(opop_coulomb_opop); + LIBINT2_SIMPLE_CORE_EVAL_CASE(op_coulomb_op); case Operator::cgtg_x_coulomb: { const auto& core_eval_ptr = any_cast temp; + for (auto s = 0; s != ntargets; ++s) temp[s] = targets_[s]; + for (auto s = 0; s != ntargets; ++s) + targets_[3 * (s % 3) + (s / 3)] = temp[s]; + } } // if need_scratch => needed to transpose and/or tform else { // did not use scratch? may still need to update targets_ if (set_targets_) { diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 4e9b10152..fbc8ac07b 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -609,8 +609,10 @@ void try_main(int argc, char* argv[]) { #endif #ifdef LIBINT_INCLUDE_RKB_ERI -#define BOOST_PP_RKB_ERI_TASK_TUPLE (coulomb_opop, opop_coulomb_opop) -#define BOOST_PP_RKB_ERI_TASK_OPER_TUPLE (CoulombσpσpOper, σpσpCoulombσpσpOper) +#define BOOST_PP_RKB_ERI_TASK_TUPLE \ + (coulomb_opop, opop_coulomb_opop, op_coulomb_op) +#define BOOST_PP_RKB_ERI_TASK_OPER_TUPLE \ + (CoulombσpσpOper, σpσpCoulombσpσpOper, opCoulombopOper) #define BOOST_PP_RKB_ERI_TASK_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_RKB_ERI_TASK_TUPLE) #define BOOST_PP_RKB_ERI_TASK_OPER_LIST \ @@ -1148,7 +1150,14 @@ static void build_TwoPRep_2b_2k( std::shared_ptr context(new CppCodeContext(cparams)); std::shared_ptr memman(new WorstFitMemoryManager()); - bool p1_p2_swappable = !std::is_same::value; + // opCoulombop has only a 2-fold bra↔ket-swap symmetry (with (a,b)↔(b,a) + // component remap). Within-side particle swap is NOT a symmetry because σ·p + // attaches to one specific function per side (ν on bra, λ on ket); swapping + // moves the operator to a different physical center that IBP cannot recover + // when centers differ. Emit code for every (la,lb,lc,ld) combination to + // avoid triggering any within-side swap at runtime. + bool p1_p2_swappable = !std::is_same::value && + !std::is_same::value; bool bra_ket_coswappable = std::is_same::value; // Note: la, lb, lc, ld generate code for chemist notation (ab|O|cd), where O @@ -1157,10 +1166,23 @@ static void build_TwoPRep_2b_2k( for (unsigned int lb = 0; lb <= lmax; lb++) { for (unsigned int lc = 0; lc <= lmax; lc++) { for (unsigned int ld = 0; ld <= lmax; ld++) { - if (!ShellQuartetSetPredicate( - LIBINT_SHELL_SET)>::value(la, lb, lc, ld, p1_p2_swappable, - bra_ket_coswappable)) - continue; + // opCoulombop has only a bra↔ket (particle 1↔2) swap symmetry; + // within-side swap is NOT a symmetry (σ·p would move to the wrong + // physical center). Canonical form: la+lb <= lc+ld only + // (ORCA: la+lb >= lc+ld). Use a dedicated predicate so within-side + // orderings are not reduced away. + if constexpr (std::is_same::value) { +#if LIBINT_SHELL_SET == LIBINT_SHELL_SET_STANDARD + if (!(la + lb <= lc + ld)) continue; +#else + if (!(la + lb >= lc + ld)) continue; +#endif + } else { + if (!ShellQuartetSetPredicate( + LIBINT_SHELL_SET)>::value(la, lb, lc, ld, p1_p2_swappable, + bra_ket_coswappable)) + continue; + } // std::shared_ptr tactic(new ParticleDirectionTactic(la+lb > // lc+ld ? false : true)); @@ -1194,6 +1216,15 @@ static void build_TwoPRep_2b_2k( descrs.emplace_back(OperDescrType(p)); } } + if constexpr (std::is_same::value) { + // reset descriptors array + descrs.resize(0); + // iterate over 9 components (3x3 Cartesian dyadic: bra-dir × + // ket-dir) + for (int p = 0; p != 9; ++p) { + descrs.emplace_back(OperDescrType(p)); + } + } // unroll only if max_am <= cparams->max_am_opt(task) using std::max; @@ -2376,9 +2407,9 @@ void config_to_api(const std::shared_ptr& cparams, { // 2-body ints -#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ - ("eri", "coulomb_opop", "opop_coulomb_opop", "r12kg12", "r12_0_g12", \ - "r12_2_g12", "g12_T1_g12", "g12dkh") +#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ + ("eri", "coulomb_opop", "opop_coulomb_opop", "op_coulomb_op", "r12kg12", \ + "r12_0_g12", "r12_2_g12", "g12_T1_g12", "g12dkh") #define BOOST_PP_TWOBODY_TASKOPER_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_TWOBODY_TASKOPER_TUPLE) diff --git a/src/bin/libint/comp_11_opCoulombop_11.h b/src/bin/libint/comp_11_opCoulombop_11.h new file mode 100644 index 000000000..a6cbb7744 --- /dev/null +++ b/src/bin/libint/comp_11_opCoulombop_11.h @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2004-2026 Edward F. Valeev + * + * This file is part of Libint compiler. + * + * Libint compiler 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 compiler 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 compiler. If not, see . + * + */ + +#ifndef LIBINT_COMP_11_OPCOULOMBOP_11_H +#define LIBINT_COMP_11_OPCOULOMBOP_11_H + +#include +#include +#include +#include +#include +#include + +namespace libint2 { + +/** + * Computes the "Gaunt LS bilinear" integral + * \f$ (\mu\, \sigma\cdot\hat{p}\,\nu | 1/r_{12} | \kappa\, + * \sigma\cdot\hat{p}\,\lambda ) \f$ by rewriting each of the 9 (a,b) components + * as a single derivative Coulomb integral \f$ ( \mu \cdot \partial_a \nu | + * 1/r_{12} | \kappa \cdot \partial_b \lambda ) \f$ with a ∈ {x,y,z} on + * bra-function-1 (ν) and b ∈ {x,y,z} on ket-function-1 (λ). + * + * Unlike Coulombσpσp (which folds 9 → 4 via σ·σ on the ket pair), all 9 + * components are exposed independently, since the two σ's here act on different + * particles and their contraction cannot be absorbed locally. + * + * @tparam F basis function type. valid choices are CGShell or CGF + */ +template +class CR_11_opCoulombop_11 + : public GenericRecurrenceRelation< + CR_11_opCoulombop_11, F, + GenIntegralSet_11_11> { + public: + typedef CR_11_opCoulombop_11 ThisType; + typedef F BasisFunctionType; + typedef opCoulombopOper OperType; + typedef GenIntegralSet_11_11 TargetType; + typedef GenericRecurrenceRelation + ParentType; + friend class GenericRecurrenceRelation; + static const unsigned int max_nchildren = 1; + + using ParentType::Instance; + + static bool directional() { return false; } + + private: + using ParentType::is_simple; + using ParentType::target_; + using ParentType::RecurrenceRelation::expr_; + using ParentType::RecurrenceRelation::nflops_; + + /// Constructor is private, used by Instance that maintains + /// registry of these objects + CR_11_opCoulombop_11(const std::shared_ptr&, unsigned int = 0); + + static std::string descr() { return "CR"; } + + // --- Code sharing overrides (mirror Coulombσpσp pattern) --- + // All shell quartets with the same quaternion component share one function. + + std::string generate_label() const override { + return "CR_opCoulombop_" + + std::to_string(target_->oper()->descr().cartesian_index()); + } + + std::string spfunction_call( + const std::shared_ptr& context, + const std::shared_ptr& dims) const override { + std::ostringstream os; + os << context->label_to_function_name(this->label()) << "(inteval, " + << context->value_to_pointer(this->rr_target()->symbol()); + const unsigned int nc = this->num_children(); + for (unsigned int c = 0; c < nc; c++) { + os << ", " << context->value_to_pointer(this->rr_child(c)->symbol()); + } + // total_dim = product of all shell dims (all 4 shells are spectators) + unsigned int total_dim = 1; + for (unsigned int p = 0; p < 2; p++) { + SubIterator* si = target_->bra().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + si = target_->ket().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + } + os << "," << total_dim; + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + taskmgr.current().params()->max_hrr_hsrank(total_dim); + os << ")" << context->end_of_stat() << std::endl; + return os.str(); + } + + std::shared_ptr adapt_dims_( + const std::shared_ptr& dims) const override { + auto high_dim = std::make_shared>("highdim"); + return std::make_shared(high_dim, dims->low(), + dims->vecdim()); + } + + /// Each of the 9 components is a single deriv-ERI child ⇒ trivial passthrough + /// loop. + void generate_code(const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::string& funcname, std::ostream& decl, + std::ostream& def) override { + extern std::string declare_function( + const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::shared_ptr& args, const std::string& tlabel, + const std::string& function_descr, std::ostream& decl); + + std::shared_ptr localdims = adapt_dims_(dims); + std::shared_ptr symbols(new CodeSymbols); + this->rr_target()->set_symbol("target"); + symbols->append_symbol("target"); + for (unsigned int c = 0; c < this->num_children(); c++) { + std::string symb = "src" + std::to_string(c); + this->rr_child(c)->set_symbol(symb); + symbols->append_symbol(symb); + } + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + const std::string tlabel = taskmgr.current().label(); + const std::string func_decl = + declare_function(context, localdims, symbols, tlabel, funcname, decl); + def << context->std_header(); + def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; + def << context->code_prefix(); + def << func_decl << context->open_block() << std::endl; + def << context->std_function_header(); + def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; + def << "for(int hsi = 0; hsiclose_block() << std::endl; + def << context->code_postfix(); + } +}; + +template +CR_11_opCoulombop_11::CR_11_opCoulombop_11( + const std::shared_ptr& Tint, unsigned int) + : ParentType(Tint, 0) { + assert(Tint->num_func_bra(/* particle */ 0) == 1); + assert(Tint->num_func_bra(/* particle */ 1) == 1); + assert(Tint->num_func_ket(/* particle */ 0) == 1); + assert(Tint->num_func_ket(/* particle */ 1) == 1); + + F a(Tint->bra(0, 0)); + F b(Tint->ket(0, 0)); + F c(Tint->bra(1, 0)); + F d(Tint->ket(1, 0)); + + const auto& oper = Tint->oper(); + + if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) + return; + + using namespace libint2::algebra; + using namespace libint2::prefactor; + using libint2::algebra::operator*; + + const mType zero_m(0u); + + ChildFactory> + factory(this); + + // Chemist notation: (a b | op c op d) — σ·p acts on one function per + // electron. Target component is indexed (a_dir, b_dir) where + // a_dir = direction of σ·p on electron 1 (applied to ket(0,0) = b) + // b_dir = direction of σ·p on electron 2 (applied to ket(1,0) = d) + // Mirrors Coulombσpσp which places BOTH derivatives on electron 2 (c and d); + // here we place ONE derivative on each electron (b on el-1, d on el-2). + const int a_dir = oper->descr().cart_a(); + const int b_dir = oper->descr().cart_b(); + + F b_deriv{b}; + b_deriv.deriv().inc(a_dir); + F d_deriv{d}; + d_deriv.deriv().inc(b_dir); + + auto child = factory.make_child(a, b_deriv, c, d_deriv, zero_m); + if (is_simple()) { + // Wrap single child in a trivial sum to satisfy expr_'s AlgebraicOperator + // type (same pattern as vrr_1_onep_1.h:261). + expr_ = Scalar(0u) + child; + nflops_ += 0; + } + +} // CR_11_opCoulombop_11::CR_11_opCoulombop_11 + +} // namespace libint2 + +#endif // LIBINT_COMP_11_OPCOULOMBOP_11_H diff --git a/src/bin/libint/master_ints_list.h b/src/bin/libint/master_ints_list.h index 37bfa29a7..ee9ca39d4 100644 --- a/src/bin/libint/master_ints_list.h +++ b/src/bin/libint/master_ints_list.h @@ -113,6 +113,9 @@ typedef GenIntegralSet_11_11 σpσpCoulombσpσp_11_11_sq; typedef GenIntegralSet_11_11 σpσpCoulombσpσp_11_11_int; +typedef GenIntegralSet_11_11 + opCoulombop_11_11_sq; +typedef GenIntegralSet_11_11 opCoulombop_11_11_int; typedef GenIntegralSet_11_11 R12kG12_11_11_sq; typedef GenIntegralSet_11_11 R12kG12_11_11_int; typedef GenIntegralSet_11_11 @@ -153,9 +156,10 @@ typedef boost::mpl::list< #endif TwoPRep_11_11_sq, TwoPRep_11_11_int, Coulombσpσp_11_11_sq, Coulombσpσp_11_11_int, σpσpCoulombσpσp_11_11_sq, σpσpCoulombσpσp_11_11_int, - R12kG12_11_11_sq, R12kG12_11_11_int, R12kR12lG12_11_11_sq, - R12kR12lG12_11_11_int, TiG12_11_11_sq, TiG12_11_11_int, G12TiG12_11_11_sq, - G12TiG12_11_11_int, DivG12prime_xTx_11_11_sq, DivG12prime_xTx_11_11_int, + opCoulombop_11_11_sq, opCoulombop_11_11_int, R12kG12_11_11_sq, + R12kG12_11_11_int, R12kR12lG12_11_11_sq, R12kR12lG12_11_11_int, + TiG12_11_11_sq, TiG12_11_11_int, G12TiG12_11_11_sq, G12TiG12_11_11_int, + DivG12prime_xTx_11_11_sq, DivG12prime_xTx_11_11_int, DummySymmIntegral_11_11_sq, DummySymmIntegral_11_11_int> MasterIntegralTypeList; diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index 5517d7c5c..62a7bfb08 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -319,6 +320,9 @@ typedef CR_11_Coulombσpσp_11 CR_11_Coulombσpσp_11_int; typedef CR_11_σpσpCoulombσpσp_11 CR_11_σpσpCoulombσpσp_11_sh; typedef CR_11_σpσpCoulombσpσp_11 CR_11_σpσpCoulombσpσp_11_int; + +typedef CR_11_opCoulombop_11 CR_11_opCoulombop_11_sh; +typedef CR_11_opCoulombop_11 CR_11_opCoulombop_11_int; }; // namespace libint2 #endif // header guard diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index 678180c28..c6d307dd1 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -476,6 +476,55 @@ struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr }; typedef GenOper<σpσpCoulombσpσp_Descr> σpσpCoulombσpσpOper; +/** opCoulombop: (μ σ·p ν | 1/r_{12} | κ σ·p λ). + * Gaunt LS "bilinear" operator with one σ·p on each side. + * Exposes the full 3×3 gradient-gradient tensor as 9 independent components + * (indexed `3*a + b`, with a,b ∈ {x=0,y=1,z=2}), unlike Coulombσpσp which + * collapses 9 → 4 via σ·σ identity on one side only. + */ +struct opCoulombop_Descr : public Contractable { + typedef MultiplicativeSymm2Body_Props Properties; + + opCoulombop_Descr() : cartesian_index_(0) {} + opCoulombop_Descr(int cartesian_index) : cartesian_index_(cartesian_index) { + assert(cartesian_index >= 0 && cartesian_index <= 8); + } + + /// 9 components = σ_a(1) ⊗ σ_b(2) bilinear, indexed as 3*a + b, + /// where a = bra-side derivative direction, b = ket-side derivative + /// direction, and a, b ∈ {x=0, y=1, z=2}. Component layout is the outer + /// product of two Cartesian unit vectors — a dyadic — analogous in spirit to + /// libint's CartesianMultipole index, but over two independent direction + /// indices. + static const unsigned int max_key = 9; + unsigned int key() const { return cartesian_index(); } + std::string description() const { + // clang-format off + static const char* labels[] = { + "XX", "XY", "XZ", + "YX", "YY", "YZ", + "ZX", "ZY", "ZZ" + }; + // clang-format on + const auto ci = cartesian_index(); + if (ci > 8) abort(); + return std::string("op_coulomb_op[") + labels[ci] + "]"; + } + std::string label() const { return description(); } + int psymm(int i, int j) const { abort(); } + int hermitian(int i) const { return +1; } + + int cartesian_index() const { return cartesian_index_; } + /// bra-side (first σ) derivative direction ∈ {0=x, 1=y, 2=z} + int cart_a() const { return cartesian_index_ / 3; } + /// ket-side (second σ) derivative direction ∈ {0=x, 1=y, 2=z} + int cart_b() const { return cartesian_index_ % 3; } + + private: + const int cartesian_index_ = -1; +}; +typedef GenOper opCoulombopOper; + /** GTG_1d is the two-body 1-dimensional Gaussian geminal */ struct GTG_1d_Descr : public Contractable { diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index 4e4804da7..e59f6e92f 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -135,6 +135,14 @@ template <> struct MasterStrategy<σpσpCoulombσpσp_11_11_int> { typedef boost::mpl::list value; }; +template <> +struct MasterStrategy { + typedef boost::mpl::list value; +}; +template <> +struct MasterStrategy { + typedef boost::mpl::list value; +}; #if LIBINT_SHELLQUARTET_STRATEGY == LIBINT_SHELLQUARTET_STRATEGY_A0C0 template <> From 590ce42f1711386318b8f1cd56adc94c5296849d Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sun, 26 Apr 2026 14:41:43 -0400 Subject: [PATCH 19/64] op_coulomb_op: redesign 9 components as SO(3) irreps Scalar trace + 3 antisym + 5 sym-TL replaces raw 3*a+b dyadic. Bra<->ket swap: per-component sign flip on antisym (was: index transpose). p1_p2_swappable=true. Tests pass: 2,112,533 assertions. --- export/tests/unit/test-2body.cc | 33 ++++- include/libint2/engine.impl.h | 18 +-- src/bin/libint/build_libint.cc | 30 ++--- src/bin/libint/comp_11_opCoulombop_11.h | 171 +++++++++++++++++++----- src/bin/libint/oper.h | 64 +++++---- 5 files changed, 229 insertions(+), 87 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index cee9b0800..60b6cf812 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -679,8 +679,12 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { int l3, m3, n3; FOR_CART(l3, m3, n3, obs[s3].contr[0].l) - std::array ref_op_coulomb_op{}; - ref_op_coulomb_op.fill(0.0); + // Raw 3x3 dyadic T_{ab} = (a b∂_a | c d∂_b), accumulated in + // chemist-notation index 3*a+b. After the primitive loop we + // project into the 9 SO(3) irreducible components that the engine + // returns: Scalar, AntisymX/Y/Z, SymTLDiagA/B, SymTLOffXY/XZ/YZ. + std::array ref_raw{}; + ref_raw.fill(0.0); for (uint p0 = 0; p0 < obs[s0].nprim(); p0++) { for (uint p1 = 0; p1 < obs[s1].nprim(); p1++) { @@ -712,12 +716,35 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { }; for (int a = 0; a < 3; ++a) for (int b = 0; b < 3; ++b) - ref_op_coulomb_op[3 * a + b] += c0123 * D(a, b); + ref_raw[3 * a + b] += c0123 * D(a, b); } } } } + // Project raw dyadic into the 9 SO(3) irrep components used by + // op_coulomb_op (must match opCoulombop_Descr::Component order). + const auto Txx = ref_raw[0]; + const auto Txy = ref_raw[1]; + const auto Txz = ref_raw[2]; + const auto Tyx = ref_raw[3]; + const auto Tyy = ref_raw[4]; + const auto Tyz = ref_raw[5]; + const auto Tzx = ref_raw[6]; + const auto Tzy = ref_raw[7]; + const auto Tzz = ref_raw[8]; + std::array ref_op_coulomb_op{ + Txx + Tyy + Tzz, // Scalar + Tyz - Tzy, // AntisymX + Tzx - Txz, // AntisymY + Txy - Tyx, // AntisymZ + Txx - Tyy, // SymTLDiagA + 2.0 * Tzz - Txx - Tyy, // SymTLDiagB + Txy + Tyx, // SymTLOffXY + Txz + Tzx, // SymTLOffXZ + Tyz + Tzy, // SymTLOffYZ + }; + const double ABSOLUTE_DEVIATION_THRESHOLD = 5.0E-14; const double RELATIVE_DEVIATION_THRESHOLD = 1.0E-9; for (auto comp = 0; comp < 9; ++comp) { diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 27187cbcc..12cf89303 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -2192,6 +2192,12 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( } if (swap_tket && oper_ == Operator::coulomb_opop && s > 0) oper_cart_component_phase = -1.0; + // op_coulomb_op irrep layout under bra↔ket swap: antisym + // components (s ∈ {1,2,3}) flip sign; scalar (0) and sym-TL + // (4..8) are invariant. swap_tket is always false for this + // operator; the sign correction applies on swap_braket alone. + if (oper_ == Operator::op_coulomb_op && s >= 1 && s <= 3) + oper_cart_component_phase = -1.0; if (swap_tbra) tgt_blk_mat = oper_cart_component_phase * src_blk_mat.transpose(); @@ -2257,15 +2263,9 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( for (auto s = 0; s != ntargets; ++s) targets_[4 * (s % 4) + (s / 4)] = temp[s]; } - // For op_coulomb_op with swap_braket: (a,b) → (b,a) because bra↔ket swap - // exchanges which side each σ·p direction came from. Layout is - // index = 3*a + b, so the remap is s_new = 3*(s%3) + (s/3). - if (permute && oper_ == Operator::op_coulomb_op && swap_braket) { - std::array temp; - for (auto s = 0; s != ntargets; ++s) temp[s] = targets_[s]; - for (auto s = 0; s != ntargets; ++s) - targets_[3 * (s % 3) + (s / 3)] = temp[s]; - } + // op_coulomb_op irrep layout: bra↔ket swap is handled in-place by + // oper_cart_component_phase above (sign flip on antisym components, + // identity on scalar / sym-TL); no pointer remap is needed. } // if need_scratch => needed to transpose and/or tform else { // did not use scratch? may still need to update targets_ if (set_targets_) { diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index fbc8ac07b..5f799ea1c 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -1150,14 +1150,14 @@ static void build_TwoPRep_2b_2k( std::shared_ptr context(new CppCodeContext(cparams)); std::shared_ptr memman(new WorstFitMemoryManager()); - // opCoulombop has only a 2-fold bra↔ket-swap symmetry (with (a,b)↔(b,a) - // component remap). Within-side particle swap is NOT a symmetry because σ·p - // attaches to one specific function per side (ν on bra, λ on ket); swapping - // moves the operator to a different physical center that IBP cannot recover - // when centers differ. Emit code for every (la,lb,lc,ld) combination to - // avoid triggering any within-side swap at runtime. - bool p1_p2_swappable = !std::is_same::value && - !std::is_same::value; + // opCoulombop has a 2-fold bra↔ket-swap symmetry, with per-component sign + // flips under the swap (Antisym* flip sign; Scalar/SymTL* invariant) — this + // is captured by p1_p2_swappable=true plus a dedicated predicate that + // canonicalizes only la+lb<=lc+ld and emits code for *all* within-side + // orderings (within-side swap is NOT a symmetry — σ·p attaches to one + // specific function per side, and IBP cannot recover the sign across the + // 1/r12 coupling). + bool p1_p2_swappable = !std::is_same::value; bool bra_ket_coswappable = std::is_same::value; // Note: la, lb, lc, ld generate code for chemist notation (ab|O|cd), where O @@ -1166,11 +1166,11 @@ static void build_TwoPRep_2b_2k( for (unsigned int lb = 0; lb <= lmax; lb++) { for (unsigned int lc = 0; lc <= lmax; lc++) { for (unsigned int ld = 0; ld <= lmax; ld++) { - // opCoulombop has only a bra↔ket (particle 1↔2) swap symmetry; - // within-side swap is NOT a symmetry (σ·p would move to the wrong - // physical center). Canonical form: la+lb <= lc+ld only - // (ORCA: la+lb >= lc+ld). Use a dedicated predicate so within-side - // orderings are not reduced away. + // opCoulombop: only bra↔ket (particle 1↔2) swap is a symmetry. + // Within-side swap is NOT (σ·p would move to a different physical + // center; IBP cannot repair the sign across 1/r12). Dedicated + // predicate canonicalizes la+lb<=lc+ld only (ORCA: >=) and accepts + // all within-side orderings. if constexpr (std::is_same::value) { #if LIBINT_SHELL_SET == LIBINT_SHELL_SET_STANDARD if (!(la + lb <= lc + ld)) continue; @@ -1219,8 +1219,8 @@ static void build_TwoPRep_2b_2k( if constexpr (std::is_same::value) { // reset descriptors array descrs.resize(0); - // iterate over 9 components (3x3 Cartesian dyadic: bra-dir × - // ket-dir) + // iterate over 9 SO(3) irrep components: 1 scalar trace + 3 + // antisym (curl-curl) + 5 sym-traceless for (int p = 0; p != 9; ++p) { descrs.emplace_back(OperDescrType(p)); } diff --git a/src/bin/libint/comp_11_opCoulombop_11.h b/src/bin/libint/comp_11_opCoulombop_11.h index a6cbb7744..38ae4763e 100644 --- a/src/bin/libint/comp_11_opCoulombop_11.h +++ b/src/bin/libint/comp_11_opCoulombop_11.h @@ -33,14 +33,12 @@ namespace libint2 { /** * Computes the "Gaunt LS bilinear" integral * \f$ (\mu\, \sigma\cdot\hat{p}\,\nu | 1/r_{12} | \kappa\, - * \sigma\cdot\hat{p}\,\lambda ) \f$ by rewriting each of the 9 (a,b) components - * as a single derivative Coulomb integral \f$ ( \mu \cdot \partial_a \nu | - * 1/r_{12} | \kappa \cdot \partial_b \lambda ) \f$ with a ∈ {x,y,z} on - * bra-function-1 (ν) and b ∈ {x,y,z} on ket-function-1 (λ). - * - * Unlike Coulombσpσp (which folds 9 → 4 via σ·σ on the ket pair), all 9 - * components are exposed independently, since the two σ's here act on different - * particles and their contraction cannot be absorbed locally. + * \sigma\cdot\hat{p}\,\lambda ) \f$ in the SO(3) irreducible decomposition of + * the rank-2 tensor \f$ T_{ab} = ( \mu \cdot \partial_a \nu | 1/r_{12} | \kappa + * \cdot \partial_b \lambda ) \f$: 1 scalar trace + 3 antisymmetric (curl-curl) + * + 5 symmetric-traceless = 9 components total. Each output is a small linear + * combination of raw deriv-TwoPRep children, mirroring + * comp_11_Coulombσpσp_11.h's pattern of trace/antisym emission. * * @tparam F basis function type. valid choices are CGShell or CGF */ @@ -58,7 +56,7 @@ class CR_11_opCoulombop_11 ParentType; friend class GenericRecurrenceRelation; - static const unsigned int max_nchildren = 1; + static const unsigned int max_nchildren = 3; using ParentType::Instance; @@ -81,7 +79,7 @@ class CR_11_opCoulombop_11 std::string generate_label() const override { return "CR_opCoulombop_" + - std::to_string(target_->oper()->descr().cartesian_index()); + std::to_string(target_->oper()->descr().component_index()); } std::string spfunction_call( @@ -118,8 +116,8 @@ class CR_11_opCoulombop_11 dims->vecdim()); } - /// Each of the 9 components is a single deriv-ERI child ⇒ trivial passthrough - /// loop. + /// Hand-emit the per-component irrep linear combination over deriv-ERI + /// children. The combination depends on the target's component index. void generate_code(const std::shared_ptr& context, const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, @@ -150,9 +148,38 @@ class CR_11_opCoulombop_11 def << context->std_function_header(); def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; def << "for(int hsi = 0; hsitarget_->oper()->descr().component_index(); + std::string rhs; + unsigned int nflops = 0; + switch (comp) { + case opCoulombop_Descr::Scalar: + rhs = "src0[hsi] + src1[hsi] + src2[hsi]"; + nflops = 2; + break; + case opCoulombop_Descr::AntisymX: + case opCoulombop_Descr::AntisymY: + case opCoulombop_Descr::AntisymZ: + case opCoulombop_Descr::SymTLDiagA: + rhs = "src0[hsi] - src1[hsi]"; + nflops = 1; + break; + case opCoulombop_Descr::SymTLDiagB: + rhs = "2.0*src0[hsi] - src1[hsi] - src2[hsi]"; + nflops = 3; + break; + case opCoulombop_Descr::SymTLOffXY: + case opCoulombop_Descr::SymTLOffXZ: + case opCoulombop_Descr::SymTLOffYZ: + rhs = "src0[hsi] + src1[hsi]"; + nflops = 1; + break; + default: + throw std::runtime_error( + "CR_11_opCoulombop_11::generate_code: invalid component index"); + } + def << "target[hsi] = " << rhs << ";\n}\n"; + def << "/** Number of flops = " << nflops << " */\n"; def << context->close_block() << std::endl; def << context->code_postfix(); } @@ -187,26 +214,100 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( GenIntegralSet_11_11> factory(this); - // Chemist notation: (a b | op c op d) — σ·p acts on one function per - // electron. Target component is indexed (a_dir, b_dir) where - // a_dir = direction of σ·p on electron 1 (applied to ket(0,0) = b) - // b_dir = direction of σ·p on electron 2 (applied to ket(1,0) = d) - // Mirrors Coulombσpσp which places BOTH derivatives on electron 2 (c and d); - // here we place ONE derivative on each electron (b on el-1, d on el-2). - const int a_dir = oper->descr().cart_a(); - const int b_dir = oper->descr().cart_b(); - - F b_deriv{b}; - b_deriv.deriv().inc(a_dir); - F d_deriv{d}; - d_deriv.deriv().inc(b_dir); - - auto child = factory.make_child(a, b_deriv, c, d_deriv, zero_m); - if (is_simple()) { - // Wrap single child in a trivial sum to satisfy expr_'s AlgebraicOperator - // type (same pattern as vrr_1_onep_1.h:261). - expr_ = Scalar(0u) + child; - nflops_ += 0; + // Chemist notation: (a b | op c op d). σ·p acts on one function per electron + // — direction `i` on ket(0,0) = b (electron 1), direction `j` on ket(1,0) = d + // (electron 2). Each output is an SO(3) irrep combination of the raw 3×3 + // T_{ij} dyadic; case bodies build only the children each combination needs. + constexpr auto x = 0; + constexpr auto y = 1; + constexpr auto z = 2; + + auto T = [&](int i, int j) { + F b_d{b}; + b_d.deriv().inc(i); + F d_d{d}; + d_d.deriv().inc(j); + return factory.make_child(a, b_d, c, d_d, zero_m); + }; + + switch (oper->descr().component_index()) { + case opCoulombop_Descr::Scalar: { + auto Txx = T(x, x); + auto Tyy = T(y, y); + auto Tzz = T(z, z); + if (is_simple()) { + expr_ = Txx + Tyy + Tzz; + nflops_ += 2; + } + } break; + case opCoulombop_Descr::AntisymX: { + auto Tyz = T(y, z); + auto Tzy = T(z, y); + if (is_simple()) { + expr_ = Tyz - Tzy; + nflops_ += 1; + } + } break; + case opCoulombop_Descr::AntisymY: { + auto Tzx = T(z, x); + auto Txz = T(x, z); + if (is_simple()) { + expr_ = Tzx - Txz; + nflops_ += 1; + } + } break; + case opCoulombop_Descr::AntisymZ: { + auto Txy = T(x, y); + auto Tyx = T(y, x); + if (is_simple()) { + expr_ = Txy - Tyx; + nflops_ += 1; + } + } break; + case opCoulombop_Descr::SymTLDiagA: { + auto Txx = T(x, x); + auto Tyy = T(y, y); + if (is_simple()) { + expr_ = Txx - Tyy; + nflops_ += 1; + } + } break; + case opCoulombop_Descr::SymTLDiagB: { + // 2·T_zz − T_xx − T_yy: child order (Tzz, Txx, Tyy) matches generate_code + auto Tzz = T(z, z); + auto Txx = T(x, x); + auto Tyy = T(y, y); + if (is_simple()) { + expr_ = Scalar(2) * Tzz - Txx - Tyy; + nflops_ += 3; + } + } break; + case opCoulombop_Descr::SymTLOffXY: { + auto Txy = T(x, y); + auto Tyx = T(y, x); + if (is_simple()) { + expr_ = Txy + Tyx; + nflops_ += 1; + } + } break; + case opCoulombop_Descr::SymTLOffXZ: { + auto Txz = T(x, z); + auto Tzx = T(z, x); + if (is_simple()) { + expr_ = Txz + Tzx; + nflops_ += 1; + } + } break; + case opCoulombop_Descr::SymTLOffYZ: { + auto Tyz = T(y, z); + auto Tzy = T(z, y); + if (is_simple()) { + expr_ = Tyz + Tzy; + nflops_ += 1; + } + } break; + default: + throw std::runtime_error("CR_11_opCoulombop_11: invalid component index"); } } // CR_11_opCoulombop_11::CR_11_opCoulombop_11 diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index c6d307dd1..8e8a4f567 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -478,35 +478,43 @@ typedef GenOper<σpσpCoulombσpσp_Descr> σpσpCoulombσpσpOper; /** opCoulombop: (μ σ·p ν | 1/r_{12} | κ σ·p λ). * Gaunt LS "bilinear" operator with one σ·p on each side. - * Exposes the full 3×3 gradient-gradient tensor as 9 independent components - * (indexed `3*a + b`, with a,b ∈ {x=0,y=1,z=2}), unlike Coulombσpσp which - * collapses 9 → 4 via σ·σ identity on one side only. + * Outputs the SO(3) irreducible decomposition of the 3×3 gradient-gradient + * tensor T_{ab} = ∂_a ∂_b (μν|κλ): 1 scalar trace + 3 antisymmetric + * (curl-curl) + 5 symmetric-traceless = 9 components. Same storage as the + * raw dyadic, but indexed by physics-meaningful irreps so consumers do not + * need to hand-build trace/antisym/sym-TL combinations at every contraction + * site. */ struct opCoulombop_Descr : public Contractable { typedef MultiplicativeSymm2Body_Props Properties; - opCoulombop_Descr() : cartesian_index_(0) {} - opCoulombop_Descr(int cartesian_index) : cartesian_index_(cartesian_index) { - assert(cartesian_index >= 0 && cartesian_index <= 8); + /// SO(3) irreducible components of the rank-2 Cartesian tensor T_{ab}. + enum Component : int { + Scalar = 0, ///< T_xx + T_yy + T_zz (trace, ∇·∇) + AntisymX = 1, ///< T_yz − T_zy = (∇×∇)_x + AntisymY = 2, ///< T_zx − T_xz = (∇×∇)_y + AntisymZ = 3, ///< T_xy − T_yx = (∇×∇)_z + SymTLDiagA = 4, ///< T_xx − T_yy + SymTLDiagB = 5, ///< 2·T_zz − T_xx − T_yy + SymTLOffXY = 6, ///< T_xy + T_yx + SymTLOffXZ = 7, ///< T_xz + T_zx + SymTLOffYZ = 8, ///< T_yz + T_zy + }; + + opCoulombop_Descr() : component_index_(0) {} + opCoulombop_Descr(int component_index) : component_index_(component_index) { + assert(component_index >= 0 && component_index <= 8); } - /// 9 components = σ_a(1) ⊗ σ_b(2) bilinear, indexed as 3*a + b, - /// where a = bra-side derivative direction, b = ket-side derivative - /// direction, and a, b ∈ {x=0, y=1, z=2}. Component layout is the outer - /// product of two Cartesian unit vectors — a dyadic — analogous in spirit to - /// libint's CartesianMultipole index, but over two independent direction - /// indices. static const unsigned int max_key = 9; - unsigned int key() const { return cartesian_index(); } + unsigned int key() const { return component_index(); } std::string description() const { - // clang-format off static const char* labels[] = { - "XX", "XY", "XZ", - "YX", "YY", "YZ", - "ZX", "ZY", "ZZ" + "scalar", "antisym_x", "antisym_y", + "antisym_z", "symtl_diag_a", "symtl_diag_b", + "symtl_off_xy", "symtl_off_xz", "symtl_off_yz", }; - // clang-format on - const auto ci = cartesian_index(); + const auto ci = component_index(); if (ci > 8) abort(); return std::string("op_coulomb_op[") + labels[ci] + "]"; } @@ -514,14 +522,20 @@ struct opCoulombop_Descr : public Contractable { int psymm(int i, int j) const { abort(); } int hermitian(int i) const { return +1; } - int cartesian_index() const { return cartesian_index_; } - /// bra-side (first σ) derivative direction ∈ {0=x, 1=y, 2=z} - int cart_a() const { return cartesian_index_ / 3; } - /// ket-side (second σ) derivative direction ∈ {0=x, 1=y, 2=z} - int cart_b() const { return cartesian_index_ % 3; } + int component_index() const { return component_index_; } + + bool is_scalar() const { return component_index_ == Scalar; } + bool is_antisym() const { + return component_index_ >= AntisymX && component_index_ <= AntisymZ; + } + bool is_sym_tl() const { + return component_index_ >= SymTLDiagA && component_index_ <= SymTLOffYZ; + } + /// for antisym components: 0=x, 1=y, 2=z (only valid if is_antisym()) + int antisym_cart() const { return component_index_ - AntisymX; } private: - const int cartesian_index_ = -1; + const int component_index_ = -1; }; typedef GenOper opCoulombopOper; From b463ca8d43632444e950971e454623345a32cd8e Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sun, 26 Apr 2026 14:43:40 -0400 Subject: [PATCH 20/64] =?UTF-8?q?Add=20=CF=83pR=CF=83p=20(oprop):=201-body?= =?UTF-8?q?=20=CF=83=C2=B7p=20=C2=B7=20r=20=C2=B7=20=CF=83=C2=B7p=20integr?= =?UTF-8?q?al?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 12 components = 3 dipole directions × 4 Pauli quaternion (trace + 3 antisym), mirroring σpVσp's fold. Engine origin via set_params. Master integral list split via mpl::joint_view (boost list50 limit). --- export/tests/unit/test-1body.cc | 66 +++++++ include/libint2/engine.h | 15 +- include/libint2/engine.impl.h | 61 +++---- src/bin/libint/build_libint.cc | 17 +- .../libint/comp_1_\317\203pR\317\203p_1.h" | 161 ++++++++++++++++++ src/bin/libint/master_ints_list.h | 18 +- src/bin/libint/master_rrs_list.h | 4 + src/bin/libint/oper.h | 39 +++++ src/bin/libint/strategy.cc | 8 + 9 files changed, 352 insertions(+), 37 deletions(-) create mode 100644 "src/bin/libint/comp_1_\317\203pR\317\203p_1.h" diff --git a/export/tests/unit/test-1body.cc b/export/tests/unit/test-1body.cc index 57c65f428..54c0cc14d 100644 --- a/export/tests/unit/test-1body.cc +++ b/export/tests/unit/test-1body.cc @@ -244,6 +244,72 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "W correctness", #endif // LIBINT2_SUPPORT_ONEBODY } +TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpRσp correctness", + "[engine][1-body]") { +#if defined(LIBINT2_SUPPORT_ONEBODY) + if (LIBINT_SHGSHELL_ORDERING != LIBINT_SHGSHELL_ORDERING_STANDARD) return; + + // Two contracted Gaussian shells at distinct centers. We exercise + // `(σ·p) r (σ·p)` over the (s|d) and (d|s) shell pairs and validate + // Hermiticity: trace components (q=0) are symmetric under bra↔ket swap, + // antisym components (q=1,2,3) are antisymmetric. + std::vector obs{ + Shell{{1.0, 3.0}, {{0, true, {1.0, 0.3}}}, {{0.0, 0.0, 0.0}}}, + Shell{{2.0, 5.0}, {{2, true, {1.0, 0.2}}}, {{1.0, 1.0, 1.0}}}}; + + const auto lmax = std::min(2, LIBINT2_MAX_AM_oprop); + if (lmax < 2) return; + + auto engine = Engine(Operator::oprop, 2, lmax); + engine.set_params(std::array{{0.0, 0.0, 0.0}}); + + // (s|σpRσp|d) and (d|σpRσp|s) + engine.compute(obs[0], obs[1]); + std::array, 12> ab; + for (int c = 0; c < 12; ++c) { + const auto* buf = engine.results()[c]; + REQUIRE(buf != nullptr); + ab[c].assign(buf, buf + (1 * 5)); // n_s × n_d_pure = 1 × 5 + } + + engine.compute(obs[1], obs[0]); + std::array, 12> ba; + for (int c = 0; c < 12; ++c) { + const auto* buf = engine.results()[c]; + REQUIRE(buf != nullptr); + ba[c].assign(buf, buf + (5 * 1)); // n_d_pure × n_s + } + + // Hermiticity check: σpRσp is Hermitian, but the Pauli identity routes the + // imaginary i factor on the antisym pieces into a real-stored sign flip. + // q=0 trace: matrix is symmetric ⇒ ab[0+k][i,j] == ba[0+k][j,i] + // q=1..3 : matrix is antisym ⇒ ab[q+k][i,j] == -ba[q+k][j,i] + // (Indices: ab is laid out (i=0..n_s-1, j=0..n_d-1); ba is (j, i).) + const double tol = 1.0e-12; + for (int k = 0; k < 3; ++k) { + for (int q = 0; q < 4; ++q) { + const int comp = 4 * k + q; + const double expected_sign = (q == 0) ? +1.0 : -1.0; + for (int i = 0; i < 1; ++i) { + for (int j = 0; j < 5; ++j) { + const double v_ab = ab[comp][i * 5 + j]; + const double v_ba = ba[comp][j * 1 + i]; + REQUIRE(std::isfinite(v_ab)); + REQUIRE(std::abs(v_ab - expected_sign * v_ba) < tol); + } + } + } + } + + // Sanity: not every component is identically zero (would mask codegen bugs). + bool any_nonzero = false; + for (int c = 0; c < 12; ++c) + for (double v : ab[c]) + if (std::abs(v) > 1.0e-10) any_nonzero = true; + REQUIRE(any_nonzero); +#endif // LIBINT2_SUPPORT_ONEBODY +} + // verify that python/tests/test_libint2.py:test_integrals is correct TEST_CASE_METHOD(libint2::unit::DefaultFixture, "python correctness", "[engine][1-body]") { diff --git a/include/libint2/engine.h b/include/libint2/engine.h index f99c6a898..d90c6709c 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -147,6 +147,12 @@ enum class Operator { sphemultipole, /// The four components of σp . V . σp, where V is the nuclear potential. opVop, + /// (1-body) σp . r . σp, the σ·p-on-both-sides analog of the dipole moment. + /// Produces 12 components = 3 dipole directions × 4 Pauli quaternion + /// components (trace + 3 antisym), indexed as `4*k + q` with `k ∈ {x,y,z}` + /// the dipole direction and `q ∈ {0=trace, 1=σ_x, 2=σ_y, 3=σ_z}` the Pauli + /// piece. Origin set via `engine.set_params(std::array)`. + oprop, /// \f$ \delta(\vec{r}_1 - \vec{r}_2) \f$ delta, /// (2-body) Coulomb operator = \f$ r_{12}^{-1} \f$ @@ -199,7 +205,7 @@ enum class Operator { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!keep this // updated!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! first_1body_oper = overlap, - last_1body_oper = opVop, + last_1body_oper = oprop, first_2body_oper = delta, last_2body_oper = stg_x_coulomb, first_oper = first_1body_oper, @@ -352,6 +358,13 @@ struct operator_traits (LIBINT_MULTIPOLE_MAX_ORDER + 1) * (LIBINT_MULTIPOLE_MAX_ORDER + 1); }; +template <> +struct operator_traits + : public operator_traits { + static constexpr auto nopers = 12; + static constexpr auto intrinsic_deriv_order = 2; +}; + template <> struct operator_traits : public detail::default_operator_traits { diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 12cf89303..a431f8ad1 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -70,40 +70,41 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { /// These MUST appear in the same order as in Operator. /// You must also update BOOST_PP_NBODY_OPERATOR_LAST_ONEBODY_INDEX when you add /// one-body ints -#define BOOST_PP_NBODY_OPERATOR_LIST \ - (overlap, /* overlap */ \ - (kinetic, /* kinetic */ \ - (elecpot, /* nuclear */ \ - (elecpot, /* erf_nuclear */ \ - (elecpot, /* erfc_nuclear */ \ - (elecpot, /* erfx_nuclear */ \ - (1emultipole, /* emultipole1 */ \ - (2emultipole, /* emultipole2 */ \ - (3emultipole, /* emultipole3 */ \ - (sphemultipole, /* sphemultipole */ \ - (opVop, /* opVop */ \ - (eri, /* delta */ \ - (eri, /* coulomb */ \ - (coulomb_opop, /* coulomb_opop */ \ - (opop_coulomb_opop, /* opop_coulomb_opop */ \ - (op_coulomb_op, /* op_coulomb_op */ \ - (eri, /* cgtg */ \ - (eri, /* cgtg_x_coulomb */ \ - (eri, /* delcgtg2 */ \ - (eri, /* r12 */ \ - (eri, /* erf_coulomb */ \ - (eri, /* erfc_coulomb */ \ - (eri, /* erfx_coulomb */ \ - (eri, /* stg */ \ - (eri, /* yukawa */ \ - BOOST_PP_NIL))))))))))))))))))))))))) +#define BOOST_PP_NBODY_OPERATOR_LIST \ + (overlap, /* overlap */ \ + (kinetic, /* kinetic */ \ + (elecpot, /* nuclear */ \ + (elecpot, /* erf_nuclear */ \ + (elecpot, /* erfc_nuclear */ \ + (elecpot, /* erfx_nuclear */ \ + (1emultipole, /* emultipole1 */ \ + (2emultipole, /* emultipole2 */ \ + (3emultipole, /* emultipole3 */ \ + (sphemultipole, /* sphemultipole */ \ + (opVop, /* opVop */ \ + (oprop, /* oprop */ \ + (eri, /* delta */ \ + (eri, /* coulomb */ \ + (coulomb_opop, /* coulomb_opop */ \ + (opop_coulomb_opop, /* opop_coulomb_opop */ \ + (op_coulomb_op, /* op_coulomb_op */ \ + (eri, /* cgtg */ \ + (eri, /* cgtg_x_coulomb */ \ + (eri, /* delcgtg2 */ \ + (eri, /* r12 */ \ + (eri, /* erf_coulomb */ \ + (eri, /* erfc_coulomb */ \ + (eri, /* erfx_coulomb */ \ + (eri, /* stg */ \ + (eri, /* yukawa */ \ + BOOST_PP_NIL)))))))))))))))))))))))))) #define BOOST_PP_NBODY_OPERATOR_INDEX_TUPLE \ BOOST_PP_MAKE_TUPLE(BOOST_PP_LIST_SIZE(BOOST_PP_NBODY_OPERATOR_LIST)) #define BOOST_PP_NBODY_OPERATOR_INDEX_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_NBODY_OPERATOR_INDEX_TUPLE) #define BOOST_PP_NBODY_OPERATOR_LAST_ONEBODY_INDEX \ - 10 // opVop, the 11th member of BOOST_PP_NBODY_OPERATOR_LIST, is the last + 11 // oprop, the 12th member of BOOST_PP_NBODY_OPERATOR_LIST, is the last // 1-body operator // make list of braket indices for n-body ints @@ -1028,7 +1029,7 @@ __libint2_engine_inline void Engine::compute_primdata(Libint_t& primdata, // } if (oper_ == Operator::emultipole1 || oper_ == Operator::emultipole2 || - oper_ == Operator::emultipole3) { + oper_ == Operator::emultipole3 || oper_ == Operator::oprop) { const auto& O = any_cast< const operator_traits::oper_params_type&>( params_); // same as emultipoleX @@ -1076,7 +1077,7 @@ __libint2_engine_inline void Engine::compute_primdata(Libint_t& primdata, primdata._0_Overlap_0_z[0] = ovlp_ss_z; if (oper_ == Operator::kinetic || (deriv_order_ > 0) || - oper_ == Operator::opVop) { + oper_ == Operator::opVop || oper_ == Operator::oprop) { #if LIBINT2_DEFINED(eri, two_alpha0_bra) primdata.two_alpha0_bra[0] = 2.0 * alpha1; #endif diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 5f799ea1c..206cd43c4 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -308,6 +308,11 @@ template <> return σpVσp_Descr(p); } +template <> +σpRσp_Descr make_descr<σpRσp_Descr>(int p, int, int) { + return σpRσp_Descr(p); +} + template <> Coulombσpσp_Descr make_descr(int p, int, int) { return Coulombσpσp_Descr(p); @@ -445,6 +450,14 @@ void build_onebody_1b_1k(std::ostream& os, std::string label, descrs.emplace_back(make_descr(p)); } } + if (std::is_same<_OperType, σpRσpOper>::value) { + // reset descriptors array + descrs.resize(0); + // iterate over 12 = 3 dipole directions × 4 Pauli components + for (int p = 0; p != 12; ++p) { + descrs.emplace_back(make_descr(p)); + } + } // derivative index is the outermost (slowest running) // operator component is second slowest @@ -584,11 +597,11 @@ void try_main(int argc, char* argv[]) { // overlap, kinetic, elecpot cannot be omitted #define BOOST_PP_ONEBODY_TASK_TUPLE \ (overlap, kinetic, elecpot, 1emultipole, 2emultipole, 3emultipole, \ - sphemultipole, opVop) + sphemultipole, opVop, oprop) #define BOOST_PP_ONEBODY_TASK_OPER_TUPLE \ (OverlapOper, KineticOper, ElecPotOper, CartesianMultipoleOper<3u>, \ CartesianMultipoleOper<3u>, CartesianMultipoleOper<3u>, \ - SphericalMultipoleOper, σpVσpOper) + SphericalMultipoleOper, σpVσpOper, σpRσpOper) #define BOOST_PP_ONEBODY_TASK_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_ONEBODY_TASK_TUPLE) #define BOOST_PP_ONEBODY_TASK_OPER_LIST \ diff --git "a/src/bin/libint/comp_1_\317\203pR\317\203p_1.h" "b/src/bin/libint/comp_1_\317\203pR\317\203p_1.h" new file mode 100644 index 000000000..e698c9047 --- /dev/null +++ "b/src/bin/libint/comp_1_\317\203pR\317\203p_1.h" @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2004-2026 Edward F. Valeev + * + * This file is part of Libint compiler. + * + * Libint compiler 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 compiler 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 compiler. If not, see . + * + */ + +#ifndef LIBINT_COMP_1_ΣPRΣP_1_H +#define LIBINT_COMP_1_ΣPRΣP_1_H + +#include + +namespace libint2 { + +/** + * Computes the integral of \f$ \sigma \cdot \hat{p}\, r_k\, \sigma \cdot + * \hat{p} \f$ over CGShell/CGF by folding the 9 raw \f$ \sigma_a \partial_a r_k + * \sigma_b \partial_b \f$ dyadics per dipole direction \f$ k \f$ to 4 + * Pauli-quaternion components via \f$ \sigma_a \sigma_b = \delta_{ab} + + * i\epsilon_{abc}\sigma_c \f$. 12 outputs total = 3 dipole directions × 4 + * Pauli components, mirroring the σpVσp fold but with the central operator + * being a Cartesian dipole instead of the electrostatic potential V. + * + * @tparam F basis function type. valid choices are CGShell or CGF + */ +template +class CR_1_σpRσp_1 + : public GenericRecurrenceRelation< + CR_1_σpRσp_1, F, GenIntegralSet_1_1> { + public: + typedef CR_1_σpRσp_1 ThisType; + typedef F BasisFunctionType; + typedef σpRσpOper OperType; + typedef GenIntegralSet_1_1 TargetType; + typedef GenericRecurrenceRelation + ParentType; + friend class GenericRecurrenceRelation; + static const unsigned int max_nchildren = 100; + + using ParentType::Instance; + + static bool directional() { return false; } + + private: + using ParentType::is_simple; + using ParentType::target_; + using ParentType::RecurrenceRelation::expr_; + using ParentType::RecurrenceRelation::nflops_; + + CR_1_σpRσp_1(const std::shared_ptr &, unsigned int = 0); + + static std::string descr() { return "CR"; } +}; + +template +CR_1_σpRσp_1::CR_1_σpRσp_1(const std::shared_ptr &Tint, + unsigned int) + : ParentType(Tint, 0) { + assert(Tint->num_func_bra(/* particle */ 0) == 1); + assert(Tint->num_func_ket(/* particle */ 0) == 1); + const auto &a = Tint->bra(0, 0); + const auto &b = Tint->ket(0, 0); + const auto &oper = Tint->oper(); + + // express σ·p r_k σ·p in terms of derivative integrals of the dipole + // operator r_k for primitive Gaussians only + if (a.contracted() || b.contracted()) return; + + using namespace libint2::algebra; + using namespace libint2::prefactor; + using libint2::algebra::operator*; + + ChildFactory, + EmptySet>> + factory(this); + + constexpr auto x = 0; + constexpr auto y = 1; + constexpr auto z = 2; + + F Dx_a{a}; + Dx_a.deriv().inc(x); + F Dx_b{b}; + Dx_b.deriv().inc(x); + F Dy_a{a}; + Dy_a.deriv().inc(y); + F Dy_b{b}; + Dy_b.deriv().inc(y); + F Dz_a{a}; + Dz_a.deriv().inc(z); + F Dz_b{b}; + Dz_b.deriv().inc(z); + + // Build the dipole multipole descriptor for direction k. + const auto k = oper->descr().dipole_dir(); + CartesianMultipole_Descr<3u> mu_k; + mu_k.inc(k, 1); // r_k = (kx,ky,kz) with k_k = 1, others 0 + + // Pauli quaternion fold per (k, q): + // q=0: trace δ_ab → Σ_a (∂_a μ | r_k | ∂_a ν) + // q=1: σ_x antisym → (∂_y μ | r_k | ∂_z ν) − (∂_z μ | r_k | ∂_y ν) + // q=2: σ_y antisym → (∂_z μ | r_k | ∂_x ν) − (∂_x μ | r_k | ∂_z ν) + // q=3: σ_z antisym → (∂_x μ | r_k | ∂_y ν) − (∂_y μ | r_k | ∂_x ν) + switch (oper->descr().quaternion_index()) { + case 0: { + auto Dx_a_R_Dx_b = factory.make_child(Dx_a, Dx_b, EmptySet(), mu_k); + auto Dy_a_R_Dy_b = factory.make_child(Dy_a, Dy_b, EmptySet(), mu_k); + auto Dz_a_R_Dz_b = factory.make_child(Dz_a, Dz_b, EmptySet(), mu_k); + if (is_simple()) { + expr_ = Dx_a_R_Dx_b + Dy_a_R_Dy_b + Dz_a_R_Dz_b; + nflops_ += 2; + } + } break; + case 1: { + auto Dy_a_R_Dz_b = factory.make_child(Dy_a, Dz_b, EmptySet(), mu_k); + auto Dz_a_R_Dy_b = factory.make_child(Dz_a, Dy_b, EmptySet(), mu_k); + if (is_simple()) { + expr_ = Dy_a_R_Dz_b - Dz_a_R_Dy_b; + nflops_ += 1; + } + } break; + case 2: { + auto Dz_a_R_Dx_b = factory.make_child(Dz_a, Dx_b, EmptySet(), mu_k); + auto Dx_a_R_Dz_b = factory.make_child(Dx_a, Dz_b, EmptySet(), mu_k); + if (is_simple()) { + expr_ = Dz_a_R_Dx_b - Dx_a_R_Dz_b; + nflops_ += 1; + } + } break; + case 3: { + auto Dx_a_R_Dy_b = factory.make_child(Dx_a, Dy_b, EmptySet(), mu_k); + auto Dy_a_R_Dx_b = factory.make_child(Dy_a, Dx_b, EmptySet(), mu_k); + if (is_simple()) { + expr_ = Dx_a_R_Dy_b - Dy_a_R_Dx_b; + nflops_ += 1; + } + } break; + default: + throw std::runtime_error("CR_1_σpRσp_1: invalid quaternionic index"); + } + +} // CR_1_σpRσp_1::CR_1_σpRσp_1 + +}; // namespace libint2 + +#endif // LIBINT_COMP_1_ΣPRΣP_1_H diff --git a/src/bin/libint/master_ints_list.h b/src/bin/libint/master_ints_list.h index ee9ca39d4..2a6a7cd0f 100644 --- a/src/bin/libint/master_ints_list.h +++ b/src/bin/libint/master_ints_list.h @@ -21,12 +21,15 @@ #ifndef _libint2_src_bin_libint_masterintslist_h_ #define _libint2_src_bin_libint_masterintslist_h_ -// need extra-long mpl list +// need extra-long mpl list — split across two sub-lists and joined via +// boost::mpl::joint_view, since boost only ships pre-generated list headers up +// to size 50. #define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS #define BOOST_MPL_LIMIT_LIST_SIZE 50 #include #include +#include #include #if LIBINT_SUPPORT_ONEBODYINTS #include @@ -47,6 +50,8 @@ typedef GenIntegralSet_1_1 ElecPot_1_1_sh; typedef GenIntegralSet_1_1 ElecPot_1_1_int; typedef GenIntegralSet_1_1 σpVσp_1_1_sh; typedef GenIntegralSet_1_1 σpVσp_1_1_int; +typedef GenIntegralSet_1_1 σpRσp_1_1_sh; +typedef GenIntegralSet_1_1 σpRσp_1_1_int; typedef GenIntegralSet_1_1, EmptySet> CMultipole_1_1_sh; typedef GenIntegralSet_1_1, EmptySet> @@ -148,12 +153,14 @@ typedef boost::mpl::list< Overlap_1_1_sh_y, Overlap_1_1_int_y, Overlap_1_1_sh_z, Overlap_1_1_int_z, Kinetic_1_1_sh, Kinetic_1_1_int, Kinetic_1_1_sh_x, Kinetic_1_1_int_x, Kinetic_1_1_sh_y, Kinetic_1_1_int_y, Kinetic_1_1_sh_z, Kinetic_1_1_int_z, - ElecPot_1_1_sh, ElecPot_1_1_int, σpVσp_1_1_sh, σpVσp_1_1_int, - CMultipole_1_1_sh, CMultipole_1_1_int, CMultipole_1_1_sh_x, + ElecPot_1_1_sh, ElecPot_1_1_int, σpVσp_1_1_sh, σpVσp_1_1_int, σpRσp_1_1_sh, + σpRσp_1_1_int, CMultipole_1_1_sh, CMultipole_1_1_int, CMultipole_1_1_sh_x, CMultipole_1_1_sh_y, CMultipole_1_1_sh_z, CMultipole_1_1_int_x, CMultipole_1_1_int_y, CMultipole_1_1_int_z, SMultipole_1_1_sh, - SMultipole_1_1_int, #endif + SMultipole_1_1_int> + MasterIntegralTypeList_1body_part; +typedef boost::mpl::list< TwoPRep_11_11_sq, TwoPRep_11_11_int, Coulombσpσp_11_11_sq, Coulombσpσp_11_11_int, σpσpCoulombσpσp_11_11_sq, σpσpCoulombσpσp_11_11_int, opCoulombop_11_11_sq, opCoulombop_11_11_int, R12kG12_11_11_sq, @@ -161,6 +168,9 @@ typedef boost::mpl::list< TiG12_11_11_sq, TiG12_11_11_int, G12TiG12_11_11_sq, G12TiG12_11_11_int, DivG12prime_xTx_11_11_sq, DivG12prime_xTx_11_11_int, DummySymmIntegral_11_11_sq, DummySymmIntegral_11_11_int> + MasterIntegralTypeList_2body_part; +typedef boost::mpl::joint_view MasterIntegralTypeList; }; // namespace libint2 diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index 62a7bfb08..067ccddf3 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -182,6 +183,9 @@ typedef VRR_1_ElecPot_1 VRR_b_1_ElecPot_1_int; typedef CR_1_σpVσp_1 CR_1_σpVσp_1_sh; typedef CR_1_σpVσp_1 CR_1_σpVσp_1_int; +typedef CR_1_σpRσp_1 CR_1_σpRσp_1_sh; +typedef CR_1_σpRσp_1 CR_1_σpRσp_1_int; + // TODO investigate whether need to stay away from HRR for now to be sure that // multipoles are computed as precisely as possible typedef HRR diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index 8e8a4f567..b231d80cc 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -321,6 +321,45 @@ struct σpVσp_Descr : public Contractable<σpVσp_Descr> { }; typedef GenOper<σpVσp_Descr> σpVσpOper; +/** opRop: (μ σ·p | r | σ·p ν), one-body σ·p-on-both-sides analog of dipole. + * σ_a σ_b = δ_ab + iε_abc σ_c folds the 9 raw σ_a∂_a r_k σ_b∂_b dyadics per + * dipole direction k down to 4 Pauli-quaternion components (trace + 3 + * antisym), mirroring σpVσp's fold of σ·p V σ·p. 12 outputs total = 3 dipole + * directions × 4 Pauli components, indexed composite_index = 4·k + q. + */ +struct σpRσp_Descr : public Contractable<σpRσp_Descr> { + typedef MultiplicativeODep1Body_Props Properties; + + σpRσp_Descr() : composite_index_(0) {} + σpRσp_Descr(int composite_index) : composite_index_(composite_index) { + assert(composite_index >= 0 && composite_index < 12); + } + + static const unsigned int max_key = 12; + unsigned int key() const { return composite_index(); } + std::string description() const { + static const char* dipole_lbl[] = {"x", "y", "z"}; + static const char* pauli_lbl[] = {"0", "X", "Y", "Z"}; + const auto ci = composite_index(); + if (ci < 0 || ci >= 12) abort(); + return std::string("opRop[") + dipole_lbl[ci / 4] + "," + + pauli_lbl[ci % 4] + "]"; + } + std::string label() const { return description(); } + int psymm(int i, int j) const { abort(); } + int hermitian(int i) const { return +1; } + + int composite_index() const { return composite_index_; } + /// dipole direction ∈ {0=x, 1=y, 2=z} + int dipole_dir() const { return composite_index_ / 4; } + /// Pauli quaternion ∈ {0=trace, 1=σ_x, 2=σ_y, 3=σ_z} + int quaternion_index() const { return composite_index_ % 4; } + + private: + const int composite_index_ = -1; +}; +typedef GenOper<σpRσp_Descr> σpRσpOper; + /// cartesian multipole operator in \c NDIM dimensions /// \f$ \hat{O}(\vec{k}) \equiv \vec{r}^{\cdot \vec{k}} = r_1^{k_1} r_2^{k_2} /// \cdots \f$ \internal OriginDerivative is used to store cartesian diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index e59f6e92f..3f0c36b0c 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -315,6 +315,14 @@ struct MasterStrategy<σpVσp_1_1_int> { typedef boost::mpl::list value; }; template <> +struct MasterStrategy<σpRσp_1_1_sh> { + typedef boost::mpl::list value; +}; +template <> +struct MasterStrategy<σpRσp_1_1_int> { + typedef boost::mpl::list value; +}; +template <> struct MasterStrategy { typedef boost::mpl::list> value; }; From 86486371071fcbc41c36b92665c13a56ebabe1ed Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Thu, 30 Apr 2026 14:53:03 -0400 Subject: [PATCH 21/64] =?UTF-8?q?Rename=20opCoulombop=20to=20=CF=83pCoulom?= =?UTF-8?q?b=CF=83p=20for=20naming=20consistency=20with=20sibling=20RKB=20?= =?UTF-8?q?integrals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- export/tests/unit/test-2body.cc | 2 +- include/libint2/engine.impl.h | 4 +- src/bin/libint/build_libint.cc | 10 +-- .../comp_11_\317\203pCoulomb\317\203p_11.h" | 66 +++++++++---------- src/bin/libint/master_ints_list.h | 8 +-- src/bin/libint/master_rrs_list.h | 6 +- src/bin/libint/oper.h | 10 +-- src/bin/libint/strategy.cc | 8 +-- 8 files changed, 57 insertions(+), 57 deletions(-) rename src/bin/libint/comp_11_opCoulombop_11.h => "src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" (85%) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 60b6cf812..dc0c01b73 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -723,7 +723,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } // Project raw dyadic into the 9 SO(3) irrep components used by - // op_coulomb_op (must match opCoulombop_Descr::Component order). + // op_coulomb_op (must match σpCoulombσp_Descr::Component order). const auto Txx = ref_raw[0]; const auto Txy = ref_raw[1]; const auto Txz = ref_raw[2]; diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index abf0db4e7..01269538d 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -1296,7 +1296,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( swap_tbra = swap_tket = swap_p1p2; } } else if (oper_ == Operator::op_coulomb_op) { - // opCoulombop: only bra↔ket (particle 1↔2) swap is a symmetry (with + // σpCoulombσp: only bra↔ket (particle 1↔2) swap is a symmetry (with // (a,b)↔(b,a) component remap). Within-side swap is NOT a symmetry // because σ·p attaches to one specific function per side; moving it to // the other function changes the integral in a way IBP cannot recover @@ -1341,7 +1341,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( swap_tbra = swap_tket = swap_p1p2; } } else if (oper_ == Operator::op_coulomb_op) { - // opCoulombop: only bra↔ket swap is a symmetry (with (a,b)↔(b,a) remap). + // σpCoulombσp: only bra↔ket swap is a symmetry (with (a,b)↔(b,a) remap). // ORCA canonical form: la+lb >= lc+ld only. const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 206cd43c4..67700a630 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -625,7 +625,7 @@ void try_main(int argc, char* argv[]) { #define BOOST_PP_RKB_ERI_TASK_TUPLE \ (coulomb_opop, opop_coulomb_opop, op_coulomb_op) #define BOOST_PP_RKB_ERI_TASK_OPER_TUPLE \ - (CoulombσpσpOper, σpσpCoulombσpσpOper, opCoulombopOper) + (CoulombσpσpOper, σpσpCoulombσpσpOper, σpCoulombσpOper) #define BOOST_PP_RKB_ERI_TASK_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_RKB_ERI_TASK_TUPLE) #define BOOST_PP_RKB_ERI_TASK_OPER_LIST \ @@ -1163,7 +1163,7 @@ static void build_TwoPRep_2b_2k( std::shared_ptr context(new CppCodeContext(cparams)); std::shared_ptr memman(new WorstFitMemoryManager()); - // opCoulombop has a 2-fold bra↔ket-swap symmetry, with per-component sign + // σpCoulombσp has a 2-fold bra↔ket-swap symmetry, with per-component sign // flips under the swap (Antisym* flip sign; Scalar/SymTL* invariant) — this // is captured by p1_p2_swappable=true plus a dedicated predicate that // canonicalizes only la+lb<=lc+ld and emits code for *all* within-side @@ -1179,12 +1179,12 @@ static void build_TwoPRep_2b_2k( for (unsigned int lb = 0; lb <= lmax; lb++) { for (unsigned int lc = 0; lc <= lmax; lc++) { for (unsigned int ld = 0; ld <= lmax; ld++) { - // opCoulombop: only bra↔ket (particle 1↔2) swap is a symmetry. + // σpCoulombσp: only bra↔ket (particle 1↔2) swap is a symmetry. // Within-side swap is NOT (σ·p would move to a different physical // center; IBP cannot repair the sign across 1/r12). Dedicated // predicate canonicalizes la+lb<=lc+ld only (ORCA: >=) and accepts // all within-side orderings. - if constexpr (std::is_same::value) { + if constexpr (std::is_same::value) { #if LIBINT_SHELL_SET == LIBINT_SHELL_SET_STANDARD if (!(la + lb <= lc + ld)) continue; #else @@ -1229,7 +1229,7 @@ static void build_TwoPRep_2b_2k( descrs.emplace_back(OperDescrType(p)); } } - if constexpr (std::is_same::value) { + if constexpr (std::is_same::value) { // reset descriptors array descrs.resize(0); // iterate over 9 SO(3) irrep components: 1 scalar trace + 3 diff --git a/src/bin/libint/comp_11_opCoulombop_11.h "b/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" similarity index 85% rename from src/bin/libint/comp_11_opCoulombop_11.h rename to "src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" index 38ae4763e..7391d3d31 100644 --- a/src/bin/libint/comp_11_opCoulombop_11.h +++ "b/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" @@ -18,8 +18,8 @@ * */ -#ifndef LIBINT_COMP_11_OPCOULOMBOP_11_H -#define LIBINT_COMP_11_OPCOULOMBOP_11_H +#ifndef LIBINT_COMP_11_ΣPCOULOMBΣP_11_H +#define LIBINT_COMP_11_ΣPCOULOMBΣP_11_H #include #include @@ -43,15 +43,15 @@ namespace libint2 { * @tparam F basis function type. valid choices are CGShell or CGF */ template -class CR_11_opCoulombop_11 +class CR_11_σpCoulombσp_11 : public GenericRecurrenceRelation< - CR_11_opCoulombop_11, F, - GenIntegralSet_11_11> { + CR_11_σpCoulombσp_11, F, + GenIntegralSet_11_11> { public: - typedef CR_11_opCoulombop_11 ThisType; + typedef CR_11_σpCoulombσp_11 ThisType; typedef F BasisFunctionType; - typedef opCoulombopOper OperType; - typedef GenIntegralSet_11_11 TargetType; + typedef σpCoulombσpOper OperType; + typedef GenIntegralSet_11_11 TargetType; typedef GenericRecurrenceRelation ParentType; friend class GenericRecurrenceRelation&, unsigned int = 0); + CR_11_σpCoulombσp_11(const std::shared_ptr&, unsigned int = 0); static std::string descr() { return "CR"; } @@ -78,7 +78,7 @@ class CR_11_opCoulombop_11 // All shell quartets with the same quaternion component share one function. std::string generate_label() const override { - return "CR_opCoulombop_" + + return "CR_σpCoulombσp_" + std::to_string(target_->oper()->descr().component_index()); } @@ -153,30 +153,30 @@ class CR_11_opCoulombop_11 std::string rhs; unsigned int nflops = 0; switch (comp) { - case opCoulombop_Descr::Scalar: + case σpCoulombσp_Descr::Scalar: rhs = "src0[hsi] + src1[hsi] + src2[hsi]"; nflops = 2; break; - case opCoulombop_Descr::AntisymX: - case opCoulombop_Descr::AntisymY: - case opCoulombop_Descr::AntisymZ: - case opCoulombop_Descr::SymTLDiagA: + case σpCoulombσp_Descr::AntisymX: + case σpCoulombσp_Descr::AntisymY: + case σpCoulombσp_Descr::AntisymZ: + case σpCoulombσp_Descr::SymTLDiagA: rhs = "src0[hsi] - src1[hsi]"; nflops = 1; break; - case opCoulombop_Descr::SymTLDiagB: + case σpCoulombσp_Descr::SymTLDiagB: rhs = "2.0*src0[hsi] - src1[hsi] - src2[hsi]"; nflops = 3; break; - case opCoulombop_Descr::SymTLOffXY: - case opCoulombop_Descr::SymTLOffXZ: - case opCoulombop_Descr::SymTLOffYZ: + case σpCoulombσp_Descr::SymTLOffXY: + case σpCoulombσp_Descr::SymTLOffXZ: + case σpCoulombσp_Descr::SymTLOffYZ: rhs = "src0[hsi] + src1[hsi]"; nflops = 1; break; default: throw std::runtime_error( - "CR_11_opCoulombop_11::generate_code: invalid component index"); + "CR_11_σpCoulombσp_11::generate_code: invalid component index"); } def << "target[hsi] = " << rhs << ";\n}\n"; def << "/** Number of flops = " << nflops << " */\n"; @@ -186,7 +186,7 @@ class CR_11_opCoulombop_11 }; template -CR_11_opCoulombop_11::CR_11_opCoulombop_11( +CR_11_σpCoulombσp_11::CR_11_σpCoulombσp_11( const std::shared_ptr& Tint, unsigned int) : ParentType(Tint, 0) { assert(Tint->num_func_bra(/* particle */ 0) == 1); @@ -231,7 +231,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( }; switch (oper->descr().component_index()) { - case opCoulombop_Descr::Scalar: { + case σpCoulombσp_Descr::Scalar: { auto Txx = T(x, x); auto Tyy = T(y, y); auto Tzz = T(z, z); @@ -240,7 +240,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( nflops_ += 2; } } break; - case opCoulombop_Descr::AntisymX: { + case σpCoulombσp_Descr::AntisymX: { auto Tyz = T(y, z); auto Tzy = T(z, y); if (is_simple()) { @@ -248,7 +248,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( nflops_ += 1; } } break; - case opCoulombop_Descr::AntisymY: { + case σpCoulombσp_Descr::AntisymY: { auto Tzx = T(z, x); auto Txz = T(x, z); if (is_simple()) { @@ -256,7 +256,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( nflops_ += 1; } } break; - case opCoulombop_Descr::AntisymZ: { + case σpCoulombσp_Descr::AntisymZ: { auto Txy = T(x, y); auto Tyx = T(y, x); if (is_simple()) { @@ -264,7 +264,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( nflops_ += 1; } } break; - case opCoulombop_Descr::SymTLDiagA: { + case σpCoulombσp_Descr::SymTLDiagA: { auto Txx = T(x, x); auto Tyy = T(y, y); if (is_simple()) { @@ -272,7 +272,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( nflops_ += 1; } } break; - case opCoulombop_Descr::SymTLDiagB: { + case σpCoulombσp_Descr::SymTLDiagB: { // 2·T_zz − T_xx − T_yy: child order (Tzz, Txx, Tyy) matches generate_code auto Tzz = T(z, z); auto Txx = T(x, x); @@ -282,7 +282,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( nflops_ += 3; } } break; - case opCoulombop_Descr::SymTLOffXY: { + case σpCoulombσp_Descr::SymTLOffXY: { auto Txy = T(x, y); auto Tyx = T(y, x); if (is_simple()) { @@ -290,7 +290,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( nflops_ += 1; } } break; - case opCoulombop_Descr::SymTLOffXZ: { + case σpCoulombσp_Descr::SymTLOffXZ: { auto Txz = T(x, z); auto Tzx = T(z, x); if (is_simple()) { @@ -298,7 +298,7 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( nflops_ += 1; } } break; - case opCoulombop_Descr::SymTLOffYZ: { + case σpCoulombσp_Descr::SymTLOffYZ: { auto Tyz = T(y, z); auto Tzy = T(z, y); if (is_simple()) { @@ -307,11 +307,11 @@ CR_11_opCoulombop_11::CR_11_opCoulombop_11( } } break; default: - throw std::runtime_error("CR_11_opCoulombop_11: invalid component index"); + throw std::runtime_error("CR_11_σpCoulombσp_11: invalid component index"); } -} // CR_11_opCoulombop_11::CR_11_opCoulombop_11 +} // CR_11_σpCoulombσp_11::CR_11_σpCoulombσp_11 } // namespace libint2 -#endif // LIBINT_COMP_11_OPCOULOMBOP_11_H +#endif // LIBINT_COMP_11_ΣPCOULOMBΣP_11_H diff --git a/src/bin/libint/master_ints_list.h b/src/bin/libint/master_ints_list.h index 2a6a7cd0f..158052fba 100644 --- a/src/bin/libint/master_ints_list.h +++ b/src/bin/libint/master_ints_list.h @@ -118,9 +118,9 @@ typedef GenIntegralSet_11_11 σpσpCoulombσpσp_11_11_sq; typedef GenIntegralSet_11_11 σpσpCoulombσpσp_11_11_int; -typedef GenIntegralSet_11_11 - opCoulombop_11_11_sq; -typedef GenIntegralSet_11_11 opCoulombop_11_11_int; +typedef GenIntegralSet_11_11 + σpCoulombσp_11_11_sq; +typedef GenIntegralSet_11_11 σpCoulombσp_11_11_int; typedef GenIntegralSet_11_11 R12kG12_11_11_sq; typedef GenIntegralSet_11_11 R12kG12_11_11_int; typedef GenIntegralSet_11_11 @@ -163,7 +163,7 @@ typedef boost::mpl::list< typedef boost::mpl::list< TwoPRep_11_11_sq, TwoPRep_11_11_int, Coulombσpσp_11_11_sq, Coulombσpσp_11_11_int, σpσpCoulombσpσp_11_11_sq, σpσpCoulombσpσp_11_11_int, - opCoulombop_11_11_sq, opCoulombop_11_11_int, R12kG12_11_11_sq, + σpCoulombσp_11_11_sq, σpCoulombσp_11_11_int, R12kG12_11_11_sq, R12kG12_11_11_int, R12kR12lG12_11_11_sq, R12kR12lG12_11_11_int, TiG12_11_11_sq, TiG12_11_11_int, G12TiG12_11_11_sq, G12TiG12_11_11_int, DivG12prime_xTx_11_11_sq, DivG12prime_xTx_11_11_int, diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index 067ccddf3..ae4e32311 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include @@ -325,8 +325,8 @@ typedef CR_11_Coulombσpσp_11 CR_11_Coulombσpσp_11_int; typedef CR_11_σpσpCoulombσpσp_11 CR_11_σpσpCoulombσpσp_11_sh; typedef CR_11_σpσpCoulombσpσp_11 CR_11_σpσpCoulombσpσp_11_int; -typedef CR_11_opCoulombop_11 CR_11_opCoulombop_11_sh; -typedef CR_11_opCoulombop_11 CR_11_opCoulombop_11_int; +typedef CR_11_σpCoulombσp_11 CR_11_σpCoulombσp_11_sh; +typedef CR_11_σpCoulombσp_11 CR_11_σpCoulombσp_11_int; }; // namespace libint2 #endif // header guard diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index b231d80cc..29b367378 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -515,7 +515,7 @@ struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr }; typedef GenOper<σpσpCoulombσpσp_Descr> σpσpCoulombσpσpOper; -/** opCoulombop: (μ σ·p ν | 1/r_{12} | κ σ·p λ). +/** σpCoulombσp: (μ σ·p ν | 1/r_{12} | κ σ·p λ). * Gaunt LS "bilinear" operator with one σ·p on each side. * Outputs the SO(3) irreducible decomposition of the 3×3 gradient-gradient * tensor T_{ab} = ∂_a ∂_b (μν|κλ): 1 scalar trace + 3 antisymmetric @@ -524,7 +524,7 @@ typedef GenOper<σpσpCoulombσpσp_Descr> σpσpCoulombσpσpOper; * need to hand-build trace/antisym/sym-TL combinations at every contraction * site. */ -struct opCoulombop_Descr : public Contractable { +struct σpCoulombσp_Descr : public Contractable<σpCoulombσp_Descr> { typedef MultiplicativeSymm2Body_Props Properties; /// SO(3) irreducible components of the rank-2 Cartesian tensor T_{ab}. @@ -540,8 +540,8 @@ struct opCoulombop_Descr : public Contractable { SymTLOffYZ = 8, ///< T_yz + T_zy }; - opCoulombop_Descr() : component_index_(0) {} - opCoulombop_Descr(int component_index) : component_index_(component_index) { + σpCoulombσp_Descr() : component_index_(0) {} + σpCoulombσp_Descr(int component_index) : component_index_(component_index) { assert(component_index >= 0 && component_index <= 8); } @@ -576,7 +576,7 @@ struct opCoulombop_Descr : public Contractable { private: const int component_index_ = -1; }; -typedef GenOper opCoulombopOper; +typedef GenOper<σpCoulombσp_Descr> σpCoulombσpOper; /** GTG_1d is the two-body 1-dimensional Gaussian geminal */ diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index 3f0c36b0c..e01f76a7d 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -136,12 +136,12 @@ struct MasterStrategy<σpσpCoulombσpσp_11_11_int> { typedef boost::mpl::list value; }; template <> -struct MasterStrategy { - typedef boost::mpl::list value; +struct MasterStrategy<σpCoulombσp_11_11_sq> { + typedef boost::mpl::list value; }; template <> -struct MasterStrategy { - typedef boost::mpl::list value; +struct MasterStrategy<σpCoulombσp_11_11_int> { + typedef boost::mpl::list value; }; #if LIBINT_SHELLQUARTET_STRATEGY == LIBINT_SHELLQUARTET_STRATEGY_A0C0 From e5464698cf1a3f1a61f9c27d7fda87af6ed52827 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 12 May 2026 13:18:18 -0400 Subject: [PATCH 22/64] 3-center integrals in RKB basis --- CMakeLists.txt | 12 ++ cmake/modules/int_am.cmake | 15 +- export/tests/unit/test-2body.cc | 141 ++++++++++++++++++ include/libint2/config.h.cmake.in | 19 +++ include/libint2/engine.impl.h | 31 +++- src/bin/libint/build_libint.cc | 235 +++++++++++++++++++++++++----- 6 files changed, 408 insertions(+), 45 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 90b641733..cd2d49359 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,10 @@ option_with_default(LIBINT2_ENABLE_ERI2 option_with_default(LIBINT2_ENABLE_RKB_ERI "Compile with support for up to N-th derivatives of relativistic restricted kinetic balance (RKB) 4-center electron repulsion integrals (-1 for OFF)" 0) +option_with_default(LIBINT2_ENABLE_RKB_ERI3 + "Compile with support for up to N-th derivatives of relativistic restricted kinetic + balance (RKB) 3-center electron repulsion integrals (-1 for OFF). + σ·p acts on the two paired (AO) centers; the unpaired/fitting center is a spectator." -1) option_with_default(LIBINT2_ENABLE_G12 "Compile with support for N-th derivatives of MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) option_with_default(LIBINT2_ENABLE_G12DKH @@ -228,6 +232,14 @@ option_with_default(LIBINT2_RKB_ERI_OPT_AM "Optimize relativistic restricted kinetic balance (RKB) 4-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) +option_with_default(LIBINT2_RKB_ERI3_MAX_AM + "Support relativistic restricted kinetic balance (RKB) 3-center ERIs for Gaussians of angular momentum up to N. + Can specify values for each derivative level as a semicolon-separated string. (default: max_am) + This option controls only the single fitting center. The paired centers (on which σ·p acts) use LIBINT2_MAX_AM." -1) +option_with_default(LIBINT2_RKB_ERI3_OPT_AM + "Optimize relativistic restricted kinetic balance (RKB) 3-center ERIs maximally for up to angular momentum N (N <= max-am). + Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) + option_with_default(LIBINT2_ERI3_MAX_AM "Support 3-center ERIs for Gaussians of angular momentum up to N. diff --git a/cmake/modules/int_am.cmake b/cmake/modules/int_am.cmake index 350924f49..2dbd1904a 100644 --- a/cmake/modules/int_am.cmake +++ b/cmake/modules/int_am.cmake @@ -358,6 +358,7 @@ endmacro() process_integrals_class(ONEBODY) process_integrals_class(ERI) process_integrals_class(RKB_ERI) +process_integrals_class(RKB_ERI3) process_integrals_class(ERI3) process_integrals_class(ERI2) # unlike above, these classes (1) don't do AM_LIST and (2) require value in config.h if enabled @@ -397,7 +398,7 @@ list(REVERSE _amlist) list(APPEND Libint2_ERI_COMPONENTS "${_amlist}") message(VERBOSE "setting components ${_amlist}") -foreach(_cls ONEBODY;ERI;RKB_ERI;ERI3;ERI2;G12;G12DKH) +foreach(_cls ONEBODY;ERI;RKB_ERI;RKB_ERI3;ERI3;ERI2;G12;G12DKH) if((_cls STREQUAL G12) OR (_cls STREQUAL G12DKH)) add_feature_info( "integral class ${_cls}" @@ -448,6 +449,18 @@ foreach(_cls ONEBODY;ERI;RKB_ERI;ERI3;ERI2;G12;G12DKH) endforeach() endforeach() endif() + if (_cls STREQUAL "RKB_ERI3") + # Mirror ERI3 component naming: fitting (single) center on bra, + # paired (AO) centers on ket where σ·p acts. Paired-center AM + # tracks LIBINT_MAX_AM via _eri3_candidate0_d${_d}. + foreach(_lfit RANGE ${LIBINT_HARD_MIN_AM} ${_candidate_${_cls}_d${_d}}) # LIBINT_RKB_ERI3_MAX_AM[_LIST], fitting + foreach(_lpr RANGE ${LIBINT_HARD_MIN_AM} ${_eri3_candidate0_d${_d}}) # LIBINT_MAX_AM[_LIST], paired + if (_lfit GREATER_EQUAL _lpr) + list(APPEND _amlist "rkb_eri_${_am${_lpr}}${_am${_lpr}}${_AM${_lfit}}_d${_d}") + endif() + endforeach() + endforeach() + endif() list(REVERSE _amlist) list(APPEND Libint2_ERI_COMPONENTS "${_amlist}") message(VERBOSE "setting components ${_amlist}") diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index dc0c01b73..e37a2bd99 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -774,6 +774,147 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } } + + SECTION("Coulombσpσp 3-center xs_xx") { + // 3-center RKB Coulombσpσp integral, (P | σ·p_μ σ·p_ν / r12) with P on + // the bra (fitting/DF) and σ·p acting on the AO pair (μ,ν) on the ket. + // + // Reference: the same operator and BraKet computed via the 4-center + // engine with Shell::unit() at the dummy bra position. The 4-center + // engine path is already validated by the "Coulombσpσp and + // σpσpCoulombσpσp" SECTION above against a primitive-eri reference, so + // by transitivity the 4-center engine is a trustworthy reference for the + // 3-center engine. Both code paths share the dummy-shell trick + // internally, but they are independently generated (different task + // labels, different dispatch tables), so this catches dispatch-table / + // codegen wiring mistakes in the new 3-center path. + + Shell dfsh{{1.5}, {{0, false, {1.0}}}, {{-1.0, 0.5, 0.0}}}; + std::vector dfs{dfsh}; + const auto &unitshell = libint2::Shell::unit(); + + Engine engine_3c, engine_4c_ref; + try { + engine_3c = Engine(Operator::coulomb_opop, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + engine_3c.set(BraKet::xs_xx); + // 4-center reference — same operator, default BraKet::xx_xx + engine_4c_ref = Engine(Operator::coulomb_opop, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + } catch (Engine::lmax_exceeded &) { + // skip if libint not configured with -DLIBINT2_ENABLE_RKB_ERI3 >= 0 + // (or the 4-center RKB ERI was disabled) + return; + } + + const auto nshell = obs.size(); + for (int sa = 0; sa != nshell; ++sa) { + for (int sb = 0; sb != nshell; ++sb) { + const auto &results_3c = + engine_3c.compute(dfsh, obs[sa], obs[sb]); + // 4-center reference: (dfsh, unit | sh_a, sh_b). σ·p still acts on + // (C, D) = (sh_a, sh_b), matching the 3-center xs_xx mapping. + const auto &results_4c_ref = + engine_4c_ref.compute(dfsh, unitshell, obs[sa], obs[sb]); + assert(results_3c.size() == 4); + assert(results_4c_ref.size() == 4); + + const auto n_df = dfsh.size(); + const auto n_a = obs[sa].size(); + const auto n_b = obs[sb].size(); + const auto n_total = n_df * n_a * n_b; + // 4-center buffer is (df, unit, sa, sb); n_unit = 1; so the linear + // layout coincides with the 3-center (df, sa, sb) layout. + + const double ABS_TOL = 5.0E-14; + const double REL_TOL = 1.0E-9; + for (auto comp = 0; comp < 4; ++comp) { + for (size_t k = 0; k < n_total; ++k) { + const auto v_3c = results_3c[comp][k]; + const auto v_ref = results_4c_ref[comp][k]; + const auto abs_err = std::abs(v_3c - v_ref); + const auto rel_err = + std::abs(v_ref) > 1e-30 ? std::abs(abs_err / v_ref) : 0.0; + bool not_ok = rel_err > REL_TOL && abs_err > ABS_TOL; + if (not_ok) { + std::cout << "(df | sa=" << sa << " sb=" << sb + << ") comp=" << comp << " elem=" << k + << ": 3c=" << v_3c << " 4c_ref=" << v_ref + << " abs_err=" << abs_err << " rel_err=" << rel_err + << std::endl; + } + REQUIRE(!not_ok); + } + } + } + } + } + + SECTION("Coulombσpσp 3-center xx_xs alias") { + // The xx_xs braket re-routes through the same xs_xx kernel via the + // Engine::compute2 bra↔ket swap. Verify the alias yields the same + // integral values, with the output buffer in the user-requested + // (sh_a, sh_b | dfsh) index layout. + + Shell dfsh{{1.5}, {{0, false, {1.0}}}, {{-1.0, 0.5, 0.0}}}; + std::vector dfs{dfsh}; + + Engine eng_xs, eng_xxs; + try { + eng_xs = Engine(Operator::coulomb_opop, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + eng_xs.set(BraKet::xs_xx); + eng_xxs = Engine(Operator::coulomb_opop, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + eng_xxs.set(BraKet::xx_xs); + } catch (Engine::lmax_exceeded &) { + return; + } + + for (size_t sa = 0; sa != obs.size(); ++sa) { + for (size_t sb = 0; sb != obs.size(); ++sb) { + const auto n_df = dfsh.size(); + const auto n_a = obs[sa].size(); + const auto n_b = obs[sb].size(); + + const auto &res_xs = eng_xs.compute(dfsh, obs[sa], obs[sb]); + // re-set xs braket each loop in case Catch2 SECTION re-entry resets + eng_xxs.set(BraKet::xx_xs); + const auto &res_xxs = eng_xxs.compute(obs[sa], obs[sb], dfsh); + + for (auto c = 0; c < 4; ++c) { + for (size_t i_df = 0; i_df < n_df; ++i_df) { + for (size_t i_a = 0; i_a < n_a; ++i_a) { + for (size_t i_b = 0; i_b < n_b; ++i_b) { + const auto v_xs = + res_xs[c][i_df * n_a * n_b + i_a * n_b + i_b]; + const auto v_xxs = + res_xxs[c][i_a * n_b * n_df + i_b * n_df + i_df]; + const auto abs_err = abs(v_xs - v_xxs); + const auto rel_err = std::abs(v_xs) > 1e-30 + ? double(abs(abs_err / v_xs)) + : 0.0; + bool not_ok = rel_err > 1.0E-9 && abs_err > 5.0E-14; + if (not_ok) { + std::cout << "xx_xs alias mismatch: (sa=" << sa + << " sb=" << sb << ") comp=" << c + << " idx(df=" << i_df << ",a=" << i_a + << ",b=" << i_b << "): xs=" << v_xs + << " xxs=" << v_xxs << " abs_err=" << abs_err + << std::endl; + } + REQUIRE(!not_ok); + } + } + } + } + } + } + } } TEST_CASE("Erfx_Coulomb integrals", "[engine][2-body]") { diff --git a/include/libint2/config.h.cmake.in b/include/libint2/config.h.cmake.in index 3eb32e2f0..e07f3cbc8 100644 --- a/include/libint2/config.h.cmake.in +++ b/include/libint2/config.h.cmake.in @@ -77,6 +77,12 @@ #undef LIBINT_INCLUDE_RKB_ERI #endif +/* Support 3-center RKB ERI derivatives up to this order */ +#define LIBINT_INCLUDE_RKB_ERI3 @LIBINT_INCLUDE_RKB_ERI3@ +#if @LIBINT_INCLUDE_RKB_ERI3@ == -1 +#undef LIBINT_INCLUDE_RKB_ERI3 +#endif + /* Support 3-center ERI derivatives up to this order */ #define LIBINT_INCLUDE_ERI3 @LIBINT_INCLUDE_ERI3@ @@ -141,6 +147,19 @@ /* Max optimized AM for ERI and its derivatives */ #cmakedefine LIBINT_RKB_ERI_OPT_AM_LIST "@LIBINT_RKB_ERI_OPT_AM_LIST@" +/* Max AM for 3-center RKB ERI fitting center (same for all derivatives; if not defined see LIBINT_RKB_ERI3_MAX_AM_LIST). + The paired AO centers (on which σ·p acts) use LIBINT_MAX_AM. */ +#cmakedefine LIBINT_RKB_ERI3_MAX_AM @LIBINT_RKB_ERI3_MAX_AM@ + +/* Max AM for 3-center RKB ERI and its derivatives */ +#cmakedefine LIBINT_RKB_ERI3_MAX_AM_LIST "@LIBINT_RKB_ERI3_MAX_AM_LIST@" + +/* Max optimized AM for 3-center RKB ERI (same for all derivatives; if not defined see LIBINT_RKB_ERI3_OPT_AM_LIST) */ +#cmakedefine LIBINT_RKB_ERI3_OPT_AM @LIBINT_RKB_ERI3_OPT_AM@ + +/* Max optimized AM for 3-center RKB ERI and its derivatives */ +#cmakedefine LIBINT_RKB_ERI3_OPT_AM_LIST "@LIBINT_RKB_ERI3_OPT_AM_LIST@" + /* Max AM for 3-center ERI (same for all derivatives; if not defined see LIBINT_ERI3_MAX_AM_LIST) */ #cmakedefine LIBINT_ERI3_MAX_AM @LIBINT_ERI3_MAX_AM@ diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 01269538d..85a744768 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -174,9 +174,22 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute( if (nargs == 2) return (this->*compute_ptr)(shells[0], Shell::unit(), shells[1], Shell::unit(), nullptr, nullptr); - if (nargs == 3) + if (nargs == 3) { + // The 3-arg user form depends on which 3-center BraKet was set: + // xs_xx — bra = (fitting, unit), ket = (AO, AO). + // User passes (fitting, AO, AO), kernel sees + // (fitting, unit, AO, AO). + // xx_xs — bra = (AO, AO), ket = (fitting, unit). + // User passes (AO, AO, fitting), kernel sees + // (AO, AO, fitting, unit). + // The non-xx_xs branch is the default (covers xs_xx and any future + // 3-center variants that follow the xs_xx convention). + if (braket_ == BraKet::xx_xs) + return (this->*compute_ptr)(shells[0], shells[1], shells[2], + Shell::unit(), nullptr, nullptr); return (this->*compute_ptr)(shells[0], Shell::unit(), shells[1], shells[2], nullptr, nullptr); + } if (nargs == 4) return (this->*compute_ptr)(shells[0], shells[1], shells[2], shells[3], nullptr, nullptr); @@ -2015,9 +2028,11 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( break; case BraKet::xx_xs: - assert(false && "this braket is not supported"); - abort(); - break; + // xx_xs is always canonicalized to xs_xx by swap_braket=true in the + // shell-aliasing logic above; the swapped shells (bra1, ket1, ket2) + // are already in xs_xx layout by this point. Fall through to the + // xs_xx case to compute buildfnidx from those swapped shells. + [[fallthrough]]; case BraKet::xs_xx: { /// lmax might be center dependent int ket_lmax = hard_lmax_; @@ -2243,6 +2258,14 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( } if (swap_tket && oper_ == Operator::coulomb_opop && s > 0) oper_cart_component_phase = -1.0; + // For coulomb_opop in BraKet::xx_xs (the 3-center alias of + // xs_xx): after swap_braket the user's bra (AO pair) becomes + // the kernel's ket where σ·p attaches. If swap_tbra is also + // applied to canonicalize that AO pair, σ·p_a σ·p_b ↔ + // σ·p_b σ·p_a — antisymmetric, so x,y,z components (s>0) + // pick up a -1 sign. (The scalar component s=0 is invariant.) + if (swap_tbra && oper_ == Operator::coulomb_opop && s > 0) + oper_cart_component_phase = -1.0; // op_coulomb_op irrep layout under bra↔ket swap: antisym // components (s ∈ {1,2,3}) flip sign; scalar (0) and sym-TL // (4..8) are invariant. swap_tket is always false for this diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 67700a630..5737e167a 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -234,9 +234,11 @@ static void build_TwoPRep_2b_2k( #endif #endif -#ifdef LIBINT_INCLUDE_ERI3 +#if defined(LIBINT_INCLUDE_ERI3) || defined(LIBINT_INCLUDE_RKB_ERI3) +template static void build_TwoPRep_1b_2k( - std::ostream& os, const std::shared_ptr& cparams, + std::ostream& os, std::string label, + const std::shared_ptr& cparams, std::shared_ptr& iface, unsigned int deriv_level); #endif @@ -640,6 +642,32 @@ void try_main(int argc, char* argv[]) { } #endif +#ifdef LIBINT_INCLUDE_RKB_ERI3 + // 3-center RKB ERIs. σ·p acts on the AO pair (ket); the fitting center is a + // spectator. Task labels are namespaced with the "3" ncenter prefix so they + // match the BOOST_PP_NBODYENGINE_MCR3_task expansion in engine.impl.h: + // ncenter("3") + oper("coulomb_opop") + deriv("") = "3coulomb_opop". + // The macro tuples mirror BOOST_PP_RKB_ERI_TASK_TUPLE but only the operators + // that make sense in xs_xx geometry are listed (opop_coulomb_opop requires + // σ·p on both bra and ket, which is incompatible with one side being a + // dummy-s fitting shell; it is intentionally omitted). +#define BOOST_PP_RKB_ERI3_TASK_TUPLE (3coulomb_opop) +#define BOOST_PP_RKB_ERI3_TASK_OPER_TUPLE (CoulombσpσpOper) +#define BOOST_PP_RKB_ERI3_TASK_LIST \ + BOOST_PP_TUPLE_TO_LIST(BOOST_PP_RKB_ERI3_TASK_TUPLE) +#define BOOST_PP_RKB_ERI3_TASK_OPER_LIST \ + BOOST_PP_TUPLE_TO_LIST(BOOST_PP_RKB_ERI3_TASK_OPER_TUPLE) + + for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI3; ++d) { +#define BOOST_PP_RKB_ERI3_MCR1(r, data, elem) \ + taskmgr.add(task_label(BOOST_PP_STRINGIZE(elem), d)); + + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR1, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR1 + } +#endif + #ifdef LIBINT_INCLUDE_ERI3 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI3; ++d) { taskmgr.add(task_label("3eri", d)); @@ -781,6 +809,75 @@ void try_main(int argc, char* argv[]) { } #endif // LIBINT_INCLUDE_RKB_ERI +#ifdef LIBINT_INCLUDE_RKB_ERI3 + // Per-task CompilationParameters for 3-center RKB ERIs. Parallel structure + // to the LIBINT_INCLUDE_ERI3 block below: + // - center 0 (fitting): LIBINT_RKB_ERI3_MAX_AM[_LIST] + // - centers 1, 2 (AO pair): LIBINT_MAX_AM[_LIST] (paired-center default) + // - num_bf = 3 (3-center dispatch table is 3-dimensional) + for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI3; ++d) { +#if defined(LIBINT_RKB_ERI3_MAX_AM_LIST) +#define BOOST_PP_RKB_ERI3_MCR_MAXAM(r, data, elem) \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + token(LIBINT_RKB_ERI3_MAX_AM_LIST, \ + ',', d)); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_MAXAM, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR_MAXAM +#elif defined(LIBINT_RKB_ERI3_MAX_AM) +#define BOOST_PP_RKB_ERI3_MCR_MAXAM(r, data, elem) \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + LIBINT_RKB_ERI3_MAX_AM); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_MAXAM, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR_MAXAM +#endif +#if defined(LIBINT_RKB_ERI3_OPT_AM_LIST) +#define BOOST_PP_RKB_ERI3_MCR_OPTAM(r, data, elem) \ + cparams->max_am_opt(task_label(BOOST_PP_STRINGIZE(elem), d), \ + token(LIBINT_RKB_ERI3_OPT_AM_LIST, \ + ',', d)); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_OPTAM, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR_OPTAM +#elif defined(LIBINT_RKB_ERI3_OPT_AM) +#define BOOST_PP_RKB_ERI3_MCR_OPTAM(r, data, elem) \ + cparams->max_am_opt(task_label(BOOST_PP_STRINGIZE(elem), d), \ + LIBINT_RKB_ERI3_OPT_AM); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_OPTAM, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR_OPTAM +#endif + // Paired centers (1, 2) follow the default basis AM. +#if defined(LIBINT_MAX_AM_LIST) +#define BOOST_PP_RKB_ERI3_MCR_PAIRED(r, data, elem) \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + cparams->max_am(task_label("default", d)), 1); \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + cparams->max_am(task_label("default", d)), 2); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_PAIRED, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR_PAIRED +#else +#define BOOST_PP_RKB_ERI3_MCR_PAIRED(r, data, elem) \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + cparams->max_am("default"), 1); \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + cparams->max_am("default"), 2); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_PAIRED, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR_PAIRED +#endif + } + for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI3; ++d) { +#define BOOST_PP_RKB_ERI3_MCR_NBF(r, data, elem) \ + cparams->num_bf(task_label(BOOST_PP_STRINGIZE(elem), d), 3); + BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_NBF, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR_NBF + } +#endif // LIBINT_INCLUDE_RKB_ERI3 + #ifdef LIBINT_INCLUDE_ERI3 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI3; ++d) { #if defined(LIBINT_ERI3_MAX_AM_LIST) @@ -1016,9 +1113,20 @@ void try_main(int argc, char* argv[]) { } #endif +#ifdef LIBINT_INCLUDE_RKB_ERI3 + for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI3; ++d) { +#define BOOST_PP_RKB_ERI3_MCR7(r, data, i, elem) \ + build_TwoPRep_1b_2k( \ + os, BOOST_PP_STRINGIZE(elem), cparams, iface, d); + BOOST_PP_LIST_FOR_EACH_I(BOOST_PP_RKB_ERI3_MCR7, _, + BOOST_PP_RKB_ERI3_TASK_LIST) +#undef BOOST_PP_RKB_ERI3_MCR7 + } +#endif + #ifdef LIBINT_INCLUDE_ERI3 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI3; ++d) { - build_TwoPRep_1b_2k(os, cparams, iface, d); + build_TwoPRep_1b_2k(os, "3eri", cparams, iface, d); } #if LIBINT_ERI3_PURE_SH iface->to_params(iface->macro_define("ERI3_PURE_SH", 1)); @@ -1398,14 +1506,22 @@ static void build_TwoPRep_2b_2k( #endif // LIBINT_INCLUDE_ERI || LIBINT_INCLUDE_RKB_ERI -#ifdef LIBINT_INCLUDE_ERI3 +#if defined(LIBINT_INCLUDE_ERI3) || defined(LIBINT_INCLUDE_RKB_ERI3) -void build_TwoPRep_1b_2k(std::ostream& os, - const std::shared_ptr& cparams, - std::shared_ptr& iface, - unsigned int deriv_level) { - const std::string task = task_label("3eri", deriv_level); - typedef TwoPRep_11_11_sq TwoPRep_sh_11_11; +// Templated on OperType so the same dummy-center "3-center via 4-center" +// machinery serves plain TwoPRep (ERI3) and RKB CoulombσpσpOper (RKB_ERI3). +// The composite recurrence (CR_11_Coulombσpσp_11) decomposes σ·p_c σ·p_d into +// derivatives of plain TwoPRep_11_11 children; those children naturally route +// through DerivGaussV2 via the strategy registered for TwoPRep_11_11 — so no +// special wiring is needed here for RKB. +template +static void build_TwoPRep_1b_2k( + std::ostream& os, std::string label, + const std::shared_ptr& cparams, + std::shared_ptr& iface, unsigned int deriv_level) { + const std::string task = task_label(label, deriv_level); + typedef GenIntegralSet_11_11 TwoBody_sh_11_11; + typedef typename OperType::Descriptor OperDescrType; vector shells; const unsigned int lmax = cparams->max_am(task); const unsigned int lmax_default = @@ -1437,6 +1553,8 @@ void build_TwoPRep_1b_2k(std::ostream& os, std::shared_ptr context(new CppCodeContext(cparams)); std::shared_ptr memman(new WorstFitMemoryManager()); + const auto nullaux = typename TwoBody_sh_11_11::AuxIndexType(0u); + for (unsigned int lbra = 0; lbra <= lmax; lbra++) { for (unsigned int lc = 0; lc <= lmax_default; lc++) { for (unsigned int ld = 0; ld <= lmax_default; ld++) { @@ -1462,11 +1580,19 @@ void build_TwoPRep_1b_2k(std::ostream& os, if (!(lbra == lim && lc == lim && ld == lim)) continue; #endif + // operator components: 1 for plain TwoPRep, 4 for RKB CoulombσpσpOper + std::vector descrs(1); + if constexpr (std::is_same::value) { + descrs.resize(0); + for (int p = 0; p != 4; ++p) descrs.emplace_back(OperDescrType(p)); + } + const auto nopers = descrs.size(); + // unroll only if max_am <= cparams->max_am_opt(task) using std::max; const unsigned int max_am = max(max(lc, ld), lbra); const bool need_to_optimize = (max_am <= cparams->max_am_opt(task)); - const bool need_to_unroll = l_to_cgshellsize(lbra) * + const bool need_to_unroll = nopers * l_to_cgshellsize(lbra) * l_to_cgshellsize(lc) * l_to_cgshellsize(ld) <= cparams->unroll_threshold(); @@ -1475,10 +1601,15 @@ void build_TwoPRep_1b_2k(std::ostream& os, ? std::numeric_limits::max() : 0; dg_xxx->registry()->unroll_threshold(unroll_threshold); - dg_xxx->registry()->do_cse(need_to_optimize); - dg_xxx->registry()->condense_expr(condense_expr( - cparams->unroll_threshold(), cparams->max_vector_length() > 1)); - // dg_xxx->registry()->condense_expr(true); + // For multi-component operators (RKB), components share no + // intermediates, so CSE/condense_expr is pure overhead — disable + // (mirrors build_TwoPRep_2b_2k). + const bool do_optimize = (nopers > 1) ? false : need_to_optimize; + dg_xxx->registry()->do_cse(do_optimize); + dg_xxx->registry()->condense_expr( + do_optimize ? condense_expr(cparams->unroll_threshold(), + cparams->max_vector_length() > 1) + : false); // Need to accumulate integrals? dg_xxx->registry()->accumulate_targets(cparams->accumulate_targets()); @@ -1487,7 +1618,7 @@ void build_TwoPRep_1b_2k(std::ostream& os, //////////// // NB translational invariance is now handled by CR_DerivGauss CartesianDerivIterator<3> diter(deriv_level); - std::vector> targets; + std::vector> targets; bool last_deriv = false; do { CGShell a = (dummy_center == 0) ? CGShell::unit() : CGShell(lbra); @@ -1495,8 +1626,12 @@ void build_TwoPRep_1b_2k(std::ostream& os, CGShell c(lc); CGShell d(ld); #if LIBINT_ERI3_PURE_SH - if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); - if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); + // pure-SH on the fitting center is meaningful only for plain ERI3; + // RKB CoulombσpσpOper does not assume the fitting center is pure SH. + if constexpr (std::is_same::value) { + if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); + if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); + } #endif unsigned int center = 0; @@ -1511,19 +1646,21 @@ void build_TwoPRep_1b_2k(std::ostream& os, ++center; } - // use 4-center integrals - std::shared_ptr abcd = - TwoPRep_sh_11_11::Instance(a, b, c, d, mType(0u)); - targets.push_back(abcd); + // emit one target per operator component (1 for plain ERI3, 4 for + // RKB CoulombσpσpOper) + for (unsigned int op = 0; op != descrs.size(); ++op) { + OperType oper(descrs[op]); + std::shared_ptr abcd = + TwoBody_sh_11_11::Instance(a, b, c, d, nullaux, oper); + targets.push_back(abcd); + } last_deriv = diter.last(); if (!last_deriv) diter.next(); } while (!last_deriv); // append all derivatives as targets to the graph - for (std::vector>::const_iterator t = - targets.begin(); - t != targets.end(); ++t) { + for (auto t = targets.begin(); t != targets.end(); ++t) { std::shared_ptr t_ptr = - std::dynamic_pointer_cast(*t); + std::dynamic_pointer_cast(*t); dg_xxx->append_target(t_ptr); } @@ -1536,27 +1673,44 @@ void build_TwoPRep_1b_2k(std::ostream& os, CGShell c(lc); CGShell d(ld); #if LIBINT_ERI3_PURE_SH - if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); - if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); + if constexpr (std::is_same::value) { + if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); + if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); + } #endif - std::shared_ptr abcd = - TwoPRep_sh_11_11::Instance(a, b, c, d, mType(0u)); - abcd_label = abcd->label(); + if constexpr (std::is_same::value) { + OperType oper(descrs[0]); + std::shared_ptr abcd = + TwoBody_sh_11_11::Instance(a, b, c, d, nullaux, oper); + abcd_label = abcd->label(); + } else { + // For RKB operators, build the label by hand (matches the + // build_TwoPRep_2b_2k convention) so that the operator name is + // embedded in the source-file name without depending on the + // TwoPRep label format. + std::ostringstream oss; + oss << "_" << a.label() << "_" << b.label(); + oss << "_" << label; + oss << "_" << c.label() << "_" << d.label(); + abcd_label = oss.str(); + } } // + derivative level (if deriv_level > 0) - std::string label; + std::string eval_label; { - label = ""; + eval_label = ""; if (deriv_level != 0) { std::ostringstream oss; oss << "deriv" << deriv_level; - label += oss.str(); + eval_label += oss.str(); + } + if constexpr (std::is_same::value) { + eval_label += "eri3"; } - label += "eri3"; - label += abcd_label; + eval_label += abcd_label; } - g_progress.current_task = label; + g_progress.current_task = eval_label; g_progress.print(); std::string prefix(cparams->source_directory()); @@ -1566,7 +1720,7 @@ void build_TwoPRep_1b_2k(std::ostream& os, // this will generate code for this targets, and potentially generate // code for its prerequisites GenerateCode(dg_xxx, context, cparams, strat, tactic, memman, - decl_filenames, def_filenames, prefix, label, false); + decl_filenames, def_filenames, prefix, eval_label, false); // update max stack size and # of targets const std::shared_ptr& tparams = @@ -1580,7 +1734,7 @@ void build_TwoPRep_1b_2k(std::ostream& os, ostringstream oss; oss << context->label_to_function_name("libint2_build_" + task) << "[" << lbra << "][" << lc << "][" << ld - << "] = " << context->label_to_function_name(label) + << "] = " << context->label_to_function_name(eval_label) << context->end_of_stat() << endl; iface->to_static_init(oss.str()); @@ -1602,7 +1756,8 @@ void build_TwoPRep_1b_2k(std::ostream& os, } // end of c loop } // end of bra loop } -#endif // LIBINT_INCLUDE_ERI3 + +#endif // LIBINT_INCLUDE_ERI3 || LIBINT_INCLUDE_RKB_ERI3 #ifdef LIBINT_INCLUDE_ERI2 From 88613eb9b63306d30bc6a5fd4666b0fd1e16395c Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sat, 6 Jun 2026 11:56:17 -0400 Subject: [PATCH 23/64] =?UTF-8?q?Rename=20=CF=83pR=CF=83p/oprop=20one-body?= =?UTF-8?q?=20operator=20to=20=CF=83pe=CE=BC=CF=83p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the 1-body σ·p · r · σ·p ("σ·p-dressed dipole", SS-block dipole) operator from oprop/σpRσp to σpeμσp for consistency with the σpXσp naming of its sibling RKB operators and with the mpqc4 consumer name (Operator::Type::σpeμσp). Mechanical rename across the compiler descriptor (oper.h σpRσp_Descr → σpeμσp_Descr, label opRop[] → opemuop[]), the master ints/rrs lists, strategy.cc specializations, build_libint.cc task tuples and make_descr, the runtime enum (Operator::oprop → σpeμσp) and operator_traits, the operator-list macro / compute_primdata in engine.impl.h, and the 1-body unit test. ASCII codegen label is "opemuop"; runtime enum is σpeμσp. --- export/tests/unit/test-1body.cc | 10 +++++----- include/libint2/engine.h | 6 +++--- include/libint2/engine.impl.h | 8 ++++---- src/bin/libint/build_libint.cc | 10 +++++----- .../comp_1_\317\203pe\316\274\317\203p_1.h" | 18 +++++++++--------- src/bin/libint/master_ints_list.h | 8 ++++---- src/bin/libint/master_rrs_list.h | 6 +++--- src/bin/libint/oper.h | 12 ++++++------ src/bin/libint/strategy.cc | 8 ++++---- 9 files changed, 43 insertions(+), 43 deletions(-) rename "src/bin/libint/comp_1_\317\203pR\317\203p_1.h" => "src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" (90%) diff --git a/export/tests/unit/test-1body.cc b/export/tests/unit/test-1body.cc index 5d0458b60..e087dbdf2 100644 --- a/export/tests/unit/test-1body.cc +++ b/export/tests/unit/test-1body.cc @@ -244,7 +244,7 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "W correctness", #endif // LIBINT2_SUPPORT_ONEBODY } -TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpRσp correctness", +TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpeμσp correctness", "[engine][1-body]") { #if defined(LIBINT2_SUPPORT_ONEBODY) if (LIBINT_SHGSHELL_ORDERING != LIBINT_SHGSHELL_ORDERING_STANDARD) return; @@ -257,13 +257,13 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpRσp correctness", Shell{{1.0, 3.0}, {{0, true, {1.0, 0.3}}}, {{0.0, 0.0, 0.0}}}, Shell{{2.0, 5.0}, {{2, true, {1.0, 0.2}}}, {{1.0, 1.0, 1.0}}}}; - const auto lmax = std::min(2, LIBINT2_MAX_AM_oprop); + const auto lmax = std::min(2, LIBINT2_MAX_AM_opemuop); if (lmax < 2) return; - auto engine = Engine(Operator::oprop, 2, lmax); + auto engine = Engine(Operator::σpeμσp, 2, lmax); engine.set_params(std::array{{0.0, 0.0, 0.0}}); - // (s|σpRσp|d) and (d|σpRσp|s) + // (s|σpeμσp|d) and (d|σpeμσp|s) engine.compute(obs[0], obs[1]); std::array, 12> ab; for (int c = 0; c < 12; ++c) { @@ -280,7 +280,7 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpRσp correctness", ba[c].assign(buf, buf + (5 * 1)); // n_d_pure × n_s } - // Hermiticity check: σpRσp is Hermitian, but the Pauli identity routes the + // Hermiticity check: σpeμσp is Hermitian, but the Pauli identity routes the // imaginary i factor on the antisym pieces into a real-stored sign flip. // q=0 trace: matrix is symmetric ⇒ ab[0+k][i,j] == ba[0+k][j,i] // q=1..3 : matrix is antisym ⇒ ab[q+k][i,j] == -ba[q+k][j,i] diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 0e04b085d..97d800982 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -197,7 +197,7 @@ enum class Operator { /// components (trace + 3 antisym), indexed as `4*k + q` with `k ∈ {x,y,z}` /// the dipole direction and `q ∈ {0=trace, 1=σ_x, 2=σ_y, 3=σ_z}` the Pauli /// piece. Origin set via `engine.set_params(std::array)`. - oprop, + σpeμσp, /// \f$ \delta(\vec{r}_1 - \vec{r}_2) \f$ delta, /// (2-body) Coulomb operator = \f$ r_{12}^{-1} \f$ @@ -250,7 +250,7 @@ enum class Operator { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!keep this // updated!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! first_1body_oper = overlap, - last_1body_oper = oprop, + last_1body_oper = σpeμσp, first_2body_oper = delta, last_2body_oper = stg_x_coulomb, first_oper = first_1body_oper, @@ -437,7 +437,7 @@ struct operator_traits }; template <> -struct operator_traits +struct operator_traits : public operator_traits { static constexpr auto nopers = 12; static constexpr auto intrinsic_deriv_order = 2; diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 85a744768..a4d0e9eb6 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -84,7 +84,7 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { (sphemultipole, /* sphemultipole */ \ (opVop, /* opVop */ \ (opVop, /* op_q_gau_op */ \ - (oprop, /* oprop */ \ + (opemuop, /* opemuop */ \ (eri, /* delta */ \ (eri, /* coulomb */ \ (coulomb_opop, /* coulomb_opop */ \ @@ -106,7 +106,7 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { #define BOOST_PP_NBODY_OPERATOR_INDEX_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_NBODY_OPERATOR_INDEX_TUPLE) #define BOOST_PP_NBODY_OPERATOR_LAST_ONEBODY_INDEX \ - 13 // oprop, the 14th member of BOOST_PP_NBODY_OPERATOR_LIST, is the last + 13 // opemuop, the 14th member of BOOST_PP_NBODY_OPERATOR_LIST, is the last // 1-body operator // make list of braket indices for n-body ints @@ -1068,7 +1068,7 @@ __libint2_engine_inline void Engine::compute_primdata(Libint_t& primdata, // } if (oper_ == Operator::emultipole1 || oper_ == Operator::emultipole2 || - oper_ == Operator::emultipole3 || oper_ == Operator::oprop) { + oper_ == Operator::emultipole3 || oper_ == Operator::σpeμσp) { const auto& O = any_cast< const operator_traits::oper_params_type&>( params_); // same as emultipoleX @@ -1117,7 +1117,7 @@ __libint2_engine_inline void Engine::compute_primdata(Libint_t& primdata, if (oper_ == Operator::kinetic || (deriv_order_ > 0) || oper_ == Operator::opVop || oper_ == Operator::op_q_gau_op || - oper_ == Operator::oprop) { + oper_ == Operator::σpeμσp) { #if LIBINT2_DEFINED(eri, two_alpha0_bra) primdata.two_alpha0_bra[0] = 2.0 * alpha1; #endif diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 5737e167a..02e3cd6cb 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -311,8 +311,8 @@ template <> } template <> -σpRσp_Descr make_descr<σpRσp_Descr>(int p, int, int) { - return σpRσp_Descr(p); +σpeμσp_Descr make_descr<σpeμσp_Descr>(int p, int, int) { + return σpeμσp_Descr(p); } template <> @@ -452,7 +452,7 @@ void build_onebody_1b_1k(std::ostream& os, std::string label, descrs.emplace_back(make_descr(p)); } } - if (std::is_same<_OperType, σpRσpOper>::value) { + if (std::is_same<_OperType, σpeμσpOper>::value) { // reset descriptors array descrs.resize(0); // iterate over 12 = 3 dipole directions × 4 Pauli components @@ -599,11 +599,11 @@ void try_main(int argc, char* argv[]) { // overlap, kinetic, elecpot cannot be omitted #define BOOST_PP_ONEBODY_TASK_TUPLE \ (overlap, kinetic, elecpot, 1emultipole, 2emultipole, 3emultipole, \ - sphemultipole, opVop, oprop) + sphemultipole, opVop, opemuop) #define BOOST_PP_ONEBODY_TASK_OPER_TUPLE \ (OverlapOper, KineticOper, ElecPotOper, CartesianMultipoleOper<3u>, \ CartesianMultipoleOper<3u>, CartesianMultipoleOper<3u>, \ - SphericalMultipoleOper, σpVσpOper, σpRσpOper) + SphericalMultipoleOper, σpVσpOper, σpeμσpOper) #define BOOST_PP_ONEBODY_TASK_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_ONEBODY_TASK_TUPLE) #define BOOST_PP_ONEBODY_TASK_OPER_LIST \ diff --git "a/src/bin/libint/comp_1_\317\203pR\317\203p_1.h" "b/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" similarity index 90% rename from "src/bin/libint/comp_1_\317\203pR\317\203p_1.h" rename to "src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" index e698c9047..128283ab6 100644 --- "a/src/bin/libint/comp_1_\317\203pR\317\203p_1.h" +++ "b/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" @@ -37,14 +37,14 @@ namespace libint2 { * @tparam F basis function type. valid choices are CGShell or CGF */ template -class CR_1_σpRσp_1 +class CR_1_σpeμσp_1 : public GenericRecurrenceRelation< - CR_1_σpRσp_1, F, GenIntegralSet_1_1> { + CR_1_σpeμσp_1, F, GenIntegralSet_1_1> { public: - typedef CR_1_σpRσp_1 ThisType; + typedef CR_1_σpeμσp_1 ThisType; typedef F BasisFunctionType; - typedef σpRσpOper OperType; - typedef GenIntegralSet_1_1 TargetType; + typedef σpeμσpOper OperType; + typedef GenIntegralSet_1_1 TargetType; typedef GenericRecurrenceRelation ParentType; friend class GenericRecurrenceRelation &, unsigned int = 0); + CR_1_σpeμσp_1(const std::shared_ptr &, unsigned int = 0); static std::string descr() { return "CR"; } }; template -CR_1_σpRσp_1::CR_1_σpRσp_1(const std::shared_ptr &Tint, +CR_1_σpeμσp_1::CR_1_σpeμσp_1(const std::shared_ptr &Tint, unsigned int) : ParentType(Tint, 0) { assert(Tint->num_func_bra(/* particle */ 0) == 1); @@ -151,10 +151,10 @@ CR_1_σpRσp_1::CR_1_σpRσp_1(const std::shared_ptr &Tint, } } break; default: - throw std::runtime_error("CR_1_σpRσp_1: invalid quaternionic index"); + throw std::runtime_error("CR_1_σpeμσp_1: invalid quaternionic index"); } -} // CR_1_σpRσp_1::CR_1_σpRσp_1 +} // CR_1_σpeμσp_1::CR_1_σpeμσp_1 }; // namespace libint2 diff --git a/src/bin/libint/master_ints_list.h b/src/bin/libint/master_ints_list.h index 158052fba..17c071c34 100644 --- a/src/bin/libint/master_ints_list.h +++ b/src/bin/libint/master_ints_list.h @@ -50,8 +50,8 @@ typedef GenIntegralSet_1_1 ElecPot_1_1_sh; typedef GenIntegralSet_1_1 ElecPot_1_1_int; typedef GenIntegralSet_1_1 σpVσp_1_1_sh; typedef GenIntegralSet_1_1 σpVσp_1_1_int; -typedef GenIntegralSet_1_1 σpRσp_1_1_sh; -typedef GenIntegralSet_1_1 σpRσp_1_1_int; +typedef GenIntegralSet_1_1 σpeμσp_1_1_sh; +typedef GenIntegralSet_1_1 σpeμσp_1_1_int; typedef GenIntegralSet_1_1, EmptySet> CMultipole_1_1_sh; typedef GenIntegralSet_1_1, EmptySet> @@ -153,8 +153,8 @@ typedef boost::mpl::list< Overlap_1_1_sh_y, Overlap_1_1_int_y, Overlap_1_1_sh_z, Overlap_1_1_int_z, Kinetic_1_1_sh, Kinetic_1_1_int, Kinetic_1_1_sh_x, Kinetic_1_1_int_x, Kinetic_1_1_sh_y, Kinetic_1_1_int_y, Kinetic_1_1_sh_z, Kinetic_1_1_int_z, - ElecPot_1_1_sh, ElecPot_1_1_int, σpVσp_1_1_sh, σpVσp_1_1_int, σpRσp_1_1_sh, - σpRσp_1_1_int, CMultipole_1_1_sh, CMultipole_1_1_int, CMultipole_1_1_sh_x, + ElecPot_1_1_sh, ElecPot_1_1_int, σpVσp_1_1_sh, σpVσp_1_1_int, σpeμσp_1_1_sh, + σpeμσp_1_1_int, CMultipole_1_1_sh, CMultipole_1_1_int, CMultipole_1_1_sh_x, CMultipole_1_1_sh_y, CMultipole_1_1_sh_z, CMultipole_1_1_int_x, CMultipole_1_1_int_y, CMultipole_1_1_int_z, SMultipole_1_1_sh, #endif diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index ae4e32311..490b5e4ec 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include @@ -183,8 +183,8 @@ typedef VRR_1_ElecPot_1 VRR_b_1_ElecPot_1_int; typedef CR_1_σpVσp_1 CR_1_σpVσp_1_sh; typedef CR_1_σpVσp_1 CR_1_σpVσp_1_int; -typedef CR_1_σpRσp_1 CR_1_σpRσp_1_sh; -typedef CR_1_σpRσp_1 CR_1_σpRσp_1_int; +typedef CR_1_σpeμσp_1 CR_1_σpeμσp_1_sh; +typedef CR_1_σpeμσp_1 CR_1_σpeμσp_1_int; // TODO investigate whether need to stay away from HRR for now to be sure that // multipoles are computed as precisely as possible diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index 29b367378..c40eb11f2 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -321,17 +321,17 @@ struct σpVσp_Descr : public Contractable<σpVσp_Descr> { }; typedef GenOper<σpVσp_Descr> σpVσpOper; -/** opRop: (μ σ·p | r | σ·p ν), one-body σ·p-on-both-sides analog of dipole. +/** opemuop: (μ σ·p | r | σ·p ν), one-body σ·p-on-both-sides analog of dipole. * σ_a σ_b = δ_ab + iε_abc σ_c folds the 9 raw σ_a∂_a r_k σ_b∂_b dyadics per * dipole direction k down to 4 Pauli-quaternion components (trace + 3 * antisym), mirroring σpVσp's fold of σ·p V σ·p. 12 outputs total = 3 dipole * directions × 4 Pauli components, indexed composite_index = 4·k + q. */ -struct σpRσp_Descr : public Contractable<σpRσp_Descr> { +struct σpeμσp_Descr : public Contractable<σpeμσp_Descr> { typedef MultiplicativeODep1Body_Props Properties; - σpRσp_Descr() : composite_index_(0) {} - σpRσp_Descr(int composite_index) : composite_index_(composite_index) { + σpeμσp_Descr() : composite_index_(0) {} + σpeμσp_Descr(int composite_index) : composite_index_(composite_index) { assert(composite_index >= 0 && composite_index < 12); } @@ -342,7 +342,7 @@ struct σpRσp_Descr : public Contractable<σpRσp_Descr> { static const char* pauli_lbl[] = {"0", "X", "Y", "Z"}; const auto ci = composite_index(); if (ci < 0 || ci >= 12) abort(); - return std::string("opRop[") + dipole_lbl[ci / 4] + "," + + return std::string("opemuop[") + dipole_lbl[ci / 4] + "," + pauli_lbl[ci % 4] + "]"; } std::string label() const { return description(); } @@ -358,7 +358,7 @@ struct σpRσp_Descr : public Contractable<σpRσp_Descr> { private: const int composite_index_ = -1; }; -typedef GenOper<σpRσp_Descr> σpRσpOper; +typedef GenOper<σpeμσp_Descr> σpeμσpOper; /// cartesian multipole operator in \c NDIM dimensions /// \f$ \hat{O}(\vec{k}) \equiv \vec{r}^{\cdot \vec{k}} = r_1^{k_1} r_2^{k_2} diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index e01f76a7d..8e73cad47 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -315,12 +315,12 @@ struct MasterStrategy<σpVσp_1_1_int> { typedef boost::mpl::list value; }; template <> -struct MasterStrategy<σpRσp_1_1_sh> { - typedef boost::mpl::list value; +struct MasterStrategy<σpeμσp_1_1_sh> { + typedef boost::mpl::list value; }; template <> -struct MasterStrategy<σpRσp_1_1_int> { - typedef boost::mpl::list value; +struct MasterStrategy<σpeμσp_1_1_int> { + typedef boost::mpl::list value; }; template <> struct MasterStrategy { From f1d80c40fbacdbe6c143f99cefa145005f70e24a Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sat, 6 Jun 2026 11:57:02 -0400 Subject: [PATCH 24/64] =?UTF-8?q?Add=203-center=20single-=CF=83=C2=B7p=20D?= =?UTF-8?q?F-Gaunt=20operator=20coulomb=5Fop=20(Coulomb=CF=83p)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New 2-body operator Coulombσp / Operator::coulomb_op: the 3-center single-σ·p "DF-Gaunt" B-factor (P | 1/r12 | μ, σ·p ν), with the fitting function P a spectator on the bra (BraKet::xs_xx) and a single σ·p acting on the 2nd ket AO function ν. Unlike Coulombσpσp (two σ·p on the ket pair, folded via the Dirac identity into 4 quaternion components), a single σ·p does no σ·σ folding: the result is the bare Cartesian gradient of ν — the SO(3) vector irrep with 3 components (x,y,z). Each component is exactly one first-derivative ERI child, routing through the TwoPRep DerivGaussV2 strategy. 3-center only (no 4-center sibling); feeds mpqc4 DF-Gaunt B_G. Wiring: - oper.h: Coulombσp_Descr (max_key=3, cartesian_index, label coulomb_op[X/Y/Z]). - comp_11_Coulombσp_11.h (new): CR_11_Coulombσp_11 — single deriv child d_k, element-wise-copy generate_code (0 flops), shared across shell combos. - master_ints_list.h / master_rrs_list.h / strategy.cc: register the int set, RR, and MasterStrategy. - build_libint.cc: RKB_ERI3 task tuples add 3coulomb_op; build_TwoPRep_1b_2k emits the FULL (lc,ld) square for CoulombσpOper (σ·p on ν only ⇒ μ↔ν is not a symmetry) and 3 operator components; TWOBODY_TASKOPER adds "coulomb_op" so LIBINT2_TASK_EXISTS_3coulomb_op is defined. - engine.h: enum coulomb_op + operator_traits (nopers=3, deriv_order=1). - engine.impl.h: operator-list entry, SIMPLE_CORE_EVAL_CASE, and a symmetry branch (swap_braket on xx_xs; no within-ket swap, no component remap). - test-2body.cc: "Coulombσp 3-center xs_xx" validates vs primitive eri() with a unit s-shell at bra-2 and a single first derivative on ν. --- export/tests/unit/test-2body.cc | 108 +++++++++ include/libint2/engine.h | 14 ++ include/libint2/engine.impl.h | 25 ++- src/bin/libint/build_libint.cc | 36 ++- .../bin/libint/comp_11_Coulomb\317\203p_11.h" | 211 ++++++++++++++++++ src/bin/libint/master_ints_list.h | 5 +- src/bin/libint/master_rrs_list.h | 4 + src/bin/libint/oper.h | 42 ++++ src/bin/libint/strategy.cc | 8 + 9 files changed, 440 insertions(+), 13 deletions(-) create mode 100644 "src/bin/libint/comp_11_Coulomb\317\203p_11.h" diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index e37a2bd99..b4a0392b8 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -915,6 +915,114 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } } + + SECTION("Coulombσp 3-center xs_xx") { + // 3-center single-σ·p ("DF-Gaunt" B-factor) integral + // (P | 1/r12 | μ, σ·p ν), + // with the fitting function P on the bra (BraKet::xs_xx) and a single σ·p + // acting on the 2nd ket AO function ν only. The 3 Cartesian components + // (x,y,z) are the bare gradient of ν, so the reference is the 3-center ERI + // with a *single first derivative on ν* (the shell that carries σ·p) — the + // other geometric derivatives are not needed. + // + // The 3-center ERI is evaluated with the primitive eri() reference by + // placing a unit s-shell (alpha=0, at the origin) at the dummy bra-2 + // position, exactly as the engine does internally via Shell::unit(): the + // 3-arg compute(df, μ, ν) maps to the 4-center order (df, unit, μ, ν) = + // (A,B,C,D), so σ·p on ν is the derivative on center D (der_idx slots + // 9..11). See the "Coulombσpσp and σpσpCoulombσpσp" SECTION for the + // (un-derivatived) primitive-eri reference pattern. + + Shell dfsh{{1.5}, {{0, false, {1.0}}}, {{-1.0, 0.5, 0.0}}}; + std::vector dfs{dfsh}; + + Engine engine_3c; + try { + engine_3c = Engine(Operator::coulomb_op, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + engine_3c.set(BraKet::xs_xx); + } catch (Engine::lmax_exceeded &) { + // skip if libint not configured with -DLIBINT2_ENABLE_RKB_ERI3 >= 0 + return; + } + + // unit (dummy) shell at the bra-2 position: alpha=0, l=0, at the origin. + LIBINT2_REF_REALTYPE Bref[3] = {0.0, 0.0, 0.0}; + const LIBINT2_REF_REALTYPE alpha_unit = 0.0; + + const auto nshell = obs.size(); + for (int sa = 0; sa != nshell; ++sa) { + for (int sb = 0; sb != nshell; ++sb) { + const auto &results_3c = engine_3c.compute(dfsh, obs[sa], obs[sb]); + assert(results_3c.size() == 3); + + LIBINT2_REF_REALTYPE Aref[3], Cref[3], Dref[3]; + for (int i = 0; i < 3; ++i) Aref[i] = dfsh.O[i]; + for (int i = 0; i < 3; ++i) Cref[i] = obs[sa].O[i]; + for (int i = 0; i < 3; ++i) Dref[i] = obs[sb].O[i]; + + int ijk = 0; + + int l0, m0, n0; + FOR_CART(l0, m0, n0, dfsh.contr[0].l) // P (fitting, s) + int l2, m2, n2; + FOR_CART(l2, m2, n2, obs[sa].contr[0].l) // μ + int l3, m3, n3; + FOR_CART(l3, m3, n3, obs[sb].contr[0].l) // ν (σ·p acts here) + + std::array ref_coulomb_op{0.0, 0.0, 0.0}; + + for (uint p0 = 0; p0 < dfsh.nprim(); p0++) { + for (uint p2 = 0; p2 < obs[sa].nprim(); p2++) { + for (uint p3 = 0; p3 < obs[sb].nprim(); p3++) { + const LIBINT2_REF_REALTYPE alpha0 = dfsh.alpha[p0]; + const LIBINT2_REF_REALTYPE alpha2 = obs[sa].alpha[p2]; + const LIBINT2_REF_REALTYPE alpha3 = obs[sb].alpha[p3]; + const LIBINT2_REF_REALTYPE c0 = dfsh.contr[0].coeff[p0]; + const LIBINT2_REF_REALTYPE c2 = obs[sa].contr[0].coeff[p2]; + const LIBINT2_REF_REALTYPE c3 = obs[sb].contr[0].coeff[p3]; + const LIBINT2_REF_REALTYPE c023 = c0 * c2 * c3; // unit coeff = 1 + + // single first derivative on ν (center D), direction k. + auto eri_dD = [&](int k) { + der_idx d = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + d[9 + k] = 1; // slots 9,10,11 = D_x, D_y, D_z + return eri(d.data(), l0, m0, n0, alpha0, Aref, 0, 0, 0, + alpha_unit, Bref, l2, m2, n2, alpha2, Cref, l3, m3, + n3, alpha3, Dref, 0); + }; + + for (int k = 0; k < 3; ++k) ref_coulomb_op[k] += c023 * eri_dD(k); + } + } + } + + const double ABS_TOL = 5.0E-14; + const double REL_TOL = 1.0E-9; + for (auto comp = 0; comp < 3; ++comp) { + const auto v_3c = results_3c[comp][ijk]; + const auto v_ref = ref_coulomb_op[comp]; + const auto abs_err = std::abs(v_3c - v_ref); + const auto rel_err = + std::abs(v_ref) > 1e-30 ? std::abs(abs_err / v_ref) : 0.0; + bool not_ok = rel_err > REL_TOL && abs_err > ABS_TOL; + if (not_ok) { + std::cout << "(df | sa=" << sa << " sb=" << sb << ") comp=" << comp + << " elem=" << ijk << ": 3c=" << v_3c << " ref=" << v_ref + << " abs_err=" << abs_err << " rel_err=" << rel_err + << std::endl; + } + REQUIRE(!not_ok); + } + + ++ijk; + END_FOR_CART + END_FOR_CART + END_FOR_CART + } + } + } } TEST_CASE("Erfx_Coulomb integrals", "[engine][2-body]") { diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 97d800982..01cbda75a 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -207,6 +207,12 @@ enum class Operator { /// (2-body) \f$ r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ where k1 & k2 are /// centers of ket1 and ket2, respectively coulomb_opop, + /// (2-body) \f$ r_{12}^{-1} (σ.p_{k2}) \f$ where k2 is the center of ket2; + /// the 3-center single-σ·p "DF-Gaunt" B-factor (P | μ, σ·p ν). σ·p acts on + /// the 2nd ket AO function only. Produces 3 Cartesian components (x=0, y=1, + /// z=2) — the SO(3) vector irrep; no σ·σ folding (cf. coulomb_opop). The + /// fitting function sits on the bra (BraKet::xs_xx); 3-center only. + coulomb_op, /// (2-body) \f$ (σ.p_{b1})(σ.p_{b2}) r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ /// where b1 & b2 are centers of bra1 and bra2 and k1 & k2 are centers of /// ket1 and ket2, respectively @@ -460,6 +466,14 @@ struct operator_traits static constexpr auto intrinsic_deriv_order = 2; }; template <> +struct operator_traits + : public operator_traits { + /// 3 Cartesian components of the single σ·p applied to the 2nd ket function. + /// index = a, a ∈ {x=0, y=1, z=2}. One intrinsic derivative (σ·p = one ∇). + static constexpr auto nopers = 3; + static constexpr auto intrinsic_deriv_order = 1; +}; +template <> struct operator_traits : public operator_traits { /// 16 components: tensor product of two independent spin-space quaternions diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index a4d0e9eb6..698abcf6d 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -88,8 +88,9 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { (eri, /* delta */ \ (eri, /* coulomb */ \ (coulomb_opop, /* coulomb_opop */ \ - (opop_coulomb_opop, /* opop_coulomb_opop */ \ - (op_coulomb_op, /* op_coulomb_op */ \ + (coulomb_op, /* coulomb_op */ \ + (opop_coulomb_opop, /* opop_coulomb_opop */ \ + (op_coulomb_op, /* op_coulomb_op */ \ (eri, /* cgtg */ \ (eri, /* cgtg_x_coulomb */ \ (eri, /* delcgtg2 */ \ @@ -99,7 +100,7 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { (eri, /* erfx_coulomb */ \ (eri, /* stg */ \ (eri, /* yukawa */ \ - BOOST_PP_NIL)))))))))))))))))))))))))))) + BOOST_PP_NIL))))))))))))))))))))))))))))) #define BOOST_PP_NBODY_OPERATOR_INDEX_TUPLE \ BOOST_PP_MAKE_TUPLE(BOOST_PP_LIST_SIZE(BOOST_PP_NBODY_OPERATOR_LIST)) @@ -1320,6 +1321,16 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( braket_ == BraKet::xx_xs; swap_tbra = false; swap_tket = false; + } else if (oper_ == Operator::coulomb_op) { + // Coulombσp (3-center single-σ·p): σ·p attaches to the 2nd ket function ν, + // so within-ket swap is NOT a symmetry (it would move σ·p onto μ). The + // kernel is generated for every (lc,ld), so no canonicalizing swap is + // needed. bra↔ket swap IS a symmetry of 1/r12 and leaves the 3 Cartesian + // components unchanged (the vector index is the ket-side σ·p direction + // only — no transpose, no sign flip). Only xx_xs needs a braket swap. + swap_braket = (braket_ == BraKet::xx_xs); + swap_tbra = false; + swap_tket = false; } else { swap_braket = ((braket_ == BraKet::xx_xx) && (tbra1.contr[0].l + tbra2.contr[0].l > @@ -1362,6 +1373,13 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( braket_ == BraKet::xx_xs; swap_tbra = false; swap_tket = false; + } else if (oper_ == Operator::coulomb_op) { + // Coulombσp (3-center single-σ·p): see the STANDARD-ordering branch above. + // Within-ket swap is NOT a symmetry (σ·p attaches to ν); bra↔ket leaves + // the 3 Cartesian components unchanged. Only xx_xs needs a braket swap. + swap_braket = (braket_ == BraKet::xx_xs); + swap_tbra = false; + swap_tket = false; } else { swap_tbra = (tbra1.contr[0].l > tbra2.contr[0].l); swap_tket = (tket1.contr[0].l > tket2.contr[0].l); @@ -1581,6 +1599,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( switch (oper_) { LIBINT2_SIMPLE_CORE_EVAL_CASE(coulomb); LIBINT2_SIMPLE_CORE_EVAL_CASE(coulomb_opop); + LIBINT2_SIMPLE_CORE_EVAL_CASE(coulomb_op); LIBINT2_SIMPLE_CORE_EVAL_CASE(opop_coulomb_opop); LIBINT2_SIMPLE_CORE_EVAL_CASE(op_coulomb_op); case Operator::cgtg_x_coulomb: { diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 02e3cd6cb..944bc4192 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -651,8 +651,12 @@ void try_main(int argc, char* argv[]) { // that make sense in xs_xx geometry are listed (opop_coulomb_opop requires // σ·p on both bra and ket, which is incompatible with one side being a // dummy-s fitting shell; it is intentionally omitted). -#define BOOST_PP_RKB_ERI3_TASK_TUPLE (3coulomb_opop) -#define BOOST_PP_RKB_ERI3_TASK_OPER_TUPLE (CoulombσpσpOper) + // - 3coulomb_opop (CoulombσpσpOper): two σ·p on the AO ket pair (4 comps) + // - 3coulomb_op (CoulombσpOper): single σ·p on the 2nd AO ket function + // ν (3 Cartesian comps, DF-Gaunt B). No + // 4-center sibling — 3-center only. +#define BOOST_PP_RKB_ERI3_TASK_TUPLE (3coulomb_opop, 3coulomb_op) +#define BOOST_PP_RKB_ERI3_TASK_OPER_TUPLE (CoulombσpσpOper, CoulombσpOper) #define BOOST_PP_RKB_ERI3_TASK_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_RKB_ERI3_TASK_TUPLE) #define BOOST_PP_RKB_ERI3_TASK_OPER_LIST \ @@ -1558,10 +1562,20 @@ static void build_TwoPRep_1b_2k( for (unsigned int lbra = 0; lbra <= lmax; lbra++) { for (unsigned int lc = 0; lc <= lmax_default; lc++) { for (unsigned int ld = 0; ld <= lmax_default; ld++) { - // eliminate some cases depending on the desired convention - if (!ShellTripletSetPredicate( - LIBINT_SHELL_SET)>::value(lbra, lc, ld)) - continue; + // eliminate some cases depending on the desired convention. + // The Standard/ORCA triangle (lc>=ld / lc<=ld) is valid only when the + // ket pair (μ,ν) is permutationally symmetric, so the engine can + // canonicalize by swapping μ↔ν. CoulombσpOper has a single σ·p on ν + // only, so that swap is NOT a symmetry — the engine never swaps and + // indexes the build table by the exact (l(μ),l(ν)); we must therefore + // emit the FULL (lc,ld) square. (Plain ERI3 and CoulombσpσpOper keep + // the triangle: ERI3's ket pair is symmetric, and Coulombσpσp carries + // σ·p on both ket functions so μ↔ν stays a symmetry.) + if constexpr (!std::is_same::value) { + if (!ShellTripletSetPredicate( + LIBINT_SHELL_SET)>::value(lbra, lc, ld)) + continue; + } // I will use 4-center recurrence relations and integrals, and have one // center carry an s function unfortunately, depending on the direction @@ -1581,10 +1595,14 @@ static void build_TwoPRep_1b_2k( #endif // operator components: 1 for plain TwoPRep, 4 for RKB CoulombσpσpOper + // (σ·p on both AO ket fns), 3 for RKB CoulombσpOper (single σ·p → x,y,z) std::vector descrs(1); if constexpr (std::is_same::value) { descrs.resize(0); for (int p = 0; p != 4; ++p) descrs.emplace_back(OperDescrType(p)); + } else if constexpr (std::is_same::value) { + descrs.resize(0); + for (int p = 0; p != 3; ++p) descrs.emplace_back(OperDescrType(p)); } const auto nopers = descrs.size(); @@ -2575,9 +2593,9 @@ void config_to_api(const std::shared_ptr& cparams, { // 2-body ints -#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ - ("eri", "coulomb_opop", "opop_coulomb_opop", "op_coulomb_op", "r12kg12", \ - "r12_0_g12", "r12_2_g12", "g12_T1_g12", "g12dkh") +#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ + ("eri", "coulomb_opop", "coulomb_op", "opop_coulomb_opop", "op_coulomb_op", \ + "r12kg12", "r12_0_g12", "r12_2_g12", "g12_T1_g12", "g12dkh") #define BOOST_PP_TWOBODY_TASKOPER_LIST \ BOOST_PP_TUPLE_TO_LIST(BOOST_PP_TWOBODY_TASKOPER_TUPLE) diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p_11.h" new file mode 100644 index 000000000..f51179a9e --- /dev/null +++ "b/src/bin/libint/comp_11_Coulomb\317\203p_11.h" @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2004-2026 Edward F. Valeev + * + * This file is part of Libint compiler. + * + * Libint compiler 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 compiler 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 compiler. If not, see . + * + */ + +#ifndef LIBINT_COMP_11_COULOMBΣP_11_H +#define LIBINT_COMP_11_COULOMBΣP_11_H + +#include +#include +#include +#include +#include +#include + +namespace libint2 { + +/** + * this computes the 3-center single-σ·p ("DF-Gaunt") integral + * \f$ \frac{1}{r_{12}} \, \sigma \cdot \hat{p}_2 \f$ acting on the 2nd ket + * function ν, by rewriting it as the bare Cartesian gradient of ν, i.e. one + * first-derivative integral over \f$ \frac{1}{r_{12}} \f$ per Cartesian + * direction. Unlike Coulombσpσp there is no Dirac σ·σ folding: each of the 3 + * components (x,y,z) is exactly one derivative ERI child, so it routes + * naturally through the TwoPRep DerivGaussV2 strategy. + * @tparam F basis function type. valid choices are CGShell or CGF + */ +template +class CR_11_Coulombσp_11 + : public GenericRecurrenceRelation< + CR_11_Coulombσp_11, F, + GenIntegralSet_11_11> { + public: + typedef CR_11_Coulombσp_11 ThisType; + typedef F BasisFunctionType; + typedef CoulombσpOper OperType; + typedef GenIntegralSet_11_11 TargetType; + typedef GenericRecurrenceRelation + ParentType; + friend class GenericRecurrenceRelation; + static const unsigned int max_nchildren = 1; + + using ParentType::Instance; + + static bool directional() { return false; } + + private: + using ParentType::is_simple; + using ParentType::target_; + using ParentType::RecurrenceRelation::expr_; + using ParentType::RecurrenceRelation::nflops_; + + /// Constructor is private, used by Instance that maintains + /// registry of these objects + CR_11_Coulombσp_11(const std::shared_ptr&, unsigned int = 0); + + static std::string descr() { return "CR"; } + + // --- Code sharing overrides --- + // All shell combos with the same Cartesian component share one function + // (the element-wise copy below is spectator-dimension-independent). + + std::string generate_label() const override { + return "CR_Coulombop_" + + std::to_string(target_->oper()->descr().cartesian_index()); + } + + std::string spfunction_call( + const std::shared_ptr& context, + const std::shared_ptr& dims) const override { + std::ostringstream os; + os << context->label_to_function_name(this->label()) << "(inteval, " + << context->value_to_pointer(this->rr_target()->symbol()); + const unsigned int nc = this->num_children(); + for (unsigned int c = 0; c < nc; c++) { + os << ", " << context->value_to_pointer(this->rr_child(c)->symbol()); + } + // total_dim = product of all shell dims (all 4 shells are spectators) + unsigned int total_dim = 1; + for (unsigned int p = 0; p < 2; p++) { + SubIterator* si = target_->bra().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + si = target_->ket().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + } + os << "," << total_dim; + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + taskmgr.current().params()->max_hrr_hsrank(total_dim); + os << ")" << context->end_of_stat() << std::endl; + return os.str(); + } + + std::shared_ptr adapt_dims_( + const std::shared_ptr& dims) const override { + auto high_dim = std::make_shared>("highdim"); + return std::make_shared(high_dim, dims->low(), + dims->vecdim()); + } + + /// Hand-emit a simple element-wise copy: target = src0 (the single + /// first-derivative child). 0 FLOPs per component. + void generate_code(const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::string& funcname, std::ostream& decl, + std::ostream& def) override { + // declare_function lives in dg.cc + extern std::string declare_function( + const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::shared_ptr& args, const std::string& tlabel, + const std::string& function_descr, std::ostream& decl); + + std::shared_ptr localdims = adapt_dims_(dims); + // inline assign_symbols_: set symbol names on target/children and + // populate CodeSymbols + std::shared_ptr symbols(new CodeSymbols); + this->rr_target()->set_symbol("target"); + symbols->append_symbol("target"); + for (unsigned int c = 0; c < this->num_children(); c++) { + std::string symb = "src" + std::to_string(c); + this->rr_child(c)->set_symbol(symb); + symbols->append_symbol(symb); + } + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + const std::string tlabel = taskmgr.current().label(); + const std::string func_decl = + declare_function(context, localdims, symbols, tlabel, funcname, decl); + def << context->std_header(); + def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; + def << context->code_prefix(); + def << func_decl << context->open_block() << std::endl; + def << context->std_function_header(); + def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; + def << "for(int hsi = 0; hsiclose_block() << std::endl; + def << context->code_postfix(); + } +}; + +template +CR_11_Coulombσp_11::CR_11_Coulombσp_11( + const std::shared_ptr& Tint, unsigned int) + : ParentType(Tint, 0) { + assert(Tint->num_func_bra(/* particle */ 0) == 1); + assert(Tint->num_func_bra(/* particle */ 1) == 1); + assert(Tint->num_func_ket(/* particle */ 0) == 1); + assert(Tint->num_func_ket(/* particle */ 1) == 1); + + F a(Tint->bra(0, 0)); + F b(Tint->ket(0, 0)); + F c(Tint->bra(1, 0)); + F d(Tint->ket(1, 0)); + + const auto& oper = Tint->oper(); + + if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) + return; + + using namespace libint2::algebra; + using namespace libint2::prefactor; + using libint2::algebra::operator*; + + const mType zero_m(0u); + + ChildFactory> + factory(this); + + // single σ·p on ν (the 2nd ket function, d). Component k = ∂(d)/∂{x,y,z}. + // (Geometric-center derivative convention, matching CR_11_Coulombσpσp_11; the + // -i factor and Pauli matrix are applied by the caller.) + const int k = oper->descr().cartesian_index(); + if (k < 0 || k > 2) + throw std::runtime_error("CR_11_Coulombσp_11: invalid Cartesian index"); + + F d_k{d}; + d_k.deriv().inc(k); // d(d)/d{x,y,z} = d_k + + auto a_b_c_dk = factory.make_child(a, b, c, d_k, zero_m); + if (is_simple()) { + // Single derivative child; Scalar(1)* is only a registration vehicle (a + // bare child is not an expression). The actual emit is the element-wise + // copy in generate_code() above, so this contributes 0 real flops. + expr_ = Scalar(1) * a_b_c_dk; + nflops_ += 0; + } + +} // CR_11_Coulombσp_11::CR_11_Coulombσp_11 +}; // namespace libint2 + +#endif // LIBINT_COMP_11_COULOMBΣP_11_H diff --git a/src/bin/libint/master_ints_list.h b/src/bin/libint/master_ints_list.h index 17c071c34..8ed891e8f 100644 --- a/src/bin/libint/master_ints_list.h +++ b/src/bin/libint/master_ints_list.h @@ -121,6 +121,8 @@ typedef GenIntegralSet_11_11 typedef GenIntegralSet_11_11 σpCoulombσp_11_11_sq; typedef GenIntegralSet_11_11 σpCoulombσp_11_11_int; +typedef GenIntegralSet_11_11 Coulombσp_11_11_sq; +typedef GenIntegralSet_11_11 Coulombσp_11_11_int; typedef GenIntegralSet_11_11 R12kG12_11_11_sq; typedef GenIntegralSet_11_11 R12kG12_11_11_int; typedef GenIntegralSet_11_11 @@ -163,7 +165,8 @@ typedef boost::mpl::list< typedef boost::mpl::list< TwoPRep_11_11_sq, TwoPRep_11_11_int, Coulombσpσp_11_11_sq, Coulombσpσp_11_11_int, σpσpCoulombσpσp_11_11_sq, σpσpCoulombσpσp_11_11_int, - σpCoulombσp_11_11_sq, σpCoulombσp_11_11_int, R12kG12_11_11_sq, + σpCoulombσp_11_11_sq, σpCoulombσp_11_11_int, Coulombσp_11_11_sq, + Coulombσp_11_11_int, R12kG12_11_11_sq, R12kG12_11_11_int, R12kR12lG12_11_11_sq, R12kR12lG12_11_11_int, TiG12_11_11_sq, TiG12_11_11_int, G12TiG12_11_11_sq, G12TiG12_11_11_int, DivG12prime_xTx_11_11_sq, DivG12prime_xTx_11_11_int, diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index 490b5e4ec..368ce4c1b 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -21,6 +21,7 @@ #ifndef _libint2_src_bin_libint_masterrrslist_h_ #define _libint2_src_bin_libint_masterrrslist_h_ +#include #include #include #include @@ -327,6 +328,9 @@ typedef CR_11_σpσpCoulombσpσp_11 CR_11_σpσpCoulombσpσp_11_int; typedef CR_11_σpCoulombσp_11 CR_11_σpCoulombσp_11_sh; typedef CR_11_σpCoulombσp_11 CR_11_σpCoulombσp_11_int; + +typedef CR_11_Coulombσp_11 CR_11_Coulombσp_11_sh; +typedef CR_11_Coulombσp_11 CR_11_Coulombσp_11_int; }; // namespace libint2 #endif // header guard diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index c40eb11f2..30816c65f 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -476,6 +476,48 @@ struct Coulombσpσp_Descr : public Contractable { }; typedef GenOper CoulombσpσpOper; +/** Coulombσp is the 3-center single-σ·p ("DF-Gaunt") operator: + * (P | 1/r_{12} | μ, σ·p ν), with the fitting function P a spectator and a + * single σ·p acting on the 2nd ket AO function ν. Unlike Coulombσpσp (two σ·p + * on the ket pair, folded via the Dirac identity into 4 quaternion + * components), a single σ·p produces no σ·σ folding: the result is the bare + * Cartesian gradient of ν, i.e. the SO(3) vector irrep with 3 components + * (x=0, y=1, z=2). Each component is exactly one first-derivative ERI child; + * the imaginary unit and Pauli matrix of σ·p are applied by the caller (MPQC). + */ +struct Coulombσp_Descr : public Contractable { + typedef MultiplicativeSymm2Body_Props Properties; + + Coulombσp_Descr() : cartesian_index_(0) {} + Coulombσp_Descr(int cartesian_index) : cartesian_index_(cartesian_index) { + assert(cartesian_index >= 0 && cartesian_index <= 2); + } + + static const unsigned int max_key = 3; + unsigned int key() const { return cartesian_index(); } + std::string description() const { + std::string descr("coulomb_op["); + if (cartesian_index() == 0) + descr += "X"; + else if (cartesian_index() == 1) + descr += "Y"; + else if (cartesian_index() == 2) + descr += "Z"; + else + abort(); + return descr + "]"; + } + std::string label() const { return description(); } + int psymm(int i, int j) const { abort(); } + int hermitian(int i) const { return +1; } + + int cartesian_index() const { return cartesian_index_; } + + private: + const int cartesian_index_ = -1; +}; +typedef GenOper CoulombσpOper; + struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr> { typedef MultiplicativeSymm2Body_Props Properties; diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index 8e73cad47..d6ae8ed16 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -143,6 +143,14 @@ template <> struct MasterStrategy<σpCoulombσp_11_11_int> { typedef boost::mpl::list value; }; +template <> +struct MasterStrategy { + typedef boost::mpl::list value; +}; +template <> +struct MasterStrategy { + typedef boost::mpl::list value; +}; #if LIBINT_SHELLQUARTET_STRATEGY == LIBINT_SHELLQUARTET_STRATEGY_A0C0 template <> From 6256cb812145151b46efe44a2d70ce3cf5133503 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Mon, 8 Jun 2026 18:35:36 -0400 Subject: [PATCH 25/64] Fix bitwise & to && in compute_directly short-circuit The (ss|ss) fast-path predicate in engine.impl.h used a single bitwise & between the deriv_order_ and intrinsic_deriv_order() boolean tests, which drops short-circuit evaluation. Use &&. (Copilot review.) --- include/libint2/engine.impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 698abcf6d..7aeb49ba1 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -2001,7 +2001,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( // compute directly (ss|ss) const auto compute_directly = - lmax == 0 && deriv_order_ == 0 & intrinsic_deriv_order() == 0; + lmax == 0 && deriv_order_ == 0 && intrinsic_deriv_order() == 0; if (compute_directly) { #ifdef LIBINT2_ENGINE_TIMERS From d4939d33a9c5e7fd0b35f26c536ad5d3c3b2832a Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Mon, 8 Jun 2026 18:35:36 -0400 Subject: [PATCH 26/64] Address review: document RKB classes, clarify DF-basis AM, fix op_coulomb_op doc - INSTALL.md: document LIBINT2_ENABLE_RKB_ERI / RKB_ERI3 and their MAX_AM/OPT_AM options (Lori review). - CMakeLists.txt + config.h.cmake.in: state that RKB_ERI3/ERI3 MAX_AM set the density-fitting (auxiliary) basis AM on the single fitting center. - int_am.cmake: collapse the RKB ERI component-code token rkb_eri_ -> rkberi_ so the operator token is single, underscore-free (like eri/onebody/g12), avoiding ambiguity if codes are split on '_'. Public LIBINT2_*_RKB_ERI* options and the RKB_ERI class id are unchanged (Lori review). - engine.h: correct the Operator::op_coulomb_op docstring to describe the actual SO(3) irreducible 9-component layout, not a raw 3*a+b Cartesian dyadic (Copilot review). --- CMakeLists.txt | 4 ++-- INSTALL.md | 8 ++++++++ cmake/modules/int_am.cmake | 4 ++-- include/libint2/config.h.cmake.in | 6 ++++-- include/libint2/engine.h | 11 +++++++---- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd2d49359..4d69d695f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -235,7 +235,7 @@ option_with_default(LIBINT2_RKB_ERI_OPT_AM option_with_default(LIBINT2_RKB_ERI3_MAX_AM "Support relativistic restricted kinetic balance (RKB) 3-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am) - This option controls only the single fitting center. The paired centers (on which σ·p acts) use LIBINT2_MAX_AM." -1) + This sets the max AM of the density fitting (auxiliary/DF) basis on the single fitting center. The paired AO centers (on which σ·p acts) use LIBINT2_MAX_AM." -1) option_with_default(LIBINT2_RKB_ERI3_OPT_AM "Optimize relativistic restricted kinetic balance (RKB) 3-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) @@ -244,7 +244,7 @@ option_with_default(LIBINT2_RKB_ERI3_OPT_AM option_with_default(LIBINT2_ERI3_MAX_AM "Support 3-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am) - This option controls only the single fitting center. The paired centers use LIBINT2_MAX_AM." -1) + This sets the max AM of the density fitting (auxiliary/DF) basis on the single fitting center. The paired AO centers use LIBINT2_MAX_AM." -1) option_with_default(LIBINT2_ERI3_OPT_AM "Optimize 3-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. (default: (max_am/2)+1)" -1) diff --git a/INSTALL.md b/INSTALL.md index dd4178010..e48abdb6e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -169,6 +169,8 @@ Use combined targets like `cmake --target check install` to avoid some unnecessa * `LIBINT2_ENABLE_ERI` - G - Compile with support for up to N-th derivatives of 4-center electron repulsion integrals. Use -1 for OFF. [Default=0] * `LIBINT2_ENABLE_ERI3` - G - Compile with support for up to N-th derivatives of 3-center electron repulsion integrals. Use -1 for OFF. [Default=-1] * `LIBINT2_ENABLE_ERI2` - G - Compile with support for up to N-th derivatives of 2-center electron repulsion integrals. Use -1 for OFF. [Default=-1] +* `LIBINT2_ENABLE_RKB_ERI` - G - Compile with support for up to N-th derivatives of relativistic restricted kinetic balance (RKB) 4-center electron repulsion integrals. Use -1 for OFF. [Default=0] +* `LIBINT2_ENABLE_RKB_ERI3` - G - Compile with support for up to N-th derivatives of relativistic restricted kinetic balance (RKB) 3-center electron repulsion integrals. Use -1 for OFF. σ·p acts on the two paired (AO) centers; the unpaired/fitting (density-fitting/auxiliary) center is a spectator. [Default=-1] * `LIBINT2_ENABLE_G12` - G - Compile with support for N-th derivatives of MP2-F12 energies with Gaussian factors. Use -1 for OFF. [Default=-1] * `LIBINT2_ENABLE_G12DKH` - G - Compile with support for N-th derivatives of DKH-MP2-F12 energies with Gaussian factors. Use -1 for OFF. [Default=-1] @@ -255,6 +257,12 @@ Note that options, docs, and CMake components are focused on the C++ interface, * `LIBINT2_ERI2_OPT_AM` - G - Optimize 2-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. [Default=-1 -> `LIBINT2_OPT_AM`] * `LIBINT2_ERI2_PURE_SH` - G - Assume the 2-center ERIs will be transformed to pure solid harmonics. [Default=OFF] +* `LIBINT2_RKB_ERI_MAX_AM` - G - Support RKB 4-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. [Default=-1 -> `LIBINT2_MAX_AM`] +* `LIBINT2_RKB_ERI_OPT_AM` - G - Optimize RKB 4-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. [Default=-1 -> `LIBINT2_OPT_AM`] + +* `LIBINT2_RKB_ERI3_MAX_AM` - G - Support RKB 3-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. This option sets the AM of the density-fitting (auxiliary) basis on the single fitting center; the paired (AO) centers, on which σ·p acts, use LIBINT2_MAX_AM. [Default=-1 -> `LIBINT2_MAX_AM`] +* `LIBINT2_RKB_ERI3_OPT_AM` - G - Optimize RKB 3-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. [Default=-1 -> `LIBINT2_OPT_AM`] + * `LIBINT2_G12_MAX_AM` - G - Support integrals for G12 methods of angular momentum up to N. No specification with per-derivative list. [Default=-1 -> `LIBINT2_MAX_AM`] * `LIBINT2_G12_OPT_AM` - G - Optimize G12 integrals for up to angular momentum N (N <= max-am). No specification with per-derivative list. [Default=-1 `LIBINT2_OPT_AM`] diff --git a/cmake/modules/int_am.cmake b/cmake/modules/int_am.cmake index 2dbd1904a..a29f2c03a 100644 --- a/cmake/modules/int_am.cmake +++ b/cmake/modules/int_am.cmake @@ -436,7 +436,7 @@ foreach(_cls ONEBODY;ERI;RKB_ERI;RKB_ERI3;ERI3;ERI2;G12;G12DKH) elseif (_cls STREQUAL "G12") list(APPEND _amlist "g12_${_am${_l}}${_am${_l}}${_am${_l}}${_am${_l}}_d${_d}") elseif (_cls STREQUAL "RKB_ERI") - list(APPEND _amlist "rkb_eri_${_am${_l}}${_am${_l}}${_am${_l}}${_am${_l}}_d${_d}") + list(APPEND _amlist "rkberi_${_am${_l}}${_am${_l}}${_am${_l}}${_am${_l}}_d${_d}") endif() endforeach() if (_cls STREQUAL "ERI3") @@ -456,7 +456,7 @@ foreach(_cls ONEBODY;ERI;RKB_ERI;RKB_ERI3;ERI3;ERI2;G12;G12DKH) foreach(_lfit RANGE ${LIBINT_HARD_MIN_AM} ${_candidate_${_cls}_d${_d}}) # LIBINT_RKB_ERI3_MAX_AM[_LIST], fitting foreach(_lpr RANGE ${LIBINT_HARD_MIN_AM} ${_eri3_candidate0_d${_d}}) # LIBINT_MAX_AM[_LIST], paired if (_lfit GREATER_EQUAL _lpr) - list(APPEND _amlist "rkb_eri_${_am${_lpr}}${_am${_lpr}}${_AM${_lfit}}_d${_d}") + list(APPEND _amlist "rkberi_${_am${_lpr}}${_am${_lpr}}${_AM${_lfit}}_d${_d}") endif() endforeach() endforeach() diff --git a/include/libint2/config.h.cmake.in b/include/libint2/config.h.cmake.in index e07f3cbc8..3c8a3eca7 100644 --- a/include/libint2/config.h.cmake.in +++ b/include/libint2/config.h.cmake.in @@ -147,7 +147,8 @@ /* Max optimized AM for ERI and its derivatives */ #cmakedefine LIBINT_RKB_ERI_OPT_AM_LIST "@LIBINT_RKB_ERI_OPT_AM_LIST@" -/* Max AM for 3-center RKB ERI fitting center (same for all derivatives; if not defined see LIBINT_RKB_ERI3_MAX_AM_LIST). +/* Max AM for the 3-center RKB ERI density fitting (auxiliary/DF) basis on the single fitting center + (same for all derivatives; if not defined see LIBINT_RKB_ERI3_MAX_AM_LIST). The paired AO centers (on which σ·p acts) use LIBINT_MAX_AM. */ #cmakedefine LIBINT_RKB_ERI3_MAX_AM @LIBINT_RKB_ERI3_MAX_AM@ @@ -160,7 +161,8 @@ /* Max optimized AM for 3-center RKB ERI and its derivatives */ #cmakedefine LIBINT_RKB_ERI3_OPT_AM_LIST "@LIBINT_RKB_ERI3_OPT_AM_LIST@" -/* Max AM for 3-center ERI (same for all derivatives; if not defined see LIBINT_ERI3_MAX_AM_LIST) */ +/* Max AM for the 3-center ERI density fitting (auxiliary/DF) basis on the single fitting center + (same for all derivatives; if not defined see LIBINT_ERI3_MAX_AM_LIST). The paired AO centers use LIBINT_MAX_AM. */ #cmakedefine LIBINT_ERI3_MAX_AM @LIBINT_ERI3_MAX_AM@ /* Max AM for 3-center ERI and its derivatives */ diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 01cbda75a..3ffea77c4 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -219,10 +219,13 @@ enum class Operator { opop_coulomb_opop, /// (2-body) \f$ (σ.p_{b2}) r_{12}^{-1} (σ.p_{k2}) \f$ where b2 is the center /// of bra2 and k2 is the center of ket2; Gaunt LS "bilinear" integral. - /// Produces 9 components (outer product of two Cartesian directions), - /// indexed as `3*a + b` with `a` = bra-side direction, `b` = ket-side - /// direction, and `a,b ∈ {x=0, y=1, z=2}`. Unlike coulomb_opop, the 9 - /// components are NOT contracted via σ·σ — all are kept independent. + /// Produces 9 components: the SO(3) irreducible decomposition of the 3×3 + /// derivative dyadic T_ab (a = bra-side ∇, b = ket-side ∇), NOT a raw + /// `3*a + b` Cartesian dyadic. Component order matches + /// `σpCoulombσp_Descr::Component`: Scalar trace (0); antisymmetric (curl-curl) + /// AntisymX,Y,Z (1–3); symmetric-traceless SymTLDiagA,SymTLDiagB (4,5) and + /// SymTLOffXY,SymTLOffXZ,SymTLOffYZ (6–8). Unlike coulomb_opop, these + /// components are not σ·σ-folded. op_coulomb_op, /// contracted Gaussian geminal cgtg, From 9dba226b56f438aa6821e0e52736ef4d1cb14917 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Mon, 8 Jun 2026 18:48:19 -0400 Subject: [PATCH 27/64] Fix second op_coulomb_op docstring (operator_traits) to SO(3) layout operator_traits carried the same stale "raw 3*a+b Cartesian dyadic" description that the Operator enum doc had; correct it to the actual SO(3) irreducible 9-component layout (Scalar / Antisym / SymTL). Completes the Copilot docstring fix, which had only updated the enum copy. --- include/libint2/engine.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 3ffea77c4..1e561cb16 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -487,9 +487,11 @@ struct operator_traits template <> struct operator_traits : public operator_traits { - /// 9 components: Cartesian dyadic of the two (σ·p) directions. - /// index = 3 * a + b, with a = bra-side direction, b = ket-side direction, - /// a,b ∈ {x=0, y=1, z=2}. + /// 9 components: the SO(3) irreducible decomposition of the 3×3 derivative + /// dyadic T_ab (NOT a raw `3*a + b` Cartesian dyadic). Order matches + /// `σpCoulombσp_Descr::Component`: Scalar trace (0); antisymmetric + /// AntisymX,Y,Z (1–3); symmetric-traceless SymTLDiagA,SymTLDiagB (4,5) and + /// SymTLOffXY,SymTLOffXZ,SymTLOffYZ (6–8). static constexpr auto nopers = 9; static constexpr auto intrinsic_deriv_order = 2; }; From 123dfe195ec1e21e3629b429c0b325d91b7c46d3 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Mon, 8 Jun 2026 19:00:10 -0400 Subject: [PATCH 28/64] Apply review fixes: coulomb_op default braket + child-count guards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - engine.h: default_braket(coulomb_op) now returns BraKet::xs_xx. coulomb_op has no 4-center kernel, so the rank-based xx_xx default silently dispatched to the plain-Coulomb `default` task and returned wrong integrals with no error in release builds. (Review finding #1.) - comp_11_σpσpCoulombσpσp_11.h / comp_11_Coulombσpσp_11.h: add a `default` branch to generate_code()'s child-count switch that throws instead of emitting a wrong/empty `target[hsi] = ...` expression if num_children() is ever an unexpected value (e.g. future child dedup). (Review finding #4.) --- include/libint2/engine.h | 19 +++++++++++++------ .../comp_11_Coulomb\317\203p\317\203p_11.h" | 9 +++++++++ ...3p\317\203pCoulomb\317\203p\317\203p_11.h" | 10 ++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 1e561cb16..17990a0ed 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -614,12 +614,19 @@ inline constexpr int rank(BraKet braket) { /// @return the default braket for @c oper /// @throw std::logic_error if invalid @c oper given inline constexpr BraKet default_braket(Operator oper) { - return (rank(oper) == 1) - ? BraKet::x_x - : ((rank(oper) == 2) - ? BraKet::xx_xx - : throw std::logic_error( - "default_braket(Operator): invalid operator given")); + // coulomb_op (3-center single-σ·p DF-Gaunt) has no 4-center kernel; its only + // valid geometry is the 3-center xs_xx braket. Default to that rather than the + // rank-based xx_xx, which has no generated kernel and would silently dispatch + // to the plain-Coulomb `default` task (wrong integrals, no error in release). + return (oper == Operator::coulomb_op) + ? BraKet::xs_xx + : ((rank(oper) == 1) + ? BraKet::x_x + : ((rank(oper) == 2) + ? BraKet::xx_xx + : throw std::logic_error( + "default_braket(Operator): invalid operator " + "given"))); } constexpr size_t nopers_2body = static_cast(Operator::last_2body_oper) - diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index db28cb7ea..b8b8a6622 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -28,6 +28,8 @@ #include #include +#include + namespace libint2 { /** @@ -153,6 +155,13 @@ class CR_11_Coulombσpσp_11 def << "src0[hsi] + src1[hsi] + src2[hsi]"; } else if (nc == 2) { def << "src0[hsi] - src1[hsi]"; + } else { + // Sign pattern is keyed on the child count (3 or 2). If canonicalization + // ever changes the count, the branches above would emit a wrong or empty + // target expression; fail the export loudly instead of emitting it. + throw std::logic_error( + "Coulombσpσp generate_code(): unexpected num_children() (expected 3 " + "or 2)"); } def << ";\n}\n"; unsigned int nflops = (nc > 1) ? nc - 1 : 0; diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" index 64d1fdee6..fd03c54e2 100644 --- "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -28,6 +28,8 @@ #include #include +#include + namespace libint2 { /** @@ -199,6 +201,14 @@ class CR_11_σpσpCoulombσpσp_11 } else if (nc == 4) { // XX,XY,...,ZZ: -src0 + src1 + src2 - src3 def << "- src0[hsi] + src1[hsi] + src2[hsi] - src3[hsi]"; + } else { + // The σ·σ-fold sign pattern is keyed on the child count (9/6/4). If + // particle-swap canonicalization ever deduplicates children to a + // different count, the branches above would emit a wrong or empty target + // expression; fail the export loudly instead of generating broken code. + throw std::logic_error( + "σpσpCoulombσpσp generate_code(): unexpected num_children() (expected " + "9, 6, or 4)"); } def << ";\n}\n"; unsigned int nflops = (nc > 1) ? nc - 1 : 0; From 05f289ce49d390e0ccaf56f4d3b097b12e1d6c76 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Mon, 8 Jun 2026 19:22:46 -0400 Subject: [PATCH 29/64] Production hardening: graceful deriv-braket error, tightened reserves, coulomb_op default-braket test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - deriv_map.h (#2): DerivMapGenerator::instance() now throws a descriptive std::logic_error for unsupported (braket, deriv>0) combinations (e.g. xx_xs) instead of std::abort()-ing the host process. xx_xs derivatives remain unimplemented, but now fail catchably rather than crashing consumers. - comp_11_σpσpCoulombσpσp_11.h / comp_11_Coulombσpσp_11.h (#6): replace the guessed `max_nchildren = 100 // TODO` with the true bounds (9 and 3, derived from the densest fold component). This is a children_.reserve() hint only (generic_rr.h), so no behavior change. - test-2body.cc (#1): regression test that default_braket(coulomb_op)==xs_xx and that a coulomb_op engine built WITHOUT an explicit set() produces results identical to the explicit-xs_xx engine (guards the silent-wrong-integrals footgun fixed in the previous commit). Verified: low-AM (MAX_AM=2) compiler->export->library->test all green; RKB Coulomb case 7600->7653 assertions, full suite 2,114,807 / 33 cases, no regressions. --- export/tests/unit/test-2body.cc | 15 +++++++++++++++ include/libint2/deriv_map.h | 13 +++++++++---- .../comp_11_Coulomb\317\203p\317\203p_11.h" | 5 ++++- ...7\203p\317\203pCoulomb\317\203p\317\203p_11.h" | 5 ++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index b4a0392b8..333c7263a 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -937,11 +937,21 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { std::vector dfs{dfsh}; Engine engine_3c; + Engine engine_3c_default; try { engine_3c = Engine(Operator::coulomb_op, std::max(max_nprim, libint2::max_nprim(dfs)), std::max(max_l, libint2::max_l(dfs)), 0); engine_3c.set(BraKet::xs_xx); + // Regression (review finding #1): coulomb_op has no 4-center kernel, so a + // forgotten set() must NOT silently dispatch to the plain-Coulomb + // `default` task. default_braket(coulomb_op) must be the 3-center xs_xx, + // so engine_3c_default — built WITHOUT an explicit set() — must produce + // results identical to engine_3c. + CHECK(default_braket(Operator::coulomb_op) == BraKet::xs_xx); + engine_3c_default = Engine(Operator::coulomb_op, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); } catch (Engine::lmax_exceeded &) { // skip if libint not configured with -DLIBINT2_ENABLE_RKB_ERI3 >= 0 return; @@ -956,6 +966,10 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { for (int sb = 0; sb != nshell; ++sb) { const auto &results_3c = engine_3c.compute(dfsh, obs[sa], obs[sb]); assert(results_3c.size() == 3); + // default-braket engine (no set()) must match the explicit-xs_xx engine + const auto &results_3c_default = + engine_3c_default.compute(dfsh, obs[sa], obs[sb]); + REQUIRE(results_3c_default.size() == 3); LIBINT2_REF_REALTYPE Aref[3], Cref[3], Dref[3]; for (int i = 0; i < 3; ++i) Aref[i] = dfsh.O[i]; @@ -1002,6 +1016,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { const double REL_TOL = 1.0E-9; for (auto comp = 0; comp < 3; ++comp) { const auto v_3c = results_3c[comp][ijk]; + CHECK(results_3c_default[comp][ijk] == v_3c); const auto v_ref = ref_coulomb_op[comp]; const auto abs_err = std::abs(v_3c - v_ref); const auto rel_err = diff --git a/include/libint2/deriv_map.h b/include/libint2/deriv_map.h index 31d6aefb2..aaf82be05 100644 --- a/include/libint2/deriv_map.h +++ b/include/libint2/deriv_map.h @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace libint2 { @@ -94,11 +95,15 @@ class DerivMapGenerator { return braket_xs_xx()[deriv_order_ - 1]; } default: - assert(false && - "Derivative index permutation maps for this braket type not yet " - "supported."); + // Derivative index-permutation maps are implemented only for xx_xx and + // xs_xx. Other brakets (e.g. xx_xs) are accepted for energies — where + // they canonicalize to xs_xx — but have no derivative map, so fail with + // a catchable error rather than std::abort()-ing the host process. + throw std::logic_error( + "DerivMapGenerator::instance(): derivative index permutation maps " + "are only implemented for the xx_xx and xs_xx brakets; " + "deriv_order > 0 is not supported for this braket."); } - std::abort(); } private: diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index b8b8a6622..d054065c2 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -53,7 +53,10 @@ class CR_11_Coulombσpσp_11 ParentType; friend class GenericRecurrenceRelation; - static const unsigned int max_nchildren = 100; // TODO figure out + // children_.reserve() hint only (see generic_rr.h). The densest component + // sums 3 deriv-ERI children (the 2-component case uses 2), so 3 is the true + // upper bound across the 4 components. + static const unsigned int max_nchildren = 3; using ParentType::Instance; diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" index fd03c54e2..6a894be42 100644 --- "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -81,7 +81,10 @@ class CR_11_σpσpCoulombσpσp_11 ParentType; friend class GenericRecurrenceRelation; - static const unsigned int max_nchildren = 100; // TODO figure out + // children_.reserve() hint only (see generic_rr.h). The densest component is + // the SS scalar (σ·σ trace), which sums 9 deriv-ERI children; all others use + // 6 or 4. So 9 is the true upper bound across the 16 components. + static const unsigned int max_nchildren = 9; using ParentType::Instance; From 773a687db0911b49b95e9643359114030e6087ab Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Mon, 8 Jun 2026 19:41:20 -0400 Subject: [PATCH 30/64] =?UTF-8?q?Dedup=20=CF=83=C2=B7p=20fold=20codegen=20?= =?UTF-8?q?into=20shared=20rkb=5Ffold=5Fcodegen.h=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four σ·p "element-wise fold" recurrence relations (Coulombσp, Coulombσpσp, σpCoulombσp, σpσpCoulombσpσp) had byte-identical spfunction_call, adapt_dims_, and generate_code scaffold logic copy-pasted four times; only the operator label and the per-target RHS expression differ. Extract the shared logic into three free-function helpers in the new src/bin/libint/rkb_fold_codegen.h: - rkb_fold_adapt_dims (the "highdim" dimension swap) - rkb_fold_spfunction_call (call-site + total_dim emission) - rkb_fold_generate_code (symbol assignment + function scaffold + the target[hsi] = loop) Each comp header now delegates spfunction_call/adapt_dims_ in one line and keeps only its operator-specific RHS computation (copy / nc-keyed / component-keyed) before calling rkb_fold_generate_code. No CRTP/Instance()/inheritance changes — the classes keep their exact GenericRecurrenceRelation base; the helpers are chosen specifically to avoid touching the registry/hierarchy. This relies only on the public RecurrenceRelation accessors (rr_target/rr_child/num_children/ label), passing the typed target_ where bra()/ket() iteration is needed. Verified byte-identical: low-AM compiler->export->library->test reproduces the exact pre-refactor counts (RKB Coulomb 7653 assertions, full suite 2,114,807 / 33 cases), so the generated code is unchanged. --- .../bin/libint/comp_11_Coulomb\317\203p_11.h" | 65 +-------- .../comp_11_Coulomb\317\203p\317\203p_11.h" | 76 ++-------- .../comp_11_\317\203pCoulomb\317\203p_11.h" | 62 +------- ...3p\317\203pCoulomb\317\203p\317\203p_11.h" | 78 ++-------- src/bin/libint/rkb_fold_codegen.h | 135 ++++++++++++++++++ 5 files changed, 173 insertions(+), 243 deletions(-) create mode 100644 src/bin/libint/rkb_fold_codegen.h diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p_11.h" index f51179a9e..e9cc50892 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p_11.h" @@ -28,6 +28,8 @@ #include #include +#include + namespace libint2 { /** @@ -84,35 +86,12 @@ class CR_11_Coulombσp_11 std::string spfunction_call( const std::shared_ptr& context, const std::shared_ptr& dims) const override { - std::ostringstream os; - os << context->label_to_function_name(this->label()) << "(inteval, " - << context->value_to_pointer(this->rr_target()->symbol()); - const unsigned int nc = this->num_children(); - for (unsigned int c = 0; c < nc; c++) { - os << ", " << context->value_to_pointer(this->rr_child(c)->symbol()); - } - // total_dim = product of all shell dims (all 4 shells are spectators) - unsigned int total_dim = 1; - for (unsigned int p = 0; p < 2; p++) { - SubIterator* si = target_->bra().member_subiter(p, 0); - total_dim *= si->num_iter(); - delete si; - si = target_->ket().member_subiter(p, 0); - total_dim *= si->num_iter(); - delete si; - } - os << "," << total_dim; - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - taskmgr.current().params()->max_hrr_hsrank(total_dim); - os << ")" << context->end_of_stat() << std::endl; - return os.str(); + return rkb_fold_spfunction_call(*this, target_, context); } std::shared_ptr adapt_dims_( const std::shared_ptr& dims) const override { - auto high_dim = std::make_shared>("highdim"); - return std::make_shared(high_dim, dims->low(), - dims->vecdim()); + return rkb_fold_adapt_dims(dims); } /// Hand-emit a simple element-wise copy: target = src0 (the single @@ -121,39 +100,9 @@ class CR_11_Coulombσp_11 const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - // declare_function lives in dg.cc - extern std::string declare_function( - const std::shared_ptr& context, - const std::shared_ptr& dims, - const std::shared_ptr& args, const std::string& tlabel, - const std::string& function_descr, std::ostream& decl); - - std::shared_ptr localdims = adapt_dims_(dims); - // inline assign_symbols_: set symbol names on target/children and - // populate CodeSymbols - std::shared_ptr symbols(new CodeSymbols); - this->rr_target()->set_symbol("target"); - symbols->append_symbol("target"); - for (unsigned int c = 0; c < this->num_children(); c++) { - std::string symb = "src" + std::to_string(c); - this->rr_child(c)->set_symbol(symb); - symbols->append_symbol(symb); - } - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - const std::string tlabel = taskmgr.current().label(); - const std::string func_decl = - declare_function(context, localdims, symbols, tlabel, funcname, decl); - def << context->std_header(); - def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; - def << context->code_prefix(); - def << func_decl << context->open_block() << std::endl; - def << context->std_function_header(); - def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; - def << "for(int hsi = 0; hsiclose_block() << std::endl; - def << context->code_postfix(); + // target = src0 (the single first-derivative child), 0 flops. + rkb_fold_generate_code(*this, context, dims, funcname, decl, def, + "src0[hsi]", 0); } }; diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index d054065c2..36dc09d59 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -28,6 +28,8 @@ #include #include +#include + #include namespace libint2 { @@ -85,35 +87,12 @@ class CR_11_Coulombσpσp_11 std::string spfunction_call( const std::shared_ptr& context, const std::shared_ptr& dims) const override { - std::ostringstream os; - os << context->label_to_function_name(this->label()) << "(inteval, " - << context->value_to_pointer(this->rr_target()->symbol()); - const unsigned int nc = this->num_children(); - for (unsigned int c = 0; c < nc; c++) { - os << ", " << context->value_to_pointer(this->rr_child(c)->symbol()); - } - // total_dim = product of all shell dims (all 4 shells are spectators) - unsigned int total_dim = 1; - for (unsigned int p = 0; p < 2; p++) { - SubIterator* si = target_->bra().member_subiter(p, 0); - total_dim *= si->num_iter(); - delete si; - si = target_->ket().member_subiter(p, 0); - total_dim *= si->num_iter(); - delete si; - } - os << "," << total_dim; - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - taskmgr.current().params()->max_hrr_hsrank(total_dim); - os << ")" << context->end_of_stat() << std::endl; - return os.str(); + return rkb_fold_spfunction_call(*this, target_, context); } std::shared_ptr adapt_dims_( const std::shared_ptr& dims) const override { - auto high_dim = std::make_shared>("highdim"); - return std::make_shared(high_dim, dims->low(), - dims->vecdim()); + return rkb_fold_adapt_dims(dims); } /// Hand-emit a simple element-wise loop function. @@ -123,54 +102,23 @@ class CR_11_Coulombσpσp_11 const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - // declare_function lives in dg.cc - extern std::string declare_function( - const std::shared_ptr& context, - const std::shared_ptr& dims, - const std::shared_ptr& args, const std::string& tlabel, - const std::string& function_descr, std::ostream& decl); - - std::shared_ptr localdims = adapt_dims_(dims); - // inline assign_symbols_: set symbol names on target/children and - // populate CodeSymbols - std::shared_ptr symbols(new CodeSymbols); - this->rr_target()->set_symbol("target"); - symbols->append_symbol("target"); - for (unsigned int c = 0; c < this->num_children(); c++) { - std::string symb = "src" + std::to_string(c); - this->rr_child(c)->set_symbol(symb); - symbols->append_symbol(symb); - } - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - const std::string tlabel = taskmgr.current().label(); - const std::string func_decl = - declare_function(context, localdims, symbols, tlabel, funcname, decl); - def << context->std_header(); - def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; - def << context->code_prefix(); - def << func_decl << context->open_block() << std::endl; - def << context->std_function_header(); const unsigned int nc = this->num_children(); - def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; - def << "for(int hsi = 0; hsi 1) ? nc - 1 : 0; - def << "/** Number of flops = " << nflops << " */\n"; - def << context->close_block() << std::endl; - def << context->code_postfix(); + const unsigned int nflops = (nc > 1) ? nc - 1 : 0; + rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs, + nflops); } }; diff --git "a/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" index 7391d3d31..fb6e8d128 100644 --- "a/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" @@ -28,6 +28,8 @@ #include #include +#include + namespace libint2 { /** @@ -85,35 +87,12 @@ class CR_11_σpCoulombσp_11 std::string spfunction_call( const std::shared_ptr& context, const std::shared_ptr& dims) const override { - std::ostringstream os; - os << context->label_to_function_name(this->label()) << "(inteval, " - << context->value_to_pointer(this->rr_target()->symbol()); - const unsigned int nc = this->num_children(); - for (unsigned int c = 0; c < nc; c++) { - os << ", " << context->value_to_pointer(this->rr_child(c)->symbol()); - } - // total_dim = product of all shell dims (all 4 shells are spectators) - unsigned int total_dim = 1; - for (unsigned int p = 0; p < 2; p++) { - SubIterator* si = target_->bra().member_subiter(p, 0); - total_dim *= si->num_iter(); - delete si; - si = target_->ket().member_subiter(p, 0); - total_dim *= si->num_iter(); - delete si; - } - os << "," << total_dim; - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - taskmgr.current().params()->max_hrr_hsrank(total_dim); - os << ")" << context->end_of_stat() << std::endl; - return os.str(); + return rkb_fold_spfunction_call(*this, target_, context); } std::shared_ptr adapt_dims_( const std::shared_ptr& dims) const override { - auto high_dim = std::make_shared>("highdim"); - return std::make_shared(high_dim, dims->low(), - dims->vecdim()); + return rkb_fold_adapt_dims(dims); } /// Hand-emit the per-component irrep linear combination over deriv-ERI @@ -122,33 +101,6 @@ class CR_11_σpCoulombσp_11 const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - extern std::string declare_function( - const std::shared_ptr& context, - const std::shared_ptr& dims, - const std::shared_ptr& args, const std::string& tlabel, - const std::string& function_descr, std::ostream& decl); - - std::shared_ptr localdims = adapt_dims_(dims); - std::shared_ptr symbols(new CodeSymbols); - this->rr_target()->set_symbol("target"); - symbols->append_symbol("target"); - for (unsigned int c = 0; c < this->num_children(); c++) { - std::string symb = "src" + std::to_string(c); - this->rr_child(c)->set_symbol(symb); - symbols->append_symbol(symb); - } - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - const std::string tlabel = taskmgr.current().label(); - const std::string func_decl = - declare_function(context, localdims, symbols, tlabel, funcname, decl); - def << context->std_header(); - def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; - def << context->code_prefix(); - def << func_decl << context->open_block() << std::endl; - def << context->std_function_header(); - def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; - def << "for(int hsi = 0; hsitarget_->oper()->descr().component_index(); std::string rhs; unsigned int nflops = 0; @@ -178,10 +130,8 @@ class CR_11_σpCoulombσp_11 throw std::runtime_error( "CR_11_σpCoulombσp_11::generate_code: invalid component index"); } - def << "target[hsi] = " << rhs << ";\n}\n"; - def << "/** Number of flops = " << nflops << " */\n"; - def << context->close_block() << std::endl; - def << context->code_postfix(); + rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs, + nflops); } }; diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" index 6a894be42..cc41ca285 100644 --- "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -28,6 +28,8 @@ #include #include +#include + #include namespace libint2 { @@ -114,35 +116,12 @@ class CR_11_σpσpCoulombσpσp_11 std::string spfunction_call( const std::shared_ptr& context, const std::shared_ptr& dims) const override { - std::ostringstream os; - os << context->label_to_function_name(this->label()) << "(inteval, " - << context->value_to_pointer(this->rr_target()->symbol()); - const unsigned int nc = this->num_children(); - for (unsigned int c = 0; c < nc; c++) { - os << ", " << context->value_to_pointer(this->rr_child(c)->symbol()); - } - // total_dim = product of all shell dims (all 4 shells are spectators) - unsigned int total_dim = 1; - for (unsigned int p = 0; p < 2; p++) { - SubIterator* si = target_->bra().member_subiter(p, 0); - total_dim *= si->num_iter(); - delete si; - si = target_->ket().member_subiter(p, 0); - total_dim *= si->num_iter(); - delete si; - } - os << "," << total_dim; - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - taskmgr.current().params()->max_hrr_hsrank(total_dim); - os << ")" << context->end_of_stat() << std::endl; - return os.str(); + return rkb_fold_spfunction_call(*this, target_, context); } std::shared_ptr adapt_dims_( const std::shared_ptr& dims) const override { - auto high_dim = std::make_shared>("highdim"); - return std::make_shared(high_dim, dims->low(), - dims->vecdim()); + return rkb_fold_adapt_dims(dims); } /// Hand-emit a simple element-wise loop function. @@ -153,57 +132,28 @@ class CR_11_σpσpCoulombσpσp_11 const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - // declare_function lives in dg.cc - extern std::string declare_function( - const std::shared_ptr& context, - const std::shared_ptr& dims, - const std::shared_ptr& args, const std::string& tlabel, - const std::string& function_descr, std::ostream& decl); - - std::shared_ptr localdims = adapt_dims_(dims); - // inline assign_symbols_: set symbol names on target/children and - // populate CodeSymbols - std::shared_ptr symbols(new CodeSymbols); - this->rr_target()->set_symbol("target"); - symbols->append_symbol("target"); - for (unsigned int c = 0; c < this->num_children(); c++) { - std::string symb = "src" + std::to_string(c); - this->rr_child(c)->set_symbol(symb); - symbols->append_symbol(symb); - } - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - const std::string tlabel = taskmgr.current().label(); - const std::string func_decl = - declare_function(context, localdims, symbols, tlabel, funcname, decl); - def << context->std_header(); - def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; - def << context->code_prefix(); - def << func_decl << context->open_block() << std::endl; - def << context->std_function_header(); // Sign patterns for each component, indexed by child order in constructor. // comp 0 (SS): 9 children, all +1 // comp 1-3 (SX,SY,SZ): 6 children, alternating +1,-1 // comp 4,8,12 (XS,YS,ZS): 6 children, alternating +1,-1 // comp 5-7,9-11,13-15 (XX..ZZ): 4 children, pattern -1,+1,+1,-1 const unsigned int nc = this->num_children(); - def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; - def << "for(int hsi = 0; hsi 0) rhs << " + "; + rhs << "src" << c << "[hsi]"; } } else if (nc == 6) { // SX,SY,SZ,XS,YS,ZS: alternating +/- for (unsigned int c = 0; c < 6; c++) { - def << ((c % 2 == 0) ? " + " : " - "); - def << "src" << c << "[hsi]"; + rhs << ((c % 2 == 0) ? " + " : " - "); + rhs << "src" << c << "[hsi]"; } } else if (nc == 4) { // XX,XY,...,ZZ: -src0 + src1 + src2 - src3 - def << "- src0[hsi] + src1[hsi] + src2[hsi] - src3[hsi]"; + rhs << "- src0[hsi] + src1[hsi] + src2[hsi] - src3[hsi]"; } else { // The σ·σ-fold sign pattern is keyed on the child count (9/6/4). If // particle-swap canonicalization ever deduplicates children to a @@ -213,11 +163,9 @@ class CR_11_σpσpCoulombσpσp_11 "σpσpCoulombσpσp generate_code(): unexpected num_children() (expected " "9, 6, or 4)"); } - def << ";\n}\n"; - unsigned int nflops = (nc > 1) ? nc - 1 : 0; - def << "/** Number of flops = " << nflops << " */\n"; - def << context->close_block() << std::endl; - def << context->code_postfix(); + const unsigned int nflops = (nc > 1) ? nc - 1 : 0; + rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs.str(), + nflops); } }; diff --git a/src/bin/libint/rkb_fold_codegen.h b/src/bin/libint/rkb_fold_codegen.h new file mode 100644 index 000000000..a4e298865 --- /dev/null +++ b/src/bin/libint/rkb_fold_codegen.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2004-2026 Edward F. Valeev + * + * This file is part of Libint compiler. + * + * Libint compiler 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 compiler 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 compiler. If not, see . + * + */ + +#ifndef LIBINT_RKB_FOLD_CODEGEN_H +#define LIBINT_RKB_FOLD_CODEGEN_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace libint2 { + +// Shared code-emission helpers for the σ·p "element-wise fold" recurrence +// relations: CR_11_Coulombσp_11, CR_11_Coulombσpσp_11, CR_11_σpCoulombσp_11 and +// CR_11_σpσpCoulombσpσp_11. Each decomposes its target into a fixed linear +// combination of first-derivative TwoPRep children, evaluated element-wise over +// the (spectator) shell dimensions. Across all four, only the operator label +// and the right-hand-side expression differ; the call-site emission, dimension +// adaptation, and per-function scaffold are identical and live here so they are +// written (and corrected) once rather than copy-pasted four times. + +/// adapt_dims_ body: replace the high dimension with the runtime "highdim" +/// symbol (the fold loops over the high/spectator dimension only). +inline std::shared_ptr rkb_fold_adapt_dims( + const std::shared_ptr& dims) { + auto high_dim = std::make_shared>("highdim"); + return std::make_shared(high_dim, dims->low(), + dims->vecdim()); +} + +/// spfunction_call body: emit the call to the shared element-wise function, +/// passing target, the children, and total_dim (= product of all four spectator +/// shell dimensions). @p self supplies the public RR interface; @p tgt is the +/// typed target (for bra()/ket() member iteration). +template +std::string rkb_fold_spfunction_call( + const RR& self, const TypedTarget& tgt, + const std::shared_ptr& context) { + std::ostringstream os; + os << context->label_to_function_name(self.label()) << "(inteval, " + << context->value_to_pointer(self.rr_target()->symbol()); + const unsigned int nc = self.num_children(); + for (unsigned int c = 0; c < nc; c++) { + os << ", " << context->value_to_pointer(self.rr_child(c)->symbol()); + } + // total_dim = product of all shell dims (all 4 shells are spectators) + unsigned int total_dim = 1; + for (unsigned int p = 0; p < 2; p++) { + SubIterator* si = tgt->bra().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + si = tgt->ket().member_subiter(p, 0); + total_dim *= si->num_iter(); + delete si; + } + os << "," << total_dim; + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + taskmgr.current().params()->max_hrr_hsrank(total_dim); + os << ")" << context->end_of_stat() << std::endl; + return os.str(); +} + +/// generate_code body: emit the full element-wise function. The caller computes +/// the per-target right-hand-side expression @p rhs (e.g. +/// "src0[hsi] + src1[hsi]") and its flop count @p nflops; everything else — +/// symbol assignment, function declaration, and the highdim loop scaffold — is +/// identical across the four operators and emitted here. +template +void rkb_fold_generate_code( + const RR& self, const std::shared_ptr& context, + const std::shared_ptr& dims, const std::string& funcname, + std::ostream& decl, std::ostream& def, const std::string& rhs, + unsigned int nflops) { + // declare_function lives in dg.cc + extern std::string declare_function( + const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::shared_ptr& args, const std::string& tlabel, + const std::string& function_descr, std::ostream& decl); + + std::shared_ptr localdims = rkb_fold_adapt_dims(dims); + // inline assign_symbols_: set symbol names on target/children and populate + // CodeSymbols + std::shared_ptr symbols(new CodeSymbols); + self.rr_target()->set_symbol("target"); + symbols->append_symbol("target"); + for (unsigned int c = 0; c < self.num_children(); c++) { + std::string symb = "src" + std::to_string(c); + self.rr_child(c)->set_symbol(symb); + symbols->append_symbol(symb); + } + LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); + const std::string tlabel = taskmgr.current().label(); + const std::string func_decl = + declare_function(context, localdims, symbols, tlabel, funcname, decl); + def << context->std_header(); + def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; + def << context->code_prefix(); + def << func_decl << context->open_block() << std::endl; + def << context->std_function_header(); + def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; + def << "for(int hsi = 0; hsiclose_block() << std::endl; + def << context->code_postfix(); +} + +} // namespace libint2 + +#endif // LIBINT_RKB_FOLD_CODEGEN_H From 280cbfe849399f02d086b225fcd6f454d05304ac Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 09:38:50 -0400 Subject: [PATCH 31/64] Define g_progress in test.cc so check-libint2compiler links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GenerateCode() (defined in buildtest.h) references the global CodeGenProgress g_progress, which was defined only in build_libint.cc. The standalone compiler test (src/bin/libint/test.cc -> libint-libcompiler-test, the check-libint2compiler target) includes buildtest.h and instantiates GenerateCode but does not link build_libint.cc, so it failed with an undefined `_g_progress` symbol — breaking CI (the macOS Repo job's check-libint2compiler step, which in turn starved the Export job of its compiler artifact). g_progress is an executable-scope global (build_libint defines its own); define it in test.cc as well so the compiler test links. buildtest.h is included only by these two executable mains and never by a library TU (GenerateCode is non-inline), so this cannot be moved into libint2-libcompiler.a without a duplicate-symbol clash — per-main definition is the correct shape. Verified: `cmake --build ... --target check-libint2compiler` now passes (coretest/build + coretest/run, 2/2). --- src/bin/libint/test.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bin/libint/test.cc b/src/bin/libint/test.cc index 9f9d0c061..0b02243a2 100644 --- a/src/bin/libint/test.cc +++ b/src/bin/libint/test.cc @@ -38,6 +38,12 @@ using namespace std; using namespace libint2; +// GenerateCode() (defined in buildtest.h) references the global progress +// reporter, so this standalone compiler-test executable must define it too, +// mirroring the definition in build_libint.cc. Without this the +// check-libint2compiler test fails to link with an undefined `g_progress`. +CodeGenProgress g_progress; + long living_count = 0; const unsigned int use_integrals = std::numeric_limits::max(); const unsigned int use_quartets = 0; From a02af2ae2b8a3bbd4b1291f05a9479f15681ae71 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 11:30:06 -0400 Subject: [PATCH 32/64] Fix xs_xx buildfnidx stride for non-center-dependent 3-center operators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 3-center (xs_xx) dispatch in compute2 selected the ket stride `ket_lmax` using a hard-coded `LIBINT2_CENTER_DEPENDENT_MAX_AM_3eri`. That macro is 1 for plain 3-center ERI (whose fitting center reaches ERI3_MAX_AM while the paired centers only reach MAX_AM, so the build table is non-cubic and the ket stride must be hard_default_lmax_). But _initialize() sets hard_default_lmax_ per the *actual* task's center-dependence: for the RKB 3-center operators (3coulomb_opop, 3coulomb_op), which are NOT center-dependent, it is INT_MAX. Because the stride selection ignored the real operator and always trusted the 3eri macro, those RKB engines computed buildfnidx = (bra1.l*ket_lmax + ket1.l)*ket_lmax + ket2.l with ket_lmax = hard_default_lmax_ = INT_MAX, i.e. a wildly out-of-bounds index into the symmetric [hard_lmax_]^3 build table — a garbage function pointer and a segfault on the first shell triplet with l(ket1) > 0 (the (s|s,p) μ<ν case; the (s|s,s) case has buildfnidx 0 and happened to survive). Replace the hard-coded, deriv-switched 3eri logic with ket_lmax = min(hard_lmax_, hard_default_lmax_) which is correct for every 3-center task: for center-dependent 3eri it yields hard_default_lmax_ (the paired max, unchanged behaviour); for the non-center-dependent RKB operators it yields hard_lmax_, matching their cubic build-table stride. The μ<->ν canonicalization (swap_tket) was already correct; only the stride was wrong. Verified at MAX_AM=2 with RKB_ERI3 enabled: "RKB Coulomb integrals" 7653 assertions pass (3-center sections now run, previously segfaulted); full unit suite 20,034,963 / 33 cases pass, no regression to plain ERI3. --- include/libint2/engine.impl.h | 36 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 7aeb49ba1..e1dd2d384 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -2053,28 +2053,20 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( // xs_xx case to compute buildfnidx from those swapped shells. [[fallthrough]]; case BraKet::xs_xx: { - /// lmax might be center dependent - int ket_lmax = hard_lmax_; - switch (deriv_order_) { - // N.B. notice extra PP_CAT to avoid using - // LIBINT2_CENTER_DEPENDENT_MAX_AM_3eri as a subtoken which gets - // expanded too early ... i.e. PP_CAT is "not associative" -#define BOOST_PP_NBODYENGINE_MCR8(r, data, i, elem) \ - case i: \ - BOOST_PP_IF(BOOST_PP_IS_1(BOOST_PP_CAT( \ - LIBINT2_CENTER_DEPENDENT_MAX_AM_, \ - BOOST_PP_CAT(3eri, BOOST_PP_IIF(BOOST_PP_GREATER(i, 0), i, \ - BOOST_PP_EMPTY())))), \ - ket_lmax = hard_default_lmax_, BOOST_PP_EMPTY()); \ - break; - - BOOST_PP_LIST_FOR_EACH_I(BOOST_PP_NBODYENGINE_MCR8, _, - BOOST_PP_NBODY_DERIV_ORDER_LIST) - - default: - assert(false && "missing case in switch"); - abort(); - } + // The ket (paired/AO) centers may have a lower max AM than the bra + // (fitting) center for *center-dependent* 3-center tasks — e.g. 3eri, + // whose fitting center goes up to ERI3_MAX_AM while the paired centers + // only reach MAX_AM, giving a non-cubic build table. _initialize() sets + // hard_default_lmax_ to that paired max (+1) for center-dependent + // tasks, or to INT_MAX for tasks that are NOT center-dependent (e.g. + // the RKB 3-center 3coulomb_opop / 3coulomb_op, whose build table is + // the symmetric [hard_lmax_]^3). Taking the min picks the correct ket + // stride in both cases and matches the per-task build-table dimensions. + // (Previously this hard-coded LIBINT2_CENTER_DEPENDENT_MAX_AM_3eri, + // which set ket_lmax = hard_default_lmax_ = INT_MAX for the non-center- + // dependent RKB operators, producing an out-of-bounds buildfnidx and a + // segfault on the first l(ket1) > 0 shell triplet.) + const int ket_lmax = std::min(hard_lmax_, hard_default_lmax_); assert(bra1.contr[0].l <= hard_lmax_ && "the angular momentum limit is exceeded"); assert(ket1.contr[0].l <= ket_lmax && From 4b3d82e6c3b3efb252ea4bba606f56a24c5fba65 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 11:30:06 -0400 Subject: [PATCH 33/64] CI: build and test RKB_ERI3; guard 3-center RKB unit sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cmake.yml: enable LIBINT2_ENABLE_RKB_ERI3=0 (RKB_ERI3_MAX_AM=2) in the shared BUILD_CONFIG so CI actually generates the 3-center RKB kernels and runs the "Coulombσpσp/Coulombσp 3-center" unit sections (previously RKB_ERI3 defaulted to OFF, so that code path was never exercised on CI). - test-2body.cc: compile-guard the three 3-center RKB sections on LIBINT2_TASK_EXISTS_3coulomb_opop / _3coulomb_op. When RKB_ERI3 is disabled, the 4-center coulomb_opop engine still constructs (so the lmax_exceeded catch does not fire), but compute() on the 3-center braket would dispatch to an absent kernel; the guard makes these sections skip cleanly instead of aborting. With RKB_ERI3 enabled (the new CI default) the guards are transparent and the sections run. Verified both ways at MAX_AM=2: RKB_ERI3 on -> full suite 20,034,963 / 33 pass (3-center sections run); RKB_ERI3 off -> 20,034,734 / 33 pass (3-center sections skipped, no abort). --- .github/workflows/cmake.yml | 2 ++ export/tests/unit/test-2body.cc | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index a6b861416..551d6e625 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -63,6 +63,8 @@ jobs: -D LIBINT2_ERI3_MAX_AM=3;2 -D LIBINT2_ENABLE_ERI=1 -D LIBINT2_ENABLE_ERI3=1 + -D LIBINT2_ENABLE_RKB_ERI3=0 + -D LIBINT2_RKB_ERI3_MAX_AM=2 -D LIBINT2_ENABLE_ONEBODY=1 -D LIBINT2_DISABLE_ONEBODY_PROPERTY_DERIVS=ON -D LIBINT2_MULTIPOLE_MAX_ORDER=2 diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 333c7263a..689dacfb3 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -775,6 +775,13 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } +// The 3-center RKB sections below exercise kernels that exist only when +// LIBINT2_ENABLE_RKB_ERI3 >= 0. The 4-center coulomb_opop engine still +// constructs successfully without them (so the lmax_exceeded catch does not +// fire), but compute() on the 3-center braket would dispatch to the absent +// kernel and abort in the `default` task. Guard at compile time on the +// generated task-exists macro. +#if LIBINT2_TASK_EXISTS_3coulomb_opop SECTION("Coulombσpσp 3-center xs_xx") { // 3-center RKB Coulombσpσp integral, (P | σ·p_μ σ·p_ν / r12) with P on // the bra (fitting/DF) and σ·p acting on the AO pair (μ,ν) on the ket. @@ -915,7 +922,9 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } } +#endif // LIBINT2_TASK_EXISTS_3coulomb_opop +#if LIBINT2_TASK_EXISTS_3coulomb_op SECTION("Coulombσp 3-center xs_xx") { // 3-center single-σ·p ("DF-Gaunt" B-factor) integral // (P | 1/r12 | μ, σ·p ν), @@ -1038,6 +1047,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } } +#endif // LIBINT2_TASK_EXISTS_3coulomb_op } TEST_CASE("Erfx_Coulomb integrals", "[engine][2-body]") { From 598b674812688ba6ad8c9550ed6a948280d1bd6e Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 12:16:26 -0400 Subject: [PATCH 34/64] Allow C++ API when only 3-center RKB ERI is enabled The cxxapi.h availability guard accepted RKB_ERI (4-center) as a sufficient 2-body class but omitted RKB_ERI3 (3-center). A build with 1-body + only RKB_ERI3 enabled (no ERI/ERI3/ERI2/RKB_ERI) would therefore hard-error even though the library provides usable 2-body integrals. Add LIBINT_INCLUDE_RKB_ERI3 to the disjunction and the message. (Copilot review.) --- include/libint2/cxxapi.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/libint2/cxxapi.h b/include/libint2/cxxapi.h index 22686f958..933268a2b 100644 --- a/include/libint2/cxxapi.h +++ b/include/libint2/cxxapi.h @@ -35,9 +35,10 @@ #if !defined(LIBINT_INCLUDE_ONEBODY) || \ !(defined(LIBINT_INCLUDE_ERI) || defined(LIBINT_INCLUDE_ERI3) || \ - defined(LIBINT_INCLUDE_ERI2) || defined(LIBINT_INCLUDE_RKB_ERI)) + defined(LIBINT_INCLUDE_ERI2) || defined(LIBINT_INCLUDE_RKB_ERI) || \ + defined(LIBINT_INCLUDE_RKB_ERI3)) #error \ - "C++ API is only supported if both 1-body and some (eri, eri3, eri2, rkb_eri) 2-body integrals are enabled" + "C++ API is only supported if both 1-body and some (eri, eri3, eri2, rkb_eri, rkb_eri3) 2-body integrals are enabled" #endif #include From e7f86812820558cf541d9ab2ed2df171f2b0028e Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 13:32:56 -0400 Subject: [PATCH 35/64] Fix Fortran ERI test: use primitive-differentiator underscore in (ss|ss) fundamental names The macOS case-insensitive-filesystem differentiator (commit d4247b0) appends an extra underscore to primitive (non-contracted) function labels in CGF::label(), so the generated struct members, prereq reads, and LIBINT2_DEFINED__ macros for the (ss|ss) fundamentals are now _aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_N (4 underscores). include/libint2.h's LIBINT_T_SS_EREP_SS macro was updated to match, but two static consumers still hardcoded the old 3-underscore name: - export/fortran/test-eri.cc - export/fortran/libint_f.F90 As a result every #ifdef LIBINT2_DEFINED__aB_s___0... guard evaluated false, the fundamental-fill code compiled out, and the prereq read uninitialized memory -> the "Fortran ERI" test produced garbage and failed in CI (it passed locally only because a fresh zeroed heap yields rel_error = 0/0 = NaN, a vacuous pass). Update both files to the 4-underscore primitive naming so the convention is applied consistently regardless of platform or Fortran/C++ API. --- export/fortran/libint_f.F90 | 84 ++++++++++++++++++------------------- export/fortran/test-eri.cc | 84 ++++++++++++++++++------------------- 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/export/fortran/libint_f.F90 b/export/fortran/libint_f.F90 index 0293da5e3..e59d4a5ed 100644 --- a/export/fortran/libint_f.F90 +++ b/export/fortran/libint_f.F90 @@ -520,88 +520,88 @@ SUBROUTINE compute_eri_f(contrdepth, deriv_order, am1, c1, alpha1, A, & am_tot = am + deriv_order -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_0 - IF (am_tot >= 0) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_0(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_0 + IF (am_tot >= 0) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_0(1) = & C1234*pfac*F(1, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_1 - IF (am_tot >= 1) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_1(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_1 + IF (am_tot >= 1) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_1(1) = & C1234*pfac*F(2, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_2 - IF (am_tot >= 2) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_2(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_2 + IF (am_tot >= 2) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_2(1) = & C1234*pfac*F(3, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_3 - IF (am_tot >= 3) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_3(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_3 + IF (am_tot >= 3) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_3(1) = & C1234*pfac*F(4, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_4 - IF (am_tot >= 4) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_4(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_4 + IF (am_tot >= 4) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_4(1) = & C1234*pfac*F(5, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_5 - IF (am_tot >= 5) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_5(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_5 + IF (am_tot >= 5) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_5(1) = & C1234*pfac*F(6, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_6 - IF (am_tot >= 6) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_6(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_6 + IF (am_tot >= 6) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_6(1) = & C1234*pfac*F(7, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_7 - IF (am_tot >= 7) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_7(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_7 + IF (am_tot >= 7) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_7(1) = & C1234*pfac*F(8, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_8 - IF (am_tot >= 8) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_8(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_8 + IF (am_tot >= 8) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_8(1) = & C1234*pfac*F(9, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_9 - IF (am_tot >= 9) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_9(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_9 + IF (am_tot >= 9) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_9(1) = & C1234*pfac*F(10, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_10 - IF (am_tot >= 10) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_10(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_10 + IF (am_tot >= 10) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_10(1) = & C1234*pfac*F(11, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_11 - IF (am_tot >= 11) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_11(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_11 + IF (am_tot >= 11) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_11(1) = & C1234*pfac*F(12, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_12 - IF (am_tot >= 12) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_12(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_12 + IF (am_tot >= 12) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_12(1) = & C1234*pfac*F(13, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_13 - IF (am_tot >= 13) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_13(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_13 + IF (am_tot >= 13) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_13(1) = & C1234*pfac*F(14, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_14 - IF (am_tot >= 14) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_14(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_14 + IF (am_tot >= 14) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_14(1) = & C1234*pfac*F(15, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_15 - IF (am_tot >= 15) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_15(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_15 + IF (am_tot >= 15) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_15(1) = & C1234*pfac*F(16, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_16 - IF (am_tot >= 16) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_16(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_16 + IF (am_tot >= 16) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_16(1) = & C1234*pfac*F(17, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_17 - IF (am_tot >= 17) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_17(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_17 + IF (am_tot >= 17) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_17(1) = & C1234*pfac*F(18, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_18 - IF (am_tot >= 18) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_18(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_18 + IF (am_tot >= 18) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_18(1) = & C1234*pfac*F(19, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_19 - IF (am_tot >= 19) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_19(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_19 + IF (am_tot >= 19) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_19(1) = & C1234*pfac*F(20, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_20 - IF (am_tot >= 20) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_20(1) = & +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_20 + IF (am_tot >= 20) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_20(1) = & C1234*pfac*F(21, p1234) #endif ENDDO diff --git a/export/fortran/test-eri.cc b/export/fortran/test-eri.cc index d25381474..7304659aa 100644 --- a/export/fortran/test-eri.cc +++ b/export/fortran/test-eri.cc @@ -257,130 +257,130 @@ void compute_eri_c(int &contrdepth, int &deriv_order, int &am1, double *c1, const int am_tot = am + deriv_order; -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_0 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_0 if (am_tot >= 0) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_0[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_0[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 0]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_1 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_1 if (am_tot >= 1) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_1[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_1[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 1]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_2 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_2 if (am_tot >= 2) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_2[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_2[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 2]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_3 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_3 if (am_tot >= 3) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_3[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_3[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 3]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_4 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_4 if (am_tot >= 4) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_4[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_4[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 4]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_5 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_5 if (am_tot >= 5) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_5[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_5[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 5]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_6 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_6 if (am_tot >= 6) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_6[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_6[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 6]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_7 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_7 if (am_tot >= 7) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_7[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_7[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 7]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_8 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_8 if (am_tot >= 8) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_8[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_8[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 8]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_9 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_9 if (am_tot >= 9) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_9[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_9[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 9]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_10 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_10 if (am_tot >= 10) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_10[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_10[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 10]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_11 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_11 if (am_tot >= 11) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_11[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_11[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 11]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_12 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_12 if (am_tot >= 12) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_12[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_12[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 12]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_13 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_13 if (am_tot >= 13) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_13[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_13[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 13]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_14 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_14 if (am_tot >= 14) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_14[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_14[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 14]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_15 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_15 if (am_tot >= 15) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_15[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_15[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 15]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_16 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_16 if (am_tot >= 16) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_16[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_16[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 16]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_17 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_17 if (am_tot >= 17) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_17[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_17[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 17]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_18 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_18 if (am_tot >= 18) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_18[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_18[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 18]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_19 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_19 if (am_tot >= 19) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_19[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_19[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 19]; #endif -#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_20 +#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_20 if (am_tot >= 20) erieval[p0123] - ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_20[0] = + ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_20[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 20]; #endif From 042a915382a9489ad0ef540f4cd458a0a5fcb727 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 14:38:08 -0400 Subject: [PATCH 36/64] =?UTF-8?q?Rename=20engine=20Operator::=CF=83pe?= =?UTF-8?q?=CE=BC=CF=83p=20to=20Operator::opemuop=20for=20naming=20consist?= =?UTF-8?q?ency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The engine Operator enum uses ASCII names for the σ·p (RKB) operators (opVop, coulomb_opop, op_coulomb_op, opop_coulomb_opop, coulomb_op); the dipole operator was the lone Unicode outlier. Everything downstream of it was already ASCII opemuop (the descriptor label in oper.h, the BOOST_PP_NBODY_OPERATOR_LIST entry, the task label, and the generated LIBINT2_MAX_AM_opemuop macro), so the enum was the only mismatch. Rename Operator::σpeμσp -> Operator::opemuop (enum value, last_1body_oper, operator_traits specialization, the two engine.impl.h dispatch checks, and the test-1body.cc Engine construction). The compiler-side descriptor class σpeμσp_Descr / σpeμσpOper stays Unicode (matches σpVσp_Descr et al.); the math notation σpeμσp is retained in comments. External consumers now reference the ASCII libint2::Operator::opemuop rather than a Unicode identifier. --- export/tests/unit/test-1body.cc | 2 +- include/libint2/engine.h | 9 ++++++--- include/libint2/engine.impl.h | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/export/tests/unit/test-1body.cc b/export/tests/unit/test-1body.cc index e087dbdf2..a58567f11 100644 --- a/export/tests/unit/test-1body.cc +++ b/export/tests/unit/test-1body.cc @@ -260,7 +260,7 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpeμσp correctness", const auto lmax = std::min(2, LIBINT2_MAX_AM_opemuop); if (lmax < 2) return; - auto engine = Engine(Operator::σpeμσp, 2, lmax); + auto engine = Engine(Operator::opemuop, 2, lmax); engine.set_params(std::array{{0.0, 0.0, 0.0}}); // (s|σpeμσp|d) and (d|σpeμσp|s) diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 17990a0ed..e7afaf4b6 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -197,7 +197,10 @@ enum class Operator { /// components (trace + 3 antisym), indexed as `4*k + q` with `k ∈ {x,y,z}` /// the dipole direction and `q ∈ {0=trace, 1=σ_x, 2=σ_y, 3=σ_z}` the Pauli /// piece. Origin set via `engine.set_params(std::array)`. - σpeμσp, + /// + /// Engine-enum names are ASCII (`op` = σ·p); the σ·p·r·σ·p math name σpeμσp + /// lives on the compiler side (σpeμσp_Descr) and in the codegen label. + opemuop, /// \f$ \delta(\vec{r}_1 - \vec{r}_2) \f$ delta, /// (2-body) Coulomb operator = \f$ r_{12}^{-1} \f$ @@ -259,7 +262,7 @@ enum class Operator { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!keep this // updated!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! first_1body_oper = overlap, - last_1body_oper = σpeμσp, + last_1body_oper = opemuop, first_2body_oper = delta, last_2body_oper = stg_x_coulomb, first_oper = first_1body_oper, @@ -446,7 +449,7 @@ struct operator_traits }; template <> -struct operator_traits +struct operator_traits : public operator_traits { static constexpr auto nopers = 12; static constexpr auto intrinsic_deriv_order = 2; diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index e1dd2d384..25a51df4d 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -1069,7 +1069,7 @@ __libint2_engine_inline void Engine::compute_primdata(Libint_t& primdata, // } if (oper_ == Operator::emultipole1 || oper_ == Operator::emultipole2 || - oper_ == Operator::emultipole3 || oper_ == Operator::σpeμσp) { + oper_ == Operator::emultipole3 || oper_ == Operator::opemuop) { const auto& O = any_cast< const operator_traits::oper_params_type&>( params_); // same as emultipoleX @@ -1118,7 +1118,7 @@ __libint2_engine_inline void Engine::compute_primdata(Libint_t& primdata, if (oper_ == Operator::kinetic || (deriv_order_ > 0) || oper_ == Operator::opVop || oper_ == Operator::op_q_gau_op || - oper_ == Operator::σpeμσp) { + oper_ == Operator::opemuop) { #if LIBINT2_DEFINED(eri, two_alpha0_bra) primdata.two_alpha0_bra[0] = 2.0 * alpha1; #endif From 6cc2b22a78664c9998518a43c675f0b382b02c62 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 14:38:18 -0400 Subject: [PATCH 37/64] =?UTF-8?q?Dedup=201-body=20=CF=83=C2=B7p=20fold=20i?= =?UTF-8?q?nto=20shared=20sigma=5Fp=5F1body=5Ffold=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CR_1_σpVσp_1 and CR_1_σpeμσp_1 had near-identical constructors: both build the 6 first-derivative basis functions and the same 4-case Pauli-quaternion fold (σ_a σ_b = δ_ab + iε_abc σ_c), differing only in the inner child integral (electrostatic potential V vs Cartesian dipole r_k). Extract the shared structure into sigma_p_1body_fold() in the new rkb_fold_1body.h; each operator now supplies just its inner child via a make_child(Da, Db) lambda. No behavior change (same children, signs, and flop counts). This is the 1-body sibling of the 2-electron rkb_fold_codegen.h sharing already used by the comp_11_*σp*_11.h headers. --- .../libint/comp_1_\317\203pV\317\203p_1.h" | 72 ++-------- .../comp_1_\317\203pe\316\274\317\203p_1.h" | 71 ++-------- src/bin/libint/rkb_fold_1body.h | 124 ++++++++++++++++++ 3 files changed, 140 insertions(+), 127 deletions(-) create mode 100644 src/bin/libint/rkb_fold_1body.h diff --git "a/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" "b/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" index 9fbdef361..4d64da30b 100644 --- "a/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" +++ "b/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" @@ -22,6 +22,7 @@ #define LIBINT_COMP_1_ΣPVΣP_1_H #include +#include namespace libint2 { @@ -78,76 +79,19 @@ CR_1_σpVσp_1::CR_1_σpVσp_1(const std::shared_ptr &Tint, // primitive Gaussians only if (a.contracted() || b.contracted()) return; - using namespace libint2::algebra; - using namespace libint2::prefactor; - using libint2::algebra::operator*; - const mType zero_m(0u); ChildFactory> factory(this); - constexpr auto x = 0; - constexpr auto y = 1; - constexpr auto z = 2; - - F Dx_a{a}; - Dx_a.deriv().inc(x); - F Dx_b{b}; - Dx_b.deriv().inc(x); - F Dy_a{a}; - Dy_a.deriv().inc(y); - F Dy_b{b}; - Dy_b.deriv().inc(y); - F Dz_a{a}; - Dz_a.deriv().inc(z); - F Dz_b{b}; - Dz_b.deriv().inc(z); - - // (a|W0|b) = (d a/dAx | V | d b/dBx) + (d a/dAy | V | d b/dBy) + (d a/dAz | V - // | d b/dBz) - switch (oper->descr().quaternion_index()) { - case 0: { - auto Dx_a_V_Dx_b = factory.make_child(Dx_a, Dx_b, zero_m); - auto Dy_a_V_Dy_b = factory.make_child(Dy_a, Dy_b, zero_m); - auto Dz_a_V_Dz_b = factory.make_child(Dz_a, Dz_b, zero_m); - if (is_simple()) { - expr_ = Dx_a_V_Dx_b + Dy_a_V_Dy_b + Dz_a_V_Dz_b; - nflops_ += 2; - } - } break; - // (a|Wx|b) = (d a/dAy | V | d b/dBz) - (d a/dAz | V | d b/dBy) - case 1: { - auto Dy_a_V_Dz_b = factory.make_child(Dy_a, Dz_b, zero_m); - auto Dz_a_V_Dy_b = factory.make_child(Dz_a, Dy_b, zero_m); - if (is_simple()) { - expr_ = Dy_a_V_Dz_b - Dz_a_V_Dy_b; - nflops_ += 1; - } - } break; - // (a|Wy|b) = (d a/dAz | V | d b/dBx) - (d a/dAx | V | d b/dBz) - case 2: { - auto Dz_a_V_Dx_b = factory.make_child(Dz_a, Dx_b, zero_m); - auto Dx_a_V_Dz_b = factory.make_child(Dx_a, Dz_b, zero_m); - if (is_simple()) { - expr_ = Dz_a_V_Dx_b - Dx_a_V_Dz_b; - nflops_ += 1; - } - } break; - // (a|Wz|b) = (d a/dAx | V | d b/dBy) - (d a/dAy | V | d - // b/dBx) - case 3: { - auto Dx_a_V_Dy_b = factory.make_child(Dx_a, Dy_b, zero_m); - auto Dy_a_V_Dx_b = factory.make_child(Dy_a, Dx_b, zero_m); - if (is_simple()) { - expr_ = Dx_a_V_Dy_b - Dy_a_V_Dx_b; - nflops_ += 1; - } - } break; - default: - throw std::runtime_error("CR_1_σpVσp_1: invalid quaternionic index"); - } + // σ·p V σ·p fold: inner child is the electrostatic-potential integral + // (Da | V | Db). Shared structure lives in sigma_p_1body_fold(). + sigma_p_1body_fold( + a, b, oper->descr().quaternion_index(), is_simple(), expr_, nflops_, + [&](const F &Da, const F &Db) { + return factory.make_child(Da, Db, zero_m); + }); } // CR_1_σpVσp_1::CR_1_σpVσp_1 diff --git "a/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" "b/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" index 128283ab6..f46bd9db4 100644 --- "a/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" +++ "b/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" @@ -22,6 +22,7 @@ #define LIBINT_COMP_1_ΣPRΣP_1_H #include +#include namespace libint2 { @@ -80,79 +81,23 @@ CR_1_σpeμσp_1::CR_1_σpeμσp_1(const std::shared_ptr &Tint, // operator r_k for primitive Gaussians only if (a.contracted() || b.contracted()) return; - using namespace libint2::algebra; - using namespace libint2::prefactor; - using libint2::algebra::operator*; - ChildFactory, EmptySet>> factory(this); - constexpr auto x = 0; - constexpr auto y = 1; - constexpr auto z = 2; - - F Dx_a{a}; - Dx_a.deriv().inc(x); - F Dx_b{b}; - Dx_b.deriv().inc(x); - F Dy_a{a}; - Dy_a.deriv().inc(y); - F Dy_b{b}; - Dy_b.deriv().inc(y); - F Dz_a{a}; - Dz_a.deriv().inc(z); - F Dz_b{b}; - Dz_b.deriv().inc(z); - // Build the dipole multipole descriptor for direction k. const auto k = oper->descr().dipole_dir(); CartesianMultipole_Descr<3u> mu_k; mu_k.inc(k, 1); // r_k = (kx,ky,kz) with k_k = 1, others 0 - // Pauli quaternion fold per (k, q): - // q=0: trace δ_ab → Σ_a (∂_a μ | r_k | ∂_a ν) - // q=1: σ_x antisym → (∂_y μ | r_k | ∂_z ν) − (∂_z μ | r_k | ∂_y ν) - // q=2: σ_y antisym → (∂_z μ | r_k | ∂_x ν) − (∂_x μ | r_k | ∂_z ν) - // q=3: σ_z antisym → (∂_x μ | r_k | ∂_y ν) − (∂_y μ | r_k | ∂_x ν) - switch (oper->descr().quaternion_index()) { - case 0: { - auto Dx_a_R_Dx_b = factory.make_child(Dx_a, Dx_b, EmptySet(), mu_k); - auto Dy_a_R_Dy_b = factory.make_child(Dy_a, Dy_b, EmptySet(), mu_k); - auto Dz_a_R_Dz_b = factory.make_child(Dz_a, Dz_b, EmptySet(), mu_k); - if (is_simple()) { - expr_ = Dx_a_R_Dx_b + Dy_a_R_Dy_b + Dz_a_R_Dz_b; - nflops_ += 2; - } - } break; - case 1: { - auto Dy_a_R_Dz_b = factory.make_child(Dy_a, Dz_b, EmptySet(), mu_k); - auto Dz_a_R_Dy_b = factory.make_child(Dz_a, Dy_b, EmptySet(), mu_k); - if (is_simple()) { - expr_ = Dy_a_R_Dz_b - Dz_a_R_Dy_b; - nflops_ += 1; - } - } break; - case 2: { - auto Dz_a_R_Dx_b = factory.make_child(Dz_a, Dx_b, EmptySet(), mu_k); - auto Dx_a_R_Dz_b = factory.make_child(Dx_a, Dz_b, EmptySet(), mu_k); - if (is_simple()) { - expr_ = Dz_a_R_Dx_b - Dx_a_R_Dz_b; - nflops_ += 1; - } - } break; - case 3: { - auto Dx_a_R_Dy_b = factory.make_child(Dx_a, Dy_b, EmptySet(), mu_k); - auto Dy_a_R_Dx_b = factory.make_child(Dy_a, Dx_b, EmptySet(), mu_k); - if (is_simple()) { - expr_ = Dx_a_R_Dy_b - Dy_a_R_Dx_b; - nflops_ += 1; - } - } break; - default: - throw std::runtime_error("CR_1_σpeμσp_1: invalid quaternionic index"); - } + // σ·p r_k σ·p fold: inner child is the Cartesian-dipole integral + // (Da | r_k | Db). Shared structure lives in sigma_p_1body_fold(). + sigma_p_1body_fold( + a, b, oper->descr().quaternion_index(), is_simple(), expr_, nflops_, + [&](const F &Da, const F &Db) { + return factory.make_child(Da, Db, EmptySet(), mu_k); + }); } // CR_1_σpeμσp_1::CR_1_σpeμσp_1 diff --git a/src/bin/libint/rkb_fold_1body.h b/src/bin/libint/rkb_fold_1body.h new file mode 100644 index 000000000..85ab05e02 --- /dev/null +++ b/src/bin/libint/rkb_fold_1body.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2004-2026 Edward F. Valeev + * + * This file is part of Libint compiler. + * + * Libint compiler 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 compiler 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 compiler. If not, see . + * + */ + +#ifndef LIBINT_RKB_FOLD_1BODY_H +#define LIBINT_RKB_FOLD_1BODY_H + +#include + +#include + +namespace libint2 { + +/** + * Shared one-body \f$ \sigma\cdot\hat{p}\, \hat{O}\, \sigma\cdot\hat{p} \f$ + * Pauli-quaternion fold for primitive Gaussians. Both \c CR_1_σpVσp_1 (inner + * operator = electrostatic potential V) and \c CR_1_σpeμσp_1 (inner operator = + * Cartesian dipole \f$ r_k \f$) reduce to exactly this combination of the 6 + * first-derivative basis functions; only the inner child integral differs, so + * the caller supplies it via @p make_child. + * + * Using \f$ \sigma_a \sigma_b = \delta_{ab} + i\epsilon_{abc}\sigma_c \f$, the + * quaternion index @p q selects: + * - q=0 (trace δ_ab): Σ_a (∂_a a | O | ∂_a b) + * - q=1 (σ_x antisym): (∂_y a | O | ∂_z b) − (∂_z a | O | ∂_y b) + * - q=2 (σ_y antisym): (∂_z a | O | ∂_x b) − (∂_x a | O | ∂_z b) + * - q=3 (σ_z antisym): (∂_x a | O | ∂_y b) − (∂_y a | O | ∂_x b) + * + * Children are always created (so they register on the graph); @p expr is set + * only when @p is_simple. + * + * @tparam F basis function type (CGShell or CGF) + * @tparam ExprPtr type of the RR's expr_ member (shared_ptr) + * @tparam MakeChild callable (const F& Da, const F& Db) -> child vertex, + * wrapping the operator-specific ChildFactory::make_child + * @param a,b bra/ket basis functions of the target + * @param q Pauli quaternion index (0..3) + * @param is_simple value of the RR's is_simple() + * @param expr the RR's expr_ (assigned when is_simple) + * @param nflops the RR's nflops_ (incremented by the fold's adds) + * @param make_child builds the inner child integral (Da | O | Db) + */ +template +void sigma_p_1body_fold(const F &a, const F &b, unsigned int q, bool is_simple, + ExprPtr &expr, unsigned int &nflops, + MakeChild &&make_child) { + using namespace libint2::algebra; + using libint2::algebra::operator*; + + constexpr int x = 0; + constexpr int y = 1; + constexpr int z = 2; + + F Dx_a{a}; + Dx_a.deriv().inc(x); + F Dx_b{b}; + Dx_b.deriv().inc(x); + F Dy_a{a}; + Dy_a.deriv().inc(y); + F Dy_b{b}; + Dy_b.deriv().inc(y); + F Dz_a{a}; + Dz_a.deriv().inc(z); + F Dz_b{b}; + Dz_b.deriv().inc(z); + + switch (q) { + case 0: { + auto Dx_a_O_Dx_b = make_child(Dx_a, Dx_b); + auto Dy_a_O_Dy_b = make_child(Dy_a, Dy_b); + auto Dz_a_O_Dz_b = make_child(Dz_a, Dz_b); + if (is_simple) { + expr = Dx_a_O_Dx_b + Dy_a_O_Dy_b + Dz_a_O_Dz_b; + nflops += 2; + } + } break; + case 1: { + auto Dy_a_O_Dz_b = make_child(Dy_a, Dz_b); + auto Dz_a_O_Dy_b = make_child(Dz_a, Dy_b); + if (is_simple) { + expr = Dy_a_O_Dz_b - Dz_a_O_Dy_b; + nflops += 1; + } + } break; + case 2: { + auto Dz_a_O_Dx_b = make_child(Dz_a, Dx_b); + auto Dx_a_O_Dz_b = make_child(Dx_a, Dz_b); + if (is_simple) { + expr = Dz_a_O_Dx_b - Dx_a_O_Dz_b; + nflops += 1; + } + } break; + case 3: { + auto Dx_a_O_Dy_b = make_child(Dx_a, Dy_b); + auto Dy_a_O_Dx_b = make_child(Dy_a, Dx_b); + if (is_simple) { + expr = Dx_a_O_Dy_b - Dy_a_O_Dx_b; + nflops += 1; + } + } break; + default: + throw std::runtime_error("sigma_p_1body_fold: invalid quaternionic index"); + } +} + +} // namespace libint2 + +#endif // LIBINT_RKB_FOLD_1BODY_H From 44de4dd4099536f2aef217cc8be4f9260509b9d6 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 14:38:31 -0400 Subject: [PATCH 38/64] Add LIBINT2_RKB_ERI3_PURE_SH option, mirroring ERI3_PURE_SH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3-center RKB ERIs had no pure-solid-harmonic option for the density fitting (auxiliary) center, unlike plain ERI3. Add LIBINT2_RKB_ERI3_PURE_SH following the ERI3_PURE_SH counterpart (default OFF): - CMakeLists.txt: option_with_print + booleanize01 + LIBINT2_->LIBINT_ propagation. - config.h.cmake.in: #cmakedefine LIBINT_RKB_ERI3_PURE_SH. - int_am.cmake: build the RKB pure-SH (_pureamlist) fitting-center components and select them via the same NOT-PURE_SH fallback used by ERI3. - build_libint.cc: apply pure_sh() to the fitting center for the RKB (non-TwoPRep) operators in build_TwoPRep_1b_2k (target + label construction), and propagate the RKB_ERI3_PURE_SH macro to the generated library — structurally identical to the existing ERI3 path. (There is no 2-center RKB ERI, so no RKB_ERI2 counterpart is needed.) All codegen additions are gated on LIBINT_RKB_ERI3_PURE_SH (default OFF), so the default build is unchanged. --- CMakeLists.txt | 5 ++++- cmake/modules/int_am.cmake | 7 +++++++ include/libint2/config.h.cmake.in | 3 +++ src/bin/libint/build_libint.cc | 21 +++++++++++++++++++-- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d69d695f..eb9d76a83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,6 +239,8 @@ option_with_default(LIBINT2_RKB_ERI3_MAX_AM option_with_default(LIBINT2_RKB_ERI3_OPT_AM "Optimize relativistic restricted kinetic balance (RKB) 3-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) +option_with_print(LIBINT2_RKB_ERI3_PURE_SH + "Assume the 'unpaired' (density fitting/auxiliary) center of 3-center RKB ERIs will be transformed to pure solid harmonics" OFF) option_with_default(LIBINT2_ERI3_MAX_AM @@ -336,6 +338,7 @@ if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES) endif () booleanize01(LIBINT2_ERI3_PURE_SH) +booleanize01(LIBINT2_RKB_ERI3_PURE_SH) booleanize01(LIBINT2_ERI2_PURE_SH) booleanize01(LIBINT2_USE_COMPOSITE_EVALUATORS) booleanize01(LIBINT2_SINGLE_EVALTYPE) @@ -406,7 +409,7 @@ configure_file( # library build time. set(_EXPORT_MODE 0) # convert user-facing LIBINT2_ variables to LIBINT_ internal variables -foreach (_var API_PREFIX;ERI3_PURE_SH;ERI2_PURE_SH;DISABLE_ONEBODY_PROPERTY_DERIVS;ENABLE_UNROLLING;ENABLE_GENERIC_CODE;VECTOR_LENGTH;VECTOR_METHOD;ALIGN_SIZE;USER_DEFINED_REAL;USER_DEFINED_REAL_INCLUDES;GENERATE_FMA;ACCUM_INTS;FLOP_COUNT;PROFILE;CONTRACTED_INTS;SINGLE_EVALTYPE;USE_COMPOSITE_EVALUATORS;ERI_STRATEGY;MULTIPOLE_MAX_ORDER) +foreach (_var API_PREFIX;ERI3_PURE_SH;RKB_ERI3_PURE_SH;ERI2_PURE_SH;DISABLE_ONEBODY_PROPERTY_DERIVS;ENABLE_UNROLLING;ENABLE_GENERIC_CODE;VECTOR_LENGTH;VECTOR_METHOD;ALIGN_SIZE;USER_DEFINED_REAL;USER_DEFINED_REAL_INCLUDES;GENERATE_FMA;ACCUM_INTS;FLOP_COUNT;PROFILE;CONTRACTED_INTS;SINGLE_EVALTYPE;USE_COMPOSITE_EVALUATORS;ERI_STRATEGY;MULTIPOLE_MAX_ORDER) if (DEFINED LIBINT2_${_var}) if (DEFINED LIBINT_${_var}) message(FATAL_ERROR "renaming user-facing LIBINT2_${_var} variable but internal variable LIBINT_${_var} already exists") diff --git a/cmake/modules/int_am.cmake b/cmake/modules/int_am.cmake index a29f2c03a..5136a8df4 100644 --- a/cmake/modules/int_am.cmake +++ b/cmake/modules/int_am.cmake @@ -382,6 +382,7 @@ add_feature_info( set(Libint2_ERI_COMPONENTS "") set(_amlist "") set(_eri3_impure_sh "") +set(_rkb_eri3_impure_sh "") set(_eri2_impure_sh "") add_feature_info( "integral class MULTIPOLE derivative 0" @@ -457,6 +458,7 @@ foreach(_cls ONEBODY;ERI;RKB_ERI;RKB_ERI3;ERI3;ERI2;G12;G12DKH) foreach(_lpr RANGE ${LIBINT_HARD_MIN_AM} ${_eri3_candidate0_d${_d}}) # LIBINT_MAX_AM[_LIST], paired if (_lfit GREATER_EQUAL _lpr) list(APPEND _amlist "rkberi_${_am${_lpr}}${_am${_lpr}}${_AM${_lfit}}_d${_d}") + list(APPEND _pureamlist "rkberi_${_am${_lpr}}${_am${_lpr}}${_am${_lfit}}_d${_d}") endif() endforeach() endforeach() @@ -469,11 +471,16 @@ foreach(_cls ONEBODY;ERI;RKB_ERI;RKB_ERI3;ERI3;ERI2;G12;G12DKH) list(APPEND _eri2_impure_sh "${_pureamlist}") elseif (_cls STREQUAL "ERI3") list(APPEND _eri3_impure_sh "${_pureamlist}") + elseif (_cls STREQUAL "RKB_ERI3") + list(APPEND _rkb_eri3_impure_sh "${_pureamlist}") endif() endforeach() if ((_cls STREQUAL "ERI3") AND NOT LIBINT2_ERI3_PURE_SH) list(APPEND Libint2_ERI_COMPONENTS "${_eri3_impure_sh}") message(VERBOSE "setting components ${_eri3_impure_sh}") + elseif ((_cls STREQUAL "RKB_ERI3") AND NOT LIBINT2_RKB_ERI3_PURE_SH) + list(APPEND Libint2_ERI_COMPONENTS "${_rkb_eri3_impure_sh}") + message(VERBOSE "setting components ${_rkb_eri3_impure_sh}") elseif ((_cls STREQUAL "ERI2") AND NOT LIBINT2_ERI2_PURE_SH) list(APPEND Libint2_ERI_COMPONENTS "${_eri2_impure_sh}") message(VERBOSE "setting components ${_eri2_impure_sh}") diff --git a/include/libint2/config.h.cmake.in b/include/libint2/config.h.cmake.in index 3c8a3eca7..881a8a8ad 100644 --- a/include/libint2/config.h.cmake.in +++ b/include/libint2/config.h.cmake.in @@ -177,6 +177,9 @@ /* If 1, assume will transform the "unpaired" center (e.g. a in (a|cd)) to solid harmonics */ #cmakedefine LIBINT_ERI3_PURE_SH @LIBINT_ERI3_PURE_SH@ +/* If 1, assume will transform the "unpaired" (density fitting/auxiliary) center of 3-center RKB ERIs to solid harmonics */ +#cmakedefine LIBINT_RKB_ERI3_PURE_SH @LIBINT_RKB_ERI3_PURE_SH@ + /* Max AM for 2-center ERI (same for all derivatives; if not defined see LIBINT_ERI2_MAX_AM_LIST) */ #cmakedefine LIBINT_ERI2_MAX_AM @LIBINT_ERI2_MAX_AM@ diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 944bc4192..f0de6aa16 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -1135,6 +1135,9 @@ void try_main(int argc, char* argv[]) { #if LIBINT_ERI3_PURE_SH iface->to_params(iface->macro_define("ERI3_PURE_SH", 1)); #endif +#if LIBINT_RKB_ERI3_PURE_SH + iface->to_params(iface->macro_define("RKB_ERI3_PURE_SH", 1)); +#endif #endif #ifdef LIBINT_INCLUDE_ERI2 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI2; ++d) { @@ -1644,13 +1647,21 @@ static void build_TwoPRep_1b_2k( CGShell c(lc); CGShell d(ld); #if LIBINT_ERI3_PURE_SH - // pure-SH on the fitting center is meaningful only for plain ERI3; - // RKB CoulombσpσpOper does not assume the fitting center is pure SH. + // pure-SH on the fitting center of plain ERI3 (gated separately from + // the RKB variant below; see LIBINT_RKB_ERI3_PURE_SH). if constexpr (std::is_same::value) { if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); } #endif +#if LIBINT_RKB_ERI3_PURE_SH + // pure-SH on the (density fitting/auxiliary) center of RKB 3-center + // ERIs, mirroring the plain-ERI3 path for the non-TwoPRep operators. + if constexpr (!std::is_same::value) { + if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); + if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); + } +#endif unsigned int center = 0; for (unsigned int i = 0; i < 4; ++i) { @@ -1695,6 +1706,12 @@ static void build_TwoPRep_1b_2k( if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); } +#endif +#if LIBINT_RKB_ERI3_PURE_SH + if constexpr (!std::is_same::value) { + if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); + if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); + } #endif if constexpr (std::is_same::value) { OperType oper(descrs[0]); From e018d3b0b6731528b4366afcd80e6d415bb7bf24 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 14:44:04 -0400 Subject: [PATCH 39/64] Add TODO markers for deferred RKB codegen generalizations Document two known refactors in-place so they are tracked rather than re-discovered: - gauss.cc CGF/CGShell::label(): the primitive-disambiguation underscore leaks into C identifiers, not just filenames; note the preferred funcname->filename mapping. - build_libint.cc / engine.impl.h / strategy.cc: the per-operator std::is_same / Operator::... branches (component counts, swappability, swap phase/remap, strategy specializations) should move onto operator-descriptor traits and be queried generically. Comments only; no behavior change. --- include/libint2/engine.impl.h | 8 +++++++- src/bin/libint/build_libint.cc | 6 ++++++ src/bin/libint/gauss.cc | 12 ++++++++++-- src/bin/libint/strategy.cc | 4 ++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 25a51df4d..71fc572d3 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -2259,7 +2259,13 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( tgt_ptr + tgt_col_idx, nr1_tgt, nr2_tgt, Eigen::Stride( nr2_tgt * ncol_tgt, ncol_tgt)); - // Coupled swap sign correction for multi-component operators + // Coupled swap sign correction for multi-component operators. + // TODO: the per-component swap phase and the component remap + // below are hardcoded per operator (op_coulomb_op, + // opop_coulomb_opop, ...). These belong with the operator + // definition; expose them as traits (e.g. swap_phase(s), + // swap_remap(s)) so new multi-component operators need no new + // branch here. Shell::real_t oper_cart_component_phase = 1.0; if (swap_tket && oper_ == Operator::opop_coulomb_opop) { const bool bra_is_spin = (s / 4) > 0; diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index f0de6aa16..b4b625c50 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -1285,6 +1285,12 @@ static void build_TwoPRep_2b_2k( // orderings (within-side swap is NOT a symmetry — σ·p attaches to one // specific function per side, and IBP cannot recover the sign across the // 1/r12 coupling). + // TODO: the per-operator swappability/component-count rules are spread across + // many std::is_same branches here (and the descriptor loops and + // strategy specializations). These are properties of the operator descriptor; + // expose them as descriptor traits (e.g. ncomponents(), p1p2_swappable(), + // bra_ket_coswappable()) and query generically so a new operator needs no edit + // here. bool p1_p2_swappable = !std::is_same::value; bool bra_ket_coswappable = std::is_same::value; diff --git a/src/bin/libint/gauss.cc b/src/bin/libint/gauss.cc index aa60b1de7..f561d8ee5 100644 --- a/src/bin/libint/gauss.cc +++ b/src/bin/libint/gauss.cc @@ -119,7 +119,11 @@ std::string CGF::label() const { std::ostringstream oss; // Some OSs can have case-insensitive filesystem e.g., MacOS. So here we add - // additional identifier for primitive function labels + // additional identifier for primitive function labels. + // TODO: this underscore leaks into the C identifiers derived from label() + // (struct members, macros, symbols), not just the generated filenames it is + // meant to disambiguate. Prefer a case-injective funcname->filename mapping so + // label() stays identifier-clean and external consumers don't depend on it. if (!this->contracted()) { am_string += "_"; } @@ -233,7 +237,11 @@ std::string CGShell::label() const { std::string am_symbol = am_to_symbol(qn_[0], contracted()); // Some OSs can have case-insensitive filesystem e.g., MacOS. So here we add - // additional identifier for primitive shell labels + // additional identifier for primitive shell labels. + // TODO: this underscore leaks into the C identifiers derived from label() + // (struct members, macros, symbols), not just the generated filenames it is + // meant to disambiguate. Prefer a case-injective funcname->filename mapping so + // label() stays identifier-clean and external consumers don't depend on it. if (!this->contracted()) { am_symbol += "_"; } diff --git a/src/bin/libint/strategy.cc b/src/bin/libint/strategy.cc index d6ae8ed16..d09b4811c 100644 --- a/src/bin/libint/strategy.cc +++ b/src/bin/libint/strategy.cc @@ -119,6 +119,10 @@ struct MasterStrategy { }; #endif +// TODO: each RKB operator adds a fixed set of MasterStrategy specializations +// that only name its composite recurrence (CR_11_*). A generic specialization +// keyed on the operator's composite-RR trait would remove this per-operator +// boilerplate and scale to new RKB operators without edits here. template <> struct MasterStrategy { typedef boost::mpl::list value; From c385ce49f61fcc720047c4e4461494f2fb7c6c72 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 14:46:50 -0400 Subject: [PATCH 40/64] comments cleanup: rewritten rkb comments to make them context-free --- export/tests/unit/test-2body.cc | 2 +- include/libint2/engine.h | 4 ++-- src/bin/libint/build_libint.cc | 4 ++-- "src/bin/libint/comp_11_Coulomb\317\203p_11.h" | 2 +- src/bin/libint/oper.h | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 689dacfb3..514f7842e 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -926,7 +926,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { #if LIBINT2_TASK_EXISTS_3coulomb_op SECTION("Coulombσp 3-center xs_xx") { - // 3-center single-σ·p ("DF-Gaunt" B-factor) integral + // 3-center single-σ·p integral // (P | 1/r12 | μ, σ·p ν), // with the fitting function P on the bra (BraKet::xs_xx) and a single σ·p // acting on the 2nd ket AO function ν only. The 3 Cartesian components diff --git a/include/libint2/engine.h b/include/libint2/engine.h index e7afaf4b6..c0f268347 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -211,7 +211,7 @@ enum class Operator { /// centers of ket1 and ket2, respectively coulomb_opop, /// (2-body) \f$ r_{12}^{-1} (σ.p_{k2}) \f$ where k2 is the center of ket2; - /// the 3-center single-σ·p "DF-Gaunt" B-factor (P | μ, σ·p ν). σ·p acts on + /// the 3-center single-σ·p integral (P | μ, σ·p ν). σ·p acts on /// the 2nd ket AO function only. Produces 3 Cartesian components (x=0, y=1, /// z=2) — the SO(3) vector irrep; no σ·σ folding (cf. coulomb_opop). The /// fitting function sits on the bra (BraKet::xs_xx); 3-center only. @@ -617,7 +617,7 @@ inline constexpr int rank(BraKet braket) { /// @return the default braket for @c oper /// @throw std::logic_error if invalid @c oper given inline constexpr BraKet default_braket(Operator oper) { - // coulomb_op (3-center single-σ·p DF-Gaunt) has no 4-center kernel; its only + // coulomb_op (3-center single-σ·p) has no 4-center kernel; its only // valid geometry is the 3-center xs_xx braket. Default to that rather than the // rank-based xx_xx, which has no generated kernel and would silently dispatch // to the plain-Coulomb `default` task (wrong integrals, no error in release). diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index b4b625c50..1f4e5a564 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -653,8 +653,8 @@ void try_main(int argc, char* argv[]) { // dummy-s fitting shell; it is intentionally omitted). // - 3coulomb_opop (CoulombσpσpOper): two σ·p on the AO ket pair (4 comps) // - 3coulomb_op (CoulombσpOper): single σ·p on the 2nd AO ket function - // ν (3 Cartesian comps, DF-Gaunt B). No - // 4-center sibling — 3-center only. + // ν (3 Cartesian comps). No 4-center + // sibling — 3-center only. #define BOOST_PP_RKB_ERI3_TASK_TUPLE (3coulomb_opop, 3coulomb_op) #define BOOST_PP_RKB_ERI3_TASK_OPER_TUPLE (CoulombσpσpOper, CoulombσpOper) #define BOOST_PP_RKB_ERI3_TASK_LIST \ diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p_11.h" index e9cc50892..79edfcd4c 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p_11.h" @@ -33,7 +33,7 @@ namespace libint2 { /** - * this computes the 3-center single-σ·p ("DF-Gaunt") integral + * this computes the 3-center single-σ·p integral * \f$ \frac{1}{r_{12}} \, \sigma \cdot \hat{p}_2 \f$ acting on the 2nd ket * function ν, by rewriting it as the bare Cartesian gradient of ν, i.e. one * first-derivative integral over \f$ \frac{1}{r_{12}} \f$ per Cartesian diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index 30816c65f..609f61ea6 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -476,14 +476,14 @@ struct Coulombσpσp_Descr : public Contractable { }; typedef GenOper CoulombσpσpOper; -/** Coulombσp is the 3-center single-σ·p ("DF-Gaunt") operator: +/** Coulombσp is the 3-center single-σ·p operator: * (P | 1/r_{12} | μ, σ·p ν), with the fitting function P a spectator and a * single σ·p acting on the 2nd ket AO function ν. Unlike Coulombσpσp (two σ·p * on the ket pair, folded via the Dirac identity into 4 quaternion * components), a single σ·p produces no σ·σ folding: the result is the bare * Cartesian gradient of ν, i.e. the SO(3) vector irrep with 3 components * (x=0, y=1, z=2). Each component is exactly one first-derivative ERI child; - * the imaginary unit and Pauli matrix of σ·p are applied by the caller (MPQC). + * the imaginary unit and Pauli matrix of σ·p are applied by the caller. */ struct Coulombσp_Descr : public Contractable { typedef MultiplicativeSymm2Body_Props Properties; From 6db2b57038f5574a1f092c2840d578e0cf98c89a Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 15:20:20 -0400 Subject: [PATCH 41/64] =?UTF-8?q?Tighten=201-body=20=CF=83=C2=B7p=20fold?= =?UTF-8?q?=20max=5Fnchildren=20to=20actual=20bound=20(3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sigma_p_1body_fold trace component folds 3 first-derivative children and the antisym components fold 2, so max_nchildren is 3, not 100. Set the real bound on both 1-body RKB folds and drop the stale "TODO figure out", matching the child-count hardening already done on the 4-center σ·p folds. --- "src/bin/libint/comp_1_\317\203pV\317\203p_1.h" | 3 ++- "src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git "a/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" "b/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" index 4d64da30b..238918f36 100644 --- "a/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" +++ "b/src/bin/libint/comp_1_\317\203pV\317\203p_1.h" @@ -46,7 +46,8 @@ class CR_1_σpVσp_1 ParentType; friend class GenericRecurrenceRelation; - static const unsigned int max_nchildren = 100; // TODO figure out + // trace component folds 3 derivative children; the antisym components fold 2 + static const unsigned int max_nchildren = 3; using ParentType::Instance; diff --git "a/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" "b/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" index f46bd9db4..c6aa9f80a 100644 --- "a/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" +++ "b/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" @@ -50,7 +50,8 @@ class CR_1_σpeμσp_1 ParentType; friend class GenericRecurrenceRelation; - static const unsigned int max_nchildren = 100; + // trace component folds 3 derivative children; the antisym components fold 2 + static const unsigned int max_nchildren = 3; using ParentType::Instance; From 8223aad79aca4593aedd2b8b732e3aab5830533c Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 15:42:07 -0400 Subject: [PATCH 42/64] Remove dead (SS|SS) der_idx constants from RKB Coulomb test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The σpσpCoulombσpσp reference in the "Coulombσpσp and σpσpCoulombσpσp" section builds its 16 components from the didx(a,b,c,d)/D(a,b,c,d) lambdas, which superseded the ~40 hand-written four-derivative der_idx constants (xxxx..xyzz). Those constants were defined but never referenced. Remove them (49 lines). The nine two-derivative constants (d_xx..d_yx) are kept — the (LL|SS) coulomb_opop reference still uses them via eri_drrrr. No behavior change; reference math is unchanged. --- export/tests/unit/test-2body.cc | 50 --------------------------------- 1 file changed, 50 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 514f7842e..d58ee379f 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -367,56 +367,6 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { der_idx d_xy = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0}; der_idx d_yx = {0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0}; - // deriv indices for (SS|SS) - // 0th component - der_idx xxxx = {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; - der_idx yyxx = {0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0}; - der_idx zzxx = {0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0}; - der_idx yxyx = {0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0}; - der_idx xyyx = {1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0}; - der_idx yxxy = {0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0}; - der_idx xyxy = {1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0}; - der_idx xxyy = {1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0}; - der_idx yyyy = {0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}; - der_idx zzyy = {0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0}; - der_idx xxzz = {1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1}; - der_idx yyzz = {0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1}; - der_idx zzzz = {0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1}; - - // x-component - der_idx zxzx = {0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0}; - der_idx xzzx = {1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0}; - der_idx zyzy = {0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0}; - der_idx yzzy = {0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0}; - der_idx zxxz = {0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1}; - der_idx xzxz = {1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1}; - der_idx zyyz = {0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1}; - der_idx yzyz = {0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1}; - - // y-component - der_idx zyzx = {0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0}; - der_idx yzzx = {0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0}; - der_idx zxzy = {0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0}; - der_idx xzzy = {1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0}; - der_idx zyxz = {0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1}; - der_idx yzxz = {0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1}; - der_idx zxyz = {0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1}; - der_idx xzyz = {1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1}; - - // z-component - der_idx yxxx = {0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; - der_idx xyxx = {1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0}; - der_idx xxyx = {1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0}; - der_idx yyyx = {0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0}; - der_idx zzyx = {0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0}; - der_idx xxxy = {1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0}; - der_idx yyxy = {0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0}; - der_idx zzxy = {0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0}; - der_idx yxyy = {0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0}; - der_idx xyyy = {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}; - der_idx yxzz = {0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1}; - der_idx xyzz = {1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1}; - SECTION("Coulombσpσp and σpσpCoulombσpσp") { Engine engine_llss, engine_ssss; try { From 9cd5192d25fbafc1cdf8307fd4f26e0a06b7e2c3 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Tue, 9 Jun 2026 16:25:54 -0400 Subject: [PATCH 43/64] =?UTF-8?q?Make=20=CF=83pe=CE=BC=CF=83p=20unit=20tes?= =?UTF-8?q?t=20concrete=20with=20reference-value=20comparison?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "σpeμσp correctness" test previously only checked Hermiticity (bra↔ket swap symmetry) and non-triviality — it never compared against known values. Redesign it to mirror the σpVσp ("W correctness") test: compute the 12 components over a (d|d) shell pair and compare each against a frozen shellset_ref_standard array via LIBINT2_TEST_ONEBODY. The reference values are the operator output, independently validated against the Pauli-quaternion fold of the dipole's geometric derivatives ∂A_a ∂B_b (μ|r_k|ν) — obtained from emultipole1 deriv-2 and, as a cross-check, finite differences of the deriv-0 dipole — agreeing to ~1e-6. So they are a verified reference, not a self-referential snapshot. Components 1, 6, 11 (Pauli direction aligned with the dipole direction) vanish by symmetry. Add .margin(1e-10) to LIBINT2_TEST_ONEBODY so those structurally-zero components pass; 1e-10 is far below the magnitudes the other one-body value tests (W/erf/nuclear) check, so their pass/fail is unchanged. Verified: full unit suite green at both LIBINT2_ENABLE_ONEBODY=0 and =2. --- export/tests/unit/test-1body.cc | 174 +++++++++++++++++++++++--------- 1 file changed, 124 insertions(+), 50 deletions(-) diff --git a/export/tests/unit/test-1body.cc b/export/tests/unit/test-1body.cc index a58567f11..0bcb6aa64 100644 --- a/export/tests/unit/test-1body.cc +++ b/export/tests/unit/test-1body.cc @@ -31,13 +31,13 @@ if (libint2::solid_harmonics_ordering() == \ libint2::SHGShellOrdering_Standard) { \ REQUIRE(engine.results()[op_idx][i0i1_s] / scale == \ - Approx(shellset_ref_standard[i0i1_s])); \ + Approx(shellset_ref_standard[i0i1_s]).margin(1e-10)); \ } else { \ const auto i0_g = libint2::INT_SOLIDHARMINDEX_GAUSSIAN(l0, m0); \ const auto i1_g = libint2::INT_SOLIDHARMINDEX_GAUSSIAN(l1, m1); \ const auto i0i1_g = i0_g * n1 + i1_g; \ REQUIRE(engine.results()[op_idx][i0i1_g] / scale == \ - Approx(shellset_ref_standard[i0i1_s])); \ + Approx(shellset_ref_standard[i0i1_s]).margin(1e-10)); \ } \ END_FOR_SOLIDHARM \ END_FOR_SOLIDHARM @@ -247,66 +247,140 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "W correctness", TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpeμσp correctness", "[engine][1-body]") { #if defined(LIBINT2_SUPPORT_ONEBODY) - if (LIBINT_SHGSHELL_ORDERING != LIBINT_SHGSHELL_ORDERING_STANDARD) return; - - // Two contracted Gaussian shells at distinct centers. We exercise - // `(σ·p) r (σ·p)` over the (s|d) and (d|s) shell pairs and validate - // Hermiticity: trace components (q=0) are symmetric under bra↔ket swap, - // antisym components (q=1,2,3) are antisymmetric. + // (1-body) σ·p · r · σ·p, the σ·p-on-both-sides analog of the dipole moment: + // 12 components = 3 dipole directions k (x,y,z) × 4 Pauli quaternion pieces q + // (0=trace, 1/2/3=antisym), component index = 4*k + q. + // + // Reference values are the operator output over the (d|d) shell pair below, + // generated once and independently validated against the Pauli-quaternion + // fold of the dipole's geometric derivatives ∂A_a ∂B_b (μ|r_k|ν) (obtained + // from emultipole1 deriv-2 and finite differences; agreement ~1e-6). + // Components 1, 6, 11 (Pauli direction aligned with the dipole direction) + // vanish by symmetry. Layout mirrors the opVop (σpVσp) test above. std::vector obs{ - Shell{{1.0, 3.0}, {{0, true, {1.0, 0.3}}}, {{0.0, 0.0, 0.0}}}, + Shell{{1.0, 3.0}, {{2, true, {1.0, 0.3}}}, {{0.0, 0.0, 0.0}}}, Shell{{2.0, 5.0}, {{2, true, {1.0, 0.2}}}, {{1.0, 1.0, 1.0}}}}; const auto lmax = std::min(2, LIBINT2_MAX_AM_opemuop); if (lmax < 2) return; + constexpr int l0 = 2; + constexpr int l1 = 2; + constexpr int n1 = 2 * l1 + 1; + auto engine = Engine(Operator::opemuop, 2, lmax); engine.set_params(std::array{{0.0, 0.0, 0.0}}); - - // (s|σpeμσp|d) and (d|σpeμσp|s) engine.compute(obs[0], obs[1]); - std::array, 12> ab; - for (int c = 0; c < 12; ++c) { - const auto* buf = engine.results()[c]; - REQUIRE(buf != nullptr); - ab[c].assign(buf, buf + (1 * 5)); // n_s × n_d_pure = 1 × 5 - } - engine.compute(obs[1], obs[0]); - std::array, 12> ba; - for (int c = 0; c < 12; ++c) { - const auto* buf = engine.results()[c]; - REQUIRE(buf != nullptr); - ba[c].assign(buf, buf + (5 * 1)); // n_d_pure × n_s + // all ref are laid out in standard solids order + { + std::vector shellset_ref_standard = { + 0.09223217251311097, 0.7337253845349039, 0.8940034778856171, 0.22249836470532133, -0.252327005953861, + 0.2544347044464152, 0.25443470444641514, 0.0, 0.2544347044464152, 0.0, + 0.4283068075013277, -0.4656966703842894, -0.8368997546246189, -0.1272426104648221, -0.10997391819356982, + 0.22249836470532133, 0.7337253845349038, -0.22848014172589765, 0.09223217251311103, -0.9003932258975146, + -0.10035593979812935, 0.8066102940212458, -0.10997391819356983, -0.4211025458090261, -0.7099128120788283}; + LIBINT2_TEST_ONEBODY(1.0, 0); } - - // Hermiticity check: σpeμσp is Hermitian, but the Pauli identity routes the - // imaginary i factor on the antisym pieces into a real-stored sign flip. - // q=0 trace: matrix is symmetric ⇒ ab[0+k][i,j] == ba[0+k][j,i] - // q=1..3 : matrix is antisym ⇒ ab[q+k][i,j] == -ba[q+k][j,i] - // (Indices: ab is laid out (i=0..n_s-1, j=0..n_d-1); ba is (j, i).) - const double tol = 1.0e-12; - for (int k = 0; k < 3; ++k) { - for (int q = 0; q < 4; ++q) { - const int comp = 4 * k + q; - const double expected_sign = (q == 0) ? +1.0 : -1.0; - for (int i = 0; i < 1; ++i) { - for (int j = 0; j < 5; ++j) { - const double v_ab = ab[comp][i * 5 + j]; - const double v_ba = ba[comp][j * 1 + i]; - REQUIRE(std::isfinite(v_ab)); - REQUIRE(std::abs(v_ab - expected_sign * v_ba) < tol); - } - } - } + { + std::vector shellset_ref_standard = { + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0}; + LIBINT2_TEST_ONEBODY(1.0, 1); + } + { + std::vector shellset_ref_standard = { + 0.04479855132698852, 0.04479855132698852, 0.0, 0.04479855132698859, 0.0, + 0.04479855132698847, -0.07520852256716505, -0.25038279554682064, -0.268870098007082, 0.06000353694707675, + 0.0, -0.25038279554682064, 0.09138051976167452, -0.25038279554682075, 0.0, + 0.04479855132698842, -0.2688700980070819, -0.2503827955468205, -0.07520852256716526, -0.06000353694707675, + 0.0, 0.06000353694707682, 0.0, -0.06000353694707674, -0.24683963008765036}; + LIBINT2_TEST_ONEBODY(1.0, 2); + } + { + std::vector shellset_ref_standard = { + 0.07520852256716518, 0.26887009800708184, -0.17715598508649683, -0.04479855132698844, -0.18683609314057345, + 0.268870098007082, 0.07520852256716505, -0.07322681046032367, -0.044798551326988484, -0.24683963008765025, + -0.17715598508649694, -0.07322681046032367, 0.16228459262531905, 0.0, -0.14645362092064743, + -0.04479855132698858, -0.04479855132698848, 0.0, -0.044798551326988525, 0.0, + -0.18683609314057373, -0.24683963008765045, -0.14645362092064743, 0.0, -0.006825482299343409}; + LIBINT2_TEST_ONEBODY(1.0, 3); + } + { + std::vector shellset_ref_standard = { + 0.09223217251311097, 0.2224983647053212, 0.8940034778856171, 0.7337253845349039, 0.25232700595386154, + 0.2224983647053212, 0.09223217251311096, -0.22848014172589742, 0.7337253845349039, 0.9003932258975148, + 0.42830680750132777, -0.127242610464822, -0.836899754624619, -0.46569667038428947, 0.10997391819356969, + 0.2544347044464152, 0.25443470444641514, 0.0, 0.25443470444641514, 0.0, + 0.10035593979812953, 0.42110254580902606, 0.10997391819356978, -0.8066102940212458, -0.7099128120788284}; + LIBINT2_TEST_ONEBODY(1.0, 4); + } + { + std::vector shellset_ref_standard = { + -0.04479855132698845, -0.04479855132698845, 0.0, -0.04479855132698854, 0.0, + -0.04479855132698835, 0.07520852256716522, 0.2503827955468206, 0.26887009800708184, -0.0600035369470768, + 0.0, 0.2503827955468207, -0.09138051976167444, 0.2503827955468207, 0.0, + -0.044798551326988435, 0.268870098007082, 0.2503827955468206, 0.07520852256716506, 0.060003536947076705, + 0.0, -0.06000353694707683, 0.0, 0.06000353694707682, 0.24683963008765056}; + LIBINT2_TEST_ONEBODY(1.0, 5); + } + { + std::vector shellset_ref_standard = { + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0}; + LIBINT2_TEST_ONEBODY(1.0, 6); + } + { + std::vector shellset_ref_standard = { + -0.07520852256716509, 0.04479855132698833, 0.177155985086497, -0.26887009800708184, -0.18683609314057353, + 0.04479855132698844, 0.04479855132698844, 0.0, 0.044798551326988505, 0.0, + 0.17715598508649694, 0.0, -0.16228459262531914, 0.07322681046032364, -0.14645362092064745, + -0.26887009800708195, 0.044798551326988456, 0.07322681046032371, -0.07520852256716508, -0.2468396300876503, + -0.1868360931405737, 0.0, -0.14645362092064748, -0.24683963008765053, 0.006825482299343263}; + LIBINT2_TEST_ONEBODY(1.0, 7); + } + { + std::vector shellset_ref_standard = { + 0.2544347044464152, 0.25443470444641514, 0.0, 0.25443470444641514, 0.0, + 0.7337253845349039, 0.09223217251311093, -0.6655233361597198, 0.2224983647053212, 0.6480662199436535, + 0.9313933407685788, -0.3010641970365056, -0.6464193408059329, -0.30106419703650567, 0.0, + 0.7337253845349039, 0.2224983647053212, -0.6655233361597195, 0.09223217251311097, -0.6480662199436531, + 0.0, 0.3207466060108965, 0.0, -0.3207466060108965, -0.9003932258975146}; + LIBINT2_TEST_ONEBODY(1.0, 8); + } + { + std::vector shellset_ref_standard = { + -0.07520852256716505, -0.268870098007082, 0.17715598508649688, 0.04479855132698845, 0.18683609314057348, + -0.2688700980070818, -0.07520852256716509, 0.07322681046032364, 0.044798551326988394, 0.2468396300876503, + 0.17715598508649705, 0.07322681046032363, -0.16228459262531927, 0.0, 0.14645362092064737, + 0.044798551326988525, 0.04479855132698848, 0.0, 0.044798551326988435, 0.0, + 0.18683609314057356, 0.2468396300876504, 0.1464536209206475, 0.0, 0.006825482299343293}; + LIBINT2_TEST_ONEBODY(1.0, 9); + } + { + std::vector shellset_ref_standard = { + 0.07520852256716505, -0.04479855132698846, -0.1771559850864969, 0.26887009800708195, 0.1868360931405736, + -0.044798551326988505, -0.04479855132698844, 0.0, -0.04479855132698858, 0.0, + -0.17715598508649705, 0.0, 0.16228459262531922, -0.07322681046032384, 0.14645362092064737, + 0.26887009800708184, -0.04479855132698835, -0.07322681046032367, 0.07520852256716509, 0.2468396300876504, + 0.18683609314057362, 0.0, 0.14645362092064745, 0.2468396300876504, -0.006825482299343255}; + LIBINT2_TEST_ONEBODY(1.0, 10); + } + { + std::vector shellset_ref_standard = { + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0}; + LIBINT2_TEST_ONEBODY(1.0, 11); } - - // Sanity: not every component is identically zero (would mask codegen bugs). - bool any_nonzero = false; - for (int c = 0; c < 12; ++c) - for (double v : ab[c]) - if (std::abs(v) > 1.0e-10) any_nonzero = true; - REQUIRE(any_nonzero); #endif // LIBINT2_SUPPORT_ONEBODY } From 452b8069009fed81a04b651d3db1312079e404ef Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Fri, 23 Oct 2026 00:07:21 -0400 Subject: [PATCH 44/64] address adversarial review of RKB integrals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Engine: if the library has no kernels for the requested operator/braket/derivative order, compute*() now throws Engine::missing_kernel instead of silently running the placeholder `default` (plain Coulomb) kernel; replaces the per-braket asserts, which also broke RKB-only builds - RKB geometric derivatives are not implemented: rejected by CMake, build_libint and Engine::initialize - fix 3-center RKB ket stride (segfault when LIBINT2_RKB_ERI3_MAX_AM < LIBINT2_MAX_AM) - revert the primitive-label underscore rename (C identifiers, libint2.h and Fortran interface are unchanged w.r.t. master); instead disambiguate generated filenames that differ only in case via CodeContext::label_to_filename - RKB 2-body operators moved to the end of Operator; RKB ERIs are OFF by default; CHANGES documents the Operator renumbering - σ·p fold kernels loop over low and vector dimensions too - restore the ORCA shell ordering guard in compute2 - cleanups: drop no-op max_hrr_hsrank calls and dead CR_DerivGauss TwoPRep typedefs, fix header guard, non-symmetric props for Coulombσp(σp), undo whitespace-only CMake churn - tests: RKB tests cover contracted and d shells, use REQUIRE and compile-time task guards; σpeμσp validated against finite differences of emultipole1; 1-body margin is opt-in per test --- .github/workflows/cmake.yml | 1 + CHANGES | 9 + CMakeLists.txt | 163 ++++---- INSTALL.md | 4 +- export/fortran/libint_f.F90 | 84 ++--- export/fortran/test-eri.cc | 84 ++--- export/tests/unit/test-1body.cc | 355 +++++++++++++----- export/tests/unit/test-2body.cc | 219 ++++++----- include/libint2.h | 17 +- include/libint2/engine.h | 84 +++-- include/libint2/engine.impl.h | 206 +++++----- src/bin/libint/build_libint.cc | 63 ++-- src/bin/libint/buildtest.cc | 4 +- src/bin/libint/buildtest.h | 4 +- .../comp_1_\317\203pe\316\274\317\203p_1.h" | 17 +- src/bin/libint/comp_deriv_gauss_v2.h | 4 - src/bin/libint/context.cc | 20 + src/bin/libint/context.h | 7 + src/bin/libint/dg.cc | 2 +- src/bin/libint/gauss.cc | 29 +- src/bin/libint/master_rrs_list.h | 29 +- src/bin/libint/oper.h | 8 +- src/bin/libint/rkb_fold_codegen.h | 28 +- 23 files changed, 802 insertions(+), 639 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 551d6e625..1e026781a 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -63,6 +63,7 @@ jobs: -D LIBINT2_ERI3_MAX_AM=3;2 -D LIBINT2_ENABLE_ERI=1 -D LIBINT2_ENABLE_ERI3=1 + -D LIBINT2_ENABLE_RKB_ERI=0 -D LIBINT2_ENABLE_RKB_ERI3=0 -D LIBINT2_RKB_ERI3_MAX_AM=2 -D LIBINT2_ENABLE_ONEBODY=1 diff --git a/CHANGES b/CHANGES index c69adbcc8..626479656 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,15 @@ Following is a brief summary of changes made in each release of Libint. +- unreleased + - PR #399: relativistic restricted kinetic balance (RKB) integrals: 2-body Operator::coulomb_opop, + Operator::coulomb_op, Operator::opop_coulomb_opop, Operator::op_coulomb_op (enabled by + LIBINT2_ENABLE_RKB_ERI and LIBINT2_ENABLE_RKB_ERI3, off by default; no geometric derivatives yet) + and 1-body Operator::opemuop. N.B. Operator::opemuop is inserted after the other 1-body + operators, hence the numeric values of all 2-body Operator enumerators increase by 1. + Engine::compute() now throws Engine::missing_kernel if the library was not configured to + compute the requested operator/braket/derivative order (previously the result was undefined). + - 2025-2-2: 2.13.1 - PR #397: cmake boost check supports in-tree (non-IMPORTED) targets, including modularized Boost - PR #s 395, 398: support for Eigen 5.0.0 (HT @EricaCMitchell) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb9d76a83..5fcb92c9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,71 +109,77 @@ option_with_default(CMAKE_BUILD_TYPE "Build type (Release or Debug)" Release) ### compiler-only option_with_print(LIBINT2_BUILD_LIBRARY_AS_SUBPROJECT - "[EXPERT] Build generated library as a subproject: if FALSE will configure and build separately" OFF) + "[EXPERT] Build generated library as a subproject: if FALSE will configure and build separately" OFF) ### library-only option_with_print(LIBINT2_REQUIRE_CXX_API - "C++11 Libint API: define library targets + test (requires Eigen3, Boost is optional but strongly recommended)" ON) + "C++11 Libint API: define library targets + test (requires Eigen3, Boost is optional but strongly recommended)" ON) option_with_print(LIBINT2_REQUIRE_CXX_API_COMPILED - "Build C++11 Compiled (not just header-only) targets (requires Eigen3, Boost strongly recommended)" ON) + "Build C++11 Compiled (not just header-only) targets (requires Eigen3, Boost strongly recommended)" ON) option_with_print(LIBINT2_ENABLE_FORTRAN - "Build Fortran03+ Libint interface (requires Fortran)" OFF) + "Build Fortran03+ Libint interface (requires Fortran)" OFF) option_with_print(LIBINT2_ENABLE_PYTHON - "Build Python bindings (requires Python and Pybind11 and Eigen3)" OFF) + "Build Python bindings (requires Python and Pybind11 and Eigen3)" OFF) option_with_print(LIBINT2_PREFIX_PYTHON_INSTALL - "For LIBINT2_ENABLE_PYTHON=ON, whether to install the Python module in the Linux manner to CMAKE_INSTALL_PREFIX or to not install it. See target libint2-python-wheel for alternate installation in the Python manner to Python_EXECUTABLE's site-packages." OFF) + "For LIBINT2_ENABLE_PYTHON=ON, whether to install the Python module in the Linux manner to CMAKE_INSTALL_PREFIX or to not install it. See target libint2-python-wheel for alternate installation in the Python manner to Python_EXECUTABLE's site-packages." OFF) option_with_print(BUILD_SHARED_LIBS - "Build Libint library as shared, not static" OFF) + "Build Libint library as shared, not static" OFF) option_with_print(LIBINT2_BUILD_SHARED_AND_STATIC_LIBS - "Build both shared and static Libint libraries in one shot. Uses -fPIC." OFF) + "Build both shared and static Libint libraries in one shot. Uses -fPIC." OFF) option_with_print(LIBINT2_ENABLE_MPFR - "Use GNU MPFR library for high-precision testing (EXPERTS ONLY). Consumed at library build-time." OFF) + "Use GNU MPFR library for high-precision testing (EXPERTS ONLY). Consumed at library build-time." OFF) # <<< Which Integrals Classes, Which Derivative Levels >>> option_with_default(LIBINT2_ENABLE_ONEBODY - "Compile with support for up to N-th derivatives of 1-body integrals (-1 for OFF)" 0) + "Compile with support for up to N-th derivatives of 1-body integrals (-1 for OFF)" 0) option_with_default(LIBINT2_ENABLE_ERI - "Compile with support for up to N-th derivatives of 4-center electron repulsion integrals (-1 for OFF)" 0) + "Compile with support for up to N-th derivatives of 4-center electron repulsion integrals (-1 for OFF)" 0) option_with_default(LIBINT2_ENABLE_ERI3 - "Compile with support for up to N-th derivatives of 3-center electron repulsion integrals (-1 for OFF)" -1) + "Compile with support for up to N-th derivatives of 3-center electron repulsion integrals (-1 for OFF)" -1) option_with_default(LIBINT2_ENABLE_ERI2 - "Compile with support for up to N-th derivatives of 2-center electron repulsion integrals (-1 for OFF)" -1) + "Compile with support for up to N-th derivatives of 2-center electron repulsion integrals (-1 for OFF)" -1) option_with_default(LIBINT2_ENABLE_RKB_ERI "Compile with support for up to N-th derivatives of relativistic restricted kinetic - balance (RKB) 4-center electron repulsion integrals (-1 for OFF)" 0) + balance (RKB) 4-center electron repulsion integrals (-1 for OFF). Only N=0 is currently supported." -1) option_with_default(LIBINT2_ENABLE_RKB_ERI3 "Compile with support for up to N-th derivatives of relativistic restricted kinetic balance (RKB) 3-center electron repulsion integrals (-1 for OFF). - σ·p acts on the two paired (AO) centers; the unpaired/fitting center is a spectator." -1) + σ·p acts on the two paired (AO) centers; the unpaired/fitting center is a spectator. + Only N=0 is currently supported." -1) +foreach(_cls RKB_ERI;RKB_ERI3) + if (LIBINT2_ENABLE_${_cls} GREATER 0) + message(FATAL_ERROR "LIBINT2_ENABLE_${_cls}=${LIBINT2_ENABLE_${_cls}}: geometric derivatives of RKB integrals are not yet implemented; use 0 (or -1 for OFF)") + endif() +endforeach() option_with_default(LIBINT2_ENABLE_G12 - "Compile with support for N-th derivatives of MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) + "Compile with support for N-th derivatives of MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) option_with_default(LIBINT2_ENABLE_G12DKH - "Compile with support for N-th derivatives of DKH-MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) + "Compile with support for N-th derivatives of DKH-MP2-F12 energies with Gaussian factors (-1 for OFF)" -1) option_with_print(LIBINT2_DISABLE_ONEBODY_PROPERTY_DERIVS - "Disable geometric derivatives of 1-body property integrals (all but overlap, kinetic, elecpot). + "Disable geometric derivatives of 1-body property integrals (all but overlap, kinetic, elecpot). These derivatives are disabled by default to save compile time. (enable with OFF) Note that the libtool build won't enable this- if forcibly enabled, build_libint balks." ON) option_with_print(LIBINT2_ENABLE_T1G12 - "Enable [Ti,G12] integrals when G12 integrals are enabled. Irrelevant when `LIBINT2_ENABLE_G12=OFF`. (disable with OFF)" ON) + "Enable [Ti,G12] integrals when G12 integrals are enabled. Irrelevant when `LIBINT2_ENABLE_G12=OFF`. (disable with OFF)" ON) # <<< Ordering Conventions >>> option_with_default(LIBINT2_SHGAUSS_ORDERING - "Ordering for shells of solid harmonic Gaussians: + "Ordering for shells of solid harmonic Gaussians: standard -- standard ordering (-l, -l+1 ... l) gaussian -- the Gaussian ordering (0, 1, -1, 2, -2, ... l, -l) See https://github.com/evaleev/libint/blob/master/INSTALL.md#solid-harmonic-ordering-scope-and-history ." standard) option_with_default(LIBINT2_CARTGAUSS_ORDERING - "Orderings for shells of cartesian Gaussians: + "Orderings for shells of cartesian Gaussians: standard -- standard ordering (xxx, xxy, xxz, xyy, xyz, xzz, yyy, ...) intv3 -- intv3 ordering (yyy, yyz, yzz, zzz, xyy, xyz, xzz, xxy, xxz, xxx) gamess -- GAMESS ordering (xxx, yyy, zzz, xxy, xxz, yyx, yyz, zzx, zzy, xyz) orca -- ORCA ordering (hydrid between GAMESS and standard) bagel -- axis-permuted version of intv3 (xxx, xxy, xyy, yyy, xxz, xyz, yyz, xzz, yzz, zzz)" standard) option_with_default(LIBINT2_SHELL_SET - "Support computation of shell sets sets subject to these restrictions: + "Support computation of shell sets sets subject to these restrictions: standard -- standard ordering: for (ab|cd): l(a) >= l(b), @@ -202,27 +208,27 @@ option_with_default(LIBINT2_SHELL_SET # `export CMAKE_BUILD_PARALLEL_LEVEL=N`. option_with_default(LIBINT2_MAX_AM - "Support Gaussians of angular momentum up to N. + "Support Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. If ERI3 ints are enabled, this option also controls the AM of the paired centers." 4) option_with_default(LIBINT2_OPT_AM - "Optimize maximally for up to angular momentum N (N <= max-am). + "Optimize maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. (default: (libint_max_am/2)+1)" -1) option_with_default(LIBINT2_MULTIPOLE_MAX_ORDER - "Maximum order of spherical multipole integrals. There is no maximum" 4) + "Maximum order of spherical multipole integrals. There is no maximum" 4) option_with_default(LIBINT2_ONEBODY_MAX_AM - "Support 1-body ints for Gaussians of angular momentum up to N. + "Support 1-body ints for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am)" -1) option_with_default(LIBINT2_ONEBODY_OPT_AM - "Optimize 1-body ints maximally for up to angular momentum N (N <= max-am). + "Optimize 1-body ints maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) option_with_default(LIBINT2_ERI_MAX_AM - "Support 4-center ERIs for Gaussians of angular momentum up to N. + "Support 4-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am)" -1) option_with_default(LIBINT2_ERI_OPT_AM - "Optimize 4-center ERIs maximally for up to angular momentum N (N <= max-am). + "Optimize 4-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) option_with_default(LIBINT2_RKB_ERI_MAX_AM @@ -242,77 +248,76 @@ option_with_default(LIBINT2_RKB_ERI3_OPT_AM option_with_print(LIBINT2_RKB_ERI3_PURE_SH "Assume the 'unpaired' (density fitting/auxiliary) center of 3-center RKB ERIs will be transformed to pure solid harmonics" OFF) - option_with_default(LIBINT2_ERI3_MAX_AM - "Support 3-center ERIs for Gaussians of angular momentum up to N. + "Support 3-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am) This sets the max AM of the density fitting (auxiliary/DF) basis on the single fitting center. The paired AO centers use LIBINT2_MAX_AM." -1) option_with_default(LIBINT2_ERI3_OPT_AM - "Optimize 3-center ERIs maximally for up to angular momentum N (N <= max-am). + "Optimize 3-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. (default: (max_am/2)+1)" -1) option_with_print(LIBINT2_ERI3_PURE_SH - "Assume the 'unpaired' center of 3-center ERIs will be transformed to pure solid harmonics" OFF) + "Assume the 'unpaired' center of 3-center ERIs will be transformed to pure solid harmonics" OFF) option_with_default(LIBINT2_ERI2_MAX_AM - "Support 2-center ERIs for Gaussians of angular momentum up to N. + "Support 2-center ERIs for Gaussians of angular momentum up to N. Can specify values for each derivative level as a semicolon-separated string. (default: max_am)" -1) option_with_default(LIBINT2_ERI2_OPT_AM - "Optimize 2-center ERIs maximally for up to angular momentum N (N <= max-am). + "Optimize 2-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string. (default: (max_am/2)+1)" -1) option_with_print(LIBINT2_ERI2_PURE_SH - "Assume the 2-center ERIs will be transformed to pure solid harmonics" OFF) + "Assume the 2-center ERIs will be transformed to pure solid harmonics" OFF) option_with_default(LIBINT2_G12_MAX_AM - "Support integrals for G12 methods of angular momentum up to N. (default: max_am)" -1) + "Support integrals for G12 methods of angular momentum up to N. (default: max_am)" -1) option_with_default(LIBINT2_G12_OPT_AM - "Optimize G12 integrals for up to angular momentum N (N <= max-am). (default: (max_am/2)+1)" -1) + "Optimize G12 integrals for up to angular momentum N (N <= max-am). (default: (max_am/2)+1)" -1) option_with_default(LIBINT2_G12DKH_MAX_AM - "Support integrals for relativistic G12 methods of angular momentum up to N. (default: max_am)" -1) + "Support integrals for relativistic G12 methods of angular momentum up to N. (default: max_am)" -1) option_with_default(LIBINT2_G12DKH_OPT_AM - "Optimize G12DKH integrals for up to angular momentum N (N <= max-am). (default: (max_am/2)+1)" -1) + "Optimize G12DKH integrals for up to angular momentum N (N <= max-am). (default: (max_am/2)+1)" -1) # <<< Miscellaneous >>> option_with_print(LIBINT2_CONTRACTED_INTS - "Turn on support for contracted integrals." ON) + "Turn on support for contracted integrals." ON) option_with_default(LIBINT2_ERI_STRATEGY - "(EXPERT) Compute ERIs using the following strategy. (0 for OS, 1 for HGP, 2 for HL)" 1) + "(EXPERT) Compute ERIs using the following strategy. (0 for OS, 1 for HGP, 2 for HL)" 1) option_with_print(LIBINT2_USE_COMPOSITE_EVALUATORS - "Libint will use composite evaluators (i.e. every evaluator will compute one integral type only)" ON) + "Libint will use composite evaluators (i.e. every evaluator will compute one integral type only)" ON) option_with_print(LIBINT2_SINGLE_EVALTYPE - "Generate single evaluator type (i.e. all tasks use the same evaluator). OFF is NYI" ON) + "Generate single evaluator type (i.e. all tasks use the same evaluator). OFF is NYI" ON) option_with_default(LIBINT2_ENABLE_UNROLLING - "Unroll shell sets into integrals (will unroll shell sets larger than N) (0 for never, N for N, 1000000000 for always)" 100) + "Unroll shell sets into integrals (will unroll shell sets larger than N) (0 for never, N for N, 1000000000 for always)" 100) option_with_default(LIBINT2_ALIGN_SIZE - "(EXPERT) if posix_memalign is available, this will specify alignment of Libint data, in units of + "(EXPERT) if posix_memalign is available, this will specify alignment of Libint data, in units of sizeof(LIBINT2_REALTYPE). Default is to use built-in heuristics: system-determined for vectorization off (default) or veclen * sizeof(LIBINT2_REALTYPE) for vectorization on." 0) mark_as_advanced(LIBINT2_ALIGN_SIZE) option_with_default(LIBINT2_REALTYPE - "Specifies the floating-point data type used by the library. Consumed at library build-time." double) + "Specifies the floating-point data type used by the library. Consumed at library build-time." double) option_with_print(LIBINT2_USER_DEFINED_REAL_INCLUDES - "Additional #includes necessary to use the real type." OFF) + "Additional #includes necessary to use the real type." OFF) include(int_userreal) option_with_print(LIBINT2_GENERATE_FMA - "Generate FMA (fused multiply-add) instructions (to benefit must have FMA-capable hardware and compiler)" OFF) + "Generate FMA (fused multiply-add) instructions (to benefit must have FMA-capable hardware and compiler)" OFF) option_with_print(LIBINT2_ENABLE_GENERIC_CODE "Use manually-written generic code" OFF) option_with_default(LIBINT2_API_PREFIX "Prepend this string to every name in the library API (except for the types)." "") option_with_print(LIBINT2_VECTOR_LENGTH - "Compute integrals in vectors of length N." OFF) + "Compute integrals in vectors of length N." OFF) option_with_default(LIBINT2_VECTOR_METHOD - "Specifies how to vectorize integrals. Irrelevant when `LIBINT2_VECTOR_LENGTH=OFF. Allowed values are 'block' (default), and 'line'." block) + "Specifies how to vectorize integrals. Irrelevant when `LIBINT2_VECTOR_LENGTH=OFF. Allowed values are 'block' (default), and 'line'." block) option_with_print(LIBINT2_ACCUM_INTS - "Accumulate integrals to the buffer, rather than copy (OFF for copy, ON for accum)." OFF) + "Accumulate integrals to the buffer, rather than copy (OFF for copy, ON for accum)." OFF) option_with_print(LIBINT2_FLOP_COUNT - "Support (approximate) FLOP counting by the library. (Generated code will require C++11!)" OFF) + "Support (approximate) FLOP counting by the library. (Generated code will require C++11!)" OFF) option_with_print(LIBINT2_PROFILE - "Turn on profiling instrumentation of the library. (Generated code will require C++11!)" OFF) + "Turn on profiling instrumentation of the library. (Generated code will require C++11!)" OFF) option_with_print(LIBINT2_ENABLE_MPFR - "Use GNU MPFR library for high-precision testing (EXPERTS ONLY). Consumed at library build-time." OFF) + "Use GNU MPFR library for high-precision testing (EXPERTS ONLY). Consumed at library build-time." OFF) option_with_default(LIBINT2_EXPORT_COMPRESSOR - "Export tarball with compression gzip or bzip2" gzip) + "Export tarball with compression gzip or bzip2" gzip) # next one defined by `include(CTest)` message(STATUS "Showing option BUILD_TESTING: ${BUILD_TESTING}") @@ -329,13 +334,13 @@ include(int_am) check_function_exists(posix_memalign HAVE_POSIX_MEMALIGN) if (NOT HAVE_POSIX_MEMALIGN) message(FATAL_ERROR "did not find posix_memalign ... this SHOULD NOT happen. Cannot proceed.") -endif () +endif() check_include_file_cxx(stdint.h HAVE_STDINT_H) # limits.h? if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES) set(LIBINT_HAS_CXX11 1) -endif () +endif() booleanize01(LIBINT2_ERI3_PURE_SH) booleanize01(LIBINT2_RKB_ERI3_PURE_SH) @@ -360,9 +365,9 @@ elseif (LIBINT2_EXPORT_COMPRESSOR STREQUAL "xz") elseif (LIBINT2_EXPORT_COMPRESSOR STREQUAL "bzip2") set(LIBINT_EXPORT_COMPRESSOR_CMD "jcf") set(LIBINT_EXPORT_COMPRESSOR_EXT "tbz2") -else () +else() message(FATAL_ERROR "No valid compressor; invoke CMake with -DLIBINT2_EXPORT_COMPRESSOR=gzip|bzip2") -endif () +endif() ################################## Dependencies ################################# @@ -373,9 +378,9 @@ if (LIBINT2_ENABLE_MPFR) # mpfr detected in CMakeLists.txt.export at appropriate time for library, but prechecking here find_package(Multiprecision MODULE REQUIRED COMPONENTS gmpxx mpfr) set(LIBINT_HAS_MPFR 1) -else () +else() find_package(Multiprecision MODULE REQUIRED COMPONENTS gmpxx) -endif () +endif() get_property(_loc TARGET Multiprecision::gmp PROPERTY LOCATION) message(VERBOSE "${Cyan}Found GMP${ColourReset}: ${_loc}") @@ -384,12 +389,12 @@ message(VERBOSE "${Cyan}Found GMPXX${ColourReset}: ${_loc}") if (TARGET Multiprecision::mpfr) get_property(_loc TARGET Multiprecision::mpfr PROPERTY LOCATION) message(VERBOSE "${Cyan}Found MPFR${ColourReset}: ${_loc} (found version ${MPFR_VERSION})") -endif () +endif() find_package(Boost 1.57 REQUIRED) if (TARGET Boost::headers) set(LIBINT_HAS_SYSTEM_BOOST_PREPROCESSOR_VARIADICS 1) -endif () +endif() # deferring find_package(Eigen3) to library (CMakeLists.txt.export) @@ -399,9 +404,9 @@ endif () set(EXPORT_STAGE_DIR ${PROJECT_BINARY_DIR}/libint-${LIBINT_EXT_VERSION}) configure_file( - cmake/modules/int_computed.cmake.in - cmake/modules/int_computed.cmake - @ONLY) + cmake/modules/int_computed.cmake.in + cmake/modules/int_computed.cmake + @ONLY) # CMake data transmitted to C++ via config.h for generator/compiler (_EXPORT_MODE=0). # Same info is positioned for the library export, but _EXPORT_MODE=1 turns on @@ -409,28 +414,28 @@ configure_file( # library build time. set(_EXPORT_MODE 0) # convert user-facing LIBINT2_ variables to LIBINT_ internal variables -foreach (_var API_PREFIX;ERI3_PURE_SH;RKB_ERI3_PURE_SH;ERI2_PURE_SH;DISABLE_ONEBODY_PROPERTY_DERIVS;ENABLE_UNROLLING;ENABLE_GENERIC_CODE;VECTOR_LENGTH;VECTOR_METHOD;ALIGN_SIZE;USER_DEFINED_REAL;USER_DEFINED_REAL_INCLUDES;GENERATE_FMA;ACCUM_INTS;FLOP_COUNT;PROFILE;CONTRACTED_INTS;SINGLE_EVALTYPE;USE_COMPOSITE_EVALUATORS;ERI_STRATEGY;MULTIPOLE_MAX_ORDER) +foreach(_var API_PREFIX;ERI3_PURE_SH;RKB_ERI3_PURE_SH;ERI2_PURE_SH;DISABLE_ONEBODY_PROPERTY_DERIVS;ENABLE_UNROLLING;ENABLE_GENERIC_CODE;VECTOR_LENGTH;VECTOR_METHOD;ALIGN_SIZE;USER_DEFINED_REAL;USER_DEFINED_REAL_INCLUDES;GENERATE_FMA;ACCUM_INTS;FLOP_COUNT;PROFILE;CONTRACTED_INTS;SINGLE_EVALTYPE;USE_COMPOSITE_EVALUATORS;ERI_STRATEGY;MULTIPOLE_MAX_ORDER) if (DEFINED LIBINT2_${_var}) if (DEFINED LIBINT_${_var}) message(FATAL_ERROR "renaming user-facing LIBINT2_${_var} variable but internal variable LIBINT_${_var} already exists") else () set(LIBINT_${_var} ${LIBINT2_${_var}}) - endif () - endif () + endif() + endif() endforeach () configure_file( - include/libint2/config.h.cmake.in - include/libint2/config.h - @ONLY) + include/libint2/config.h.cmake.in + include/libint2/config.h + @ONLY) set(_EXPORT_MODE 1) configure_file( - include/libint2/config.h.cmake.in - ${EXPORT_STAGE_DIR}/include/libint2/config.h - @ONLY) + include/libint2/config.h.cmake.in + ${EXPORT_STAGE_DIR}/include/libint2/config.h + @ONLY) configure_file( - include/libint2/config2.h.cmake.in - ${EXPORT_STAGE_DIR}/include/libint2/config2.h.cmake.in - COPYONLY) + include/libint2/config2.h.cmake.in + ${EXPORT_STAGE_DIR}/include/libint2/config2.h.cmake.in + COPYONLY) add_subdirectory(src) diff --git a/INSTALL.md b/INSTALL.md index e48abdb6e..4fb68b76c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -169,8 +169,8 @@ Use combined targets like `cmake --target check install` to avoid some unnecessa * `LIBINT2_ENABLE_ERI` - G - Compile with support for up to N-th derivatives of 4-center electron repulsion integrals. Use -1 for OFF. [Default=0] * `LIBINT2_ENABLE_ERI3` - G - Compile with support for up to N-th derivatives of 3-center electron repulsion integrals. Use -1 for OFF. [Default=-1] * `LIBINT2_ENABLE_ERI2` - G - Compile with support for up to N-th derivatives of 2-center electron repulsion integrals. Use -1 for OFF. [Default=-1] -* `LIBINT2_ENABLE_RKB_ERI` - G - Compile with support for up to N-th derivatives of relativistic restricted kinetic balance (RKB) 4-center electron repulsion integrals. Use -1 for OFF. [Default=0] -* `LIBINT2_ENABLE_RKB_ERI3` - G - Compile with support for up to N-th derivatives of relativistic restricted kinetic balance (RKB) 3-center electron repulsion integrals. Use -1 for OFF. σ·p acts on the two paired (AO) centers; the unpaired/fitting (density-fitting/auxiliary) center is a spectator. [Default=-1] +* `LIBINT2_ENABLE_RKB_ERI` - G - Compile with support for up to N-th derivatives of relativistic restricted kinetic balance (RKB) 4-center electron repulsion integrals. Use -1 for OFF. Derivatives are not yet implemented, i.e. only N=0 is supported. [Default=-1] +* `LIBINT2_ENABLE_RKB_ERI3` - G - Compile with support for up to N-th derivatives of relativistic restricted kinetic balance (RKB) 3-center electron repulsion integrals. Use -1 for OFF. σ·p acts on the two paired (AO) centers; the unpaired/fitting (density-fitting/auxiliary) center is a spectator. Derivatives are not yet implemented, i.e. only N=0 is supported. [Default=-1] * `LIBINT2_ENABLE_G12` - G - Compile with support for N-th derivatives of MP2-F12 energies with Gaussian factors. Use -1 for OFF. [Default=-1] * `LIBINT2_ENABLE_G12DKH` - G - Compile with support for N-th derivatives of DKH-MP2-F12 energies with Gaussian factors. Use -1 for OFF. [Default=-1] diff --git a/export/fortran/libint_f.F90 b/export/fortran/libint_f.F90 index e59d4a5ed..0293da5e3 100644 --- a/export/fortran/libint_f.F90 +++ b/export/fortran/libint_f.F90 @@ -520,88 +520,88 @@ SUBROUTINE compute_eri_f(contrdepth, deriv_order, am1, c1, alpha1, A, & am_tot = am + deriv_order -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_0 - IF (am_tot >= 0) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_0(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_0 + IF (am_tot >= 0) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_0(1) = & C1234*pfac*F(1, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_1 - IF (am_tot >= 1) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_1(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_1 + IF (am_tot >= 1) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_1(1) = & C1234*pfac*F(2, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_2 - IF (am_tot >= 2) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_2(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_2 + IF (am_tot >= 2) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_2(1) = & C1234*pfac*F(3, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_3 - IF (am_tot >= 3) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_3(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_3 + IF (am_tot >= 3) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_3(1) = & C1234*pfac*F(4, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_4 - IF (am_tot >= 4) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_4(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_4 + IF (am_tot >= 4) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_4(1) = & C1234*pfac*F(5, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_5 - IF (am_tot >= 5) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_5(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_5 + IF (am_tot >= 5) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_5(1) = & C1234*pfac*F(6, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_6 - IF (am_tot >= 6) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_6(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_6 + IF (am_tot >= 6) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_6(1) = & C1234*pfac*F(7, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_7 - IF (am_tot >= 7) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_7(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_7 + IF (am_tot >= 7) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_7(1) = & C1234*pfac*F(8, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_8 - IF (am_tot >= 8) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_8(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_8 + IF (am_tot >= 8) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_8(1) = & C1234*pfac*F(9, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_9 - IF (am_tot >= 9) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_9(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_9 + IF (am_tot >= 9) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_9(1) = & C1234*pfac*F(10, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_10 - IF (am_tot >= 10) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_10(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_10 + IF (am_tot >= 10) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_10(1) = & C1234*pfac*F(11, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_11 - IF (am_tot >= 11) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_11(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_11 + IF (am_tot >= 11) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_11(1) = & C1234*pfac*F(12, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_12 - IF (am_tot >= 12) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_12(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_12 + IF (am_tot >= 12) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_12(1) = & C1234*pfac*F(13, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_13 - IF (am_tot >= 13) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_13(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_13 + IF (am_tot >= 13) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_13(1) = & C1234*pfac*F(14, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_14 - IF (am_tot >= 14) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_14(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_14 + IF (am_tot >= 14) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_14(1) = & C1234*pfac*F(15, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_15 - IF (am_tot >= 15) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_15(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_15 + IF (am_tot >= 15) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_15(1) = & C1234*pfac*F(16, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_16 - IF (am_tot >= 16) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_16(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_16 + IF (am_tot >= 16) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_16(1) = & C1234*pfac*F(17, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_17 - IF (am_tot >= 17) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_17(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_17 + IF (am_tot >= 17) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_17(1) = & C1234*pfac*F(18, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_18 - IF (am_tot >= 18) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_18(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_18 + IF (am_tot >= 18) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_18(1) = & C1234*pfac*F(19, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_19 - IF (am_tot >= 19) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_19(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_19 + IF (am_tot >= 19) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_19(1) = & C1234*pfac*F(20, p1234) #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_20 - IF (am_tot >= 20) erieval(p1234)%f_aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_20(1) = & +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_20 + IF (am_tot >= 20) erieval(p1234)%f_aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_20(1) = & C1234*pfac*F(21, p1234) #endif ENDDO diff --git a/export/fortran/test-eri.cc b/export/fortran/test-eri.cc index 7304659aa..d25381474 100644 --- a/export/fortran/test-eri.cc +++ b/export/fortran/test-eri.cc @@ -257,130 +257,130 @@ void compute_eri_c(int &contrdepth, int &deriv_order, int &am1, double *c1, const int am_tot = am + deriv_order; -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_0 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_0 if (am_tot >= 0) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_0[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_0[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 0]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_1 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_1 if (am_tot >= 1) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_1[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_1[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 1]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_2 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_2 if (am_tot >= 2) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_2[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_2[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 2]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_3 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_3 if (am_tot >= 3) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_3[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_3[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 3]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_4 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_4 if (am_tot >= 4) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_4[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_4[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 4]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_5 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_5 if (am_tot >= 5) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_5[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_5[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 5]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_6 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_6 if (am_tot >= 6) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_6[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_6[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 6]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_7 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_7 if (am_tot >= 7) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_7[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_7[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 7]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_8 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_8 if (am_tot >= 8) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_8[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_8[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 8]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_9 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_9 if (am_tot >= 9) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_9[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_9[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 9]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_10 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_10 if (am_tot >= 10) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_10[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_10[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 10]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_11 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_11 if (am_tot >= 11) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_11[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_11[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 11]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_12 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_12 if (am_tot >= 12) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_12[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_12[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 12]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_13 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_13 if (am_tot >= 13) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_13[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_13[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 13]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_14 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_14 if (am_tot >= 14) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_14[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_14[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 14]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_15 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_15 if (am_tot >= 15) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_15[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_15[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 15]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_16 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_16 if (am_tot >= 16) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_16[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_16[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 16]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_17 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_17 if (am_tot >= 17) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_17[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_17[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 17]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_18 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_18 if (am_tot >= 18) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_18[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_18[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 18]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_19 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_19 if (am_tot >= 19) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_19[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_19[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 19]; #endif -#ifdef LIBINT2_DEFINED__aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_20 +#ifdef LIBINT2_DEFINED__aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_20 if (am_tot >= 20) erieval[p0123] - ._aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_20[0] = + ._aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_20[0] = c1234 * pfac * F[p0123 * (am_tot + 1) + 20]; #endif diff --git a/export/tests/unit/test-1body.cc b/export/tests/unit/test-1body.cc index 0bcb6aa64..c236aa72d 100644 --- a/export/tests/unit/test-1body.cc +++ b/export/tests/unit/test-1body.cc @@ -21,27 +21,30 @@ #include "catch.hpp" #include "fixture.h" -#define LIBINT2_TEST_ONEBODY(scale, op_idx) \ - int m0, m1; \ - FOR_SOLIDHARM_STANDARD(l0, m0) \ - FOR_SOLIDHARM_STANDARD(l1, m1) \ - const auto i0_s = libint2::INT_SOLIDHARMINDEX_STANDARD(l0, m0); \ - const auto i1_s = libint2::INT_SOLIDHARMINDEX_STANDARD(l1, m1); \ - const auto i0i1_s = i0_s * n1 + i1_s; \ - if (libint2::solid_harmonics_ordering() == \ - libint2::SHGShellOrdering_Standard) { \ - REQUIRE(engine.results()[op_idx][i0i1_s] / scale == \ - Approx(shellset_ref_standard[i0i1_s]).margin(1e-10)); \ - } else { \ - const auto i0_g = libint2::INT_SOLIDHARMINDEX_GAUSSIAN(l0, m0); \ - const auto i1_g = libint2::INT_SOLIDHARMINDEX_GAUSSIAN(l1, m1); \ - const auto i0i1_g = i0_g * n1 + i1_g; \ - REQUIRE(engine.results()[op_idx][i0i1_g] / scale == \ - Approx(shellset_ref_standard[i0i1_s]).margin(1e-10)); \ - } \ - END_FOR_SOLIDHARM \ +#define LIBINT2_TEST_ONEBODY_MARGIN(scale, op_idx, abs_margin) \ + int m0, m1; \ + FOR_SOLIDHARM_STANDARD(l0, m0) \ + FOR_SOLIDHARM_STANDARD(l1, m1) \ + const auto i0_s = libint2::INT_SOLIDHARMINDEX_STANDARD(l0, m0); \ + const auto i1_s = libint2::INT_SOLIDHARMINDEX_STANDARD(l1, m1); \ + const auto i0i1_s = i0_s * n1 + i1_s; \ + if (libint2::solid_harmonics_ordering() == \ + libint2::SHGShellOrdering_Standard) { \ + REQUIRE(engine.results()[op_idx][i0i1_s] / scale == \ + Approx(shellset_ref_standard[i0i1_s]).margin(abs_margin)); \ + } else { \ + const auto i0_g = libint2::INT_SOLIDHARMINDEX_GAUSSIAN(l0, m0); \ + const auto i1_g = libint2::INT_SOLIDHARMINDEX_GAUSSIAN(l1, m1); \ + const auto i0i1_g = i0_g * n1 + i1_g; \ + REQUIRE(engine.results()[op_idx][i0i1_g] / scale == \ + Approx(shellset_ref_standard[i0i1_s]).margin(abs_margin)); \ + } \ + END_FOR_SOLIDHARM \ END_FOR_SOLIDHARM +#define LIBINT2_TEST_ONEBODY(scale, op_idx) \ + LIBINT2_TEST_ONEBODY_MARGIN(scale, op_idx, 0.0) + TEST_CASE_METHOD(libint2::unit::DefaultFixture, "electrostatic potential", "[engine][1-body]") { #if defined(LIBINT2_SUPPORT_ONEBODY) @@ -251,15 +254,21 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpeμσp correctness", // 12 components = 3 dipole directions k (x,y,z) × 4 Pauli quaternion pieces q // (0=trace, 1/2/3=antisym), component index = 4*k + q. // - // Reference values are the operator output over the (d|d) shell pair below, - // generated once and independently validated against the Pauli-quaternion - // fold of the dipole's geometric derivatives ∂A_a ∂B_b (μ|r_k|ν) (obtained - // from emultipole1 deriv-2 and finite differences; agreement ~1e-6). + // Reference values are the operator output over the (d|d) shell pair below + // (regression check); all components are also validated independently at + // the end against the Pauli-quaternion fold of the dipole's geometric + // derivatives ∂A_a ∂B_b (μ|r_k|ν), obtained by finite differences. // Components 1, 6, 11 (Pauli direction aligned with the dipole direction) // vanish by symmetry. Layout mirrors the opVop (σpVσp) test above. - std::vector obs{ - Shell{{1.0, 3.0}, {{2, true, {1.0, 0.3}}}, {{0.0, 0.0, 0.0}}}, - Shell{{2.0, 5.0}, {{2, true, {1.0, 0.2}}}, {{1.0, 1.0, 1.0}}}}; + auto make_shell0 = [](std::array O) { + return Shell{{1.0, 3.0}, {{2, true, {1.0, 0.3}}}, O}; + }; + auto make_shell1 = [](std::array O) { + return Shell{{2.0, 5.0}, {{2, true, {1.0, 0.2}}}, O}; + }; + const std::array O0{{0.0, 0.0, 0.0}}; + const std::array O1{{1.0, 1.0, 1.0}}; + std::vector obs{make_shell0(O0), make_shell1(O1)}; const auto lmax = std::min(2, LIBINT2_MAX_AM_opemuop); if (lmax < 2) return; @@ -275,116 +284,258 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpeμσp correctness", // all ref are laid out in standard solids order { std::vector shellset_ref_standard = { - 0.09223217251311097, 0.7337253845349039, 0.8940034778856171, 0.22249836470532133, -0.252327005953861, - 0.2544347044464152, 0.25443470444641514, 0.0, 0.2544347044464152, 0.0, - 0.4283068075013277, -0.4656966703842894, -0.8368997546246189, -0.1272426104648221, -0.10997391819356982, - 0.22249836470532133, 0.7337253845349038, -0.22848014172589765, 0.09223217251311103, -0.9003932258975146, - -0.10035593979812935, 0.8066102940212458, -0.10997391819356983, -0.4211025458090261, -0.7099128120788283}; - LIBINT2_TEST_ONEBODY(1.0, 0); + 0.09223217251311097, 0.7337253845349039, + 0.8940034778856171, 0.22249836470532133, + -0.252327005953861, 0.2544347044464152, + 0.25443470444641514, 0.0, + 0.2544347044464152, 0.0, + 0.4283068075013277, -0.4656966703842894, + -0.8368997546246189, -0.1272426104648221, + -0.10997391819356982, 0.22249836470532133, + 0.7337253845349038, -0.22848014172589765, + 0.09223217251311103, -0.9003932258975146, + -0.10035593979812935, 0.8066102940212458, + -0.10997391819356983, -0.4211025458090261, + -0.7099128120788283}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 0, 1e-10); } { std::vector shellset_ref_standard = { - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0}; - LIBINT2_TEST_ONEBODY(1.0, 1); + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 1, 1e-10); } { - std::vector shellset_ref_standard = { - 0.04479855132698852, 0.04479855132698852, 0.0, 0.04479855132698859, 0.0, - 0.04479855132698847, -0.07520852256716505, -0.25038279554682064, -0.268870098007082, 0.06000353694707675, - 0.0, -0.25038279554682064, 0.09138051976167452, -0.25038279554682075, 0.0, - 0.04479855132698842, -0.2688700980070819, -0.2503827955468205, -0.07520852256716526, -0.06000353694707675, - 0.0, 0.06000353694707682, 0.0, -0.06000353694707674, -0.24683963008765036}; - LIBINT2_TEST_ONEBODY(1.0, 2); + std::vector shellset_ref_standard = {0.04479855132698852, + 0.04479855132698852, + 0.0, + 0.04479855132698859, + 0.0, + 0.04479855132698847, + -0.07520852256716505, + -0.25038279554682064, + -0.268870098007082, + 0.06000353694707675, + 0.0, + -0.25038279554682064, + 0.09138051976167452, + -0.25038279554682075, + 0.0, + 0.04479855132698842, + -0.2688700980070819, + -0.2503827955468205, + -0.07520852256716526, + -0.06000353694707675, + 0.0, + 0.06000353694707682, + 0.0, + -0.06000353694707674, + -0.24683963008765036}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 2, 1e-10); } { std::vector shellset_ref_standard = { - 0.07520852256716518, 0.26887009800708184, -0.17715598508649683, -0.04479855132698844, -0.18683609314057345, - 0.268870098007082, 0.07520852256716505, -0.07322681046032367, -0.044798551326988484, -0.24683963008765025, - -0.17715598508649694, -0.07322681046032367, 0.16228459262531905, 0.0, -0.14645362092064743, - -0.04479855132698858, -0.04479855132698848, 0.0, -0.044798551326988525, 0.0, - -0.18683609314057373, -0.24683963008765045, -0.14645362092064743, 0.0, -0.006825482299343409}; - LIBINT2_TEST_ONEBODY(1.0, 3); + 0.07520852256716518, 0.26887009800708184, + -0.17715598508649683, -0.04479855132698844, + -0.18683609314057345, 0.268870098007082, + 0.07520852256716505, -0.07322681046032367, + -0.044798551326988484, -0.24683963008765025, + -0.17715598508649694, -0.07322681046032367, + 0.16228459262531905, 0.0, + -0.14645362092064743, -0.04479855132698858, + -0.04479855132698848, 0.0, + -0.044798551326988525, 0.0, + -0.18683609314057373, -0.24683963008765045, + -0.14645362092064743, 0.0, + -0.006825482299343409}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 3, 1e-10); } { std::vector shellset_ref_standard = { - 0.09223217251311097, 0.2224983647053212, 0.8940034778856171, 0.7337253845349039, 0.25232700595386154, - 0.2224983647053212, 0.09223217251311096, -0.22848014172589742, 0.7337253845349039, 0.9003932258975148, - 0.42830680750132777, -0.127242610464822, -0.836899754624619, -0.46569667038428947, 0.10997391819356969, - 0.2544347044464152, 0.25443470444641514, 0.0, 0.25443470444641514, 0.0, - 0.10035593979812953, 0.42110254580902606, 0.10997391819356978, -0.8066102940212458, -0.7099128120788284}; - LIBINT2_TEST_ONEBODY(1.0, 4); + 0.09223217251311097, 0.2224983647053212, + 0.8940034778856171, 0.7337253845349039, + 0.25232700595386154, 0.2224983647053212, + 0.09223217251311096, -0.22848014172589742, + 0.7337253845349039, 0.9003932258975148, + 0.42830680750132777, -0.127242610464822, + -0.836899754624619, -0.46569667038428947, + 0.10997391819356969, 0.2544347044464152, + 0.25443470444641514, 0.0, + 0.25443470444641514, 0.0, + 0.10035593979812953, 0.42110254580902606, + 0.10997391819356978, -0.8066102940212458, + -0.7099128120788284}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 4, 1e-10); } { - std::vector shellset_ref_standard = { - -0.04479855132698845, -0.04479855132698845, 0.0, -0.04479855132698854, 0.0, - -0.04479855132698835, 0.07520852256716522, 0.2503827955468206, 0.26887009800708184, -0.0600035369470768, - 0.0, 0.2503827955468207, -0.09138051976167444, 0.2503827955468207, 0.0, - -0.044798551326988435, 0.268870098007082, 0.2503827955468206, 0.07520852256716506, 0.060003536947076705, - 0.0, -0.06000353694707683, 0.0, 0.06000353694707682, 0.24683963008765056}; - LIBINT2_TEST_ONEBODY(1.0, 5); + std::vector shellset_ref_standard = {-0.04479855132698845, + -0.04479855132698845, + 0.0, + -0.04479855132698854, + 0.0, + -0.04479855132698835, + 0.07520852256716522, + 0.2503827955468206, + 0.26887009800708184, + -0.0600035369470768, + 0.0, + 0.2503827955468207, + -0.09138051976167444, + 0.2503827955468207, + 0.0, + -0.044798551326988435, + 0.268870098007082, + 0.2503827955468206, + 0.07520852256716506, + 0.060003536947076705, + 0.0, + -0.06000353694707683, + 0.0, + 0.06000353694707682, + 0.24683963008765056}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 5, 1e-10); } { std::vector shellset_ref_standard = { - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0}; - LIBINT2_TEST_ONEBODY(1.0, 6); + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 6, 1e-10); } { std::vector shellset_ref_standard = { - -0.07520852256716509, 0.04479855132698833, 0.177155985086497, -0.26887009800708184, -0.18683609314057353, - 0.04479855132698844, 0.04479855132698844, 0.0, 0.044798551326988505, 0.0, - 0.17715598508649694, 0.0, -0.16228459262531914, 0.07322681046032364, -0.14645362092064745, - -0.26887009800708195, 0.044798551326988456, 0.07322681046032371, -0.07520852256716508, -0.2468396300876503, - -0.1868360931405737, 0.0, -0.14645362092064748, -0.24683963008765053, 0.006825482299343263}; - LIBINT2_TEST_ONEBODY(1.0, 7); + -0.07520852256716509, 0.04479855132698833, + 0.177155985086497, -0.26887009800708184, + -0.18683609314057353, 0.04479855132698844, + 0.04479855132698844, 0.0, + 0.044798551326988505, 0.0, + 0.17715598508649694, 0.0, + -0.16228459262531914, 0.07322681046032364, + -0.14645362092064745, -0.26887009800708195, + 0.044798551326988456, 0.07322681046032371, + -0.07520852256716508, -0.2468396300876503, + -0.1868360931405737, 0.0, + -0.14645362092064748, -0.24683963008765053, + 0.006825482299343263}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 7, 1e-10); } { - std::vector shellset_ref_standard = { - 0.2544347044464152, 0.25443470444641514, 0.0, 0.25443470444641514, 0.0, - 0.7337253845349039, 0.09223217251311093, -0.6655233361597198, 0.2224983647053212, 0.6480662199436535, - 0.9313933407685788, -0.3010641970365056, -0.6464193408059329, -0.30106419703650567, 0.0, - 0.7337253845349039, 0.2224983647053212, -0.6655233361597195, 0.09223217251311097, -0.6480662199436531, - 0.0, 0.3207466060108965, 0.0, -0.3207466060108965, -0.9003932258975146}; - LIBINT2_TEST_ONEBODY(1.0, 8); + std::vector shellset_ref_standard = {0.2544347044464152, + 0.25443470444641514, + 0.0, + 0.25443470444641514, + 0.0, + 0.7337253845349039, + 0.09223217251311093, + -0.6655233361597198, + 0.2224983647053212, + 0.6480662199436535, + 0.9313933407685788, + -0.3010641970365056, + -0.6464193408059329, + -0.30106419703650567, + 0.0, + 0.7337253845349039, + 0.2224983647053212, + -0.6655233361597195, + 0.09223217251311097, + -0.6480662199436531, + 0.0, + 0.3207466060108965, + 0.0, + -0.3207466060108965, + -0.9003932258975146}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 8, 1e-10); } { std::vector shellset_ref_standard = { - -0.07520852256716505, -0.268870098007082, 0.17715598508649688, 0.04479855132698845, 0.18683609314057348, - -0.2688700980070818, -0.07520852256716509, 0.07322681046032364, 0.044798551326988394, 0.2468396300876503, - 0.17715598508649705, 0.07322681046032363, -0.16228459262531927, 0.0, 0.14645362092064737, - 0.044798551326988525, 0.04479855132698848, 0.0, 0.044798551326988435, 0.0, - 0.18683609314057356, 0.2468396300876504, 0.1464536209206475, 0.0, 0.006825482299343293}; - LIBINT2_TEST_ONEBODY(1.0, 9); + -0.07520852256716505, -0.268870098007082, + 0.17715598508649688, 0.04479855132698845, + 0.18683609314057348, -0.2688700980070818, + -0.07520852256716509, 0.07322681046032364, + 0.044798551326988394, 0.2468396300876503, + 0.17715598508649705, 0.07322681046032363, + -0.16228459262531927, 0.0, + 0.14645362092064737, 0.044798551326988525, + 0.04479855132698848, 0.0, + 0.044798551326988435, 0.0, + 0.18683609314057356, 0.2468396300876504, + 0.1464536209206475, 0.0, + 0.006825482299343293}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 9, 1e-10); } { std::vector shellset_ref_standard = { - 0.07520852256716505, -0.04479855132698846, -0.1771559850864969, 0.26887009800708195, 0.1868360931405736, - -0.044798551326988505, -0.04479855132698844, 0.0, -0.04479855132698858, 0.0, - -0.17715598508649705, 0.0, 0.16228459262531922, -0.07322681046032384, 0.14645362092064737, - 0.26887009800708184, -0.04479855132698835, -0.07322681046032367, 0.07520852256716509, 0.2468396300876504, - 0.18683609314057362, 0.0, 0.14645362092064745, 0.2468396300876504, -0.006825482299343255}; - LIBINT2_TEST_ONEBODY(1.0, 10); + 0.07520852256716505, -0.04479855132698846, + -0.1771559850864969, 0.26887009800708195, + 0.1868360931405736, -0.044798551326988505, + -0.04479855132698844, 0.0, + -0.04479855132698858, 0.0, + -0.17715598508649705, 0.0, + 0.16228459262531922, -0.07322681046032384, + 0.14645362092064737, 0.26887009800708184, + -0.04479855132698835, -0.07322681046032367, + 0.07520852256716509, 0.2468396300876504, + 0.18683609314057362, 0.0, + 0.14645362092064745, 0.2468396300876504, + -0.006825482299343255}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 10, 1e-10); } { std::vector shellset_ref_standard = { - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0}; - LIBINT2_TEST_ONEBODY(1.0, 11); + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + LIBINT2_TEST_ONEBODY_MARGIN(1.0, 11, 1e-10); + } + + // independent check of all components: the Pauli-quaternion fold of + // (∂_a μ|r_k|∂_b ν) = ∂A_a ∂B_b (μ|r_k|ν), with the latter evaluated by + // central finite differences of the emultipole1 integrals + { + engine.compute(obs[0], obs[1]); + auto emu_engine = Engine(Operator::emultipole1, 2, lmax); + emu_engine.set_params(std::array{{0.0, 0.0, 0.0}}); + const auto n01 = obs[0].size() * obs[1].size(); + const double h = 1e-4; + // dAdB[(3*a + b)*3 + k][i] = ∂A_a ∂B_b (μ|r_k|ν)_i + std::vector> dAdB(27, std::vector(n01, 0.0)); + for (int a = 0; a != 3; ++a) { + for (int b = 0; b != 3; ++b) { + for (int sa : {-1, 1}) { + for (int sb : {-1, 1}) { + auto A = O0; + A[a] += sa * h; + auto B = O1; + B[b] += sb * h; + const auto& emu = + emu_engine.compute(make_shell0(A), make_shell1(B)); + for (int k = 0; k != 3; ++k) + for (size_t i = 0; i != n01; ++i) + dAdB[(3 * a + b) * 3 + k][i] += + sa * sb * emu[1 + k][i] / (4 * h * h); + } + } + } + } + auto d = [&](int a, int b, int k, size_t i) { + return dAdB[(3 * a + b) * 3 + k][i]; + }; + constexpr int x = 0, y = 1, z = 2; + for (int k = 0; k != 3; ++k) { + for (size_t i = 0; i != n01; ++i) { + const double ref[4] = {d(x, x, k, i) + d(y, y, k, i) + d(z, z, k, i), + d(y, z, k, i) - d(z, y, k, i), + d(z, x, k, i) - d(x, z, k, i), + d(x, y, k, i) - d(y, x, k, i)}; + for (int q = 0; q != 4; ++q) + REQUIRE(engine.results()[4 * k + q][i] == + Approx(ref[q]).margin(1e-6)); + } + } } + #endif // LIBINT2_SUPPORT_ONEBODY } - // Helper: max number of primitives across all centers in q_gau data. static size_t max_nprim(const libint2::GaussianPotentialCentersData& data) { size_t result = 0; diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index d58ee379f..88b2227bb 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -20,6 +20,8 @@ #include +#include + #include "catch.hpp" #include "fixture.h" @@ -345,13 +347,23 @@ TEST_CASE("eri geometric derivatives", "[engine][2-body]") { } TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { - std::vector obs{// pseudorandom s - Shell{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}, - // pseudorandom p - Shell{{2.0}, {{1, false, {1.0}}}, {{1.0, 1.0, 1.0}}}}; + const std::vector all_shells{ + // pseudorandom s + Shell{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}, + // pseudorandom p + Shell{{2.0}, {{1, false, {1.0}}}, {{1.0, 1.0, 1.0}}}, + // pseudorandom contracted p + Shell{{3.0, 0.7}, {{1, false, {0.4, 0.8}}}, {{-0.5, 0.2, 0.9}}}, + // pseudorandom d + Shell{{1.2}, {{2, false, {1.0}}}, {{0.3, -0.8, 0.4}}}}; + // the subset of all_shells supported by a kernel with max AM lmax + auto shells_upto = [&all_shells](int lmax) { + std::vector result; + for (const auto &sh : all_shells) + if (sh.contr[0].l <= lmax) result.push_back(sh); + return result; + }; - const auto max_nprim = libint2::max_nprim(obs); - const auto max_l = libint2::max_l(obs); typedef std::array der_idx; // e.g. d_xx maps the derivative index of derivative w.r.t x @@ -367,29 +379,40 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { der_idx d_xy = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0}; der_idx d_yx = {0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0}; +#if LIBINT2_TASK_EXISTS_4coulomb_opop && LIBINT2_TASK_EXISTS_4opop_coulomb_opop SECTION("Coulombσpσp and σpσpCoulombσpσp") { - Engine engine_llss, engine_ssss; - try { - engine_llss = Engine(Operator::coulomb_opop, max_nprim, max_l, 0); - engine_ssss = Engine(Operator::opop_coulomb_opop, max_nprim, max_l, 0); - // TODO: need another unit test for derivatives of RKB ERIs - } catch ( - Engine::lmax_exceeded &) { // skip the test if lmax exceeded or libint2 - // not configured with RKB support - return; - } + // the reference is expensive: skip the uncontracted p shell (the + // contracted p shell covers p) + auto obs = shells_upto(std::min(LIBINT2_MAX_AM_coulomb_opop, + LIBINT2_MAX_AM_opop_coulomb_opop)); + obs.erase(std::remove_if(obs.begin(), obs.end(), + [](const Shell &sh) { + return sh.contr[0].l == 1 && sh.nprim() == 1; + }), + obs.end()); + const auto max_nprim = libint2::max_nprim(obs); + const auto max_l = libint2::max_l(obs); + Engine engine_llss(Operator::coulomb_opop, max_nprim, max_l, 0); + Engine engine_ssss(Operator::opop_coulomb_opop, max_nprim, max_l, 0); const auto nshell = obs.size(); for (int s0 = 0; s0 != nshell; ++s0) { for (int s1 = 0; s1 != nshell; ++s1) { for (int s2 = 0; s2 != nshell; ++s2) { for (int s3 = 0; s3 != nshell; ++s3) { + // the reference is expensive, include at most 1 d shell + if ((obs[s0].contr[0].l > 1) + (obs[s1].contr[0].l > 1) + + (obs[s2].contr[0].l > 1) + (obs[s3].contr[0].l > 1) > + 1) + continue; const auto &results_llss = engine_llss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); const auto &results_ssss = engine_ssss.compute(obs[s0], obs[s1], obs[s2], obs[s3]); - assert(results_llss.size() == - 4); // 4 buffers for single-spin quaternion components + // 4 buffers for single-spin quaternion components + REQUIRE(results_llss.size() == 4); + // 16 buffers for two independent spin-space quaternions + REQUIRE(results_ssss.size() == 16); LIBINT2_REF_REALTYPE Aref[3]; for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; @@ -560,7 +583,9 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { REQUIRE(!llss_not_ok); } - // (SS|SS) has 16 components (two independent spin spaces) + // (SS|SS) has 16 components (two independent spin spaces); the + // reference involves 4th derivatives and loses more precision + const double SSSS_ABSOLUTE_DEVIATION_THRESHOLD = 1.0E-12; for (auto comp = 0; comp < 16; ++comp) { auto abs_err_ssss = abs(ref_opop_coulomb_opop[comp] - results_ssss[comp][ijkl]); @@ -569,7 +594,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { bool ssss_not_ok = rel_abs_err_ssss > RELATIVE_DEVIATION_THRESHOLD && - abs_err_ssss > ABSOLUTE_DEVIATION_THRESHOLD; + abs_err_ssss > SSSS_ABSOLUTE_DEVIATION_THRESHOLD; if (ssss_not_ok) { std::cout << "(l0 l1| l2 l3) = " @@ -595,13 +620,14 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } +#endif // 4-center Coulombσpσp and σpσpCoulombσpσp + +#if LIBINT2_TASK_EXISTS_4op_coulomb_op SECTION("op_coulomb_op") { - Engine engine_opCop; - try { - engine_opCop = Engine(Operator::op_coulomb_op, max_nprim, max_l, 0); - } catch (Engine::lmax_exceeded &) { - return; - } + const auto obs = shells_upto(LIBINT2_MAX_AM_op_coulomb_op); + const auto max_nprim = libint2::max_nprim(obs); + const auto max_l = libint2::max_l(obs); + Engine engine_opCop(Operator::op_coulomb_op, max_nprim, max_l, 0); const auto nshell = obs.size(); for (int s0 = 0; s0 != nshell; ++s0) { @@ -610,7 +636,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { for (int s3 = 0; s3 != nshell; ++s3) { const auto &results = engine_opCop.compute(obs[s0], obs[s1], obs[s2], obs[s3]); - assert(results.size() == 9); + REQUIRE(results.size() == 9); LIBINT2_REF_REALTYPE Aref[3], Bref[3], Cref[3], Dref[3]; for (int i = 0; i < 3; ++i) Aref[i] = obs[s0].O[i]; @@ -725,13 +751,12 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } +#endif // 4-center op_coulomb_op + // The 3-center RKB sections below exercise kernels that exist only when -// LIBINT2_ENABLE_RKB_ERI3 >= 0. The 4-center coulomb_opop engine still -// constructs successfully without them (so the lmax_exceeded catch does not -// fire), but compute() on the 3-center braket would dispatch to the absent -// kernel and abort in the `default` task. Guard at compile time on the -// generated task-exists macro. -#if LIBINT2_TASK_EXISTS_3coulomb_opop +// LIBINT2_ENABLE_RKB_ERI3 >= 0 (else compute() throws Engine::missing_kernel). +// Guard at compile time on the generated task-exists macros. +#if LIBINT2_TASK_EXISTS_3coulomb_opop && LIBINT2_TASK_EXISTS_4coulomb_opop SECTION("Coulombσpσp 3-center xs_xx") { // 3-center RKB Coulombσpσp integral, (P | σ·p_μ σ·p_ν / r12) with P on // the bra (fitting/DF) and σ·p acting on the AO pair (μ,ν) on the ket. @@ -750,33 +775,29 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { std::vector dfs{dfsh}; const auto &unitshell = libint2::Shell::unit(); - Engine engine_3c, engine_4c_ref; - try { - engine_3c = Engine(Operator::coulomb_opop, + const auto obs = shells_upto( + std::min(LIBINT2_MAX_AM_3coulomb_opop, LIBINT2_MAX_AM_coulomb_opop)); + const auto max_nprim = libint2::max_nprim(obs); + const auto max_l = libint2::max_l(obs); + Engine engine_3c(Operator::coulomb_opop, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + engine_3c.set(BraKet::xs_xx); + // 4-center reference — same operator, default BraKet::xx_xx + Engine engine_4c_ref(Operator::coulomb_opop, std::max(max_nprim, libint2::max_nprim(dfs)), std::max(max_l, libint2::max_l(dfs)), 0); - engine_3c.set(BraKet::xs_xx); - // 4-center reference — same operator, default BraKet::xx_xx - engine_4c_ref = Engine(Operator::coulomb_opop, - std::max(max_nprim, libint2::max_nprim(dfs)), - std::max(max_l, libint2::max_l(dfs)), 0); - } catch (Engine::lmax_exceeded &) { - // skip if libint not configured with -DLIBINT2_ENABLE_RKB_ERI3 >= 0 - // (or the 4-center RKB ERI was disabled) - return; - } const auto nshell = obs.size(); for (int sa = 0; sa != nshell; ++sa) { for (int sb = 0; sb != nshell; ++sb) { - const auto &results_3c = - engine_3c.compute(dfsh, obs[sa], obs[sb]); + const auto &results_3c = engine_3c.compute(dfsh, obs[sa], obs[sb]); // 4-center reference: (dfsh, unit | sh_a, sh_b). σ·p still acts on // (C, D) = (sh_a, sh_b), matching the 3-center xs_xx mapping. const auto &results_4c_ref = engine_4c_ref.compute(dfsh, unitshell, obs[sa], obs[sb]); - assert(results_3c.size() == 4); - assert(results_4c_ref.size() == 4); + REQUIRE(results_3c.size() == 4); + REQUIRE(results_4c_ref.size() == 4); const auto n_df = dfsh.size(); const auto n_a = obs[sa].size(); @@ -797,10 +818,9 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { bool not_ok = rel_err > REL_TOL && abs_err > ABS_TOL; if (not_ok) { std::cout << "(df | sa=" << sa << " sb=" << sb - << ") comp=" << comp << " elem=" << k - << ": 3c=" << v_3c << " 4c_ref=" << v_ref - << " abs_err=" << abs_err << " rel_err=" << rel_err - << std::endl; + << ") comp=" << comp << " elem=" << k << ": 3c=" << v_3c + << " 4c_ref=" << v_ref << " abs_err=" << abs_err + << " rel_err=" << rel_err << std::endl; } REQUIRE(!not_ok); } @@ -818,19 +838,17 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { Shell dfsh{{1.5}, {{0, false, {1.0}}}, {{-1.0, 0.5, 0.0}}}; std::vector dfs{dfsh}; - Engine eng_xs, eng_xxs; - try { - eng_xs = Engine(Operator::coulomb_opop, - std::max(max_nprim, libint2::max_nprim(dfs)), - std::max(max_l, libint2::max_l(dfs)), 0); - eng_xs.set(BraKet::xs_xx); - eng_xxs = Engine(Operator::coulomb_opop, - std::max(max_nprim, libint2::max_nprim(dfs)), - std::max(max_l, libint2::max_l(dfs)), 0); - eng_xxs.set(BraKet::xx_xs); - } catch (Engine::lmax_exceeded &) { - return; - } + const auto obs = shells_upto(LIBINT2_MAX_AM_3coulomb_opop); + const auto max_nprim = libint2::max_nprim(obs); + const auto max_l = libint2::max_l(obs); + Engine eng_xs(Operator::coulomb_opop, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + eng_xs.set(BraKet::xs_xx); + Engine eng_xxs(Operator::coulomb_opop, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + eng_xxs.set(BraKet::xx_xs); for (size_t sa = 0; sa != obs.size(); ++sa) { for (size_t sb = 0; sb != obs.size(); ++sb) { @@ -847,22 +865,19 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { for (size_t i_df = 0; i_df < n_df; ++i_df) { for (size_t i_a = 0; i_a < n_a; ++i_a) { for (size_t i_b = 0; i_b < n_b; ++i_b) { - const auto v_xs = - res_xs[c][i_df * n_a * n_b + i_a * n_b + i_b]; + const auto v_xs = res_xs[c][i_df * n_a * n_b + i_a * n_b + i_b]; const auto v_xxs = res_xxs[c][i_a * n_b * n_df + i_b * n_df + i_df]; const auto abs_err = abs(v_xs - v_xxs); - const auto rel_err = std::abs(v_xs) > 1e-30 - ? double(abs(abs_err / v_xs)) - : 0.0; + const auto rel_err = + std::abs(v_xs) > 1e-30 ? double(abs(abs_err / v_xs)) : 0.0; bool not_ok = rel_err > 1.0E-9 && abs_err > 5.0E-14; if (not_ok) { - std::cout << "xx_xs alias mismatch: (sa=" << sa - << " sb=" << sb << ") comp=" << c - << " idx(df=" << i_df << ",a=" << i_a - << ",b=" << i_b << "): xs=" << v_xs - << " xxs=" << v_xxs << " abs_err=" << abs_err - << std::endl; + std::cout + << "xx_xs alias mismatch: (sa=" << sa << " sb=" << sb + << ") comp=" << c << " idx(df=" << i_df << ",a=" << i_a + << ",b=" << i_b << "): xs=" << v_xs << " xxs=" << v_xxs + << " abs_err=" << abs_err << std::endl; } REQUIRE(!not_ok); } @@ -872,7 +887,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { } } } -#endif // LIBINT2_TASK_EXISTS_3coulomb_opop +#endif // 3-center Coulombσpσp #if LIBINT2_TASK_EXISTS_3coulomb_op SECTION("Coulombσp 3-center xs_xx") { @@ -895,25 +910,29 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { Shell dfsh{{1.5}, {{0, false, {1.0}}}, {{-1.0, 0.5, 0.0}}}; std::vector dfs{dfsh}; - Engine engine_3c; - Engine engine_3c_default; - try { - engine_3c = Engine(Operator::coulomb_op, - std::max(max_nprim, libint2::max_nprim(dfs)), - std::max(max_l, libint2::max_l(dfs)), 0); - engine_3c.set(BraKet::xs_xx); - // Regression (review finding #1): coulomb_op has no 4-center kernel, so a - // forgotten set() must NOT silently dispatch to the plain-Coulomb - // `default` task. default_braket(coulomb_op) must be the 3-center xs_xx, - // so engine_3c_default — built WITHOUT an explicit set() — must produce - // results identical to engine_3c. - CHECK(default_braket(Operator::coulomb_op) == BraKet::xs_xx); - engine_3c_default = Engine(Operator::coulomb_op, - std::max(max_nprim, libint2::max_nprim(dfs)), - std::max(max_l, libint2::max_l(dfs)), 0); - } catch (Engine::lmax_exceeded &) { - // skip if libint not configured with -DLIBINT2_ENABLE_RKB_ERI3 >= 0 - return; + const auto obs = shells_upto(LIBINT2_MAX_AM_3coulomb_op); + const auto max_nprim = libint2::max_nprim(obs); + const auto max_l = libint2::max_l(obs); + Engine engine_3c(Operator::coulomb_op, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + engine_3c.set(BraKet::xs_xx); + // coulomb_op has no 4-center kernel, hence default_braket(coulomb_op) + // must be the 3-center xs_xx, so engine_3c_default — built WITHOUT an + // explicit set() — must produce results identical to engine_3c. + CHECK(default_braket(Operator::coulomb_op) == BraKet::xs_xx); + Engine engine_3c_default(Operator::coulomb_op, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + // no 4-center coulomb_op kernel -> must throw rather than silently + // computing plain Coulomb integrals + { + Engine engine_4c(Operator::coulomb_op, + std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0); + engine_4c.set(BraKet::xx_xx); + CHECK_THROWS_AS(engine_4c.compute(obs[0], obs[0], obs[0], obs[0]), + Engine::missing_kernel); } // unit (dummy) shell at the bra-2 position: alpha=0, l=0, at the origin. @@ -924,7 +943,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { for (int sa = 0; sa != nshell; ++sa) { for (int sb = 0; sb != nshell; ++sb) { const auto &results_3c = engine_3c.compute(dfsh, obs[sa], obs[sb]); - assert(results_3c.size() == 3); + REQUIRE(results_3c.size() == 3); // default-braket engine (no set()) must match the explicit-xs_xx engine const auto &results_3c_default = engine_3c_default.compute(dfsh, obs[sa], obs[sb]); @@ -938,7 +957,7 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { int ijk = 0; int l0, m0, n0; - FOR_CART(l0, m0, n0, dfsh.contr[0].l) // P (fitting, s) + FOR_CART(l0, m0, n0, dfsh.contr[0].l) // P (fitting, s) int l2, m2, n2; FOR_CART(l2, m2, n2, obs[sa].contr[0].l) // μ int l3, m3, n3; diff --git a/include/libint2.h b/include/libint2.h index 25a231be7..59d17ef65 100644 --- a/include/libint2.h +++ b/include/libint2.h @@ -22,19 +22,18 @@ #define _libint2_header_ #define LIBINT_T_SS_EREP_SS(mValue) \ - _aB_s____0__s____1___TwoPRep_s____0__s____1___Ab__up_##mValue + _aB_s___0__s___1___TwoPRep_s___0__s___1___Ab__up_##mValue #define LIBINT_T_SS_Km1G12_SS(mValue) \ - _aB_s____0__s____1___r12_minus_1_g12_s____0__s____1___Ab__up_##mValue + _aB_s___0__s___1___r12_minus_1_g12_s___0__s___1___Ab__up_##mValue #define LIBINT_T_SS_K0G12_SS_0 \ - _aB_s____0__s____1___r12_0_g12_s____0__s____1___Ab__up_0 + _aB_s___0__s___1___r12_0_g12_s___0__s___1___Ab__up_0 #define LIBINT_T_SS_K2G12_SS_0 \ - _aB_s____0__s____1___r12_2_g12_s____0__s____1___Ab__up_0 + _aB_s___0__s___1___r12_2_g12_s___0__s___1___Ab__up_0 #define LIBINT_T_SS_K4G12_SS_0 \ - _aB_s____0__s____1___r12_4_g12_s____0__s____1___Ab__up_0 -#define LIBINT_T_S_OVERLAP_S _aB_s____0___Overlap_s____0___Ab__up_ -#define LIBINT_T_S_KINETIC_S _aB_s____0___Kinetic_s____0___Ab__up_ -#define LIBINT_T_S_ELECPOT_S(mValue) \ - _aB_s____0___ElecPot_s____0___Ab__up_##mValue + _aB_s___0__s___1___r12_4_g12_s___0__s___1___Ab__up_0 +#define LIBINT_T_S_OVERLAP_S _aB_s___0___Overlap_s___0___Ab__up_ +#define LIBINT_T_S_KINETIC_S _aB_s___0___Kinetic_s___0___Ab__up_ +#define LIBINT_T_S_ELECPOT_S(mValue) _aB_s___0___ElecPot_s___0___Ab__up_##mValue #include #include diff --git a/include/libint2/engine.h b/include/libint2/engine.h index c0f268347..4f60d0645 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -207,29 +207,6 @@ enum class Operator { coulomb, /// alias for Operator::coulomb r12_m1 = coulomb, - /// (2-body) \f$ r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ where k1 & k2 are - /// centers of ket1 and ket2, respectively - coulomb_opop, - /// (2-body) \f$ r_{12}^{-1} (σ.p_{k2}) \f$ where k2 is the center of ket2; - /// the 3-center single-σ·p integral (P | μ, σ·p ν). σ·p acts on - /// the 2nd ket AO function only. Produces 3 Cartesian components (x=0, y=1, - /// z=2) — the SO(3) vector irrep; no σ·σ folding (cf. coulomb_opop). The - /// fitting function sits on the bra (BraKet::xs_xx); 3-center only. - coulomb_op, - /// (2-body) \f$ (σ.p_{b1})(σ.p_{b2}) r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ - /// where b1 & b2 are centers of bra1 and bra2 and k1 & k2 are centers of - /// ket1 and ket2, respectively - opop_coulomb_opop, - /// (2-body) \f$ (σ.p_{b2}) r_{12}^{-1} (σ.p_{k2}) \f$ where b2 is the center - /// of bra2 and k2 is the center of ket2; Gaunt LS "bilinear" integral. - /// Produces 9 components: the SO(3) irreducible decomposition of the 3×3 - /// derivative dyadic T_ab (a = bra-side ∇, b = ket-side ∇), NOT a raw - /// `3*a + b` Cartesian dyadic. Component order matches - /// `σpCoulombσp_Descr::Component`: Scalar trace (0); antisymmetric (curl-curl) - /// AntisymX,Y,Z (1–3); symmetric-traceless SymTLDiagA,SymTLDiagB (4,5) and - /// SymTLOffXY,SymTLOffXZ,SymTLOffYZ (6–8). Unlike coulomb_opop, these - /// components are not σ·σ-folded. - op_coulomb_op, /// contracted Gaussian geminal cgtg, /// contracted Gaussian geminal times Coulomb @@ -257,6 +234,29 @@ enum class Operator { stg_x_coulomb, /// alias for Operator::stg_x_coulomb yukawa = stg_x_coulomb, + /// (2-body) \f$ r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ where k1 & k2 are + /// centers of ket1 and ket2, respectively + coulomb_opop, + /// (2-body) \f$ r_{12}^{-1} (σ.p_{k2}) \f$ where k2 is the center of ket2; + /// the 3-center single-σ·p integral (P | μ, σ·p ν). σ·p acts on + /// the 2nd ket AO function only. Produces 3 Cartesian components (x=0, y=1, + /// z=2) — the SO(3) vector irrep; no σ·σ folding (cf. coulomb_opop). The + /// fitting function sits on the bra (BraKet::xs_xx); 3-center only. + coulomb_op, + /// (2-body) \f$ (σ.p_{b1})(σ.p_{b2}) r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ + /// where b1 & b2 are centers of bra1 and bra2 and k1 & k2 are centers of + /// ket1 and ket2, respectively + opop_coulomb_opop, + /// (2-body) \f$ (σ.p_{b2}) r_{12}^{-1} (σ.p_{k2}) \f$ where b2 is the center + /// of bra2 and k2 is the center of ket2; Gaunt LS "bilinear" integral. + /// Produces 9 components: the SO(3) irreducible decomposition of the 3×3 + /// derivative dyadic T_ab (a = bra-side ∇, b = ket-side ∇), NOT a raw + /// `3*a + b` Cartesian dyadic. Component order matches + /// `σpCoulombσp_Descr::Component`: Scalar trace (0); antisymmetric + /// (curl-curl) AntisymX,Y,Z (1-3); symmetric-traceless SymTLDiagA,SymTLDiagB + /// (4,5) and SymTLOffXY,SymTLOffXZ,SymTLOffYZ (6-8). Unlike coulomb_opop, + /// these components are not σ·σ-folded. + op_coulomb_op, // do not modify this invalid = -1, // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!keep this @@ -264,7 +264,7 @@ enum class Operator { first_1body_oper = overlap, last_1body_oper = opemuop, first_2body_oper = delta, - last_2body_oper = stg_x_coulomb, + last_2body_oper = op_coulomb_op, first_oper = first_1body_oper, last_oper = last_2body_oper }; @@ -493,8 +493,8 @@ struct operator_traits /// 9 components: the SO(3) irreducible decomposition of the 3×3 derivative /// dyadic T_ab (NOT a raw `3*a + b` Cartesian dyadic). Order matches /// `σpCoulombσp_Descr::Component`: Scalar trace (0); antisymmetric - /// AntisymX,Y,Z (1–3); symmetric-traceless SymTLDiagA,SymTLDiagB (4,5) and - /// SymTLOffXY,SymTLOffXZ,SymTLOffYZ (6–8). + /// AntisymX,Y,Z (1-3); symmetric-traceless SymTLDiagA,SymTLDiagB (4,5) and + /// SymTLOffXY,SymTLOffXZ,SymTLOffYZ (6-8). static constexpr auto nopers = 9; static constexpr auto intrinsic_deriv_order = 2; }; @@ -618,9 +618,10 @@ inline constexpr int rank(BraKet braket) { /// @throw std::logic_error if invalid @c oper given inline constexpr BraKet default_braket(Operator oper) { // coulomb_op (3-center single-σ·p) has no 4-center kernel; its only - // valid geometry is the 3-center xs_xx braket. Default to that rather than the - // rank-based xx_xx, which has no generated kernel and would silently dispatch - // to the plain-Coulomb `default` task (wrong integrals, no error in release). + // valid geometry is the 3-center xs_xx braket. Default to that rather than + // the rank-based xx_xx, which has no generated kernel and would silently + // dispatch to the plain-Coulomb `default` task (wrong integrals, no error in + // release). return (oper == Operator::coulomb_op) ? BraKet::xs_xx : ((rank(oper) == 1) @@ -632,6 +633,15 @@ inline constexpr BraKet default_braket(Operator oper) { "given"))); } +namespace detail { +/// @return true if @c oper is one of the 2-body relativistic (restricted +/// kinetic balance, RKB) operators +inline constexpr bool is_rkb_2body(Operator oper) { + return oper == Operator::coulomb_opop || oper == Operator::coulomb_op || + oper == Operator::opop_coulomb_opop || oper == Operator::op_coulomb_op; +} +} // namespace detail + constexpr size_t nopers_2body = static_cast(Operator::last_2body_oper) - static_cast(Operator::first_2body_oper) + 1; @@ -1119,6 +1129,19 @@ class Engine { size_t lmax_requested_; }; + /// Exception class to be used when the library does not contain the kernels + /// for the operator/braket/derivative order this Engine is set up for (e.g. + /// the corresponding integral class was not enabled when configuring the + /// library) + class missing_kernel : virtual public std::logic_error { + public: + missing_kernel() + : std::logic_error( + "Engine::missing_kernel -- this library was not configured to " + "compute integrals of this operator/braket/derivative order") {} + ~missing_kernel() noexcept {} + }; + /// Exception class to be used when using default-initialized Engine class using_default_initialized : virtual public std::logic_error { public: @@ -1182,7 +1205,8 @@ class Engine { // scratch2_ points to second such block. It could point into scratch_ or at // primdata_[0].stack typedef void (*buildfnptr_t)(const Libint_t*); - buildfnptr_t* buildfnptrs_; + /// null if the library has no kernels for this operator/braket/deriv order + buildfnptr_t* buildfnptrs_ = nullptr; /// reports the number of shell sets that each call to compute() produces. unsigned int compute_nshellsets() const { diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 71fc572d3..cea5d3c9a 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -70,37 +70,37 @@ typename std::remove_all_extents::type* to_ptr1(T (&a)[N]) { /// These MUST appear in the same order as in Operator. /// You must also update BOOST_PP_NBODY_OPERATOR_LAST_ONEBODY_INDEX when you add /// one-body ints -#define BOOST_PP_NBODY_OPERATOR_LIST \ - (overlap, /* overlap */ \ - (kinetic, /* kinetic */ \ - (elecpot, /* nuclear */ \ - (elecpot, /* erf_nuclear */ \ - (elecpot, /* erfc_nuclear */ \ - (elecpot, /* erfx_nuclear */ \ - (elecpot, /* q_gau */ \ - (1emultipole, /* emultipole1 */ \ - (2emultipole, /* emultipole2 */ \ - (3emultipole, /* emultipole3 */ \ - (sphemultipole, /* sphemultipole */ \ - (opVop, /* opVop */ \ - (opVop, /* op_q_gau_op */ \ - (opemuop, /* opemuop */ \ - (eri, /* delta */ \ - (eri, /* coulomb */ \ - (coulomb_opop, /* coulomb_opop */ \ - (coulomb_op, /* coulomb_op */ \ - (opop_coulomb_opop, /* opop_coulomb_opop */ \ - (op_coulomb_op, /* op_coulomb_op */ \ - (eri, /* cgtg */ \ - (eri, /* cgtg_x_coulomb */ \ - (eri, /* delcgtg2 */ \ - (eri, /* r12 */ \ - (eri, /* erf_coulomb */ \ - (eri, /* erfc_coulomb */ \ - (eri, /* erfx_coulomb */ \ - (eri, /* stg */ \ - (eri, /* yukawa */ \ - BOOST_PP_NIL))))))))))))))))))))))))))))) +#define BOOST_PP_NBODY_OPERATOR_LIST \ + (overlap, /* overlap */ \ + (kinetic, /* kinetic */ \ + (elecpot, /* nuclear */ \ + (elecpot, /* erf_nuclear */ \ + (elecpot, /* erfc_nuclear */ \ + (elecpot, /* erfx_nuclear */ \ + (elecpot, /* q_gau */ \ + (1emultipole, /* emultipole1 */ \ + (2emultipole, /* emultipole2 */ \ + (3emultipole, /* emultipole3 */ \ + (sphemultipole, /* sphemultipole */ \ + (opVop, /* opVop */ \ + (opVop, /* op_q_gau_op */ \ + (opemuop, /* opemuop */ \ + (eri, /* delta */ \ + (eri, /* coulomb */ \ + (eri, /* cgtg */ \ + (eri, /* cgtg_x_coulomb */ \ + (eri, /* delcgtg2 */ \ + (eri, /* r12 */ \ + (eri, /* erf_coulomb */ \ + (eri, /* erfc_coulomb */ \ + (eri, /* erfx_coulomb */ \ + (eri, /* stg */ \ + (eri, /* yukawa */ \ + (coulomb_opop, /* coulomb_opop */ \ + (coulomb_op, /* coulomb_op */ \ + (opop_coulomb_opop, /* opop_coulomb_opop */ \ + (op_coulomb_op, /* op_coulomb_op */ \ + BOOST_PP_NIL))))))))))))))))))))))))))))) #define BOOST_PP_NBODY_OPERATOR_INDEX_TUPLE \ BOOST_PP_MAKE_TUPLE(BOOST_PP_LIST_SIZE(BOOST_PP_NBODY_OPERATOR_LIST)) @@ -206,6 +206,8 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute( /// \note resulting shell sets are stored in row-major order __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute1( const libint2::Shell& s1, const libint2::Shell& s2) { + if (buildfnptrs_ == nullptr) throw missing_kernel(); + // can only handle 1 contraction at a time assert((s1.ncontr() == 1 && s2.ncontr() == 1) && "generally-contracted shells not yet supported"); @@ -632,6 +634,18 @@ __libint2_engine_inline void Engine::_initialize() { if (static_cast(oper_) == BOOST_PP_TUPLE_ELEM(3, 0, product) && \ static_cast(rank(braket_)) == BOOST_PP_TUPLE_ELEM(3, 1, product) && \ deriv_order_ == BOOST_PP_TUPLE_ELEM(3, 2, product)) { \ + buildfnptrs_ = to_ptr1(LIBINT2_PREFIXED_NAME(BOOST_PP_CAT( \ + libint2_build_, BOOST_PP_NBODYENGINE_MCR3_TASK(product)))); \ + /* missing tasks resolve to the placeholder `default` task, i.e. there */ \ + /* are no kernels for this task; compute*() will throw missing_kernel */ \ + if (buildfnptrs_ == \ + to_ptr1(LIBINT2_PREFIXED_NAME(libint2_build_default))) { \ + if (stack_size_ > 0) \ + LIBINT2_PREFIXED_NAME(libint2_cleanup_default)(&primdata_[0]); \ + stack_size_ = 0; \ + buildfnptrs_ = nullptr; \ + return; \ + } \ hard_lmax_ = BOOST_PP_CAT(LIBINT2_MAX_AM_, \ BOOST_PP_NBODYENGINE_MCR3_TASK(product)) + \ 1; \ @@ -662,8 +676,6 @@ __libint2_engine_inline void Engine::_initialize() { LIBINT2_PREFIXED_NAME(libint2_init_flopcounter)( \ &primdata_[0], primdata_.size()), \ BOOST_PP_EMPTY()); \ - buildfnptrs_ = to_ptr1(LIBINT2_PREFIXED_NAME(BOOST_PP_CAT( \ - libint2_build_, BOOST_PP_NBODYENGINE_MCR3_TASK(product)))); \ reset_scratch(); \ return; \ } @@ -680,30 +692,16 @@ __libint2_engine_inline void Engine::_initialize() { __libint2_engine_inline void Engine::initialize(size_t max_nprim) { assert(libint2::initialized() && "libint is not initialized"); + if (detail::is_rkb_2body(oper_) && deriv_order_ > 0) + throw std::invalid_argument( + "Engine::initialize -- geometric derivatives of RKB integrals are not " + "yet supported"); assert(deriv_order_ <= LIBINT2_MAX_DERIV_ORDER && "exceeded the max derivative order of the library"); - // validate braket -#ifndef LIBINT_INCLUDE_ONEBODY - assert(braket_ != BraKet::x_x && - "this braket type not supported by the library; configure with " - "-DLIBINT2_ENABLE_ONEBODY >= 0"); -#endif -#ifndef LIBINT_INCLUDE_ERI - assert(braket_ != BraKet::xx_xx && - "this braket type not supported by the library; configure with " - "-DLIBINT2_ENABLE_ERI >= 0"); -#endif -#ifndef LIBINT_INCLUDE_ERI3 - assert((braket_ != BraKet::xs_xx && braket_ != BraKet::xx_xs) && - "this braket type not supported by the library; configure with " - "-DLIBINT2_ENABLE_ERI3 >= 0"); -#endif -#ifndef LIBINT_INCLUDE_ERI2 - assert(braket_ != BraKet::xs_xs && - "this braket type not supported by the library; configure with " - "-DLIBINT2_ENABLE_ERI2 >= 0"); -#endif + // N.B. whether the library supports this operator/braket/deriv order + // is determined by _initialize(); if it does not, compute*() throw + // missing_kernel // make sure it's no default initialized if (lmax_ < 0) throw using_default_initialized(); @@ -1262,6 +1260,7 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( (tspbra != nullptr && tspket != nullptr)) && "Engine::compute2 -- expects zero or two ShellPair objects"); assert(screening_method_ != ScreeningMethod::Invalid); + if (buildfnptrs_ == nullptr) throw missing_kernel(); // // i.e. bra and ket refer to chemists bra and ket @@ -1344,51 +1343,14 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( // N.B. cannot swap bra and ket for coulomb_opop since the ket is mutated by // this operator #else // orca angular momentum ordering - bool swap_braket; - bool swap_tbra, swap_tket; - if (oper_ == Operator::opop_coulomb_opop) { - // ORCA canonical for σpσpCoulombσpσp: lc <= ld (or la <= lb when lc == ld), - // la+lb >= lc+ld (or min(la,lb) >= lc when sums equal) - const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; - const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; - const auto bra_min = std::min(tbra1.contr[0].l, tbra2.contr[0].l); - const auto ket_min = std::min(tket1.contr[0].l, tket2.contr[0].l); - swap_braket = ((braket_ == BraKet::xx_xx) && - (bra_total < ket_total || - (bra_total == ket_total && bra_min < ket_min))) || - braket_ == BraKet::xx_xs; - if (swap_braket) { - const bool swap_p1p2 = (tbra1.contr[0].l > tbra2.contr[0].l); - swap_tbra = swap_tket = swap_p1p2; - } else { - const bool swap_p1p2 = (tket1.contr[0].l > tket2.contr[0].l); - swap_tbra = swap_tket = swap_p1p2; - } - } else if (oper_ == Operator::op_coulomb_op) { - // σpCoulombσp: only bra↔ket swap is a symmetry (with (a,b)↔(b,a) remap). - // ORCA canonical form: la+lb >= lc+ld only. - const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; - const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; - swap_braket = ((braket_ == BraKet::xx_xx) && (bra_total < ket_total)) || - braket_ == BraKet::xx_xs; - swap_tbra = false; - swap_tket = false; - } else if (oper_ == Operator::coulomb_op) { - // Coulombσp (3-center single-σ·p): see the STANDARD-ordering branch above. - // Within-ket swap is NOT a symmetry (σ·p attaches to ν); bra↔ket leaves - // the 3 Cartesian components unchanged. Only xx_xs needs a braket swap. - swap_braket = (braket_ == BraKet::xx_xs); - swap_tbra = false; - swap_tket = false; - } else { - swap_tbra = (tbra1.contr[0].l > tbra2.contr[0].l); - swap_tket = (tket1.contr[0].l > tket2.contr[0].l); - swap_braket = ((braket_ == BraKet::xx_xx) && - (tbra1.contr[0].l + tbra2.contr[0].l < - tket1.contr[0].l + tket2.contr[0].l) && - (oper_ != Operator::coulomb_opop)) || - braket_ == BraKet::xx_xs; - } + const auto swap_tbra = (tbra1.contr[0].l > tbra2.contr[0].l); + const auto swap_tket = (tket1.contr[0].l > tket2.contr[0].l); + const auto swap_braket = + ((braket_ == BraKet::xx_xx) && (tbra1.contr[0].l + tbra2.contr[0].l < + tket1.contr[0].l + tket2.contr[0].l)) || + braket_ == BraKet::xx_xs; + assert(false && "feature not implemented"); + abort(); #endif const auto& bra1 = swap_braket ? (swap_tket ? tket2 : tket1) : (swap_tbra ? tbra2 : tbra1); @@ -2053,20 +2015,17 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( // xs_xx case to compute buildfnidx from those swapped shells. [[fallthrough]]; case BraKet::xs_xx: { - // The ket (paired/AO) centers may have a lower max AM than the bra - // (fitting) center for *center-dependent* 3-center tasks — e.g. 3eri, - // whose fitting center goes up to ERI3_MAX_AM while the paired centers - // only reach MAX_AM, giving a non-cubic build table. _initialize() sets - // hard_default_lmax_ to that paired max (+1) for center-dependent - // tasks, or to INT_MAX for tasks that are NOT center-dependent (e.g. - // the RKB 3-center 3coulomb_opop / 3coulomb_op, whose build table is - // the symmetric [hard_lmax_]^3). Taking the min picks the correct ket - // stride in both cases and matches the per-task build-table dimensions. - // (Previously this hard-coded LIBINT2_CENTER_DEPENDENT_MAX_AM_3eri, - // which set ket_lmax = hard_default_lmax_ = INT_MAX for the non-center- - // dependent RKB operators, producing an out-of-bounds buildfnidx and a - // segfault on the first l(ket1) > 0 shell triplet.) - const int ket_lmax = std::min(hard_lmax_, hard_default_lmax_); + // The ket (paired/AO) centers may have a different max AM than the + // bra (fitting) center for *center-dependent* 3-center tasks (e.g. + // 3eri, or 3coulomb_opop with LIBINT2_RKB_ERI3_MAX_AM != + // LIBINT2_MAX_AM), giving a non-cubic build table. _initialize() sets + // hard_default_lmax_ to the paired max (+1) for center-dependent + // tasks, or to INT_MAX for tasks that are not, whose build table is + // [hard_lmax_]^3. + const int ket_lmax = + hard_default_lmax_ != std::numeric_limits::max() + ? hard_default_lmax_ + : hard_lmax_; assert(bra1.contr[0].l <= hard_lmax_ && "the angular momentum limit is exceeded"); assert(ket1.contr[0].l <= ket_lmax && @@ -2260,6 +2219,8 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( Eigen::Stride( nr2_tgt * ncol_tgt, ncol_tgt)); // Coupled swap sign correction for multi-component operators. + // N.B. s indexes operator components only because RKB + // integrals have no geometric derivatives (see initialize()). // TODO: the per-component swap phase and the component remap // below are hardcoded per operator (op_coulomb_op, // opop_coulomb_opop, ...). These belong with the operator @@ -2273,14 +2234,15 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( if (bra_is_spin != ket_is_spin) oper_cart_component_phase = -1.0; } - if (swap_tket && oper_ == Operator::coulomb_opop && s > 0) - oper_cart_component_phase = -1.0; - // For coulomb_opop in BraKet::xx_xs (the 3-center alias of - // xs_xx): after swap_braket the user's bra (AO pair) becomes - // the kernel's ket where σ·p attaches. If swap_tbra is also - // applied to canonicalize that AO pair, σ·p_a σ·p_b ↔ - // σ·p_b σ·p_a — antisymmetric, so x,y,z components (s>0) - // pick up a -1 sign. (The scalar component s=0 is invariant.) + // coulomb_opop is bra<->ket swapped only in BraKet::xx_xs + // (the 3-center alias of xs_xx), where tket2 is the unit shell, + // hence swap_tket is false. After swap_braket the user's bra + // (AO pair) becomes the kernel's ket where σ·p attaches. If + // swap_tbra is also applied to canonicalize that AO pair, + // σ·p_a σ·p_b ↔ σ·p_b σ·p_a — antisymmetric, so x,y,z + // components (s>0) pick up a -1 sign. (The scalar component + // s=0 is invariant.) + assert(!(swap_tket && oper_ == Operator::coulomb_opop)); if (swap_tbra && oper_ == Operator::coulomb_opop && s > 0) oper_cart_component_phase = -1.0; // op_coulomb_op irrep layout under bra↔ket swap: antisym @@ -2349,6 +2311,8 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( // component (α,β) → (β,α). With Option A ordering (index=4*bra+ket), // this is a matrix transpose: s_new = 4*(s%4) + (s/4). if (permute && oper_ == Operator::opop_coulomb_opop && swap_braket) { + // N.B. assumes no geometric derivatives (see initialize()) + assert(ntargets == 16); std::array temp; for (auto s = 0; s != ntargets; ++s) temp[s] = targets_[s]; for (auto s = 0; s != ntargets; ++s) diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 1f4e5a564..69b2ace15 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -623,6 +623,10 @@ void try_main(int argc, char* argv[]) { } #endif +#if (defined(LIBINT_INCLUDE_RKB_ERI) && LIBINT_INCLUDE_RKB_ERI > 0) || \ + (defined(LIBINT_INCLUDE_RKB_ERI3) && LIBINT_INCLUDE_RKB_ERI3 > 0) +#error "geometric derivatives of RKB integrals are not yet implemented" +#endif #ifdef LIBINT_INCLUDE_RKB_ERI #define BOOST_PP_RKB_ERI_TASK_TUPLE \ (coulomb_opop, opop_coulomb_opop, op_coulomb_op) @@ -821,53 +825,53 @@ void try_main(int argc, char* argv[]) { // - num_bf = 3 (3-center dispatch table is 3-dimensional) for (unsigned int d = 0; d <= LIBINT_INCLUDE_RKB_ERI3; ++d) { #if defined(LIBINT_RKB_ERI3_MAX_AM_LIST) -#define BOOST_PP_RKB_ERI3_MCR_MAXAM(r, data, elem) \ - cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ - token(LIBINT_RKB_ERI3_MAX_AM_LIST, \ - ',', d)); +#define BOOST_PP_RKB_ERI3_MCR_MAXAM(r, data, elem) \ + cparams->max_am( \ + task_label(BOOST_PP_STRINGIZE(elem), d), \ + token(LIBINT_RKB_ERI3_MAX_AM_LIST, ',', d)); BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_MAXAM, _, BOOST_PP_RKB_ERI3_TASK_LIST) #undef BOOST_PP_RKB_ERI3_MCR_MAXAM #elif defined(LIBINT_RKB_ERI3_MAX_AM) #define BOOST_PP_RKB_ERI3_MCR_MAXAM(r, data, elem) \ cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ - LIBINT_RKB_ERI3_MAX_AM); + LIBINT_RKB_ERI3_MAX_AM); BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_MAXAM, _, BOOST_PP_RKB_ERI3_TASK_LIST) #undef BOOST_PP_RKB_ERI3_MCR_MAXAM #endif #if defined(LIBINT_RKB_ERI3_OPT_AM_LIST) -#define BOOST_PP_RKB_ERI3_MCR_OPTAM(r, data, elem) \ - cparams->max_am_opt(task_label(BOOST_PP_STRINGIZE(elem), d), \ - token(LIBINT_RKB_ERI3_OPT_AM_LIST, \ - ',', d)); +#define BOOST_PP_RKB_ERI3_MCR_OPTAM(r, data, elem) \ + cparams->max_am_opt( \ + task_label(BOOST_PP_STRINGIZE(elem), d), \ + token(LIBINT_RKB_ERI3_OPT_AM_LIST, ',', d)); BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_OPTAM, _, BOOST_PP_RKB_ERI3_TASK_LIST) #undef BOOST_PP_RKB_ERI3_MCR_OPTAM #elif defined(LIBINT_RKB_ERI3_OPT_AM) #define BOOST_PP_RKB_ERI3_MCR_OPTAM(r, data, elem) \ cparams->max_am_opt(task_label(BOOST_PP_STRINGIZE(elem), d), \ - LIBINT_RKB_ERI3_OPT_AM); + LIBINT_RKB_ERI3_OPT_AM); BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_OPTAM, _, BOOST_PP_RKB_ERI3_TASK_LIST) #undef BOOST_PP_RKB_ERI3_MCR_OPTAM #endif // Paired centers (1, 2) follow the default basis AM. #if defined(LIBINT_MAX_AM_LIST) -#define BOOST_PP_RKB_ERI3_MCR_PAIRED(r, data, elem) \ - cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ - cparams->max_am(task_label("default", d)), 1); \ - cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ - cparams->max_am(task_label("default", d)), 2); +#define BOOST_PP_RKB_ERI3_MCR_PAIRED(r, data, elem) \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + cparams->max_am(task_label("default", d)), 1); \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + cparams->max_am(task_label("default", d)), 2); BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_PAIRED, _, BOOST_PP_RKB_ERI3_TASK_LIST) #undef BOOST_PP_RKB_ERI3_MCR_PAIRED #else -#define BOOST_PP_RKB_ERI3_MCR_PAIRED(r, data, elem) \ - cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ - cparams->max_am("default"), 1); \ - cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ - cparams->max_am("default"), 2); +#define BOOST_PP_RKB_ERI3_MCR_PAIRED(r, data, elem) \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + cparams->max_am("default"), 1); \ + cparams->max_am(task_label(BOOST_PP_STRINGIZE(elem), d), \ + cparams->max_am("default"), 2); BOOST_PP_LIST_FOR_EACH(BOOST_PP_RKB_ERI3_MCR_PAIRED, _, BOOST_PP_RKB_ERI3_TASK_LIST) #undef BOOST_PP_RKB_ERI3_MCR_PAIRED @@ -1286,11 +1290,11 @@ static void build_TwoPRep_2b_2k( // specific function per side, and IBP cannot recover the sign across the // 1/r12 coupling). // TODO: the per-operator swappability/component-count rules are spread across - // many std::is_same branches here (and the descriptor loops and - // strategy specializations). These are properties of the operator descriptor; - // expose them as descriptor traits (e.g. ncomponents(), p1p2_swappable(), - // bra_ket_coswappable()) and query generically so a new operator needs no edit - // here. + // many std::is_same branches here (and the descriptor loops + // and strategy specializations). These are properties of the operator + // descriptor; expose them as descriptor traits (e.g. ncomponents(), + // p1p2_swappable(), bra_ket_coswappable()) and query generically so a new + // operator needs no edit here. bool p1_p2_swappable = !std::is_same::value; bool bra_ket_coswappable = std::is_same::value; @@ -1604,7 +1608,8 @@ static void build_TwoPRep_1b_2k( #endif // operator components: 1 for plain TwoPRep, 4 for RKB CoulombσpσpOper - // (σ·p on both AO ket fns), 3 for RKB CoulombσpOper (single σ·p → x,y,z) + // (σ·p on both AO ket fns), 3 for RKB CoulombσpOper (single σ·p → + // x,y,z) std::vector descrs(1); if constexpr (std::is_same::value) { descrs.resize(0); @@ -2484,9 +2489,9 @@ void build_G12DKH_2b_2k(std::ostream& os, label = os.str(); } std::string prefix(cparams->source_directory()); - std::string decl_filename(prefix + context->label_to_name(label)); + std::string decl_filename(prefix + context->label_to_filename(label)); decl_filename += ".h"; - std::string src_filename(prefix + context->label_to_name(label)); + std::string src_filename(prefix + context->label_to_filename(label)); src_filename += ".cc"; std::basic_ofstream declfile(decl_filename.c_str()); std::basic_ofstream srcfile(src_filename.c_str()); @@ -2616,7 +2621,7 @@ void config_to_api(const std::shared_ptr& cparams, { // 2-body ints -#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ +#define BOOST_PP_TWOBODY_TASKOPER_TUPLE \ ("eri", "coulomb_opop", "coulomb_op", "opop_coulomb_opop", "op_coulomb_op", \ "r12kg12", "r12_0_g12", "r12_2_g12", "g12_T1_g12", "g12dkh") #define BOOST_PP_TWOBODY_TASKOPER_LIST \ diff --git a/src/bin/libint/buildtest.cc b/src/bin/libint/buildtest.cc index 604a3869f..1555d5fdc 100644 --- a/src/bin/libint/buildtest.cc +++ b/src/bin/libint/buildtest.cc @@ -73,9 +73,9 @@ void generate_rr_code(std::ostream& os, os << "generating code for " << context->label_to_name(rr->label()) << " target=" << rr->rr_target()->label() << endl; - std::string decl_filename(prefix + context->label_to_name(rr->label())); + std::string decl_filename(prefix + context->label_to_filename(rr->label())); decl_filename += ".h"; - std::string def_filename(prefix + context->label_to_name(rr->label())); + std::string def_filename(prefix + context->label_to_filename(rr->label())); def_filename += ".cc"; std::basic_ofstream declfile(decl_filename.c_str()); std::basic_ofstream deffile(def_filename.c_str()); diff --git a/src/bin/libint/buildtest.h b/src/bin/libint/buildtest.h index 0ea5b571e..f63aed489 100644 --- a/src/bin/libint/buildtest.h +++ b/src/bin/libint/buildtest.h @@ -321,9 +321,9 @@ void GenerateCode(const std::shared_ptr& dg, } #endif - std::string decl_filename(prefix + context->label_to_name(label)); + std::string decl_filename(prefix + context->label_to_filename(label)); decl_filename += ".h"; - std::string def_filename(prefix + context->label_to_name(label)); + std::string def_filename(prefix + context->label_to_filename(label)); def_filename += ".cc"; std::basic_ofstream declfile(decl_filename.c_str()); std::basic_ofstream deffile(def_filename.c_str()); diff --git "a/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" "b/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" index c6aa9f80a..ef5bf52b5 100644 --- "a/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" +++ "b/src/bin/libint/comp_1_\317\203pe\316\274\317\203p_1.h" @@ -18,8 +18,8 @@ * */ -#ifndef LIBINT_COMP_1_ΣPRΣP_1_H -#define LIBINT_COMP_1_ΣPRΣP_1_H +#ifndef LIBINT_COMP_1_ΣPEΜΣP_1_H +#define LIBINT_COMP_1_ΣPEΜΣP_1_H #include #include @@ -70,7 +70,7 @@ class CR_1_σpeμσp_1 template CR_1_σpeμσp_1::CR_1_σpeμσp_1(const std::shared_ptr &Tint, - unsigned int) + unsigned int) : ParentType(Tint, 0) { assert(Tint->num_func_bra(/* particle */ 0) == 1); assert(Tint->num_func_ket(/* particle */ 0) == 1); @@ -94,14 +94,13 @@ CR_1_σpeμσp_1::CR_1_σpeμσp_1(const std::shared_ptr &Tint, // σ·p r_k σ·p fold: inner child is the Cartesian-dipole integral // (Da | r_k | Db). Shared structure lives in sigma_p_1body_fold(). - sigma_p_1body_fold( - a, b, oper->descr().quaternion_index(), is_simple(), expr_, nflops_, - [&](const F &Da, const F &Db) { - return factory.make_child(Da, Db, EmptySet(), mu_k); - }); + sigma_p_1body_fold(a, b, oper->descr().quaternion_index(), is_simple(), expr_, + nflops_, [&](const F &Da, const F &Db) { + return factory.make_child(Da, Db, EmptySet(), mu_k); + }); } // CR_1_σpeμσp_1::CR_1_σpeμσp_1 }; // namespace libint2 -#endif // LIBINT_COMP_1_ΣPRΣP_1_H +#endif // LIBINT_COMP_1_ΣPEΜΣP_1_H diff --git a/src/bin/libint/comp_deriv_gauss_v2.h b/src/bin/libint/comp_deriv_gauss_v2.h index 970bab0ae..c3a8435d1 100644 --- a/src/bin/libint/comp_deriv_gauss_v2.h +++ b/src/bin/libint/comp_deriv_gauss_v2.h @@ -480,10 +480,6 @@ DerivGaussV2:: } } - // Use TaskParameters to keep track of maximum ranks - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - taskmgr.current().params()->max_hrr_hsrank(hsr); - if (expl_high_dim()) os << "," << hsr; if (expl_low_dim()) os << "," << lsr; os << ")" << context->end_of_stat() << std::endl; diff --git a/src/bin/libint/context.cc b/src/bin/libint/context.cc index 0d91728ca..7f21ba604 100644 --- a/src/bin/libint/context.cc +++ b/src/bin/libint/context.cc @@ -22,8 +22,12 @@ #include #include +#include #include +#include #include +#include +#include using namespace std; using namespace libint2; @@ -103,6 +107,22 @@ std::string CodeContext::label_to_function_name( return cparams()->api_prefix() + label_to_name(label_to_funcname(label)); } +std::string CodeContext::label_to_filename(const std::string& label) const { + const std::string name = label_to_name(label); + std::string folded_name = name; + std::transform(folded_name.begin(), folded_name.end(), folded_name.begin(), + [](unsigned char c) { return std::tolower(c); }); + // folded name -> distinct names seen so far; index in vector = suffix + static std::map> registry; + auto& names = registry[folded_name]; + auto it = std::find(names.begin(), names.end(), name); + const auto idx = std::distance(names.begin(), it); + if (it == names.end()) names.push_back(name); + // '-' cannot appear in label_to_name() output, so suffixed names cannot + // collide with unsuffixed ones + return idx == 0 ? name : name + "-" + std::to_string(idx); +} + std::string CodeContext::replace_chars(const std::string& S, const std::string& From, const std::string& To) { diff --git a/src/bin/libint/context.h b/src/bin/libint/context.h index d1c48bef9..45b115e27 100644 --- a/src/bin/libint/context.h +++ b/src/bin/libint/context.h @@ -63,6 +63,13 @@ class CodeContext { /// label_to_function_name(label) converts label to a valid function name /// with the API prefix prepended std::string label_to_function_name(const std::string& label) const; + /// label_to_filename(label) converts label to a base name (no extension) for + /// the generated source/header files. Same as label_to_name(label) unless + /// that name differs only in letter case from a name already handed out + /// (e.g. contracted vs primitive shells, \c P vs \c p), in which case a + /// suffix is appended so that the files do not clash on case-insensitive + /// filesystems (macOS, Windows). The mapping is process-wide and stable. + std::string label_to_filename(const std::string& label) const; /** declare returns a statement which declares variable named 'name' of type 'type' */ diff --git a/src/bin/libint/dg.cc b/src/bin/libint/dg.cc index 9e03277ae..4587ec719 100644 --- a/src/bin/libint/dg.cc +++ b/src/bin/libint/dg.cc @@ -1101,7 +1101,7 @@ void DirectedGraph::generate_code( for (FuncNameContainer::const_iterator fn = func_names_.begin(); fn != func_names_.end(); fn++) { string function_name = (*fn).first; - def << "#include <" << context->label_to_name(function_name) << ".h>" + def << "#include <" << context->label_to_filename(function_name) << ".h>" << endl; } def << endl; diff --git a/src/bin/libint/gauss.cc b/src/bin/libint/gauss.cc index f561d8ee5..3899283ab 100644 --- a/src/bin/libint/gauss.cc +++ b/src/bin/libint/gauss.cc @@ -115,19 +115,8 @@ std::string CGF::label() const { unsigned int am = qn_[0] + qn_[1] + qn_[2]; std::string deriv_label; if (!deriv_.zero()) deriv_label = deriv_.label(); - std::string am_string = am_to_symbol(am, contracted()); + const std::string am_string = am_to_symbol(am, contracted()); std::ostringstream oss; - - // Some OSs can have case-insensitive filesystem e.g., MacOS. So here we add - // additional identifier for primitive function labels. - // TODO: this underscore leaks into the C identifiers derived from label() - // (struct members, macros, symbols), not just the generated filenames it is - // meant to disambiguate. Prefer a case-injective funcname->filename mapping so - // label() stays identifier-clean and external consumers don't depend on it. - if (!this->contracted()) { - am_string += "_"; - } - oss << (pure_sh_ && am > 0 ? "W" : "") << am_string << deriv_label << "_"; if (am == 0) return oss.str(); @@ -234,20 +223,8 @@ CGShell::~CGShell() {} std::string CGShell::label() const { if (is_unit()) return "unit"; - std::string am_symbol = am_to_symbol(qn_[0], contracted()); - - // Some OSs can have case-insensitive filesystem e.g., MacOS. So here we add - // additional identifier for primitive shell labels. - // TODO: this underscore leaks into the C identifiers derived from label() - // (struct members, macros, symbols), not just the generated filenames it is - // meant to disambiguate. Prefer a case-injective funcname->filename mapping so - // label() stays identifier-clean and external consumers don't depend on it. - if (!this->contracted()) { - am_symbol += "_"; - } - - std::string result = - std::string(pure_sh_ && qn_[0] > 0 ? "W" : "") + am_symbol; + std::string result = std::string(pure_sh_ && qn_[0] > 0 ? "W" : "") + + am_to_symbol(qn_[0], contracted()); if (!deriv_.zero()) result += deriv_.label(); return result; } diff --git a/src/bin/libint/master_rrs_list.h b/src/bin/libint/master_rrs_list.h index 368ce4c1b..be345f285 100644 --- a/src/bin/libint/master_rrs_list.h +++ b/src/bin/libint/master_rrs_list.h @@ -25,12 +25,12 @@ #include #include #include -#include #include #include +#include #include -#include #include +#include #include #include #include @@ -250,31 +250,6 @@ typedef ITR_11_TwoPRep_11 typedef ITR_11_TwoPRep_11 ITR_d_11_TwoPRep_11_int; -typedef CR_DerivGauss - Deriv_a_11_TwoPRep_11_sh; -typedef CR_DerivGauss - Deriv_b_11_TwoPRep_11_sh; -typedef CR_DerivGauss - Deriv_c_11_TwoPRep_11_sh; -typedef CR_DerivGauss - Deriv_d_11_TwoPRep_11_sh; -typedef CR_DerivGauss - Deriv_a_11_TwoPRep_11_int; -typedef CR_DerivGauss - Deriv_b_11_TwoPRep_11_int; -typedef CR_DerivGauss - Deriv_c_11_TwoPRep_11_int; -typedef CR_DerivGauss - Deriv_d_11_TwoPRep_11_int; - // DerivGaussV2 for TwoPRep (shell sets) typedef DerivGaussV2 diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index 609f61ea6..73c7963c0 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -441,7 +441,8 @@ typedef GenOper TwoPRep; /** Coulombσpσp is the two-body repulsion operator. */ struct Coulombσpσp_Descr : public Contractable { - typedef MultiplicativeSymm2Body_Props Properties; + // σ·p acts on particle 2 only, hence no particle 1<->2 symmetry + typedef MultiplicativeNonsymm2Body_Props Properties; Coulombσpσp_Descr() : quaternion_index_(0) {} Coulombσpσp_Descr(int quaternion_index) @@ -486,7 +487,8 @@ typedef GenOper CoulombσpσpOper; * the imaginary unit and Pauli matrix of σ·p are applied by the caller. */ struct Coulombσp_Descr : public Contractable { - typedef MultiplicativeSymm2Body_Props Properties; + // σ·p acts on particle 2 only, hence no particle 1<->2 symmetry + typedef MultiplicativeNonsymm2Body_Props Properties; Coulombσp_Descr() : cartesian_index_(0) {} Coulombσp_Descr(int cartesian_index) : cartesian_index_(cartesian_index) { @@ -519,6 +521,7 @@ struct Coulombσp_Descr : public Contractable { typedef GenOper CoulombσpOper; struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr> { + // particle 1<->2 symmetric up to a remap of the components typedef MultiplicativeSymm2Body_Props Properties; σpσpCoulombσpσp_Descr() : quaternion_index_(0) {} @@ -567,6 +570,7 @@ typedef GenOper<σpσpCoulombσpσp_Descr> σpσpCoulombσpσpOper; * site. */ struct σpCoulombσp_Descr : public Contractable<σpCoulombσp_Descr> { + // particle 1<->2 symmetric up to the sign of the antisymmetric components typedef MultiplicativeSymm2Body_Props Properties; /// SO(3) irreducible components of the rank-2 Cartesian tensor T_{ab}. diff --git a/src/bin/libint/rkb_fold_codegen.h b/src/bin/libint/rkb_fold_codegen.h index a4e298865..f757b086b 100644 --- a/src/bin/libint/rkb_fold_codegen.h +++ b/src/bin/libint/rkb_fold_codegen.h @@ -78,8 +78,6 @@ std::string rkb_fold_spfunction_call( delete si; } os << "," << total_dim; - LibraryTaskManager& taskmgr = LibraryTaskManager::Instance(); - taskmgr.current().params()->max_hrr_hsrank(total_dim); os << ")" << context->end_of_stat() << std::endl; return os.str(); } @@ -87,14 +85,15 @@ std::string rkb_fold_spfunction_call( /// generate_code body: emit the full element-wise function. The caller computes /// the per-target right-hand-side expression @p rhs (e.g. /// "src0[hsi] + src1[hsi]") and its flop count @p nflops; everything else — -/// symbol assignment, function declaration, and the highdim loop scaffold — is +/// symbol assignment, function declaration, and the element loop scaffold — is /// identical across the four operators and emitted here. template -void rkb_fold_generate_code( - const RR& self, const std::shared_ptr& context, - const std::shared_ptr& dims, const std::string& funcname, - std::ostream& decl, std::ostream& def, const std::string& rhs, - unsigned int nflops) { +void rkb_fold_generate_code(const RR& self, + const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::string& funcname, std::ostream& decl, + std::ostream& def, const std::string& rhs, + unsigned int nflops) { // declare_function lives in dg.cc extern std::string declare_function( const std::shared_ptr& context, @@ -118,12 +117,21 @@ void rkb_fold_generate_code( const std::string func_decl = declare_function(context, localdims, symbols, tlabel, funcname, decl); def << context->std_header(); - def << "#include <" << context->label_to_name(funcname) << ".h>\n\n"; + def << "#include <" << context->label_to_filename(funcname) << ".h>\n\n"; def << context->code_prefix(); def << func_decl << context->open_block() << std::endl; def << context->std_function_header(); def << "#ifdef __INTEL_COMPILER\n#pragma ivdep\n#endif\n"; - def << "for(int hsi = 0; hsihigh_label(); + if (!localdims->low_is_static() || localdims->low_label() != "1") + nelem += "*" + localdims->low_label(); + if (!localdims->vecdim_is_static()) + nelem += "*veclen"; + else if (localdims->vecdim_label() != "1") + nelem += "*" + localdims->vecdim_label(); + def << "for(int hsi = 0; hsi<" << nelem << "; hsi++) {\n"; def << "target[hsi] = " << rhs << ";\n}\n"; def << "/** Number of flops = " << nflops << " */\n"; def << context->close_block() << std::endl; From bf18fdc38044ba00755d989b814337859c9a8ac4 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Fri, 23 Oct 2026 16:03:54 -0400 Subject: [PATCH 45/64] Add finite-difference test of ERI geometric derivatives for s/p/d shells and all brakets The existing "eri geometric derivatives" test validates only the (pp|pp) 4-center quartet against the primitive reference. The new test checks every derivative kernel in the build: all s/p/d combinations (f on the fitting center) for 4-, 3- and 2-center Coulomb integrals, contracted and uncontracted shells, at every supported derivative order, against 5-point central finite differences of the next lower order. Runs in ~0.4 s (Release) at max AM 2. --- export/tests/unit/test-2body.cc | 202 ++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 88b2227bb..f6ec69e57 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -21,6 +21,8 @@ #include #include +#include +#include #include "catch.hpp" #include "fixture.h" @@ -346,6 +348,206 @@ TEST_CASE("eri geometric derivatives", "[engine][2-body]") { } } +// Validates geometric derivatives of Coulomb integrals against 5-point central +// finite differences of the derivatives of the next lower order, for every +// combination of s, p, d shells (and up to f on the fitting center), with +// contracted and uncontracted shells, in all brakets with derivative kernels. +// Complements "eri geometric derivatives" above, which compares (pp|pp) against +// an independent reference: this covers every kernel in the build, including +// the code-sharing paths that only appear with spectator shells of unequal +// size, at the cost of relying on the correctness of the next lower order. +namespace { + +// index of the second derivative with respect to coordinates i and j: second +// derivatives are the upper triangle of the Hessian, in row-major order +size_t hessian_index(size_t ncoords, size_t i, size_t j) { + const auto row = std::min(i, j); + const auto col = std::max(i, j); + return row * (2 * ncoords - row - 1) / 2 + col; +} + +// all centers distinct and in general position +Shell make_fd_shell(int l, bool contracted, int center) { + const std::array O[] = {{{0.0, 0.0, 0.0}}, + {{0.7, 0.2, -0.3}}, + {{-0.4, 0.9, 0.5}}, + {{0.3, -0.6, 1.1}}}; + const double scale = 1.0 + 0.2 * center; + if (contracted) + return Shell{{2.3 * scale, 0.8 * scale, 0.31 * scale}, + {{l, false, {0.5, 0.4, 0.3}}}, + O[center]}; + return Shell{{0.65 * scale}, {{l, false, {1.0}}}, O[center]}; +} + +std::vector> compute_fd_shellsets( + Engine &engine, BraKet braket, const std::vector &shells) { + const Engine::target_ptr_vec *results = nullptr; + switch (braket) { + case BraKet::xx_xx: + results = &engine.compute(shells[0], shells[1], shells[2], shells[3]); + break; + case BraKet::xs_xx: + results = &engine.compute(shells[0], shells[1], shells[2]); + break; + case BraKet::xs_xs: + results = &engine.compute(shells[0], shells[1]); + break; + default: + FAIL("unsupported braket"); + } + size_t n = 1; + for (const auto &sh : shells) n *= sh.size(); + std::vector> shellsets; + for (const auto *target : *results) { + if (target == nullptr) // screened out + shellsets.emplace_back(n, 0.0); + else + shellsets.emplace_back(target, target + n); + } + return shellsets; +} + +// checks derivatives of order deriv_order of a shell set with the given +// angular momenta against finite differences of order deriv_order - 1; +// returns false if the library lacks the kernels for this shell set +bool check_fd_derivs(BraKet braket, int deriv_order, const std::vector &l, + const std::vector &contracted) { + const auto ncenters = l.size(); + std::vector shells; + for (size_t c = 0; c != ncenters; ++c) + shells.push_back(make_fd_shell(l[c], contracted[c], c)); + const auto nprim = libint2::max_nprim(shells); + const auto lmax = libint2::max_l(shells); + const auto eps = std::numeric_limits::epsilon(); + Engine engine, engine_lower; + try { + engine = Engine(Operator::coulomb, nprim, lmax, deriv_order, eps, + libint2::default_params(Operator::coulomb), braket); + engine_lower = Engine(Operator::coulomb, nprim, lmax, deriv_order - 1, eps, + libint2::default_params(Operator::coulomb), braket); + } catch (Engine::lmax_exceeded &) { + return false; + } + engine.set_precision(0.0); + engine_lower.set_precision(0.0); + + const auto analytic = compute_fd_shellsets(engine, braket, shells); + const auto ncoords = 3 * ncenters; + REQUIRE(analytic.size() == + libint2::num_geometrical_derivatives(ncenters, deriv_order)); + + const double h = 1e-3; + for (size_t coord1 = 0; coord1 != ncoords; ++coord1) { + // derivatives of order deriv_order - 1 at a displaced geometry; + // N.B. displace Shell::O in place, Shell's constructor would renormalize + // the contraction coefficients + auto lower = [&](double delta) { + auto displaced = shells; + displaced[coord1 / 3].O[coord1 % 3] += delta; + return compute_fd_shellsets(engine_lower, braket, displaced); + }; + const auto m2 = lower(-2 * h); + const auto m1 = lower(-h); + const auto p1 = lower(h); + const auto p2 = lower(2 * h); + + for (size_t s = 0; s != p1.size(); ++s) { + const auto deriv_index = + deriv_order == 1 ? coord1 : hessian_index(ncoords, s, coord1); + const auto &a = analytic.at(deriv_index); + double max_error = 0, max_numeric = 0; + for (size_t i = 0; i != a.size(); ++i) { + const auto numeric = + (m2[s][i] - 8 * m1[s][i] + 8 * p1[s][i] - p2[s][i]) / (12 * h); + max_error = std::max(max_error, std::abs(a[i] - numeric)); + max_numeric = std::max(max_numeric, std::abs(numeric)); + } + std::ostringstream oss; + for (size_t c = 0; c != ncenters; ++c) + oss << (contracted[c] ? "LPDF"[l[c]] : "lpdf"[l[c]]); + INFO("braket=" << static_cast(braket) + << " deriv_order=" << deriv_order + << " shells=" << oss.str() << " coord=" << coord1 + << " lower_shellset=" << s << " max_error=" << max_error); + // 5-point stencil with h=1e-3: truncation and roundoff errors are both + // ~1e-12 relative to the scale of the derivatives + REQUIRE(max_error <= 1e-9 * std::max(1.0, max_numeric)); + } + } + return true; +} + +// contraction pattern for a shell set, cycling through the patterns so that +// each angular momentum combination is tested once +std::vector fd_contraction_pattern(const std::vector &l) { + int key = 0; + for (size_t c = 0; c != l.size(); ++c) key += (c + 1) * l[c]; + std::vector contracted(l.size(), false); + switch (key % 3) { + case 0: + break; + case 1: + for (size_t c = 0; c < l.size(); c += 2) contracted[c] = true; + break; + case 2: + contracted.assign(l.size(), true); + break; + } + return contracted; +} + +} // namespace + +TEST_CASE("eri geometric derivatives vs finite differences", + "[engine][2-body][deriv]") { + size_t ntested = 0; +#if defined(LIBINT2_SUPPORT_ERI) && LIBINT2_DERIV_ERI_ORDER > 0 + SECTION("4-center") { + const int lmax = std::min(2, LIBINT2_MAX_AM_eri); + for (int d = 1; d <= LIBINT2_DERIV_ERI_ORDER; ++d) + for (int la = 0; la <= lmax; ++la) + for (int lb = 0; lb <= lmax; ++lb) + for (int lc = 0; lc <= lmax; ++lc) + for (int ld = 0; ld <= lmax; ++ld) { + const std::vector l{la, lb, lc, ld}; + if (check_fd_derivs(BraKet::xx_xx, d, l, + fd_contraction_pattern(l))) + ++ntested; + } + REQUIRE(ntested > 0); + } +#endif +#if defined(LIBINT2_SUPPORT_ERI3) && LIBINT2_DERIV_ERI3_ORDER > 0 + SECTION("3-center") { + const int lmax_fit = std::min(3, LIBINT2_MAX_AM_3eri); + const int lmax = std::min(2, LIBINT2_MAX_AM_default); + for (int d = 1; d <= LIBINT2_DERIV_ERI3_ORDER; ++d) + for (int lp = 0; lp <= lmax_fit; ++lp) + for (int lc = 0; lc <= lmax; ++lc) + for (int ld = 0; ld <= lmax; ++ld) { + const std::vector l{lp, lc, ld}; + if (check_fd_derivs(BraKet::xs_xx, d, l, fd_contraction_pattern(l))) + ++ntested; + } + REQUIRE(ntested > 0); + } +#endif +#if defined(LIBINT2_SUPPORT_ERI2) && LIBINT2_DERIV_ERI2_ORDER > 0 + SECTION("2-center") { + const int lmax = std::min(3, LIBINT2_MAX_AM_2eri); + for (int d = 1; d <= LIBINT2_DERIV_ERI2_ORDER; ++d) + for (int lp = 0; lp <= lmax; ++lp) + for (int lq = 0; lq <= lmax; ++lq) { + const std::vector l{lp, lq}; + if (check_fd_derivs(BraKet::xs_xs, d, l, fd_contraction_pattern(l))) + ++ntested; + } + REQUIRE(ntested > 0); + } +#endif +} + TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { const std::vector all_shells{ // pseudorandom s From e6f85b461550552be40fce5823f0336ea89f76f6 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Fri, 23 Oct 2026 17:35:51 -0400 Subject: [PATCH 46/64] Remove LIBINT2_RKB_ERI3_PURE_SH: a solid harmonics fitting center is incorrect for RKB 3-center kernels pure_sh() on a shell enables the Ahlrichs simplification in the VRR, which drops terms that vanish only after that shell's cartesian components are projected to solid harmonics as is. The RKB 3-center kernels are built from derivative TwoPRep children, and translational invariance routes one derivative onto the fitting shell; the derivative of a (pure) shell needs exactly the dropped components, so the generated kernels were wrong for any fitting shell with l > 0 (verified against the 4-center kernel: 3-center (d|ss) scalar component -0.310 vs -0.676). This is the same reason plain ERI3 enables pure_sh only for deriv_level == 0. The option was new in this PR and OFF by default; remove it rather than ship it broken. Engine::compute2 now rejects a cartesian fitting shell of l > 1 with std::invalid_argument in all builds when the library was generated with LIBINT2_ERI3_PURE_SH (previously a Debug-only assert, which also fired for the RKB 3-center kernels that never make that assumption). --- CMakeLists.txt | 5 +---- cmake/modules/int_am.cmake | 2 +- include/libint2/config.h.cmake.in | 3 --- include/libint2/engine.h | 9 +++++++++ include/libint2/engine.impl.h | 17 +++++++++++------ src/bin/libint/build_libint.cc | 28 +++++++++------------------- 6 files changed, 31 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 949c5fb99..fbbcce01e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,8 +246,6 @@ option_with_default(LIBINT2_RKB_ERI3_MAX_AM option_with_default(LIBINT2_RKB_ERI3_OPT_AM "Optimize relativistic restricted kinetic balance (RKB) 3-center ERIs maximally for up to angular momentum N (N <= max-am). Can specify values for each derivative level as a semicolon-separated string (default: (max_am/2)+1)" -1) -option_with_print(LIBINT2_RKB_ERI3_PURE_SH - "Assume the 'unpaired' (density fitting/auxiliary) center of 3-center RKB ERIs will be transformed to pure solid harmonics" OFF) option_with_default(LIBINT2_ERI3_MAX_AM "Support 3-center ERIs for Gaussians of angular momentum up to N. @@ -344,7 +342,6 @@ if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES) endif() booleanize01(LIBINT2_ERI3_PURE_SH) -booleanize01(LIBINT2_RKB_ERI3_PURE_SH) booleanize01(LIBINT2_ERI2_PURE_SH) booleanize01(LIBINT2_USE_COMPOSITE_EVALUATORS) booleanize01(LIBINT2_SINGLE_EVALTYPE) @@ -415,7 +412,7 @@ configure_file( # library build time. set(_EXPORT_MODE 0) # convert user-facing LIBINT2_ variables to LIBINT_ internal variables -foreach(_var API_PREFIX;ERI3_PURE_SH;RKB_ERI3_PURE_SH;ERI2_PURE_SH;DISABLE_ONEBODY_PROPERTY_DERIVS;ENABLE_UNROLLING;ENABLE_GENERIC_CODE;VECTOR_LENGTH;VECTOR_METHOD;ALIGN_SIZE;USER_DEFINED_REAL;USER_DEFINED_REAL_INCLUDES;GENERATE_FMA;ACCUM_INTS;FLOP_COUNT;PROFILE;CONTRACTED_INTS;SINGLE_EVALTYPE;USE_COMPOSITE_EVALUATORS;ERI_STRATEGY;MULTIPOLE_MAX_ORDER) +foreach(_var API_PREFIX;ERI3_PURE_SH;ERI2_PURE_SH;DISABLE_ONEBODY_PROPERTY_DERIVS;ENABLE_UNROLLING;ENABLE_GENERIC_CODE;VECTOR_LENGTH;VECTOR_METHOD;ALIGN_SIZE;USER_DEFINED_REAL;USER_DEFINED_REAL_INCLUDES;GENERATE_FMA;ACCUM_INTS;FLOP_COUNT;PROFILE;CONTRACTED_INTS;SINGLE_EVALTYPE;USE_COMPOSITE_EVALUATORS;ERI_STRATEGY;MULTIPOLE_MAX_ORDER) if (DEFINED LIBINT2_${_var}) if (DEFINED LIBINT_${_var}) message(FATAL_ERROR "renaming user-facing LIBINT2_${_var} variable but internal variable LIBINT_${_var} already exists") diff --git a/cmake/modules/int_am.cmake b/cmake/modules/int_am.cmake index 5136a8df4..481473f84 100644 --- a/cmake/modules/int_am.cmake +++ b/cmake/modules/int_am.cmake @@ -478,7 +478,7 @@ foreach(_cls ONEBODY;ERI;RKB_ERI;RKB_ERI3;ERI3;ERI2;G12;G12DKH) if ((_cls STREQUAL "ERI3") AND NOT LIBINT2_ERI3_PURE_SH) list(APPEND Libint2_ERI_COMPONENTS "${_eri3_impure_sh}") message(VERBOSE "setting components ${_eri3_impure_sh}") - elseif ((_cls STREQUAL "RKB_ERI3") AND NOT LIBINT2_RKB_ERI3_PURE_SH) + elseif (_cls STREQUAL "RKB_ERI3") # RKB 3-center kernels never assume a solid harmonics fitting center list(APPEND Libint2_ERI_COMPONENTS "${_rkb_eri3_impure_sh}") message(VERBOSE "setting components ${_rkb_eri3_impure_sh}") elseif ((_cls STREQUAL "ERI2") AND NOT LIBINT2_ERI2_PURE_SH) diff --git a/include/libint2/config.h.cmake.in b/include/libint2/config.h.cmake.in index 6a1e15526..5e6fb36e9 100644 --- a/include/libint2/config.h.cmake.in +++ b/include/libint2/config.h.cmake.in @@ -177,9 +177,6 @@ /* If 1, assume will transform the "unpaired" center (e.g. a in (a|cd)) to solid harmonics */ #cmakedefine LIBINT_ERI3_PURE_SH @LIBINT_ERI3_PURE_SH@ -/* If 1, assume will transform the "unpaired" (density fitting/auxiliary) center of 3-center RKB ERIs to solid harmonics */ -#cmakedefine LIBINT_RKB_ERI3_PURE_SH @LIBINT_RKB_ERI3_PURE_SH@ - /* Max AM for 2-center ERI (same for all derivatives; if not defined see LIBINT_ERI2_MAX_AM_LIST) */ #cmakedefine LIBINT_ERI2_MAX_AM @LIBINT_ERI2_MAX_AM@ diff --git a/include/libint2/engine.h b/include/libint2/engine.h index ab211eecd..37ab7d010 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -651,6 +651,15 @@ inline constexpr bool is_rkb_2body(Operator oper) { return oper == Operator::coulomb_opop || oper == Operator::coulomb_op || oper == Operator::opop_coulomb_opop || oper == Operator::op_coulomb_op; } + +/// whether the library was generated assuming that the fitting (unpaired) +/// center of plain 3-center ERIs will be transformed to solid harmonics +/// (LIBINT2_ERI3_PURE_SH) +#ifdef LIBINT_ERI3_PURE_SH +constexpr bool eri3_pure_sh = true; +#else +constexpr bool eri3_pure_sh = false; +#endif } // namespace detail constexpr size_t nopers_2body = static_cast(Operator::last_2body_oper) - diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 8f7f4297a..c0f08866b 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -2024,12 +2024,17 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( buildfnidx = (bra1.contr[0].l * ket_lmax + ket1.contr[0].l) * ket_lmax + ket2.contr[0].l; -#ifdef LIBINT_ERI3_PURE_SH - if (bra1.contr[0].l > 1) - assert(bra1.contr[0].pure && - "library assumes a solid harmonics shell in bra of a 3-center " - "2-body int, but a cartesian shell given"); -#endif + // the kernels may have been generated assuming that the fitting + // (bra) shell will be transformed to solid harmonics + // (LIBINT2_ERI3_PURE_SH); this never applies to the RKB 3-center + // kernels. Such kernels produce garbage for a cartesian shell, hence + // check in all builds + if (detail::eri3_pure_sh && !detail::is_rkb_2body(oper_) && + bra1.contr[0].l > 1 && !bra1.contr[0].pure) + throw std::invalid_argument( + "Engine::compute2 -- this library assumes that the fitting " + "(bra) shell of 3-center 2-body integrals is a solid harmonics " + "shell, but a cartesian shell was given"); } break; case BraKet::xs_xs: diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 03f5bf84b..752b48b45 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -1142,9 +1142,6 @@ void try_main(int argc, char* argv[]) { #if LIBINT_ERI3_PURE_SH iface->to_params(iface->macro_define("ERI3_PURE_SH", 1)); #endif -#if LIBINT_RKB_ERI3_PURE_SH - iface->to_params(iface->macro_define("RKB_ERI3_PURE_SH", 1)); -#endif #endif #ifdef LIBINT_INCLUDE_ERI2 for (unsigned int d = 0; d <= LIBINT_INCLUDE_ERI2; ++d) { @@ -1661,21 +1658,20 @@ static void build_TwoPRep_1b_2k( CGShell c(lc); CGShell d(ld); #if LIBINT_ERI3_PURE_SH - // pure-SH on the fitting center of plain ERI3 (gated separately from - // the RKB variant below; see LIBINT_RKB_ERI3_PURE_SH). + // pure-SH on the fitting center of plain ERI3 only. The RKB + // operators cannot use it: pure_sh() enables the Ahlrichs + // simplification in the VRR, which is only valid if the shell's + // cartesian components are projected to solid harmonics as is. The + // RKB kernels are built from derivative TwoPRep children, and + // translational invariance routes a derivative onto the fitting + // shell; the derivative of a (pure) shell needs exactly the + // components the simplification drops. The same reason restricts + // pure_sh to deriv_level == 0 for plain ERI3. if constexpr (std::is_same::value) { if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); } #endif -#if LIBINT_RKB_ERI3_PURE_SH - // pure-SH on the (density fitting/auxiliary) center of RKB 3-center - // ERIs, mirroring the plain-ERI3 path for the non-TwoPRep operators. - if constexpr (!std::is_same::value) { - if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); - if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); - } -#endif unsigned int center = 0; for (unsigned int i = 0; i < 4; ++i) { @@ -1720,12 +1716,6 @@ static void build_TwoPRep_1b_2k( if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); } -#endif -#if LIBINT_RKB_ERI3_PURE_SH - if constexpr (!std::is_same::value) { - if (dummy_center == 1 && deriv_level == 0) a.pure_sh(true); - if (dummy_center == 0 && deriv_level == 0) b.pure_sh(true); - } #endif if constexpr (std::is_same::value) { OperType oper(descrs[0]); From a287bd75776506d454a8ffa608704ef231d860ac Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Fri, 23 Oct 2026 17:35:53 -0400 Subject: [PATCH 47/64] Test RKB operators on solid harmonics shells and the xx_xs alias of coulomb_op; guard ERI-only unit tests - new test case "RKB integrals with solid harmonics shells": for every RKB operator and braket, the result for pure shells must equal the cartesian result transformed with libint2::solidharmonics; the 3-center kernel with a pure d fitting shell is also checked against the 4-center kernel. Covers the per-component sign corrections on the transform-and-permute path, which the cartesian-only reference tests do not reach. Runs in 0.07 s. - unit tests that need 4-center ERI kernels are now guarded by LIBINT2_SUPPORT_ERI, so the unit tests compile in a build with only RKB 2-body integrals (which the C++ API now allows) - CI: one lane builds the 3-center RKB ERIs with LIBINT2_RKB_ERI3_MAX_AM=3, exercising a center-dependent build table --- .github/workflows/cmake.yml | 8 +- export/tests/unit/test-2body.cc | 246 ++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 465ab9069..131a38546 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -45,6 +45,12 @@ jobs: build_shared_libs: ON os: macos-15 do_fortran: ON + # exercise a center-dependent AM table for the 3-center RKB ERIs in + # one lane only + - build_type: Release + os: macos-15 + subproject: OFF + rkb_eri3_max_am: 3 name: "Repo • ${{ matrix.os }}: ${{ matrix.cxx }} ${{ matrix.build_type }} shared=${{ matrix.build_shared_libs }} fetch=${{ matrix.subproject }}" runs-on: ${{ matrix.os }} @@ -68,7 +74,7 @@ jobs: -D LIBINT2_ENABLE_ERI3=1 -D LIBINT2_ENABLE_RKB_ERI=0 -D LIBINT2_ENABLE_RKB_ERI3=0 - -D LIBINT2_RKB_ERI3_MAX_AM=2 + -D LIBINT2_RKB_ERI3_MAX_AM=${{ matrix.rkb_eri3_max_am || 2 }} -D LIBINT2_ENABLE_ONEBODY=1 -D LIBINT2_DISABLE_ONEBODY_PROPERTY_DERIVS=${{ matrix.disable_onebody_property_derivs || 'ON' }} -D LIBINT2_MULTIPOLE_MAX_ORDER=2 diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index f6ec69e57..1a9dc1543 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -36,6 +36,7 @@ typedef unsigned int uint; TEST_CASE("Slater/Yukawa integrals", "[engine][2-body]") { +#if defined(LIBINT2_SUPPORT_ERI) std::vector obs{ // pseudorandom d Shell{{1.0, 3.0}, {{2, true, {1.0, 0.3}}}, {{0.0, 0.0, 0.0}}}, @@ -124,11 +125,13 @@ TEST_CASE("Slater/Yukawa integrals", "[engine][2-body]") { } } } +#endif // LIBINT2_SUPPORT_ERI } // see https://github.com/evaleev/libint/issues/216 TEST_CASE_METHOD(libint2::unit::DefaultFixture, "bra-ket permutation", "[engine][2-body]") { +#if defined(LIBINT2_SUPPORT_ERI) if (LIBINT2_MAX_AM_eri < 2) return; using libint2::BasisSet; using libint2::Engine; @@ -190,9 +193,11 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "bra-ket permutation", } } } +#endif // LIBINT2_SUPPORT_ERI } TEST_CASE("eri geometric derivatives", "[engine][2-body]") { +#if defined(LIBINT2_SUPPORT_ERI) std::vector obs{ // pseudorandom p Shell{{1.0, 0.3}, {{1, false, {0.9, 0.3}}}, {{0.0, 0.0, 0.0}}}, @@ -346,6 +351,7 @@ TEST_CASE("eri geometric derivatives", "[engine][2-body]") { } } } +#endif // LIBINT2_SUPPORT_ERI } // Validates geometric derivatives of Coulomb integrals against 5-point central @@ -1221,7 +1227,246 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { #endif // LIBINT2_TASK_EXISTS_3coulomb_op } +// RKB integrals over solid harmonics shells. The RKB reference tests above use +// cartesian shells only; the engine's cartesian-to-solid-harmonics transform +// shares the scratch/permute path with the per-component sign corrections of +// the RKB operators, so verify here that the two compose: the result for pure +// shells must equal the cartesian result transformed with the library's own +// solid harmonics routines. Also covers the xx_xs alias of coulomb_op and a +// solid harmonics fitting center. +namespace { + +typedef std::vector> rkb_shellsets_t; + +rkb_shellsets_t rkb_copy_results(const Engine::target_ptr_vec &results, + const std::vector &shells) { + size_t n = 1; + for (const auto &sh : shells) n *= sh.size(); + rkb_shellsets_t sets; + for (const auto *target : results) { + REQUIRE(target != nullptr); + sets.emplace_back(target, target + n); + } + return sets; +} + +/// transforms shell sets computed over @c src_shells to the basis of +/// @c tgt_shells; the two differ only in which shells are pure +rkb_shellsets_t rkb_to_pure(const rkb_shellsets_t &sets, + const std::vector &src_shells, + const std::vector &tgt_shells) { + REQUIRE(src_shells.size() == tgt_shells.size()); + const auto nsh = src_shells.size(); + rkb_shellsets_t result; + for (const auto &set : sets) { + auto src = set; + for (size_t k = 0; k != nsh; ++k) { + const auto l = src_shells[k].contr[0].l; + REQUIRE(l == tgt_shells[k].contr[0].l); + if (src_shells[k].contr[0].pure == tgt_shells[k].contr[0].pure || l == 0) + continue; + REQUIRE(tgt_shells[k].contr[0].pure); + // dimensions before k are already transformed, those after are not + size_t n1 = 1, n2 = 1; + for (size_t i = 0; i != k; ++i) n1 *= tgt_shells[i].size(); + for (size_t i = k + 1; i != nsh; ++i) n2 *= src_shells[i].size(); + std::vector tgt(n1 * (2 * l + 1) * n2); + if (k == 0) + libint2::solidharmonics::transform_first(l, n2, src.data(), tgt.data()); + else if (k + 1 == nsh) + libint2::solidharmonics::transform_last(n1, l, src.data(), tgt.data()); + else + libint2::solidharmonics::transform_inner(n1, l, n2, src.data(), + tgt.data()); + src = std::move(tgt); + } + result.push_back(std::move(src)); + } + return result; +} + +void rkb_require_equal(const rkb_shellsets_t &a, const rkb_shellsets_t &b, + const std::string &what) { + REQUIRE(a.size() == b.size()); + for (size_t c = 0; c != a.size(); ++c) { + REQUIRE(a[c].size() == b[c].size()); + for (size_t i = 0; i != a[c].size(); ++i) { + const auto abs_err = std::abs(a[c][i] - b[c][i]); + const auto rel_err = + std::abs(b[c][i]) > 1e-30 ? abs_err / std::abs(b[c][i]) : 0.0; + INFO(what << " comp=" << c << " elem=" << i << " value=" << a[c][i] + << " ref=" << b[c][i]); + REQUIRE(!(rel_err > 1e-10 && abs_err > 1e-13)); + } + } +} + +Shell rkb_shell(const Shell &sh, bool pure) { + auto result = sh; + result.contr[0].pure = pure; + return result; +} + +} // namespace + +TEST_CASE("RKB integrals with solid harmonics shells", "[engine][2-body]") { + // cartesian s, p, contracted p, d; the solid harmonics variants of the same + const std::vector cart_shells{ + Shell{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}, + Shell{{2.0}, {{1, false, {1.0}}}, {{1.0, 1.0, 1.0}}}, + Shell{{3.0, 0.7}, {{1, false, {0.4, 0.8}}}, {{-0.5, 0.2, 0.9}}}, + Shell{{1.2}, {{2, false, {1.0}}}, {{0.3, -0.8, 0.4}}}}; + std::vector pure_shells; + for (const auto &sh : cart_shells) pure_shells.push_back(rkb_shell(sh, true)); + const auto nprim = libint2::max_nprim(cart_shells); + + auto upto = [](const std::vector &shells, int lmax) { + std::vector result; + for (const auto &sh : shells) + if (sh.contr[0].l <= lmax) result.push_back(sh); + return result; + }; + + // 4-center: compare pure vs transformed cartesian for every quartet + auto check_4center = [&](Operator oper, int lmax_oper) { + const auto cart = upto(cart_shells, lmax_oper); + const auto pure = upto(pure_shells, lmax_oper); + Engine engine(oper, nprim, libint2::max_l(cart), 0); + const auto nsh = cart.size(); + for (size_t a = 0; a != nsh; ++a) + for (size_t b = 0; b != nsh; ++b) + for (size_t c = 0; c != nsh; ++c) + for (size_t d = 0; d != nsh; ++d) { + const std::vector cs{cart[a], cart[b], cart[c], cart[d]}; + const std::vector ps{pure[a], pure[b], pure[c], pure[d]}; + const auto ref = + rkb_to_pure(rkb_copy_results( + engine.compute(cs[0], cs[1], cs[2], cs[3]), cs), + cs, ps); + const auto val = rkb_copy_results( + engine.compute(ps[0], ps[1], ps[2], ps[3]), ps); + std::ostringstream oss; + oss << "oper=" << static_cast(oper) << " (" << a << b << "|" + << c << d << ")"; + rkb_require_equal(val, ref, oss.str()); + } + }; + +#if LIBINT2_TASK_EXISTS_4coulomb_opop + SECTION("coulomb_opop 4-center") { + check_4center(Operator::coulomb_opop, LIBINT2_MAX_AM_coulomb_opop); + } +#endif +#if LIBINT2_TASK_EXISTS_4opop_coulomb_opop + SECTION("opop_coulomb_opop 4-center") { + check_4center(Operator::opop_coulomb_opop, + LIBINT2_MAX_AM_opop_coulomb_opop); + } +#endif +#if LIBINT2_TASK_EXISTS_4op_coulomb_op + SECTION("op_coulomb_op 4-center") { + check_4center(Operator::op_coulomb_op, LIBINT2_MAX_AM_op_coulomb_op); + } +#endif + + // 3-center: fitting shells s and d, AO shells s, p, d, cartesian vs pure, + // both brakets + auto check_3center = [&](Operator oper, int lmax_fit, int lmax_ao) { + const std::vector fit_pure{ + Shell{{1.5}, {{0, true, {1.0}}}, {{-1.0, 0.5, 0.0}}}, + Shell{{0.9}, {{2, true, {1.0}}}, {{-1.0, 0.5, 0.0}}}}; + const auto fit = upto(fit_pure, lmax_fit); + const auto cart = upto(cart_shells, lmax_ao); + const auto pure = upto(pure_shells, lmax_ao); + const auto nprim_3c = std::max(nprim, libint2::max_nprim(fit)); + const auto lmax_3c = std::max(libint2::max_l(cart), libint2::max_l(fit)); + const auto eps = std::numeric_limits::epsilon(); + Engine eng_xs(oper, nprim_3c, lmax_3c, 0, eps, + libint2::default_params(oper), BraKet::xs_xx); + Engine eng_xxs(oper, nprim_3c, lmax_3c, 0, eps, + libint2::default_params(oper), BraKet::xx_xs); + + for (size_t p = 0; p != fit.size(); ++p) { + const auto fit_ref = rkb_shell(fit[p], false); + for (size_t a = 0; a != cart.size(); ++a) + for (size_t b = 0; b != cart.size(); ++b) { + const std::vector rs{fit_ref, cart[a], cart[b]}; + const std::vector ps{fit[p], pure[a], pure[b]}; + const auto ref = rkb_to_pure( + rkb_copy_results(eng_xs.compute(rs[0], rs[1], rs[2]), rs), rs, + ps); + const auto val = + rkb_copy_results(eng_xs.compute(ps[0], ps[1], ps[2]), ps); + std::ostringstream oss; + oss << "oper=" << static_cast(oper) << " (" << p << "|" << a << b + << ")"; + rkb_require_equal(val, ref, oss.str() + " xs_xx"); + + // xx_xs alias: same integrals, (a b | fit) layout + const auto val_xxs = + rkb_copy_results(eng_xxs.compute(ps[1], ps[2], ps[0]), + std::vector{ps[1], ps[2], ps[0]}); + const auto n_fit = ps[0].size(), n_a = ps[1].size(), + n_b = ps[2].size(); + rkb_shellsets_t val_xxs_as_xs(val_xxs.size()); + for (size_t c = 0; c != val_xxs.size(); ++c) { + val_xxs_as_xs[c].resize(n_fit * n_a * n_b); + for (size_t i_f = 0; i_f != n_fit; ++i_f) + for (size_t i_a = 0; i_a != n_a; ++i_a) + for (size_t i_b = 0; i_b != n_b; ++i_b) + val_xxs_as_xs[c][(i_f * n_a + i_a) * n_b + i_b] = + val_xxs[c][(i_a * n_b + i_b) * n_fit + i_f]; + } + rkb_require_equal(val_xxs_as_xs, val, oss.str() + " xx_xs"); + } + } + }; + +#if LIBINT2_TASK_EXISTS_3coulomb_opop + SECTION("coulomb_opop 3-center") { + check_3center(Operator::coulomb_opop, LIBINT2_MAX_AM_3coulomb_opop, + LIBINT2_MAX_AM_default); + } +#if LIBINT2_TASK_EXISTS_4coulomb_opop + SECTION("coulomb_opop 3-center vs 4-center, pure fitting shell") { + // the 3-center kernel is generated with a dummy shell next to the fitting + // shell; check against the 4-center kernel, which has no such structure + const Shell fit{{0.9}, {{2, true, {1.0}}}, {{-1.0, 0.5, 0.0}}}; + const auto lmax_ao = std::min( + LIBINT2_MAX_AM_default, + std::min(LIBINT2_MAX_AM_3coulomb_opop, LIBINT2_MAX_AM_coulomb_opop)); + if (LIBINT2_MAX_AM_3coulomb_opop >= 2 && LIBINT2_MAX_AM_coulomb_opop >= 2) { + const auto pure = upto(pure_shells, lmax_ao); + const auto eps = std::numeric_limits::epsilon(); + Engine eng_3c(Operator::coulomb_opop, nprim, 2, 0, eps, + libint2::default_params(Operator::coulomb_opop), + BraKet::xs_xx); + Engine eng_4c(Operator::coulomb_opop, nprim, 2, 0); + for (size_t a = 0; a != pure.size(); ++a) + for (size_t b = 0; b != pure.size(); ++b) { + const std::vector ps{fit, pure[a], pure[b]}; + const auto val = + rkb_copy_results(eng_3c.compute(fit, pure[a], pure[b]), ps); + const auto ref = rkb_copy_results( + eng_4c.compute(fit, Shell::unit(), pure[a], pure[b]), ps); + std::ostringstream oss; + oss << "(d|" << a << b << ") 3c vs 4c"; + rkb_require_equal(val, ref, oss.str()); + } + } + } +#endif +#endif +#if LIBINT2_TASK_EXISTS_3coulomb_op + SECTION("coulomb_op 3-center") { + check_3center(Operator::coulomb_op, LIBINT2_MAX_AM_3coulomb_op, + LIBINT2_MAX_AM_default); + } +#endif +} + TEST_CASE("Erfx_Coulomb integrals", "[engine][2-body]") { +#if defined(LIBINT2_SUPPORT_ERI) // pseudorandom s shells std::vector obs{ Shell{{1.0}, {{0, true, {1.0}}}, {{0.0, 0.0, 0.0}}}, @@ -1296,4 +1541,5 @@ TEST_CASE("Erfx_Coulomb integrals", "[engine][2-body]") { } } } +#endif // LIBINT2_SUPPORT_ERI } From 54afe4d84452eceb196645e5922990bf043b3888 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 17:05:12 -0400 Subject: [PATCH 48/64] RKB CR: select fold sign pattern by component index, not child count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coulombσpσp and σpσpCoulombσpσp generate_code() keyed the right-hand side on num_children(). GenericRecurrenceRelation::add_child() deduplicates children by instance, so a collision among the deriv-ERI children would have changed the count and silently selected another component's sign pattern (9 -> 6, 3 -> 2) instead of failing. Select the pattern from quaternion_index(), as σpCoulombσp already does, and throw if the child count differs from what that component must have (throw rather than assert, since build_libint is built Release). Also drop the comment claiming that TwoPRep particle-swap canonicalization deduplicates children: no such canonicalization exists; the instance key (ArrayBraket::key()) is positional per particle. --- .../comp_11_Coulomb\317\203p\317\203p_11.h" | 43 +++++++++----- ...3p\317\203pCoulomb\317\203p\317\203p_11.h" | 59 +++++++++++-------- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index 36dc09d59..f2b6b44b2 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -95,30 +95,43 @@ class CR_11_Coulombσpσp_11 return rkb_fold_adapt_dims(dims); } - /// Hand-emit a simple element-wise loop function. + /// Hand-emit a simple element-wise loop function. The right-hand side is + /// selected by the target's quaternion component; the child order it relies + /// on is fixed by the constructor: /// comp 0: target = src0 + src1 + src2 (dot product) /// comp 1-3: target = src0 - src1 (cross product components) void generate_code(const std::shared_ptr& context, const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - const unsigned int nc = this->num_children(); + const int comp = target_->oper()->descr().quaternion_index(); std::string rhs; - if (nc == 3) { - rhs = "src0[hsi] + src1[hsi] + src2[hsi]"; - } else if (nc == 2) { - rhs = "src0[hsi] - src1[hsi]"; - } else { - // Sign pattern is keyed on the child count (3 or 2). If canonicalization - // ever changes the count, fail the export loudly instead of emitting a - // wrong or empty target expression. - throw std::logic_error( - "Coulombσpσp generate_code(): unexpected num_children() (expected 3 " - "or 2)"); + unsigned int nchildren; + switch (comp) { + case 0: + rhs = "src0[hsi] + src1[hsi] + src2[hsi]"; + nchildren = 3; + break; + case 1: + case 2: + case 3: + rhs = "src0[hsi] - src1[hsi]"; + nchildren = 2; + break; + default: + throw std::runtime_error( + "CR_11_Coulombσpσp_11::generate_code: invalid quaternionic index"); } - const unsigned int nflops = (nc > 1) ? nc - 1 : 0; + // add_child() deduplicates children by instance (see generic_rr.h), so a + // collision among the deriv-ERI children would silently shift the src + // slots; fail the export loudly instead of emitting a wrong expression. + if (this->num_children() != nchildren) + throw std::logic_error("CR_11_Coulombσpσp_11::generate_code: component " + + std::to_string(comp) + " expects " + + std::to_string(nchildren) + " children, got " + + std::to_string(this->num_children())); rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs, - nflops); + nchildren - 1); } }; diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" index cc41ca285..8bd64b418 100644 --- "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -124,48 +124,55 @@ class CR_11_σpσpCoulombσpσp_11 return rkb_fold_adapt_dims(dims); } - /// Hand-emit a simple element-wise loop function. - /// Cannot use S-shell dummy because TwoPRep particle-swap canonicalization - /// deduplicates children (e.g., (S_x S_x|S_y S_y) = (S_y S_y|S_x S_x)), - /// giving fewer children than the real instance. + /// Hand-emit a simple element-wise loop function. The right-hand side is + /// selected by the target's component index (4 * bra_spin + ket_spin, with + /// spin indices S=0, X=1, Y=2, Z=3); the child order it relies on is fixed by + /// the constructor: + /// SS: 9 children, all +1 + /// SX,SY,SZ,XS,YS,ZS: 6 children, alternating +1,-1 + /// XX..ZZ: 4 children, pattern -1,+1,+1,-1 void generate_code(const std::shared_ptr& context, const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - // Sign patterns for each component, indexed by child order in constructor. - // comp 0 (SS): 9 children, all +1 - // comp 1-3 (SX,SY,SZ): 6 children, alternating +1,-1 - // comp 4,8,12 (XS,YS,ZS): 6 children, alternating +1,-1 - // comp 5-7,9-11,13-15 (XX..ZZ): 4 children, pattern -1,+1,+1,-1 - const unsigned int nc = this->num_children(); + const int comp = target_->oper()->descr().quaternion_index(); + if (comp < 0 || comp > 15) + throw std::runtime_error( + "CR_11_σpσpCoulombσpσp_11::generate_code: invalid component index " + "(expected 0-15)"); + const bool bra_scalar = (comp / 4 == 0); + const bool ket_scalar = (comp % 4 == 0); std::ostringstream rhs; - if (nc == 9) { - // SS component: all positive - for (unsigned int c = 0; c < 9; c++) { + unsigned int nchildren; + if (bra_scalar && ket_scalar) { + // SS: all positive + nchildren = 9; + for (unsigned int c = 0; c < nchildren; c++) { if (c > 0) rhs << " + "; rhs << "src" << c << "[hsi]"; } - } else if (nc == 6) { + } else if (bra_scalar || ket_scalar) { // SX,SY,SZ,XS,YS,ZS: alternating +/- - for (unsigned int c = 0; c < 6; c++) { + nchildren = 6; + for (unsigned int c = 0; c < nchildren; c++) { rhs << ((c % 2 == 0) ? " + " : " - "); rhs << "src" << c << "[hsi]"; } - } else if (nc == 4) { + } else { // XX,XY,...,ZZ: -src0 + src1 + src2 - src3 + nchildren = 4; rhs << "- src0[hsi] + src1[hsi] + src2[hsi] - src3[hsi]"; - } else { - // The σ·σ-fold sign pattern is keyed on the child count (9/6/4). If - // particle-swap canonicalization ever deduplicates children to a - // different count, the branches above would emit a wrong or empty target - // expression; fail the export loudly instead of generating broken code. - throw std::logic_error( - "σpσpCoulombσpσp generate_code(): unexpected num_children() (expected " - "9, 6, or 4)"); } - const unsigned int nflops = (nc > 1) ? nc - 1 : 0; + // add_child() deduplicates children by instance (see generic_rr.h), so a + // collision among the deriv-ERI children would silently shift the src + // slots; fail the export loudly instead of emitting a wrong expression. + if (this->num_children() != nchildren) + throw std::logic_error( + "CR_11_σpσpCoulombσpσp_11::generate_code: component " + + std::to_string(comp) + " expects " + std::to_string(nchildren) + + " children, got " + std::to_string(this->num_children())); rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs.str(), - nflops); + nchildren - 1); } }; From 5ff3574c036f78d8557fac251780945268409229 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 17:39:16 -0400 Subject: [PATCH 49/64] config.h: fix comment on LIBINT_RKB_ERI_OPT_AM (it is the RKB_ERI opt AM, not ERI) --- include/libint2/config.h.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libint2/config.h.cmake.in b/include/libint2/config.h.cmake.in index 5e6fb36e9..15bf069e8 100644 --- a/include/libint2/config.h.cmake.in +++ b/include/libint2/config.h.cmake.in @@ -141,7 +141,7 @@ /* Max AM for RKB_ERI and its derivatives */ #cmakedefine LIBINT_RKB_ERI_MAX_AM_LIST "@LIBINT_RKB_ERI_MAX_AM_LIST@" -/* Max optimized AM for ERI (same for all derivatives; if not defined see LIBINT_RKB_ERI_OPT_AM_LIST) */ +/* Max optimized AM for RKB_ERI (same for all derivatives; if not defined see LIBINT_RKB_ERI_OPT_AM_LIST) */ #cmakedefine LIBINT_RKB_ERI_OPT_AM @LIBINT_RKB_ERI_OPT_AM@ /* Max optimized AM for ERI and its derivatives */ From 9c02f59f55610d27a3b0fc9701622d2f60435f9f Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 17:54:15 -0400 Subject: [PATCH 50/64] =?UTF-8?q?=CF=83pCoulomb=CF=83p=20CR:=20ASCII=20gen?= =?UTF-8?q?erated=20label=20and=20child-count=20guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit generate_label() returned "CR_σpCoulombσp_", so the Unicode σ leaked into generated function names and source filenames (CR_σpCoulombσp_0.cc etc.), unlike the sibling fold CRs (CR_Coulombopop_, CR_opopCoulombopop_, CR_Coulombop_). Use "CR_opCoulombop_". generate_code() already selected the right-hand side from the component index but did not check num_children(); add the same guard as the other two fold CRs so a child collision in add_child() fails the export instead of producing a call with fewer src arguments than the expression uses. --- .../comp_11_\317\203pCoulomb\317\203p_11.h" | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git "a/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" index fb6e8d128..a2e970868 100644 --- "a/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" @@ -80,7 +80,7 @@ class CR_11_σpCoulombσp_11 // All shell quartets with the same quaternion component share one function. std::string generate_label() const override { - return "CR_σpCoulombσp_" + + return "CR_opCoulombop_" + std::to_string(target_->oper()->descr().component_index()); } @@ -96,7 +96,8 @@ class CR_11_σpCoulombσp_11 } /// Hand-emit the per-component irrep linear combination over deriv-ERI - /// children. The combination depends on the target's component index. + /// children. The combination is selected by the target's component index; + /// the child order it relies on is fixed by the constructor. void generate_code(const std::shared_ptr& context, const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, @@ -104,10 +105,12 @@ class CR_11_σpCoulombσp_11 const int comp = this->target_->oper()->descr().component_index(); std::string rhs; unsigned int nflops = 0; + unsigned int nchildren = 0; switch (comp) { case σpCoulombσp_Descr::Scalar: rhs = "src0[hsi] + src1[hsi] + src2[hsi]"; nflops = 2; + nchildren = 3; break; case σpCoulombσp_Descr::AntisymX: case σpCoulombσp_Descr::AntisymY: @@ -115,21 +118,32 @@ class CR_11_σpCoulombσp_11 case σpCoulombσp_Descr::SymTLDiagA: rhs = "src0[hsi] - src1[hsi]"; nflops = 1; + nchildren = 2; break; case σpCoulombσp_Descr::SymTLDiagB: rhs = "2.0*src0[hsi] - src1[hsi] - src2[hsi]"; nflops = 3; + nchildren = 3; break; case σpCoulombσp_Descr::SymTLOffXY: case σpCoulombσp_Descr::SymTLOffXZ: case σpCoulombσp_Descr::SymTLOffYZ: rhs = "src0[hsi] + src1[hsi]"; nflops = 1; + nchildren = 2; break; default: throw std::runtime_error( "CR_11_σpCoulombσp_11::generate_code: invalid component index"); } + // add_child() deduplicates children by instance (see generic_rr.h), so a + // collision among the deriv-ERI children would silently shift the src + // slots; fail the export loudly instead of emitting a wrong expression. + if (this->num_children() != nchildren) + throw std::logic_error("CR_11_σpCoulombσp_11::generate_code: component " + + std::to_string(comp) + " expects " + + std::to_string(nchildren) + " children, got " + + std::to_string(this->num_children())); rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs, nflops); } From f3d21c162b554f015cb8da577de1767a7f090610 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Fri, 23 Oct 2026 17:58:39 -0400 Subject: [PATCH 51/64] Express the permutational symmetry of 2-body operators as traits; document the RKB component conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Engine: operator_traits gains pair_swap (independent/coupled/none), braket_swap, pair_swap_phase(c), braket_swap_phase(c) and braket_swap_remap(c). Engine::compute2 canonicalizes shell sets and applies per-component phases and remaps from these traits; the per-operator branches for coulomb_opop, coulomb_op, opop_coulomb_opop and op_coulomb_op are gone. Engine::_initialize no longer leaves stale target/scratch buffers when the library lacks the kernels. Compiler: the 2-body operator descriptors gain the matching p1p2_swappable and pair_swap traits, ShellQuartetSetPredicate takes PairSwap instead of a bool, and build_TwoPRep_{2b_2k,1b_2k} derive the shell sets and the per-component targets (make_descr over max_key) from the descriptor instead of std::is_same branches. Adds the missing make_descr specializations for σpCoulombσp and Coulombσp. Generated shell-set coverage is unchanged (verified per task against the predicates). engine.h gains a doc page (\page rkb_operators) with one table of the component layouts, spin folding and naming (engine vs compiler vs task label) of the four RKB 2-body operators and opemuop. Also: the progress counter no longer counts each 2-body kernel twice, and print_config reports RKB 3-center ERIs (and spells "kinetic balance"). --- include/libint2/engine.h | 102 ++++++++++++++++ include/libint2/engine.impl.h | 208 ++++++++++++++------------------- src/bin/libint/build_libint.cc | 191 ++++++++++++------------------ src/bin/libint/oper.h | 39 ++++++- 4 files changed, 304 insertions(+), 236 deletions(-) diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 37ab7d010..00a76f422 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -272,6 +272,43 @@ enum class Operator { /// @param[in] oper an Operator object /// @return the particle rank of \c oper /// @throw std::logic_error if an invalid @c oper given +/** + \page rkb_operators Conventions for the relativistic (RKB) 2-body operators + + The four operators built from σ·p and 1/r₁₂ expose their components in + different ways; this table is the single reference for their layouts. + "Folded" means that the Pauli matrices were contracted using + σ_a σ_b = δ_ab + i ε_abc σ_c, so the user must not apply them again; + "not folded" means the result is the spatial tensor and the user contracts it + with the Pauli matrices. In chemists' notation (ab|cd) the operator acts on + the second function of a pair, i.e. on b and/or d. + + | Engine (C++ API) | Compiler | task label | #comp | + component index | spin | + |----------------------|--------------------|----------------|-------|------------------------------------------------------|--------| + | `coulomb_opop` | `Coulombσpσp` | `coulomb_opop` | 4 | 0: + ∇_c·∇_d, 1-3: (∇_c×∇_d)_{x,y,z} | folded | | `coulomb_op` | + `Coulombσp` | `coulomb_op` | 3 | ∂_{d,x}, ∂_{d,y}, ∂_{d,z} + (3-center only) | n/a | | `opop_coulomb_opop` | + `σpσpCoulombσpσp` | `opop_coulomb_opop` | 16 | 4·bra + ket, with bra, ket ∈ + {0: ∇·∇, 1-3: (∇×∇)_{x,y,z}} of (a,b) and (c,d); the sign of i² is folded into + the 1-3 ⊗ 1-3 block | folded | | `op_coulomb_op` | `σpCoulombσp` | + `op_coulomb_op` | 9 | SO(3) irreps of T_ab = ∂_{b,a} ∂_{d,b}: 0: trace, 1-3: + T_yz−T_zy, T_zx−T_xz, T_xy−T_yx, 4: T_xx−T_yy, 5: 2T_zz−T_xx−T_yy, 6-8: + T_xy+T_yx, T_xz+T_zx, T_yz+T_zy | not folded | + + The 1-body analog of `opVop` with the dipole operator in the middle is + `opemuop` (compiler: `σpeμσp`), with 12 = 3 (dipole direction, slow) × 4 + (folded Pauli component, fast) components. + + The behavior of the components under the permutations Engine uses to + canonicalize shell sets is encoded in operator_traits: + pair_swap, braket_swap, pair_swap_phase(), braket_swap_phase() and + braket_swap_remap(). The generated library covers exactly the shell sets that + are canonical under these permutations (see the matching descriptor traits + in the compiler). + */ + inline constexpr int rank(Operator oper) { return (oper >= Operator::first_1body_oper && oper <= Operator::last_1body_oper) @@ -294,6 +331,19 @@ inline constexpr bool is_nuclear_type(Operator oper) { oper == Operator::op_q_gau_op; } +/// which swaps of the two functions within the bra and within the ket of a +/// 2-body integral Engine may perform to canonicalize a shell set; the +/// generated kernels cover exactly the shell sets that are canonical with +/// respect to these swaps +enum class PairSwap { + /// bra and ket are sorted independently of each other + independent, + /// bra and ket are swapped together, or not at all (the ket is sorted) + coupled, + /// neither bra nor ket may be swapped + none +}; + namespace detail { struct default_operator_traits { typedef struct { @@ -303,6 +353,21 @@ struct default_operator_traits { // N.B.: Below field means we *should* template specialize operator_traits for // Operator::kinetic, but L2 doesn't use that anywhere. static constexpr auto intrinsic_deriv_order = 0u; + /// which swaps within bra and within ket of a 2-body integral the engine + /// may perform to canonicalize a shell set + static constexpr auto pair_swap = PairSwap::independent; + /// whether the engine may swap bra and ket (particles 1 and 2) of a 2-body + /// integral to canonicalize a shell set + static constexpr bool braket_swap = true; + /// @return the phase acquired by component @p c when the two functions of + /// the ket (and, if pair_swap is PairSwap::coupled, of the bra) are swapped + static constexpr int pair_swap_phase(unsigned int /* c */) { return 1; } + /// @return the phase acquired by component @p c when bra and ket are + /// swapped + static constexpr int braket_swap_phase(unsigned int /* c */) { return 1; } + /// @return the index of the component that component @p c becomes when bra + /// and ket are swapped + static constexpr unsigned int braket_swap_remap(unsigned int c) { return c; } struct _core_eval_type { template static std::shared_ptr instance(params...) { @@ -481,6 +546,13 @@ struct operator_traits : public operator_traits { static constexpr auto nopers = 4; static constexpr auto intrinsic_deriv_order = 2; + /// σ·p acts on the ket only, hence bra and ket cannot be swapped + static constexpr bool braket_swap = false; + /// swapping the ket functions swaps the two ∇ in ∇_c × ∇_d: the vector + /// components (1-3) change sign, the scalar (0) does not + static constexpr int pair_swap_phase(unsigned int c) { + return c > 0 ? -1 : 1; + } }; template <> struct operator_traits @@ -489,6 +561,10 @@ struct operator_traits /// index = a, a ∈ {x=0, y=1, z=2}. One intrinsic derivative (σ·p = one ∇). static constexpr auto nopers = 3; static constexpr auto intrinsic_deriv_order = 1; + /// σ·p acts on one specific function, hence no swaps (the kernels are + /// generated for every ordering of the paired centers) + static constexpr auto pair_swap = PairSwap::none; + static constexpr bool braket_swap = false; }; template <> struct operator_traits @@ -497,6 +573,18 @@ struct operator_traits /// index = 4 * bra_spin + ket_spin, where spin in {S=0, X=1, Y=2, Z=3} static constexpr auto nopers = 16; static constexpr auto intrinsic_deriv_order = 4; + /// (ab|cd) = (cd|ab) = (ba|dc): the functions of bra and ket can only be + /// swapped together + static constexpr auto pair_swap = PairSwap::coupled; + /// swapping both pairs changes the sign of a component with exactly one + /// vector (spin ≠ S) factor + static constexpr int pair_swap_phase(unsigned int c) { + return ((c / 4 > 0) != (c % 4 > 0)) ? -1 : 1; + } + /// swapping bra and ket transposes the (bra_spin, ket_spin) index + static constexpr unsigned int braket_swap_remap(unsigned int c) { + return 4 * (c % 4) + c / 4; + } }; template <> struct operator_traits @@ -508,6 +596,14 @@ struct operator_traits /// SymTLOffXY,SymTLOffXZ,SymTLOffYZ (6-8). static constexpr auto nopers = 9; static constexpr auto intrinsic_deriv_order = 2; + /// σ·p acts on one specific function of each pair, hence no swaps within + /// bra or ket + static constexpr auto pair_swap = PairSwap::none; + /// swapping bra and ket transposes T_ab: the antisymmetric components (1-3) + /// change sign, the scalar (0) and symmetric-traceless (4-8) do not + static constexpr int braket_swap_phase(unsigned int c) { + return (c >= 1 && c <= 3) ? -1 : 1; + } }; namespace detail { @@ -1288,6 +1384,12 @@ class Engine { unsigned int nparams() const; unsigned int nopers() const; unsigned int intrinsic_deriv_order() const; + /// operator_traits::pair_swap etc. (see \ref rkb_operators) + PairSwap pair_swap() const; + bool braket_swap() const; + int pair_swap_phase(unsigned int c) const; + int braket_swap_phase(unsigned int c) const; + unsigned int braket_swap_remap(unsigned int c) const; /// if Params == operator_traits::oper_params_type, will return /// \c any(params) /// else will set return \c any initialized with default value for diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index c0f08866b..3e87dea32 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -636,6 +636,9 @@ __libint2_engine_inline void Engine::_initialize() { LIBINT2_PREFIXED_NAME(libint2_cleanup_default)(&primdata_[0]); \ stack_size_ = 0; \ buildfnptrs_ = nullptr; \ + targets_.clear(); \ + scratch_.clear(); \ + scratch2_ = nullptr; \ return; \ } \ hard_lmax_ = BOOST_PP_CAT(LIBINT2_MAX_AM_, \ @@ -837,6 +840,33 @@ __libint2_engine_inline unsigned int Engine::intrinsic_deriv_order() const { abort(); } +// clang-format off +// accessors to the permutational-symmetry traits of oper_ (see +// operator_traits): RET NAME ARGS const { return +// operator_traits::TRAIT_EXPR; } +#define BOOST_PP_NBODYENGINE_MCR10(r, trait_expr, i, elem) \ + case i: \ + return operator_traits(i)>::trait_expr; +#define LIBINT2_ENGINE_OPERATOR_TRAIT_ACCESSOR(RET, NAME, ARGS, TRAIT_EXPR) \ + __libint2_engine_inline RET Engine::NAME ARGS const { \ + switch (static_cast(oper_)) { \ + BOOST_PP_LIST_FOR_EACH_I(BOOST_PP_NBODYENGINE_MCR10, TRAIT_EXPR, \ + BOOST_PP_NBODY_OPERATOR_LIST) \ + default: \ + break; \ + } \ + assert(false && "missing case in switch"); /* unreachable */ \ + abort(); \ + } +LIBINT2_ENGINE_OPERATOR_TRAIT_ACCESSOR(PairSwap, pair_swap, (), pair_swap) +LIBINT2_ENGINE_OPERATOR_TRAIT_ACCESSOR(bool, braket_swap, (), braket_swap) +LIBINT2_ENGINE_OPERATOR_TRAIT_ACCESSOR(int, pair_swap_phase, (unsigned int c), pair_swap_phase(c)) +LIBINT2_ENGINE_OPERATOR_TRAIT_ACCESSOR(int, braket_swap_phase, (unsigned int c), braket_swap_phase(c)) +LIBINT2_ENGINE_OPERATOR_TRAIT_ACCESSOR(unsigned int, braket_swap_remap, (unsigned int c), braket_swap_remap(c)) +#undef LIBINT2_ENGINE_OPERATOR_TRAIT_ACCESSOR +#undef BOOST_PP_NBODYENGINE_MCR10 +// clang-format on + template <> __libint2_engine_inline any Engine::enforce_params_type( Operator oper, const any& params, bool throw_if_wrong_type) { @@ -1269,67 +1299,43 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( #if LIBINT2_SHELLQUARTET_SET == \ LIBINT2_SHELLQUARTET_SET_STANDARD // standard angular momentum ordering + // canonicalize the shell set according to the permutational symmetry of + // the operator (see operator_traits: pair_swap, braket_swap); the kernels + // are generated for the canonical shell sets only bool swap_braket; bool swap_tbra, swap_tket; - if (oper_ == Operator::opop_coulomb_opop) { - // For σpσpCoulombσpσp: (ab|cd) = (cd|ab) = (ba|dc)* = (dc|ba)* - // Canonical form: lc >= ld (or la >= lb when lc == ld), - // la+lb <= lc+ld (or max(la,lb) <= lc when sums equal) + { + const auto pair_swap = this->pair_swap(); const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; const auto bra_max = std::max(tbra1.contr[0].l, tbra2.contr[0].l); const auto ket_max = std::max(tket1.contr[0].l, tket2.contr[0].l); - swap_braket = ((braket_ == BraKet::xx_xx) && + // xx_xs is the alias of xs_xx, hence always swapped; else swap if the bra + // is "heavier" than the ket; with coupled pair swaps the kernels are + // generated for max(la,lb) <= lc when the totals tie + swap_braket = braket_ == BraKet::xx_xs || + (braket_ == BraKet::xx_xx && this->braket_swap() && (bra_total > ket_total || - (bra_total == ket_total && bra_max > ket_max))) || - braket_ == BraKet::xx_xs; - // Coupled swap: after braket swap, sort the pair that ends up in ket - // position to ensure lc >= ld; when lc == ld, also sort bra (la >= lb) - if (swap_braket) { - // After braket swap: new ket = original bra, new bra = original ket. - // Coupled swap sorts new ket (ensure lc >= ld). - const bool swap_p1p2 = (tbra1.contr[0].l < tbra2.contr[0].l); - swap_tbra = swap_tket = swap_p1p2; - } else { - // No braket swap: ket stays as original ket. - // Coupled swap sorts ket (ensure lc >= ld). - const bool swap_p1p2 = (tket1.contr[0].l < tket2.contr[0].l); - swap_tbra = swap_tket = swap_p1p2; + (pair_swap == PairSwap::coupled && bra_total == ket_total && + bra_max > ket_max))); + switch (pair_swap) { + case PairSwap::independent: + swap_tbra = (tbra1.contr[0].l < tbra2.contr[0].l); + swap_tket = (tket1.contr[0].l < tket2.contr[0].l); + break; + case PairSwap::coupled: { + // sort the pair that ends up in the ket of the kernel, and swap the + // other pair along with it + const bool swap_pairs = swap_braket + ? (tbra1.contr[0].l < tbra2.contr[0].l) + : (tket1.contr[0].l < tket2.contr[0].l); + swap_tbra = swap_tket = swap_pairs; + } break; + case PairSwap::none: + swap_tbra = swap_tket = false; + break; } - } else if (oper_ == Operator::op_coulomb_op) { - // σpCoulombσp: only bra↔ket (particle 1↔2) swap is a symmetry (with - // (a,b)↔(b,a) component remap). Within-side swap is NOT a symmetry - // because σ·p attaches to one specific function per side; moving it to - // the other function changes the integral in a way IBP cannot recover - // across electrons. Canonical form: la+lb <= lc+ld only. - const auto bra_total = tbra1.contr[0].l + tbra2.contr[0].l; - const auto ket_total = tket1.contr[0].l + tket2.contr[0].l; - swap_braket = ((braket_ == BraKet::xx_xx) && (bra_total > ket_total)) || - braket_ == BraKet::xx_xs; - swap_tbra = false; - swap_tket = false; - } else if (oper_ == Operator::coulomb_op) { - // Coulombσp (3-center single-σ·p): σ·p attaches to the 2nd ket function ν, - // so within-ket swap is NOT a symmetry (it would move σ·p onto μ). The - // kernel is generated for every (lc,ld), so no canonicalizing swap is - // needed. bra↔ket swap IS a symmetry of 1/r12 and leaves the 3 Cartesian - // components unchanged (the vector index is the ket-side σ·p direction - // only — no transpose, no sign flip). Only xx_xs needs a braket swap. - swap_braket = (braket_ == BraKet::xx_xs); - swap_tbra = false; - swap_tket = false; - } else { - swap_braket = ((braket_ == BraKet::xx_xx) && - (tbra1.contr[0].l + tbra2.contr[0].l > - tket1.contr[0].l + tket2.contr[0].l) && - (oper_ != Operator::coulomb_opop)) || - braket_ == BraKet::xx_xs; - swap_tbra = (tbra1.contr[0].l < tbra2.contr[0].l); - swap_tket = (tket1.contr[0].l < tket2.contr[0].l); } - - // N.B. cannot swap bra and ket for coulomb_opop since the ket is mutated by - // this operator #else // orca angular momentum ordering const auto swap_tbra = (tbra1.contr[0].l > tbra2.contr[0].l); const auto swap_tket = (tket1.contr[0].l > tket2.contr[0].l); @@ -2155,6 +2161,14 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( const auto* src_row_ptr = source; auto tgt_ptr = target; + // the phase of this component due to the swaps (see + // operator_traits); N.B. shell sets are ordered as + // deriv index * nopers + component + const auto component = s % this->nopers(); + const value_type component_phase = + (swap_ket ? this->pair_swap_phase(component) : 1) * + (swap_braket ? this->braket_swap_phase(component) : 1); + // if permuting derivatives ints must update their derivative index // Additional BraKet types would require adding support to // DerivMapGenerator::generate_deriv_index_map @@ -2211,70 +2225,20 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( tgt_ptr + tgt_col_idx, nr1_tgt, nr2_tgt, Eigen::Stride( nr2_tgt * ncol_tgt, ncol_tgt)); - // Coupled swap sign correction for multi-component operators. - // N.B. s indexes operator components only because RKB - // integrals have no geometric derivatives (see initialize()). - // TODO: the per-component swap phase and the component remap - // below are hardcoded per operator (op_coulomb_op, - // opop_coulomb_opop, ...). These belong with the operator - // definition; expose them as traits (e.g. swap_phase(s), - // swap_remap(s)) so new multi-component operators need no new - // branch here. - Shell::real_t oper_cart_component_phase = 1.0; - if (swap_tket && oper_ == Operator::opop_coulomb_opop) { - const bool bra_is_spin = (s / 4) > 0; - const bool ket_is_spin = (s % 4) > 0; - if (bra_is_spin != ket_is_spin) - oper_cart_component_phase = -1.0; - } - // coulomb_opop is bra<->ket swapped only in BraKet::xx_xs - // (the 3-center alias of xs_xx), where tket2 is the unit shell, - // hence swap_tket is false. After swap_braket the user's bra - // (AO pair) becomes the kernel's ket where σ·p attaches. If - // swap_tbra is also applied to canonicalize that AO pair, - // σ·p_a σ·p_b ↔ σ·p_b σ·p_a — antisymmetric, so x,y,z - // components (s>0) pick up a -1 sign. (The scalar component - // s=0 is invariant.) - assert(!(swap_tket && oper_ == Operator::coulomb_opop)); - if (swap_tbra && oper_ == Operator::coulomb_opop && s > 0) - oper_cart_component_phase = -1.0; - // op_coulomb_op irrep layout under bra↔ket swap: antisym - // components (s ∈ {1,2,3}) flip sign; scalar (0) and sym-TL - // (4..8) are invariant. swap_tket is always false for this - // operator; the sign correction applies on swap_braket alone. - if (oper_ == Operator::op_coulomb_op && s >= 1 && s <= 3) - oper_cart_component_phase = -1.0; if (swap_tbra) - tgt_blk_mat = - oper_cart_component_phase * src_blk_mat.transpose(); + tgt_blk_mat = component_phase * src_blk_mat.transpose(); else - tgt_blk_mat = oper_cart_component_phase * src_blk_mat; + tgt_blk_mat = component_phase * src_blk_mat; } else { // source row {r1,r2} is mapped to target row {r1,r2} if // !swap_tbra, else to {r2,r1} const auto tgt_row_idx = !swap_tbra ? r1 * nr2 + r2 : r2 * nr1 + r1; Map tgt_blk_mat(tgt_ptr + tgt_row_idx * ncol, nc1_tgt, nc2_tgt); - if (swap_tket) { - Shell::real_t oper_cart_component_phase = 1.0; - if (oper_ == Operator::opop_coulomb_opop) { - // Option A ordering: index = 4*bra + ket - // Coupled swap (a<->b AND c<->d) flips sign when exactly - // one of bra/ket is a cross product (spin != S): - // bra_spin = s/4, ket_spin = s%4 (0=S, 1-3=X/Y/Z) - const bool bra_is_spin = (s / 4) > 0; - const bool ket_is_spin = (s % 4) > 0; - if (bra_is_spin != ket_is_spin) - oper_cart_component_phase = -1.0; - } - if (oper_ == Operator::coulomb_opop && s > 0) - oper_cart_component_phase = - -1.0; // x,y,z quaternion components flip sign on - // swapping ket for coulomb_opop - tgt_blk_mat = - oper_cart_component_phase * src_blk_mat.transpose(); - } else - tgt_blk_mat = src_blk_mat; + if (swap_tket) + tgt_blk_mat = component_phase * src_blk_mat.transpose(); + else + tgt_blk_mat = component_phase * src_blk_mat; } } // end of loop } // over rows of source @@ -2300,20 +2264,22 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( } } // loop over shellsets - // For opop_coulomb_opop with swap_braket: swapping particles remaps - // component (α,β) → (β,α). With Option A ordering (index=4*bra+ket), - // this is a matrix transpose: s_new = 4*(s%4) + (s/4). - if (permute && oper_ == Operator::opop_coulomb_opop && swap_braket) { - // N.B. assumes no geometric derivatives (see initialize()) - assert(ntargets == 16); - std::array temp; - for (auto s = 0; s != ntargets; ++s) temp[s] = targets_[s]; - for (auto s = 0; s != ntargets; ++s) - targets_[4 * (s % 4) + (s / 4)] = temp[s]; + // swapping bra and ket may permute the components (see + // operator_traits::braket_swap_remap) + if (permute && swap_braket) { + const auto nopers = this->nopers(); + bool trivial = true; + for (auto c = 0u; c != nopers && trivial; ++c) + trivial = (this->braket_swap_remap(c) == c); + if (!trivial) { + const auto tmp = targets_; + for (auto s = 0; s != ntargets; ++s) { + const auto d = s / nopers; + const auto c = s % nopers; + targets_[d * nopers + this->braket_swap_remap(c)] = tmp[s]; + } + } } - // op_coulomb_op irrep layout: bra↔ket swap is handled in-place by - // oper_cart_component_phase above (sign flip on antisym components, - // identity on scalar / sym-TL); no pointer remap is needed. } // if need_scratch => needed to transpose and/or tform else { // did not use scratch? may still need to update targets_ if (set_targets_) { diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 752b48b45..c172654b0 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -76,40 +76,52 @@ enum ShellSetType { }; template struct ShellQuartetSetPredicate { - // return true if this set of angular momenta is included - static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true); + /// @return true if code is to be generated for this set of angular momenta, + /// i.e. if it is canonical with respect to the permutations the engine + /// performs for this operator (see the descriptor traits p1p2_swappable and + /// pair_swap, and operator_traits in the engine) + /// @param p1p2_swappable whether the operator allows swapping particles 1 + /// and 2 (bra and ket in chemists' notation) + /// @param pair_swap which swaps of the two functions within bra and within + /// ket the operator allows + static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true, + PairSwap pair_swap = PairSwap::independent); }; - -/** - * standard ordering for angular momenta la, lb, lc, ld - * @param p1p2_swappable whether operator allows swaps of particle 1 and 2 - * functions (e.g., not allowed for Coulombσpσp but allowed - * for Coulomb (TwoPRep)). - * @param bra_ket_coswappable whether need to swap within both bra and ket. - * Not individually swapping of either ket of bra allowed - * ( e.g., for σpσpCoulombσpσp) - */ template <> struct ShellQuartetSetPredicate { static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true, - bool bra_ket_coswappable = false) { - if (bra_ket_coswappable) - return lc >= ld && (la + lb < lc + ld || - (la + lb == lc + ld && std::max(la, lb) <= lc)); - else - return la >= lb && lc >= ld && (!p1p2_swappable || la + lb <= lc + ld); + PairSwap pair_swap = PairSwap::independent) { + switch (pair_swap) { + case PairSwap::independent: + return la >= lb && lc >= ld && (!p1p2_swappable || la + lb <= lc + ld); + case PairSwap::coupled: + // the ket is sorted and the bra follows; when the totals tie the + // engine swaps bra and ket such that max(la,lb) <= lc + assert(p1p2_swappable); + return lc >= ld && (la + lb < lc + ld || + (la + lb == lc + ld && std::max(la, lb) <= lc)); + case PairSwap::none: + return !p1p2_swappable || la + lb <= lc + ld; + } + abort(); } }; template <> struct ShellQuartetSetPredicate { static bool value(int la, int lb, int lc, int ld, bool p1p2_swappable = true, - bool bra_ket_coswappable = false) { - if (bra_ket_coswappable) - return lc <= ld && (la + lb > lc + ld || - (la + lb == lc + ld && std::min(la, lb) >= lc)); - else - return la <= lb && lc <= ld && - (!p1p2_swappable || (la < lc || (la == lc && lb <= ld))); + PairSwap pair_swap = PairSwap::independent) { + switch (pair_swap) { + case PairSwap::independent: + return la <= lb && lc <= ld && + (!p1p2_swappable || (la < lc || (la == lc && lb <= ld))); + case PairSwap::coupled: + assert(p1p2_swappable); + return lc <= ld && (la + lb > lc + ld || + (la + lb == lc + ld && std::min(la, lb) >= lc)); + case PairSwap::none: + return !p1p2_swappable || la + lb >= lc + ld; + } + abort(); } }; template @@ -328,6 +340,16 @@ template <> return σpσpCoulombσpσp_Descr(p); } +template <> +σpCoulombσp_Descr make_descr<σpCoulombσp_Descr>(int p, int, int) { + return σpCoulombσp_Descr(p); +} + +template <> +Coulombσp_Descr make_descr(int p, int, int) { + return Coulombσp_Descr(p); +} + template void build_onebody_1b_1k(std::ostream& os, std::string label, const std::shared_ptr& cparams, @@ -1237,10 +1259,15 @@ void print_config(std::ostream& os) { os << "Will support G12DKH" << endl; #endif #ifdef LIBINT_INCLUDE_RKB_ERI - os << "Will support restricted kinetically balance (RKB) 4-center ERIs " - << std::endl; + os << "Will support restricted kinetic balance (RKB) 4-center ERIs"; if (LIBINT_INCLUDE_RKB_ERI > 0) - os << "(deriv order = " << LIBINT_INCLUDE_RKB_ERI << ")"; + os << " (deriv order = " << LIBINT_INCLUDE_RKB_ERI << ")"; + os << endl; +#endif +#ifdef LIBINT_INCLUDE_RKB_ERI3 + os << "Will support restricted kinetic balance (RKB) 3-center ERIs"; + if (LIBINT_INCLUDE_RKB_ERI3 > 0) + os << " (deriv order = " << LIBINT_INCLUDE_RKB_ERI3 << ")"; os << endl; #endif } @@ -1282,21 +1309,11 @@ static void build_TwoPRep_2b_2k( std::shared_ptr context(new CppCodeContext(cparams)); std::shared_ptr memman(new WorstFitMemoryManager()); - // σpCoulombσp has a 2-fold bra↔ket-swap symmetry, with per-component sign - // flips under the swap (Antisym* flip sign; Scalar/SymTL* invariant) — this - // is captured by p1_p2_swappable=true plus a dedicated predicate that - // canonicalizes only la+lb<=lc+ld and emits code for *all* within-side - // orderings (within-side swap is NOT a symmetry — σ·p attaches to one - // specific function per side, and IBP cannot recover the sign across the - // 1/r12 coupling). - // TODO: the per-operator swappability/component-count rules are spread across - // many std::is_same branches here (and the descriptor loops - // and strategy specializations). These are properties of the operator - // descriptor; expose them as descriptor traits (e.g. ncomponents(), - // p1p2_swappable(), bra_ket_coswappable()) and query generically so a new - // operator needs no edit here. - bool p1_p2_swappable = !std::is_same::value; - bool bra_ket_coswappable = std::is_same::value; + // the shell sets to generate code for are those that are canonical with + // respect to the permutations the engine performs for this operator + // (see the descriptor's p1p2_swappable and pair_swap) + constexpr bool p1p2_swappable = OperDescrType::p1p2_swappable; + constexpr PairSwap pair_swap = OperDescrType::pair_swap; // Note: la, lb, lc, ld generate code for chemist notation (ab|O|cd), where O // is a two-body operator. @@ -1304,23 +1321,10 @@ static void build_TwoPRep_2b_2k( for (unsigned int lb = 0; lb <= lmax; lb++) { for (unsigned int lc = 0; lc <= lmax; lc++) { for (unsigned int ld = 0; ld <= lmax; ld++) { - // σpCoulombσp: only bra↔ket (particle 1↔2) swap is a symmetry. - // Within-side swap is NOT (σ·p would move to a different physical - // center; IBP cannot repair the sign across 1/r12). Dedicated - // predicate canonicalizes la+lb<=lc+ld only (ORCA: >=) and accepts - // all within-side orderings. - if constexpr (std::is_same::value) { -#if LIBINT_SHELL_SET == LIBINT_SHELL_SET_STANDARD - if (!(la + lb <= lc + ld)) continue; -#else - if (!(la + lb >= lc + ld)) continue; -#endif - } else { - if (!ShellQuartetSetPredicate( - LIBINT_SHELL_SET)>::value(la, lb, lc, ld, p1_p2_swappable, - bra_ket_coswappable)) - continue; - } + if (!ShellQuartetSetPredicate( + LIBINT_SHELL_SET)>::value(la, lb, lc, ld, p1p2_swappable, + pair_swap)) + continue; // std::shared_ptr tactic(new ParticleDirectionTactic(la+lb > // lc+ld ? false : true)); @@ -1337,32 +1341,10 @@ static void build_TwoPRep_2b_2k( ///////////////////////////////// // loop over operator components ///////////////////////////////// - std::vector descrs(1); - if constexpr (std::is_same::value) { - // reset descriptors array - descrs.resize(0); - // iterate over 4 quaternion components (single spin space) - for (int p = 0; p != 4; ++p) { - descrs.emplace_back(OperDescrType(p)); - } - } - if constexpr (std::is_same::value) { - // reset descriptors array - descrs.resize(0); - // iterate over 16 components (tensor product of two spin spaces) - for (int p = 0; p != 16; ++p) { - descrs.emplace_back(OperDescrType(p)); - } - } - if constexpr (std::is_same::value) { - // reset descriptors array - descrs.resize(0); - // iterate over 9 SO(3) irrep components: 1 scalar trace + 3 - // antisym (curl-curl) + 5 sym-traceless - for (int p = 0; p != 9; ++p) { - descrs.emplace_back(OperDescrType(p)); - } - } + // one target per operator component + std::vector descrs; + for (unsigned int p = 0; p != OperDescrType::max_key; ++p) + descrs.emplace_back(make_descr(p)); // unroll only if max_am <= cparams->max_am_opt(task) using std::max; @@ -1512,9 +1494,6 @@ static void build_TwoPRep_2b_2k( dg_xxxx->reset(); memman->reset(); - ++g_progress.done; - g_progress.print(); - } // end of d loop } // end of c loop } // end of b loop @@ -1575,16 +1554,11 @@ static void build_TwoPRep_1b_2k( for (unsigned int lbra = 0; lbra <= lmax; lbra++) { for (unsigned int lc = 0; lc <= lmax_default; lc++) { for (unsigned int ld = 0; ld <= lmax_default; ld++) { - // eliminate some cases depending on the desired convention. - // The Standard/ORCA triangle (lc>=ld / lc<=ld) is valid only when the - // ket pair (μ,ν) is permutationally symmetric, so the engine can - // canonicalize by swapping μ↔ν. CoulombσpOper has a single σ·p on ν - // only, so that swap is NOT a symmetry — the engine never swaps and - // indexes the build table by the exact (l(μ),l(ν)); we must therefore - // emit the FULL (lc,ld) square. (Plain ERI3 and CoulombσpσpOper keep - // the triangle: ERI3's ket pair is symmetric, and Coulombσpσp carries - // σ·p on both ket functions so μ↔ν stays a symmetry.) - if constexpr (!std::is_same::value) { + // eliminate some cases depending on the desired convention: the + // Standard/ORCA triangle (lc>=ld / lc<=ld) applies only if the engine + // may swap the two functions of the paired (AO) centers (see the + // descriptor's pair_swap) + if (OperDescrType::pair_swap != PairSwap::none) { if (!ShellTripletSetPredicate( LIBINT_SHELL_SET)>::value(lbra, lc, ld)) continue; @@ -1607,17 +1581,10 @@ static void build_TwoPRep_1b_2k( if (!(lbra == lim && lc == lim && ld == lim)) continue; #endif - // operator components: 1 for plain TwoPRep, 4 for RKB CoulombσpσpOper - // (σ·p on both AO ket fns), 3 for RKB CoulombσpOper (single σ·p → - // x,y,z) - std::vector descrs(1); - if constexpr (std::is_same::value) { - descrs.resize(0); - for (int p = 0; p != 4; ++p) descrs.emplace_back(OperDescrType(p)); - } else if constexpr (std::is_same::value) { - descrs.resize(0); - for (int p = 0; p != 3; ++p) descrs.emplace_back(OperDescrType(p)); - } + // one target per operator component + std::vector descrs; + for (unsigned int p = 0; p != OperDescrType::max_key; ++p) + descrs.emplace_back(make_descr(p)); const auto nopers = descrs.size(); // unroll only if max_am <= cparams->max_am_opt(task) @@ -1789,8 +1756,6 @@ static void build_TwoPRep_1b_2k( #endif dg_xxx->reset(); memman->reset(); - ++g_progress.done; - g_progress.print(); } // end of d loop } // end of c loop } // end of bra loop @@ -1989,8 +1954,6 @@ void build_TwoPRep_1b_1k(std::ostream& os, #endif dg_xxx->reset(); memman->reset(); - ++g_progress.done; - g_progress.print(); } // end of ket loop } // end of bra loop } diff --git a/src/bin/libint/oper.h b/src/bin/libint/oper.h index 73c7963c0..74d62c4c9 100644 --- a/src/bin/libint/oper.h +++ b/src/bin/libint/oper.h @@ -238,6 +238,19 @@ typedef OperatorProperties<2, false, PermutationalSymmetry::symm> typedef OperatorProperties<2, false, PermutationalSymmetry::nonsymm> NonmultiplicativeNonsymm2Body_Props; +/// which swaps of the two functions within the bra and within the ket of a +/// 2-body integral the engine may perform to canonicalize a shell set; the +/// generated kernels cover exactly the shell sets that are canonical with +/// respect to these swaps. Must match PairSwap in the engine (engine.h). +enum class PairSwap { + /// bra and ket are sorted independently of each other + independent, + /// bra and ket are swapped together, or not at all (the ket is sorted) + coupled, + /// neither bra nor ket may be swapped + none +}; + /** GenMultSymmOper is a generic multiplicative symmetric N-body operator */ template @@ -430,6 +443,11 @@ using SphericalMultipoleOper = GenOper; struct TwoPRep_Descr : public Contractable { typedef MultiplicativeSymm2Body_Props Properties; static const unsigned int max_key = 1; + /// whether the engine may swap particles 1 and 2 (bra and ket in chemists' + /// notation) to canonicalize a shell set + static constexpr bool p1p2_swappable = true; + /// which swaps within bra and within ket the engine may perform + static constexpr PairSwap pair_swap = PairSwap::independent; unsigned int key() const { return 0; } std::string description() const { return "1/r_{12}"; } std::string label() const { return "TwoPRep"; } @@ -438,11 +456,18 @@ struct TwoPRep_Descr : public Contractable { }; typedef GenOper TwoPRep; -/** Coulombσpσp is the two-body repulsion operator. +/** Coulombσpσp: (a b | 1/r_{12} | σ·p c σ·p d), the Coulomb operator with + * σ·p acting on both functions of particle 2. The Dirac identity + * σ_i σ_j = δ_ij + i ε_ijk σ_k folds the Pauli matrices into 4 quaternion + * components: 0: ∇_c·∇_d, 1-3: (∇_c × ∇_d)_{x,y,z}. */ struct Coulombσpσp_Descr : public Contractable { // σ·p acts on particle 2 only, hence no particle 1<->2 symmetry typedef MultiplicativeNonsymm2Body_Props Properties; + static constexpr bool p1p2_swappable = false; + /// swapping c and d changes the sign of components 1-3 (handled by the + /// engine) + static constexpr PairSwap pair_swap = PairSwap::independent; Coulombσpσp_Descr() : quaternion_index_(0) {} Coulombσpσp_Descr(int quaternion_index) @@ -489,6 +514,10 @@ typedef GenOper CoulombσpσpOper; struct Coulombσp_Descr : public Contractable { // σ·p acts on particle 2 only, hence no particle 1<->2 symmetry typedef MultiplicativeNonsymm2Body_Props Properties; + static constexpr bool p1p2_swappable = false; + /// σ·p acts on d only, hence c and d cannot be swapped: kernels are + /// generated for every ordering of the paired centers + static constexpr PairSwap pair_swap = PairSwap::none; Coulombσp_Descr() : cartesian_index_(0) {} Coulombσp_Descr(int cartesian_index) : cartesian_index_(cartesian_index) { @@ -523,6 +552,10 @@ typedef GenOper CoulombσpOper; struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr> { // particle 1<->2 symmetric up to a remap of the components typedef MultiplicativeSymm2Body_Props Properties; + static constexpr bool p1p2_swappable = true; + /// (ab|cd) = (ba|dc) up to the sign of some components, but + /// (ab|cd) != (ba|cd): the pairs can only be swapped together + static constexpr PairSwap pair_swap = PairSwap::coupled; σpσpCoulombσpσp_Descr() : quaternion_index_(0) {} σpσpCoulombσpσp_Descr(int quaternion_index) @@ -572,6 +605,10 @@ typedef GenOper<σpσpCoulombσpσp_Descr> σpσpCoulombσpσpOper; struct σpCoulombσp_Descr : public Contractable<σpCoulombσp_Descr> { // particle 1<->2 symmetric up to the sign of the antisymmetric components typedef MultiplicativeSymm2Body_Props Properties; + static constexpr bool p1p2_swappable = true; + /// σ·p acts on one specific function of each pair, hence no swaps within + /// bra or ket + static constexpr PairSwap pair_swap = PairSwap::none; /// SO(3) irreducible components of the rank-2 Cartesian tensor T_{ab}. enum Component : int { From 884e8d733932bafa3e8d7059e9ac691ee022f414 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Fri, 23 Oct 2026 17:58:41 -0400 Subject: [PATCH 52/64] Build/test/doc cleanups for the RKB integrals - Libint compiler requires C++17 (it uses if constexpr); was compiled as a C++14 extension on clang and relied on the gcc default elsewhere - code generation progress: on a terminal keep the single updating line; in logs (CI) print one line per kernel so a failure can be attributed - the Hartree-Fock examples are only built/tested if the library has 4-center Coulomb integrals, so the check target passes for an RKB-only library - config.h: fix copy-pasted comments of the RKB options - CHANGES: document the new std::invalid_argument for cartesian fitting shells with LIBINT2_ERI3_PURE_SH and the compiler's C++17 requirement --- CHANGES | 5 ++- INSTALL.md | 2 +- export/tests/CMakeLists.txt | 3 ++ include/libint2/config.h.cmake.in | 4 +- src/bin/libint/CMakeLists.txt | 2 +- src/bin/libint/buildtest.h | 65 +++++++++++++++++++++---------- 6 files changed, 56 insertions(+), 25 deletions(-) diff --git a/CHANGES b/CHANGES index a7f603710..20ca8ae71 100644 --- a/CHANGES +++ b/CHANGES @@ -10,7 +10,10 @@ Following is a brief summary of changes made in each release of Libint. and 1-body Operator::opemuop. N.B. Operator::opemuop is inserted after the other 1-body operators, hence the numeric values of all 2-body Operator enumerators increase by 1. Engine::compute() now throws Engine::missing_kernel if the library was not configured to - compute the requested operator/braket/derivative order (previously the result was undefined). + compute the requested operator/braket/derivative order (previously the result was undefined), + and std::invalid_argument if given a cartesian fitting shell with l > 1 when the library was + configured with LIBINT2_ERI3_PURE_SH (previously a Debug-only assert). The Libint compiler + now requires C++17; the generated library still requires C++11 only. - PR #440: incremental builds re-export the library when include/libint2, export/tests, etc. change, without recompiling unchanged library sources (#438) - PR #436: GenericGmEval no longer copies the core evaluator on every call; fixes poor thread diff --git a/INSTALL.md b/INSTALL.md index 9109d1af2..d96fd8014 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -113,7 +113,7 @@ Use combined targets like `cmake --target check install` to avoid some unnecessa * `🔸` required or recommended, but there's a path forward without * `-` not involved -[^1]: C++ compiler that supports C++11 standard. C++11 standard is the fourth most recent international standard for C++, hence most modern compilers support it fully. A common compiler flag is `-std=c++11`, which CMake will impose on the compilation. +[^1]: C++ compiler that supports C++11 standard (the Libint compiler itself requires C++17). C++11 standard is the fourth most recent international standard for C++, hence most modern compilers support it fully. A common compiler flag is `-std=c++11`, which CMake will impose on the compilation. [^2]: Fortran 2003 compiler to enable Fortran bindings generation. diff --git a/export/tests/CMakeLists.txt b/export/tests/CMakeLists.txt index f5a24c5a2..94a0b74f5 100644 --- a/export/tests/CMakeLists.txt +++ b/export/tests/CMakeLists.txt @@ -150,6 +150,8 @@ if (LIBINT2_REQUIRE_CXX_API) FIXTURES_REQUIRED LIBINT2_UNIT_TESTS_EXEC ) + # the Hartree-Fock examples need 4-center Coulomb integrals + if (NOT "${Libint2_MAX_AM_ERI}" STREQUAL "") add_executable( hf-libint2 EXCLUDE_FROM_ALL @@ -240,6 +242,7 @@ if (LIBINT2_REQUIRE_CXX_API) PROPERTIES FIXTURES_REQUIRED LIBINT2_HFXXTEST_EXEC ) + endif() # 4-center ERI endif (LIBINT2_REQUIRE_CXX_API) diff --git a/include/libint2/config.h.cmake.in b/include/libint2/config.h.cmake.in index 15bf069e8..da515400c 100644 --- a/include/libint2/config.h.cmake.in +++ b/include/libint2/config.h.cmake.in @@ -71,7 +71,7 @@ #undef LIBINT_INCLUDE_ERI #endif -/* Support ERI derivatives up to this order */ +/* Support 4-center RKB ERI derivatives up to this order */ #define LIBINT_INCLUDE_RKB_ERI @LIBINT_INCLUDE_RKB_ERI@ #if @LIBINT_INCLUDE_RKB_ERI@ == -1 #undef LIBINT_INCLUDE_RKB_ERI @@ -144,7 +144,7 @@ /* Max optimized AM for RKB_ERI (same for all derivatives; if not defined see LIBINT_RKB_ERI_OPT_AM_LIST) */ #cmakedefine LIBINT_RKB_ERI_OPT_AM @LIBINT_RKB_ERI_OPT_AM@ -/* Max optimized AM for ERI and its derivatives */ +/* Max optimized AM for RKB_ERI and its derivatives */ #cmakedefine LIBINT_RKB_ERI_OPT_AM_LIST "@LIBINT_RKB_ERI_OPT_AM_LIST@" /* Max AM for the 3-center RKB ERI density fitting (auxiliary/DF) basis on the single fitting center diff --git a/src/bin/libint/CMakeLists.txt b/src/bin/libint/CMakeLists.txt index ebb7983b7..4e452727f 100644 --- a/src/bin/libint/CMakeLists.txt +++ b/src/bin/libint/CMakeLists.txt @@ -52,7 +52,7 @@ target_compile_options( target_compile_features( libint-libcompiler PUBLIC - "cxx_std_11" + "cxx_std_17" # if constexpr ) set_target_properties( diff --git a/src/bin/libint/buildtest.h b/src/bin/libint/buildtest.h index f63aed489..67eecd6c1 100644 --- a/src/bin/libint/buildtest.h +++ b/src/bin/libint/buildtest.h @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -38,46 +39,70 @@ #include #include #include +#ifdef _WIN32 +#include +#else +#include +#endif -/// Progress tracker for code generation. +/// Progress tracker for code generation. On a terminal shows a single +/// updating line; otherwise (e.g. in CI logs) prints one line per kernel so +/// that a failure can be attributed to the kernel being generated. struct CodeGenProgress { unsigned int done = 0; std::string current_task; std::chrono::steady_clock::time_point start_time; bool started = false; + bool interactive = false; + std::string last_printed_task; void start() { start_time = std::chrono::steady_clock::now(); started = true; + interactive = libint2_stderr_is_a_terminal(); + } + + std::string elapsed() const { + const auto secs = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time) + .count(); + std::ostringstream oss; + oss << std::setfill('0') << std::setw(2) << secs / 60 << ":" << std::setw(2) + << secs % 60; + return oss.str(); } - void print() const { + void print() { if (!started) return; - static const char spinner[] = "|/-\\"; - const auto elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start_time) - .count(); - const auto mins = elapsed / 60; - const auto secs = elapsed % 60; - std::cerr << "\r " << spinner[done % 4] << " " << std::setfill('0') - << std::setw(2) << mins << ":" << std::setw(2) << secs - << std::setfill(' ') << " [" << done << " functions generated] " - << current_task << " " << std::flush; + if (interactive) { + static const char spinner[] = "|/-\\"; + std::cerr << "\r " << spinner[done % 4] << " " << elapsed() << " [" + << done << " functions generated] " << current_task + << " " << std::flush; + } else if (current_task != last_printed_task) { + std::cerr << " " << elapsed() << " [" << done << "] " << current_task + << std::endl; + last_printed_task = current_task; + } } void finish() { if (!started) return; - const auto elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start_time) - .count(); - const auto mins = elapsed / 60; - const auto secs = elapsed % 60; - std::cerr << "\r done " << std::setfill('0') << std::setw(2) << mins - << ":" << std::setw(2) << secs << std::setfill(' ') << " [" + std::cerr << (interactive ? "\r" : "") << " done " << elapsed() << " [" << done << " functions generated]" - << " " << std::endl; + << (interactive ? " " : "") + << std::endl; started = false; } + + private: + static bool libint2_stderr_is_a_terminal() { +#ifdef _WIN32 + return _isatty(_fileno(stderr)) != 0; +#else + return isatty(fileno(stderr)) != 0; +#endif + } }; extern CodeGenProgress g_progress; From edf8161363381661f01796d69477a0ab4adef065 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 18:19:52 -0400 Subject: [PATCH 53/64] Engine: pure-SH fitting-shell checks apply to energy kernels only; reject xx_xs derivatives early; defined AM-limit state - The std::invalid_argument for a cartesian fitting shell with LIBINT2_ERI3_PURE_SH (a287bd75) now applies to derivative order 0 only: the generator marks the fitting shell pure_sh only for deriv_level == 0 (build_libint.cc), so derivative kernels are cartesian-complete and were wrongly rejected. Same condition on the ERI2 asserts (xs_xs), whose ket message also said "in bra". - BraKet::xx_xs with deriv_order > 0 is rejected at initialize(); before it reached a std::logic_error from DerivMapGenerator::instance inside compute2. - _initialize() clears buildfnptrs_ before throwing lmax_exceeded, so an Engine that outlives the exception throws missing_kernel instead of dispatching a build table that does not match primdata_. hard_lmax_ and hard_default_lmax_ are initialized; they were indeterminate for a kernel-less or default-constructed Engine and copied as such. --- include/libint2/engine.h | 15 ++++++++------- include/libint2/engine.impl.h | 26 +++++++++++++++++++------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 00a76f422..4a5d86763 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -1276,13 +1276,14 @@ class Engine { size_t stack_size_; // amount allocated by libint2_init_xxx in // primdata_[0].stack int lmax_; - int hard_lmax_; // max L supported by library for this operator type (i.e. - // LIBINT2_MAX_AM_) + 1 - int hard_default_lmax_; // max L supported by library for default operator - // type (i.e. LIBINT2_MAX_AM_default) + - // 1 this is only set and used if - // LIBINT2_CENTER_DEPENDENT_MAX_AM_ - // == 1 + // max L supported by library for this operator type (i.e. + // LIBINT2_MAX_AM_) + 1; 0 until _initialize() finds + // the kernels + int hard_lmax_ = 0; + // max L supported by library for default operator type (i.e. + // LIBINT2_MAX_AM_default) + 1; this is only set and used if + // LIBINT2_CENTER_DEPENDENT_MAX_AM_ == 1 + int hard_default_lmax_ = 0; int deriv_order_; scalar_type precision_; scalar_type ln_precision_; diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 3e87dea32..679ffe9f7 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -657,6 +657,7 @@ __libint2_engine_inline void Engine::_initialize() { BOOST_PP_NBODYENGINE_MCR3_task(product))), \ std::max(hard_lmax_, hard_default_lmax_), hard_lmax_); \ if (lmax_ >= lmax) { \ + buildfnptrs_ = nullptr; /* leave no table for kernels we cannot use */ \ throw Engine::lmax_exceeded(BOOST_PP_STRINGIZE(BOOST_PP_NBODYENGINE_MCR3_TASK(product)), lmax, lmax_); \ } \ if (stack_size_ > 0) \ @@ -691,6 +692,12 @@ __libint2_engine_inline void Engine::initialize(size_t max_nprim) { throw std::invalid_argument( "Engine::initialize -- geometric derivatives of RKB integrals are not " "yet supported"); + // energies canonicalize xx_xs to xs_xx, but DerivMapGenerator has no + // derivative index map for xx_xs; fail here rather than inside compute2 + if (braket_ == BraKet::xx_xs && deriv_order_ > 0) + throw std::invalid_argument( + "Engine::initialize -- geometric derivatives are not supported for " + "BraKet::xx_xs, use BraKet::xs_xx"); assert(deriv_order_ <= LIBINT2_MAX_DERIV_ORDER && "exceeded the max derivative order of the library"); @@ -2033,10 +2040,13 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( // the kernels may have been generated assuming that the fitting // (bra) shell will be transformed to solid harmonics // (LIBINT2_ERI3_PURE_SH); this never applies to the RKB 3-center - // kernels. Such kernels produce garbage for a cartesian shell, hence - // check in all builds - if (detail::eri3_pure_sh && !detail::is_rkb_2body(oper_) && - bra1.contr[0].l > 1 && !bra1.contr[0].pure) + // kernels, nor to derivative kernels (the generator marks the fitting + // shell pure only for derivative order 0, see build_libint.cc). Such + // kernels produce garbage for a cartesian shell, hence check in all + // builds + if (detail::eri3_pure_sh && deriv_order_ == 0 && + !detail::is_rkb_2body(oper_) && bra1.contr[0].l > 1 && + !bra1.contr[0].pure) throw std::invalid_argument( "Engine::compute2 -- this library assumes that the fitting " "(bra) shell of 3-center 2-body integrals is a solid harmonics " @@ -2050,14 +2060,16 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( "the angular momentum limit is exceeded"); buildfnidx = bra1.contr[0].l * hard_lmax_ + ket1.contr[0].l; #ifdef LIBINT_ERI2_PURE_SH - if (bra1.contr[0].l > 1) + // as for 3-center: derivative kernels are generated for cartesian + // shells, only the energy kernels assume solid harmonics + if (deriv_order_ == 0 && bra1.contr[0].l > 1) assert(bra1.contr[0].pure && "library assumes solid harmonics shells in a 2-center " "2-body int, but a cartesian shell given in bra"); - if (ket1.contr[0].l > 1) + if (deriv_order_ == 0 && ket1.contr[0].l > 1) assert(ket1.contr[0].pure && "library assumes solid harmonics shells in a 2-center " - "2-body int, but a cartesian shell given in bra"); + "2-body int, but a cartesian shell given in ket"); #endif break; From 00042b6b3c5a3fe1d6728efc3a9316479cf8b15f Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 18:19:52 -0400 Subject: [PATCH 54/64] int_am.cmake: read LIBINT__MAX_AM in the scalar branch; RKB classes in the derivative glob Upstream e22dfdd2 renamed set(${class}_MAX_AM ...) to LIBINT_${class}_MAX_AM but left the reads in the scalar branch of both process_integrals_class* macros bare, so with a scalar LIBINT2__MAX_AM the _candidate__d values were empty: Libint2_ERI_COMPONENTS under-reported the generated kernels (find_package(Libint2 COMPONENTS ...)), the "Enabling integrals class X to max AM" message printed a blank, and the AM-range validation was dead. The restored validation rejects negative values (the list branch's rule) rather than zero. Also: LIBINT2_ENABLE_RKB_ERI/RKB_ERI3 take part in _glob_classes_derivs, and _rkb_eri3_impure_sh is renamed _rkb_eri3_components now that LIBINT2_RKB_ERI3_PURE_SH is gone. --- cmake/modules/int_am.cmake | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cmake/modules/int_am.cmake b/cmake/modules/int_am.cmake index 481473f84..cabcc18bd 100644 --- a/cmake/modules/int_am.cmake +++ b/cmake/modules/int_am.cmake @@ -61,7 +61,7 @@ message(STATUS "Processing integrals classes ...") # <<< overall derivatives level >>> -set(_glob_classes_derivs ${LIBINT2_ENABLE_ONEBODY};${LIBINT2_ENABLE_ERI};${LIBINT2_ENABLE_ERI3};${LIBINT2_ENABLE_ERI2};${LIBINT2_ENABLE_G12};${LIBINT2_ENABLE_G12DKH}) +set(_glob_classes_derivs ${LIBINT2_ENABLE_ONEBODY};${LIBINT2_ENABLE_ERI};${LIBINT2_ENABLE_ERI3};${LIBINT2_ENABLE_ERI2};${LIBINT2_ENABLE_RKB_ERI};${LIBINT2_ENABLE_RKB_ERI3};${LIBINT2_ENABLE_G12};${LIBINT2_ENABLE_G12DKH}) numerical_max_of_list(_max_deriv "${_glob_classes_derivs}") message(STATUS "Preparing highest derivative level ${_max_deriv}") @@ -231,14 +231,14 @@ macro(process_integrals_class class) set(LIBINT_${class}_MAX_AM ${LIBINT2_${class}_MAX_AM}) foreach(_d RANGE ${LIBINT_INCLUDE_${class}}) - set(_candidate_${class}_d${_d} ${${class}_MAX_AM}) + set(_candidate_${class}_d${_d} ${LIBINT_${class}_MAX_AM}) message(VERBOSE "setting _candidate_${class}_d${_d}=${_candidate_${class}_d${_d}}") endforeach() - if (${class}_MAX_AM GREATER_EQUAL ${LIBINT_HARD_MAX_AM}) - message(WARNING "Value for ${class}_MAX_AM too high (${${class}_MAX_AM} >= ${LIBINT_HARD_MAX_AM}). Are you sure you know what you are doing?") - elseif (${class}_MAX_AM LESS_EQUAL 0) - message(FATAL_ERROR "Invalid value for ${class}_MAX_AM (${${class}_MAX_AM} <= 0).") + if (LIBINT_${class}_MAX_AM GREATER_EQUAL ${LIBINT_HARD_MAX_AM}) + message(WARNING "Value for LIBINT2_${class}_MAX_AM too high (${LIBINT_${class}_MAX_AM} >= ${LIBINT_HARD_MAX_AM}). Are you sure you know what you are doing?") + elseif (LIBINT_${class}_MAX_AM LESS 0) + message(FATAL_ERROR "Invalid value for LIBINT2_${class}_MAX_AM (${LIBINT_${class}_MAX_AM} < 0).") endif() endif() endif() @@ -247,7 +247,7 @@ macro(process_integrals_class class) else() set(_msg ${LIBINT_MAX_AM}) endif() - message(STATUS "Enabling integrals class ${class} to max AM ${${class}_MAX_AM}${${class}_MAX_AM_LIST} (else ${_msg})") + message(STATUS "Enabling integrals class ${class} to max AM ${LIBINT_${class}_MAX_AM}${LIBINT_${class}_MAX_AM_LIST} (else ${_msg})") list(LENGTH LIBINT2_${class}_OPT_AM _ntokens) if (_ntokens GREATER 1) @@ -270,7 +270,7 @@ macro(process_integrals_class class) else() set(_msg ${LIBINT_OPT_AM}) endif() - message(STATUS "Enabling integrals class ${class} to opt AM ${${class}_OPT_AM}${${class}_OPT_AM_LIST} (else ${_msg})") + message(STATUS "Enabling integrals class ${class} to opt AM ${LIBINT_${class}_OPT_AM}${LIBINT_${class}_OPT_AM_LIST} (else ${_msg})") endif() endmacro() @@ -323,14 +323,14 @@ macro(process_integrals_class_alt class) set(LIBINT_${class}_MAX_AM ${LIBINT2_${class}_MAX_AM}) foreach(_d RANGE ${LIBINT_INCLUDE_${class}}) - set(_candidate_${class}_d${_d} ${${class}_MAX_AM}) + set(_candidate_${class}_d${_d} ${LIBINT_${class}_MAX_AM}) message(VERBOSE "setting _candidate_${class}_d${_d}=${_candidate_${class}_d${_d}}") endforeach() - if (${class}_MAX_AM GREATER_EQUAL ${LIBINT_HARD_MAX_AM}) - message(WARNING "Value for ${class}_MAX_AM too high (${${class}_MAX_AM} >= ${LIBINT_HARD_MAX_AM}). Are you sure you know what you are doing?") - elseif (${class}_MAX_AM LESS_EQUAL 0) - message(FATAL_ERROR "Invalid value for ${class}_MAX_AM (${${class}_MAX_AM} <= 0).") + if (LIBINT_${class}_MAX_AM GREATER_EQUAL ${LIBINT_HARD_MAX_AM}) + message(WARNING "Value for LIBINT2_${class}_MAX_AM too high (${LIBINT_${class}_MAX_AM} >= ${LIBINT_HARD_MAX_AM}). Are you sure you know what you are doing?") + elseif (LIBINT_${class}_MAX_AM LESS 0) + message(FATAL_ERROR "Invalid value for LIBINT2_${class}_MAX_AM (${LIBINT_${class}_MAX_AM} < 0).") endif() endif() endif() @@ -382,7 +382,7 @@ add_feature_info( set(Libint2_ERI_COMPONENTS "") set(_amlist "") set(_eri3_impure_sh "") -set(_rkb_eri3_impure_sh "") +set(_rkb_eri3_components "") set(_eri2_impure_sh "") add_feature_info( "integral class MULTIPOLE derivative 0" @@ -472,15 +472,15 @@ foreach(_cls ONEBODY;ERI;RKB_ERI;RKB_ERI3;ERI3;ERI2;G12;G12DKH) elseif (_cls STREQUAL "ERI3") list(APPEND _eri3_impure_sh "${_pureamlist}") elseif (_cls STREQUAL "RKB_ERI3") - list(APPEND _rkb_eri3_impure_sh "${_pureamlist}") + list(APPEND _rkb_eri3_components "${_pureamlist}") endif() endforeach() if ((_cls STREQUAL "ERI3") AND NOT LIBINT2_ERI3_PURE_SH) list(APPEND Libint2_ERI_COMPONENTS "${_eri3_impure_sh}") message(VERBOSE "setting components ${_eri3_impure_sh}") elseif (_cls STREQUAL "RKB_ERI3") # RKB 3-center kernels never assume a solid harmonics fitting center - list(APPEND Libint2_ERI_COMPONENTS "${_rkb_eri3_impure_sh}") - message(VERBOSE "setting components ${_rkb_eri3_impure_sh}") + list(APPEND Libint2_ERI_COMPONENTS "${_rkb_eri3_components}") + message(VERBOSE "setting components ${_rkb_eri3_components}") elseif ((_cls STREQUAL "ERI2") AND NOT LIBINT2_ERI2_PURE_SH) list(APPEND Libint2_ERI_COMPONENTS "${_eri2_impure_sh}") message(VERBOSE "setting components ${_eri2_impure_sh}") From 2a5178cfd6eb6e38df25146eaa9747d3c10a26d1 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 18:19:52 -0400 Subject: [PATCH 55/64] build_libint: export LIBINT2_SUPPORT_RKB_ERI3 and LIBINT2_DERIV_RKB_ERI3_ORDER Mirrors the RKB_ERI feature macros so a consumer can test for 3-center RKB support without probing LIBINT2_TASK_EXISTS_3coulomb_opop. --- src/bin/libint/build_libint.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index c172654b0..c1f734610 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -2523,6 +2523,12 @@ void config_to_api(const std::shared_ptr& cparams, iface->macro_define("DERIV_RKB_ERI_ORDER", LIBINT_INCLUDE_RKB_ERI)); max_deriv_order = std::max(max_deriv_order, LIBINT_INCLUDE_RKB_ERI); #endif +#ifdef LIBINT_INCLUDE_RKB_ERI3 + iface->to_params(iface->macro_define("SUPPORT_RKB_ERI3", 1)); + iface->to_params( + iface->macro_define("DERIV_RKB_ERI3_ORDER", LIBINT_INCLUDE_RKB_ERI3)); + max_deriv_order = std::max(max_deriv_order, LIBINT_INCLUDE_RKB_ERI3); +#endif #ifdef LIBINT_INCLUDE_ERI3 iface->to_params(iface->macro_define("SUPPORT_ERI3", 1)); iface->to_params( From af3d628fdcfe25258f2e1ee3556b2654ebd7da94 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 18:19:52 -0400 Subject: [PATCH 56/64] DerivGaussV2: include the differentiation direction in the generated-function label directional() is always true, so a shell carrying derivative quanta in two directions (second derivatives) is a valid target for two instances whose children and a[dir] coefficients differ. The RRStack deduplicates generated functions by label, which omitted dir, so only the first instance's function was emitted and correctness relied on the tactic tie-break selecting that same instance. Encode dir in both the code-shared and the translational-invariance labels (the latter inherited the omission from CR_DerivGauss). Also drop a dead `if (np == 1) return true;` in expl_low_dim(). --- src/bin/libint/comp_deriv_gauss_v2.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bin/libint/comp_deriv_gauss_v2.h b/src/bin/libint/comp_deriv_gauss_v2.h index c3a8435d1..c19f3928d 100644 --- a/src/bin/libint/comp_deriv_gauss_v2.h +++ b/src/bin/libint/comp_deriv_gauss_v2.h @@ -411,17 +411,22 @@ std::string DerivGaussV2 aux0(new mType(0u)); os << "CR_DerivGauss" - << "P" << part << to_string(where) + << "P" << part << to_string(where) << "D" << dir_ << genintegralset_label(target_->bra(), target_->ket(), aux0, target_->oper()); return os.str(); } - os << "DerivGaussV2 P" << part << " " << to_string(where) << " "; + os << "DerivGaussV2 P" << part << " " << to_string(where) << " D" << dir_ + << " "; // Only encode the differentiated shell — not spectators if (where == InBra) { @@ -506,8 +511,6 @@ bool DerivGaussV2(np) - 1 == part && where == InKet) return false; - // corner case: 1-particle operator - if (np == 1) return true; return true; } From 7d8b6ad11bb2e6a951f9de3b7e9ac55499d6793e Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 20:07:30 -0400 Subject: [PATCH 57/64] Engine: do not copy the stack-allocated targets_ vector when remapping components `targets_` is a std::vector with ext_stack_allocator, its elements live in primdata_[0].targets. The component remap after a bra-ket swap (f3d21c16) copied that vector (`const auto tmp = targets_;`); the copy inherits the allocator, and whether the copy's storage overlaps the original's depends on how the standard library maintains the bump pointer, so on libc++ (mingw CLANG64 CI lane) the remap read entries it had already overwritten and the opop_coulomb_opop components came out scrambled for every quartet that needs a bra-ket swap ((s d|s p) in "RKB Coulomb integrals"; the canonical kernel (p s|d s) was bit-identical to Linux). Copy the pointers into a local std::array, as the pre-f3d21c16 code did. Also make ext_stack_allocator safe to copy through a container: select_on_container_copy_construction() returns a heap-only allocator, and allocate()/deallocate() handle the heap-only state. --- include/libint2/engine.impl.h | 7 ++++++- include/libint2/util/array_adaptor.h | 12 ++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 679ffe9f7..7538e395c 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -2284,7 +2284,12 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( for (auto c = 0u; c != nopers && trivial; ++c) trivial = (this->braket_swap_remap(c) == c); if (!trivial) { - const auto tmp = targets_; + // N.B. targets_ uses ext_stack_allocator (its elements live in + // primdata_[0].targets); do not copy the vector, a copy inherits + // the allocator and may be placed on the same arena + assert(ntargets <= static_cast(max_ntargets)); + std::array tmp; + std::copy(targets_.begin(), targets_.begin() + ntargets, tmp.begin()); for (auto s = 0; s != ntargets; ++s) { const auto d = s / nopers; const auto c = s % nopers; diff --git a/include/libint2/util/array_adaptor.h b/include/libint2/util/array_adaptor.h index 8c4fea497..0f1f9ba84 100644 --- a/include/libint2/util/array_adaptor.h +++ b/include/libint2/util/array_adaptor.h @@ -69,13 +69,21 @@ class ext_stack_allocator { explicit ext_stack_allocator(U (&array)[N]) noexcept : stack_(const_cast(&array[0])), free_(stack_) {} + /// a container copy-constructed from one using this allocator gets a + /// heap-only allocator: the arena belongs to the original container, and a + /// copy of the allocator state would hand out the same (or overlapping) + /// storage + ext_stack_allocator select_on_container_copy_construction() const noexcept { + return ext_stack_allocator(); + } + template struct rebind { using other = ext_stack_allocator<_Up, N>; }; T* allocate(std::size_t n) { - assert(stack_ != nullptr && "array_view_allocator not initialized"); + if (stack_ == nullptr) return new T[n]; // heap-only allocator if (stack_ + N - free_ >= static_cast(n)) { // have free space on stack const auto result = free_; @@ -86,7 +94,7 @@ class ext_stack_allocator { } } void deallocate(T* p, std::size_t n) noexcept { - if (pointer_on_stack(p)) { + if (stack_ != nullptr && pointer_on_stack(p)) { assert(p + n == free_ && "stack deallocation out of order"); free_ -= n; } else { From 307d3dd1dcd438180a1c33c3916ce6eadd7809ca Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Fri, 23 Oct 2026 20:50:31 -0400 Subject: [PATCH 58/64] solidharmonics: bind the parity helper by qualified name; fix test_eri parity for negative arguments SolidHarmonicsCoefficients::coeff() called an anonymous-namespace `parity` unqualified. Under MSVC-style delayed template parsing (clang-cl's default) an unqualified name in a template body is bound at instantiation, so in a TU that had brought another `parity` into namespace libint2 the call resolved to that one instead. test_eri/eri.h declares such a libint2::parity, and it returns +1 for negative odd arguments (`a % 2 == 1` is false for -1). Since a287bd75 test-2body.cc (which includes test_eri/eri.h) instantiates the solid harmonics transforms, its copy of coeff won the COMDAT for the whole unit-test executable, and the one coefficient that depends on parity(-1) for l <= 2, the xx entry of the d-shell m=0 row, came out +0.5 instead of -0.5. Every 1-body absolute-reference test with pure shells then failed on the windows-clang-cl CI lane while all other lanes, which use two-phase lookup, passed. Reproduced with clang++ -fdelayed-template-parsing. - solidharmonics.h: move parity into libint2::solidharmonics::detail, make it constexpr and correct for negative arguments, call it qualified - test_eri/eri.h: libint2::parity returns (-1)^a for negative a too - test-2body.cc: check coeff(2,0,2,0,0) == -0.5 from the TU that includes test_eri/eri.h --- export/tests/unit/test-2body.cc | 5 +++++ include/libint2/solidharmonics.h | 29 +++++++++++++++++------------ src/bin/test_eri/eri.h | 4 ++-- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 1a9dc1543..20f95cc60 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -1310,6 +1310,11 @@ Shell rkb_shell(const Shell &sh, bool pure) { } // namespace TEST_CASE("RKB integrals with solid harmonics shells", "[engine][2-body]") { + // this TU includes test_eri/eri.h, which declares libint2::parity; make sure + // the solid harmonics coefficients computed from this TU's instantiation + // (which wins at link time on some toolchains) do not pick it up + REQUIRE(libint2::solidharmonics::SolidHarmonicsCoefficients::coeff( + 2, 0, 2, 0, 0) == Approx(-0.5)); // cartesian s, p, contracted p, d; the solid harmonics variants of the same const std::vector cart_shells{ Shell{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}, diff --git a/include/libint2/solidharmonics.h b/include/libint2/solidharmonics.h index 7caa7ecea..7a3c341d1 100644 --- a/include/libint2/solidharmonics.h +++ b/include/libint2/solidharmonics.h @@ -39,17 +39,22 @@ #include #include -namespace { -template -signed char parity(Int i) { - return i % 2 ? -1 : 1; -} -} // namespace - namespace libint2 { namespace solidharmonics { +namespace detail { +/// @return \f$ (-1)^i \f$ for any integer @p i , negative included +/// @note always call this qualified (detail::parity): under MSVC-style delayed +/// template parsing (clang-cl's default) an unqualified call from a template +/// body is bound at instantiation and can pick up an unrelated `parity` that +/// an including TU has brought into namespace libint2 (test_eri/eri.h did) +template +constexpr signed char parity(Int i) { + return (i % 2 != 0) ? -1 : 1; +} +} // namespace detail + // to avoid overhead of Eigen::SparseMatrix will roll our own /// Transformation coefficients from unnormalized Cartesian Gaussians (rows) to @@ -129,7 +134,7 @@ class SolidHarmonicsCoefficients { auto comp = (m >= 0) ? 1 : -1; /* if (comp != ((abs_m-lx)%2 ? -1 : 1))*/ auto i = abs_m - lx; - if (comp != parity(abs(i))) return 0.0; + if (comp != detail::parity(std::abs(i))) return 0.0; Real pfac; pfac = sqrt(((fac_real(2 * lx) * fac_real(2 * ly) * @@ -144,16 +149,16 @@ class SolidHarmonicsCoefficients { * sqrt(fac_real(l-abs_m)/(fac_real(l)*fac_real(l)*fac[l+abs_m]));*/ pfac /= (1L << l); if (m < 0) - pfac *= parity((i - 1) / 2); + pfac *= detail::parity((i - 1) / 2); else - pfac *= parity(i / 2); + pfac *= detail::parity(i / 2); auto i_min = j; auto i_max = (l - abs_m) / 2; Real sum = 0; for (auto i = i_min; i <= i_max; i++) { Real pfac1 = bc_real(l, i) * bc_real(i, j); - pfac1 *= (Real(parity(i) * fac_real(2 * (l - i))) / + pfac1 *= (Real(detail::parity(i) * fac_real(2 * (l - i))) / fac_real(l - abs_m - 2 * i)); Real sum1 = 0.0; const int k_min = std::max((lx - abs_m) / 2, 0); @@ -161,7 +166,7 @@ class SolidHarmonicsCoefficients { for (int k = k_min; k <= k_max; k++) { if (lx - 2 * k <= abs_m) sum1 += bc_real(j, k) * bc_real(abs_m, lx - 2 * k) * - parity(k); + detail::parity(k); } sum += pfac1 * sum1; } diff --git a/src/bin/test_eri/eri.h b/src/bin/test_eri/eri.h index 079db0f02..deecbda33 100644 --- a/src/bin/test_eri/eri.h +++ b/src/bin/test_eri/eri.h @@ -101,10 +101,10 @@ struct ExpensiveMath { namespace libint2 { inline int min(int t1, unsigned int t2) { return t1 > (int)t2 ? (int)t2 : t1; } +/// @return \f$ (-1)^a \f$ for any integer @p a , negative included template int parity(Int a) { - if (a % 2 == 1) return -1; - return 1; + return (a % 2 != 0) ? -1 : 1; } }; // namespace libint2 From aceb113ea30c17dd7602f327d21640f3ba97a991 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sat, 24 Oct 2026 14:41:15 -0400 Subject: [PATCH 59/64] Address review of the RKB integrals: honor OPT_AM for RKB tasks, document xx_xs, port generic code to DerivGaussV2, extend tests - build_libint: LIBINT_RKB_ERI[3]_OPT_AM were no-ops because CSE/condense_expr were forced off for multi-component operators; both builders now treat them as every other class (CSE only affects unrolled expressions, the DAG dedups by instance) - engine.h: document that with BraKet::xx_xs sigma.p acts on the user's bra for coulomb_opop/coulomb_op; restore the rkb_operators table (clang-format had reflowed it) and guard it with clang-format off/on - DerivGaussV2: implement has_generic/generic_header/generic_instance so LIBINT2_ENABLE_GENERIC_CODE keeps working for derivative ERIs; the generic call takes the shared function's runtime highdim/lowdim - CHANGES: list the library-wide changes (xx_xs energies, DerivGaussV2 for all ERI derivative kernels, missing_kernel instead of asserts, int_am.cmake validation) - CI: the macOS Release lane also builds 2nd ERI derivatives - tests: opemuop checked vs finite differences for all s/p/d pairs (contracted, pure, displaced origin); new test that a failed re-initialization leaves the Engine throwing missing_kernel --- .github/workflows/cmake.yml | 13 +-- CHANGES | 13 +++ export/tests/unit/test-1body.cc | 130 +++++++++++++++++---------- export/tests/unit/test-2body.cc | 20 +++++ include/libint2/engine.h | 38 ++++---- src/bin/libint/build_libint.cc | 30 +++---- src/bin/libint/comp_deriv_gauss_v2.h | 94 +++++++++++++++++++ 7 files changed, 255 insertions(+), 83 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 131a38546..e10e10a3c 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -45,12 +45,15 @@ jobs: build_shared_libs: ON os: macos-15 do_fortran: ON - # exercise a center-dependent AM table for the 3-center RKB ERIs in - # one lane only + # exercise a center-dependent AM table for the 3-center RKB ERIs and + # 2nd geometric derivatives of the 4-center ERIs in one lane only - build_type: Release os: macos-15 subproject: OFF rkb_eri3_max_am: 3 + eri_deriv_order: 2 + max_am: '2;2;1' + eri_max_am: '2;2;1' name: "Repo • ${{ matrix.os }}: ${{ matrix.cxx }} ${{ matrix.build_type }} shared=${{ matrix.build_shared_libs }} fetch=${{ matrix.subproject }}" runs-on: ${{ matrix.os }} @@ -67,10 +70,10 @@ jobs: -D BUILD_SHARED_LIBS=${{ matrix.build_shared_libs }} -D LIBINT2_BUILD_LIBRARY_AS_SUBPROJECT=${{ matrix.subproject }} -D CMAKE_INSTALL_PREFIX=${{github.workspace}}/build/library - -D LIBINT2_MAX_AM=2;2 - -D LIBINT2_ERI_MAX_AM=2;2 + -D LIBINT2_MAX_AM=${{ matrix.max_am || '2;2' }} + -D LIBINT2_ERI_MAX_AM=${{ matrix.eri_max_am || '2;2' }} -D LIBINT2_ERI3_MAX_AM=3;2 - -D LIBINT2_ENABLE_ERI=1 + -D LIBINT2_ENABLE_ERI=${{ matrix.eri_deriv_order || 1 }} -D LIBINT2_ENABLE_ERI3=1 -D LIBINT2_ENABLE_RKB_ERI=0 -D LIBINT2_ENABLE_RKB_ERI3=0 diff --git a/CHANGES b/CHANGES index 20ca8ae71..6af09f5af 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,19 @@ Following is a brief summary of changes made in each release of Libint. and std::invalid_argument if given a cartesian fitting shell with l > 1 when the library was configured with LIBINT2_ERI3_PURE_SH (previously a Debug-only assert). The Libint compiler now requires C++17; the generated library still requires C++11 only. + Other changes that affect all libraries, not just the RKB integrals: + - BraKet::xx_xs is now usable for 3-center energies (as the alias of BraKet::xs_xx with the + fitting function last); previously Engine::compute() aborted for it. + - the geometric-derivative kernels of all 2-body Coulomb integrals (ERI, ERI3, ERI2) are now + generated by DerivGaussV2 (comp_deriv_gauss_v2.h), which shares one function between all + shell sets with the same differentiated shell, instead of by CR_DerivGauss; the generated + code is validated against finite differences for every shell set in the unit tests. With + LIBINT2_ENABLE_GENERIC_CODE the generic kernel is used when the differentiated shell is + beyond LIBINT2_OPT_AM (previously: when the average AM of the shell set is). + - Engine::initialize() no longer asserts (in Debug builds only) that the library includes the + braket type; missing kernels are reported by Engine::missing_kernel from compute(). + - the validation of LIBINT2__MAX_AM in cmake/modules/int_am.cmake now actually runs + (it referred to undefined variables); a value of 0 is accepted, negative values are rejected. - PR #440: incremental builds re-export the library when include/libint2, export/tests, etc. change, without recompiling unchanged library sources (#438) - PR #436: GenericGmEval no longer copies the core evaluator on every call; fixes poor thread diff --git a/export/tests/unit/test-1body.cc b/export/tests/unit/test-1body.cc index 39b470385..0c2debd07 100644 --- a/export/tests/unit/test-1body.cc +++ b/export/tests/unit/test-1body.cc @@ -18,6 +18,8 @@ * */ +#include + #include "catch.hpp" #include "fixture.h" @@ -271,13 +273,91 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpeμσp correctness", std::vector obs{make_shell0(O0), make_shell1(O1)}; const auto lmax = std::min(2, LIBINT2_MAX_AM_opemuop); + auto engine = Engine(Operator::opemuop, 2, lmax); + + // independent check of all components of (make0(OA)|make1(OB)) with the + // dipole origin at @p origin: the Pauli-quaternion fold of + // (∂_a μ|r_k|∂_b ν) = ∂A_a ∂B_b (μ|r_k|ν), with the latter evaluated by + // central finite differences of the emultipole1 integrals + typedef std::function)> shell_factory_t; + auto check_vs_fd = [&](const shell_factory_t& make0, + const shell_factory_t& make1, + const std::array& OA, + const std::array& OB, + const std::array& origin) { + engine.set_params(origin); + engine.compute(make0(OA), make1(OB)); + auto emu_engine = Engine(Operator::emultipole1, 2, lmax); + emu_engine.set_params(origin); + const auto n01 = make0(OA).size() * make1(OB).size(); + const double h = 1e-4; + // dAdB[(3*a + b)*3 + k][i] = ∂A_a ∂B_b (μ|r_k|ν)_i + std::vector> dAdB(27, std::vector(n01, 0.0)); + for (int a = 0; a != 3; ++a) { + for (int b = 0; b != 3; ++b) { + for (int sa : {-1, 1}) { + for (int sb : {-1, 1}) { + auto A = OA; + A[a] += sa * h; + auto B = OB; + B[b] += sb * h; + const auto& emu = emu_engine.compute(make0(A), make1(B)); + for (int k = 0; k != 3; ++k) + for (size_t i = 0; i != n01; ++i) + dAdB[(3 * a + b) * 3 + k][i] += + sa * sb * emu[1 + k][i] / (4 * h * h); + } + } + } + } + auto d = [&](int a, int b, int k, size_t i) { + return dAdB[(3 * a + b) * 3 + k][i]; + }; + constexpr int x = 0, y = 1, z = 2; + for (int k = 0; k != 3; ++k) { + for (size_t i = 0; i != n01; ++i) { + const double ref[4] = {d(x, x, k, i) + d(y, y, k, i) + d(z, z, k, i), + d(y, z, k, i) - d(z, y, k, i), + d(z, x, k, i) - d(x, z, k, i), + d(x, y, k, i) - d(y, x, k, i)}; + for (int q = 0; q != 4; ++q) { + INFO("l0=" << make0(OA).contr[0].l << " l1=" << make1(OB).contr[0].l + << " nprim0=" << make0(OA).nprim() + << " nprim1=" << make1(OB).nprim() << " k=" << k + << " q=" << q << " i=" << i); + REQUIRE(engine.results()[4 * k + q][i] == + Approx(ref[q]).margin(1e-6)); + } + } + } + }; + + // every pair of s, p, d shells (up to lmax), contracted and uncontracted, + // pure and cartesian, with the dipole origin away from both centers + { + auto make = [](int l, bool contracted, bool pure) { + return shell_factory_t([=](std::array O) { + if (contracted) return Shell{{1.7, 0.4}, {{l, pure, {0.6, 0.5}}}, O}; + return Shell{{0.9}, {{l, pure, {1.0}}}, O}; + }); + }; + const std::array A{{0.4, -0.3, 0.2}}; + const std::array B{{-0.5, 0.7, 1.1}}; + const std::array origin{{0.1, -0.2, 0.3}}; + for (int l0 = 0; l0 <= lmax; ++l0) + for (int l1 = 0; l1 <= lmax; ++l1) + for (bool c0 : {false, true}) + for (bool c1 : {false, true}) + for (bool pure : {false, true}) + check_vs_fd(make(l0, c0, pure), make(l1, c1, pure), A, B, origin); + } + if (lmax < 2) return; constexpr int l0 = 2; constexpr int l1 = 2; constexpr int n1 = 2 * l1 + 1; - auto engine = Engine(Operator::opemuop, 2, lmax); engine.set_params(std::array{{0.0, 0.0, 0.0}}); engine.compute(obs[0], obs[1]); @@ -487,51 +567,9 @@ TEST_CASE_METHOD(libint2::unit::DefaultFixture, "σpeμσp correctness", LIBINT2_TEST_ONEBODY_MARGIN(1.0, 11, 1e-10); } - // independent check of all components: the Pauli-quaternion fold of - // (∂_a μ|r_k|∂_b ν) = ∂A_a ∂B_b (μ|r_k|ν), with the latter evaluated by - // central finite differences of the emultipole1 integrals - { - engine.compute(obs[0], obs[1]); - auto emu_engine = Engine(Operator::emultipole1, 2, lmax); - emu_engine.set_params(std::array{{0.0, 0.0, 0.0}}); - const auto n01 = obs[0].size() * obs[1].size(); - const double h = 1e-4; - // dAdB[(3*a + b)*3 + k][i] = ∂A_a ∂B_b (μ|r_k|ν)_i - std::vector> dAdB(27, std::vector(n01, 0.0)); - for (int a = 0; a != 3; ++a) { - for (int b = 0; b != 3; ++b) { - for (int sa : {-1, 1}) { - for (int sb : {-1, 1}) { - auto A = O0; - A[a] += sa * h; - auto B = O1; - B[b] += sb * h; - const auto& emu = - emu_engine.compute(make_shell0(A), make_shell1(B)); - for (int k = 0; k != 3; ++k) - for (size_t i = 0; i != n01; ++i) - dAdB[(3 * a + b) * 3 + k][i] += - sa * sb * emu[1 + k][i] / (4 * h * h); - } - } - } - } - auto d = [&](int a, int b, int k, size_t i) { - return dAdB[(3 * a + b) * 3 + k][i]; - }; - constexpr int x = 0, y = 1, z = 2; - for (int k = 0; k != 3; ++k) { - for (size_t i = 0; i != n01; ++i) { - const double ref[4] = {d(x, x, k, i) + d(y, y, k, i) + d(z, z, k, i), - d(y, z, k, i) - d(z, y, k, i), - d(z, x, k, i) - d(x, z, k, i), - d(x, y, k, i) - d(y, x, k, i)}; - for (int q = 0; q != 4; ++q) - REQUIRE(engine.results()[4 * k + q][i] == - Approx(ref[q]).margin(1e-6)); - } - } - } + // the independent check for the (d|d) pair above + check_vs_fd(make_shell0, make_shell1, O0, O1, + std::array{{0.0, 0.0, 0.0}}); #endif // LIBINT2_SUPPORT_ONEBODY } diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 20f95cc60..4b3a37e00 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -554,6 +554,26 @@ TEST_CASE("eri geometric derivatives vs finite differences", #endif } +// re-initializing an Engine for a task whose AM limit is below the Engine's +// lmax must throw Engine::lmax_exceeded and leave the Engine without kernels: +// the next compute() must throw Engine::missing_kernel rather than use the +// kernel table of the previous task +TEST_CASE("lmax_exceeded leaves no kernels", "[engine][2-body]") { +#if defined(LIBINT2_SUPPORT_ERI) && defined(LIBINT2_SUPPORT_ERI3) && \ + LIBINT2_MAX_AM_3eri > LIBINT2_MAX_AM_eri + const int lmax = LIBINT2_MAX_AM_3eri; // valid for 3eri, too high for eri + const Shell fit{{1.5}, {{lmax, true, {1.0}}}, {{-1.0, 0.5, 0.0}}}; + const Shell s{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}; + const auto eps = std::numeric_limits::epsilon(); + Engine engine(Operator::coulomb, 1, lmax, 0, eps, + libint2::default_params(Operator::coulomb), BraKet::xs_xx); + REQUIRE(engine.compute(fit, s, s)[0] != nullptr); + REQUIRE_THROWS_AS(engine.set(BraKet::xx_xx), Engine::lmax_exceeded); + CHECK_THROWS_AS(engine.compute(s, s, s, s), Engine::missing_kernel); + CHECK_THROWS_AS(engine.compute(fit, s, s), Engine::missing_kernel); +#endif +} + TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { const std::vector all_shells{ // pseudorandom s diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 4a5d86763..2f6b88311 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -235,13 +235,20 @@ enum class Operator { /// alias for Operator::stg_x_coulomb yukawa = stg_x_coulomb, /// (2-body) \f$ r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ where k1 & k2 are - /// centers of ket1 and ket2, respectively + /// centers of ket1 and ket2, respectively. Produces 4 components (see + /// \ref rkb_operators). For 3-center integrals the σ·p pair is the pair of + /// AO functions, whichever side of the braket it is on: with BraKet::xs_xx + /// σ·p acts on ket1 and ket2, with BraKet::xx_xs (the alias of xs_xx that + /// only changes the order of the arguments and of the result) σ·p acts on + /// bra1 and bra2 and the fitting function is ket1. coulomb_opop, /// (2-body) \f$ r_{12}^{-1} (σ.p_{k2}) \f$ where k2 is the center of ket2; /// the 3-center single-σ·p integral (P | μ, σ·p ν). σ·p acts on /// the 2nd ket AO function only. Produces 3 Cartesian components (x=0, y=1, /// z=2) — the SO(3) vector irrep; no σ·σ folding (cf. coulomb_opop). The - /// fitting function sits on the bra (BraKet::xs_xx); 3-center only. + /// fitting function sits on the bra (BraKet::xs_xx); 3-center only. With + /// BraKet::xx_xs (the alias of xs_xx) σ·p acts on bra2, the 2nd AO + /// function, and the fitting function is ket1. coulomb_op, /// (2-body) \f$ (σ.p_{b1})(σ.p_{b2}) r_{12}^{-1} (σ.p_{k1})(σ.p_{k2})\f$ /// where b1 & b2 are centers of bra1 and bra2 and k1 & k2 are centers of @@ -272,6 +279,7 @@ enum class Operator { /// @param[in] oper an Operator object /// @return the particle rank of \c oper /// @throw std::logic_error if an invalid @c oper given +// clang-format off /** \page rkb_operators Conventions for the relativistic (RKB) 2-body operators @@ -283,19 +291,18 @@ enum class Operator { with the Pauli matrices. In chemists' notation (ab|cd) the operator acts on the second function of a pair, i.e. on b and/or d. - | Engine (C++ API) | Compiler | task label | #comp | - component index | spin | - |----------------------|--------------------|----------------|-------|------------------------------------------------------|--------| - | `coulomb_opop` | `Coulombσpσp` | `coulomb_opop` | 4 | 0: - ∇_c·∇_d, 1-3: (∇_c×∇_d)_{x,y,z} | folded | | `coulomb_op` | - `Coulombσp` | `coulomb_op` | 3 | ∂_{d,x}, ∂_{d,y}, ∂_{d,z} - (3-center only) | n/a | | `opop_coulomb_opop` | - `σpσpCoulombσpσp` | `opop_coulomb_opop` | 16 | 4·bra + ket, with bra, ket ∈ - {0: ∇·∇, 1-3: (∇×∇)_{x,y,z}} of (a,b) and (c,d); the sign of i² is folded into - the 1-3 ⊗ 1-3 block | folded | | `op_coulomb_op` | `σpCoulombσp` | - `op_coulomb_op` | 9 | SO(3) irreps of T_ab = ∂_{b,a} ∂_{d,b}: 0: trace, 1-3: - T_yz−T_zy, T_zx−T_xz, T_xy−T_yx, 4: T_xx−T_yy, 5: 2T_zz−T_xx−T_yy, 6-8: - T_xy+T_yx, T_xz+T_zx, T_yz+T_zy | not folded | + | Engine (C++ API) | Compiler | task label | #comp | component index | spin | + |---------------------|-------------------|---------------------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------|------------| + | `coulomb_opop` | `Coulombσpσp` | `coulomb_opop` | 4 | 0: ∇_c·∇_d, 1-3: (∇_c×∇_d)_{x,y,z} | folded | + | `coulomb_op` | `Coulombσp` | `coulomb_op` | 3 | ∂_{d,x}, ∂_{d,y}, ∂_{d,z} (3-center only) | n/a | + | `opop_coulomb_opop` | `σpσpCoulombσpσp` | `opop_coulomb_opop` | 16 | 4·bra + ket, with bra, ket ∈ {0: ∇·∇, 1-3: (∇×∇)_{x,y,z}} of (a,b) and (c,d); the sign of i² is folded into the 1-3 ⊗ 1-3 block | folded | + | `op_coulomb_op` | `σpCoulombσp` | `op_coulomb_op` | 9 | SO(3) irreps of T_ab = ∂_{b,a} ∂_{d,b}: 0: trace, 1-3: T_yz−T_zy, T_zx−T_xz, T_xy−T_yx, 4: T_xx−T_yy, 5: 2T_zz−T_xx−T_yy, 6-8: T_xy+T_yx, T_xz+T_zx, T_yz+T_zy | not folded | + + For the 3-center operators (`coulomb_opop` and `coulomb_op`) the kernels + are generated for BraKet::xs_xx, with the fitting function as bra1 and the + AO pair as the ket; the Engine accepts BraKet::xx_xs as an alias that only + changes the order of the arguments of compute() and the layout of the + result (fitting function last), hence with xx_xs σ·p acts on the bra. The 1-body analog of `opVop` with the dipole operator in the middle is `opemuop` (compiler: `σpeμσp`), with 12 = 3 (dipole direction, slow) × 4 @@ -308,6 +315,7 @@ enum class Operator { are canonical under these permutations (see the matching descriptor traits in the compiler). */ +// clang-format on inline constexpr int rank(Operator oper) { return (oper >= Operator::first_1body_oper && diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index c1f734610..679f7d44c 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -1360,14 +1360,14 @@ static void build_TwoPRep_2b_2k( ? std::numeric_limits::max() : 0; dg_xxxx->registry()->unroll_threshold(unroll_threshold); - // For multi-component operators (RKB), components share no - // intermediates, so CSE/condense_expr is pure overhead — disable. - const bool do_optimize = (nopers > 1) ? false : need_to_optimize; - dg_xxxx->registry()->do_cse(do_optimize); - dg_xxxx->registry()->condense_expr( - do_optimize ? condense_expr(cparams->unroll_threshold(), - cparams->max_vector_length() > 1) - : false); + // CSE and condense_expr act on the unrolled (CGF-level) expressions + // only; the integral-set DAG is deduplicated by instance regardless. + // Multi-component (RKB) operators rarely unroll (nopers multiplies + // the unroll cost above), but LIBINT__OPT_AM is honored for + // them exactly as for every other class. + dg_xxxx->registry()->do_cse(need_to_optimize); + dg_xxxx->registry()->condense_expr(condense_expr( + cparams->unroll_threshold(), cparams->max_vector_length() > 1)); // Need to accumulate integrals? dg_xxxx->registry()->accumulate_targets( cparams->accumulate_targets()); @@ -1600,15 +1600,11 @@ static void build_TwoPRep_1b_2k( ? std::numeric_limits::max() : 0; dg_xxx->registry()->unroll_threshold(unroll_threshold); - // For multi-component operators (RKB), components share no - // intermediates, so CSE/condense_expr is pure overhead — disable - // (mirrors build_TwoPRep_2b_2k). - const bool do_optimize = (nopers > 1) ? false : need_to_optimize; - dg_xxx->registry()->do_cse(do_optimize); - dg_xxx->registry()->condense_expr( - do_optimize ? condense_expr(cparams->unroll_threshold(), - cparams->max_vector_length() > 1) - : false); + // see build_TwoPRep_2b_2k: LIBINT__OPT_AM is honored for + // multi-component operators as for every other class + dg_xxx->registry()->do_cse(need_to_optimize); + dg_xxx->registry()->condense_expr(condense_expr( + cparams->unroll_threshold(), cparams->max_vector_length() > 1)); // Need to accumulate integrals? dg_xxx->registry()->accumulate_targets(cparams->accumulate_targets()); diff --git a/src/bin/libint/comp_deriv_gauss_v2.h b/src/bin/libint/comp_deriv_gauss_v2.h index c19f3928d..dc803932e 100644 --- a/src/bin/libint/comp_deriv_gauss_v2.h +++ b/src/bin/libint/comp_deriv_gauss_v2.h @@ -22,6 +22,7 @@ #define _libint2_src_bin_libint_compderivgaussv2_h_ #include +#include // CR_DerivGauss_GenericInstantiator #include #include #include @@ -115,6 +116,22 @@ class DerivGaussV2 : public RecurrenceRelation { bool expl_high_dim() const; bool expl_low_dim() const; +#if LIBINT_ENABLE_GENERIC_CODE + /// Implementation of RecurrenceRelation::has_generic(): the generic + /// (runtime-dimensioned) GenericGaussDeriv kernel replaces the explicit code + /// of the code-shared function when the differentiated shell is beyond the + /// optimized AM range (the spectator dimensions are runtime arguments of the + /// shared function anyway, so only the differentiated shell matters here) + bool has_generic( + const std::shared_ptr& cparams) const override; + /// Implementation of RecurrenceRelation::generic_header() + std::string generic_header() const override; + /// Implementation of RecurrenceRelation::generic_instance() + std::string generic_instance( + const std::shared_ptr& context, + const std::shared_ptr& args) const override; +#endif + /// add child, deduplicating const std::shared_ptr& add_child( const std::shared_ptr& child) { @@ -541,6 +558,83 @@ DerivGaussV2:: return localdims; } +#if LIBINT_ENABLE_GENERIC_CODE +template +bool DerivGaussV2:: + has_generic(const std::shared_ptr& cparams) const { + // no generic relation for translational invariance (as in CR_DerivGauss) + if (using_trans_inv) return false; + // generic code applies to shell sets only + if (TrivialBFSet::result) return false; + // the function is shared by all shell sets with the same differentiated + // shell, hence base the decision on that shell alone + const BasisFunctionType& sh = + where == InBra ? target_->bra(part, 0) : target_->ket(part, 0); + return sh.norm() > cparams->max_am_opt(); +} + +template +std::string DerivGaussV2::generic_header() const { + return std::string("GenericGaussDeriv.h"); +} + +template +std::string +DerivGaussV2:: + generic_instance(const std::shared_ptr& context, + const std::shared_ptr& args) const { + std::ostringstream oss; + + oss << "using namespace libint2;" << std::endl; + + BasisFunctionType sh(where == InBra ? target_->bra(part, 0) + : target_->ket(part, 0)); + + const unsigned int L = sh.norm(); + const bool vectorize = context->cparams()->max_vector_length() != 1; + oss << "libint2::GenericGaussDeriv<" << L << "," + << (vectorize ? "true" : "false") << ">::compute(inteval"; + + oss << "," << args->symbol(0); // target + const unsigned int nargs = args->n(); + for (unsigned int a = 1; a < nargs; a++) { + oss << "," << args->symbol(a); + } + // L == 0 => second argument not needed + if (nargs == 2) oss << ",0"; + + // the spectator dimensions: runtime arguments of the (shared) function + // where explicit (see adapt_dims_), else 1 by construction (the + // differentiated shell is the first/last function) + const auto localdims = adapt_dims_(ImplicitDimensions::default_dims()); + if (expl_high_dim()) + oss << "," << localdims->high()->id(); + else + oss << ",1"; + if (expl_low_dim()) + oss << "," << localdims->low()->id(); + else + oss << ",1"; + + // direction + oss << "," << dir_; + + // two_alpha prefactor + oss << ",inteval->two_alpha" << part << "_" + << (where == InBra ? "bra" : "ket"); + + oss << ");"; + + CR_DerivGauss_GenericInstantiator::instance().add(L, vectorize); + + return oss.str(); +} +#endif // LIBINT_ENABLE_GENERIC_CODE + }; // namespace libint2 #endif From d20e0924b34a32f71a5c13a4d96c57fba73d632c Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sun, 25 Oct 2026 11:33:17 -0400 Subject: [PATCH 60/64] Define the code-generator progress reporter once, as an inline variable in buildtest.h GenerateCode() in buildtest.h references g_progress, which was defined only in build_libint.cc and test.cc, so libint-compiler-eri-test-generate failed to link. --- src/bin/libint/build_libint.cc | 2 -- src/bin/libint/buildtest.h | 3 ++- src/bin/libint/test.cc | 6 ------ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/bin/libint/build_libint.cc b/src/bin/libint/build_libint.cc index 679f7d44c..2726ea585 100644 --- a/src/bin/libint/build_libint.cc +++ b/src/bin/libint/build_libint.cc @@ -68,8 +68,6 @@ using namespace std; using namespace libint2; -CodeGenProgress g_progress; - enum ShellSetType { ShellSetType_Standard = LIBINT_SHELL_SET_STANDARD, ShellSetType_ORCA = LIBINT_SHELL_SET_ORCA diff --git a/src/bin/libint/buildtest.h b/src/bin/libint/buildtest.h index 67eecd6c1..1b18cf95c 100644 --- a/src/bin/libint/buildtest.h +++ b/src/bin/libint/buildtest.h @@ -104,7 +104,8 @@ struct CodeGenProgress { #endif } }; -extern CodeGenProgress g_progress; +// the global progress reporter of the code generator, used by GenerateCode() +inline CodeGenProgress g_progress; namespace libint2 { diff --git a/src/bin/libint/test.cc b/src/bin/libint/test.cc index 0b02243a2..9f9d0c061 100644 --- a/src/bin/libint/test.cc +++ b/src/bin/libint/test.cc @@ -38,12 +38,6 @@ using namespace std; using namespace libint2; -// GenerateCode() (defined in buildtest.h) references the global progress -// reporter, so this standalone compiler-test executable must define it too, -// mirroring the definition in build_libint.cc. Without this the -// check-libint2compiler test fails to link with an undefined `g_progress`. -CodeGenProgress g_progress; - long living_count = 0; const unsigned int use_integrals = std::numeric_limits::max(); const unsigned int use_quartets = 0; From 5a3755cddf18956ef2669e6c64e917f14283e769 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sun, 25 Oct 2026 11:33:25 -0400 Subject: [PATCH 61/64] Engine: leave no kernels after initialize() rejects the operator/braket; fix the RKB xx_xs alias test - set() changes oper_/braket_ before calling initialize(), so when initialize() rejected RKB or xx_xs derivatives the Engine kept the kernel table of the previous operator and compute() dispatched it (e.g. eri1 kernels for coulomb_opop). Clear buildfnptrs_ before throwing, as for lmax_exceeded, so compute() throws missing_kernel; add a test. - the xx_xs alias test constructed its Engines with the default (4-center) braket, which throws lmax_exceeded when LIBINT2_RKB_ERI_MAX_AM is below the 3-center AM; pass the braket to the constructor. --- export/tests/unit/test-2body.cc | 31 +++++++++++++++++++++++-------- include/libint2/engine.impl.h | 11 +++++++++-- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 4b3a37e00..82bc3ae0d 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -574,6 +574,18 @@ TEST_CASE("lmax_exceeded leaves no kernels", "[engine][2-body]") { #endif } +// likewise, if set() changes the operator to one that initialize() rejects, +// the next compute() must not use the kernel table of the previous operator +TEST_CASE("rejected set() leaves no kernels", "[engine][2-body]") { +#if defined(LIBINT2_SUPPORT_ERI) && LIBINT2_DERIV_ERI_ORDER > 0 + const Shell s{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}; + Engine engine(Operator::coulomb, 1, 0, 1); + REQUIRE(engine.compute(s, s, s, s)[0] != nullptr); + REQUIRE_THROWS_AS(engine.set(Operator::coulomb_opop), std::invalid_argument); + CHECK_THROWS_AS(engine.compute(s, s, s, s), Engine::missing_kernel); +#endif +} + TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { const std::vector all_shells{ // pseudorandom s @@ -1069,14 +1081,17 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { const auto obs = shells_upto(LIBINT2_MAX_AM_3coulomb_opop); const auto max_nprim = libint2::max_nprim(obs); const auto max_l = libint2::max_l(obs); - Engine eng_xs(Operator::coulomb_opop, - std::max(max_nprim, libint2::max_nprim(dfs)), - std::max(max_l, libint2::max_l(dfs)), 0); - eng_xs.set(BraKet::xs_xx); - Engine eng_xxs(Operator::coulomb_opop, - std::max(max_nprim, libint2::max_nprim(dfs)), - std::max(max_l, libint2::max_l(dfs)), 0); - eng_xxs.set(BraKet::xx_xs); + // pass the braket to the constructor: the default (4-center) braket may + // not support max_l if LIBINT2_RKB_ERI_MAX_AM < LIBINT2_RKB_ERI3_MAX_AM + const auto eps = std::numeric_limits::epsilon(); + Engine eng_xs( + Operator::coulomb_opop, std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0, eps, + libint2::default_params(Operator::coulomb_opop), BraKet::xs_xx); + Engine eng_xxs( + Operator::coulomb_opop, std::max(max_nprim, libint2::max_nprim(dfs)), + std::max(max_l, libint2::max_l(dfs)), 0, eps, + libint2::default_params(Operator::coulomb_opop), BraKet::xx_xs); for (size_t sa = 0; sa != obs.size(); ++sa) { for (size_t sb = 0; sb != obs.size(); ++sb) { diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index 7538e395c..aea2c7354 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -688,16 +688,23 @@ __libint2_engine_inline void Engine::_initialize() { __libint2_engine_inline void Engine::initialize(size_t max_nprim) { assert(libint2::initialized() && "libint is not initialized"); - if (detail::is_rkb_2body(oper_) && deriv_order_ > 0) + // if the arguments are rejected leave no table for the previous + // operator/braket (set() has already changed them), so that compute() + // throws missing_kernel instead of dispatching it + if (detail::is_rkb_2body(oper_) && deriv_order_ > 0) { + buildfnptrs_ = nullptr; throw std::invalid_argument( "Engine::initialize -- geometric derivatives of RKB integrals are not " "yet supported"); + } // energies canonicalize xx_xs to xs_xx, but DerivMapGenerator has no // derivative index map for xx_xs; fail here rather than inside compute2 - if (braket_ == BraKet::xx_xs && deriv_order_ > 0) + if (braket_ == BraKet::xx_xs && deriv_order_ > 0) { + buildfnptrs_ = nullptr; throw std::invalid_argument( "Engine::initialize -- geometric derivatives are not supported for " "BraKet::xx_xs, use BraKet::xs_xx"); + } assert(deriv_order_ <= LIBINT2_MAX_DERIV_ORDER && "exceeded the max derivative order of the library"); From 40e93893103af589ccdfe11983ebd7a7c77d6772 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sun, 25 Oct 2026 13:53:31 -0400 Subject: [PATCH 62/64] Tests: fix the two CI failures of 5a3755cd - "lmax_exceeded leaves no kernels" aborted in Debug builds: set(BraKet::xx_xx) changes the braket before initialize() throws, so the Engine is left as a kernel-less 4-center Engine and the 3-shell compute(fit, s, s) that followed tripped the argument-count assert of compute() (Release builds fell through to missing_kernel by accident). Check the braket instead, and add the coulomb_opop/3coulomb_opop variant of the test, which is live whenever LIBINT2_RKB_ERI3_MAX_AM > LIBINT2_RKB_ERI_MAX_AM. - The Release lane with LIBINT2_MAX_AM="2;2;1" and ERI derivatives up to 2 (introduced in aceb113e) segfaulted in the Fortran ERI test and example and failed hartree-fock++: all three computed 2nd-derivative integrals over a d shell although the 2nd-derivative kernels stop at p. The Fortran test and example clamp the shells of each derivative order to LIBINT2_MAX_AM_eri; hartree-fock++ computes the 2-body forces/hessian (and the derivative 2-center checks) only if the kernels support the basis and says so otherwise, which the validator accepts. - The xx_xs alias and coulomb_op 3-center tests limit the AO shells by LIBINT2_MAX_AM, not by the fitting-center limit. --- export/fortran/fortran_example.F90 | 16 ++- export/fortran/test-eri.cc | 29 +++-- .../hartree-fock/hartree-fock++-validate.py | 19 +++ export/tests/hartree-fock/hartree-fock++.cc | 120 +++++++++++------- export/tests/unit/test-2body.cc | 48 +++++-- 5 files changed, 157 insertions(+), 75 deletions(-) diff --git a/export/fortran/fortran_example.F90 b/export/fortran/fortran_example.F90 index 25689a8b2..7343254a4 100644 --- a/export/fortran/fortran_example.F90 +++ b/export/fortran/fortran_example.F90 @@ -10,10 +10,10 @@ PROGRAM fortran_example #ifdef LIBINT_INCLUDE_ERI USE libint_f, ONLY: libint2_init_eri, libint2_cleanup_eri #if LIBINT_INCLUDE_ERI >= 1 - USE libint_f, ONLY: libint2_init_eri1, libint2_cleanup_eri1 + USE libint_f, ONLY: libint2_init_eri1, libint2_cleanup_eri1, libint2_max_am_eri1 #endif #if LIBINT_INCLUDE_ERI >= 2 - USE libint_f, ONLY: libint2_init_eri2, libint2_cleanup_eri2 + USE libint_f, ONLY: libint2_init_eri2, libint2_cleanup_eri2, libint2_max_am_eri2 #endif #endif @@ -68,7 +68,14 @@ PROGRAM fortran_example CALL libint2_cleanup_eri(erieval) #if LIBINT_INCLUDE_ERI >= 1 + ! the derivative kernels may stop at a lower angular momentum than the + ! energy kernels (e.g. LIBINT2_MAX_AM="2;2;1"): clamp the shells to it deriv_order = 1 + am1 = MINVAL([1, libint2_max_am_eri1]) + am2 = MINVAL([0, libint2_max_am_eri1]) + am3 = MINVAL([2, libint2_max_am_eri1]) + am4 = MINVAL([0, libint2_max_am_eri1]) + max_am = MAXVAL([am1, am2, am3, am4]) CALL libint2_init_eri1(erieval, max_am, C_NULL_PTR) CALL compute_eri_f(1, deriv_order, am1, c1, alpha1, A, & am2, c2, alpha2, B, & @@ -80,6 +87,11 @@ PROGRAM fortran_example #endif #if LIBINT_INCLUDE_ERI >= 2 deriv_order = 2 + am1 = MINVAL([1, libint2_max_am_eri2]) + am2 = MINVAL([0, libint2_max_am_eri2]) + am3 = MINVAL([2, libint2_max_am_eri2]) + am4 = MINVAL([0, libint2_max_am_eri2]) + max_am = MAXVAL([am1, am2, am3, am4]) CALL libint2_init_eri2(erieval, max_am, C_NULL_PTR) CALL compute_eri_f(1, deriv_order, am1, c1, alpha1, A, & am2, c2, alpha2, B, & diff --git a/export/fortran/test-eri.cc b/export/fortran/test-eri.cc index d25381474..50cc86306 100644 --- a/export/fortran/test-eri.cc +++ b/export/fortran/test-eri.cc @@ -580,23 +580,24 @@ TEST_CASE("Fortran ERI", "[eri]") { double c4[] = {rsqset.coef[3][0][0], rsqset.coef[3][0][1]}; #ifdef LIBINT_INCLUDE_ERI - int deriv_order = 0; - REQUIRE(test_eri_c_f(contrdepth, am1, c1, alpha1, A, am2, c2, alpha2, B, am3, - c3, alpha3, C, am4, c4, alpha4, D, deriv_order, - threshold)); - + // the derivative kernels may stop at a lower angular momentum than the + // energy kernels (e.g. LIBINT2_MAX_AM="2;2;1"): clamp the shells of each + // derivative order to the limit of its kernels + auto test_deriv_order = [&](int deriv_order, int lmax) { + int am1_d = std::min(am1, lmax); + int am2_d = std::min(am2, lmax); + int am3_d = std::min(am3, lmax); + int am4_d = std::min(am4, lmax); + return test_eri_c_f(contrdepth, am1_d, c1, alpha1, A, am2_d, c2, alpha2, B, + am3_d, c3, alpha3, C, am4_d, c4, alpha4, D, deriv_order, + threshold); + }; + REQUIRE(test_deriv_order(0, LIBINT2_MAX_AM_eri)); #if LIBINT_INCLUDE_ERI >= 1 - deriv_order = 1; - REQUIRE(test_eri_c_f(contrdepth, am1, c1, alpha1, A, am2, c2, alpha2, B, am3, - c3, alpha3, C, am4, c4, alpha4, D, deriv_order, - threshold)); + REQUIRE(test_deriv_order(1, LIBINT2_MAX_AM_eri1)); #endif - #if LIBINT_INCLUDE_ERI >= 2 - deriv_order = 2; - REQUIRE(test_eri_c_f(contrdepth, am1, c1, alpha1, A, am2, c2, alpha2, B, am3, - c3, alpha3, C, am4, c4, alpha4, D, deriv_order, - threshold)); + REQUIRE(test_deriv_order(2, LIBINT2_MAX_AM_eri2)); #endif #endif // LIBINT_INCLUDE_ERI } diff --git a/export/tests/hartree-fock/hartree-fock++-validate.py b/export/tests/hartree-fock/hartree-fock++-validate.py index 030525dc5..95be09631 100644 --- a/export/tests/hartree-fock/hartree-fock++-validate.py +++ b/export/tests/hartree-fock/hartree-fock++-validate.py @@ -137,6 +137,13 @@ def validate(label, data, refdata, tolerance, textline): match13 = re.match(r'\*\* Hartree-Fock hessian =' + pat_numbers(45), line) match14 = re.match(r'\*\* sph edipole =' + pat_numbers(3), line) match15 = re.match(r'\*\* sph equadrupole =' + pat_numbers(5), line) + # the derivative ERI kernels of the library may stop below the AM of the + # basis (e.g. LIBINT2_MAX_AM="2;2;1"); hartree-fock++ then says so instead + # of printing the quantity, and there is nothing to validate + match16 = re.match(r'\*\* 2-body forces skipped', line) + match17 = re.match(r'\*\* Hartree-Fock forces skipped', line) + match18 = re.match(r'\*\* 2-body hessian skipped', line) + match19 = re.match(r'\*\* Hartree-Fock hessian skipped', line) if match1: eok = validate("HF energy", match1.groups(), eref, etol, line) elif match2: @@ -167,6 +174,18 @@ def validate(label, data, refdata, tolerance, textline): smuok = validate("spherical electric dipole moment", match14.groups(), smuref, smutol, line) elif match15: sQok = validate("spherical electric quadrupole moment", match15.groups(), sQref, sQtol, line) + elif match16: + F2ok = True + print(line,end="") + elif match17: + Fok = True + print(line,end="") + elif match18: + H2ok = True + print(line,end="") + elif match19: + Hok = True + print(line,end="") else: print(line,end="") diff --git a/export/tests/hartree-fock/hartree-fock++.cc b/export/tests/hartree-fock/hartree-fock++.cc index 13064ff9a..2e45d3b41 100644 --- a/export/tests/hartree-fock/hartree-fock++.cc +++ b/export/tests/hartree-fock/hartree-fock++.cc @@ -661,23 +661,32 @@ int main(int argc, char* argv[]) { #if LIBINT2_DERIV_ERI_ORDER // compute 2-e forces Matrix F2 = Matrix::Zero(atoms.size(), 3); - - ////////// - // two-body contributions to the forces - ////////// - auto G1 = compute_2body_fock_deriv<1>(obs, atoms, D); - for (auto atom = 0, i = 0; atom != atoms.size(); ++atom) { - for (auto xyz = 0; xyz != 3; ++xyz, ++i) { - // identity prefactor since E(HF) = trace(H + F, D) = trace(2H + G, D) - auto force = G1[i].cwiseProduct(D).sum(); - F2(atom, xyz) += force; + // the derivative kernels may stop at a lower angular momentum than the + // energy kernels (e.g. LIBINT2_MAX_AM="2;2;1"): then say so instead of + // printing the quantity (the validator accepts that) + const bool have_F2 = obs.max_l() <= LIBINT2_MAX_AM_eri1; + if (have_F2) { + ////////// + // two-body contributions to the forces + ////////// + auto G1 = compute_2body_fock_deriv<1>(obs, atoms, D); + for (auto atom = 0, i = 0; atom != atoms.size(); ++atom) { + for (auto xyz = 0; xyz != 3; ++xyz, ++i) { + // identity prefactor since E(HF) = trace(H + F, D) = trace(2H + G, + // D) + auto force = G1[i].cwiseProduct(D).sum(); + F2(atom, xyz) += force; + } } - } - std::cout << "** 2-body forces = "; - for (int atom = 0; atom != atoms.size(); ++atom) - for (int xyz = 0; xyz != 3; ++xyz) std::cout << F2(atom, xyz) << " "; - std::cout << std::endl; + std::cout << "** 2-body forces = "; + for (int atom = 0; atom != atoms.size(); ++atom) + for (int xyz = 0; xyz != 3; ++xyz) std::cout << F2(atom, xyz) << " "; + std::cout << std::endl; + } else + std::cout << "** 2-body forces skipped: the 1st-derivative ERI kernels " + "of this library support l <= " + << LIBINT2_MAX_AM_eri1 << " only" << std::endl; #endif // if support 1-e and 2-e derivatives compute nuclear repulsion force and the @@ -720,11 +729,15 @@ int main(int argc, char* argv[]) { for (int xyz = 0; xyz != 3; ++xyz) std::cout << FN(atom, xyz) << " "; std::cout << std::endl; - auto F = F1 + F_Pulay + F2 + FN; - std::cout << "** Hartree-Fock forces = "; - for (int atom = 0; atom != atoms.size(); ++atom) - for (int xyz = 0; xyz != 3; ++xyz) std::cout << F(atom, xyz) << " "; - std::cout << std::endl; + if (have_F2) { + auto F = F1 + F_Pulay + F2 + FN; + std::cout << "** Hartree-Fock forces = "; + for (int atom = 0; atom != atoms.size(); ++atom) + for (int xyz = 0; xyz != 3; ++xyz) std::cout << F(atom, xyz) << " "; + std::cout << std::endl; + } else + std::cout << "** Hartree-Fock forces skipped: no 2-body forces" + << std::endl; #endif } @@ -780,28 +793,36 @@ int main(int argc, char* argv[]) { #endif // LIBINT2_DERIV_ONEBODY_ORDER > 1 #if LIBINT2_DERIV_ERI_ORDER > 1 - // compute 2-e forces + // compute 2-e hessian Matrix H2 = Matrix::Zero(ncoords, ncoords); - - ////////// - // two-body contributions to the forces - ////////// - auto G2 = compute_2body_fock_deriv<2>(obs, atoms, D); - for (auto row = 0, i = 0; row != ncoords; ++row) { - for (auto col = row; col != ncoords; ++col, ++i) { - // identity prefactor since E(HF) = trace(H + F, D) = trace(2H + G, D) - auto hess = G2[i].cwiseProduct(D).sum(); - H2(row, col) += hess; + // as for the forces: the 2nd-derivative kernels may not support the AM + // of the basis (e.g. LIBINT2_MAX_AM="2;2;1") + const bool have_H2 = obs.max_l() <= LIBINT2_MAX_AM_eri2; + if (have_H2) { + ////////// + // two-body contributions to the hessian + ////////// + auto G2 = compute_2body_fock_deriv<2>(obs, atoms, D); + for (auto row = 0, i = 0; row != ncoords; ++row) { + for (auto col = row; col != ncoords; ++col, ++i) { + // identity prefactor since E(HF) = trace(H + F, D) = trace(2H + G, + // D) + auto hess = G2[i].cwiseProduct(D).sum(); + H2(row, col) += hess; + } } - } - std::cout << "** 2-body hessian = "; - for (auto row = 0, i = 0; row != ncoords; ++row) { - for (auto col = row; col != ncoords; ++col) { - std::cout << H2(row, col) << " "; + std::cout << "** 2-body hessian = "; + for (auto row = 0, i = 0; row != ncoords; ++row) { + for (auto col = row; col != ncoords; ++col) { + std::cout << H2(row, col) << " "; + } } - } - std::cout << std::endl; + std::cout << std::endl; + } else + std::cout << "** 2-body hessian skipped: the 2nd-derivative ERI " + "kernels of this library support l <= " + << LIBINT2_MAX_AM_eri2 << " only" << std::endl; #endif // if support 1-e and 2-e 2nd derivatives compute nuclear repulsion hessian and @@ -866,14 +887,18 @@ int main(int argc, char* argv[]) { } std::cout << std::endl; - auto H = H1 + H_Pulay + H2 + HN; - std::cout << "** Hartree-Fock hessian = "; - for (auto row = 0, i = 0; row != ncoords; ++row) { - for (auto col = row; col != ncoords; ++col) { - std::cout << H(row, col) << " "; + if (have_H2) { + auto H = H1 + H_Pulay + H2 + HN; + std::cout << "** Hartree-Fock hessian = "; + for (auto row = 0, i = 0; row != ncoords; ++row) { + for (auto col = row; col != ncoords; ++col) { + std::cout << H(row, col) << " "; + } } - } - std::cout << std::endl; + std::cout << std::endl; + } else + std::cout << "** Hartree-Fock hessian skipped: no 2-body hessian" + << std::endl; #endif } @@ -2564,7 +2589,8 @@ void api_basic_compile_test(const BasisSet& obs, } #if LIBINT2_DERIV_ERI_ORDER - { // test deriv 2-index ints + // skipped if the derivative kernels do not support the AM of the basis + if (obs.max_l() <= LIBINT2_MAX_AM_eri1) { // test deriv 2-index ints Engine eri4_engine(Operator::coulomb, obs.max_nprim(), obs.max_l(), 1); Engine eri2_engine = eri4_engine; eri2_engine.set(BraKet::xs_xs); @@ -2599,7 +2625,7 @@ void api_basic_compile_test(const BasisSet& obs, #endif #if LIBINT2_DERIV_ERI_ORDER > 1 - { // test 2nd deriv 2-index ints + if (obs.max_l() <= LIBINT2_MAX_AM_eri2) { // test 2nd deriv 2-index ints Engine eri4_engine(Operator::coulomb, obs.max_nprim(), obs.max_l(), 2); Engine eri2_engine = eri4_engine; eri2_engine.set(BraKet::xs_xs); diff --git a/export/tests/unit/test-2body.cc b/export/tests/unit/test-2body.cc index 82bc3ae0d..f0a3428ee 100644 --- a/export/tests/unit/test-2body.cc +++ b/export/tests/unit/test-2body.cc @@ -559,18 +559,38 @@ TEST_CASE("eri geometric derivatives vs finite differences", // the next compute() must throw Engine::missing_kernel rather than use the // kernel table of the previous task TEST_CASE("lmax_exceeded leaves no kernels", "[engine][2-body]") { + // @param lmax valid for the 3-center task of @p oper, too high for its + // 4-center task + // @param pure_fit whether the fitting shell is a solid harmonics shell + // (required by LIBINT2_ERI3_PURE_SH for l > 1, not + // generated for the RKB 3-center kernels) + auto check = [](Operator oper, int lmax, bool pure_fit) { + const Shell fit{{1.5}, {{lmax, pure_fit, {1.0}}}, {{-1.0, 0.5, 0.0}}}; + const Shell s{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}; + const auto eps = std::numeric_limits::epsilon(); + Engine engine(oper, 1, lmax, 0, eps, libint2::default_params(oper), + BraKet::xs_xx); + REQUIRE(engine.compute(fit, s, s)[0] != nullptr); + REQUIRE_THROWS_AS(engine.set(BraKet::xx_xx), Engine::lmax_exceeded); + // set() changes the braket before it (re)initializes the kernels, so the + // Engine is left as a kernel-less 4-center Engine: compute() takes 4 + // shells (a 3-shell call would violate the argument count, which is + // asserted in Debug builds) and has no kernels to dispatch + CHECK(engine.braket() == BraKet::xx_xx); + CHECK_THROWS_AS(engine.compute(s, s, s, s), Engine::missing_kernel); + }; #if defined(LIBINT2_SUPPORT_ERI) && defined(LIBINT2_SUPPORT_ERI3) && \ LIBINT2_MAX_AM_3eri > LIBINT2_MAX_AM_eri - const int lmax = LIBINT2_MAX_AM_3eri; // valid for 3eri, too high for eri - const Shell fit{{1.5}, {{lmax, true, {1.0}}}, {{-1.0, 0.5, 0.0}}}; - const Shell s{{1.0}, {{0, false, {1.0}}}, {{0.0, 0.0, 0.0}}}; - const auto eps = std::numeric_limits::epsilon(); - Engine engine(Operator::coulomb, 1, lmax, 0, eps, - libint2::default_params(Operator::coulomb), BraKet::xs_xx); - REQUIRE(engine.compute(fit, s, s)[0] != nullptr); - REQUIRE_THROWS_AS(engine.set(BraKet::xx_xx), Engine::lmax_exceeded); - CHECK_THROWS_AS(engine.compute(s, s, s, s), Engine::missing_kernel); - CHECK_THROWS_AS(engine.compute(fit, s, s), Engine::missing_kernel); + SECTION("coulomb") { + check(Operator::coulomb, LIBINT2_MAX_AM_3eri, /* pure_fit = */ true); + } +#endif +#if LIBINT2_TASK_EXISTS_3coulomb_opop && LIBINT2_TASK_EXISTS_4coulomb_opop && \ + LIBINT2_MAX_AM_3coulomb_opop > LIBINT2_MAX_AM_coulomb_opop + SECTION("coulomb_opop") { + check(Operator::coulomb_opop, LIBINT2_MAX_AM_3coulomb_opop, + /* pure_fit = */ false); + } #endif } @@ -1078,7 +1098,10 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { Shell dfsh{{1.5}, {{0, false, {1.0}}}, {{-1.0, 0.5, 0.0}}}; std::vector dfs{dfsh}; - const auto obs = shells_upto(LIBINT2_MAX_AM_3coulomb_opop); + // the AO pair is limited by LIBINT2_MAX_AM, the fitting center by + // LIBINT2_RKB_ERI3_MAX_AM + const auto obs = shells_upto( + std::min(LIBINT2_MAX_AM_3coulomb_opop, LIBINT2_MAX_AM_default)); const auto max_nprim = libint2::max_nprim(obs); const auto max_l = libint2::max_l(obs); // pass the braket to the constructor: the default (4-center) braket may @@ -1153,7 +1176,8 @@ TEST_CASE("RKB Coulomb integrals", "[engine][2-body]") { Shell dfsh{{1.5}, {{0, false, {1.0}}}, {{-1.0, 0.5, 0.0}}}; std::vector dfs{dfsh}; - const auto obs = shells_upto(LIBINT2_MAX_AM_3coulomb_op); + const auto obs = shells_upto( + std::min(LIBINT2_MAX_AM_3coulomb_op, LIBINT2_MAX_AM_default)); const auto max_nprim = libint2::max_nprim(obs); const auto max_l = libint2::max_l(obs); Engine engine_3c(Operator::coulomb_op, From 9fba2b0de355513221a0317d6483b60177535a2e Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sun, 25 Oct 2026 13:53:31 -0400 Subject: [PATCH 63/64] =?UTF-8?q?Engine:=20check=20the=20per-center=20AM?= =?UTF-8?q?=20limits=20in=20all=20builds;=20rebuild=20the=20core=20evaluat?= =?UTF-8?q?or=20in=20set(BraKet)/set=5Fmax=5Fl;=20document=20the=20=CF=83?= =?UTF-8?q?=C2=B7p=20prefactors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compute2() checked the angular momentum of each shell against the limit of its center only by assert. initialize() checks lmax_ against the most permissive center, so with a center-dependent table (3eri, or 3coulomb_opop with LIBINT2_RKB_ERI3_MAX_AM != LIBINT2_MAX_AM) an AO shell could pass that check and, in Release, index the kernel table out of range and run another shell set's kernel. Throw lmax_exceeded instead, in all builds; the asserts also used <= where < was needed (hard_lmax_ is the limit + 1). The task label is kept for the message. set(BraKet) and set_max_l() did not rebuild core_eval_pack_, whose order is braket_rank() * lmax_ + deriv_order_, so e.g. Engine(..., BraKet::xs_xx) followed by set(BraKet::xx_xx) could need a higher Boys order than the evaluator was built for. The rkb_operators page gets a prefactor column: all kernels differentiate with respect to the centers of the functions (∂_X = -∂_r), so σ·p contributes +i (on b, d) or -i (on the conjugated a, c). coulomb_opop and opop_coulomb_opop need no prefactor beyond the folded spin block q₀ + i σ·q, coulomb_op needs +i, op_coulomb_op needs -1. The old comment on coulomb_op said -i, which is wrong. The page is moved so that it no longer separates the doc of rank() from the function, and the sentence "acts on b and/or d" (wrong for coulomb_opop and opop_coulomb_opop) is replaced by a column. --- CHANGES | 8 ++++ include/libint2/engine.h | 70 ++++++++++++++++++++++++----------- include/libint2/engine.impl.h | 39 ++++++++++--------- 3 files changed, 78 insertions(+), 39 deletions(-) diff --git a/CHANGES b/CHANGES index 8c5f968d1..966564eb2 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,14 @@ Following is a brief summary of changes made in each release of Libint. braket type; missing kernels are reported by Engine::missing_kernel from compute(). - the validation of LIBINT2__MAX_AM in cmake/modules/int_am.cmake now actually runs (it referred to undefined variables); a value of 0 is accepted, negative values are rejected. + - Engine::compute() throws Engine::lmax_exceeded in all builds if a shell exceeds the angular + momentum limit of its center (e.g. an AO shell beyond LIBINT2_MAX_AM given to a 3-center Engine + whose max_l is within LIBINT2_ERI3_MAX_AM); previously a Debug-only assert, and Release builds + dispatched the kernel of another shell set. + - Engine::set(BraKet) and Engine::set_max_l() rebuild the core integral evaluator, whose order + depends on the rank of the braket and on the maximum angular momentum. + - the Fortran ERI test and example and the hartree-fock++ test honor a lower angular momentum + limit of the derivative kernels (e.g. LIBINT2_MAX_AM="2;2;1"). - PR #449: generated and generic RR functions declare their target LIBINT2_RESTRICT (new config.h macro), up to ~1.2x faster high-am and derivative ERIs - PR #446: a value of 0 for LIBINT2_OPT_AM (or for the per-class LIBINT2__MAX_AM / diff --git a/include/libint2/engine.h b/include/libint2/engine.h index 2f6b88311..63c3c75b8 100644 --- a/include/libint2/engine.h +++ b/include/libint2/engine.h @@ -245,7 +245,9 @@ enum class Operator { /// (2-body) \f$ r_{12}^{-1} (σ.p_{k2}) \f$ where k2 is the center of ket2; /// the 3-center single-σ·p integral (P | μ, σ·p ν). σ·p acts on /// the 2nd ket AO function only. Produces 3 Cartesian components (x=0, y=1, - /// z=2) — the SO(3) vector irrep; no σ·σ folding (cf. coulomb_opop). The + /// z=2), the derivatives of ν with respect to its center, i.e. the SO(3) + /// vector irrep without σ·σ folding (cf. coulomb_opop); the user multiplies + /// them by +i (see \ref rkb_operators). The /// fitting function sits on the bra (BraKet::xs_xx); 3-center only. With /// BraKet::xx_xs (the alias of xs_xx) σ·p acts on bra2, the 2nd AO /// function, and the fitting function is ket1. @@ -262,7 +264,8 @@ enum class Operator { /// `σpCoulombσp_Descr::Component`: Scalar trace (0); antisymmetric /// (curl-curl) AntisymX,Y,Z (1-3); symmetric-traceless SymTLDiagA,SymTLDiagB /// (4,5) and SymTLOffXY,SymTLOffXZ,SymTLOffYZ (6-8). Unlike coulomb_opop, - /// these components are not σ·σ-folded. + /// these components are not σ·σ-folded; the user multiplies them by -1 + /// (see \ref rkb_operators). op_coulomb_op, // do not modify this invalid = -1, @@ -276,27 +279,38 @@ enum class Operator { last_oper = last_2body_oper }; -/// @param[in] oper an Operator object -/// @return the particle rank of \c oper -/// @throw std::logic_error if an invalid @c oper given // clang-format off /** \page rkb_operators Conventions for the relativistic (RKB) 2-body operators The four operators built from σ·p and 1/r₁₂ expose their components in - different ways; this table is the single reference for their layouts. - "Folded" means that the Pauli matrices were contracted using - σ_a σ_b = δ_ab + i ε_abc σ_c, so the user must not apply them again; - "not folded" means the result is the spatial tensor and the user contracts it - with the Pauli matrices. In chemists' notation (ab|cd) the operator acts on - the second function of a pair, i.e. on b and/or d. - - | Engine (C++ API) | Compiler | task label | #comp | component index | spin | - |---------------------|-------------------|---------------------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------|------------| - | `coulomb_opop` | `Coulombσpσp` | `coulomb_opop` | 4 | 0: ∇_c·∇_d, 1-3: (∇_c×∇_d)_{x,y,z} | folded | - | `coulomb_op` | `Coulombσp` | `coulomb_op` | 3 | ∂_{d,x}, ∂_{d,y}, ∂_{d,z} (3-center only) | n/a | - | `opop_coulomb_opop` | `σpσpCoulombσpσp` | `opop_coulomb_opop` | 16 | 4·bra + ket, with bra, ket ∈ {0: ∇·∇, 1-3: (∇×∇)_{x,y,z}} of (a,b) and (c,d); the sign of i² is folded into the 1-3 ⊗ 1-3 block | folded | - | `op_coulomb_op` | `σpCoulombσp` | `op_coulomb_op` | 9 | SO(3) irreps of T_ab = ∂_{b,a} ∂_{d,b}: 0: trace, 1-3: T_yz−T_zy, T_zx−T_xz, T_xy−T_yx, 4: T_xx−T_yy, 5: 2T_zz−T_xx−T_yy, 6-8: T_xy+T_yx, T_xz+T_zx, T_yz+T_zy | not folded | + different ways; this table is the single reference for their layouts and + prefactors. + + All kernels evaluate integrals over derivatives of the basis functions with + respect to their *centers* (libint's derivative convention): for a function + φ_X centered at X, ∂_{X,k} φ_X ≡ ∂φ_X/∂X_k = −∂φ_X/∂r_k. Hence + p_k φ_X = −i ∂φ_X/∂r_k = +i ∂_{X,k} φ_X, and (p_k φ_X)* = −i ∂_{X,k} φ_X for + a real φ_X. In chemists' notation (ab|cd) the first function of each pair + (a, c) is complex conjugated; a σ·p that acts on a or c therefore contributes + −i, one that acts on b or d contributes +i. The "prefactor" column is what + the user multiplies the kernel's center-derivative integrals with to obtain + the σ·p integrals, with σ₁ the Pauli matrices in the spin space of electron 1 + (the pair ab) and σ₂ those of electron 2 (the pair cd). + + "Folded" means that the two σ of the same electron were contracted using + σ_i σ_j = δ_ij + i ε_ijk σ_k into a scalar (component 0) and a vector + (components 1-3) part; the user assembles the 2×2 spin block of that electron + as q₀·1 + i (q₁ σ_x + q₂ σ_y + q₃ σ_z) and must not apply the Pauli matrices + again. "Not folded" means the result is the spatial tensor and the user + contracts it with the Pauli matrices. + + | Engine (C++ API) | Compiler | task label | #comp | σ·p acts on | component index | prefactor | + |---------------------|-------------------|---------------------|-------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | `coulomb_opop` | `Coulombσpσp` | `coulomb_opop` | 4 | c and d | 0: ∇_c·∇_d, 1-3: (∇_c×∇_d)_{x,y,z} (folded) | 1: (σ₂·p c)† (σ₂·p d) = Σ_ij (−i ∂_{C,i} c)(+i ∂_{D,j} d) σ_i σ_j = Σ_ij ∂_{C,i} c ∂_{D,j} d σ_i σ_j, i.e. the spin block is q₀ + i σ₂·q | + | `coulomb_op` | `Coulombσp` | `coulomb_op` | 3 | d | ∂_{D,x}, ∂_{D,y}, ∂_{D,z} of d (3-center only; not folded) | +i: (σ₂·p d) = +i Σ_k σ_{2,k} ∂_{D,k} d | + | `opop_coulomb_opop` | `σpσpCoulombσpσp` | `opop_coulomb_opop` | 16 | a, b, c, d | 4·q₁ + q₂, with q₁ the folded component of (σ₁·p a)† (σ₁·p b) and q₂ that of (σ₂·p c)† (σ₂·p d), each ∈ {0: ∇·∇, 1-3: (∇×∇)_{x,y,z}}; the sign of i² is folded into the q₁,q₂ ≥ 1 block | 1 for q₁ = q₂ = 0, i σ_{2,q₂} for q₁ = 0, i σ_{1,q₁} for q₂ = 0, σ_{1,q₁} σ_{2,q₂} for q₁,q₂ ≥ 1 (the −1 of i² is in the kernel); the center derivatives contribute no prefactor, as for `coulomb_opop` | + | `op_coulomb_op` | `σpCoulombσp` | `op_coulomb_op` | 9 | b and d | SO(3) irreps of T_ij = (a ∂_{B,i} b \| c ∂_{D,j} d): 0: trace, 1-3: T_yz−T_zy, T_zx−T_xz, T_xy−T_yx, 4: T_xx−T_yy, 5: 2T_zz−T_xx−T_yy, 6-8: T_xy+T_yx, T_xz+T_zx, T_yz+T_zy (not folded) | −1: (a σ₁·p b \| c σ₂·p d) = Σ_ij σ_{1,i} σ_{2,j} (+i ∂_{B,i} b)(+i ∂_{D,j} d) = −Σ_ij σ_{1,i} σ_{2,j} T_ij | For the 3-center operators (`coulomb_opop` and `coulomb_op`) the kernels are generated for BraKet::xs_xx, with the fitting function as bra1 and the @@ -304,9 +318,11 @@ enum class Operator { changes the order of the arguments of compute() and the layout of the result (fitting function last), hence with xx_xs σ·p acts on the bra. - The 1-body analog of `opVop` with the dipole operator in the middle is - `opemuop` (compiler: `σpeμσp`), with 12 = 3 (dipole direction, slow) × 4 - (folded Pauli component, fast) components. + The 1-body operators `opVop`, `op_q_gau_op` and `opemuop` (compiler: + `σpVσp`, `σpeμσp`) follow the convention of `coulomb_opop`: (σ·p a)† O (σ·p b) + is folded into q₀ + i σ·q with no further prefactor. `opemuop` has + 12 = 3 (dipole direction, slow) × 4 (folded Pauli component, fast) + components. The behavior of the components under the permutations Engine uses to canonicalize shell sets is encoded in operator_traits: @@ -317,6 +333,9 @@ enum class Operator { */ // clang-format on +/// @param[in] oper an Operator object +/// @return the particle rank of \c oper +/// @throw std::logic_error if an invalid @c oper given inline constexpr int rank(Operator oper) { return (oper >= Operator::first_1body_oper && oper <= Operator::last_1body_oper) @@ -875,6 +894,7 @@ class Engine { lmax_(other.lmax_), hard_lmax_(other.hard_lmax_), hard_default_lmax_(other.hard_default_lmax_), + task_name_(other.task_name_), deriv_order_(other.deriv_order_), precision_(other.precision_), ln_precision_(other.ln_precision_), @@ -929,6 +949,7 @@ class Engine { lmax_ = other.lmax_; hard_lmax_ = other.hard_lmax_; hard_default_lmax_ = other.hard_default_lmax_; + task_name_ = other.task_name_; deriv_order_ = other.deriv_order_; precision_ = other.precision_; ln_precision_ = other.ln_precision_; @@ -1012,6 +1033,8 @@ class Engine { if (braket_ != new_braket) { braket_ = new_braket; initialize(); + // the order of the core evaluator depends on the rank of the braket + core_eval_pack_ = make_core_eval_pack(oper_); } return *this; } @@ -1057,6 +1080,8 @@ class Engine { if (L >= static_cast(lmax_)) { lmax_ = L; initialize(); + // the order of the core evaluator depends on lmax_ + core_eval_pack_ = make_core_eval_pack(oper_); } return *this; } @@ -1292,6 +1317,9 @@ class Engine { // LIBINT2_MAX_AM_default) + 1; this is only set and used if // LIBINT2_CENTER_DEPENDENT_MAX_AM_ == 1 int hard_default_lmax_ = 0; + // the label of the task whose kernels this Engine dispatches (a string + // literal, for error messages); set by _initialize() + const char* task_name_ = ""; int deriv_order_; scalar_type precision_; scalar_type ln_precision_; diff --git a/include/libint2/engine.impl.h b/include/libint2/engine.impl.h index aea2c7354..d56001481 100644 --- a/include/libint2/engine.impl.h +++ b/include/libint2/engine.impl.h @@ -641,6 +641,7 @@ __libint2_engine_inline void Engine::_initialize() { scratch2_ = nullptr; \ return; \ } \ + task_name_ = BOOST_PP_STRINGIZE(BOOST_PP_NBODYENGINE_MCR3_TASK(product)); \ hard_lmax_ = BOOST_PP_CAT(LIBINT2_MAX_AM_, \ BOOST_PP_NBODYENGINE_MCR3_TASK(product)) + \ 1; \ @@ -1998,17 +1999,24 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( timers.start(1); #endif + // the per-center angular momentum limits are checked in all builds: lmax_ + // (checked by initialize()) is the limit of the most permissive center, so + // for a task whose limit differs between the centers (e.g. 3eri, or + // 3coulomb_opop with LIBINT2_RKB_ERI3_MAX_AM != LIBINT2_MAX_AM) a shell can + // be within lmax_ and still beyond the limit of its center, and the index + // into the kernel table computed below would be out of range + auto check_lmax = [this](const Shell& sh, int lmax_plus_1) { + if (sh.contr[0].l >= lmax_plus_1) + throw lmax_exceeded(task_name_, lmax_plus_1 - 1, sh.contr[0].l); + }; + size_t buildfnidx; switch (braket_) { case BraKet::xx_xx: - assert(bra1.contr[0].l <= hard_lmax_ && - "the angular momentum limit is exceeded"); - assert(bra2.contr[0].l <= hard_lmax_ && - "the angular momentum limit is exceeded"); - assert(ket1.contr[0].l <= hard_lmax_ && - "the angular momentum limit is exceeded"); - assert(ket2.contr[0].l <= hard_lmax_ && - "the angular momentum limit is exceeded"); + check_lmax(bra1, hard_lmax_); + check_lmax(bra2, hard_lmax_); + check_lmax(ket1, hard_lmax_); + check_lmax(ket2, hard_lmax_); buildfnidx = ((bra1.contr[0].l * hard_lmax_ + bra2.contr[0].l) * hard_lmax_ + ket1.contr[0].l) * @@ -2034,12 +2042,9 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( hard_default_lmax_ != std::numeric_limits::max() ? hard_default_lmax_ : hard_lmax_; - assert(bra1.contr[0].l <= hard_lmax_ && - "the angular momentum limit is exceeded"); - assert(ket1.contr[0].l <= ket_lmax && - "the angular momentum limit is exceeded"); - assert(ket2.contr[0].l <= ket_lmax && - "the angular momentum limit is exceeded"); + check_lmax(bra1, hard_lmax_); + check_lmax(ket1, ket_lmax); + check_lmax(ket2, ket_lmax); buildfnidx = (bra1.contr[0].l * ket_lmax + ket1.contr[0].l) * ket_lmax + ket2.contr[0].l; @@ -2061,10 +2066,8 @@ __libint2_engine_inline const Engine::target_ptr_vec& Engine::compute2( } break; case BraKet::xs_xs: - assert(bra1.contr[0].l <= hard_lmax_ && - "the angular momentum limit is exceeded"); - assert(ket1.contr[0].l <= hard_lmax_ && - "the angular momentum limit is exceeded"); + check_lmax(bra1, hard_lmax_); + check_lmax(ket1, hard_lmax_); buildfnidx = bra1.contr[0].l * hard_lmax_ + ket1.contr[0].l; #ifdef LIBINT_ERI2_PURE_SH // as for 3-center: derivative kernels are generated for cartesian From 046a3133b7c080cbd6c1145ad26755c70820fdd6 Mon Sep 17 00:00:00 2001 From: Kshitij Surjuse Date: Sun, 25 Oct 2026 13:53:31 -0400 Subject: [PATCH 64/64] RKB composite relations: one term table per component drives both the children and the generated code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each of the four σ·p fold relations (CR_11_Coulombσp_11, CR_11_Coulombσpσp_11, CR_11_σpCoulombσp_11, CR_11_σpσpCoulombσpσp_11) kept its expansion twice: the order of the make_child() calls in the constructor and the sign string in generate_code(). The child-count guard caught a deduplicated child but not a reordering. Now each relation defines fold_terms(component): a list of (coefficient, derivative direction on a, b, c, d) terms, from which rkb_fold_make_children() creates the children (and the symbolic expression) and rkb_fold_generate_code_from_terms() emits "target = Σ coef_t src_t", so the two cannot disagree. The Pauli components of (σ·∇)(σ·∇') live in one table (rkb_fold_pair_terms); the 16-component relation is its product (with the -1 of i² on the cross × cross block), the 9 irreps of op_coulomb_op are listed explicitly. The generated kernels are unchanged up to the order of the terms in the dot × cross components and the spacing of a leading sign. Also: - σpσpCoulombσpσp_Descr::quaternion_index() is renamed component_index(): the 16 components are not a quaternion - the RKB descriptors note that their Multiplicative*_Props make hermitian() return +1, so HRR must stay out of their strategies - Coulombσp_Descr and CR_11_Coulombσp_11 state the correct prefactor (+i, not -i) and that the derivative is with respect to the center of the function - DerivGaussV2: the expansion formula is d/dA, not d/dr; the translational invariance instances no longer share the "CR_DerivGauss" label prefix with CR_DerivGauss's own instances (the RRStack deduplicates by label); the explicit-vs-generic decision uses the OPT_AM of the current task, not of the default task --- .../bin/libint/comp_11_Coulomb\317\203p_11.h" | 81 ++-- .../comp_11_Coulomb\317\203p\317\203p_11.h" | 145 ++----- .../comp_11_\317\203pCoulomb\317\203p_11.h" | 219 +++------- ...3p\317\203pCoulomb\317\203p\317\203p_11.h" | 377 +++--------------- src/bin/libint/comp_deriv_gauss_v2.h | 14 +- src/bin/libint/oper.h | 42 +- src/bin/libint/rkb_fold_codegen.h | 167 +++++++- 7 files changed, 391 insertions(+), 654 deletions(-) diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p_11.h" index 79edfcd4c..fd85b01f4 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p_11.h" @@ -30,23 +30,25 @@ #include +#include + namespace libint2 { /** - * this computes the 3-center single-σ·p integral - * \f$ \frac{1}{r_{12}} \, \sigma \cdot \hat{p}_2 \f$ acting on the 2nd ket - * function ν, by rewriting it as the bare Cartesian gradient of ν, i.e. one - * first-derivative integral over \f$ \frac{1}{r_{12}} \f$ per Cartesian - * direction. Unlike Coulombσpσp there is no Dirac σ·σ folding: each of the 3 - * components (x,y,z) is exactly one derivative ERI child, so it routes - * naturally through the TwoPRep DerivGaussV2 strategy. + * Computes the 3-center single-σ·p integral (a b | 1/r_{12} | c σ·p d), with + * the fitting function as a (bra1) and the unit function as b (ket1). Each + * Cartesian component k is the TwoPRep integral over d differentiated with + * respect to its center D in direction k (libint's derivative convention), + * i.e. exactly one first-derivative ERI child, so it routes naturally through + * the TwoPRep DerivGaussV2 strategy; unlike Coulombσpσp there is no σ·σ + * folding. Since p_k = -i ∂/∂r_k = +i ∂/∂D_k, the user multiplies component k + * by +i and contracts with σ_k (see the rkb_operators page in engine.h). * @tparam F basis function type. valid choices are CGShell or CGF */ template -class CR_11_Coulombσp_11 - : public GenericRecurrenceRelation< - CR_11_Coulombσp_11, F, - GenIntegralSet_11_11> { +class CR_11_Coulombσp_11 : public GenericRecurrenceRelation< + CR_11_Coulombσp_11, F, + GenIntegralSet_11_11> { public: typedef CR_11_Coulombσp_11 ThisType; typedef F BasisFunctionType; @@ -74,9 +76,20 @@ class CR_11_Coulombσp_11 static std::string descr() { return "CR"; } + /// The single term of Cartesian component @p k: the first derivative of d + /// (ket2) with respect to its center, direction k. The constructor (the + /// children) and generate_code() (the element-wise expression) are both + /// driven by this table, so they cannot disagree. + static RKBFoldTerms fold_terms(int k) { + if (k < 0 || k > 2) + throw std::runtime_error("CR_11_Coulombσp_11: invalid Cartesian index " + + std::to_string(k)); + return {RKBFoldTerm{1, {{-1, -1, -1, k}}}}; + } + // --- Code sharing overrides --- - // All shell combos with the same Cartesian component share one function - // (the element-wise copy below is spectator-dimension-independent). + // All shell sets with the same Cartesian component share one function + // (the element-wise copy is spectator-dimension-independent). std::string generate_label() const override { return "CR_Coulombop_" + @@ -94,15 +107,14 @@ class CR_11_Coulombσp_11 return rkb_fold_adapt_dims(dims); } - /// Hand-emit a simple element-wise copy: target = src0 (the single - /// first-derivative child). 0 FLOPs per component. + /// emits the element-wise copy target = src0 (0 flops) void generate_code(const std::shared_ptr& context, const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - // target = src0 (the single first-derivative child), 0 flops. - rkb_fold_generate_code(*this, context, dims, funcname, decl, def, - "src0[hsi]", 0); + rkb_fold_generate_code_from_terms( + *this, fold_terms(target_->oper()->descr().cartesian_index()), + "CR_11_Coulombσp_11", context, dims, funcname, decl, def); } }; @@ -120,41 +132,18 @@ CR_11_Coulombσp_11::CR_11_Coulombσp_11( F c(Tint->bra(1, 0)); F d(Tint->ket(1, 0)); - const auto& oper = Tint->oper(); - if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) return; - using namespace libint2::algebra; - using namespace libint2::prefactor; - using libint2::algebra::operator*; - - const mType zero_m(0u); - ChildFactory> factory(this); - - // single σ·p on ν (the 2nd ket function, d). Component k = ∂(d)/∂{x,y,z}. - // (Geometric-center derivative convention, matching CR_11_Coulombσpσp_11; the - // -i factor and Pauli matrix are applied by the caller.) - const int k = oper->descr().cartesian_index(); - if (k < 0 || k > 2) - throw std::runtime_error("CR_11_Coulombσp_11: invalid Cartesian index"); - - F d_k{d}; - d_k.deriv().inc(k); // d(d)/d{x,y,z} = d_k - - auto a_b_c_dk = factory.make_child(a, b, c, d_k, zero_m); - if (is_simple()) { - // Single derivative child; Scalar(1)* is only a registration vehicle (a - // bare child is not an expression). The actual emit is the element-wise - // copy in generate_code() above, so this contributes 0 real flops. - expr_ = Scalar(1) * a_b_c_dk; - nflops_ += 0; - } + rkb_fold_make_children(factory, a, b, c, d, + fold_terms(Tint->oper()->descr().cartesian_index()), + is_simple(), expr_, nflops_); } // CR_11_Coulombσp_11::CR_11_Coulombσp_11 -}; // namespace libint2 + +} // namespace libint2 #endif // LIBINT_COMP_11_COULOMBΣP_11_H diff --git "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" index f2b6b44b2..a5126cef6 100644 --- "a/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_Coulomb\317\203p\317\203p_11.h" @@ -35,10 +35,15 @@ namespace libint2 { /** - * this computes integral of - * \f$ \frac{1}{r_{ij}} \sigma \cdot \hat{p}_1 \sigma \cdot \hat{p}_2 \f$ over - * CGShell/CGF by rewriting it as a linear combination of integrals over - * derivatives of \frac{1}{r_{ij}} + * Computes the integral (a b | 1/r_{12} | (σ·p c)† (σ·p d)) over CGShell/CGF, + * with σ·p acting on both functions of electron 2 (c = bra2, d = ket2), as a + * linear combination of first-derivative TwoPRep integrals. The Dirac identity + * σ_i σ_j = δ_ij + i ε_ijk σ_k folds the two Pauli matrices into 4 quaternion + * components: 0: ∇_c·∇_d, 1-3: (∇_c × ∇_d)_{x,y,z}. The derivatives are with + * respect to the centers of c and d; since (p_i c)† (p_j d) = + * (-i ∂_{C,i} c)(+i ∂_{D,j} d) = ∂_{C,i} c ∂_{D,j} d, the user assembles the + * 2×2 spin block as q₀ + i (q₁ σ_x + q₂ σ_y + q₃ σ_z) with no further + * prefactor (see the rkb_operators page in engine.h). * @tparam F basis function type. valid choices are CGShell or CGF */ template @@ -55,9 +60,8 @@ class CR_11_Coulombσpσp_11 ParentType; friend class GenericRecurrenceRelation; - // children_.reserve() hint only (see generic_rr.h). The densest component - // sums 3 deriv-ERI children (the 2-component case uses 2), so 3 is the true - // upper bound across the 4 components. + // children_.reserve() hint only (see generic_rr.h): the scalar component + // sums 3 deriv-ERI children, the cross-product components 2 static const unsigned int max_nchildren = 3; using ParentType::Instance; @@ -76,8 +80,19 @@ class CR_11_Coulombσpσp_11 static std::string descr() { return "CR"; } + /// The terms of quaternion component @p q of (σ·∇_c)(σ·∇_d) (see + /// rkb_fold_pair_terms): derivative direction i on c (bra2), j on d (ket2). + /// The constructor (the children) and generate_code() (the element-wise + /// expression) are both driven by this table, so they cannot disagree. + static RKBFoldTerms fold_terms(int q) { + RKBFoldTerms terms; + for (const auto& p : rkb_fold_pair_terms(q)) + terms.push_back(RKBFoldTerm{p.coef, {{-1, -1, p.i, p.j}}}); + return terms; + } + // --- Code sharing overrides --- - // All shell combos with the same quaternion component share one function. + // All shell sets with the same quaternion component share one function. std::string generate_label() const override { return "CR_Coulombopop_" + @@ -95,43 +110,15 @@ class CR_11_Coulombσpσp_11 return rkb_fold_adapt_dims(dims); } - /// Hand-emit a simple element-wise loop function. The right-hand side is - /// selected by the target's quaternion component; the child order it relies - /// on is fixed by the constructor: - /// comp 0: target = src0 + src1 + src2 (dot product) - /// comp 1-3: target = src0 - src1 (cross product components) + /// emits the element-wise fold of the children, e.g. + /// target = src0 + src1 + src2 for the scalar component void generate_code(const std::shared_ptr& context, const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - const int comp = target_->oper()->descr().quaternion_index(); - std::string rhs; - unsigned int nchildren; - switch (comp) { - case 0: - rhs = "src0[hsi] + src1[hsi] + src2[hsi]"; - nchildren = 3; - break; - case 1: - case 2: - case 3: - rhs = "src0[hsi] - src1[hsi]"; - nchildren = 2; - break; - default: - throw std::runtime_error( - "CR_11_Coulombσpσp_11::generate_code: invalid quaternionic index"); - } - // add_child() deduplicates children by instance (see generic_rr.h), so a - // collision among the deriv-ERI children would silently shift the src - // slots; fail the export loudly instead of emitting a wrong expression. - if (this->num_children() != nchildren) - throw std::logic_error("CR_11_Coulombσpσp_11::generate_code: component " + - std::to_string(comp) + " expects " + - std::to_string(nchildren) + " children, got " + - std::to_string(this->num_children())); - rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs, - nchildren - 1); + rkb_fold_generate_code_from_terms( + *this, fold_terms(target_->oper()->descr().quaternion_index()), + "CR_11_Coulombσpσp_11", context, dims, funcname, decl, def); } }; @@ -149,84 +136,18 @@ CR_11_Coulombσpσp_11::CR_11_Coulombσpσp_11( F c(Tint->bra(1, 0)); F d(Tint->ket(1, 0)); - const auto& oper = Tint->oper(); - if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) return; - using namespace libint2::algebra; - using namespace libint2::prefactor; - using libint2::algebra::operator*; - - const mType zero_m(0u); - ChildFactory> factory(this); - - constexpr auto x = 0; - constexpr auto y = 1; - constexpr auto z = 2; - - F c_x{c}; - c_x.deriv().inc(x); // d(c)/dx = c_x - F c_y{c}; - c_y.deriv().inc(y); // d(c)/dy = c_y - F c_z{c}; - c_z.deriv().inc(z); // d(c)/dz = c_z - - F d_x{d}; - d_x.deriv().inc(x); // d(d)/dx = d_x - F d_y{d}; - d_y.deriv().inc(y); // d(d)/dy = d_y - F d_z{d}; - d_z.deriv().inc(z); // d(d)/dz = d_z - - // Component wise generation for quaternion ( a b | 1/r12 | (σ.p) c (σ.p) d ) - switch (oper->descr().quaternion_index()) { - case 0: { - // zeroth component = (a b | c_x d_x) + (a b | c_y d_y) + (a b | c_z d_z) - auto a_b_cx_dx = factory.make_child(a, b, c_x, d_x, zero_m); - auto a_b_cy_dy = factory.make_child(a, b, c_y, d_y, zero_m); - auto a_b_cz_dz = factory.make_child(a, b, c_z, d_z, zero_m); - if (is_simple()) { - expr_ = a_b_cx_dx + a_b_cy_dy + a_b_cz_dz; - nflops_ += 2; - } - } break; - case 1: { - // x component = (a b | c_y d_z) - (a b | c_z d_y) - auto a_b_cy_dz = factory.make_child(a, b, c_y, d_z, zero_m); - auto a_b_cz_dy = factory.make_child(a, b, c_z, d_y, zero_m); - if (is_simple()) { - expr_ = a_b_cy_dz - a_b_cz_dy; - nflops_ += 1; - } - } break; - case 2: { - // y component = (a b | c_z d_x) - (a b | c_x d_z) - auto a_b_cz_dx = factory.make_child(a, b, c_z, d_x, zero_m); - auto a_b_cx_dz = factory.make_child(a, b, c_x, d_z, zero_m); - if (is_simple()) { - expr_ = a_b_cz_dx - a_b_cx_dz; - nflops_ += 1; - } - } break; - case 3: { - // z component = (a b | c_x d_y) - (a b | c_y d_x) - auto a_b_cx_dy = factory.make_child(a, b, c_x, d_y, zero_m); - auto a_b_cy_dx = factory.make_child(a, b, c_y, d_x, zero_m); - if (is_simple()) { - expr_ = a_b_cx_dy - a_b_cy_dx; - nflops_ += 1; - } - } break; - default: - throw std::runtime_error( - "CR_11_Coulombσpσp_11: invalid quaternionic index"); - } + rkb_fold_make_children(factory, a, b, c, d, + fold_terms(Tint->oper()->descr().quaternion_index()), + is_simple(), expr_, nflops_); } // CR_11_Coulombσpσp_11::CR_11_Coulombσpσp_11 -}; // namespace libint2 + +} // namespace libint2 #endif // LIBINT_COMP_11_COULOMBΣPΣP_11_H diff --git "a/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" index a2e970868..9189ea408 100644 --- "a/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203pCoulomb\317\203p_11.h" @@ -30,17 +30,22 @@ #include +#include + namespace libint2 { /** - * Computes the "Gaunt LS bilinear" integral - * \f$ (\mu\, \sigma\cdot\hat{p}\,\nu | 1/r_{12} | \kappa\, - * \sigma\cdot\hat{p}\,\lambda ) \f$ in the SO(3) irreducible decomposition of - * the rank-2 tensor \f$ T_{ab} = ( \mu \cdot \partial_a \nu | 1/r_{12} | \kappa - * \cdot \partial_b \lambda ) \f$: 1 scalar trace + 3 antisymmetric (curl-curl) - * + 5 symmetric-traceless = 9 components total. Each output is a small linear - * combination of raw deriv-TwoPRep children, mirroring - * comp_11_Coulombσpσp_11.h's pattern of trace/antisym emission. + * Computes the "Gaunt LS bilinear" integral (a σ·p b | 1/r_{12} | c σ·p d), + * with one σ·p on the second function of each electron (b = ket1 in the spin + * space of electron 1, d = ket2 in that of electron 2), in the SO(3) + * irreducible decomposition of the rank-2 tensor + * T_ij = (a ∂_{B,i} b | 1/r_{12} | c ∂_{D,j} d): 1 scalar trace + 3 + * antisymmetric (curl-curl) + 5 symmetric-traceless = 9 components, each a + * small linear combination of first-derivative TwoPRep integrals. The + * derivatives are with respect to the centers B and D; since + * (p_i b)(p_j d) = (+i ∂_{B,i} b)(+i ∂_{D,j} d) = -∂_{B,i} b ∂_{D,j} d, the + * user multiplies the tensor by -1 before contracting with σ_{1,i} σ_{2,j} + * (see the rkb_operators page in engine.h). * * @tparam F basis function type. valid choices are CGShell or CGF */ @@ -76,8 +81,45 @@ class CR_11_σpCoulombσp_11 static std::string descr() { return "CR"; } + /// The terms of SO(3) irreducible component @p comp of T_ij: coefficient, + /// derivative direction i on b (ket1) and j on d (ket2). The constructor + /// (the children) and generate_code() (the element-wise expression) are both + /// driven by this table, so they cannot disagree. + static RKBFoldTerms fold_terms(int comp) { + constexpr int x = 0; + constexpr int y = 1; + constexpr int z = 2; + auto T = [](int coef, int i, int j) { + return RKBFoldTerm{coef, {{-1, i, -1, j}}}; + }; + switch (comp) { + case σpCoulombσp_Descr::Scalar: // T_xx + T_yy + T_zz + return {T(1, x, x), T(1, y, y), T(1, z, z)}; + case σpCoulombσp_Descr::AntisymX: // T_yz - T_zy + return {T(1, y, z), T(-1, z, y)}; + case σpCoulombσp_Descr::AntisymY: // T_zx - T_xz + return {T(1, z, x), T(-1, x, z)}; + case σpCoulombσp_Descr::AntisymZ: // T_xy - T_yx + return {T(1, x, y), T(-1, y, x)}; + case σpCoulombσp_Descr::SymTLDiagA: // T_xx - T_yy + return {T(1, x, x), T(-1, y, y)}; + case σpCoulombσp_Descr::SymTLDiagB: // 2 T_zz - T_xx - T_yy + return {T(2, z, z), T(-1, x, x), T(-1, y, y)}; + case σpCoulombσp_Descr::SymTLOffXY: // T_xy + T_yx + return {T(1, x, y), T(1, y, x)}; + case σpCoulombσp_Descr::SymTLOffXZ: // T_xz + T_zx + return {T(1, x, z), T(1, z, x)}; + case σpCoulombσp_Descr::SymTLOffYZ: // T_yz + T_zy + return {T(1, y, z), T(1, z, y)}; + default: + throw std::runtime_error( + "CR_11_σpCoulombσp_11: invalid component index " + + std::to_string(comp)); + } + } + // --- Code sharing overrides (mirror Coulombσpσp pattern) --- - // All shell quartets with the same quaternion component share one function. + // All shell sets with the same component share one function. std::string generate_label() const override { return "CR_opCoulombop_" + @@ -95,57 +137,15 @@ class CR_11_σpCoulombσp_11 return rkb_fold_adapt_dims(dims); } - /// Hand-emit the per-component irrep linear combination over deriv-ERI - /// children. The combination is selected by the target's component index; - /// the child order it relies on is fixed by the constructor. + /// emits the element-wise irrep combination of the children, e.g. + /// target = 2.0*src0 - src1 - src2 for SymTLDiagB void generate_code(const std::shared_ptr& context, const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - const int comp = this->target_->oper()->descr().component_index(); - std::string rhs; - unsigned int nflops = 0; - unsigned int nchildren = 0; - switch (comp) { - case σpCoulombσp_Descr::Scalar: - rhs = "src0[hsi] + src1[hsi] + src2[hsi]"; - nflops = 2; - nchildren = 3; - break; - case σpCoulombσp_Descr::AntisymX: - case σpCoulombσp_Descr::AntisymY: - case σpCoulombσp_Descr::AntisymZ: - case σpCoulombσp_Descr::SymTLDiagA: - rhs = "src0[hsi] - src1[hsi]"; - nflops = 1; - nchildren = 2; - break; - case σpCoulombσp_Descr::SymTLDiagB: - rhs = "2.0*src0[hsi] - src1[hsi] - src2[hsi]"; - nflops = 3; - nchildren = 3; - break; - case σpCoulombσp_Descr::SymTLOffXY: - case σpCoulombσp_Descr::SymTLOffXZ: - case σpCoulombσp_Descr::SymTLOffYZ: - rhs = "src0[hsi] + src1[hsi]"; - nflops = 1; - nchildren = 2; - break; - default: - throw std::runtime_error( - "CR_11_σpCoulombσp_11::generate_code: invalid component index"); - } - // add_child() deduplicates children by instance (see generic_rr.h), so a - // collision among the deriv-ERI children would silently shift the src - // slots; fail the export loudly instead of emitting a wrong expression. - if (this->num_children() != nchildren) - throw std::logic_error("CR_11_σpCoulombσp_11::generate_code: component " + - std::to_string(comp) + " expects " + - std::to_string(nchildren) + " children, got " + - std::to_string(this->num_children())); - rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs, - nflops); + rkb_fold_generate_code_from_terms( + *this, fold_terms(target_->oper()->descr().component_index()), + "CR_11_σpCoulombσp_11", context, dims, funcname, decl, def); } }; @@ -163,116 +163,15 @@ CR_11_σpCoulombσp_11::CR_11_σpCoulombσp_11( F c(Tint->bra(1, 0)); F d(Tint->ket(1, 0)); - const auto& oper = Tint->oper(); - if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) return; - using namespace libint2::algebra; - using namespace libint2::prefactor; - using libint2::algebra::operator*; - - const mType zero_m(0u); - ChildFactory> factory(this); - - // Chemist notation: (a b | op c op d). σ·p acts on one function per electron - // — direction `i` on ket(0,0) = b (electron 1), direction `j` on ket(1,0) = d - // (electron 2). Each output is an SO(3) irrep combination of the raw 3×3 - // T_{ij} dyadic; case bodies build only the children each combination needs. - constexpr auto x = 0; - constexpr auto y = 1; - constexpr auto z = 2; - - auto T = [&](int i, int j) { - F b_d{b}; - b_d.deriv().inc(i); - F d_d{d}; - d_d.deriv().inc(j); - return factory.make_child(a, b_d, c, d_d, zero_m); - }; - - switch (oper->descr().component_index()) { - case σpCoulombσp_Descr::Scalar: { - auto Txx = T(x, x); - auto Tyy = T(y, y); - auto Tzz = T(z, z); - if (is_simple()) { - expr_ = Txx + Tyy + Tzz; - nflops_ += 2; - } - } break; - case σpCoulombσp_Descr::AntisymX: { - auto Tyz = T(y, z); - auto Tzy = T(z, y); - if (is_simple()) { - expr_ = Tyz - Tzy; - nflops_ += 1; - } - } break; - case σpCoulombσp_Descr::AntisymY: { - auto Tzx = T(z, x); - auto Txz = T(x, z); - if (is_simple()) { - expr_ = Tzx - Txz; - nflops_ += 1; - } - } break; - case σpCoulombσp_Descr::AntisymZ: { - auto Txy = T(x, y); - auto Tyx = T(y, x); - if (is_simple()) { - expr_ = Txy - Tyx; - nflops_ += 1; - } - } break; - case σpCoulombσp_Descr::SymTLDiagA: { - auto Txx = T(x, x); - auto Tyy = T(y, y); - if (is_simple()) { - expr_ = Txx - Tyy; - nflops_ += 1; - } - } break; - case σpCoulombσp_Descr::SymTLDiagB: { - // 2·T_zz − T_xx − T_yy: child order (Tzz, Txx, Tyy) matches generate_code - auto Tzz = T(z, z); - auto Txx = T(x, x); - auto Tyy = T(y, y); - if (is_simple()) { - expr_ = Scalar(2) * Tzz - Txx - Tyy; - nflops_ += 3; - } - } break; - case σpCoulombσp_Descr::SymTLOffXY: { - auto Txy = T(x, y); - auto Tyx = T(y, x); - if (is_simple()) { - expr_ = Txy + Tyx; - nflops_ += 1; - } - } break; - case σpCoulombσp_Descr::SymTLOffXZ: { - auto Txz = T(x, z); - auto Tzx = T(z, x); - if (is_simple()) { - expr_ = Txz + Tzx; - nflops_ += 1; - } - } break; - case σpCoulombσp_Descr::SymTLOffYZ: { - auto Tyz = T(y, z); - auto Tzy = T(z, y); - if (is_simple()) { - expr_ = Tyz + Tzy; - nflops_ += 1; - } - } break; - default: - throw std::runtime_error("CR_11_σpCoulombσp_11: invalid component index"); - } + rkb_fold_make_children(factory, a, b, c, d, + fold_terms(Tint->oper()->descr().component_index()), + is_simple(), expr_, nflops_); } // CR_11_σpCoulombσp_11::CR_11_σpCoulombσp_11 diff --git "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" index 8bd64b418..0404da395 100644 --- "a/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" +++ "b/src/bin/libint/comp_11_\317\203p\317\203pCoulomb\317\203p\317\203p_11.h" @@ -35,37 +35,43 @@ namespace libint2 { /** - * Computes integral of - * \f$ (\sigma_1 \cdot \hat{p}_a)(\sigma_1 \cdot \hat{p}_b) + * Computes the integral + * \f$ ((\sigma_1\cdot\hat{p}) a)^\dagger ((\sigma_1\cdot\hat{p}) b) * \frac{1}{r_{12}} - * (\sigma_2 \cdot \hat{p}_c)(\sigma_2 \cdot \hat{p}_d) \f$ - * over CGShell/CGF by rewriting it as a linear combination of integrals - * over derivatives of \f$ \frac{1}{r_{12}} \f$. + * ((\sigma_2\cdot\hat{p}) c)^\dagger ((\sigma_2\cdot\hat{p}) d) \f$ + * over CGShell/CGF by rewriting it as a linear combination of integrals over + * first derivatives of all four functions (with respect to their centers). * - * The two sigma operators act on independent spin spaces (electron 1 and - * electron 2). Using the Dirac identity (see e.g. Eq. 1.27 of I. P. Grant, - * "Relativistic Quantum Theory of Atoms and Molecules", Springer, 2007): - * \f$ (\sigma \cdot a)(\sigma \cdot b) = (a \cdot b)I - * + i\sigma \cdot (a \times b) \f$ - * applied independently to each particle's spin space gives a tensor product + * The two σ operators act on independent spin spaces (electron 1 on the bra + * pair a, b; electron 2 on the ket pair c, d). Using the Dirac identity (see + * e.g. Eq. 1.27 of I. P. Grant, "Relativistic Quantum Theory of Atoms and + * Molecules", Springer, 2007): + * \f$ (\sigma \cdot u)(\sigma \cdot v) = (u \cdot v)I + * + i\sigma \cdot (u \times v) \f$ + * applied independently to each electron's spin space gives a tensor product * of two quaternions with \f$ 4 \times 4 = 16 \f$ components: * - * index = 4 * bra_spin_index + ket_spin_index + * index = 4 * q₁ + q₂ * - * where spin indices are: 0=S (scalar/dot product), 1=X, 2=Y, 3=Z - * (cross product components). + * where q₁ (bra pair) and q₂ (ket pair) are: 0=S (scalar, ∇·∇), 1=X, 2=Y, + * 3=Z (the components of the cross product ∇×∇). The components are built by + * fold_terms() as the product of the Pauli components of the two pairs (see + * rkb_fold_pair_terms): + * SS (index 0): (∇_a·∇_b)(∇_c·∇_d) 9 terms + * SX,SY,SZ (1-3), XS,YS,ZS (4,8,12): dot × cross 6 terms + * XX..ZZ (5-7, 9-11, 13-15): -(∇_a×∇_b)_i (∇_c×∇_d)_j 4 terms * - * The 16 components map to: - * T1 (index 0): SS = (a.b)(c.d) [scalar x scalar] - * T2 (indices 1-3): SX,SY,SZ = (a.b)(cxd)_{x,y,z} [scalar x spin] - * T3 (indices 4-6): XS,YS,ZS = (axb)_{x,y,z}(c.d) [spin x scalar] - * T4 (indices 7-15): XX..ZZ = -(axb)_i(cxd)_j [spin x spin] - * - * Sign convention: T4 components include the minus sign from \f$ i^2 = -1 \f$, - * arising from the product of two \f$ i \f$ factors in the Dirac identity: - * \f$ [i\sigma_1 \cdot (a \times b)] \otimes [i\sigma_2 \cdot (c \times d)] - * = -\sigma_{1,i} \otimes \sigma_{2,j}\; (a \times b)_i\, (c \times d)_j + * Sign convention: the cross × cross block includes the minus sign from + * \f$ i^2 = -1 \f$, the product of the two \f$ i \f$ factors of the Dirac + * identity: + * \f$ [i\sigma_1 \cdot (u \times v)] \otimes [i\sigma_2 \cdot (w \times x)] + * = -\sigma_{1,i} \otimes \sigma_{2,j}\; (u \times v)_i\, (w \times x)_j * \f$ + * so the user multiplies component 4q₁+q₂ by 1 (q₁=q₂=0), i σ_{2,q₂} (q₁=0), + * i σ_{1,q₁} (q₂=0), or σ_{1,q₁} σ_{2,q₂} (both ≥ 1); the center-derivative + * convention contributes no further prefactor since + * (p_i a)† (p_j b) = ∂_{A,i} a ∂_{B,j} b (see the rkb_operators page in + * engine.h). * * @tparam F basis function type. valid choices are CGShell or CGF */ @@ -83,9 +89,8 @@ class CR_11_σpσpCoulombσpσp_11 ParentType; friend class GenericRecurrenceRelation; - // children_.reserve() hint only (see generic_rr.h). The densest component is - // the SS scalar (σ·σ trace), which sums 9 deriv-ERI children; all others use - // 6 or 4. So 9 is the true upper bound across the 16 components. + // children_.reserve() hint only (see generic_rr.h): the SS component sums + // 9 deriv-ERI children, all others 6 or 4 static const unsigned int max_nchildren = 9; using ParentType::Instance; @@ -105,12 +110,34 @@ class CR_11_σpσpCoulombσpσp_11 static std::string descr() { return "CR"; } + /// The terms of component @p comp = 4 q₁ + q₂: the product of the terms of + /// Pauli component q₁ of (σ₁·∇_a)(σ₁·∇_b) (derivative directions on a = bra1 + /// and b = ket1) and of q₂ of (σ₂·∇_c)(σ₂·∇_d) (on c = bra2 and d = ket2), + /// see rkb_fold_pair_terms; the i's of two cross products multiply to -1. + /// The constructor (the children) and generate_code() (the element-wise + /// expression) are both driven by this table, so they cannot disagree. + static RKBFoldTerms fold_terms(int comp) { + if (comp < 0 || comp > 15) + throw std::runtime_error( + "CR_11_σpσpCoulombσpσp_11: invalid component index " + + std::to_string(comp) + " (expected 0-15)"); + const int q1 = comp / 4; + const int q2 = comp % 4; + const int sign = (q1 != 0 && q2 != 0) ? -1 : 1; + RKBFoldTerms terms; + for (const auto& p1 : rkb_fold_pair_terms(q1)) + for (const auto& p2 : rkb_fold_pair_terms(q2)) + terms.push_back( + RKBFoldTerm{sign * p1.coef * p2.coef, {{p1.i, p1.j, p2.i, p2.j}}}); + return terms; + } + // --- Code sharing overrides --- - // All shell combos with the same quaternion component share one function. + // All shell sets with the same component share one function. std::string generate_label() const override { return "CR_opopCoulombopop_" + - std::to_string(target_->oper()->descr().quaternion_index()); + std::to_string(target_->oper()->descr().component_index()); } std::string spfunction_call( @@ -124,55 +151,15 @@ class CR_11_σpσpCoulombσpσp_11 return rkb_fold_adapt_dims(dims); } - /// Hand-emit a simple element-wise loop function. The right-hand side is - /// selected by the target's component index (4 * bra_spin + ket_spin, with - /// spin indices S=0, X=1, Y=2, Z=3); the child order it relies on is fixed by - /// the constructor: - /// SS: 9 children, all +1 - /// SX,SY,SZ,XS,YS,ZS: 6 children, alternating +1,-1 - /// XX..ZZ: 4 children, pattern -1,+1,+1,-1 + /// emits the element-wise fold of the children, e.g. + /// target = src0 + src1 + ... + src8 for the SS component void generate_code(const std::shared_ptr& context, const std::shared_ptr& dims, const std::string& funcname, std::ostream& decl, std::ostream& def) override { - const int comp = target_->oper()->descr().quaternion_index(); - if (comp < 0 || comp > 15) - throw std::runtime_error( - "CR_11_σpσpCoulombσpσp_11::generate_code: invalid component index " - "(expected 0-15)"); - const bool bra_scalar = (comp / 4 == 0); - const bool ket_scalar = (comp % 4 == 0); - std::ostringstream rhs; - unsigned int nchildren; - if (bra_scalar && ket_scalar) { - // SS: all positive - nchildren = 9; - for (unsigned int c = 0; c < nchildren; c++) { - if (c > 0) rhs << " + "; - rhs << "src" << c << "[hsi]"; - } - } else if (bra_scalar || ket_scalar) { - // SX,SY,SZ,XS,YS,ZS: alternating +/- - nchildren = 6; - for (unsigned int c = 0; c < nchildren; c++) { - rhs << ((c % 2 == 0) ? " + " : " - "); - rhs << "src" << c << "[hsi]"; - } - } else { - // XX,XY,...,ZZ: -src0 + src1 + src2 - src3 - nchildren = 4; - rhs << "- src0[hsi] + src1[hsi] + src2[hsi] - src3[hsi]"; - } - // add_child() deduplicates children by instance (see generic_rr.h), so a - // collision among the deriv-ERI children would silently shift the src - // slots; fail the export loudly instead of emitting a wrong expression. - if (this->num_children() != nchildren) - throw std::logic_error( - "CR_11_σpσpCoulombσpσp_11::generate_code: component " + - std::to_string(comp) + " expects " + std::to_string(nchildren) + - " children, got " + std::to_string(this->num_children())); - rkb_fold_generate_code(*this, context, dims, funcname, decl, def, rhs.str(), - nchildren - 1); + rkb_fold_generate_code_from_terms( + *this, fold_terms(target_->oper()->descr().component_index()), + "CR_11_σpσpCoulombσpσp_11", context, dims, funcname, decl, def); } }; @@ -190,250 +177,18 @@ CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11( F c(Tint->bra(1, 0)); F d(Tint->ket(1, 0)); - const auto& oper = Tint->oper(); - if (a.contracted() || b.contracted() || c.contracted() || d.contracted()) return; - using namespace libint2::algebra; - using namespace libint2::prefactor; - using libint2::algebra::operator*; - - const mType zero_m(0u); - ChildFactory> factory(this); - - constexpr auto x = 0; - constexpr auto y = 1; - constexpr auto z = 2; - - auto mc = [&](const int r1, const int r2, const int r3, const int r4) { - F a_r1{a}; - a_r1.deriv().inc(r1); - F b_r2{b}; - b_r2.deriv().inc(r2); - F c_r3{c}; - c_r3.deriv().inc(r3); - F d_r4{d}; - d_r4.deriv().inc(r4); - return factory.make_child(a_r1, b_r2, c_r3, d_r4, zero_m); - }; - - // 16-component generation for two independent spin spaces: - // ( (σ₁.p) a (σ₁.p) b | 1/r12 | (σ₂.p) c (σ₂.p) d ) - // - // Option A (tensor product) ordering: index = 4 * bra_spin + ket_spin - // bra_spin = index / 4, ket_spin = index % 4 - // spin indices: S=0, X=1, Y=2, Z=3 - // - // Row bra=S: 0=SS, 1=SX, 2=SY, 3=SZ (T1 + T2) - // Row bra=X: 4=XS, 5=XX, 6=XY, 7=XZ (T3 + T4) - // Row bra=Y: 8=YS, 9=YX, 10=YY, 11=YZ (T3 + T4) - // Row bra=Z: 12=ZS, 13=ZX, 14=ZY, 15=ZZ (T3 + T4) - // - // T4 components include minus sign from i^2 = -1. - switch (oper->descr().quaternion_index()) { - // ===== 0: SS = (a.b)(c.d) ===== - case 0: { - auto xxxx = mc(x, x, x, x); - auto xxyy = mc(x, x, y, y); - auto xxzz = mc(x, x, z, z); - auto yyxx = mc(y, y, x, x); - auto yyyy = mc(y, y, y, y); - auto yyzz = mc(y, y, z, z); - auto zzxx = mc(z, z, x, x); - auto zzyy = mc(z, z, y, y); - auto zzzz = mc(z, z, z, z); - if (is_simple()) { - expr_ = xxxx + xxyy + xxzz + yyxx + yyyy + yyzz + zzxx + zzyy + zzzz; - nflops_ += 8; - } - } break; - // ===== 1: SX = (a.b)(c×d)_x ===== - case 1: { - auto xxyz = mc(x, x, y, z); - auto xxzy = mc(x, x, z, y); - auto yyyz = mc(y, y, y, z); - auto yyzy = mc(y, y, z, y); - auto zzyz = mc(z, z, y, z); - auto zzzy = mc(z, z, z, y); - if (is_simple()) { - expr_ = xxyz - xxzy + yyyz - yyzy + zzyz - zzzy; - nflops_ += 5; - } - } break; - // ===== 2: SY = (a.b)(c×d)_y ===== - case 2: { - auto xxzx = mc(x, x, z, x); - auto xxxz = mc(x, x, x, z); - auto yyzx = mc(y, y, z, x); - auto yyxz = mc(y, y, x, z); - auto zzzx = mc(z, z, z, x); - auto zzxz = mc(z, z, x, z); - if (is_simple()) { - expr_ = xxzx - xxxz + yyzx - yyxz + zzzx - zzxz; - nflops_ += 5; - } - } break; - // ===== 3: SZ = (a.b)(c×d)_z ===== - case 3: { - auto xxxy = mc(x, x, x, y); - auto xxyx = mc(x, x, y, x); - auto yyxy = mc(y, y, x, y); - auto yyyx = mc(y, y, y, x); - auto zzxy = mc(z, z, x, y); - auto zzyx = mc(z, z, y, x); - if (is_simple()) { - expr_ = xxxy - xxyx + yyxy - yyyx + zzxy - zzyx; - nflops_ += 5; - } - } break; - // ===== 4: XS = (a×b)_x(c.d) ===== - case 4: { - auto yzxx = mc(y, z, x, x); - auto zyxx = mc(z, y, x, x); - auto yzyy = mc(y, z, y, y); - auto zyyy = mc(z, y, y, y); - auto yzzz = mc(y, z, z, z); - auto zyzz = mc(z, y, z, z); - if (is_simple()) { - expr_ = yzxx - zyxx + yzyy - zyyy + yzzz - zyzz; - nflops_ += 5; - } - } break; - // ===== 5: XX = -(a×b)_x(c×d)_x (minus from i²=-1) ===== - case 5: { - auto yzyz = mc(y, z, y, z); - auto yzzy = mc(y, z, z, y); - auto zyyz = mc(z, y, y, z); - auto zyzy = mc(z, y, z, y); - if (is_simple()) { - expr_ = yzzy - yzyz + zyyz - zyzy; - nflops_ += 3; - } - } break; - // ===== 6: XY = -(a×b)_x(c×d)_y ===== - case 6: { - auto yzzx = mc(y, z, z, x); - auto yzxz = mc(y, z, x, z); - auto zyzx = mc(z, y, z, x); - auto zyxz = mc(z, y, x, z); - if (is_simple()) { - expr_ = yzxz - yzzx + zyzx - zyxz; - nflops_ += 3; - } - } break; - // ===== 7: XZ = -(a×b)_x(c×d)_z ===== - case 7: { - auto yzxy = mc(y, z, x, y); - auto yzyx = mc(y, z, y, x); - auto zyxy = mc(z, y, x, y); - auto zyyx = mc(z, y, y, x); - if (is_simple()) { - expr_ = yzyx - yzxy + zyxy - zyyx; - nflops_ += 3; - } - } break; - // ===== 8: YS = (a×b)_y(c.d) ===== - case 8: { - auto zxxx = mc(z, x, x, x); - auto xzxx = mc(x, z, x, x); - auto zxyy = mc(z, x, y, y); - auto xzyy = mc(x, z, y, y); - auto zxzz = mc(z, x, z, z); - auto xzzz = mc(x, z, z, z); - if (is_simple()) { - expr_ = zxxx - xzxx + zxyy - xzyy + zxzz - xzzz; - nflops_ += 5; - } - } break; - // ===== 9: YX = -(a×b)_y(c×d)_x ===== - case 9: { - auto zxyz = mc(z, x, y, z); - auto zxzy = mc(z, x, z, y); - auto xzyz = mc(x, z, y, z); - auto xzzy = mc(x, z, z, y); - if (is_simple()) { - expr_ = zxzy - zxyz + xzyz - xzzy; - nflops_ += 3; - } - } break; - // ===== 10: YY = -(a×b)_y(c×d)_y ===== - case 10: { - auto zxzx = mc(z, x, z, x); - auto zxxz = mc(z, x, x, z); - auto xzzx = mc(x, z, z, x); - auto xzxz = mc(x, z, x, z); - if (is_simple()) { - expr_ = zxxz - zxzx + xzzx - xzxz; - nflops_ += 3; - } - } break; - // ===== 11: YZ = -(a×b)_y(c×d)_z ===== - case 11: { - auto zxxy = mc(z, x, x, y); - auto zxyx = mc(z, x, y, x); - auto xzxy = mc(x, z, x, y); - auto xzyx = mc(x, z, y, x); - if (is_simple()) { - expr_ = zxyx - zxxy + xzxy - xzyx; - nflops_ += 3; - } - } break; - // ===== 12: ZS = (a×b)_z(c.d) ===== - case 12: { - auto xyxx = mc(x, y, x, x); - auto yxxx = mc(y, x, x, x); - auto xyyy = mc(x, y, y, y); - auto yxyy = mc(y, x, y, y); - auto xyzz = mc(x, y, z, z); - auto yxzz = mc(y, x, z, z); - if (is_simple()) { - expr_ = xyxx - yxxx + xyyy - yxyy + xyzz - yxzz; - nflops_ += 5; - } - } break; - // ===== 13: ZX = -(a×b)_z(c×d)_x ===== - case 13: { - auto xyyz = mc(x, y, y, z); - auto xyzy = mc(x, y, z, y); - auto yxyz = mc(y, x, y, z); - auto yxzy = mc(y, x, z, y); - if (is_simple()) { - expr_ = xyzy - xyyz + yxyz - yxzy; - nflops_ += 3; - } - } break; - // ===== 14: ZY = -(a×b)_z(c×d)_y ===== - case 14: { - auto xyzx = mc(x, y, z, x); - auto xyxz = mc(x, y, x, z); - auto yxzx = mc(y, x, z, x); - auto yxxz = mc(y, x, x, z); - if (is_simple()) { - expr_ = xyxz - xyzx + yxzx - yxxz; - nflops_ += 3; - } - } break; - // ===== 15: ZZ = -(a×b)_z(c×d)_z ===== - case 15: { - auto xyxy = mc(x, y, x, y); - auto xyyx = mc(x, y, y, x); - auto yxxy = mc(y, x, x, y); - auto yxyx = mc(y, x, y, x); - if (is_simple()) { - expr_ = xyyx - xyxy + yxxy - yxyx; - nflops_ += 3; - } - } break; - default: - throw std::runtime_error( - "CR_11_σpσpCoulombσpσp_11: invalid component index (expected 0-15)"); - } + rkb_fold_make_children(factory, a, b, c, d, + fold_terms(Tint->oper()->descr().component_index()), + is_simple(), expr_, nflops_); } // CR_11_σpσpCoulombσpσp_11::CR_11_σpσpCoulombσpσp_11 -}; // namespace libint2 + +} // namespace libint2 #endif // LIBINT_COMP_11_ΣPΣPCOULOMBΣPΣP_11_H diff --git a/src/bin/libint/comp_deriv_gauss_v2.h b/src/bin/libint/comp_deriv_gauss_v2.h index dc803932e..172540677 100644 --- a/src/bin/libint/comp_deriv_gauss_v2.h +++ b/src/bin/libint/comp_deriv_gauss_v2.h @@ -44,8 +44,9 @@ namespace libint2 { /** Optimized compute relation for (geometric) derivative Gaussian integrals. * - * Like CR_DerivGauss, this expands derivative Gaussians via: - * d/dr G(a) = 2*alpha * G(a+1) - a_i * G(a-1) + * Like CR_DerivGauss, this expands derivative Gaussians via + * d/dA G(a) = 2*alpha * G(a+1) - a_i * G(a-1), + * the derivative with respect to the center A of the Gaussian (= -d/dr). * * Unlike CR_DerivGauss, this uses the HRR-like code-sharing optimization: * since differentiation of a Gaussian at position (part, where) depends only @@ -435,7 +436,9 @@ std::string DerivGaussV2 aux0(new mType(0u)); - os << "CR_DerivGauss" + // N.B. not "CR_DerivGauss...": CR_DerivGauss labels its own instances + // that way, and the RRStack deduplicates by label + os << "DerivGaussV2TI" << "P" << part << to_string(where) << "D" << dir_ << genintegralset_label(target_->bra(), target_->ket(), aux0, target_->oper()); @@ -571,7 +574,10 @@ bool DerivGaussV2:: // shell, hence base the decision on that shell alone const BasisFunctionType& sh = where == InBra ? target_->bra(part, 0) : target_->ket(part, 0); - return sh.norm() > cparams->max_am_opt(); + // the OPT_AM of the current task (the per-class value, e.g. + // LIBINT2_ERI_OPT_AM), not that of the default task + return sh.norm() > + cparams->max_am_opt(LibraryTaskManager::Instance().current().label()); } template { } std::string label() const { return description(); } int psymm(int i, int j) const { abort(); } + /// N.B. the Multiplicative*2Body_Props above make Oper::hermitian() return + /// +1, i.e. HRR would treat this operator as commuting with the transfer of + /// quanta between the centers, which it does not (σ·p acts on specific + /// functions). Harmless as long as only the composite relation is in this + /// operator's strategy: do not add HRR to it. int hermitian(int i) const { return +1; } int quaternion_index() const { return quaternion_index_; } @@ -508,8 +513,10 @@ typedef GenOper CoulombσpσpOper; * on the ket pair, folded via the Dirac identity into 4 quaternion * components), a single σ·p produces no σ·σ folding: the result is the bare * Cartesian gradient of ν, i.e. the SO(3) vector irrep with 3 components - * (x=0, y=1, z=2). Each component is exactly one first-derivative ERI child; - * the imaginary unit and Pauli matrix of σ·p are applied by the caller. + * (x=0, y=1, z=2), each exactly one first-derivative ERI child: the + * derivative of ν with respect to its center D. Since + * p_k = -i ∂/∂r_k = +i ∂/∂D_k, the caller multiplies component k by +i and + * contracts with σ_k (see the rkb_operators page in engine.h). */ struct Coulombσp_Descr : public Contractable { // σ·p acts on particle 2 only, hence no particle 1<->2 symmetry @@ -540,6 +547,11 @@ struct Coulombσp_Descr : public Contractable { } std::string label() const { return description(); } int psymm(int i, int j) const { abort(); } + /// N.B. the Multiplicative*2Body_Props above make Oper::hermitian() return + /// +1, i.e. HRR would treat this operator as commuting with the transfer of + /// quanta between the centers, which it does not (σ·p acts on specific + /// functions). Harmless as long as only the composite relation is in this + /// operator's strategy: do not add HRR to it. int hermitian(int i) const { return +1; } int cartesian_index() const { return cartesian_index_; } @@ -557,17 +569,17 @@ struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr /// (ab|cd) != (ba|cd): the pairs can only be swapped together static constexpr PairSwap pair_swap = PairSwap::coupled; - σpσpCoulombσpσp_Descr() : quaternion_index_(0) {} - σpσpCoulombσpσp_Descr(int quaternion_index) - : quaternion_index_(quaternion_index) { - assert(quaternion_index >= 0 && quaternion_index <= 15); + σpσpCoulombσpσp_Descr() : component_index_(0) {} + σpσpCoulombσpσp_Descr(int component_index) + : component_index_(component_index) { + assert(component_index >= 0 && component_index <= 15); } /// 16 components from tensor product of two independent spin spaces: /// index = 4 * bra_spin_index + ket_spin_index /// where spin indices are: 0=S (scalar), 1=X, 2=Y, 3=Z (cross product) static const unsigned int max_key = 16; - unsigned int key() const { return quaternion_index(); } + unsigned int key() const { return component_index(); } std::string description() const { // clang-format off // Option A (tensor product order): index = 4 * bra_spin + ket_spin @@ -578,18 +590,23 @@ struct σpσpCoulombσpσp_Descr : public Contractable<σpσpCoulombσpσp_Descr "ZS", "ZX", "ZY", "ZZ" }; // clang-format on - const auto qi = quaternion_index(); + const auto qi = component_index(); if (qi > 15) abort(); return std::string("opop_coulomb_opop[") + labels[qi] + "]"; } std::string label() const { return description(); } int psymm(int i, int j) const { abort(); } + /// N.B. the Multiplicative*2Body_Props above make Oper::hermitian() return + /// +1, i.e. HRR would treat this operator as commuting with the transfer of + /// quanta between the centers, which it does not (σ·p acts on specific + /// functions). Harmless as long as only the composite relation is in this + /// operator's strategy: do not add HRR to it. int hermitian(int i) const { return +1; } - int quaternion_index() const { return quaternion_index_; } + int component_index() const { return component_index_; } private: - const int quaternion_index_ = -1; + const int component_index_ = -1; }; typedef GenOper<σpσpCoulombσpσp_Descr> σpσpCoulombσpσpOper; @@ -642,6 +659,11 @@ struct σpCoulombσp_Descr : public Contractable<σpCoulombσp_Descr> { } std::string label() const { return description(); } int psymm(int i, int j) const { abort(); } + /// N.B. the Multiplicative*2Body_Props above make Oper::hermitian() return + /// +1, i.e. HRR would treat this operator as commuting with the transfer of + /// quanta between the centers, which it does not (σ·p acts on specific + /// functions). Harmless as long as only the composite relation is in this + /// operator's strategy: do not add HRR to it. int hermitian(int i) const { return +1; } int component_index() const { return component_index_; } diff --git a/src/bin/libint/rkb_fold_codegen.h b/src/bin/libint/rkb_fold_codegen.h index f757b086b..43360a91a 100644 --- a/src/bin/libint/rkb_fold_codegen.h +++ b/src/bin/libint/rkb_fold_codegen.h @@ -24,24 +24,148 @@ #include #include #include +#include #include +#include +#include #include #include #include #include #include +#include namespace libint2 { -// Shared code-emission helpers for the σ·p "element-wise fold" recurrence -// relations: CR_11_Coulombσp_11, CR_11_Coulombσpσp_11, CR_11_σpCoulombσp_11 and +// Shared machinery of the σ·p "element-wise fold" recurrence relations: +// CR_11_Coulombσp_11, CR_11_Coulombσpσp_11, CR_11_σpCoulombσp_11 and // CR_11_σpσpCoulombσpσp_11. Each decomposes its target into a fixed linear // combination of first-derivative TwoPRep children, evaluated element-wise over -// the (spectator) shell dimensions. Across all four, only the operator label -// and the right-hand-side expression differ; the call-site emission, dimension -// adaptation, and per-function scaffold are identical and live here so they are -// written (and corrected) once rather than copy-pasted four times. +// the (spectator) shell dimensions. Every such relation is defined by one table +// of terms per component (RKBFoldTerms): the constructor creates the children +// from it (rkb_fold_make_children) and generate_code() emits the element-wise +// expression from it (rkb_fold_generate_code_from_terms), so the order of the +// children and the signs in the generated code cannot disagree. The call-site +// emission, dimension adaptation and per-function scaffold are identical across +// the four relations and live here as well. + +/// One term of an element-wise σ·p fold: @c coef times the TwoPRep integral +/// over copies of the functions (a, b, c, d) = (bra1, ket1, bra2, ket2) of the +/// target that carry one extra derivative quantum in Cartesian direction +/// @c deriv[0..3] (-1: no derivative on that function). The derivatives are +/// with respect to the centers of the functions (libint's convention), see +/// the rkb_operators page in engine.h for the resulting prefactors. +struct RKBFoldTerm { + int coef; + std::array deriv; +}; +typedef std::vector RKBFoldTerms; + +/// One term of a Pauli component of the product (σ·∇)(σ·∇') of two gradients: +/// @c coef times ∂_i ∂'_j +struct RKBFoldPairTerm { + int coef; + int i; + int j; +}; + +/// @return the terms of Pauli component @p q of +/// (σ·∇)(σ·∇') = ∇·∇' + i σ·(∇ × ∇'): q = 0 is the dot product (δ_ij), +/// q = 1..3 the x, y, z component of the cross product (the Levi-Civita symbol +/// ε_{qij}). The imaginary unit and the Pauli matrix of the cross-product +/// components are left to the user of the library (see the rkb_operators page +/// in engine.h). +inline const std::vector& rkb_fold_pair_terms(int q) { + static const std::vector terms[4] = { + {{1, 0, 0}, {1, 1, 1}, {1, 2, 2}}, // ∇·∇' = ∂_x∂'_x + ∂_y∂'_y + ∂_z∂'_z + {{1, 1, 2}, {-1, 2, 1}}, // (∇×∇')_x = ∂_y∂'_z - ∂_z∂'_y + {{1, 2, 0}, {-1, 0, 2}}, // (∇×∇')_y = ∂_z∂'_x - ∂_x∂'_z + {{1, 0, 1}, {-1, 1, 0}} // (∇×∇')_z = ∂_x∂'_y - ∂_y∂'_x + }; + if (q < 0 || q > 3) + throw std::invalid_argument( + "rkb_fold_pair_terms: invalid Pauli component index " + + std::to_string(q)); + return terms[q]; +} + +/// Constructor body of a fold relation: creates one TwoPRep child per term of +/// @p terms, in the order of @p terms (the order generate_code() relies on), +/// and, if @p simple, the symbolic expression Σ_t coef_t · child_t in @p expr +/// and its flop count in @p nflops. The children are created even when not +/// @p simple so that they register on the graph. +/// @tparam Factory ChildFactory> +/// @tparam ExprPtr type of RecurrenceRelation::expr_ +/// @param a,b,c,d the functions of the target: bra1, ket1, bra2, ket2 +template +void rkb_fold_make_children(Factory& factory, const F& a, const F& b, + const F& c, const F& d, const RKBFoldTerms& terms, + bool simple, ExprPtr& expr, unsigned int& nflops) { + using namespace libint2::algebra; + using libint2::algebra::operator*; + using libint2::prefactor::Scalar; + + const typename Factory::AuxIndexType zero_m(0u); + std::shared_ptr first_child; + for (std::size_t t = 0; t != terms.size(); ++t) { + const RKBFoldTerm& term = terms[t]; + F fa{a}; + F fb{b}; + F fc{c}; + F fd{d}; + if (term.deriv[0] >= 0) fa.deriv().inc(term.deriv[0]); + if (term.deriv[1] >= 0) fb.deriv().inc(term.deriv[1]); + if (term.deriv[2] >= 0) fc.deriv().inc(term.deriv[2]); + if (term.deriv[3] >= 0) fd.deriv().inc(term.deriv[3]); + const std::shared_ptr child = + factory.make_child(fa, fb, fc, fd, zero_m); + if (!simple) continue; + + if (t == 0) { + first_child = child; + // a bare child is not an expression: a leading +child is combined with + // the second term below, any other leading term is Scalar(coef) * child + if (term.coef != 1 || terms.size() == 1) expr = Scalar(term.coef) * child; + } else { + const std::shared_ptr lhs = + (t == 1 && terms[0].coef == 1) ? first_child + : std::shared_ptr(expr); + if (term.coef == 1) + expr = lhs + child; + else if (term.coef == -1) + expr = lhs - child; + else + expr = lhs + Scalar(term.coef) * child; + nflops += 1; + } + if (std::abs(term.coef) != 1) nflops += 1; + } +} + +/// @return the right-hand side of the element-wise fold of @p terms with child +/// t of the relation named src, e.g. "src0[hsi] - src1[hsi]" or +/// "2.0*src0[hsi] - src1[hsi] - src2[hsi]"; @p nflops receives its flop count +inline std::string rkb_fold_rhs(const RKBFoldTerms& terms, + unsigned int& nflops) { + std::ostringstream os; + nflops = 0; + for (std::size_t t = 0; t != terms.size(); ++t) { + const int coef = terms[t].coef; + if (t == 0) { + if (coef < 0) os << "-"; + } else { + os << (coef < 0 ? " - " : " + "); + ++nflops; + } + if (std::abs(coef) != 1) { + os << std::abs(coef) << ".0*"; + ++nflops; + } + os << "src" << t << "[hsi]"; + } + return os.str(); +} /// adapt_dims_ body: replace the high dimension with the runtime "highdim" /// symbol (the fold loops over the high/spectator dimension only). @@ -82,11 +206,11 @@ std::string rkb_fold_spfunction_call( return os.str(); } -/// generate_code body: emit the full element-wise function. The caller computes -/// the per-target right-hand-side expression @p rhs (e.g. -/// "src0[hsi] + src1[hsi]") and its flop count @p nflops; everything else — -/// symbol assignment, function declaration, and the element loop scaffold — is -/// identical across the four operators and emitted here. +/// Emits the full element-wise function: the caller supplies the per-target +/// right-hand-side expression @p rhs (e.g. "src0[hsi] + src1[hsi]") and its +/// flop count @p nflops; everything else (symbol assignment, function +/// declaration, and the element loop scaffold) is identical across the four +/// operators and emitted here. Prefer rkb_fold_generate_code_from_terms(). template void rkb_fold_generate_code(const RR& self, const std::shared_ptr& context, @@ -138,6 +262,27 @@ void rkb_fold_generate_code(const RR& self, def << context->code_postfix(); } +/// generate_code body of a fold relation whose children were created by +/// rkb_fold_make_children() from the same @p terms: emits +/// target = Σ_t coef_t · src. @p rr_name is used in the error message only. +template +void rkb_fold_generate_code_from_terms( + const RR& self, const RKBFoldTerms& terms, const std::string& rr_name, + const std::shared_ptr& context, + const std::shared_ptr& dims, + const std::string& funcname, std::ostream& decl, std::ostream& def) { + // add_child() deduplicates children by instance (see generic_rr.h), so a + // collision among the children would silently shift the src slots; fail the + // export loudly instead of emitting a wrong expression + if (self.num_children() != terms.size()) + throw std::logic_error(rr_name + "::generate_code: expected " + + std::to_string(terms.size()) + " children, got " + + std::to_string(self.num_children())); + unsigned int nflops = 0; + const std::string rhs = rkb_fold_rhs(terms, nflops); + rkb_fold_generate_code(self, context, dims, funcname, decl, def, rhs, nflops); +} + } // namespace libint2 #endif // LIBINT_RKB_FOLD_CODEGEN_H