diff --git a/CMakeLists.txt b/CMakeLists.txt index 56438797b..ec93ec063 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,20 @@ cmake_minimum_required(VERSION 3.15.0) # need list(PREPEND for toolchains +# Preload versions/tags of all dependencies ==================================== +include(external/versions.cmake) + +############################################################################### +# CMake defaults to address key pain points +############################################################################### + +# safety net for dev workflow: accidental install will not affect FindOrFetch* +if (NOT DEFINED CACHE{CMAKE_FIND_NO_INSTALL_PREFIX}) + set(CMAKE_FIND_NO_INSTALL_PREFIX ON CACHE BOOL "Whether find_* commands will search CMAKE_INSTALL_PREFIX and CMAKE_STAGING_PREFIX; see https://cmake.org/cmake/help/latest/variable/CMAKE_FIND_NO_INSTALL_PREFIX.html#variable:CMAKE_FIND_NO_INSTALL_PREFIX") +endif() + ############################################################################### # Bring ValeevGroup cmake toolkit ############################################################################### -include(external/versions.cmake) include(FetchContent) if (DEFINED PROJECT_BINARY_DIR) set(VG_CMAKE_KIT_PREFIX_DIR PROJECT_BINARY_DIR) @@ -191,6 +202,8 @@ set(SeQuant_src SeQuant/core/eval_expr.cpp SeQuant/core/eval_expr.hpp SeQuant/core/eval_node.hpp + SeQuant/core/export/itf.cpp + SeQuant/core/export/itf.hpp SeQuant/core/expr.cpp SeQuant/core/expr.hpp SeQuant/core/expr_algorithm.hpp @@ -199,6 +212,7 @@ set(SeQuant_src SeQuant/core/hugenholtz.hpp SeQuant/core/index.cpp SeQuant/core/index.hpp + SeQuant/core/index_space_registry.hpp SeQuant/core/interval.hpp SeQuant/core/latex.cpp SeQuant/core/latex.ipp @@ -219,7 +233,6 @@ set(SeQuant_src SeQuant/core/rational.hpp SeQuant/core/runtime.cpp SeQuant/core/runtime.hpp - SeQuant/core/space.cpp SeQuant/core/space.hpp SeQuant/core/tag.hpp SeQuant/core/tensor.cpp @@ -230,6 +243,7 @@ set(SeQuant_src SeQuant/core/tensor_network.hpp SeQuant/core/timer.hpp SeQuant/core/utility/context.hpp + SeQuant/core/utility/indices.hpp SeQuant/core/utility/macros.hpp SeQuant/core/utility/nodiscard.hpp SeQuant/core/utility/singleton.hpp @@ -246,8 +260,6 @@ set(SeQuant_src SeQuant/domain/mbpt/context.cpp SeQuant/domain/mbpt/convention.cpp SeQuant/domain/mbpt/convention.hpp - SeQuant/domain/mbpt/mr.cpp - SeQuant/domain/mbpt/mr.hpp SeQuant/domain/mbpt/models/cc.cpp SeQuant/domain/mbpt/models/cc.hpp SeQuant/domain/mbpt/op.cpp @@ -256,12 +268,7 @@ set(SeQuant_src SeQuant/domain/mbpt/rdm.hpp SeQuant/domain/mbpt/spin.cpp SeQuant/domain/mbpt/spin.hpp - SeQuant/domain/mbpt/sr.cpp - SeQuant/domain/mbpt/sr.hpp - SeQuant/domain/mbpt/vac_av.ipp - ) -set_source_files_properties( - SeQuant/domain/mbpt/op.cpp SeQuant/domain/mbpt/sr.cpp SeQuant/domain/mbpt/mr.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + SeQuant/domain/mbpt/vac_av.ipp) ### optional prereqs if (SEQUANT_EVAL_TESTS) @@ -381,16 +388,16 @@ if (Boost_BUILT_FROM_SOURCE AND TARGET build-boost-in-SeQuant) endif() if (SEQUANT_IWYU) - find_program(iwyu_path NAMES include-what-you-use iwyu) - - if (iwyu_path) - set(iwyu_options_and_path - "${iwyu_path}" - -Xiwyu --cxx17ns - -Xiwyu --no_comments - ) - set_property(TARGET SeQuant PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_options_and_path}) - endif() + find_program(iwyu_path NAMES include-what-you-use iwyu) + + if (iwyu_path) + set(iwyu_options_and_path + "${iwyu_path}" + -Xiwyu --cxx17ns + -Xiwyu --no_comments + ) + set_property(TARGET SeQuant PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_options_and_path}) + endif() endif() ### unit tests @@ -428,6 +435,7 @@ if (BUILD_TESTING) tests/unit/test_string.cpp tests/unit/test_latex.cpp tests/unit/test_utilities.cpp + tests/unit/test_export.cpp ) if (TARGET tiledarray) @@ -474,7 +482,8 @@ if (BUILD_TESTING) add_executable(${example0} EXCLUDE_FROM_ALL examples/${example0}/${example0}.cpp) target_link_libraries(${example0} SeQuant) - set(${example0}_cmdline_args ";3 t std so;3 t csv so;3 lambda std so;3 lambda csv so;2 t std sf;2 t csv sf;2 lambda std sf;2 lambda csv sf") + # N.B. empty space, to appease foreach + set(${example0}_cmdline_args " ;3 t std so;3 t csv so;3 lambda std so;3 lambda csv so;2 t std sf;2 t csv sf;2 lambda std sf;2 lambda csv sf") if (TARGET Eigen3::Eigen) # these examples require Eigen for full functionality # Single-Reference closed-shell Coupled-Cluster equation @@ -539,9 +548,9 @@ if (BUILD_TESTING) target_link_libraries(${example7} SeQuant) endif (BTAS_SOURCE_DIR) - - foreach (i RANGE 8 11) - math(EXPR s "${i} - 7") # map 8..11 -> 1..4 + set(lastexample 13) + foreach (i RANGE 8 ${lastexample}) + math(EXPR s "${i} - 7") # map 8..13 -> 1..6 set(example${i} synopsis${s}) add_executable(${example${i}} EXCLUDE_FROM_ALL examples/synopsis/${example${i}}.cpp) @@ -554,18 +563,17 @@ if (BUILD_TESTING) target_link_libraries(${example12} SeQuant) # add tests for running examples - set(lastexample 12) foreach (i RANGE ${lastexample}) if (TARGET ${example${i}}) add_dependencies(examples-sequant ${example${i}}) add_test(sequant/example/${example${i}}/build "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target ${example${i}}) if (NOT DEFINED ${example${i}}_cmdline_args) - set(${example${i}}_cmdline_args "") + set(${example${i}}_cmdline_args " ") # N.B. empty space, to appease foreach endif() set(${example${i}}_run_counter 0) foreach (args ${${example${i}}_cmdline_args}) - string(REPLACE " " ";" args ${args}) + string(REPLACE " " ";" args "${args}") add_test(NAME sequant/example/${example${i}}/run/${${example${i}}_run_counter} COMMAND ${example${i}} ${args} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/examples) diff --git a/INSTALL.md b/INSTALL.md index ec1c5e26a..2ad99b044 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,11 +1,16 @@ SeQuant: installation guide =========================== -prerequisites: +## TL;DR +While in the SeQuant source directory: +* `cmake -B build -S .` +* `cmake --build build --target check-sequant` + +## Prerequisites * mandatory: * CMake 3.15 or later * a C++17 compiler - * [Boost](https://www.boost.org/), version 1.81 or higher (N.B. older compilers _may_ work with older Boost releases). *SeQuant can download and build Boost if configured with `Boost_FETCH_IF_MISSING=ON`, but this is not recommended.* The following non-header-only Boost libraries are required, hence Boost must be configured/built: + * [Boost](https://www.boost.org/), version 1.81 or higher. *SeQuant can download and build Boost if configured with `Boost_FETCH_IF_MISSING=ON`, but this is not recommended.* The following non-header-only Boost libraries are required, hence Boost must be configured/built: - [Boost.Regex](https://www.boost.org/doc/libs/master/libs/regex/doc/html/index.html) - [Boost.Locale](https://www.boost.org/doc/libs/master/libs/locale/doc/html/index.html) * [Range-V3](https://github.com/ericniebler/range-v3.git), tag 0.12.0, *if not found, SeQuant will download and build Range-V3* @@ -15,14 +20,24 @@ prerequisites: * for building `stcc*` example programs * [Eigen](http://eigen.tuxfamily.org/), version 3 -for the impatient (from the top of the SeQuant source directory): - * `cmake -B build -S .` - * `cmake --build build --target check-sequant` +## Configure + +From the SeQuant source directory run the following command to configure the build harness: +* `cmake -B build -S .` -useful CMake variables: +### Useful CMake variables * [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/module/CTest.html) --- enables unit tests targets, e.g. `check-sequant` [default=ON] * [`CMAKE_CXX_COMPILER`](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER.html#variable:CMAKE_%3CLANG%3E_COMPILER) --- specifies the C++ compiler to use * [`CMAKE_PREFIX_PATH`](https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html) --- this semicolon-separated list specifies search paths for dependencies (Boost, Range-V3, etc.) + * [`CMAKE_INSTALL_PREFIX`](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html) --- the installation path for SeQuant * `SEQUANT_MIMALLOC` --- use [mimalloc](https://github.com/microsoft/mimalloc) for fast memory allocation * `SEQUANT_EVAL_TRACE` --- enables tracing of expression interpretation; especially useful in combination with TiledArray's memory tracing mechanism (configure TiledArray with `TA_TENSOR_MEM_PROFILE=ON` to enable that) + * `SEQUANT_PYTHON` --- enables building of Pythin bindings * `Boost_FETCH_IF_MISSING` --- if set to `ON`, SeQuant will download and build Boost if it is not found by `find_package(Boost ...)`; this is not recommended. [default=OFF] + +## Build + +To build, test, and install SeQuant, run the following commands: +* `cmake --build build` +* (optional) `cmake --build build --target check-sequant` +* `cmake --build build --target install` diff --git a/README.md b/README.md index 632293469..b44f9accd 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,40 @@ -SeQuant: second quantization toolkit in C++ -=========================================== +SeQuant: symbolic tensor algebra in C++ +======================================= # Synopsis -`SeQuant` is a framework for performing symbolic algebra designed specifically -for the algebra of second quantization in quantum many-body physics. -More abstractly it can symbolically transform and numerically -evaluate (with an appropriate external tensor backend) general +SeQuant is a framework for performing symbolic algebra of tensors over scalar fields (regular tensors) and over +operator fields (tensor operators in, e.g., quantum many-body physics). +In addition to symbolic manipulation it can numerically evaluate +(with an appropriate external tensor backend) general tensor algebra expressions. +Computer algebra systems (CAS) like SeQuant are typically implemented within generic CAS like Mathematica or Maple, or +using high-level languages like Python. In fact, version 1 of SeQuant was written in Mathematica. However, the +performance of high-level languages not sufficient for practical use cases. +SeQuant is written in C++ and is designed to be as efficient as possible without loss of generality. + # Installation -See file INSTALL.md . +The short version: + +- configure (from top SeQuant source dfirectory): `cmake -B build -S . -DCMAKE_INSTALL_PREFIX=/path/where/sequant/to/be/installed` +- build and test: `cmake --build build --target install` + +The long version is in file [INSTALL.md](INSTALL.md). # Getting started ## Build harness -To use SeQuant from within an existing codebase that has a CMake harness (the only case considered here) it should be sufficient to do this: +We will only consider how to use SeQuant from within an existing codebase that has a [CMake](https://cmake.org) harness. If SeQuant has already been built and installed, it should be sufficient to add the SeQuant install prefix to `CMAKE_INSTALL_PREFIX` CMake cache variable and adding the following lines to the `CMakeLists.txt` file in your codebase: + ```cmake find_package(SeQuant CONFIG REQUIRED) target_link_libraries(your_executable_or_library_target PUBLIC SeQuant::SeQuant) ``` -It is often desirable to build SeQuant from source within a standalone codebase; this case be done using the FetchContent CMake module as follows: + +It is often desirable to build SeQuant from source within a standalone codebase; this case be done using the [FetchContent CMake module](https://cmake.org/cmake/help/latest/module/FetchContent.html) as follows: + ```cmake find_package(SeQuant CONFIG) if (NOT TARGET SeQuant::SeQuant) @@ -31,36 +44,38 @@ if (NOT TARGET SeQuant::SeQuant) GIT_REPOSITORY https://github.com/ValeevGroup/SeQuant2.git ) FetchContent_MakeAvailable(sequant) + add_library(SeQuant::SeQuant ALIAS SeQuant) endif() target_link_libraries(your_executable_or_library_target PUBLIC SeQuant::SeQuant) ``` ## Using +SeQuant is a general-purpose symbolic tensor algebra, but the primary use case is in quantum many-body physics. The following is a brief tutorial on using SeQuant for this purpose. + ### Getting Started -To get started let's use SeQuant to apply Wick's theorem to a simple product of elementary (creation and annihilation) - fermionic operators: +To get started let's use SeQuant to apply [Wick's theorem](https://en.wikipedia.org/wiki/Wick's_theorem) to a simple product of elementary (creation and annihilation) fermionic operators: -![a_{p_3} a_{p_4} a^\dagger_{p_1} a^\dagger_{p_2}](doc/images/tut-expr1.svg). +$$ +a_{p_3} a_{p_4} a^\dagger_{p_1} a^\dagger_{p_2}. +$$ This is achieved by the following SeQuant program: -```c++ +```cxx #include int main() { using namespace sequant; - IndexSpace sp; - Index p1(L"p_1", sp), p2(L"p_2", sp), p3(L"p_3", sp), p4(L"p_4", sp); + Index p1(L"p_1"), p2(L"p_2"), p3(L"p_3"), p4(L"p_4"); auto cp1 = fcrex(p1), cp2 = fcrex(p2); auto ap3 = fannx(p3), ap4 = fannx(p4); std::wcout << to_latex(ap3 * ap4 * cp1 * cp2) << " = " << to_latex(FWickTheorem{ap3 * ap4 * cp1 * cp2} - .set_external_indices(std::array{p1, p2, p3, p4}) .full_contractions(false) .compute()) << std::endl; @@ -73,26 +88,27 @@ N.B. All _core_ user-facing SeQuant code lives in C++ namespace `sequant`, from Running this program should produce a LaTeX expression for this formula: -![{{a^{}_{{p_3}}}{a^{}_{{p_4}}}{a^{{p_1}}_{}}{a^{{p_2}}_{}}} = { \bigl( - {{a^{{p_1}{p_2}}_{{p_3}{p_4}}}} - {{s^{{p_1}}_{{p_3}}}{s^{{p_2}}_{{p_4}}}} + {{s^{{p_1}}_{{p_3}}}{a^{{p_2}}_{{p_4}}}} - {{s^{{p_1}}_{{p_4}}}{a^{{p_2}}_{{p_3}}}} + {{s^{{p_2}}_{{p_3}}}{s^{{p_1}}_{{p_4}}}} - {{s^{{p_2}}_{{p_3}}}{a^{{p_1}}_{{p_4}}}} + {{s^{{p_2}}_{{p_4}}}{a^{{p_1}}_{{p_3}}}}\bigr) }](doc/images/tut-expr1-result1.svg) - -, where the tensor notation is used to denote elementary and composite _normal_ operators, +$$ +{{a_ {{p_3}}}{a_ {{p_4}}}{a^{{p_1}}}{a^{{p_2}}}} = { \bigl( - {{a^{{p_1}{p_2}}_ {{p_3}{p_4}}}} - {{s^{{p_1}}_ {{p_3}}}{s^{{p_2}}_ {{p_4}}}} + {{s^{{p_1}}_ {{p_3}}}{a^{{p_2}}_ {{p_4}}}} - {{s^{{p_1}}_ {{p_4}}}{a^{{p_2}}_ {{p_3}}}} + {{s^{{p_2}}_ {{p_3}}}{s^{{p_1}}_ {{p_4}}}} - {{s^{{p_2}}_ {{p_3}}}{a^{{p_1}}_ {{p_4}}}} + {{s^{{p_2}}_ {{p_4}}}{a^{{p_1}}_ {{p_3}}}}\bigr) }, +$$ -![a^p \equiv \, & a_p^\dagger \\ a^{p_1 p_2 \dots p_c}_{q_1 q_2 \dots q_a} \equiv \, & a_{p_1}^\dagger a_{p_2}^\dagger \dots a_{p_c}^\dagger a_{q_a} \dots a_{q_2} a_{q_1}](doc/images/tut-notation-eq1.svg) +where the tensor notation is used to denote elementary and composite _normal-ordered_ (or, shortly, _normal_) operators: + +$$a^p \equiv a_p^\dagger,$$ -, and +$$ +a^{p_1 p_2 \dots p_c}_ {q_1 q_2 \dots q_a} \equiv a_ {p_1}^\dagger a_ {p_2}^\dagger \dots a_ {p_c}^\dagger a_ {q_a} \dots a_ {q_2} a_ {q_1}, +$$ -![s^p_q \equiv \langle q | p \rangle](doc/images/tut-notation-eq2.svg) -denotes 1-particle state inner products (overlaps). Wick's theorem can of course be applied to directly to products of normal composite operators, e.g, +$s^p_q \equiv \langle q | p \rangle$ denotes inner products ("overlaps") of 1-particle states. Wick's theorem can of course be applied directly to products of normal composite operators, e.g, -```c++ +```cxx auto nop1 = ex(std::array{p1, p2}, std::array{p3, p4}); auto nop2 = ex(std::array{p5}, std::array{p6, p7}); std::wcout << to_latex(nop1 * nop2) << " = " << to_latex(FWickTheorem{nop1 * nop2} - .set_external_indices( - std::array{p1, p2, p3, p4, p5, p6, p7}) .full_contractions(false) .compute()) << std::endl; @@ -100,34 +116,121 @@ denotes 1-particle state inner products (overlaps). Wick's theorem can of course produces -![{{a^{{p_1}{p_2}}_{{p_3}{p_4}}}{a^{\textvisiblespace\,{p_5}}_{{p_6}{p_7}}}} = { \bigl({a^{\textvisiblespace\,{p_1}{p_2}{p_5}}_{{p_3}{p_4}{p_6}{p_7}}} - {{s^{{p_5}}_{{p_4}}}{a^{\textvisiblespace\,{p_1}{p_2}}_{{p_3}{p_6}{p_7}}}} + {{s^{{p_5}}_{{p_3}}}{a^{\textvisiblespace\,{p_1}{p_2}}_{{p_4}{p_6}{p_7}}}}\bigr) }](doc/images/tut-expr2-result1.svg) +$$ +{{a^{{p_1}{p_2}}_ {{p_3}{p_4}}}{a^{␣\,{p_5}}_ {{p_6}{p_7}}}} = { \bigl({a^{␣\,{p_1}{p_2}{p_5}}_ {{p_3}{p_4}{p_6}{p_7}}} - {{s^{{p_5}}_ {{p_4}}}{a^{␣\,{p_1}{p_2}}_ {{p_3}{p_6}{p_7}}}} + {{s^{{p_5}}_ {{p_3}}}{a^{␣\,{p_1}{p_2}}_ {{p_4}{p_6}{p_7}}}}\bigr) }, +$$ + +where $␣$ is used in number-nonconserving operators to point out the empty "slots". -, where `␣` is used in number-nonconserving operators to point out the empty "slots". +Same algebra can be performed for bosons: -Of course, same manipulations can be used for bosons: +$$ +{{b^{{p_1}{p_2}}_ {{p_3}{p_4}}}{b^{{p_5}{p_6}}_ {␣\,{p_7}}}} = \bigl( {b^{{p_5}{p_1}{p_2}{p_6}}_ {␣\,{p_3}{p_4}{p_7}}} + {{s^{{p_6}}_ {{p_3}}}{b^{{p_5}{p_2}{p_1}}_ {␣\,{p_4}{p_7}}}} + {{s^{{p_5}}_ {{p_3}}}{b^{{p_1}{p_2}{p_6}}_ {␣\,{p_4}{p_7}}}} +$$ -![{{b^{{p_1}{p_2}}_{{p_3}{p_4}}}{b^{{p_5}{p_6}}_{\textvisiblespace\,{p_7}}}} = { \bigl({b^{{p_5}{p_1}{p_2}{p_6}}_{\textvisiblespace\,{p_3}{p_4}{p_7}}} + {{s^{{p_6}}_{{p_3}}}{b^{{p_5}{p_2}{p_1}}_{\textvisiblespace\,{p_4}{p_7}}}} + {{s^{{p_5}}_{{p_3}}}{b^{{p_1}{p_2}{p_6}}_{\textvisiblespace\,{p_4}{p_7}}}} + {{s^{{p_6}}_{{p_4}}}{b^{{p_5}{p_1}{p_2}}_{\textvisiblespace\,{p_3}{p_7}}}} + {{s^{{p_5}}_{{p_4}}}{b^{{p_2}{p_1}{p_6}}_{\textvisiblespace\,{p_3}{p_7}}}} + {{s^{{p_5}}_{{p_3}}}{s^{{p_6}}_{{p_4}}}{b^{{p_1}{p_2}}_{\textvisiblespace\,{p_7}}}} + {{s^{{p_6}}_{{p_3}}}{s^{{p_5}}_{{p_4}}}{b^{{p_2}{p_1}}_{\textvisiblespace\,{p_7}}}}\bigr) }](doc/images/tut-expr3-result1.svg) +$$ +\qquad \qquad \qquad \qquad \quad + ~ {{s^{{p_6}}_ {{p_4}}}{b^{{p_5}{p_1}{p_2}}_ {␣\,{p_3}{p_7}}}} + {{s^{{p_5}}_ {{p_4}}}{b^{{p_2}{p_1}{p_6}}_ {␣\,{p_3}{p_7}}}} + {{s^{{p_5}}_ {{p_3}}}{s^{{p_6}}_ {{p_4}}}{b^{{p_1}{p_2}}_ {␣\,{p_7}}}} + {{s^{{p_6}}_ {{p_3}}}{s^{{p_5}}_ {{p_4}}}{b^{{p_2}{p_1}}_ {␣\,{p_7}}}} \bigr) +$$ -, where `b` denotes normal bosonic operators constructed analogously with the normal fermionic operators `a`, is obtained via +where $b$ denotes normal bosonic operators constructed analogously with the normal fermionic operators $a$, is obtained via -```c++ +```cxx auto nop3 = ex(std::array{p1, p2}, std::array{p3, p4}); auto nop4 = ex(std::array{p5, p6}, std::array{p7}); std::wcout << to_latex(nop3 * nop4) << " = " << to_latex(BWickTheorem{nop3 * nop4} - .set_external_indices( - std::array{p1, p2, p3, p4, p5, p6, p7}) .full_contractions(false) .compute()) << std::endl; ``` +Tensor and operator constructors can take arbitrary (uniform) sequences of objects that can be converted to `Index`; all of the following are equivalent: + +```cxx +auto nop1 = ex(std::array{p1, p2}, std::array{p3, p4}); +auto nop1 = ex(std::vector{p1, p2}, std::array{L"p3", L"p4"}); +auto nop1 = ex(std::set{"p1", "p2"}, std::vector{L"p3", L"p4"}); +``` + +Note the mixed use of narrow- (`""`) and wide-character (`L""`) strings. SeQuant uses wide characters internally to make it easy parsing of strings into characters without additional dependencies. Thus the use of wide characters is recommended (but not required) as it is more efficient. + +### Register Index Spaces + +Tensor expressions annotated by [abstract indices](https://en.wikipedia.org/wiki/Abstract_index_notation) are common. In some contexts all tensor modes refer to the same range or underlying vector space (as in all examples shown so far); then there is no need to distinguish modes of different types. But in some contexts indices carry important semantic meaning. For example, the energy expression in the [coupled-cluster method](https://doi.org/10.1017/CBO9780511596834), + +$$ +E_\mathrm{CC} = F^{a_1}_ {i_1} t^{i_1}_ {a_1} + \frac{1}{4} \bar{g}^{a_1 a_2}_ {i_1 i_2} (t^{i_1 i_2}_ {a_1 a_2} + 2 t^{i_1}_ {a_1} t^{i_2}_ {a_2}) +$$ + +contains tensors with 2 types of modes, denoted by $i$ and $a$, that represent single-particle (SP) states occupied and unoccupied in the reference state, respectively. To simplify symbolic manipulation of such expressions SeQuant allows to define a custom vocabulary of index spaces and to define their set-theoretic relationships. The following example illustrates the full space denoted by $p$ partitioned into occupied $i$ and unoccupied $a$ base subspaces: + +```cxx +#include +#include + +int main() { + using namespace sequant; + IndexSpaceRegistry isr; + + // base spaces + isr.add(L"i", 0b01) + .add(L"a", 0b10); + // union of 2 base spaces + // can create manually, as isr.add(L"p", 0b11) , or explicitly ... + isr.add_union(L"p", {L"i", L"a"}); // union of i and a + + // can access unions and intersections of base and composite spaces + assert(isr.unIon(L"i", L"a") == isr.retrieve(L"p")); + assert(isr.intersection(L"p", L"i") == isr.retrieve(L"i")); + + // to use the vocabulary defined by isr use it to make a Context object and make it the default + set_default_context(Context(std::move(isr))); + + // now can use space labels to construct Index objects representing said spaces + Index i1(L"i_1"); + Index a1(L"a_1"); + Index p1(L"p_1"); + + // set theoretic operations on spaces + assert(i1.space().intersection(a1.space()) == IndexSpace::null); +} +``` + +This and other vocabularies commonly used in quantum many-body context are supported out-of-the-box by SeQuant; their definition is in `SeQuant/domain/mbpt/convention.hpp`. The previous example is equivalent to the following: + +```cxx +#include +#include + +int main() { + using namespace sequant; + using namespace sequant::mbpt; + + // makes 2 base spaces, i and a, and their union + auto isr = make_min_sr_spaces(); + set_default_context(Context(isr)); + + // set theoretic operations on spaces + auto i1 = Index(L"i_1"); + auto a1 = Index(L"a_1"); + assert(i1.space().attr().intersection(a1.space().attr()).type() == + IndexSpace::Type::null); + assert(i1.space().attr().intersection(a1.space().attr()).qns() == + Spin::any); +} +``` + +Bitset representation of index spaces allows to define set-theoretic operations naturally. Bitset-based representation is used not only for index space _type_ attribute (`IndexSpace::Type`) but also for the _quantum numbers_ attribute (`IndexSpace::QuantumNumbers`). The latter can be used to represent spin quantum numbers, particle types, etc. +The main difference of the last example with the original example is that the `make_min_sr_spaces()` factory changes the quantum numbers used by default (`mbpt::Spin::any`) to make spin algebraic manipulations (like tracing out spin degrees of freedom) easier. Users can create their own definitions to suit their needs, but the vast majority of users will not need to venture outside of the predefined vacbularies. + +Notice that the set-theoretic operations are only partially automated. It is the user's responsibility to define any and all unions and intersections of base spaces that they may encounter in their context. For this reason `IndexSpaceRegistry` has its own `unIon()` and `intersection()` methods that perform error checking to ensure that only registered spaces are defined. + ### Quasiparticles -In most cases we are interested in using SeQuant to manipulate expressions involving operators in normal order relative to a vacuum state with a finite number of particles, rather than with respect to the genuine vacuum with zero particles. To make such composition easier SeQuant expressions depend on SeQuant _context_, which specifies things like the vacuum type, whether the single-particle (SP) basis is orthonormal, etc. The above SeQuant program used the default context, which assumes the genuine vacuum. The active context can be examined by calling `get_default_context()`, changed via `set_default_context()`, and reset to the default via `reset_default_context()`: +In most cases we are interested in using SeQuant to manipulate expressions involving operators in normal order relative to a vacuum state with a finite number of particles, rather than with respect to the genuine vacuum with zero particles. The choice of vacuum state as well as other related traits (whether the SP states are orthonormal, etc.) is defined by the implicit global context. The SeQuant programs until now used the genuine vacuum. The active context can be examined by calling `get_default_context()`, changed via `set_default_context()`, and reset to the default via `reset_default_context()`: -```c++ +```cxx #include int main() { @@ -146,60 +249,222 @@ int main() { } ``` -However, to deal with the single-product vacuum it is necessary to declare the `IndexSpace` objects that will represent SP states included (_occupied_) in the vacuum state and excluded from (_unoccupied_ in) the vacuum state. Since there is no convention for labeling such states SeQuant demands such choices to be specified explicitly, e.g., by declaring +However, to deal with the single-product vacuum it is necessary to register at least one space and announce it as occupied in the vacuum state: -```c++ -IndexSpace::register_instance(L"y", IndexSpace::occupied); -IndexSpace::register_instance(L"z", IndexSpace::complete_maybe_unoccupied); +```cxx +isr.add(L"y", 0b01).vacuum_occupied_space(L"i"); ``` -we can evaluate Wick's theorem in single-product normal order: +or, shorter, -```c++ - auto cp1 = fcrex(p1), cp2 = fcrex(p2); - auto ap3 = fannx(p3), ap4 = fannx(p4); +```cxx +isr.add(L"y", 0b01, is_vacuum_occupied); +``` + +It is also necessary to specify the _complete_ space (union of all base spaces) so that the the space of unoccupied SP states can be determined: + +```cxx +isr.add(L"y", 0b01, is_vacuum_occupied) + .add(L"z", 0b10) + .add(L"p", 0b11, is_complete); +``` + + +The Wick's theorem code itself is independent of the choice of vacuum: + +```cxx +#include +#include + +int main() { + using namespace sequant; + + set_default_context(Context{IndexSpaceRegistry{} + .add(L"y", 0b01, is_vacuum_occupied) + .add(L"z", 0b10) + .add(L"p", 0b11, is_complete), + Vacuum::SingleProduct}); + + auto cp1 = fcrex(L"p_1"), cp2 = fcrex(L"p_2"); + auto ap3 = fannx(L"p_3"), ap4 = fannx(L"p_4"); std::wcout << to_latex(ap3 * cp1 * ap4 * cp2) << " = " << to_latex(FWickTheorem{ap3 * cp1 * ap4 * cp2} - .set_external_indices(std::array{p1, p2, p3, p4}) .full_contractions(false) .compute()) << std::endl; + + return 0; +} ``` produces -![{{\tilde{a}_{{p_3}}}{\tilde{a}^{{p_1}}}{\tilde{a}_{{p_4}}}{\tilde{a}^{{p_2}}}} = { \bigl({\tilde{a}^{{p_1}{p_2}}_{{p_3}{p_4}}} - {{s^{{p_1}}_{{z_1}}}{s^{{z_1}}_{{p_3}}}{\tilde{a}^{{p_2}}_{{p_4}}}} - {{s^{{p_2}}_{{z_1}}}{s^{{z_1}}_{{p_4}}}{\tilde{a}^{{p_1}}_{{p_3}}}} - {{s^{{p_1}}_{{y_1}}}{s^{{y_1}}_{{p_4}}}{\tilde{a}^{{p_2}}_{{p_3}}}} + {{s^{{p_2}}_{{z_1}}}{s^{{z_1}}_{{p_3}}}{\tilde{a}^{{p_1}}_{{p_4}}}} + {{s^{{p_1}}_{{z_1}}}{s^{{p_2}}_{{z_2}}}{s^{{z_1}}_{{p_3}}}{s^{{z_2}}_{{p_4}}}} + {{s^{{p_1}}_{{y_1}}}{s^{{p_2}}_{{z_1}}}{s^{{z_1}}_{{p_3}}}{s^{{y_1}}_{{p_4}}}}\bigr) }](doc/images/tut-expr4-result1.svg) +$$ +{{\tilde{a}_ {{p_3}}}{\tilde{a}^{{p_1}}}{\tilde{a}_ {{p_4}}}{\tilde{a}^{{p_2}}}} = \bigl({\tilde{a}^{{p_1}{p_2}}_ {{p_3}{p_4}}} - {{s^{{p_1}}_ {{z_1}}}{s^{{z_1}}_ {{p_3}}}{\tilde{a}^{{p_2}}_ {{p_4}}}} - {{s^{{p_2}}_ {{z_1}}}{s^{{z_1}}_ {{p_4}}}{\tilde{a}^{{p_1}}_ {{p_3}}}} - {{s^{{p_1}}_ {{y_1}}}{s^{{y_1}}_ {{p_4}}}{\tilde{a}^{{p_2}}_ {{p_3}}}} +$$ + +$$ +\qquad \qquad \qquad \quad + ~ {{s^{{p_2}}_ {{z_1}}}{s^{{z_1}}_ {{p_3}}}{\tilde{a}^{{p_1}}_ {{p_4}}}} + {{s^{{p_1}}_ {{z_1}}}{s^{{p_2}}_ {{z_2}}}{s^{{z_1}}_ {{p_3}}}{s^{{z_2}}_ {{p_4}}}} + {{s^{{p_1}}_ {{y_1}}}{s^{{p_2}}_ {{z_1}}}{s^{{z_1}}_ {{p_3}}}{s^{{y_1}}_ {{p_4}}}}\bigr) . +$$ + +Note that: -. Note that: -- the tilde in `ã` denotes normal order with respect to single-product vacuum, and -- Einstein summation convention is implied, i.e., indices that appear twice in a given product are summed over. +- the tilde in $\tilde{a}$ denotes normal order with respect to single-product vacuum, and +- Einstein summation convention is implied, i.e., indices that appear twice in a given product (once in superscript, once in a subscript) are summed over. -Registering spaces has more benefits than being able to deal with non-genuine vacuum; it can also simplify composition. By registering an index space we associate it with a given base index. This allows to subsequently map index labels to the registered spaces in constructors of indices and operators: +## Operators -```c++ -Index y21(L"y_21"); // <- represents IndexSpace::occupied -Index z1(L"z_1"); // <- represents IndexSpace::complete_maybe_unoccupied -auto op_oo_oo = ex(WstrList{L"y_1", L"y_2"}, WstrList{L"y_3", L"y_4"}); +Development of SeQuant is primarily motivated by the perturbative many-body methods, collectively referred to here as Many-Body Perturbation Theory (MBPT). Examples of such methods include the [coupled-cluster (CC) method](https://doi.org/10.1017/CBO9780511596834) and [GW](https://doi.org/10.1103/PhysRev.139.A796). The typical use case is to compute canonical forms of products of operators. For example, consider the coupled-cluster doubles (CCD) method. +_Amplitudes_ $t^{i_1 i_2}_ {a_1 a_2}$ of the cluster operator, + +$$ +\hat{t} \equiv \hat{t}_ 2 = \frac{1}{4} t^{i_1 i_2}_ {a_1 a_2} a_ {i_1 i_2}^{a_1 a_2}, +$$ + +are determined by solving the CCD equations: + +$$ +\forall i_1, i_2, a_1, a_2: \quad 0 = \langle0\vert a^{i_1 i_2}_ {a_1 a_2} \exp(-\hat{t}_ 2) \hat{H} \exp(\hat{t}_ 2) \vert 0 \rangle = \langle0\vert a^{i_1 i_2}_ {a_1 a_2} \bigl( \hat{H} + [\hat{H}, \hat{t}_ 2] + \frac{1}{2} [[\hat{H}, \hat{t}_ 2], \hat{t}_ 2] \bigr) \vert 0 \rangle. +$$ + +A pedestrian way to compose such expression is to define a cluster operator object using SeQuant tensors and normal-ordered operators: + +```cxx +auto t2 = + ex(rational(1, 4)) * + ex(L"t", std::array{L"a_1", L"a_2"}, std::array{L"i_1", L"i_2"}, + Symmetry::antisymm) * + ex(std::array{L"a_1", L"a_2"}, std::array{L"i_1", L"i_2"}); +``` + +The normal-ordered Hamiltonian is defined similarly as a sum of 1- and 2-body contributions: + +```cxx +auto H = ex(L"f", std::array{L"p_1"}, std::array{L"p_2"}, + Symmetry::nonsymm) * + ex(std::array{L"p_1"}, std::array{L"p_2"}) + + + ex(rational(1, 4)) * + ex(L"g", std::array{L"p_1", L"p_2"}, + std::array{L"p_3", L"p_4"}, Symmetry::antisymm) * + ex(std::array{L"p_1", L"p_2"}, + std::array{L"p_3", L"p_4"}); ``` -To simplify index registration we provide support for a particular convention that the SeQuant developers prefer that we call the "Quantum Chemistry in Fock Space" (QCiFS), named after the [series of articles](http://doi.org/10.1063/1.444231) by Werner Kutzelnigg that introduced its essential elements. It can be loaded in 1 line: +Note that the compact definition of the Hamiltonian is due to the use of the union ($p$) of base occupied ($i$) and unoccupied ($a$) spaces. Many other symbolic algebras only support use of nonoverlapping base spaces, in terms of which Hamiltonian and other tensor expressions would have a much more verbose form. -```c++ -#include +Commutator of the Hamiltonian and cluster operator is trivially composed: + +```cxx +inline auto commutator(auto op1, auto op2) { + return simplify(op1 * op2 - op2 * op1); +} + +auto c_ht = commutator(H, t2); +``` + +Note the use of `simplify` to rewrite an expression in a simpler form. Its role will be emphasized later. + +Unfortunately, we immediately run into the limitation of the "pedestrian" approach. Namely, +the double commutator cannot be correctly obtained as + +```cxx +auto c_htt = ex(rational(1, 2)) * commutator(commutator(H, t2), t2); +``` + +due to the explicit use of specific dummy indices in the definition of `t2`. Using it more than once in a given product will produce an expresson where each dummy index appears more than 2 times, breaking the Einstein summation convention. + +The issue is actually not the reuse of the same of operator object, but the reuse of dummy indices. A straightforward, but brittle, solution is to ensure that each dummy index is only used once. E.g., +to use $\hat{t}_2$ more than once in an expression we must make several versions of it, each with a separate set of dummy indices: + +```cxx +auto t2_0 = + ex(rational(1, 4)) * + ex(L"t", std::array{L"a_1", L"a_2"}, std::array{L"i_1", L"i_2"}, + Symmetry::antisymm) * + ex(std::array{L"a_1", L"a_2"}, std::array{L"i_1", L"i_2"}); + +auto t2_1 = + ex(rational(1, 4)) * + ex(L"t", std::array{L"a_3", L"a_4"}, std::array{L"i_3", L"i_4"}, + Symmetry::antisymm) * + ex(std::array{L"a_3", L"a_4"}, std::array{L"i_3", L"i_4"}); + +auto c_htt = ex(rational(1, 4)) * commutator(commutator(H, t2_0), t2_1); +``` + +This is too error-prone for making complex expressions. A better way is to represent $\hat{t}_2$ by an object that generates tensor form with unique dummy indices generated on the fly. in SeQuant such MBPT _operators_ live in `mbpt` namespace. The entire CCD amplitude equation is evaluated as follows: + +```cxx +#include +#include +#include +#include #include +#include + +inline auto commutator(auto op1, auto op2) { + return op1 * op2 - op2 * op1; +} int main() { - // load the QCiFS convention - mbpt::set_default_convention(); // =mbpt::set_default_convention(mbpt::Convention::QCiFS) - - Index i1(L"i_1"); // active occupied - Index a1(L"a_1"); // active unoccupied - Index p1(L"p_1"); // any state in computational basis - // etc. + using namespace sequant; + using namespace sequant::mbpt; + set_default_context(Context(make_min_sr_spaces(), Vacuum::SingleProduct)); + + auto hbar = H(2) + commutator(H(2), T_(2)) + ex(rational(1,2)) * commutator(commutator(H(2), T_(2)), T_(2)); + auto ccd_eq = vac_av(P(2) * hbar); + std::wcout << "<" << to_latex(P(2) * hbar) << "> = " << to_latex(ccd_eq) << std::endl; + + return 0; } ``` +The result is + +$$ +\bigl({{{\frac{1}{4}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{\bar{g}^{{i_1}{i_2}}_ {{a_1}{a_2}}}} - {{{\frac{1}{2}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{f^{{a_3}}_ {{a_1}}}{\bar{t}^{{i_1}{i_2}}_ {{a_2}{a_3}}}} - {{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{\bar{g}^{{i_1}{a_3}}_ {{i_3}{a_1}}}{\bar{t}^{{i_2}{i_3}}_ {{a_2}{a_3}}}} + {{{\frac{1}{8}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{\bar{g}^{{a_3}{a_4}}_ {{a_1}{a_2}}}{\bar{t}^{{i_1}{i_2}}_ {{a_3}{a_4}}}} +$$ + +$$ +\qquad \quad + ~ {{{\frac{1}{8}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{\bar{g}^{{i_1}{i_2}}_ {{i_3}{i_4}}}{\bar{t}^{{i_3}{i_4}}_ {{a_1}{a_2}}}} + {{{\frac{1}{2}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{f^{{i_1}}_ {{i_3}}}{\bar{t}^{{i_2}{i_3}}_ {{a_1}{a_2}}}} + {{{\frac{1}{16}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{\bar{g}^{{a_3}{a_4}}_ {{i_3}{i_4}}}{\bar{t}^{{i_1}{i_2}}_ {{a_3}{a_4}}}{\bar{t}^{{i_3}{i_4}}_ {{a_1}{a_2}}}} +$$ + +$$ +\quad - ~ {{{\frac{1}{4}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{\bar{g}^{{a_3}{a_4}}_ {{i_3}{i_4}}}{\bar{t}^{{i_1}{i_3}}_ {{a_3}{a_4}}}{\bar{t}^{{i_2}{i_4}}_ {{a_1}{a_2}}}} - {{{\frac{1}{4}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{\bar{g}^{{a_3}{a_4}}_ {{i_3}{i_4}}}{\bar{t}^{{i_1}{i_2}}_ {{a_1}{a_3}}}{\bar{t}^{{i_3}{i_4}}_ {{a_2}{a_4}}}} +$$ + +$$ + ~ + {{{\frac{1}{2}}}{A^{{a_1}{a_2}}_ {{i_1}{i_2}}}{\bar{g}^{{a_3}{a_4}}_ {{i_3}{i_4}}}{\bar{t}^{{i_1}{i_3}}_ {{a_1}{a_3}}}{\bar{t}^{{i_2}{i_4}}_ {{a_2}{a_4}}}}\bigr) +$$ + +The use of MBPT operators rather than their tensor-level forms not only solves problems with the reuse of dummy indices, but also allows to implement additional optimizations such as algebraic simplifications of complex operator expressions and avoiding evaluation of operator products whose vacuum expectation values are guaranteed to vanish. This allows very efficient derivation of complex equations, e.g. CC equations through CCSDTQ are derived in a fraction of a second on a laptop: + +```sh +$ cmake --build build --target srcc +$ time build/srcc time ./srcc 4 t std so +CC equations [type=t,rank=1,spinfree=false,screen=true,use_topology=true,use_connectivity=true,canonical_only=true] computed in 0.0027805 seconds +R1(expS1) has 8 terms: +CC equations [type=t,rank=2,spinfree=false,screen=true,use_topology=true,use_connectivity=true,canonical_only=true] computed in 0.012890749999999999 seconds +R1(expS2) has 14 terms: +R2(expS2) has 31 terms: +CC equations [type=t,rank=3,spinfree=false,screen=true,use_topology=true,use_connectivity=true,canonical_only=true] computed in 0.039590500000000001 seconds +R1(expS3) has 15 terms: +R2(expS3) has 37 terms: +R3(expS3) has 47 terms: +CC equations [type=t,rank=4,spinfree=false,screen=true,use_topology=true,use_connectivity=true,canonical_only=true] computed in 0.107501417 seconds +R1(expS4) has 15 terms: +R2(expS4) has 38 terms: +R3(expS4) has 53 terms: +R4(expS4) has 74 terms: +./srcc 4 t std so 0.27s user 0.41s system 100% cpu 0.674 total +``` + # Developers -`SeQuant` is developed by the Valeev group at Virginia Tech. +SeQuant is developed by the [Valeev Research Group](https://valeevgroup.github.io) in the Department of Chemistry at Virginia Tech. + +# Acknowledgement + +Development of SeQuant has been possible thanks to the support of the US National Science Foundation (award 2217081) and the US Department of Energy (awards DE-SC0022327 and DE-SC0022263) diff --git a/SeQuant/core/algorithm.hpp b/SeQuant/core/algorithm.hpp index 009ddd2c5..6581aeafd 100644 --- a/SeQuant/core/algorithm.hpp +++ b/SeQuant/core/algorithm.hpp @@ -53,7 +53,6 @@ void bubble_sort(ForwardIter begin, Sentinel end, Compare comp) { const auto& val1 = *i; static_assert(std::tuple_size_v> == 2, "need to generalize comparer to handle tuples"); - using lhs_type = decltype(std::get<0>(val0)); using rhs_type = decltype(std::get<1>(val0)); constexpr const bool comp_works_for_tuple_entries = diff --git a/SeQuant/core/attr.hpp b/SeQuant/core/attr.hpp index 409141a2b..f864ccbf4 100644 --- a/SeQuant/core/attr.hpp +++ b/SeQuant/core/attr.hpp @@ -79,7 +79,13 @@ inline std::wstring to_wolfram(BraKetPos a) { } } -enum class Statistics { BoseEinstein, FermiDirac }; +enum class Statistics { + Null, + FermiDirac, + BoseEinstein, + Arbitrary, + Invalid = Null +}; enum class Action { create, annihilate, invalid }; diff --git a/SeQuant/core/context.cpp b/SeQuant/core/context.cpp index 072ba93f6..f9cbea806 100644 --- a/SeQuant/core/context.cpp +++ b/SeQuant/core/context.cpp @@ -2,6 +2,8 @@ #include #include +#include + namespace sequant { bool operator==(const Context& ctx1, const Context& ctx2) { @@ -11,55 +13,139 @@ bool operator==(const Context& ctx1, const Context& ctx2) { return ctx1.vacuum() == ctx2.vacuum() && ctx1.metric() == ctx2.metric() && ctx1.braket_symmetry() == ctx2.braket_symmetry() && ctx1.spbasis() == ctx2.spbasis() && - ctx1.first_dummy_index_ordinal() == ctx2.first_dummy_index_ordinal(); + ctx1.first_dummy_index_ordinal() == + ctx2.first_dummy_index_ordinal() && + *ctx1.index_space_registry() == *ctx2.index_space_registry(); } bool operator!=(const Context& ctx1, const Context& ctx2) { return !(ctx1 == ctx2); } -const Context& get_default_context() { - return detail::get_implicit_context(); +static std::recursive_mutex ctx_mtx; // used to protect the context + +const Context& get_default_context(Statistics s) { + std::scoped_lock lock(ctx_mtx); + auto& contexts = + detail::get_implicit_context>(); + auto it = contexts.find(s); + /// default for arbitrary statistics is initialized lazily here + if (it == contexts.end() && s == Statistics::Arbitrary) { + set_default_context({}, Statistics::Arbitrary); + } + it = contexts.find(s); + // have context for this statistics? else return for arbitrary statistics + if (it != contexts.end()) + return it->second; + else + return get_default_context(Statistics::Arbitrary); } -void set_default_context(const Context& ctx) { - return detail::set_implicit_context(ctx); +void set_default_context(const Context& ctx, Statistics s) { + std::scoped_lock lock(ctx_mtx); + auto& contexts = + detail::implicit_context_instance>(); + auto it = contexts.find(s); + if (it != contexts.end()) { + it->second = ctx; + } else { + contexts.emplace(s, ctx); + } } -void reset_default_context() { detail::reset_implicit_context(); } +void set_default_context(const std::map& ctxs) { + for (const auto& [s, ctx] : ctxs) { + set_default_context(ctx, s); + } +} -[[nodiscard]] detail::ImplicitContextResetter -set_scoped_default_context(const Context& ctx) { +void reset_default_context() { + std::scoped_lock lock(ctx_mtx); + detail::reset_implicit_context>(); +} + +[[nodiscard]] detail::ImplicitContextResetter> +set_scoped_default_context(const std::map& ctx) { + std::scoped_lock lock(ctx_mtx); return detail::set_scoped_implicit_context(ctx); } +[[nodiscard]] detail::ImplicitContextResetter> +set_scoped_default_context(const Context& ctx) { + return detail::set_scoped_implicit_context( + std::map{{Statistics::Arbitrary, ctx}}); +} + +Context::Context(std::shared_ptr isr, Vacuum vac, + IndexSpaceMetric m, BraKetSymmetry bks, SPBasis spb, + std::size_t fdio) + : idx_space_reg_(std::move(isr)), + vacuum_(vac), + metric_(m), + braket_symmetry_(bks), + spbasis_(spb), + first_dummy_index_ordinal_(fdio) {} + +Context::Context(IndexSpaceRegistry isr, Vacuum vac, IndexSpaceMetric m, + BraKetSymmetry bks, SPBasis spb, std::size_t fdio) + : idx_space_reg_(std::make_shared(std::move(isr))), + vacuum_(vac), + metric_(m), + braket_symmetry_(bks), + spbasis_(spb), + first_dummy_index_ordinal_(fdio) {} + Context::Context(Vacuum vac, IndexSpaceMetric m, BraKetSymmetry bks, SPBasis spb, std::size_t fdio) - : vacuum_(vac), + : idx_space_reg_{}, + vacuum_(vac), metric_(m), braket_symmetry_(bks), spbasis_(spb), first_dummy_index_ordinal_(fdio) {} Vacuum Context::vacuum() const { return vacuum_; } + +std::shared_ptr Context::index_space_registry() + const { + return idx_space_reg_; +} + +std::shared_ptr Context::mutable_index_space_registry() + const { + return idx_space_reg_; +} + IndexSpaceMetric Context::metric() const { return metric_; } + BraKetSymmetry Context::braket_symmetry() const { return braket_symmetry_; } + SPBasis Context::spbasis() const { return spbasis_; } + std::size_t Context::first_dummy_index_ordinal() const { return first_dummy_index_ordinal_; } + Context& Context::set(Vacuum vacuum) { vacuum_ = vacuum; return *this; } + +Context& Context::set(IndexSpaceRegistry ISR) { + idx_space_reg_ = std::make_shared(ISR); + return *this; +} + Context& Context::set(IndexSpaceMetric metric) { metric_ = metric; return *this; } + Context& Context::set(BraKetSymmetry braket_symmetry) { braket_symmetry_ = braket_symmetry; return *this; } + Context& Context::set(SPBasis spbasis) { spbasis_ = spbasis; return *this; diff --git a/SeQuant/core/context.hpp b/SeQuant/core/context.hpp index 50b9c4f9f..6d7ee2874 100644 --- a/SeQuant/core/context.hpp +++ b/SeQuant/core/context.hpp @@ -2,15 +2,11 @@ #define SEQUANT_CORE_CONTEXT_HPP #include +#include #include -#include -#include - namespace sequant { -class IndexRegistry; - /// Specifies second quantization context, such as vacuum choice, whether index /// spaces are orthonormal, sizes of index spaces, etc. class Context { @@ -23,6 +19,38 @@ class Context { constexpr static auto first_dummy_index_ordinal = 100; }; + /// standard full-form constructor + + /// @param isr_sptr a shared_ptr to an IndexSpaceRegistry object + /// @param vac a Vacuum object + /// @param m an IndexSpaceMetric object + /// @param bks a BraKetSymmetry object + /// @param spb single-particle basis (spin-free or spin-dependent) + /// @param fdio first dummy index ordinal + explicit Context(std::shared_ptr isr_sptr, + Vacuum vac = Defaults::vacuum, + IndexSpaceMetric m = Defaults::metric, + BraKetSymmetry bks = Defaults::braket_symmetry, + SPBasis spb = Defaults::spbasis, + std::size_t fdio = Defaults::first_dummy_index_ordinal); + + /// @brief same as the standard ctor, using IndexSpaceRegistry passed by value + + /// @param isr an IndexSpaceRegistry object + /// @param vac a Vacuum object + /// @param m an IndexSpaceMetric object + /// @param bks a BraKetSymmetry object + /// @param spb single-particle basis (spin-free or spin-dependent) + /// @param fdio first dummy index ordinal + explicit Context(IndexSpaceRegistry isr, Vacuum vac = Defaults::vacuum, + IndexSpaceMetric m = Defaults::metric, + BraKetSymmetry bks = Defaults::braket_symmetry, + SPBasis spb = Defaults::spbasis, + std::size_t fdio = Defaults::first_dummy_index_ordinal); + + /// @brief same as the standard ctor, using default-constructed + /// IndexSpaceRegistry + /// @param vac a Vacuum object /// @param m an IndexSpaceMetric object /// @param bks a BraKetSymmetry object @@ -36,6 +64,8 @@ class Context { /// default constructor, equivalent to Context(Vacuum::Physical, /// IndexSpaceMetric::Unit, BraKetSymmetry::conjugate, /// sequant::SPBasis::spinorbital, 100) + /// @warning default constructor does not create an IndexSpaceRegistry, thus + /// `this->index_space_registry()` will return nullptr Context() = default; ~Context() = default; @@ -45,6 +75,13 @@ class Context { /// \return Vacuum of this context Vacuum vacuum() const; + /// @return a constant pointer to the IndexSpaceRegistry for this context + /// @warning can be null when user did not provide one to Context (i.e., it + /// was default constructed) + std::shared_ptr index_space_registry() const; + /// @return a pointer to the IndexSpaceRegistry for this context. + /// @throw std::logic_error if the IndexSpaceRegistry is null + std::shared_ptr mutable_index_space_registry() const; /// \return IndexSpaceMetric of this context IndexSpaceMetric metric() const; /// \return BraKetSymmetry of this context @@ -59,6 +96,10 @@ class Context { /// \param vacuum Vacuum /// \return ref to `*this`, for chaining Context& set(Vacuum vacuum); + /// sets the IndexSpaceRegistry for this context + /// \param ISR an IndexSpaceRegistry + /// \return ref to '*this' for chaining + Context& set(IndexSpaceRegistry ISR); /// Sets the IndexSpaceMetric for this context, convenient for chaining /// \param metric IndexSpaceMetric /// \return ref to `*this`, for chaining @@ -76,10 +117,8 @@ class Context { /// \return ref to `*this`, for chaining Context& set_first_dummy_index_ordinal(std::size_t first_dummy_index_ordinal); - /// @return the IndexRegistry object - std::shared_ptr index_registry() const; - private: + std::shared_ptr idx_space_reg_ = nullptr; Vacuum vacuum_ = Defaults::vacuum; IndexSpaceMetric metric_ = Defaults::metric; BraKetSymmetry braket_symmetry_ = Defaults::braket_symmetry; @@ -87,11 +126,6 @@ class Context { std::size_t first_dummy_index_ordinal_ = Defaults::first_dummy_index_ordinal; }; -/// old name of Context is a deprecated alias -using SeQuant - [[deprecated("use sequant::Context instead of sequant::SeQuant")]] = - Context; - /// Context object equality comparison /// \param ctx1 /// \param ctx2 @@ -107,13 +141,48 @@ bool operator==(const Context& ctx1, const Context& ctx2); bool operator!=(const Context& ctx1, const Context& ctx2); /// \name manipulation of implicit context for SeQuant +/// \warning all of these are re-entrant /// @{ -const Context& get_default_context(); -void set_default_context(const Context& ctx); +/// @brief access default Context for the given Statistics +/// @param s Statistics +/// @return the default context used for Statistics @p s +const Context& get_default_context(Statistics s = Statistics::Arbitrary); + +/// @brief sets default Context for the given Statistics +/// @param ctx Context object +/// @param s Statistics +void set_default_context(const Context& ctx, + Statistics s = Statistics::Arbitrary); + +/// @brief sets default Context for several given Statistics +/// @param ctxs a Statistics->Context map +void set_default_context(const std::map& ctxs); + +/// @brief resets default Contexts for all statistics to their initial values void reset_default_context(); -[[nodiscard]] detail::ImplicitContextResetter + +/// @brief changes default contexts +/// @param ctx Context objects for one or more statistics +/// @return a move-only ContextResetter object whose destruction will reset the +/// default context to the previous value +/// @example +/// ```cpp +/// { +/// auto resetter = set_scoped_default_context({{Statistics::Arbitrary, +/// ctx}}); +/// // ctx is now the default context for all statistics +/// } // leaving scope, resetter is destroyed, default context is reset back to +/// the old value +/// ``` +[[nodiscard]] detail::ImplicitContextResetter> +set_scoped_default_context(const std::map& ctx); + +/// @brief changes default context for arbitrary statistics +/// @note equivalent to `set_scoped_default_context({{Statistics::Arbitrary, +/// ctx}})` +[[nodiscard]] detail::ImplicitContextResetter> set_scoped_default_context(const Context& ctx); ///@} diff --git a/SeQuant/core/eval_node.hpp b/SeQuant/core/eval_node.hpp index 0ae623fa4..a5c9ab852 100644 --- a/SeQuant/core/eval_node.hpp +++ b/SeQuant/core/eval_node.hpp @@ -35,26 +35,13 @@ template >> using EvalNode = FullBinaryNode; -template -constexpr bool IsIterable{}; - -template -constexpr bool IsIterable< - T, std::void_t< - decltype(std::begin(std::declval>())), - decltype(std::end(std::declval>()))>> = - true; - -template >> -using IteredT = - std::remove_reference_t()))>; - template constexpr bool IsIterableOfEvalNodes{}; template constexpr bool IsIterableOfEvalNodes< - Iterable, std::enable_if_t>>> = true; + Iterable, std::enable_if_t>>> = + true; /// /// \brief Creates an evaluation tree from @c ExprPtr. @@ -171,7 +158,9 @@ enum NodePos { Left = 0, Right, This }; [[maybe_unused]] std::pair occ_virt(Tensor const& t) { auto bk_rank = t.bra_rank() + t.ket_rank(); auto nocc = ranges::count_if(t.const_braket(), [](Index const& idx) { - return idx.space() == IndexSpace::active_occupied; + return idx.space() == + get_default_context().index_space_registry()->hole_space( + idx.space().qns()); }); auto nvirt = bk_rank - nocc; return {nocc, nvirt}; @@ -289,8 +278,7 @@ struct Memory { /// symbolic cost of flops required for evaluation as an AsyCost object. /// @see AsyCost. If the cost can be reduced due to symmetry, it is done /// so. -/// \detail -/// - The cost of evaluation of leaf nodes is assumed to be zero. +/// \note The cost of evaluation of leaf nodes is assumed to be zero. /// struct FlopsWithSymm { template >> @@ -340,6 +328,7 @@ struct FlopsWithSymm { /// evaluating the node as an AsyCost object. The cost is computed by /// summing the cost of evaluation of children nodes and the cost of /// evaluation of the node itself. +/// \param node The evaluation node whose cost to be evaluated /// \param cost_fn A function object that takes an evaluation node and returns /// the symbolic cost of flops required for evaluation as an /// AsyCost object. @see AsyCost. diff --git a/SeQuant/core/export/itf.cpp b/SeQuant/core/export/itf.cpp new file mode 100644 index 000000000..87c5cfd37 --- /dev/null +++ b/SeQuant/core/export/itf.cpp @@ -0,0 +1,640 @@ +// +// Created by Robert Adam on 2023-09-04 +// + +#include "itf.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace sequant { + +std::wstring to_itf(const itf::CodeBlock &block) { + itf::detail::ITFGenerator generator; + generator.addBlock(block); + return generator.generate(); +} + +namespace itf { + +struct IndexTypeComparer { + bool operator()(const IndexSpace::Type &lhs, + const IndexSpace::Type &rhs) const { + assert(get_default_context().index_space_registry()->retrieve("i").type() < + get_default_context().index_space_registry()->retrieve("a").type()); + return lhs < rhs; + } +}; + +Result::Result(ExprPtr expression, Tensor resultTensor, bool importResultTensor) + : expression(std::move(expression)), + resultTensor(std::move(resultTensor)), + importResultTensor(importResultTensor) {} + +Tensor generateResultTensor(ExprPtr expr) { + // This (same as ITF itself) assumes that all indices that are repeated in the + // expression are also contracted (summed) over and therefore don't contribute + // to the result of the expression + IndexGroups externals = get_unique_indices(expr); + + return Tensor(L"Result", std::move(externals.bra), std::move(externals.ket), std::move(externals.aux)); +} + +Result::Result(ExprPtr expression, bool importResultTensor) + : expression(std::move(expression)), + resultTensor(generateResultTensor(expression)), + importResultTensor(importResultTensor) {} + +CodeBlock::CodeBlock(std::wstring blockName, Result result) + : CodeBlock(std::move(blockName), std::vector{std::move(result)}) {} + +CodeBlock::CodeBlock(std::wstring blockName, std::vector results) + : name(std::move(blockName)), results(std::move(results)) {} + +namespace detail { + +bool TensorBlockCompare::operator()(const Tensor &lhs, + const Tensor &rhs) const { + if (lhs.label() != rhs.label()) { + return lhs.label() < rhs.label(); + } + if (lhs.braket().size() != rhs.braket().size()) { + return lhs.braket().size() < rhs.braket().size(); + } + auto lhsBraket = lhs.braket(); + auto rhsBraket = rhs.braket(); + + for (std::size_t i = 0; i < lhsBraket.size(); ++i) { + if (lhsBraket.at(i).space() != rhsBraket.at(i).space()) { + return lhsBraket.at(i).space() < rhsBraket.at(i).space(); + } + } + + return false; +} + +std::vector to_contractions(const ExprPtr &expression, + const Tensor &resultTensor); + +std::vector to_contractions(const Product &product, + const Tensor &resultTensor) { + static std::size_t intermediateCounter = 1; + + if (product.factors().size() == 1) { + assert(product.factor(0).is()); + assert(product.scalar().imag() == 0); + + return {Contraction{product.scalar().real(), + resultTensor, + product.factor(0).as(), + {}}}; + } + + // We assume that we're dealing with a binary tree + assert(product.factors().size() == 2); + + std::unordered_map intermediates; + std::vector contractions; + + // Handle intermediates + for (const ExprPtr &factor : product.factors()) { + if (factor.is()) { + // Create intermediate that computes this nested product + // This (same as ITF itself) assumes that all indices that are repeated in + // the expression are also contracted (summed) over and therefore don't + // contribute to the result of the expression + IndexGroups intermediateIndexGroups = get_unique_indices(factor); + + // Collect all intermediate indices and sort them such that the order of + // index spaces is the canonical one within ITF (largest space leftmost). + // This is possible, because the index ordering of intermediates is + // arbitrary + std::vector intermediateIndices; + intermediateIndices.reserve(intermediateIndexGroups.bra.size() + + intermediateIndexGroups.ket.size()); + intermediateIndices.insert(intermediateIndices.end(), + intermediateIndexGroups.bra.begin(), + intermediateIndexGroups.bra.end()); + intermediateIndices.insert(intermediateIndices.end(), + intermediateIndexGroups.ket.begin(), + intermediateIndexGroups.ket.end()); + intermediateIndices.insert(intermediateIndices.end(), + intermediateIndexGroups.aux.begin(), + intermediateIndexGroups.aux.end()); + std::sort(intermediateIndices.begin(), intermediateIndices.end(), + [](const Index &lhs, const Index &rhs) { + IndexTypeComparer cmp; + if (cmp(lhs.space().type(), rhs.space().type())) { + return false; + } else if (cmp(rhs.space().type(), lhs.space().type())) { + return true; + } else { + // Indices are of same space + return lhs < rhs; + } + }); + + std::array intermediateName; + swprintf(intermediateName.data(), intermediateName.size(), L"INTER%06u", + intermediateCounter++); + + // There is no notion of bra and ket for intermediates, so we dump all + // indices in the bra for now + Tensor intermediate(intermediateName.data(), + std::move(intermediateIndices), std::vector{}, std::vector{}); + + std::vector intermediateContractions = + to_contractions(factor, intermediate); + contractions.reserve(contractions.size() + + intermediateContractions.size()); + contractions.insert( + contractions.end(), + std::make_move_iterator(intermediateContractions.begin()), + std::make_move_iterator(intermediateContractions.end())); + + intermediates.insert({factor.get(), std::move(intermediate)}); + } else if (factor.is()) { + // TODO: Handle on-the-fly antisymmetrization (K[abij] - K[baij]) + throw std::invalid_argument( + "Products of sums can not yet be translated to ITF"); + } + } + + // Now create the contraction for the two factors + auto lhsIntermediate = intermediates.find(product.factor(0).get()); + auto rhsIntermediate = intermediates.find(product.factor(1).get()); + + assert(product.scalar().imag() == 0); + contractions.push_back(Contraction{ + product.scalar().real(), resultTensor, + lhsIntermediate == intermediates.end() ? product.factor(0).as() + : lhsIntermediate->second, + rhsIntermediate == intermediates.end() ? product.factor(1).as() + : rhsIntermediate->second}); + + return contractions; +} + +std::vector to_contractions(const ExprPtr &expression, + const Tensor &resultTensor) { + std::wstring itfCode; + + if (expression.is()) { + throw std::invalid_argument("Can't transform constants into contractions"); + } else if (expression.is()) { + return {Contraction{1, resultTensor, expression.as(), {}}}; + } else if (expression.is()) { + // Separate into binary contractions + return to_contractions(expression.as(), resultTensor); + } else if (expression.is()) { + // Process each summand + std::vector contractions; + + for (const ExprPtr &summand : expression.as().summands()) { + std::vector currentContractions = + to_contractions(summand, resultTensor); + + contractions.reserve(contractions.size() + currentContractions.size()); + contractions.insert(contractions.end(), + std::make_move_iterator(currentContractions.begin()), + std::make_move_iterator(currentContractions.end())); + } + + return contractions; + } else { + throw std::invalid_argument( + "Unhandled expression type in to_contractions function"); + } +} + +template +bool isSpacePattern(const IndexContainer &indices, + const SpaceTypeContainer &pattern) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + assert(indices.size() == pattern.size()); + + auto indexIter = indices.cbegin(); + auto typeIter = pattern.cbegin(); + + while (indexIter != indices.cend() && typeIter != pattern.cend()) { + if (indexIter->space().type() != *typeIter) { + return false; + } + + ++indexIter; + ++typeIter; + } + + return true; +} + +void one_electron_integral_remapper( + ExprPtr &expr, const std::wstring_view integralTensorLabel) { + if (!expr.is()) { + return; + } + + const Tensor &tensor = expr.as(); + + if (tensor.label() != integralTensorLabel || tensor.bra().size() != 1 || + tensor.ket().size() != 1) { + return; + } + + assert(tensor.bra().size() == 1); + assert(tensor.ket().size() == 1); + + auto braIndices = tensor.bra(); + auto ketIndices = tensor.ket(); + assert(tensor.auxiliary().empty()); + + IndexTypeComparer cmp; + + // Use the bra-ket (hermitian) symmetry of the integrals to exchange creators + // and annihilators such that the larger index space is on the left (in the + // bra) + if (cmp(braIndices[0].space().type(), ketIndices[0].space().type())) { + std::swap(braIndices[0], ketIndices[0]); + } else if (braIndices[0].space().type() == ketIndices[0].space().type() && + ketIndices[0] < braIndices[0]) { + // Cosmetic exchange to arrive at a more canonical index ordering + std::swap(braIndices[0], ketIndices[0]); + } + + expr = + ex(tensor.label(), std::move(braIndices), std::move(ketIndices), tensor.auxiliary()); +} + +template +bool isExceptionalJ(const Container &braIndices, const Container &ketIndices) { + assert(braIndices.size() == 2); + assert(ketIndices.size() == 2); + // integrals with 3 external (virtual) indices ought to be converted to + // J-integrals + return braIndices[0].space().type() == get_default_context() + .index_space_registry() + ->retrieve("a") + .type() && + braIndices[1].space().type() == get_default_context() + .index_space_registry() + ->retrieve("a") + .type() && + ketIndices[0].space().type() == get_default_context() + .index_space_registry() + ->retrieve("a") + .type() && + ketIndices[1].space().type() != + get_default_context().index_space_registry()->retrieve("a").type(); +} + +void two_electron_integral_remapper( + ExprPtr &expr, const std::wstring_view integralTensorLabel) { + if (!expr.is()) { + return; + } + + const Tensor &tensor = expr.as(); + + if (tensor.label() != integralTensorLabel || tensor.bra().size() != 2 || + tensor.ket().size() != 2) { + return; + } + + assert(tensor.bra().size() == 2); + assert(tensor.ket().size() == 2); + + // Copy indices as we might have to mutate them + auto braIndices = tensor.bra(); + auto ketIndices = tensor.ket(); + assert(tensor.auxiliary().empty()); + + IndexTypeComparer cmp; + + // Step 1: Use 8-fold permutational symmetry of spin-summed integrals + // to bring indices into a canonical order in terms of the index + // spaces they belong to. Note: This symmetry is generated by the two + // individual bra-ket symmetries for indices for particle one and two + // as well as the particle-1,2-symmetry (column-symmetry) + // + // The final goal is to order the indices in descending index space + // size, where the assumed relative sizes are + // occ < virt + + // Step 1a: Particle-intern bra-ket symmetry + for (std::size_t i = 0; i < braIndices.size(); ++i) { + if (cmp(braIndices.at(i).space().type(), ketIndices.at(i).space().type())) { + // This bra index belongs to a smaller space than the ket index -> + // swap them + std::swap(braIndices[i], ketIndices[i]); + } + } + + // Step 1b: Particle-1,2-symmetry + bool switchColumns = false; + if (braIndices[0].space().type() != braIndices[1].space().type()) { + switchColumns = + cmp(braIndices[0].space().type(), braIndices[1].space().type()); + } else if (ketIndices[0].space().type() != ketIndices[1].space().type()) { + switchColumns = + cmp(ketIndices[0].space().type(), ketIndices[1].space().type()); + } + + if (switchColumns) { + std::swap(braIndices[0], braIndices[1]); + std::swap(ketIndices[0], ketIndices[1]); + } + + // Step 2: Look at the index space patterns to figure out whether + // this is a K or a J integral. If the previously attempted sorting + // of index spaces can be improved by switching the second and third + // index, do that and thereby produce a J tensor. Otherwise, we retain + // the index sequence as-is and thereby produce a K tensor. + // There are some explicit exceptions for J-tensors though. + std::wstring tensorLabel; + Index *particle1_1 = nullptr; + Index *particle1_2 = nullptr; + Index *particle2_1 = nullptr; + Index *particle2_2 = nullptr; + + if (isExceptionalJ(braIndices, ketIndices) || + cmp(braIndices[1].space().type(), ketIndices[0].space().type())) { + std::swap(braIndices[1], ketIndices[0]); + tensorLabel = L"J"; + + particle1_1 = &braIndices[0]; + particle1_2 = &braIndices[1]; + particle2_1 = &ketIndices[0]; + particle2_2 = &ketIndices[1]; + } else { + tensorLabel = L"K"; + + particle1_1 = &braIndices[0]; + particle1_2 = &ketIndices[0]; + particle2_1 = &braIndices[1]; + particle2_2 = &ketIndices[1]; + } + + // Go through the symmetries again to try and produce the most canonical + // index ordering possible without breaking the index-space ordering + // established up to this point. + // This is a purely cosmetic change, but it is very useful for testing + // purposes to have unique representation of the integrals. + if (particle1_1->space().type() == particle1_2->space().type() && + *particle1_2 < *particle1_1) { + std::swap(*particle1_1, *particle1_2); + } + if (particle2_1->space().type() == particle2_2->space().type() && + *particle2_2 < *particle2_1) { + std::swap(*particle2_1, *particle2_2); + } + if (particle1_1->space().type() == particle2_1->space().type() && + particle1_2->space().type() == particle2_2->space().type()) { + if (*particle2_1 < *particle1_1 || + (*particle1_1 == *particle2_1 && *particle2_2 < *particle1_2)) { + std::swap(*particle1_1, *particle2_1); + std::swap(*particle1_2, *particle2_2); + } + } + + expr = ex(std::move(tensorLabel), std::move(braIndices), + std::move(ketIndices), tensor.auxiliary()); +} + +void integral_remapper(ExprPtr &expr, std::wstring_view oneElectronIntegralName, + std::wstring_view twoElectronIntegralName) { + two_electron_integral_remapper(expr, twoElectronIntegralName); + one_electron_integral_remapper(expr, oneElectronIntegralName); +} + +void remap_integrals(ExprPtr &expr, std::wstring_view oneElectronIntegralName, + std::wstring_view twoElectronIntegralName) { + auto remapper = std::bind(integral_remapper, std::placeholders::_1, + oneElectronIntegralName, twoElectronIntegralName); + + const bool visitedRoot = expr->visit(remapper, true); + + if (!visitedRoot) { + remapper(expr); + } +} + +void ITFGenerator::addBlock(const itf::CodeBlock &block) { + m_codes.reserve(m_codes.size() + block.results.size()); + + std::vector> contractionBlocks; + + for (const Result ¤tResult : block.results) { + ExprPtr expression = currentResult.expression; + remap_integrals(expression); + + contractionBlocks.push_back( + to_contractions(expression, currentResult.resultTensor)); + + if (currentResult.importResultTensor) { + m_importedTensors.insert(currentResult.resultTensor); + } else { + m_createdTensors.insert(currentResult.resultTensor); + } + + // If we encounter a tensor in an expression that we have not yet seen + // before, it must be an imported tensor (otherwise the expression would be + // invalid) + expression->visit( + [this](const ExprPtr &expr) { + if (expr.is()) { + const Tensor &tensor = expr.as(); + if (m_createdTensors.find(tensor) == m_createdTensors.end()) { + m_importedTensors.insert(tensor); + } + m_encounteredIndices.insert(tensor.braket().begin(), + tensor.braket().end()); + } + }, + true); + + // Now go through all result tensors of the contractions that we have + // produced and add all new tensors to the set of created tensors + for (const Contraction ¤tContraction : contractionBlocks.back()) { + if (m_importedTensors.find(currentContraction.result) == + m_importedTensors.end()) { + m_createdTensors.insert(currentContraction.result); + } + } + } + + m_codes.push_back(CodeSection{block.name, std::move(contractionBlocks)}); +} + +struct IndexComponents { + IndexSpace space; + std::size_t id; +}; + +IndexComponents decomposeIndex(const Index &idx) { + // The labels are of the form _ and we want + // Note that SeQuant uses 1-based indexing, but we want 0-based + int num = + std::stoi(std::wstring(idx.label().substr(idx.label().find('_') + 1))) - + 1; + assert(num >= 0 && num <= 6); + + return {idx.space(), static_cast(num)}; +} + +std::map> indicesBySpace( + const std::set &indices) { + std::map> indexMap; + + for (const Index ¤t : indices) { + IndexComponents components = decomposeIndex(current); + + indexMap[components.space].insert(components.id); + } + + return indexMap; +} + +std::wstring to_itf(const Tensor &tensor, bool includeIndexing = true) { + std::wstring tags; + std::wstring indices; + + for (const Index ¤t : tensor.braket()) { + IndexComponents components = decomposeIndex(current); + + assert(components.id <= 7); + + if (components.space.type() == + get_default_context().index_space_registry()->retrieve("i").type()) { + tags += L"c"; + indices += static_cast(L'i' + components.id); + } else if (components.space.type() == get_default_context() + .index_space_registry() + ->retrieve("a") + .type()) { + tags += L"e"; + indices += static_cast(L'a' + components.id); + } else { + throw std::runtime_error("Encountered unhandled index space type"); + } + } + + return std::wstring(tensor.label()) + (tags.empty() ? L"" : L":" + tags) + + (includeIndexing ? L"[" + indices + L"]" : L""); +} + +std::wstring ITFGenerator::generate() const { + std::wstring itf = + L"// This ITF algo file has been generated via SeQuant's ITF export\n\n"; + + itf += L"---- decl\n"; + + // Index declarations + std::map> indexGroups = + indicesBySpace(m_encounteredIndices); + for (auto iter = indexGroups.begin(); iter != indexGroups.end(); ++iter) { + wchar_t baseLabel; + std::wstring spaceLabel; + std::wstring spaceTag; + if (iter->first.type() == + get_default_context().index_space_registry()->retrieve("i").type()) { + baseLabel = L'i'; + spaceLabel = L"Closed"; + spaceTag = L"c"; + } else if (iter->first.type() == get_default_context() + .index_space_registry() + ->retrieve("a") + .type()) { + baseLabel = L'a'; + spaceLabel = L"External"; + spaceTag = L"e"; + } else { + throw std::runtime_error("Encountered unhandled index space type"); + } + + itf += L"index-space: "; + for (std::size_t i : iter->second) { + assert(i <= 7); + itf += static_cast(baseLabel + i); + } + itf += L", " + spaceLabel + L", " + spaceTag + L"\n"; + } + + itf += L"\n"; + + // Tensor declarations + for (const Tensor ¤t : m_importedTensors) { + itf += + L"tensor: " + to_itf(current) + L", " + to_itf(current, false) + L"\n"; + } + itf += L"\n"; + for (const Tensor ¤t : m_createdTensors) { + itf += L"tensor: " + to_itf(current) + L", !Create{type:disk}\n"; + } + itf += L"\n\n"; + + // Actual code + for (const CodeSection ¤tSection : m_codes) { + itf += L"---- code(\"" + currentSection.name + L"\")\n"; + + std::set allocatedTensors; + + for (const std::vector ¤tBlock : + currentSection.contractionBlocks) { + for (const Contraction ¤tContraction : currentBlock) { + // For now we'll do a really silly contribution-by-contribution + // load-process-store strategy + if (allocatedTensors.find(currentContraction.result) == + allocatedTensors.end()) { + itf += L"alloc " + to_itf(currentContraction.result) + L"\n"; + allocatedTensors.insert(currentContraction.result); + } else { + itf += L"load " + to_itf(currentContraction.result) + L"\n"; + } + itf += L"load " + to_itf(currentContraction.lhs) + L"\n"; + if (currentContraction.rhs.has_value()) { + itf += L"load " + to_itf(currentContraction.rhs.value()) + L"\n"; + } + + itf += L"." + to_itf(currentContraction.result) + L" "; + int sign = currentContraction.factor < 0 ? -1 : 1; + + itf += (sign < 0 ? L"-= " : L"+= "); + if (currentContraction.factor * sign != 1) { + itf += to_wstring(currentContraction.factor * sign) + L" * "; + } + itf += to_itf(currentContraction.lhs) + + (currentContraction.rhs.has_value() + ? L" " + to_itf(currentContraction.rhs.value()) + : L"") + + L"\n"; + + if (currentContraction.rhs.has_value()) { + itf += L"drop " + to_itf(currentContraction.rhs.value()) + L"\n"; + } + itf += L"drop " + to_itf(currentContraction.lhs) + L"\n"; + itf += L"store " + to_itf(currentContraction.result) + L"\n"; + } + + itf += L"\n"; + } + + itf += L"\n---- end\n"; + } + + return itf; +} + +} // namespace detail + +} // namespace itf + +} // namespace sequant diff --git a/SeQuant/core/export/itf.hpp b/SeQuant/core/export/itf.hpp new file mode 100644 index 000000000..a245ec0be --- /dev/null +++ b/SeQuant/core/export/itf.hpp @@ -0,0 +1,142 @@ +// +// Created by Robert Adam on 2023-09-04 +// + +#ifndef SEQUANT_CORE_EXPORT_ITF_HPP +#define SEQUANT_CORE_EXPORT_ITF_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace sequant { + +namespace itf { + +/// A result consists of a single result tensor along with an expression tree +/// that contains the expressions needed to build the aforementioned result +/// tensor +struct Result { + ExprPtr expression; + Tensor resultTensor; + bool importResultTensor; + + Result(ExprPtr expression, Tensor resultTensor, + bool importResultTensor = true); + Result(ExprPtr expression, bool importResultTensor = false); +}; + +/// A code block groups one or multiple Results together. Upon ITF code +/// generation a CodeBlock object will be mapped to a single '---- code("xyz")' +/// block in ITF. The individual results will be computed serially inside the +/// code block and the computation of individual results will be separated in +/// different "paragraphs" in the generated code. +struct CodeBlock { + std::wstring name; + std::vector results; + + CodeBlock(std::wstring blockName, Result result); + CodeBlock(std::wstring blockName, std::vector results); +}; + +namespace detail { + +/// Comparator that identifies Tensors only by their "block", which is defined +/// by its name, the amount of its indices as well as the space these indices +/// belong to. Note that it explicitly does not depend on the explicit index +/// labelling. +struct TensorBlockCompare { + bool operator()(const Tensor &lhs, const Tensor &rhs) const; +}; + +/// Replaces one- and two-electron integrals in the given expression with +/// versions that use the index ordering and tensor naming as expected in ITF +void remap_integrals(ExprPtr &expr, + std::wstring_view oneElectronIntegralName = L"f", + std::wstring_view twoElectronIntegralName = L"g"); + +/// Represents a single contraction where the contraction of lhs with rhs, +/// multiplied by the given factor contributes to the given result. If rhs is +/// not given, this implies that the contribution is only lhs scaled with the +/// provided factor. +struct Contraction { + rational factor; + + Tensor result; + Tensor lhs; + std::optional rhs; +}; + +/// This is essentially a low-level representation of a CodeBlock object, where +/// all expressions have been broken down into binary contractions, which leads +/// to the creation of intermediate result tensors. +struct CodeSection { + std::wstring name; + std::vector> contractionBlocks; +}; + +/// This is stateful object that is used to implement the actual ITF code +/// generating capabilities +class ITFGenerator { + public: + ITFGenerator() = default; + + void addBlock(const itf::CodeBlock &block); + + std::wstring generate() const; + + private: + std::set m_encounteredIndices; + std::set m_importedTensors; + std::set m_createdTensors; + std::vector m_codes; +}; + +} // namespace detail + +} // namespace itf + +/// Translates the given ITF CodeBlock to executable ITF code +std::wstring to_itf(const itf::CodeBlock &block); + +/// Translates the given collection/range of ITF CodeBlocks or Results to +/// executable ITF code +template >, + itf::CodeBlock>>> +std::wstring to_itf(Container &&container) { + static_assert( + std::is_same_v || + std::is_same_v, + ExprPtr>); + itf::detail::ITFGenerator generator; + + if constexpr (std::is_same_v) { + for (const itf::CodeBlock ¤t : container) { + generator.addBlock(current); + } + } else { + static_assert( + std::is_same_v, + "Container::value_type must either be itf::CodeBlock or itf::Result"); + + itf::CodeBlock block(L"Generate_Results", + std::forward(container)); + + generator.addBlock(block); + } + + return generator.generate(); +} + +} // namespace sequant + +#endif // SEQUANT_CORE_EXPORT_ITF_HPP diff --git a/SeQuant/core/expr.hpp b/SeQuant/core/expr.hpp index 09e117b3b..f66a9004f 100644 --- a/SeQuant/core/expr.hpp +++ b/SeQuant/core/expr.hpp @@ -5,7 +5,7 @@ #ifndef SEQUANT_EXPR_HPP #define SEQUANT_EXPR_HPP -#include "SeQuant/core/expr_fwd.hpp" +#include #include #include @@ -251,6 +251,21 @@ class Expr : public std::enable_shared_from_this, /// - the default implementation throws an exception virtual ExprPtr clone() const; + /// like Expr::shared_from_this, but returns ExprPtr + /// @return a shared_ptr to this object wrapped into ExprPtr + /// @throw std::bad_weak_ptr if this object is not managed by a shared_ptr + ExprPtr exprptr_from_this() { + return static_cast(this->shared_from_this()); + } + + /// like Expr::shared_from_this, but returns ExprPtr + /// @return a shared_ptr to this object wrapped into ExprPtr + /// @throw std::bad_weak_ptr if this object is not managed by a shared_ptr + ExprPtr exprptr_from_this() const { + return static_cast( + std::const_pointer_cast(this->shared_from_this())); + } + /// Canonicalizes @c this and returns the biproduct of canonicalization (e.g. /// phase) /// @return the biproduct of canonicalization, or @c nullptr if no biproduct @@ -281,26 +296,13 @@ class Expr : public std::enable_shared_from_this, // clang-format on template bool visit(Visitor &&visitor, const bool atoms_only = false) { - for (auto &subexpr_ptr : expr()) { - const auto subexpr_is_an_atom = subexpr_ptr->is_atom(); - const auto need_to_visit_subexpr = !atoms_only || subexpr_is_an_atom; - bool visited = false; - if (!subexpr_is_an_atom) // if not a leaf, recur into it - visited = - subexpr_ptr->visit(std::forward(visitor), atoms_only); - // call on the subexpression itself, if not yet done so - if (need_to_visit_subexpr && !visited) visitor(subexpr_ptr); - } - // can only visit itself here if visitor(const ExprPtr&) is valid - bool this_visited = false; - if constexpr (std::is_invocable_r_v, - const ExprPtr &>) { - if (!atoms_only || this->is_atom()) { - visitor(shared_from_this()); - this_visited = true; - } - } - return this_visited; + return visit_impl(*this, std::forward(visitor), atoms_only); + } + + /// const version of visit + template + bool visit(Visitor &&visitor, const bool atoms_only = false) const { + return visit_impl(*this, std::forward(visitor), atoms_only); } auto begin_subexpr() { return range_type::begin(); } @@ -312,6 +314,7 @@ class Expr : public std::enable_shared_from_this, auto end_subexpr() const { return range_type::end(); } Expr &expr() { return *this; } + const Expr &expr() const { return *this; } template struct is_shared_ptr_of_expr : std::false_type {}; @@ -510,6 +513,36 @@ class Expr : public std::enable_shared_from_this, private: friend ranges::range_access; + template , Expr>>> + static bool visit_impl(E &&expr, Visitor &&visitor, const bool atoms_only) { + if (expr.weak_from_this().use_count() == 0) + throw std::invalid_argument( + "Expr::visit: cannot visit expressions not managed by shared_ptr"); + for (auto &subexpr_ptr : expr.expr()) { + const auto subexpr_is_an_atom = subexpr_ptr->is_atom(); + const auto need_to_visit_subexpr = !atoms_only || subexpr_is_an_atom; + bool visited = false; + if (!subexpr_is_an_atom) // if not a leaf, recur into it + visited = visit_impl(*subexpr_ptr, std::forward(visitor), + atoms_only); + // call on the subexpression itself, if not yet done so + if (need_to_visit_subexpr && !visited) visitor(subexpr_ptr); + } + // N.B. can only visit itself if visitor is nonmutating! + bool this_visited = false; + if constexpr (std::is_invocable_r_v, + const ExprPtr &>) { + if (!atoms_only || expr.is_atom()) { + const ExprPtr this_exprptr = expr.exprptr_from_this(); + visitor(this_exprptr); + this_visited = true; + } + } + return this_visited; + } + protected: Expr(Expr &&) = default; Expr(const Expr &) = default; @@ -1151,7 +1184,6 @@ class Product : public Expr { }); Product result(this->scalar(), ExprPtrList{}); ranges::for_each(cloned_factors, [&](const auto &cloned_factor) { - if (cloned_factor.template is()) std::cout << ""; result.append(1, std::move(cloned_factor), Flatten::No); }); return result; @@ -1685,6 +1717,6 @@ T &ExprPtr::as() { #endif // SEQUANT_EXPR_HPP -#include "expr_operator.hpp" +#include -#include "expr_algorithm.hpp" +#include diff --git a/SeQuant/core/hash.hpp b/SeQuant/core/hash.hpp index 41350b706..7c2b48d60 100644 --- a/SeQuant/core/hash.hpp +++ b/SeQuant/core/hash.hpp @@ -159,7 +159,11 @@ void hash_range(size_t& seed, It begin, It end) { template struct _)&&meta::is_range_v>> { - std::size_t operator()(T const& v) const { return range(begin(v), end(v)); } + std::size_t operator()(T const& v) const { + using std::begin; + using std::end; + return range(begin(v), end(v)); + } }; template diff --git a/SeQuant/core/hugenholtz.hpp b/SeQuant/core/hugenholtz.hpp index b5a5ee198..a33375a62 100644 --- a/SeQuant/core/hugenholtz.hpp +++ b/SeQuant/core/hugenholtz.hpp @@ -80,8 +80,8 @@ class HugenholtzVertex { } /// Group accessor - /// @param group_idx the group ordinal index - /// @return the group whose ordinal index is group_idx + /// @param grp_idx the group ordinal index + /// @return the group whose ordinal index is @p group_idx const auto& group_at(size_t grp_idx) const { return groups_.at(grp_idx); } /// Group size accessor diff --git a/SeQuant/core/index.hpp b/SeQuant/core/index.hpp index f698e6683..1321a6cc7 100644 --- a/SeQuant/core/index.hpp +++ b/SeQuant/core/index.hpp @@ -7,12 +7,13 @@ #include #include -#include +#include +// #include +#include #include #include - // Only needed due to a (likely) compiler bug in Apple Clang -#include +// #include #include #include @@ -52,6 +53,15 @@ using IndexList = std::initializer_list; /// @note Index label can be plain (`label`) or composite (`label_ordinal`) /// where `label` is a string of characters excluding '_', and `ordinal` is /// an integer less than the value returned by min_tmp_label() . +/// @note Index and other SeQuant classes currently use wide characters to +/// represent labels and other strings; this goes against some popular +/// recommendations to use narrow strings (bytestrings) everywhere. The +/// rationale for such choice makes "character"-centric operations easy without +/// the need to grok Unicode and to introduce extra dependencies such as +/// [https://github.com/unicode-org/icu](ICU). Many functions accept +/// bytestrings as input, but they are recoded to wide (but UTF-8 encoded) +/// strings. For optimal efficiency and simplicity users are recommended to use +/// wide strings until further notice. class Index : public Taggable { static auto &tmp_index_accessor() { // initialized so that the first call to next_tmp_index will return @@ -61,6 +71,11 @@ class Index : public Taggable { } public: + /// protoindices cannot be represented by small_vector because it does not + /// accept incomplete types, see + /// https://www.boost.org/doc/libs/master/doc/html/container/main_features.html#container.main_features.containers_of_incomplete_types + /// N.B. hard-coding alignment should lift this restriction but it seems that + /// alignof is still invoked (for no good reason) using index_vector = container::vector; Index() = default; @@ -68,13 +83,16 @@ class Index : public Taggable { /// @param label the label, does not need to be unique /// @param space (a const ref to) the IndexSpace object that specifies to this /// space this object belongs - /// @param proto_index labels of proto indices (all must be unique, + /// @param proto_indices labels of proto indices (all must be unique, /// i.e. duplicates are not allowed) /// @param symmetric_proto_indices if true, proto_indices can be permuted at /// will and will always be sorted - Index(std::wstring_view label, const IndexSpace &space, - IndexList proto_indices, bool symmetric_proto_indices = true) - : label_(label), + template >>> + Index(String &&label, const IndexSpace &space, IndexList proto_indices, + bool symmetric_proto_indices = true) + : label_(to_wstring(std::forward(label))), space_(space), proto_indices_(proto_indices), symmetric_proto_indices_(symmetric_proto_indices) { @@ -86,14 +104,17 @@ class Index : public Taggable { /// @param label the label, does not need to be unique /// @param space (a const ref to) the IndexSpace object that specifies to this /// space this object belongs - /// @param proto_index labels of proto indices (all must be unique, + /// @param proto_indices labels of proto indices (all must be unique, /// i.e. duplicates are not allowed) /// @param symmetric_proto_indices if true, proto_indices can be permuted at /// will and will always be sorted - Index(std::wstring_view label, const IndexSpace &space, + template >>> + Index(String &&label, const IndexSpace &space, container::vector proto_indices, bool symmetric_proto_indices = true) - : label_(label), + : label_(to_wstring(std::forward(label))), space_(space), proto_indices_(std::move(proto_indices)), symmetric_proto_indices_(symmetric_proto_indices) { @@ -104,52 +125,47 @@ class Index : public Taggable { /// @param label the index label, does not need to be unique, but must be /// convertible into an IndexSpace (@sa IndexSpace::instance ) - Index(const std::wstring_view label) - : Index(label, IndexSpace::instance(label), {}) { - check_nontmp_label(); - } - - /// @param label the index label, does not need to be unique, but must be - /// convertible into an IndexSpace (@sa IndexSpace::instance ) - template - Index(const wchar_t (&label)[N]) - : Index(std::wstring_view(&label[0]), IndexSpace::instance(&label[0]), - {}) { - check_nontmp_label(); - } - - /// @param label the index label, does not need to be unique, but must be - /// convertible into an IndexSpace (@sa IndexSpace::instance ) - Index(const wchar_t *label) - : Index(std::wstring_view(label), IndexSpace::instance(label), {}) { + template >>> + Index(String &&label) + : Index( + std::forward(label), + get_default_context().index_space_registry() + ? get_default_context().index_space_registry()->retrieve(label) + : Index::default_space, + {}) { check_nontmp_label(); } /// @brief constructs an Index using an existing Index's label and space and a /// list of proto indices - /// @tparam IndexOrIndexLabel either Index or std::wstring or - /// std::wstring_view + /// @tparam IndexOrIndexLabel either Index or a type that can be + /// viewed/converted to a string (i.e., + /// `meta::is_basic_string_convertible_v>==true`) /// @tparam I either Index or a type that can be converted to Index - /// @param label the label, does not need to be unique + /// @param index_or_index_label an Index or a label, does not need to be + /// unique /// @param proto_indices list of proto indices, or their labels (all must be /// unique, i.e. duplicates are not allowed) /// @param symmetric_proto_indices if true, proto_indices can be permuted at /// will and will always be sorted - template < - typename IndexOrIndexLabel, typename I, - typename = std::enable_if_t< - (std::is_same_v, Index> || - meta::is_wstring_convertible_v>)>> - Index(IndexOrIndexLabel &&index, std::initializer_list proto_indices, + template , Index> || + meta::is_basic_string_convertible_v< + std::decay_t>)>> + Index(IndexOrIndexLabel &&index_or_index_label, + std::initializer_list proto_indices, bool symmetric_proto_indices = true) : symmetric_proto_indices_(symmetric_proto_indices) { if constexpr (!std::is_same_v, Index>) { - label_ = index; - space_ = IndexSpace::instance(label_); + label_ = index_or_index_label; + space_ = get_default_context().index_space_registry()->retrieve(label_); } else { - label_ = index.label(); - space_ = index.space(); + label_ = index_or_index_label.label(); + space_ = index_or_index_label.space(); } if constexpr (!std::is_same_v, Index>) { if (proto_indices.size() != 0) { @@ -167,9 +183,11 @@ class Index : public Taggable { /// @brief constructs an Index using an existing Index's label and space and a /// list of proto indices - /// @tparam IndexOrIndexLabel either Index or std::wstring or - /// std::wstring_view - /// @param label the label, does not need to be unique + /// @tparam IndexOrIndexLabel either Index or a type that can be + /// viewed/converted to a string (i.e., + /// `meta::is_basic_string_convertible_v>==true`) + /// @param index_or_index_label an Index or a label, does not need to be + /// unique /// @param proto_indices list of proto indices (all must be unique, /// i.e. duplicates are not allowed) /// @param symmetric_proto_indices if true, proto_indices can be permuted at @@ -181,17 +199,17 @@ class Index : public Taggable { container::vector> && (std::is_same_v, Index> || meta::is_wstring_convertible_v>)>> - Index(IndexOrIndexLabel &&index, IndexContainer &&proto_indices, - bool symmetric_proto_indices = true) + Index(IndexOrIndexLabel &&index_or_index_label, + IndexContainer &&proto_indices, bool symmetric_proto_indices = true) : proto_indices_(std::forward(proto_indices)), symmetric_proto_indices_(symmetric_proto_indices) { if constexpr (!std::is_same_v, Index>) { - label_ = index; + label_ = index_or_index_label; check_nontmp_label(); - space_ = IndexSpace::instance(label_); + space_ = get_default_context().index_space_registry()->retrieve(label_); } else { - label_ = index.label(); - space_ = index.space(); + label_ = index_or_index_label.label(); + space_ = index_or_index_label.space(); } canonicalize_proto_indices(); check_for_duplicate_proto_indices(); @@ -200,13 +218,12 @@ class Index : public Taggable { /// @brief constructs an Index using an existing Index's label and proto /// indices (if any) and an IndexSpace - /// @tparam IndexOrIndexLabel either Index or std::wstring or - /// std::wstring_view - /// @param[in] index an Index object - /// @param space an IndexSpace object - /// @param label the label, does not need to be unique - /// @param space (a const ref to) the IndexSpace object that specifies to this - /// space this object belongs + /// @tparam IndexOrIndexLabel either Index or a type that can be + /// viewed/converted to a string (i.e., + /// `meta::is_basic_string_convertible_v>==true`) + /// @param[in] index_or_index_label an Index object or a label + /// @param space (a const ref to) the IndexSpace object that specifies the + /// space to which ths object refers to template Index(IndexOrIndexLabel &&index_or_index_label, IndexSpace space) { if constexpr (std::is_same_v) { @@ -225,7 +242,8 @@ class Index : public Taggable { canonicalize_proto_indices(); check_for_duplicate_proto_indices(); } else { - label_ = index_or_index_label; + label_ = + to_wstring(std::forward(index_or_index_label)); space_ = std::move(space); } check_nontmp_label(); @@ -233,7 +251,8 @@ class Index : public Taggable { /// @brief constructs an Index using this object's label and proto indices (if /// any) and a new IndexSpace - /// @param space an IndexSpace object + /// @param space (a const ref to) the IndexSpace object that specifies the + /// space to which ths object refers to [[nodiscard]] Index replace_space(IndexSpace space) const { return Index(*this, std::move(space)); } @@ -241,7 +260,8 @@ class Index : public Taggable { /// @brief constructs an Index using this object's label and proto indices (if /// any), its IndexSpaceType, and a new set of QuantumNumbers [[nodiscard]] Index replace_qns(QuantumNumbersAttr qns) const { - return Index(*this, IndexSpace(this->space().type(), std::move(qns))); + return Index(*this, IndexSpace(this->space().base_key(), + this->space().attr(), std::move(qns))); } /// @return this cast to Taggable& @@ -267,8 +287,8 @@ class Index : public Taggable { /// @return a unique temporary index in space @c space static Index make_tmp_index(const IndexSpace &space) { Index result; - result.label_ = IndexSpace::base_key(space) + L'_' + - std::to_wstring(Index::next_tmp_index()); + result.label_ = + space.base_key() + L'_' + std::to_wstring(Index::next_tmp_index()); result.space_ = space; return result; } @@ -285,29 +305,67 @@ class Index : public Taggable { /// @param symmetric_proto_indices if true, proto_indices can be permuted at /// will and will always be sorted /// @return a unique temporary index in space @c space - template < - typename IndexContainer, - typename = std::enable_if_t, container::vector>>> + template >>> static Index make_tmp_index(const IndexSpace &space, - IndexContainer &&proto_indices, + IndexRange &&proto_indices, bool symmetric_proto_indices = true) { Index result; - result.label_ = IndexSpace::base_key(space) + L'_' + - std::to_wstring(Index::next_tmp_index()); + result.label_ = + space.base_key() + L'_' + std::to_wstring(Index::next_tmp_index()); result.space_ = space; - result.proto_indices_ = std::forward(proto_indices); + if constexpr (std::is_convertible_v, + Index::index_vector>) { + result.proto_indices_ = std::forward(proto_indices); + } else { + result.proto_indices_ = proto_indices | ranges::to; + } result.symmetric_proto_indices_ = symmetric_proto_indices; result.canonicalize_proto_indices(); result.check_for_duplicate_proto_indices(); return result; } + /// @param label an Index label (e.g., returned by Index::label()) + /// @return @p label split into base and ordinal parts; the ordinal part is + /// empty, if missing + static std::pair make_split_label( + std::wstring_view label) { + auto underscore_position = label.find(L'_'); + if (underscore_position == std::wstring::npos) + return {label, {}}; + else + return {{label.data(), underscore_position}, + {label.begin() + underscore_position + 1}}; + } + + /// @param base_label base part of an Index label + /// @param ordinal_label ordinal part of an Index label + /// @return @p base_label and @p ordinal_label merged + static std::wstring make_merged_label(std::wstring_view base_label, + std::wstring_view ordinal_label) { + if (ordinal_label.empty()) + return std::wstring(base_label); + else { + auto result = std::wstring(base_label) + L'_'; + result.append(ordinal_label); + return result; + } + } + /// @return the label as a UTF-8 encoded wide-character string + /// @note label format is `base` or `base_ordinal` /// @warning this does not include the proto index labels, use /// Index::full_label() instead std::wstring_view label() const { return label_; } + /// @return the label split into base and ordinal parts; the ordinal part is + /// empty, if missing + /// @warning this does not include the proto index labels + std::pair split_label() const { + return make_split_label(this->label()); + } + /// @return A string label representable in ASCII encoding /// @warning not to be used with proto indices /// @brief Replaces non-ascii wstring characters with human-readable analogs, @@ -354,8 +412,9 @@ class Index : public Taggable { /// @brief makes a new label by appending a suffix to the label - /// Appends @p suffix to the label itself (if plain) or to its core (if + /// Appends @p suffix to @p label itself (if plain) or to its core (if /// composite) + /// @param label the label to append the suffix to /// @param suffix a string to append to the label /// @return `this->label()` with @p suffix appended template (label); result += suffix; } else { result = label.substr(0, underscore_position); @@ -392,8 +451,9 @@ class Index : public Taggable { /// @brief makes a new label by removing a substring from the label - /// Removes @p substr from the label itself (if plain) or from its core (if + /// Removes @p substr from @p label itself (if plain) or from its core (if /// composite) + /// @param label the label to remove the substring from /// @param substr a string to remove from the label /// @return `this->label()` with @p substr removed template (label); erase(result, substr); } else { result = label.substr(0, underscore_position); @@ -436,10 +496,7 @@ class Index : public Taggable { } /// @return the IndexSpace object - const IndexSpace &space() const { - assert(space_.attr().is_valid()); - return space_; - } + const IndexSpace &space() const { return space_; } /// @return true if this index has proto indices bool has_proto_indices() const { return !proto_indices_.empty(); } @@ -456,7 +513,7 @@ class Index : public Taggable { std::wstring to_latex() const; - template + /*template std::wstring to_wolfram(Attrs &&...attrs) const { auto protect_subscript = [](const std::wstring_view str) { auto subsc_pos = str.rfind(L'_'); @@ -485,7 +542,7 @@ class Index : public Taggable { ...); result += L"]"; return result; - } + }*/ /// @param protoindex_range a range of Index objects /// @return the color of the protoindices @@ -643,6 +700,8 @@ class Index : public Taggable { mutable std::optional full_label_; + const static IndexSpace default_space; + /// sorts proto_indices_ if symmetric_proto_indices_ inline void canonicalize_proto_indices(); @@ -702,8 +761,6 @@ class Index : public Taggable { /// for both), then by space, then by label, then by protoindices (if any) friend bool operator<(const Index &i1, const Index &i2) { // compare qns, tags and spaces in that sequence - assert(i1.space().attr().is_valid()); - assert(i2.space().attr().is_valid()); auto compare_space = [&i1, &i2]() { if (i1.space() != i2.space()) { @@ -725,25 +782,26 @@ class Index : public Taggable { const auto i1_Q = i1.space().qns(); const auto i2_Q = i2.space().qns(); - const bool have_qns = - i1_Q != IndexSpace::nullqns || i2_Q != IndexSpace::nullqns; - if (have_qns || i1_Q != i2_Q) { - // Note that comparison of index spaces contains comparison of QNs - return compare_space(); - } + if (i1_Q == i2_Q) { + const bool have_tags = i1.tag().has_value() && i2.tag().has_value(); - const bool have_tags = i1.tag().has_value() && i2.tag().has_value(); + if (!have_tags || i1.tag() == i2.tag()) { + // Note that comparison of index spaces contains comparison of QNs + return compare_space(); + } - if (!have_tags || i1.tag() == i2.tag()) { - return compare_space(); - } else { return i1.tag() < i2.tag(); } + + return i1_Q < i2_Q; } }; // class Index +inline const IndexSpace Index::default_space{ + L"", IndexSpace::Type::reserved, IndexSpace::QuantumNumbers::reserved}; + void Index::check_for_duplicate_proto_indices() { #ifndef NDEBUG if (!symmetric_proto_indices_) { // if proto indices not symmetric, sort via @@ -806,6 +864,7 @@ class IndexFactory { IndexFactory() = default; /// @tparam IndexValidator IndexValidator(const Index&) -> bool is valid and /// returns true generated index is valid + /// @param validator a validator for the generated indices /// @param min_index start indexing indices for each space with this value; /// must be greater than 0; the default is to use Index::min_tmp_index() template @@ -835,9 +894,9 @@ class IndexFactory { counter_it = counters_.find(space); } } - result = Index(IndexSpace::base_key(space) + L'_' + - std::to_wstring(++(counter_it->second)), - &space); + result = Index( + space.base_key() + L'_' + std::to_wstring(++(counter_it->second)), + &space); valid = validator_ ? validator_(result) : true; } while (!valid); return result; @@ -865,7 +924,7 @@ class IndexFactory { counter_it = counters_.find(space); } } - result = Index(Index(IndexSpace::base_key(space) + L'_' + + result = Index(Index(space.base_key() + L'_' + std::to_wstring(++(counter_it->second)), &space), idx.proto_indices()); @@ -890,7 +949,7 @@ class IndexFactory { /// @brief hashing function -/// @paramp[in] idx a const reference to an Index object +/// @param[in] idx a const reference to an Index object /// @return the hash value of the object referred to by idx inline auto hash_value(const Index &idx) { const auto &proto_indices = idx.proto_indices(); @@ -910,45 +969,6 @@ auto make_indices(WstrList index_labels = {}) { return result; } -class IndexRegistry { - public: - using Record = - std::tuple>; // index record = {sizer} - - IndexRegistry() = default; - - /// updates an existing entry, or creates a new one if it does not exist - template - void update(const Index &idx, Args &&...args) { - auto it = registry_.find(idx); - if (it != registry_.end()) { - registry_.erase(it); - } - auto insertion_result = - registry_.try_emplace(idx, std::forward(args)...); - } - /// creates a new entry - template - void make(const Index &idx, Args &&...args) { - auto insertion_result = - registry_.try_emplace(idx, std::forward(args)...); - assert(insertion_result.second); - } - - /// retrieves the pointer to the Record object for Index @idx , or nullptr if - /// not found - const Record *retrieve(const Index &idx) const { - auto result = registry_.find(idx); - if (result != registry_.end()) - return &(result->second); - else - return nullptr; - } - - private: - container::map registry_; -}; - } // namespace sequant #endif // SEQUANT_INDEX_H diff --git a/SeQuant/core/index_space_registry.hpp b/SeQuant/core/index_space_registry.hpp new file mode 100644 index 000000000..8962c89a6 --- /dev/null +++ b/SeQuant/core/index_space_registry.hpp @@ -0,0 +1,1308 @@ +// +// Created by Conner Masteran on 4/16/24. +// + +#ifndef SEQUANT_INDEX_SPACE_REGISTRY_HPP +#define SEQUANT_INDEX_SPACE_REGISTRY_HPP + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace sequant { + +inline namespace space_tags { +struct IsVacuumOccupied {}; +struct IsReferenceOccupied {}; +struct IsComplete {}; +struct IsHole {}; +struct IsParticle {}; + +constexpr auto is_vacuum_occupied = IsVacuumOccupied{}; +constexpr auto is_reference_occupied = IsReferenceOccupied{}; +constexpr auto is_complete = IsComplete{}; +constexpr auto is_hole = IsHole{}; +constexpr auto is_particle = IsParticle{}; + +} // namespace space_tags + +/// @brief set of known IndexSpace objects + +/// Each IndexSpace object has hardwired base key (label) that gives +/// indexed expressions appropriate semantics; e.g., spaces referred to by +/// indices in \f$ t_{p_1}^{i_1} \f$ are defined if IndexSpace objects with +/// base keys \f$ p \f$ and \f$ i \f$ are registered. +/// Since index spaces have set-theoretic semantics, the user must +/// provide complete set of unions/intersects of the base spaces to +/// cover all possible IndexSpace objects that can be generated in their +/// program. +/// +/// Registry contains 2 parts: set of IndexSpace objects (managed by a +/// `std::shared_ptr`, see IndexSpaceRegistry::spaces()) and specification of +/// various spaces (vacuum, reference, complete, etc.). Copy semantics is thus +/// partially shallow, with spaces shared between copies. This allows to have +/// multiple registries share same set of spaces but have different +/// specifications of vacuum, reference, etc.; this is useful for providing +/// different contexts for fermions and bosons, for example. +class IndexSpaceRegistry { + public: + /// default constructor creates a registry containing only IndexSpace::null + /// @note null space is registered so we don't have to handle it as a corner + /// case in retrieve() and other methods + IndexSpaceRegistry() + : spaces_(std::make_shared< + container::set>()) { + // register nullspace + this->add(IndexSpace::null); + } + + /// constructs an IndexSpaceRegistry from an existing set of IndexSpace + /// objects + IndexSpaceRegistry( + std::shared_ptr> + spaces) + : spaces_(std::move(spaces)) {} + + /// copy constructor + IndexSpaceRegistry(const IndexSpaceRegistry& other) + : spaces_(other.spaces_), + vacocc_(other.vacocc_), + refocc_(other.refocc_), + complete_(other.complete_), + hole_space_(other.hole_space_), + particle_space_(other.particle_space_) {} + + /// move constructor + IndexSpaceRegistry(IndexSpaceRegistry&& other) + : spaces_(std::move(other.spaces_)), + vacocc_(std::move(other.vacocc_)), + refocc_(std::move(other.refocc_)), + complete_(std::move(other.complete_)), + hole_space_(std::move(other.hole_space_)), + particle_space_(std::move(other.particle_space_)) {} + + /// copy assignment operator + IndexSpaceRegistry& operator=(const IndexSpaceRegistry& other) { + spaces_ = other.spaces_; + vacocc_ = other.vacocc_; + refocc_ = other.refocc_; + complete_ = other.complete_; + hole_space_ = other.hole_space_; + particle_space_ = other.particle_space_; + return *this; + } + + /// move assignment operator + IndexSpaceRegistry& operator=(IndexSpaceRegistry&& other) { + spaces_ = std::move(other.spaces_); + vacocc_ = std::move(other.vacocc_); + refocc_ = std::move(other.refocc_); + complete_ = std::move(other.complete_); + hole_space_ = std::move(other.hole_space_); + particle_space_ = std::move(other.particle_space_); + return *this; + } + + const auto& spaces() const { return spaces_; } + + decltype(auto) begin() const { return spaces_->cbegin(); } + decltype(auto) end() const { return spaces_->cend(); } + + /// @brief retrieve a pointer to IndexSpace from the registry by the label + /// @param label a @c base_key of an IndexSpace, or a label of an Index (see + /// Index::label() ) + /// @return pointer to IndexSpace associated with that key, or nullptr if not + /// found + template > + const IndexSpace* retrieve_ptr(S&& label) const { + auto it = + spaces_->find(IndexSpace::reduce_key(to_basic_string_view(label))); + return it != spaces_->end() ? &(*it) : nullptr; + } + + /// @brief retrieve a pointer to IndexSpace from the registry by the label + /// @param label a @c base_key of an IndexSpace, or a label of an Index (see + /// Index::label() ) + /// @return pointer to IndexSpace associated with that key, or nullptr if not + /// found + template > + IndexSpace* retrieve_ptr(S&& label) { + auto it = + spaces_->find(IndexSpace::reduce_key(to_basic_string_view(label))); + return it != spaces_->end() ? &(*it) : nullptr; + } + + /// @brief retrieve an IndexSpace from the registry by the label + /// @param label a @c base_key of an IndexSpace, or a label of an Index (see + /// Index::label() ) + /// @return IndexSpace associated with that key + /// @throw IndexSpace::bad_key if matching space is not found + template > + const IndexSpace& retrieve(S&& label) const { + if (const auto* ptr = retrieve_ptr(std::forward(label))) { + return *ptr; + } else + throw IndexSpace::bad_key(label); + } + + /// @brief retrieve a pointer to IndexSpace from the registry by its type and + /// quantum numbers + /// @param type IndexSpace::Type + /// @param qns IndexSpace::QuantumNumbers + /// @return pointer to the IndexSpace associated with that key, or nullptr if + /// not found + const IndexSpace* retrieve_ptr(const IndexSpace::Type& type, + const IndexSpace::QuantumNumbers& qns) const { + auto it = std::find_if( + spaces_->begin(), spaces_->end(), + [&](const auto& is) { return is.type() == type && is.qns() == qns; }); + return it != spaces_->end() ? &(*it) : nullptr; + } + + /// @brief retrieve an IndexSpace from the registry by its type and quantum + /// numbers + /// @param type IndexSpace::Type + /// @param qns IndexSpace::QuantumNumbers + /// @return IndexSpace associated with that key. + /// @throw std::invalid_argument if matching space is not found + const IndexSpace& retrieve(const IndexSpace::Type& type, + const IndexSpace::QuantumNumbers& qns) const { + if (const auto* ptr = retrieve_ptr(type, qns)) { + return *ptr; + } else + throw std::invalid_argument( + "IndexSpaceRegistry::retrieve(type,qn): missing { IndexSpace::Type=" + + std::to_string(type.to_int32()) + " , IndexSpace::QuantumNumbers=" + + std::to_string(qns.to_int32()) + " } combination"); + } + + /// @brief retrieve pointer to the IndexSpace from the registry by the + /// IndexSpace::Attr + /// @param space_attr an IndexSpace::Attr + /// @return pointer to the IndexSpace associated with that key, or nullptr if + /// not found + const IndexSpace* retrieve_ptr(const IndexSpace::Attr& space_attr) const { + auto it = std::find_if( + spaces_->begin(), spaces_->end(), + [&space_attr](const IndexSpace& s) { return s.attr() == space_attr; }); + return it != spaces_->end() ? &(*it) : nullptr; + } + + /// @brief retrieve an IndexSpace from the registry by the IndexSpace::Attr + /// @param space_attr an IndexSpace::Attr + /// @return IndexSpace associated with that key. + /// @throw std::invalid_argument if matching space is not found + const IndexSpace& retrieve(const IndexSpace::Attr& space_attr) const { + if (const auto* ptr = retrieve_ptr(space_attr)) { + return *ptr; + } else + throw std::invalid_argument( + "IndexSpaceRegistry::retrieve(attr): missing { IndexSpace::Type=" + + std::to_string(space_attr.type().to_int32()) + + " , IndexSpace::QuantumNumbers=" + + std::to_string(space_attr.qns().to_int32()) + " } combination"); + } + + /// queries presence of a registered IndexSpace + /// @param label a @c base_key of an IndexSpace, or a label of an Index (see + /// Index::label() ) + /// @return true, if an IndexSpace with key @p label is registered + template > + bool contains(S&& label) const { + return this->retrieve_ptr(std::forward(label)); + } + + /// queries presence of a registered IndexSpace + /// @param space an IndexSpace object + /// @return true, if an IndexSpace with key `{type,qns}` is registered + bool contains(const IndexSpace& space) const { + return this->retrieve_ptr(space.type(), space.qns()); + } + + /// queries presence of a registered IndexSpace + /// @param type an IndexSpace::Type object + /// @param qns an IndexSpace::QuantumNumbers object + /// @return true, if an IndexSpace with key `{type,qns}` is registered + bool contains(const IndexSpace::Type& type, + const IndexSpace::QuantumNumbers& qns) const { + return this->retrieve_ptr(type, qns); + } + + /// queries presence of a registered IndexSpace + /// @param space_attr an IndexSpace::Attr object + /// @return true, if an IndexSpace with key @p space_attr is registered + bool contains(const IndexSpace::Attr& space_attr) const { + return this->retrieve_ptr(space_attr); + } + + /// @name adding IndexSpace objects to the registry + /// @{ + + /// @brief add an IndexSpace to this registry. + /// @param IS an IndexSpace + /// @return reference to `this` + /// @throw std::invalid_argument if `IS.base_key()` or `IS.attr()` matches + /// an already registered IndexSpace + IndexSpaceRegistry& add(const IndexSpace& IS) { + auto it = spaces_->find(IS.base_key()); + if (it != spaces_->end()) { + throw std::invalid_argument( + "IndexSpaceRegistry::add(is): already have an IndexSpace associated " + "with is.base_key(); if you are trying to replace the IndexSpace use " + "IndexSpaceRegistry::replace(is)"); + } else { + // make sure there are no duplicate IndexSpaces whose attribute is + // IS.attr() + if (ranges::any_of(*spaces_, + [&IS](auto&& is) { return IS.attr() == is.attr(); })) { + throw std::invalid_argument( + "IndexSpaceRegistry::add(is): already have an IndexSpace " + "associated with is.attr(); if you are trying to replace the " + "IndexSpace use IndexSpaceRegistry::replace(is)"); + } + spaces_->emplace(IS); + } + + return clear_memoized_data_and_return_this(); + } + + /// @brief add an IndexSpace to this registry. + /// @param type_label a label that will denote the space type, + /// must be convertible to a std::string + /// @param type an IndexSpace::Type + /// @param args optional arguments consisting of a mix of zero or more of + /// the following: + /// - IndexSpace::QuantumNumbers + /// - approximate size of the space (unsigned long) + /// - any of { is_vacuum_occupied , is_reference_occupied , is_complete , + /// is_hole , is_particle } + /// @return reference to `this` + /// @throw std::invalid_argument if `type_label` or `type` matches + /// an already registered IndexSpace + template > + IndexSpaceRegistry& add(S&& type_label, IndexSpace::Type type, + OptionalArgs&&... args) { + auto h_args = boost::hana::make_tuple(args...); + + // process IndexSpace::QuantumNumbers, set to default is not given + auto h_qns = boost::hana::filter(h_args, [](auto arg) { + return boost::hana::type_c == + boost::hana::type_c; + }); + constexpr auto nqns = boost::hana::size(h_qns); + static_assert( + nqns == boost::hana::size_c<0> || nqns == boost::hana::size_c<1>, + "IndexSpaceRegistry::add: only one IndexSpace::QuantumNumbers argument " + "is allowed"); + constexpr auto have_qns = nqns == boost::hana::size_c<1>; + IndexSpace::QuantumNumbers qns; + if constexpr (have_qns) { + qns = boost::hana::at_c<0>(h_qns); + } + + // process approximate_size, set to default is not given + auto h_ints = boost::hana::filter(h_args, [](auto arg) { + return boost::hana::traits::is_integral(boost::hana::decltype_(arg)); + }); + constexpr auto nints = boost::hana::size(h_ints); + static_assert( + nints == boost::hana::size_c<0> || nints == boost::hana::size_c<1>, + "IndexSpaceRegistry::add: only one integral argument is allowed"); + constexpr auto have_approximate_size = nints == boost::hana::size_c<1>; + unsigned long approximate_size = 10; + if constexpr (have_approximate_size) { + approximate_size = boost::hana::at_c<0>(h_ints); + } + + // make space + IndexSpace space(std::forward(type_label), type, qns, approximate_size); + this->add(space); + + // process attribute tags + auto h_attributes = boost::hana::filter(h_args, [](auto arg) { + return !boost::hana::traits::is_integral( + boost::hana::type_c) && + boost::hana::type_c != + boost::hana::type_c; + }); + process_attribute_tags(h_attributes, type); + + return clear_memoized_data_and_return_this(); + } + + /// @brief add a union of IndexSpace objects to this registry. + /// @param type_label a label that will denote the space type, + /// must be convertible to a std::string + /// @param components sequence of IndexSpace objects or labels (known to this) + /// whose union will be known by @p type_label + /// @param args optional arguments consisting of a mix of zero or more of + /// { is_vacuum_occupied , is_reference_occupied , is_complete , is_hole , + /// is_particle } + /// @return reference to `this` + template , + typename = std::enable_if_t< + (std::is_same_v, IndexSpace> || + meta::is_basic_string_convertible_v< + std::decay_t>)>> + IndexSpaceRegistry& add_unIon( + S&& type_label, std::initializer_list components, + OptionalArgs&&... args) { + assert(components.size() > 1); + + auto h_args = boost::hana::make_tuple(args...); + + // make space + IndexSpace::Attr space_attr; + long count = 0; + if (components.size() <= 1) { + throw std::invalid_argument( + "IndexSpaceRegistry::add_union: must have at least two components"); + } + for (auto&& component : components) { + const IndexSpace* component_ptr; + if constexpr (std::is_same_v, + IndexSpace>) { + component_ptr = &component; + } else { + component_ptr = &(this->retrieve(component)); + } + if (count == 0) + space_attr = component_ptr->attr(); + else + space_attr = space_attr.unIon(component_ptr->attr()); + ++count; + } + const auto approximate_size = compute_approximate_size(space_attr); + + IndexSpace space(std::forward(type_label), space_attr.type(), + space_attr.qns(), approximate_size); + this->add(space); + auto type = space.type(); + + // process attribute tags + auto h_attributes = boost::hana::filter(h_args, [](auto arg) { + return !boost::hana::traits::is_integral( + boost::hana::type_c) && + boost::hana::type_c != + boost::hana::type_c; + }); + process_attribute_tags(h_attributes, type); + + return clear_memoized_data_and_return_this(); + } + + /// alias to add_unIon + template , + typename = std::enable_if_t< + (std::is_same_v, IndexSpace> || + meta::is_basic_string_convertible_v< + std::decay_t>)>> + IndexSpaceRegistry& add_union( + S&& type_label, std::initializer_list components, + OptionalArgs&&... args) { + return this->add_unIon(std::forward(type_label), components, + std::forward(args)...); + } + + /// @brief add a union of IndexSpace objects to this registry. + /// @param type_label a label that will denote the space type, + /// must be convertible to a std::string + /// @param components sequence of IndexSpace objects or labels (known to this) + /// whose intersection will be known by @p type_label + /// @param args optional arguments consisting of a mix of zero or more of + /// { is_vacuum_occupied , is_reference_occupied , is_complete , is_hole , + /// is_particle } + /// @return reference to `this` + template , + typename = std::enable_if_t< + (std::is_same_v, IndexSpace> || + meta::is_basic_string_convertible_v< + std::decay_t>)>> + IndexSpaceRegistry& add_intersection( + S&& type_label, std::initializer_list components, + OptionalArgs&&... args) { + assert(components.size() > 1); + + auto h_args = boost::hana::make_tuple(args...); + + // make space + IndexSpace::Attr space_attr; + long count = 0; + if (components.size() <= 1) { + throw std::invalid_argument( + "IndexSpaceRegistry::add_intersection: must have at least two " + "components"); + } + for (auto&& component : components) { + const IndexSpace* component_ptr; + if constexpr (std::is_same_v, + IndexSpace>) { + component_ptr = &component; + } else { + component_ptr = &(this->retrieve(component)); + } + if (count == 0) + space_attr = component_ptr->attr(); + else + space_attr = space_attr.intersection(component_ptr->attr()); + ++count; + } + const auto approximate_size = compute_approximate_size(space_attr); + + IndexSpace space(std::forward(type_label), space_attr.type(), + space_attr.qns(), approximate_size); + this->add(space); + auto type = space.type(); + + // process attribute tags + auto h_attributes = boost::hana::filter(h_args, [](auto arg) { + return !boost::hana::traits::is_integral( + boost::hana::type_c) && + boost::hana::type_c != + boost::hana::type_c; + }); + process_attribute_tags(h_attributes, type); + + return clear_memoized_data_and_return_this(); + } + + /// @} + + /// @brief removes an IndexSpace associated with `IS.base_key()` from this + /// @param IS an IndexSpace + /// @return reference to `this` + IndexSpaceRegistry& remove(const IndexSpace& IS) { + auto it = spaces_->find(IS.base_key()); + if (it != spaces_->end()) { + spaces_->erase(IS); + } + return clear_memoized_data_and_return_this(); + } + + /// @brief equivalent to `remove(this->retrieve(label))` + /// @param label space label + /// @return reference to `this` + template > + IndexSpaceRegistry& remove(S&& label) { + auto&& IS = this->retrieve(std::forward(label)); + return this->remove(IS); + } + + /// @brief replaces an IndexSpace registered in the registry under + /// IS.base_key() + /// with @p IS + /// @param IS an IndexSpace + /// @return reference to `this` + IndexSpaceRegistry& replace(const IndexSpace& IS) { + this->remove(IS); + return this->add(IS); + } + + /// @brief returns the list of _basis_ IndexSpace::Type objects + + /// A base IndexSpace::Type object has 1 bit in its bitstring. + /// @sa IndexSpaceRegistry::is_base + /// @return (memoized) set of base IndexSpace::Type objects, sorted in + /// increasing order + const std::vector& base_space_types() const { + if (!base_space_types_) { + auto types = *spaces_ | ranges::views::transform([](const auto& s) { + return s.type(); + }) | ranges::views::filter([](const auto& t) { return is_base(t); }) | + ranges::views::unique | ranges::to_vector; + ranges::sort(types, [](auto t1, auto t2) { return t1 < t2; }); + std::scoped_lock guard{mtx_memoized_}; + if (!base_space_types_) { + base_space_types_ = + std::make_shared>(std::move(types)); + } + } + return *base_space_types_; + } + + /// @brief returns the list of _basis_ IndexSpace objects + + /// A base IndexSpace object has 1 bit in its type() bitstring. + /// @sa IndexSpaceRegistry::is_base + /// @return (memoized) set of base IndexSpace objects, sorted in the order of + /// increasing type() + const std::vector& base_spaces() const { + if (!base_spaces_) { + auto spaces = + *spaces_ | + ranges::views::filter([](const auto& s) { return is_base(s); }) | + ranges::views::unique | ranges::to_vector; + ranges::sort(spaces, + [](auto s1, auto s2) { return s1.type() < s2.type(); }); + std::scoped_lock guard{mtx_memoized_}; + if (!base_spaces_) { + base_spaces_ = + std::make_shared>(std::move(spaces)); + } + } + return *base_spaces_; + } + + /// @brief checks if an IndexSpace is in the basis + /// @param IS IndexSpace + /// @return true if @p IS is in the basis + /// @sa base_spaces + static bool is_base(const IndexSpace& IS) { + return has_single_bit(IS.type().to_int32()); + } + + /// @brief checks if an IndexSpace::Type is in the basis + /// @param t IndexSpace::Type + /// @return true if @p t is in the basis + /// @sa space_type_basis + static bool is_base(const IndexSpace::Type& t) { + return has_single_bit(t.to_int32()); + } + + /// @brief clear the contents of *this + /// @return reference to `this` + IndexSpaceRegistry& clear() { + *this = IndexSpaceRegistry{}; + return *this; + } + + /// @brief queries if the intersection space is registered + /// @param space1 + /// @param space2 + /// @return true if `space1.intersection(space2)` is registered + bool valid_intersection(const IndexSpace& space1, + const IndexSpace& space2) const { + auto result_attr = space1.attr().intersection(space2.attr()); + return retrieve_ptr(result_attr); + } + + /// @brief return the resulting space corresponding to a bitwise intersection + /// between two spaces. + /// @param space1 a registered IndexSpace + /// @param space2 a registered IndexSpace + /// @return the intersection of @p space1 and @p space2 + /// @note can return nullspace + /// @note throw invalid_argument if the nonnull intersection is not registered + const IndexSpace& intersection(const IndexSpace& space1, + const IndexSpace& space2) const { + if (space1 == space2) { + return space1; + } else { + const auto target_qns = space1.qns().intersection(space2.qns()); + bool same_qns = space1.qns() == space2.qns(); + if (!target_qns && !same_qns) { // spaces with different quantum numbers + // do not intersect. + return IndexSpace::null; + } + + // check the registry + auto intersection_attr = space1.type().intersection(space2.type()); + const IndexSpace& intersection_space = + find_by_attr({intersection_attr, space1.qns()}); + // the nullspace is a reasonable return value for intersection + if (intersection_space == IndexSpace::null && intersection_attr) { + throw std::invalid_argument( + "The resulting space is not registered in this context. Add this " + "space to the registry with a label to use it."); + } else { + return intersection_space; + } + } + } + + /// @param space1_key base key of a registered IndexSpace + /// @param space2_key base key of a registered IndexSpace + /// @return the intersection of @p space1 and @p space2 + /// @note can return nullspace + /// @note throw invalid_argument if the nonnull intersection is not registered + template > + const IndexSpace& intersection(S1&& space1_key, S2&& space2_key) const { + if (!contains(space1_key) || !contains(space2_key)) + throw std::invalid_argument( + "IndexSpaceRegistry::intersection(s1,s2): s1 and s2 must both be " + "registered"); + return this->intersection(this->retrieve(std::forward(space1_key)), + this->retrieve(std::forward(space2_key))); + } + + /// @brief is a union between spaces eligible and registered + /// @param space1 + /// @param space2 + /// @return true if space is constructable and registered + bool valid_unIon(const IndexSpace& space1, const IndexSpace& space2) const { + // check typeattr + if (!space1.type().includes(space2.type()) && + space1.qns() == space2.qns()) { + // union possible + auto union_type = space1.type().unIon(space2.type()); + IndexSpace::Attr union_attr{union_type, space1.qns()}; + if (!find_by_attr(union_attr)) { // possible but not registered + return false; + } else + return true; + } + // check qn + else if (!space1.qns().includes(space2.qns()) && + space1.type() == space2.type()) { + // union possible + auto union_qn = space1.qns().unIon(space2.qns()); + IndexSpace::Attr union_attr{space1.type(), union_qn}; + if (!find_by_attr(union_attr)) { // possible but not registered + return false; + } else + return true; + } else { // union not mathematically allowed. + return false; + } + } + + /// @param space1 + /// @param space2 + /// @return the union of two spaces. + /// @note can only return registered spaces + /// @note never returns nullspace + const IndexSpace& unIon(const IndexSpace& space1, + const IndexSpace& space2) const { + if (!contains(space1) || !contains(space2)) + throw std::invalid_argument( + "IndexSpaceRegistry::unIon(s1,s2): s1 and s2 must both be " + "registered"); + + if (space1 == space2) { + return space1; + } else { + bool same_qns = space1.qns() == space2.qns(); + if (!same_qns) { + throw std::invalid_argument( + "IndexSpaceRegistry::unIon(s1,s2): s1 and s2 must have identical " + "quantum number attributes."); + } + auto unIontype = space1.type().unIon(space2.type()); + const IndexSpace& unIonSpace = find_by_attr({unIontype, space1.qns()}); + if (unIonSpace == IndexSpace::null) { + throw std::invalid_argument( + "IndexSpaceRegistry::unIon(s1,s2): the result is not registered, " + "must register first."); + } else { + return unIonSpace; + } + } + } + + /// @param space1_key base key of a registered IndexSpace + /// @param space2_key base key of a registered IndexSpace + /// @return the union of two spaces. + /// @note can only return registered spaces + /// @note never returns nullspace + template > + const IndexSpace& unIon(S1&& space1_key, S2&& space2_key) const { + if (!contains(space1_key) || !contains(space2_key)) + throw std::invalid_argument( + "IndexSpaceRegistry::unIon(s1,s2): s1 and s2 must both be " + "registered"); + return this->unIon(this->retrieve(std::forward(space1_key)), + this->retrieve(std::forward(space2_key))); + } + + /// @brief an @c IndexSpace is occupied with respect to the fermi vacuum or a + /// subset of that space + /// @note only makes sense to ask this if in a SingleProduct vacuum context. + bool is_pure_occupied(const IndexSpace& IS) const { + if (!IS) { + return false; + } + if (IS.type().to_int32() <= + vacuum_occupied_space(IS.qns()).type().to_int32()) { + return true; + } else { + return false; + } + } + + /// @brief all states are unoccupied in the fermi vacuum + /// @note again, this only makes sense to ask if in a SingleProduct vacuum + /// context. + bool is_pure_unoccupied(const IndexSpace& IS) const { + if (!IS) { + return false; + } else { + return !IS.type().intersection(vacuum_occupied_space(IS.qns()).type()); + } + } + + /// @brief some states are fermi vacuum occupied + bool contains_occupied(const IndexSpace& IS) const { + return IS.type().intersection(vacuum_occupied_space(IS.qns()).type()) != + IndexSpace::Type::null; + } + + /// @brief some states are fermi vacuum unoccupied + bool contains_unoccupied(const IndexSpace& IS) const { + return IS.type().intersection(vacuum_unoccupied_space(IS.qns()).type()) != + IndexSpace::Type::null; + } + + /// @name specifies which spaces have nonzero occupancy in the vacuum wave + /// function + /// @note needed for applying Wick theorem with Fermi vacuum + /// @{ + + /// @param t an IndexSpace::Type specifying which base spaces have nonzero + /// occupancy in + /// the vacuum wave function by default (i.e. for any quantum number + /// choice); to specify occupied space per specific QN set use the + /// other overload + /// @return reference to `this` + IndexSpaceRegistry& vacuum_occupied_space(const IndexSpace::Type& t) { + throw_if_missing(t, "vacuum_occupied_space"); + std::get<0>(vacocc_) = t; + return *this; + } + + /// @param qn2type for each quantum number specifies which base spaces have + /// nonzero occupancy in the reference wave function + /// @return reference to `this` + IndexSpaceRegistry& vacuum_occupied_space( + std::map qn2type) { + throw_if_missing_any(qn2type, "vacuum_occupied_space"); + std::get<1>(vacocc_) = std::move(qn2type); + return *this; + } + + /// equivalent to `vacuum_occupied_space(s.type())` + /// @note QuantumNumbers attribute of `s` ignored + /// @param s an IndexSpace + /// @return reference to `this` + IndexSpaceRegistry& vacuum_occupied_space(const IndexSpace& s) { + return vacuum_occupied_space(s.type()); + } + + /// equivalent to `vacuum_occupied_space(retrieve(l).type())` + /// @param l label of a known IndexSpace + /// @return reference to `this` + template > + IndexSpaceRegistry& vacuum_occupied_space(S&& l) { + return vacuum_occupied_space(this->retrieve(std::forward(l)).type()); + } + + /// @return the space occupied in vacuum state for any set of quantum numbers + /// @throw std::invalid_argument if @p nulltype_ok is false and + /// vacuum_occupied_space had not been specified + const IndexSpace::Type& vacuum_occupied_space( + bool nulltype_ok = false) const { + if (!std::get<0>(vacocc_)) { + if (nulltype_ok) return IndexSpace::Type::null; + throw std::invalid_argument( + "vacuum occupied space has not been specified, invoke " + "vacuum_occupied_space(IndexSpace::Type) or " + "vacuum_occupied_space(std::map)"); + } else + return std::get<0>(vacocc_); + } + + /// @param qn the quantum numbers of the space + /// @return the space occupied in vacuum state for the given set of quantum + /// numbers + const IndexSpace& vacuum_occupied_space( + const IndexSpace::QuantumNumbers& qn) const { + auto it = std::get<1>(vacocc_).find(qn); + if (it != std::get<1>(vacocc_).end()) { + return retrieve(it->second, qn); + } else { + return retrieve(this->vacuum_occupied_space(), qn); + } + } + + /// @} + + /// @name assign which spaces have nonzero occupancy in the reference wave + /// function (i.e., the wave function uses to compute reference + /// expectation value) + /// @note needed for computing expectation values when the vacuum state does + /// not match the wave function of interest. + /// @{ + + /// @param t an IndexSpace::Type specifying which base spaces have nonzero + /// occupancy in + /// the reference wave function by default (i.e., for any choice of + /// quantum numbers); to specify occupied space per specific QN set + /// use the other overload + /// @return reference to `this` + IndexSpaceRegistry& reference_occupied_space(const IndexSpace::Type& t) { + throw_if_missing(t, "reference_occupied_space"); + std::get<0>(refocc_) = t; + return *this; + } + + /// @param qn2type for each quantum number specifies which base spaces have + /// nonzero occupancy in + /// the reference wave function + /// @return reference to `this` + IndexSpaceRegistry& reference_occupied_space( + std::map qn2type) { + throw_if_missing_any(qn2type, "reference_occupied_space"); + std::get<1>(refocc_) = std::move(qn2type); + return *this; + } + + /// equivalent to `reference_occupied_space(s.type())` + /// @note QuantumNumbers attribute of `s` ignored + /// @param s an IndexSpace + /// @return reference to `this` + IndexSpaceRegistry& reference_occupied_space(const IndexSpace& s) { + return reference_occupied_space(s.type()); + } + + /// equivalent to `reference_occupied_space(retrieve(l).type())` + /// @param l label of a known IndexSpace + /// @return reference to `this` + template > + IndexSpaceRegistry& reference_occupied_space(S&& l) { + return reference_occupied_space(this->retrieve(std::forward(l)).type()); + } + + /// @return the space occupied in reference state for any set of quantum + /// numbers + /// @throw std::invalid_argument if @p nulltype_ok is false and + /// reference_occupied_space had not been specified + const IndexSpace::Type& reference_occupied_space( + bool nulltype_ok = false) const { + if (!std::get<0>(refocc_)) { + if (nulltype_ok) return IndexSpace::Type::null; + throw std::invalid_argument( + "reference occupied space has not been specified, invoke " + "reference_occupied_space(IndexSpace::Type) or " + "reference_occupied_space(std::map)"); + } else + return std::get<0>(refocc_); + } + + /// @param qn the quantum numbers of the space + /// @return the space occupied in vacuum state for the given set of quantum + /// numbers + const IndexSpace& reference_occupied_space( + const IndexSpace::QuantumNumbers& qn) const { + auto it = std::get<1>(refocc_).find(qn); + if (it != std::get<1>(refocc_).end()) { + return retrieve(it->second, qn); + } else { + return retrieve(this->reference_occupied_space(), qn); + } + } + + /// @} + + /// @name specifies which spaces comprise the entirety of Hilbert space + /// @note needed for creating general operators in mbpt/op + /// @{ + + /// @param s an IndexSpace::Type specifying the complete Hilbert space; + /// to specify occupied space per specific QN set use the other + /// overload + IndexSpaceRegistry& complete_space(const IndexSpace::Type& s) { + throw_if_missing(s, "complete_space"); + std::get<0>(complete_) = s; + return *this; + } + + /// @param qn2type for each quantum number specifies which base spaces have + /// nonzero occupancy in + /// the reference wave function + IndexSpaceRegistry& complete_space( + std::map qn2type) { + throw_if_missing_any(qn2type, "complete_space"); + std::get<1>(complete_) = std::move(qn2type); + return *this; + } + + /// equivalent to `complete_space(s.type())` + /// @note QuantumNumbers attribute of `s` ignored + /// @param s an IndexSpace + /// @return reference to `this` + IndexSpaceRegistry& complete_space(const IndexSpace& s) { + return complete_space(s.type()); + } + + /// equivalent to `complete_space(retrieve(l).type())` + /// @param l label of a known IndexSpace + /// @return reference to `this` + template > + IndexSpaceRegistry& complete_space(S&& l) { + return complete_space(this->retrieve(std::forward(l)).type()); + } + + /// @return the complete Hilbert space for any set of quantum numbers + /// @throw std::invalid_argument if @p nulltype_ok is false and complete_space + /// had not been specified + const IndexSpace::Type& complete_space(bool nulltype_ok = false) const { + if (!std::get<0>(complete_)) { + if (nulltype_ok) return IndexSpace::Type::null; + throw std::invalid_argument( + "complete space has not been specified, call " + "complete_space(IndexSpace::Type)"); + } else + return std::get<0>(complete_); + } + + /// @param qn the quantum numbers of the space + /// @return the complete Hilbert space for the given set of quantum numbers + const IndexSpace& complete_space(const IndexSpace::QuantumNumbers& qn) const { + auto it = std::get<1>(complete_).find(qn); + if (it != std::get<1>(complete_).end()) { + return retrieve(it->second, qn); + } else { + return retrieve(this->complete_space(), qn); + } + } + + /// @} + + /// @return the space that is unoccupied in the vacuum state + const IndexSpace& vacuum_unoccupied_space( + const IndexSpace::QuantumNumbers& qn) const { + auto complete_type = this->complete_space(qn).type(); + auto vacocc_type = this->vacuum_occupied_space(qn).type(); + auto vacuocc_type = + complete_type.xOr(vacocc_type).intersection(complete_type); + return this->retrieve(vacuocc_type, qn); + } + + /// @name specifies in which space holes can be created successfully from the + /// reference wave function + /// @note convenience for making operators + /// @{ + + /// @param t an IndexSpace::Type specifying where holes can be created; + /// to specify hole space per specific QN set use the other + /// overload + IndexSpaceRegistry& hole_space(const IndexSpace::Type& t) { + throw_if_missing(t, "hole_space"); + std::get<0>(hole_space_) = t; + return *this; + } + + /// @param qn2type for each quantum number specifies the space in which holes + /// can be created + IndexSpaceRegistry& hole_space( + std::map qn2type) { + throw_if_missing_any(qn2type, "hole_space"); + std::get<1>(hole_space_) = std::move(qn2type); + return *this; + } + + /// equivalent to `hole_space(s.type())` + /// @note QuantumNumbers attribute of `s` ignored + /// @param s an IndexSpace + /// @return reference to `this` + IndexSpaceRegistry& hole_space(const IndexSpace& s) { + return hole_space(s.type()); + } + + /// equivalent to `hole_space(retrieve(l).type())` + /// @param l label of a known IndexSpace + /// @return reference to `this` + template > + IndexSpaceRegistry& hole_space(S&& l) { + return hole_space(this->retrieve(std::forward(l)).type()); + } + + /// @return default space in which holes can be created + /// @throw std::invalid_argument if @p nulltype_ok is false and + /// hole_space had not been specified + const IndexSpace::Type& hole_space(bool nulltype_ok = false) const { + if (!std::get<0>(hole_space_)) { + if (nulltype_ok) return IndexSpace::Type::null; + throw std::invalid_argument( + "active hole space has not been specified, invoke " + "hole_space(IndexSpace::Type) or " + "hole_space(std::map)"); + } else + return std::get<0>(hole_space_); + } + + /// @param qn the quantum numbers of the space + /// @return the space in which holes can be created for the given set of + /// quantum numbers + const IndexSpace& hole_space(const IndexSpace::QuantumNumbers& qn) const { + auto it = std::get<1>(hole_space_).find(qn); + if (it != std::get<1>(hole_space_).end()) { + return this->retrieve(it->second, qn); + } else { + return this->retrieve(this->hole_space(), qn); + } + } + + /// @} + + /// @name specifies in which space particles can be created successfully from + /// the reference wave function + /// @note convenience for making operators + /// @{ + + /// @param t an IndexSpace::Type specifying where particles can be created; + /// to specify particle space per specific QN set use the other + /// overload + IndexSpaceRegistry& particle_space(const IndexSpace::Type& t) { + throw_if_missing(t, "particle_space"); + std::get<0>(particle_space_) = t; + return *this; + } + + /// @param qn2type for each quantum number specifies the space in which + /// particles can be created + IndexSpaceRegistry& particle_space( + std::map qn2type) { + throw_if_missing_any(qn2type, "particle_space"); + std::get<1>(particle_space_) = std::move(qn2type); + return *this; + } + + /// equivalent to `particle_space(s.type())` + /// @note QuantumNumbers attribute of `s` ignored + /// @param s an IndexSpace + /// @return reference to `this` + IndexSpaceRegistry& particle_space(const IndexSpace& s) { + return particle_space(s.type()); + } + + /// equivalent to `particle_space(retrieve(l).type())` + /// @param l label of a known IndexSpace + /// @return reference to `this` + template > + IndexSpaceRegistry& particle_space(S&& l) { + return particle_space(this->retrieve(std::forward(l)).type()); + } + + /// @return default space in which particles can be created + /// @throw std::invalid_argument if @p nulltype_ok is false and + /// particle_space had not been specified + const IndexSpace::Type& particle_space(bool nulltype_ok = false) const { + if (!std::get<0>(particle_space_)) { + if (nulltype_ok) return IndexSpace::Type::null; + throw std::invalid_argument( + "active particle space has not been specified, invoke " + "particle_space(IndexSpace::Type) or " + "particle_space(std::map)"); + } else + return std::get<0>(particle_space_); + } + + /// @param qn the quantum numbers of the space + /// @return the space in which particles can be created for the given set of + /// quantum numbers + const IndexSpace& particle_space(const IndexSpace::QuantumNumbers& qn) const { + auto it = std::get<1>(particle_space_).find(qn); + if (it != std::get<1>(particle_space_).end()) { + return this->retrieve(it->second, qn); + } else { + return this->retrieve(this->particle_space(), qn); + } + } + + /// @} + + private: + // N.B. need transparent comparator, see https://stackoverflow.com/a/35525806 + std::shared_ptr> spaces_; + + // memoized data + mutable std::shared_ptr> base_space_types_; + mutable std::shared_ptr> base_spaces_; + mutable std::recursive_mutex + mtx_memoized_; // used to update the memoized data + IndexSpaceRegistry& clear_memoized_data_and_return_this() { + std::scoped_lock guard{mtx_memoized_}; + base_space_types_.reset(); + base_spaces_.reset(); + return *this; + } + + ///@brief true if has one and only one bit set. + static bool has_single_bit(std::uint32_t bits) { + return bits && !(bits & (bits - 1)); + } + + /// @brief find an IndexSpace from its attr. return nullspace if not present. + /// @param find the IndexSpace via it's @c attr + const IndexSpace& find_by_attr(const IndexSpace::Attr& attr) const { + for (auto&& space : *spaces_) { + if (space.attr() == attr) { + return space; + } + } + return IndexSpace::null; + } + + void throw_if_missing(const IndexSpace::Type& t, + const IndexSpace::QuantumNumbers& qn, + std::string call_context = "") { + for (auto&& space : *spaces_) { + if (space.type() == t && space.qns() == qn) { + return; + } + } + throw std::invalid_argument( + call_context + + ": missing { IndexSpace::Type=" + std::to_string(t.to_int32()) + + " , IndexSpace::QuantumNumbers=" + std::to_string(qn.to_int32()) + + " } combination"); + } + + // same as above, but ignoring qn + void throw_if_missing(const IndexSpace::Type& t, + std::string call_context = "") { + for (auto&& space : *spaces_) { + if (space.type() == t) { + return; + } + } + throw std::invalid_argument(call_context + ": missing { IndexSpace::Type=" + + std::to_string(t.to_int32()) + + " , any IndexSpace::QuantumNumbers } space"); + } + + void throw_if_missing_any( + const std::map& qn2type, + std::string call_context = "") { + std::map qn2type_found; + for (auto&& space : *spaces_) { + for (auto&& [qn, t] : qn2type) { + if (space.type() == t && space.qns() == qn) { + auto [it, found] = qn2type_found.try_emplace(qn, t); + assert(!found); + // found all? return + if (qn2type_found.size() == qn2type.size()) { + return; + } + } + } + } + + std::string errmsg; + for (auto&& [qn, t] : qn2type) { +#if __cplusplus < 202002L + if (qn2type_found.find(qn) == qn2type_found.end()) { +#else + if (!qn2type_found.contains(qn)) { +#endif + errmsg += + call_context + + ": missing { IndexSpace::Type=" + std::to_string(t.to_int32()) + + " , IndexSpace::QuantumNumbers=" + std::to_string(qn.to_int32()) + + " } combination\n"; + } + } + throw std::invalid_argument(errmsg); + } + + // Need to define defaults for various traits, like which spaces are occupied + // in vacuum, etc. Makes sense to make these part of the registry to avoid + // having to pass these around in every call N.B. default and QN-specific + // space selections merged into single tuple + + // used for fermi vacuum wick application + std::tuple> + vacocc_ = {{}, {}}; + + // used for MR MBPT to take average over multiconfiguration reference + std::tuple> + refocc_ = {{}, {}}; + + // defines active bits in TypeAttr; used by general operators in mbpt/op + std::tuple> + complete_ = {{}, {}}; + + // both needed to make excitation and de-excitation operators. not + // necessarily equivalent in the case of multi-reference context. + std::tuple> + hole_space_ = {{}, {}}; + std::tuple> + particle_space_ = {{}, {}}; + + // Boost.Hana snippet to process attribute tag arguments + template + void process_attribute_tags(ArgsHanaTuple h_tuple, + const IndexSpace::Type& type) { + boost::hana::for_each(h_tuple, [this, &type](auto arg) { + if constexpr (boost::hana::type_c == + boost::hana::type_c) { + this->vacuum_occupied_space(type); + } else if constexpr (boost::hana::type_c == + boost::hana::type_c< + space_tags::IsReferenceOccupied>) { + this->reference_occupied_space(type); + } else if constexpr (boost::hana::type_c == + boost::hana::type_c) { + this->complete_space(type); + } else if constexpr (boost::hana::type_c == + boost::hana::type_c) { + this->hole_space(type); + } else if constexpr (boost::hana::type_c == + boost::hana::type_c) { + this->particle_space(type); + } else { + static_assert(meta::always_false::value, + "IndexSpaceRegistry::add{,_union,_intersect}: unknown " + "attribute tag"); + } + }); + } + + /// @brief computes the approximate size of the space + + /// for a base space return its extent, for a composite space compute as a sum + /// of extents of base subspaces + /// @param s an IndexSpace object + /// @return the approximate size of the space + unsigned long compute_approximate_size( + const IndexSpace::Attr& space_attr) const { + if (is_base(space_attr.type())) { + return this->retrieve(space_attr).approximate_size(); + } else { + // compute_approximate_size is used when populating the registry + // so don't use base_spaces() here + unsigned long size = ranges::accumulate( + *spaces_ | ranges::views::filter([&space_attr](auto& s) { + return s.qns() == space_attr.qns() && is_base(s) && + space_attr.type().intersection(s.type()); + }), + 0ul, [](unsigned long size, const IndexSpace& s) { + return size + s.approximate_size(); + }); + return size; + } + } + + friend bool operator==(const IndexSpaceRegistry& isr1, + const IndexSpaceRegistry& isr2) { + return *isr1.spaces_ == *isr2.spaces_; + } +}; // class IndexSpaceRegistry + +} // namespace sequant +#endif // SEQUANT_INDEX_SPACE_REGISTRY_HPP diff --git a/SeQuant/core/meta.hpp b/SeQuant/core/meta.hpp index 697673b86..f27f45144 100644 --- a/SeQuant/core/meta.hpp +++ b/SeQuant/core/meta.hpp @@ -8,10 +8,9 @@ #include #include #include +#include #include -#include - namespace sequant { template @@ -22,6 +21,10 @@ namespace meta { template struct type_printer; +/// always_false::value is always false +template +struct always_false : std::false_type {}; + ///////// remove_cvref /////////// #if __cplusplus < 202002L @@ -115,6 +118,26 @@ struct is_complex> : std::true_type {}; template static constexpr bool is_complex_v = is_complex::value; +/// Evaluates to true if ``T`` is a character type +template +struct is_char : std::false_type {}; +template <> +struct is_char : std::true_type {}; +template <> +struct is_char : std::true_type {}; +#if __cplusplus >= 202002L +template <> +struct is_char : std::true_type {}; +#endif +template <> +struct is_char : std::true_type {}; +template <> +struct is_char : std::true_type {}; +template +struct is_char : is_char {}; +template +static constexpr bool is_char_v = is_char::value; + ///////// is_less_than_comparable ///////// template > @@ -226,6 +249,11 @@ static constexpr bool is_range_v = (is_detected_v && is_detected_v); +template >>> +using range_value_t = ranges::range_value_t>; + /// is_same /// Checks whether \c T is a \c Base (is either the same class or a sub-class /// ignoring CV and reference qualifiers @@ -245,6 +273,34 @@ struct is_same template constexpr bool is_same_v = is_same::value; +/// is_statically_castable checks if a static_cast from From to To is possible +/// N.B. is_statically_castable_v != is_constructible +/// see https://stackoverflow.com/a/16944130 for why this is +/// not called is_explicitly_convertible + +template +struct is_statically_castable { + template + static void f(T); + + template + static constexpr auto test(int) + -> decltype(f(static_cast(std::declval())), true) { + return true; + } + + template + static constexpr auto test(...) -> bool { + return false; + } + + static bool const value = test(0); +}; + +template +constexpr bool is_statically_castable_v = + is_statically_castable::value; + } // namespace meta } // namespace sequant diff --git a/SeQuant/core/op.hpp b/SeQuant/core/op.hpp index 2e6b3a73e..106dd3aa2 100644 --- a/SeQuant/core/op.hpp +++ b/SeQuant/core/op.hpp @@ -55,7 +55,7 @@ class Op { void adjoint() { action_ = sequant::adjoint(action_); } static std::wstring core_label() { - return get_default_context().spbasis() == SPBasis::spinorbital + return get_default_context(S).spbasis() == SPBasis::spinorbital ? (S == Statistics::FermiDirac ? L"a" : L"b") : L"E"; } @@ -122,7 +122,7 @@ inline bool operator<(const Op &op1, const Op &op2) { /// @brief hashing function /// @tparam S a Statistics value specifying the operator statistics -/// @paramp[in] op a const reference to an Op object +/// @param[in] op a const reference to an `Op` object /// @return the hash value of the object referred to by @c op template inline auto hash_value(const Op &op) { @@ -182,14 +182,16 @@ bool is_annihilator(const Op &op) { /// given vacuum, false otherwise template bool is_pure_qpcreator(const Op &op, - Vacuum vacuum = get_default_context().vacuum()) { + Vacuum vacuum = get_default_context(S).vacuum()) { + const auto &isr = get_default_context(S).index_space_registry(); switch (vacuum) { case Vacuum::Physical: return op.action() == Action::create; case Vacuum::SingleProduct: { - const auto occ_class = occupancy_class(op.index().space()); - return (occ_class < 0 && op.action() == Action::annihilate) || - (occ_class > 0 && op.action() == Action::create); + return (isr->is_pure_occupied(op.index().space()) && + op.action() == Action::annihilate) || + (isr->is_pure_unoccupied(op.index().space()) && + op.action() == Action::create); } default: throw std::logic_error( @@ -201,14 +203,16 @@ bool is_pure_qpcreator(const Op &op, /// vacuum, false otherwise template bool is_qpcreator(const Op &op, - Vacuum vacuum = get_default_context().vacuum()) { + Vacuum vacuum = get_default_context(S).vacuum()) { + const auto &isr = get_default_context(S).index_space_registry(); switch (vacuum) { case Vacuum::Physical: return op.action() == Action::create; case Vacuum::SingleProduct: { - const auto occ_class = occupancy_class(op.index().space()); - return (occ_class <= 0 && op.action() == Action::annihilate) || - (occ_class >= 0 && op.action() == Action::create); + return (isr->contains_occupied(op.index().space()) && + op.action() == Action::annihilate) || + (isr->contains_unoccupied(op.index().space()) && + op.action() == Action::create); } default: throw std::logic_error("is_qpcreator: cannot handle MultiProduct vacuum"); @@ -217,18 +221,20 @@ bool is_qpcreator(const Op &op, template IndexSpace qpcreator_space(const Op &op, - Vacuum vacuum = get_default_context().vacuum()) { + Vacuum vacuum = get_default_context(S).vacuum()) { + const auto &isr = get_default_context(S).index_space_registry(); switch (vacuum) { case Vacuum::Physical: return op.action() == Action::create ? op.index().space() - : IndexSpace::null_instance(); + : IndexSpace::null; case Vacuum::SingleProduct: return op.action() == Action::annihilate - ? intersection(op.index().space(), - IndexSpace::instance(IndexSpace::occupied)) - : intersection(op.index().space(), - IndexSpace::instance( - IndexSpace::complete_maybe_unoccupied)); + ? isr->intersection( + op.index().space(), + isr->vacuum_occupied_space(op.index().space().qns())) + : isr->intersection( + op.index().space(), + isr->vacuum_unoccupied_space(op.index().space().qns())); default: throw std::logic_error( "qpcreator_space: cannot handle MultiProduct vacuum"); @@ -239,14 +245,16 @@ IndexSpace qpcreator_space(const Op &op, /// the given vacuum, false otherwise template bool is_pure_qpannihilator(const Op &op, - Vacuum vacuum = get_default_context().vacuum()) { + Vacuum vacuum = get_default_context(S).vacuum()) { + const auto &isr = get_default_context(S).index_space_registry(); switch (vacuum) { case Vacuum::Physical: return op.action() == Action::annihilate; case Vacuum::SingleProduct: { - const auto occ_class = occupancy_class(op.index().space()); - return (occ_class > 0 && op.action() == Action::annihilate) || - (occ_class < 0 && op.action() == Action::create); + return (isr->is_pure_unoccupied(op.index().space()) && + op.action() == Action::annihilate) || + (isr->is_pure_occupied(op.index().space()) && + op.action() == Action::create); } default: throw std::logic_error( @@ -258,14 +266,16 @@ bool is_pure_qpannihilator(const Op &op, /// given vacuum, false otherwise template bool is_qpannihilator(const Op &op, - Vacuum vacuum = get_default_context().vacuum()) { + Vacuum vacuum = get_default_context(S).vacuum()) { + const auto &isr = get_default_context(S).index_space_registry(); switch (vacuum) { case Vacuum::Physical: return op.action() == Action::annihilate; case Vacuum::SingleProduct: { - const auto occ_class = occupancy_class(op.index().space()); - return (occ_class >= 0 && op.action() == Action::annihilate) || - (occ_class <= 0 && op.action() == Action::create); + return (isr->contains_occupied(op.index().space()) && + op.action() == Action::create) || + (isr->contains_unoccupied(op.index().space()) && + op.action() == Action::annihilate); } default: throw std::logic_error( @@ -274,19 +284,21 @@ bool is_qpannihilator(const Op &op, }; template -IndexSpace qpannihilator_space(const Op &op, - Vacuum vacuum = get_default_context().vacuum()) { +IndexSpace qpannihilator_space( + const Op &op, Vacuum vacuum = get_default_context(S).vacuum()) { + const auto &isr = get_default_context(S).index_space_registry(); switch (vacuum) { case Vacuum::Physical: return op.action() == Action::annihilate ? op.index().space() - : IndexSpace::null_instance(); + : IndexSpace::null; case Vacuum::SingleProduct: return op.action() == Action::create - ? intersection(op.index().space(), - IndexSpace::instance(IndexSpace::occupied)) - : intersection(op.index().space(), - IndexSpace::instance( - IndexSpace::complete_maybe_unoccupied)); + ? isr->intersection( + op.index().space(), + isr->vacuum_occupied_space(op.index().space().qns())) + : isr->intersection( + op.index().space(), + isr->vacuum_unoccupied_space(op.index().space().qns())); default: throw std::logic_error( "qpcreator_space: cannot handle MultiProduct vacuum"); @@ -371,8 +383,8 @@ class Operator : public container::svector>, public Expr { bool commutes_with_atom(const Expr &that) const override { bool result = true; - /// does not commute with Operator - /// TODO implement checks of commutativity with Operator + // does not commute with Operator + // TODO implement checks of commutativity with Operator if (that.is>()) { result = false; } else if (that.is>()) { @@ -453,22 +465,25 @@ class NormalOperator : public Operator, using base_type::operator[]; /// constructs an identity operator - NormalOperator(Vacuum v = get_default_context().vacuum()) {} - - /// @tparam IndexSequence1 type representing a sequence of indices - /// @tparam IndexSequence2 type representing a sequence of indices - /// @param creators sequence of creator indices - /// @param annihilators sequence of annihilator indices (in order of particle - /// indices, see the class documentation for more info). + NormalOperator(Vacuum v = get_default_context(S).vacuum()) {} + + /// @tparam IndexSequence1 type representing a sequence of Index objects or + /// objects that can be statically cast into Index + /// @tparam IndexSequence2 type representing a sequence of Index objects or + /// objects that can be statically cast into Index + /// @param creator_indices sequence of creator indices + /// @param annihilator_indices sequence of annihilator indices (in order of + /// particle indices, see the class documentation for more info). + /// @param v vacuum state with respect to which the operator is normal-ordered template ::value_type, Index> && - std::is_same_v< - typename std::decay_t::value_type, Index>>> + meta::is_statically_castable_v< + meta::range_value_t, Index> && + meta::is_statically_castable_v< + meta::range_value_t, Index>>> NormalOperator(IndexSequence1 &&creator_indices, IndexSequence2 &&annihilator_indices, - Vacuum v = get_default_context().vacuum()) + Vacuum v = get_default_context(S).vacuum()) : Operator{}, vacuum_(v), ncreators_(creator_indices.size()) { this->reserve(creator_indices.size() + annihilator_indices.size()); for (const auto &i : creator_indices) { @@ -479,15 +494,16 @@ class NormalOperator : public Operator, } } - /// @tparam OpSequence1 type representing a sequence of Op objects - /// @tparam OpSequence2 type representing a sequence of Op objects + /// @tparam OpSequence1 type representing a sequence of `Op` objects + /// @tparam OpSequence2 type representing a sequence of `Op` objects /// @param creators sequence of creators /// @param annihilators sequence of annihilators (in order of particle /// indices, see the class documentation for more info). + /// @param v vacuum state with respect to which the operator is normal-ordered template NormalOperator( OpSequence1 &&creators, OpSequence2 &&annihilators, - Vacuum v = get_default_context().vacuum(), + Vacuum v = get_default_context(S).vacuum(), std::enable_if_t< !std::is_same_v, NormalOperator> && !std::is_same_v, NormalOperator> && @@ -510,14 +526,15 @@ class NormalOperator : public Operator, ranges::crend(annihilators)); } - /// @param creators initializer_list of creator indices - /// @param annihilators initializer_list of annihilator indices (in order of - /// particle indices, see the class documentation for more info). + /// @param creator_indices initializer_list of creator indices + /// @param annihilator_indices initializer_list of annihilator indices (in + /// order of particle indices, see the class documentation for more info). + /// @param v vacuum state with respect to which the operator is normal-ordered template , Op>>> NormalOperator(std::initializer_list creator_indices, std::initializer_list annihilator_indices, - Vacuum v = get_default_context().vacuum()) + Vacuum v = get_default_context(S).vacuum()) : Operator{}, vacuum_(v), ncreators_(creator_indices.size()) { this->reserve(creator_indices.size() + annihilator_indices.size()); for (const auto &i : creator_indices) { @@ -531,9 +548,10 @@ class NormalOperator : public Operator, /// @param creators initializer_list of creators /// @param annihilators initializer_list of annihilators (in order of particle /// indices, see the class documentation for more info). + /// @param v vacuum state with respect to which the operator is normal-ordered NormalOperator(std::initializer_list> creators, std::initializer_list> annihilators, - Vacuum v = get_default_context().vacuum()) + Vacuum v = get_default_context(S).vacuum()) : Operator{}, vacuum_(v), ncreators_(std::size(creators)) { for (const auto &op : creators) { assert(op.action() == Action::create); @@ -763,14 +781,16 @@ class NormalOperator : public Operator, template friend class Operator; bool commutes_with_atom(const Expr &that) const override { + const auto &isr = get_default_context(S).index_space_registry(); // same as WickTheorem::can_contract - auto can_contract = [this](const Op &left, const Op &right) { + auto can_contract = [this, &isr](const Op &left, const Op &right) { if (is_qpannihilator(left, vacuum_) && is_qpcreator(right, vacuum_)) { const auto qpspace_left = qpannihilator_space(left, vacuum_); const auto qpspace_right = qpcreator_space(right, vacuum_); - const auto qpspace_common = intersection(qpspace_left, qpspace_right); - if (qpspace_common != IndexSpace::null_instance()) return true; + const auto qpspace_common = + isr->intersection(qpspace_left, qpspace_right); + if (qpspace_common) return true; } return false; }; @@ -825,7 +845,7 @@ class NormalOperator : public Operator, std::size_t _auxiliary_rank() const override final { return 0; } Symmetry _symmetry() const override final { return (S == Statistics::FermiDirac - ? (get_default_context().spbasis() == SPBasis::spinorbital + ? (get_default_context(S).spbasis() == SPBasis::spinorbital ? Symmetry::antisymm : Symmetry::nonsymm) : (Symmetry::symm)); @@ -921,8 +941,18 @@ class NormalOperatorSequence : public container::svector>, using base_type::operator[]; /// constructs an empty sequence - NormalOperatorSequence() : vacuum_(get_default_context().vacuum()) {} + NormalOperatorSequence() : vacuum_(get_default_context(S).vacuum()) {} + + /// constructs from a parameter pack + template > && ...)>> + NormalOperatorSequence(NOps &&...operators) + : base_type({std::forward(operators)...}) { + check_vacuum(); + } + /// constructs from an initializer list NormalOperatorSequence(std::initializer_list> operators) : base_type(operators) { check_vacuum(); @@ -933,9 +963,9 @@ class NormalOperatorSequence : public container::svector>, operator const base_type &() const & { return *this; } operator base_type &&() && { return *this; } - /// @return the total number of Op objects in this - /// @warning not to be confused with NormalOperatorSequence::size() that - /// returns the number of NormalOperator objects + /// @return the total number of `Op` objects in this + /// @warning not to be confused with `NormalOperatorSequence::size()` that + /// returns the number of `NormalOperator` objects auto opsize() const { size_t opsz = 0; for (auto &&nop : *this) { @@ -1041,12 +1071,12 @@ struct OpIdRegistrar { /// converts NormalOperatorSequence to NormalOperator /// @tparam S Statistics -/// @param[in] opseq a NormalOperatorSequence object -/// @param[in] target_partner_indices ptr to sequence of Index pairs whose Op -/// will act on same particle, if possible; if null, will not be used +/// @param[in] opseq a `NormalOperatorSequence` object +/// @param[in] target_partner_indices ptr to sequence of Index pairs whose +/// `Op` will act on same particle, if possible; if null, will not be used /// @return @c {phase,normal_operator} , where @c phase is +1 or -1, and @c -/// normal_operator is a NormalOperator -/// @note will try to ensure that Op objects for each there is a pairs of +/// normal_operator is a `NormalOperator` object +/// @note will try to ensure that `Op` objects for each there is a pairs of /// Indices in @p target_index_columns will act on the same particle in the /// result template diff --git a/SeQuant/core/optimize.hpp b/SeQuant/core/optimize.hpp index b3a759a73..06bc49cce 100644 --- a/SeQuant/core/optimize.hpp +++ b/SeQuant/core/optimize.hpp @@ -342,12 +342,10 @@ container::vector> clusters(Sum const& expr); /// Sum reorder(Sum const& sum); -} // namespace opt - -///// -///// \param expr Expression to be optimized. -///// \param idxsz An invocable object that maps an Index object to size. -///// \return Optimized expression for lower evaluation cost. +/// +/// \param expr Expression to be optimized. +/// \param idxsz An invocable object that maps an Index object to size. +/// \return Optimized expression for lower evaluation cost. template >> @@ -367,6 +365,16 @@ ExprPtr optimize(ExprPtr const& expr, IdxToSize const& idx2size) { throw std::runtime_error{"Optimization attempted on unsupported Expr type"}; } +} // namespace opt + +/// +/// Optimize the expression using IndexSpace::aproximate_size() for reference +/// index extent. +/// +/// \param expr Expression to be optimized. +/// \return Optimized expression for lower evaluation cost. +ExprPtr optimize(ExprPtr const& expr); + } // namespace sequant #endif // SEQUANT_OPTIMIZE_OPTIMIZE_HPP diff --git a/SeQuant/core/optimize/fusion.cpp b/SeQuant/core/optimize/fusion.cpp index b0d7d29a2..23b62892e 100644 --- a/SeQuant/core/optimize/fusion.cpp +++ b/SeQuant/core/optimize/fusion.cpp @@ -1,4 +1,4 @@ -#include "fusion.hpp" +#include #include #include @@ -49,12 +49,13 @@ ExprPtr Fusion::fuse_left(Product const& lhs, Product const& rhs) { auto lsmand_prod = Product{lsmand.begin(), lsmand.end()}; auto rsmand_prod = Product{rsmand.begin(), rsmand.end()}; - if (lhs.scalar() == rhs.scalar()) - fac_prod.scale(lhs.scalar()); - else { - lsmand_prod.scale(lhs.scalar()); - rsmand_prod.scale(rhs.scalar()); - } + assert(lhs.scalar().imag().is_zero() && rhs.scalar().imag().is_zero() && + "Complex valued gcd not supported"); + auto scalars_fused = fuse_scalar(lhs.scalar().real(), rhs.scalar().real()); + + fac_prod.scale(scalars_fused.at(0)); + lsmand_prod.scale(scalars_fused.at(1)); + rsmand_prod.scale(scalars_fused.at(2)); // f (a + b) @@ -86,12 +87,13 @@ ExprPtr Fusion::fuse_right(Product const& lhs, Product const& rhs) { auto lsmand_prod = Product{lsmand.begin(), lsmand.end()}; auto rsmand_prod = Product{rsmand.begin(), rsmand.end()}; - if (lhs.scalar() == rhs.scalar()) - fac_prod.scale(lhs.scalar()); - else { - lsmand_prod.scale(lhs.scalar()); - rsmand_prod.scale(rhs.scalar()); - } + assert(lhs.scalar().imag().is_zero() && rhs.scalar().imag().is_zero() && + "Complex valued gcd not supported"); + auto scalars_fused = fuse_scalar(lhs.scalar().real(), rhs.scalar().real()); + + fac_prod.scale(scalars_fused.at(0)); + lsmand_prod.scale(scalars_fused.at(1)); + rsmand_prod.scale(scalars_fused.at(2)); // (a + b) f @@ -102,4 +104,29 @@ ExprPtr Fusion::fuse_right(Product const& lhs, Product const& rhs) { return ex(ExprPtrList{ex(ExprPtrList{a, b}), f}); } +rational Fusion::gcd_rational(rational const& left, rational const& right) { + auto&& r1 = left.real(); + auto&& r2 = right.real(); + auto&& n1 = numerator(r1); + auto&& d1 = denominator(r1); + auto&& n2 = numerator(r2); + auto&& d2 = denominator(r2); + + auto num = gcd(n1 * d2, n2 * d1); + return {num, d1 * d2}; +} + +std::array Fusion::fuse_scalar(rational const& left, + rational const& right) { + auto fused = gcd_rational(left, right); + rational left_fused = left / fused; + rational right_fused = right / fused; + if (left < 0 && right < 0) { + fused *= -1; + left_fused *= -1; + right_fused *= -1; + } + return {fused, left_fused, right_fused}; +} + } // namespace sequant::opt diff --git a/SeQuant/core/optimize/fusion.hpp b/SeQuant/core/optimize/fusion.hpp index 97c4cf869..210a9b120 100644 --- a/SeQuant/core/optimize/fusion.hpp +++ b/SeQuant/core/optimize/fusion.hpp @@ -36,6 +36,20 @@ class Fusion { static ExprPtr fuse_right(Product const& lhs, Product const& rhs); + /// + /// Get the greatest common divisor of two rational numbers. + /// + static rational gcd_rational(rational const& left, rational const& right); + + /// + /// Fuse scalars @param left and @param right and the return the result + /// as an array of three elements: first is the greatest common factor, + /// second the fused sub-factor of @param left and the third is that + /// of @param right. + /// + static std::array fuse_scalar(rational const& left, + rational const& right); + private: ExprPtr left_; diff --git a/SeQuant/core/optimize/optimize.cpp b/SeQuant/core/optimize/optimize.cpp index 1cb5708b9..86a7df5e2 100644 --- a/SeQuant/core/optimize/optimize.cpp +++ b/SeQuant/core/optimize/optimize.cpp @@ -157,4 +157,10 @@ Sum reorder(Sum const& sum) { } } // namespace opt + +ExprPtr optimize(ExprPtr const& expr) { + return opt::optimize( + expr, [](Index const& ix) { return ix.space().approximate_size(); }); +} + } // namespace sequant diff --git a/SeQuant/core/parse/ast.hpp b/SeQuant/core/parse/ast.hpp index 4c8f42063..2bf749278 100644 --- a/SeQuant/core/parse/ast.hpp +++ b/SeQuant/core/parse/ast.hpp @@ -87,11 +87,12 @@ using NullaryValue = boost::variant; struct Product : boost::spirit::x3::position_tagged { std::vector factors; - Product() = default; - explicit Product(std::vector factors); + Product() noexcept = default; template - explicit Product(T value) : factors({std::move(value)}) {} + Product(T value); + + Product(std::vector factors); // Required to use as a container using value_type = decltype(factors)::value_type; @@ -100,15 +101,22 @@ struct Product : boost::spirit::x3::position_tagged { struct Sum : boost::spirit::x3::position_tagged { std::vector summands; - Sum(decltype(summands) summands = {}) : summands(std::move(summands)) {} + Sum() noexcept = default; + + Sum(std::vector summands); // Required to use as a container using value_type = decltype(summands)::value_type; }; +template +Product::Product(T value) : factors({std::move(value)}) {} + Product::Product(std::vector factors) : factors(std::move(factors)) {} +Sum::Sum(std::vector summands) : summands(std::move(summands)) {} + } // namespace sequant::parse::ast BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::IndexLabel, label, id); diff --git a/SeQuant/core/parse/ast_conversions.hpp b/SeQuant/core/parse/ast_conversions.hpp index e0b6a50b0..4311dccdd 100644 --- a/SeQuant/core/parse/ast_conversions.hpp +++ b/SeQuant/core/parse/ast_conversions.hpp @@ -41,7 +41,8 @@ Index to_index(const parse::ast::Index &index, for (const parse::ast::IndexLabel ¤t : index.protoLabels) { try { std::wstring label = current.label + L"_" + std::to_wstring(current.id); - IndexSpace space = IndexSpace::instance(label); + IndexSpace space = + get_default_context().index_space_registry()->retrieve(label); protoIndices.push_back(Index(std::move(label), std::move(space))); } catch (const IndexSpace::bad_key &) { auto [offset, length] = get_pos(current, position_cache, begin); @@ -59,7 +60,8 @@ Index to_index(const parse::ast::Index &index, try { std::wstring label = index.label.label + L"_" + std::to_wstring(index.label.id); - IndexSpace space = IndexSpace::instance(label); + IndexSpace space = + get_default_context().index_space_registry()->retrieve(label); return Index(std::move(label), std::move(space), std::move(protoIndices)); } catch (const IndexSpace::bad_key &e) { auto [offset, length] = get_pos(index.label, position_cache, begin); diff --git a/SeQuant/core/parse/deparse.cpp b/SeQuant/core/parse/deparse.cpp index 97c597eae..63f901110 100644 --- a/SeQuant/core/parse/deparse.cpp +++ b/SeQuant/core/parse/deparse.cpp @@ -204,6 +204,8 @@ std::wstring deparse_expr(Sum const& sum, bool annot_sym) { } // namespace details std::wstring deparse_expr(ExprPtr expr, bool annot_sym) { + if (!expr) return {}; + using namespace details; if (expr->is()) return deparse_expr(expr->as(), annot_sym); diff --git a/SeQuant/core/parse/parse.cpp b/SeQuant/core/parse/parse.cpp index 2c91d5a56..c444706bb 100644 --- a/SeQuant/core/parse/parse.cpp +++ b/SeQuant/core/parse/parse.cpp @@ -2,13 +2,17 @@ // Created by Robert Adam on 2023-09-20 // -#include -#include #include #include #include #include + +#include +#include +#include +#include #include +#include #define BOOST_SPIRIT_X3_UNICODE #include @@ -16,15 +20,13 @@ #include #include -#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include +#include +#include namespace sequant { @@ -98,9 +100,6 @@ auto index_def = x3::lexeme[ index_label >> -('<' >> index_label % ',' >> ">") ]; -SEQUANT_PRAGMA_GCC(diagnostic push) -SEQUANT_PRAGMA_GCC(diagnostic ignored "-Wparentheses") - const std::vector noIndices; auto index_groups_def = L"_{" > -(index % ',') > L"}^{" > -(index % ',') > L"}" >> x3::attr(noIndices) >> x3::attr(false) | L"^{" > -(index % ',') > L"}_{" > -(index % ',') > L"}" >> x3::attr(noIndices) >> x3::attr(true) @@ -114,8 +113,6 @@ auto nullary = number | tensor | variable; auto grouped = '(' > sum > ')' | nullary; -SEQUANT_PRAGMA_GCC(diagnostic pop) - auto product_def = grouped % -x3::lit('*'); auto first_addend = (('-' >> x3::attr(-1) | -x3::lit('+') >> x3::attr(1)) >> product)[actions::process_addend{}]; diff --git a/SeQuant/core/runtime.hpp b/SeQuant/core/runtime.hpp index 9a8466759..6930aa87d 100644 --- a/SeQuant/core/runtime.hpp +++ b/SeQuant/core/runtime.hpp @@ -84,7 +84,7 @@ void parallel_do(Lambda&& lambda) { /// @c op(std::advance(begin(rng) + task_id)) where @c task_id is an integer in /// @c [0,size(rng)) . @c op(t1) will be commenced not /// after @c op(t2) if @c t1 void for_each(SizedRange& rng, const UnaryOp& op) { diff --git a/SeQuant/core/space.cpp b/SeQuant/core/space.cpp deleted file mode 100644 index f9c788782..000000000 --- a/SeQuant/core/space.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// -// Created by Eduard Valeyev on 3/27/18. -// - -#include -#include - -#include - -int32_t sequant::TypeAttr::used_bits = 0xffff; - -sequant::container::map - sequant::IndexSpace::attr2basekey_{}; -sequant::container::map - sequant::IndexSpace::key2attr_{}; -sequant::container::map - sequant::IndexSpace::instances_{}; -sequant::IndexSpace sequant::IndexSpace::null_instance_{ - sequant::IndexSpace::Attr::null()}; - -namespace sequant { - -std::wstring to_wolfram(const IndexSpace& space) { - std::wstring result = L"particleSpace["; - - // this is a hack due to partial representation of spaces in SeQuant - if (space.type() == IndexSpace::active_occupied) - result += L"occupied"; - else if (space.type() == IndexSpace::active_unoccupied) - result += L"virtual"; - else if (space.type() == IndexSpace::all) - result += L"occupied,virtual"; - else if (space.type() == IndexSpace::other_unoccupied) - result += L"othervirtual"; - else if (space.type() == IndexSpace::complete_unoccupied) - result += L"virtual,othervirtual"; - else if (space.type() == IndexSpace::complete) - result += L"occupied,virtual,othervirtual"; - else - throw std::invalid_argument( - "to_wolfram(IndexSpace) received a nonstandard space"); - - result += L"]"; - - return result; -} - -std::wstring to_wstring(TypeAttr type) { - using TypeBitset = - std::bitset; - if (type == IndexSpace::frozen_occupied) { - return L"frozen_occupied"; - } else if (type == IndexSpace::inactive_occupied) { - return L"inactive_occupied"; - } else if (type == IndexSpace::active_occupied) { - return L"active_occupied"; - } else if (type == IndexSpace::occupied) { - return L"occupied"; - } else if (type == IndexSpace::active) { - return L"active"; - } else if (type == IndexSpace::maybe_occupied) { - return L"maybe_occupied"; - } else if (type == IndexSpace::active_maybe_occupied) { - return L"active_maybe_occupied"; - } else if (type == IndexSpace::active_unoccupied) { - return L"active_unoccupied"; - } else if (type == IndexSpace::inactive_unoccupied) { - return L"inactive_unoccupied"; - } else if (type == IndexSpace::unoccupied) { - return L"unoccupied"; - } else if (type == IndexSpace::maybe_unoccupied) { - return L"maybe_unoccupied"; - } else if (type == IndexSpace::active_maybe_unoccupied) { - return L"active_maybe_unoccupied"; - } else if (type == IndexSpace::all_active) { - return L"all_active"; - } else if (type == IndexSpace::all) { - return L"all"; - } else if (type == IndexSpace::other_unoccupied) { - return L"other_unoccupied"; - } else if (type == IndexSpace::complete_unoccupied) { - return L"complete_unoccupied"; - } else if (type == IndexSpace::complete_maybe_unoccupied) { - return L"complete_maybe_unoccupied"; - } else if (type == IndexSpace::complete) { - return L"complete"; - } else { - std::wstring_convert> converter; - return std::wstring(L"Custom (") + - converter.from_bytes(TypeBitset(type.bitset).to_string()) + L")"; - } -} - -std::wstring to_wstring(QuantumNumbersAttr qns) { - using QNBitset = - std::bitset; - - if (qns == IndexSpace::alpha) { - return L"↑"; - } else if (qns == IndexSpace::beta) { - return L"↓"; - } else if (qns == IndexSpace::nullqns) { - return L""; - } else { - std::wstring_convert> converter; - return L"Custom (" + - converter.from_bytes(QNBitset(qns.bitset).to_string()) + L")"; - } -} - -} // namespace sequant diff --git a/SeQuant/core/space.hpp b/SeQuant/core/space.hpp index 5050471a8..cc928c65e 100644 --- a/SeQuant/core/space.hpp +++ b/SeQuant/core/space.hpp @@ -5,91 +5,226 @@ #ifndef SEQUANT_SPACE_H #define SEQUANT_SPACE_H +#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include +#include +#include #include namespace sequant { +namespace bitset { +using type = int32_t; +constexpr type reserved = 0x80000000; +constexpr type null = 0x00000000; +} // namespace bitset + +using bitset_t = bitset::type; + +class QuantumNumbersAttr; // used to constrain TypeAttr ctor + +class Index; // friend of TypeAttr + /// @brief TypeAttr denotes the type of index space. /// /// This class models a host (complete) space partitioned into disjoint /// subspaces. To simplify implementation of set operations /// (intersection, union, etc.) it is encoded as a fixed-width (32) bitset. -/// By default, all bits are used, but user can disable some bits from being -/// used for comparison -struct TypeAttr { - /// @brief used_bits is a bitset of bits that are used for comparison - static int32_t used_bits; - int32_t bitset = 0; +class TypeAttr { + public: + /// default ctor creates a null TypeAttr + constexpr TypeAttr() noexcept = default; - constexpr explicit TypeAttr(int32_t value) noexcept : bitset(value) {} + /// the null TypeAttr + const static TypeAttr null; - [[nodiscard]] constexpr explicit operator int64_t() const { - return static_cast(bitset); + /// @brief Constructs from a bitset representation + + /// @warning first (most significant) bit is reserved for internal use + /// @param bitset bitset representation of this Type + /// @pre `(bitset & make_reserved().bitset) == null.bitset` + explicit constexpr TypeAttr(bitset_t bitset) noexcept : bitset(bitset) { + assert((this->bitset & bitset::reserved) == bitset::null); } - [[nodiscard]] constexpr int32_t to_int32() const { return bitset; } - [[nodiscard]] int32_t to_int32_used() const { return used_bits & bitset; } - [[nodiscard]] constexpr TypeAttr intersection(TypeAttr other) const { - return TypeAttr(this->to_int32() & other.to_int32()); + + /// construct TypeAddr from things that can be cast to bitset_t, but exclude + /// bool and QuantumNumbersAttr + template , bitset_t> && + !std::is_same_v, bool> && + !std::is_same_v, QuantumNumbersAttr> && + !std::is_same_v, TypeAttr>>> + constexpr TypeAttr(T &&value) noexcept + : TypeAttr(static_cast(std::forward(value))) {} + + constexpr explicit operator int64_t() const { + return static_cast(bitset); } - [[nodiscard]] constexpr TypeAttr unIon(TypeAttr other) const { + constexpr explicit operator bitset_t() const { return bitset; } + constexpr int32_t to_int32() const { return bitset; } + + /// @return true if this object is non-null (i.e. has any bits set) + constexpr explicit operator bool() const { return bitset != 0; } + + constexpr TypeAttr(const TypeAttr &other) { bitset = other.to_int32(); } + + /// @return union of `*this` and @p other, i.e. `*this` AND @p other + /// @note equivalent to `this->to_int32() | other.to_int32()` + constexpr TypeAttr unIon(TypeAttr other) const { return TypeAttr(this->to_int32() | other.to_int32()); } - [[nodiscard]] friend bool operator==(TypeAttr lhs, TypeAttr rhs) { - return lhs.to_int32_used() == rhs.to_int32_used(); + /// @return union of @p a and @p b, i.e. @p a AND @p b + friend constexpr TypeAttr operator|(const TypeAttr a, const TypeAttr b) { + return a.unIon(b); + } + + /// @return `*this` XOR @p other + /// @note equivalent to `this->to_int32() ^ other.to_int32()` + constexpr const TypeAttr xOr(TypeAttr other) const { + return TypeAttr(this->to_int32() ^ other.to_int32()); } - [[nodiscard]] friend bool operator!=(TypeAttr lhs, TypeAttr rhs) { + /// @return @p a XOR @p b + friend constexpr TypeAttr operator^(const TypeAttr a, const TypeAttr b) { + return a.xOr(b); + } + + /// @return intersection of `*this` AND @p other + /// @note equivalent to `this->to_int32() & other.to_int32()` + constexpr const TypeAttr intersection(TypeAttr other) const { + return TypeAttr(this->to_int32() & other.to_int32()); + } + + /// @return intersection of @p a AND @p b + friend constexpr TypeAttr operator&(const TypeAttr a, const TypeAttr b) { + return a.intersection(b); + } + + /// @return complement of `*this` + /// @note equivalent to `~this->to_int32()` + constexpr TypeAttr operator~() const { return ~this->to_int32(); } + + friend constexpr bool operator==(const TypeAttr lhs, const TypeAttr rhs) { + return lhs.to_int32() == rhs.to_int32(); + } + friend constexpr bool operator!=(const TypeAttr lhs, const TypeAttr rhs) { return !(lhs == rhs); } /// @return true if \c other is included in this object - [[nodiscard]] bool includes(TypeAttr other) const { + constexpr bool includes(TypeAttr other) const { return intersection(other) == other; } /// @return true if in canonical order this object preceeds \c other - [[nodiscard]] bool operator<(TypeAttr other) const { - return this->to_int32_used() < other.to_int32_used(); + friend constexpr bool operator<(TypeAttr a, TypeAttr b) { + return a.to_int32() < b.to_int32(); } - /// @return an invalid TypeAttr - [[nodiscard]] static constexpr TypeAttr invalid() noexcept { - return TypeAttr(0xffff); + private: + bitset_t bitset = 0; + + friend class Index; + + /// first (most significant) bit reserved for creating default space used by + /// Index that is distinct from the null space + const static TypeAttr reserved; + + /// makes reserved object + static TypeAttr make_reserved() { + TypeAttr result; + result.bitset = 0x80000000; + return result; } -}; +}; // struct TypeAttr + +inline const TypeAttr TypeAttr::null; +inline const TypeAttr TypeAttr::reserved = TypeAttr::make_reserved(); /// denotes other quantum numbers (particle type, spin, etc.) -struct QuantumNumbersAttr { - int32_t bitset = 0; +class QuantumNumbersAttr { + public: + /// default ctor creates a null QuantumNumbersAttr + /// @post `static_cast(*this) == false` + constexpr QuantumNumbersAttr() noexcept = default; + + /// the null TypeAttr + const static QuantumNumbersAttr null; + + /// @brief Constructs from a bitset representation + + /// @warning first (most significant) bit is reserved for internal use + /// @param bitset bitset representation of this Type + /// @pre `(bitset & make_reserved().bitset()) == null.bitset()` + explicit constexpr QuantumNumbersAttr(bitset_t bitset) noexcept + : bitset(bitset) { + assert((this->bitset & bitset::reserved) == bitset::null); + } + + template , bitset_t> && + !std::is_same_v, bool> && + !std::is_same_v, TypeAttr> && + !std::is_same_v, QuantumNumbersAttr>>> + constexpr QuantumNumbersAttr(QN &&value) noexcept + : bitset(static_cast(std::forward(value))) {} - constexpr explicit QuantumNumbersAttr(int32_t value) noexcept - : bitset(value) {} constexpr explicit operator int64_t() const { return static_cast(bitset); } + constexpr explicit operator bitset_t() const { return bitset; } constexpr int32_t to_int32() const { return bitset; } - constexpr QuantumNumbersAttr intersection(QuantumNumbersAttr other) const { - return QuantumNumbersAttr(this->to_int32() & other.to_int32()); + + /// @return true if this object is non-null (i.e. has any bits set) + constexpr explicit operator bool() const { return bitset != 0; } + + /// @return `*this` XOR @p other + /// @note equivalent to `this->to_int32() ^ other.to_int32()` + constexpr QuantumNumbersAttr xOr(QuantumNumbersAttr other) const { + return QuantumNumbersAttr(this->to_int32() ^ other.to_int32()); } + + /// @return @p a XOR @p b + friend constexpr QuantumNumbersAttr operator^(const QuantumNumbersAttr a, + const QuantumNumbersAttr b) { + return a.xOr(b); + } + + /// @return union of `*this` and @p other, i.e. `*this` AND @p other + /// @note equivalent to `this->to_int32() | other.to_int32()` constexpr QuantumNumbersAttr unIon(QuantumNumbersAttr other) const { return QuantumNumbersAttr(this->to_int32() | other.to_int32()); } - constexpr QuantumNumbersAttr operator~() const { - return QuantumNumbersAttr(~this->to_int32()); + + /// @return union of @p a and @p b, i.e. @p a AND @p b + friend constexpr QuantumNumbersAttr operator|(const QuantumNumbersAttr a, + const QuantumNumbersAttr b) { + return a.unIon(b); + } + + /// @return intersection of `*this` AND @p other + /// @note equivalent to `this->to_int32() & other.to_int32()` + constexpr QuantumNumbersAttr intersection(QuantumNumbersAttr other) const { + return QuantumNumbersAttr(this->to_int32() & other.to_int32()); + } + + /// @return intersection of @p a AND @p b + friend constexpr QuantumNumbersAttr operator&(const QuantumNumbersAttr a, + const QuantumNumbersAttr b) { + return a.intersection(b); } + /// @return complement of `*this` + /// @note equivalent to `~this->to_int32()` + constexpr QuantumNumbersAttr operator~() const { return ~this->to_int32(); } + friend constexpr bool operator==(QuantumNumbersAttr lhs, QuantumNumbersAttr rhs) { return lhs.to_int32() == rhs.to_int32(); @@ -100,31 +235,47 @@ struct QuantumNumbersAttr { } /// @return true if \c other is included in this object - constexpr bool includes(QuantumNumbersAttr other) const { + bool includes(QuantumNumbersAttr other) const { return intersection(other) == other; } /// @return true if in canonical order this object preceeds \c other - constexpr bool operator<(QuantumNumbersAttr other) const { - return this->to_int32() < other.to_int32(); + friend constexpr bool operator<(QuantumNumbersAttr lhs, + QuantumNumbersAttr rhs) { + return lhs.to_int32() < rhs.to_int32(); } - /// @return an invalid TypeAttr - static constexpr QuantumNumbersAttr invalid() noexcept { - return QuantumNumbersAttr(-0); + private: + bitset_t bitset = bitset::null; + + friend class Index; + + /// first (most significant) bit reserved for creating default space used by + /// Index that is distinct from the null space + const static QuantumNumbersAttr reserved; + + /// makes reserved object + static QuantumNumbersAttr make_reserved() { + QuantumNumbersAttr result; + result.bitset = bitset::reserved; + return result; } -}; +}; // struct QuantumNumbersAttr -std::wstring to_wstring(TypeAttr type); -std::wstring to_wstring(QuantumNumbersAttr qns); +inline const QuantumNumbersAttr QuantumNumbersAttr::null; +inline const QuantumNumbersAttr QuantumNumbersAttr::reserved = + QuantumNumbersAttr::make_reserved(); -/// @brief space of Index objects +/// @brief a collection of attributes which define a space of (1-particle) +/// states /// -/// IndexSpace is a set of attributes associated 1-to-1 with keys +/// IndexSpace has a base_label, TypeAttr or interpretable bitset in the +/// context of other spaces. IndexSpace may also have QuantumNumberAttr which +/// differentiate spaces with different quanta, such as spin projection quantum +/// numbers. IndexSpace may additionally have an approximate extent which is +/// useful in symbolic manipulation of indexed expressions, such as +/// tensor network evaluation. class IndexSpace { public: - using TypeAttr = sequant::TypeAttr; - using QuantumNumbersAttr = sequant::QuantumNumbersAttr; - /// @brief Attr describes all attributes of a space (occupancy + quantum /// numbers) struct Attr : TypeAttr, QuantumNumbersAttr { @@ -132,37 +283,58 @@ class IndexSpace { : TypeAttr(type), QuantumNumbersAttr(qns){}; Attr(int32_t type, int32_t qns) noexcept : TypeAttr(type), QuantumNumbersAttr(qns){}; - // explicit Attr(int64_t value) : TypeAttr((value & 0xffffffff00000000) - // >> 32), QuantumNumbersAttr(value & 0x00000000ffffffff) {} + + /// @brief default ctor creates a null Attr + /// @post `static_cast(*this) == false` + Attr() = default; Attr(const Attr &) = default; Attr(Attr &&) = default; Attr &operator=(const Attr &) = default; Attr &operator=(Attr &&) = default; - const TypeAttr &type() const { + const static Attr null; + + constexpr const TypeAttr &type() const { return static_cast(*this); } - TypeAttr &type() { return static_cast(*this); } - const QuantumNumbersAttr &qns() const { + constexpr TypeAttr &type() { return static_cast(*this); } + constexpr const QuantumNumbersAttr &qns() const { return static_cast(*this); } - QuantumNumbersAttr &qns() { + constexpr QuantumNumbersAttr &qns() { return static_cast(*this); } - explicit operator int64_t() const { + constexpr explicit operator int64_t() const { return (static_cast(this->type()) << 32) + static_cast(this->qns()); } + /// @return true if either `type()` or `qns()` is non-null + explicit operator bool() const { + return static_cast(this->type()) || static_cast(this->qns()); + } + + /// union of Attr = union of TypeAttr and union of QuantumNumbersAttr + /// @return union of `*this` and @p other, i.e. `*this` AND @p other + Attr unIon(Attr other) const { + return {this->type().unIon(other.type()), this->qns().unIon(other.qns())}; + } + + /// @return union of @p a and @p b + /// @sa Attr::unIon + friend Attr operator|(Attr a, Attr b) { return a.unIon(b); } + + /// intersection of Attr = intersection of TypeAttr and intersection of + /// QuantumNumbersAttr + /// @return intersection of `*this` AND @p other Attr intersection(Attr other) const { return Attr(this->type().intersection(other.type()), this->qns().intersection(other.qns())); } - Attr unIon(Attr other) const { - return Attr(this->type().unIon(other.type()), - this->qns().unIon(other.qns())); - } + /// @return intersection of @p a and @p b + /// @sa Attr::intersection + friend Attr operator&(Attr a, Attr b) { return a.intersection(b); } /// @return true if \p other is included in this object bool includes(Attr other) const { @@ -170,166 +342,51 @@ class IndexSpace { this->qns().includes(other.qns()); } - friend bool operator==(Attr a, Attr b) { - return a.type() == b.type() && a.qns() == b.qns(); + constexpr bool operator==(Attr other) const { + return this->type() == other.type() && this->qns() == other.qns(); } - friend bool operator!=(Attr a, Attr b) { return !(a == b); } - - static Attr null() noexcept { return Attr{nulltype, nullqns}; } - static Attr invalid() noexcept { - return Attr{TypeAttr::invalid(), QuantumNumbersAttr::invalid()}; - } - bool is_valid() const noexcept { return *this != Attr::invalid(); } + constexpr bool operator!=(Attr other) const { return !(*this == other); } /// Attr objects are ordered by quantum numbers, then by type - bool operator<(Attr other) const { + constexpr bool operator<(Attr other) const { if (this->qns() == other.qns()) { return this->type() < other.type(); } else { return this->qns() < other.qns(); } } - }; + }; // struct Attr + using Type = TypeAttr; using QuantumNumbers = QuantumNumbersAttr; - /// \name default space tags - - /// @{ - // clang-format off - /// null space (empty subset), needed to define intersection operation - static constexpr Type nulltype{0}; - /// represents any space, standard (see below) or otherwise - static constexpr Type nonnulltype{0x7fffffff}; - /// @} - - /// \name standard space tags - - /// standard space tags are predefined that helps implement set theory of - /// standard spaces as binary ops on bitsets - /// @{ - // clang-format off - /// space of sp states that are fully occupied (i.e., non-correlated) in the reference (vacuum) state and are "frozen" in their reference form - static constexpr Type frozen_occupied{0b0000001}; - /// space of sp states that are fully occupied (i.e., non-correlated) in the reference (vacuum) state but - /// can be rotated by mixing with the rest of non-frozen orbitals - static constexpr Type inactive_occupied{0b0000010}; - /// space of sp states that are fully occupied (i.e., non-correlated) in the reference (vacuum) state but - /// can be correlated and rotated by mixing with the rest of non-frozen orbitals - static constexpr Type active_occupied{0b0000100}; - /// space of sp states that are partially occupied (i.e., correlated, or open shells in spin-free single-determinant reference) in the reference (vacuum) state; - static constexpr Type active{0b0001000}; - /// space of sp states that are fully occupied in the reference (vacuum) state - /// @note this is the union of IndexSpace::frozen_occupied , IndexSpace::inactive_occupied , IndexSpace::active_occupied - static constexpr Type occupied = IndexSpace::frozen_occupied.unIon(IndexSpace::inactive_occupied).unIon(IndexSpace::active_occupied); - /// space of sp states that are fully or partially occupied in the reference (vacuum) state - /// @note this is the union of IndexSpace::occupied and IndexSpace::active - static constexpr Type maybe_occupied = IndexSpace::occupied.unIon(IndexSpace::active); - /// space of sp states that are fully or partially occupied in the reference (vacuum) state and can be correlated - /// @note this is the union of IndexSpace::active_occupied and IndexSpace::active - static constexpr Type active_maybe_occupied = IndexSpace::active_occupied.unIon(IndexSpace::active); - /// space of sp states that are not used to define the reference (vacuum) state (i.e., they are unoccupied) but - /// can be correlated and rotated by mixing with the rest of non-frozen orbitals - /// @note unlike IndexSpace::other_unoccupied, these states are supported by a finite computational basis - static constexpr Type active_unoccupied{0b0010000}; - /// space of sp states that are not used to define the reference (vacuum) state (i.e., they are unoccupied) but - /// can be rotated by mixing with the rest of non-frozen orbitals - /// @note unlike IndexSpace::other_unoccupied, these states are supported by a finite computational basis - static constexpr Type inactive_unoccupied{0b0100000}; - /// space of sp states that are fully unoccupied in the reference (vacuum) state - /// @note this is the union of IndexSpace::inactive_unoccupied and IndexSpace::active_unoccupied - /// @note unlike IndexSpace::other_unoccupied, these states are supported by a finite computational basis - static constexpr Type unoccupied = IndexSpace::active_unoccupied.unIon(IndexSpace::inactive_unoccupied); - /// space of sp states that are fully or partially unoccupied in the reference (vacuum) state - /// @note this is the union of IndexSpace::unoccupied and IndexSpace::active - /// @note unlike IndexSpace::other_unoccupied, these states are supported by a finite computational basis - static constexpr Type maybe_unoccupied = IndexSpace::unoccupied.unIon(IndexSpace::active); - /// space of sp states that are fully or partially unoccupied in the reference (vacuum) state and can be correlated - /// @note this is the union of IndexSpace::active_unoccupied and IndexSpace::active - static constexpr Type active_maybe_unoccupied = IndexSpace::active_unoccupied.unIon(IndexSpace::active); - /// space of sp states that can be correlated - /// @note this is the union of IndexSpace::active_occupied , IndexSpace::active_unoccupied and IndexSpace::active - static constexpr Type all_active = IndexSpace::active_occupied.unIon(IndexSpace::active_unoccupied).unIon(IndexSpace::active); - /// space of sp states represented in computational basis - /// @note this is the union of IndexSpace::maybe_occupied and IndexSpace::maybe_unoccupied - static constexpr Type all = IndexSpace::maybe_occupied.unIon(IndexSpace::maybe_unoccupied); - /// space of sp states that are not used to define the reference (vacuum) state (i.e., they are unoccupied) and not supported - /// by a supported by a finite computational basis; i.e., these states are the rest of the sp Hilbert space - static constexpr Type other_unoccupied{0b1000000}; - /// a union of the IndexSpace::other_unoccupied space and IndexSpace::inactive_unoccupied space - /// @note useful when treating active and inactive unoccupieds differently in e.g. valence-correlated F12 theory - static constexpr Type complete_inactive_unoccupied = IndexSpace::other_unoccupied.unIon(IndexSpace::inactive_unoccupied); - /// set of all fully unoccupied states - /// @note this is a union of IndexSpace::unoccupied and IndexSpace::other_unoccupied - static constexpr Type complete_unoccupied = IndexSpace::unoccupied.unIon(IndexSpace::other_unoccupied); - /// set of arbitrary fully or partially unoccupied states -/// @note this is a union of IndexSpace::complete_unoccupied and IndexSpace::active - static constexpr Type complete_maybe_unoccupied = IndexSpace::complete_unoccupied.unIon(IndexSpace::active); - /// union of all previous spaces - /// @note this is a union of IndexSpace::all and IndexSpace::other_unoccupied - static constexpr Type complete = IndexSpace::all.unIon(IndexSpace::other_unoccupied); - // clang-format on - - /// list of all standard types - static constexpr Type standard_types[] = {frozen_occupied, - inactive_occupied, - active_occupied, - occupied, - active, - maybe_occupied, - active_maybe_occupied, - active_unoccupied, - inactive_unoccupied, - unoccupied, - maybe_unoccupied, - active_maybe_unoccupied, - all_active, - all, - other_unoccupied, - complete_inactive_unoccupied, - complete_unoccupied, - complete_maybe_unoccupied, - complete}; - - template - static constexpr bool is_standard_type() { - return ranges::any_of(standard_types, - [](const auto t) { return t == Type{typeint}; }); - } - /// @} - - /// \name standard quantum numbers tags - /// \note spin quantum number takes 2 rightmost bits - /// @{ - /// no quantum numbers - constexpr static QuantumNumbers nullqns{0b000000}; - /// spin-up - constexpr static QuantumNumbers alpha{0b000001}; - /// spin-down - constexpr static QuantumNumbers beta{0b000010}; - /// spin mask - constexpr static QuantumNumbers spinmask{0b000011}; - - /// list of all standard quantum numbers - static constexpr QuantumNumbers standard_qns[] = {nullqns, alpha, beta}; - - template - static const constexpr bool is_standard_qns() { - return ranges::any_of( - standard_qns, [](const auto t) { return t == QuantumNumbers{qnsint}; }); - } - /// @} - + /// exception type thrown when ancountered unknown/invalid + /// IndexSpace::base_key() or Index::label() struct bad_key : std::invalid_argument { bad_key() : std::invalid_argument("bad key") {} - }; - struct bad_attr : std::invalid_argument { - bad_attr() : std::invalid_argument("bad attribute") {} - using std::invalid_argument::invalid_argument; + template > + bad_key(S &&key) + : std::invalid_argument(std::string("bad key: ") + + sequant::to_string(key)) {} }; struct KeyCompare { using is_transparent = void; + bool operator()(const IndexSpace &a, const IndexSpace &b) const { + return a.base_key() < b.base_key(); + } + bool operator()(const std::wstring &a, const IndexSpace &b) const { + return a < b.base_key(); + } + bool operator()(const std::wstring_view &a, const IndexSpace &b) const { + return a < b.base_key(); + } + bool operator()(const IndexSpace &a, const std::wstring &b) const { + return a.base_key() < b; + } + bool operator()(const IndexSpace &a, const std::wstring_view &b) const { + return a.base_key() < b; + } bool operator()(const std::wstring &a, const std::wstring &b) const { return a < b; } @@ -341,266 +398,75 @@ class IndexSpace { } }; - /// IndexSpace needs null IndexSpace - static const IndexSpace &null_instance() { return null_instance_; } - /// the null IndexSpace is keyed by this key - static std::wstring null_key() { return L""; } - - /// @brief returns the instance of an IndexSpace object - /// @param attr the space attribute - /// @throw bad_attr if \p attr has not been registered - static const IndexSpace &instance(Attr attr) { - assert(attr.is_valid()); - if (attr == Attr::null()) return null_instance(); - if (!instance_exists(attr)) throw bad_attr(); - return instances_.find(attr)->second; - } + friend constexpr bool operator==(IndexSpace const &, + IndexSpace const &) noexcept; + friend constexpr bool operator!=(IndexSpace const &, + IndexSpace const &) noexcept; + friend constexpr bool operator<(IndexSpace const &, + IndexSpace const &) noexcept; - /// @brief returns the instance of an IndexSpace object - /// @param type the type attribute - /// @param qns the quantum numbers attribute - /// @throw bad_key if key not found - static const IndexSpace &instance(Type type, QuantumNumbers qns = nullqns) { - const auto attr = Attr(type, qns); - assert(attr.is_valid()); - if (attr == Attr::null()) return null_instance(); - if (!instance_exists(attr)) { - std::wstring_convert> converter; - throw bad_attr(converter.to_bytes(L"Request to non-registered space: " + - sequant::to_wstring(type) + L" " + - sequant::to_wstring(qns))); - } - return instances_.find(attr)->second; - } + constexpr Attr attr() const noexcept { return attr_; } + constexpr Type type() const noexcept { return attr().type(); } + QuantumNumbers qns() const noexcept { return attr().qns(); } - /// @brief returns the instance of an IndexSpace object associated - /// with the given key - /// @param key the key associated with this space; this can be either - /// the base key used to invoke `IndexSpace::register_instance()` - /// or a key used to invoke `IndexSpace::register_key()` - /// @throw bad_key if key not found - static const IndexSpace &instance(const std::wstring_view key) { - if (key == null_key()) return null_instance(); - const auto attr = to_attr(reduce_key(key)); - assert(attr.is_valid()); - if (!instance_exists(attr)) throw bad_key(); - return instances_.find(attr)->second; - } + /// Default ctor creates null space (with null label, type and quantum + /// numbers) + IndexSpace() noexcept {} - /// @brief constructs a registered instance of an IndexSpace object, - /// associates it with a base key - /// @param base_key string key that will be used as the "base key" for this - /// particular space, i.e. the default used for example for - /// constructing temporary indices for this space - /// @param type the IndexSpace::Type attribute to associate \p base_key with - /// @param qns the IndexSpace::QuantumNumbers attribute to associate \p - /// base_key with - /// @param throw_if_already_registered if true, throws an exception if \p - /// base_key has already been registered - /// @throw bad_key if \p base_key has already been registered - /// and \p throw_if_already_registered is true - static void register_instance(const std::wstring_view base_key, Type type, - QuantumNumbers qn = nullqns, - bool throw_if_already_registered = true) { - const auto attr = Attr(type, qn); - assert(attr.is_valid()); - if (instance_exists(attr) && throw_if_already_registered) throw bad_key(); - const auto irreducible_basekey = reduce_key(base_key); - const auto irreducible_basekey_str = to_wstring(irreducible_basekey); - attr2basekey_[attr] = irreducible_basekey_str; - key2attr_.emplace(irreducible_basekey_str, attr); - instances_.emplace(std::make_pair(attr, IndexSpace(attr))); - } + const static IndexSpace null; - static bool instance_exists(std::wstring_view key) noexcept { - return instance_exists(to_attr(reduce_key(key))); - } + explicit operator bool() const { return *this != null; } - /// @brief associate a given key with the IndexSpace - /// @note every IndexSpace constructed via - /// `register_instance(base_key,...)` is associated - /// with `base_key`; this allows to associated additional - /// keys to map to the same IndexSpace - /// @param key string key that will map to this particular space - static void register_key(const std::wstring_view key, Type type, - QuantumNumbers qn = nullqns, - bool throw_if_already_registered = true) { - const auto attr = Attr(type, qn); - assert(attr.is_valid()); - const auto irreducible_key = reduce_key(key); - const auto irreducible_key_str = to_wstring(irreducible_key); - if (key2attr_.find(irreducible_key_str) != key2attr_.end() && - throw_if_already_registered) - throw bad_key(); - key2attr_.emplace(irreducible_key_str, attr); - } + template > + IndexSpace(S &&type_label, TypeAttr typeattr, + QuantumNumbersAttr qnattr = QuantumNumbersAttr{0}, + unsigned long approximate_size = 10) + : attr_(typeattr, qnattr), + base_key_(sequant::to_wstring(std::forward(type_label))), + approximate_size_(approximate_size) {} - Attr attr() const noexcept { - assert(attr_.is_valid()); - return attr_; - } - Type type() const noexcept { return attr().type(); } - QuantumNumbers qns() const noexcept { return attr().qns(); } + IndexSpace(const IndexSpace &other) = default; + IndexSpace(IndexSpace &&other) = default; + IndexSpace &operator=(const IndexSpace &other) = default; + IndexSpace &operator=(IndexSpace &&other) = default; - /// @brief returns the base key for IndexSpace objects - /// @param space an IndexSpace object - /// @throw bad_key if this space has not been registered - static std::wstring base_key(const IndexSpace &space) { - return base_key(space.attr()); + const std::wstring &base_key() const { return base_key_; } + static std::wstring_view reduce_key(std::wstring_view key) { + const auto underscore_position = key.rfind(L'_'); + if (underscore_position != std::wstring::npos) { // key can be reduced + return key.substr(0, underscore_position); + } else { + return key; + } } - - /// @brief returns the base key for IndexSpace objects of the given attribute - /// @param attr the space attribute - /// @throw bad_attr if \p attr has not been registered - static std::wstring base_key(Attr attr) { - assert(attr.is_valid()); - if (attr == Attr::null()) return L""; - if (!instance_exists(attr)) throw bad_attr(); - return attr2basekey_.find(attr)->second; + static std::wstring reduce_key(std::string_view key) { + const auto underscore_position = key.rfind(L'_'); + if (underscore_position != std::string::npos) { // key can be reduced + return sequant::to_wstring(key.substr(0, underscore_position)); + } else { + return sequant::to_wstring(key); + } } - /// Default ctor creates space with nonnull type and null quantum numbers - IndexSpace() noexcept : attr_(nonnulltype, nullqns) {} + /// @return approximate size of a space + std::size_t approximate_size() const { return approximate_size_; } - IndexSpace(const IndexSpace &other) { - if (!other.attr().is_valid()) - throw std::invalid_argument( - "IndexSpace copy ctor received invalid argument"); - attr_ = other.attr_; - } - IndexSpace(IndexSpace &&other) { - if (!other.attr().is_valid()) - throw std::invalid_argument( - "IndexSpace move ctor received invalid argument"); - attr_ = other.attr_; - } - IndexSpace &operator=(const IndexSpace &other) { - if (!other.attr().is_valid()) - throw std::invalid_argument( - "IndexSpace copy assignment operator received invalid argument"); - attr_ = other.attr_; - return *this; - } - IndexSpace &operator=(IndexSpace &&other) { - if (!other.attr().is_valid()) - throw std::invalid_argument( - "IndexSpace move assignment operator received invalid argument"); - attr_ = other.attr_; - return *this; - } - - /// @brief constructs an instance of an IndexSpace object - IndexSpace(Type type, QuantumNumbers qns) noexcept : attr_(type, qns) { - assert(attr_.is_valid()); - } + /// Set the approximate size of a space. + void approximate_size(size_t n) { approximate_size_ = n; } private: - Attr attr_ = Attr::invalid(); - /// @brief constructs an instance of an IndexSpace object - explicit IndexSpace(Attr attr) noexcept : attr_(attr) { - assert(attr.is_valid()); - } - - static container::map attr2basekey_; - static container::map key2attr_; - static container::map instances_; - static IndexSpace null_instance_; - - static std::wstring_view reduce_key(std::wstring_view key) { - const auto underscore_position = key.rfind(L'_'); - return key.substr(0, underscore_position); - } - - /// @param key the key associated with a registered IndexSpace; this can be - /// either - /// the base key used to invoke `IndexSpace::register_instance()` - /// or a key used to invoke `IndexSpace::register_key()` - /// @return the attribute of the IndexSpace object corresponding to @p key - /// @throw bad_key if \p key has not been registered - static Attr to_attr(std::wstring_view key) { - const auto found_it = key2attr_.find(key); - if (found_it != key2attr_.end()) return found_it->second; - throw bad_key(); - } + Attr attr_; + std::wstring base_key_; + std::size_t approximate_size_; static std::wstring to_wstring(std::wstring_view key) { return std::wstring(key.begin(), key.end()); } +}; // class IndexSpace - static bool instance_exists(Attr attr) { - return instances_.find(attr) != instances_.end(); - } -}; +inline const IndexSpace IndexSpace::null; +inline const IndexSpace::Attr IndexSpace::Attr::null; -inline bool operator==(const IndexSpace &space, IndexSpace::Type t) { - return space.type() == t; -} -inline bool operator==(IndexSpace::Type t, const IndexSpace &space) { - return space.type() == t; -} -inline bool operator!=(const IndexSpace &space, IndexSpace::Type t) { - return !(space == t); -} -inline bool operator!=(IndexSpace::Type t, const IndexSpace &space) { - return !(t == space); -} -inline bool operator==(const IndexSpace &space, - IndexSpace::QuantumNumbers qns) { - return space.qns() == qns; -} -inline bool operator==(IndexSpace::QuantumNumbers qns, - const IndexSpace &space) { - return space.qns() == qns; -} -inline bool operator!=(const IndexSpace &space, - IndexSpace::QuantumNumbers qns) { - return !(space == qns); -} -inline bool operator!=(IndexSpace::QuantumNumbers qns, - const IndexSpace &space) { - return !(qns == space); -} -inline bool operator==(const IndexSpace &space1, const IndexSpace &space2) { - return space1.type() == space2.type() && space1.qns() == space2.qns(); -} -inline bool operator!=(const IndexSpace &space1, const IndexSpace &space2) { - return !(space1 == space2); -} -inline IndexSpace::Type intersection(IndexSpace::Type type1, - IndexSpace::Type type2) { - return type1 == type2 ? type1 : type1.intersection(type2); -} -inline IndexSpace::QuantumNumbers intersection(IndexSpace::QuantumNumbers v1, - IndexSpace::QuantumNumbers v2) { - return v1 == v2 ? v1 : v1.intersection(v2); -} -inline const IndexSpace &intersection(const IndexSpace &space1, - const IndexSpace &space2) { - return space1 == space2 - ? space1 - : IndexSpace::instance(space1.attr().intersection(space2.attr())); -} -inline const IndexSpace &intersection(const IndexSpace &space1, - const IndexSpace &space2, - const IndexSpace &space3) { - return space1 == space2 && space1 == space3 - ? space1 - : IndexSpace::instance(space1.attr().intersection( - space2.attr().intersection(space3.attr()))); -} -inline IndexSpace::Type unIon(IndexSpace::Type type1, IndexSpace::Type type2) { - return type1 == type2 ? type1 : type1.unIon(type2); -} -inline IndexSpace::QuantumNumbers unIon(IndexSpace::QuantumNumbers qns1, - IndexSpace::QuantumNumbers qns2) { - return qns1 == qns2 ? qns1 : qns1.unIon(qns2); -} -inline const IndexSpace &unIon(const IndexSpace &space1, - const IndexSpace &space2) { - return space1 == space2 - ? space1 - : IndexSpace::instance(space1.attr().unIon(space2.attr())); -} /// @return true if type2 is included in type1, i.e. intersection(type1, type2) /// == type2 inline bool includes(IndexSpace::Type type1, IndexSpace::Type type2) { @@ -620,29 +486,29 @@ inline bool includes(const IndexSpace &space1, const IndexSpace &space2) { /// IndexSpace are ordered by their attributes (i.e. labels do not matter one /// bit) -inline bool operator<(const IndexSpace &space1, const IndexSpace &space2) { +[[nodiscard]] inline constexpr bool operator<( + const IndexSpace &space1, const IndexSpace &space2) noexcept { return space1.attr() < space2.attr(); } -/// @return -1 if @c space only include orbitals with complete occupancy, +1 if -/// it includes no orbitals with complete occupancy, -/// and 0 of it includes some orbitals with with complete occupancy. -inline int occupancy_class(const IndexSpace &space) { - const auto included_in_occupied = - includes(IndexSpace::occupied, space.type()); - const auto included_in_unoccupied = - includes(IndexSpace::complete_maybe_unoccupied, space.type()); - assert(!(included_in_occupied && included_in_unoccupied)); - if (included_in_occupied && !included_in_unoccupied) - return -1; - else if (!included_in_occupied && !included_in_unoccupied) - return 0; - else if (!included_in_occupied && included_in_unoccupied) - return 1; - abort(); // unreachable +/// +/// IndexSpace are equal if they have equal @c IndexSpace::type(), +/// @c IndexSpace::qns(), and @c IndexSpace::base_key(). +/// +[[nodiscard]] inline constexpr bool operator==( + IndexSpace const &space1, IndexSpace const &space2) noexcept { + return space1.type() == space2.type() && space1.qns() == space2.qns() && + space1.base_key() == space2.base_key(); } -std::wstring to_wolfram(const IndexSpace &space); +/// +/// IndexSpace are equal if they have equal @c IndexSpace::type(), +/// @c IndexSpace::qns(), and @c IndexSpace::base_key(). +/// +[[nodiscard]] inline constexpr bool operator!=( + IndexSpace const &space1, IndexSpace const &space2) noexcept { + return !(space1 == space2); +} } // namespace sequant diff --git a/SeQuant/core/tag.hpp b/SeQuant/core/tag.hpp index 1b92a8371..effaec5cf 100644 --- a/SeQuant/core/tag.hpp +++ b/SeQuant/core/tag.hpp @@ -241,11 +241,16 @@ class Taggable { Taggable() noexcept : tag_{} { assert(!has_value()); } /// tags this object with tag @c t + /// @param t tag to assign + /// @return reference to this (for chaining) + /// @pre `!this->has_value()` + /// @post `this->value() == t` template - void assign(const T &t) const { + const Taggable &assign(const T &t) const { assert(!tag_.has_value()); tag_ = t; assert(tag_.has_value()); + return *this; } /// @return this tag's value @@ -262,7 +267,11 @@ class Taggable { bool has_value() const { return tag_.has_value(); } /// resets this tag - void reset() const { tag_.reset(); } + /// @return reference to this (for chaining) + const Taggable &reset() const { + tag_.reset(); + return *this; + } private: mutable any_comparable tag_; diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index cad634f79..5fc502d2e 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -113,8 +113,9 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// to indices) /// @param auxiliary_indices list of auxiliary indices (or objects that can be /// converted to indices) - /// @param symmetry the symmetry of bra or ket - /// @param braket_symmetry the symmetry with respect to bra-ket exchange + /// @param s the symmetry of bra or ket + /// @param bks the symmetry with respect to bra-ket exchange + /// @param ps the symmetry under exchange of particles template > && @@ -143,8 +144,9 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// to indices) /// @param auxiliary_indices list of auxiliary indices (or objects that can be /// converted to indices) - /// @param symmetry the symmetry of bra or ket - /// @param braket_symmetry the symmetry with respect to bra-ket exchange + /// @param s the symmetry of bra or ket + /// @param bks the symmetry with respect to bra-ket exchange + /// @param ps the symmetry under exchange of particles template Tensor(std::wstring_view label, std::initializer_list bra_indices, std::initializer_list ket_indices, @@ -181,7 +183,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { auto const_indices() const { return this->indices(); } /// Returns the Symmetry object describing the symmetry of the bra and ket of /// the Tensor, i.e. what effect swapping indices in positions @c i and @c j - /// in either bra or ket has on the elements of the Tensor; + /// in either bra or ket has on the elements of the Tensor; /// Tensor's are always assumed to be particle-symmetric, i.e. /// swapping indices in positions @c i and @c j in both bra and ket; /// The allowed values are Symmetry::symm, Symmetry::antisymm, and @@ -243,27 +245,6 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { return result; } - std::wstring to_wolfram() const override { - std::wstring result; - result = L"SQM[OHead[\"\\!\\(\\*OverscriptBox[\\("; - result += this->label(); - result += L"\\), \\(_\\)]\\)\","; - result += sequant::to_wolfram(this->symmetry()); - result += L"],"; - for (const auto &i : this->ket()) { - result += i.to_wolfram(BraKetPos::ket) + L","; - } - for (const auto &i : this->bra()) { - result += i.to_wolfram(BraKetPos::bra) + L","; - } - for (const auto &i : this->auxiliary()) { - result += i.to_wolfram(BraKetPos::none) + L","; - } - result = result.erase(result.size() - 1); - result += L"]"; - return result; - } - ExprPtr canonicalize() override; /// @brief adjoint of a Tensor swaps its bra and ket diff --git a/SeQuant/core/utility/indices.hpp b/SeQuant/core/utility/indices.hpp index d3ced0135..322943c79 100644 --- a/SeQuant/core/utility/indices.hpp +++ b/SeQuant/core/utility/indices.hpp @@ -1,17 +1,18 @@ -// -// Created by Robert Adam on 2023-09-27 -// - #ifndef SEQUANT_CORE_UTILITY_INDICES_HPP #define SEQUANT_CORE_UTILITY_INDICES_HPP #include #include #include +#include +#include +#include #include #include #include +#include +#include namespace sequant { @@ -27,38 +28,204 @@ struct not_in { return std::find(range.begin(), range.end(), element) == range.end(); } }; + +/// This function is equal to std::remove in case the given container +/// contains none or only a single occurrence of the given element. +/// If the given element is contained multiple times, only the first +/// occurrence is removed. +/// If it is known that there is only a single occurrence of the given +/// element, this function can be more efficient than std::remove as it +/// can terminate as soon as the element has been found instead of having +/// to traverse the entire container. +template +void remove_one(Container& container, const Element& e) { + auto iter = std::find(container.begin(), container.end(), e); + + if (iter != container.end()) { + container.erase(iter); + } +} + } // namespace detail +/// A composite type for holding different named groups of indices +template > +struct IndexGroups { + Container bra; + Container ket; + Container aux; + + bool operator==(const IndexGroups& other) const { + return bra == other.bra && ket == other.ket && aux == other.aux; + } + + bool operator!=(const IndexGroups& other) const { + return !(*this == other); + } +}; + +template > +IndexGroups get_unique_indices(const ExprPtr& expr); + +template > +IndexGroups get_unique_indices(const Constant&) { + return {}; +} + +template > +IndexGroups get_unique_indices(const Variable&) { + return {}; +} + /// @returns Lists of non-contracted indices arising when contracting the two /// given tensors in the order bra, ket, auxiliary template > -std::tuple get_uncontracted_indices( +IndexGroups get_uncontracted_indices( const Tensor &t1, const Tensor &t2) { static_assert(std::is_same_v); - Container bra; - Container ket; - Container auxiliary; + IndexGroups groups; // Bra indices - std::copy_if(t1.bra().begin(), t1.bra().end(), std::back_inserter(bra), + std::copy_if(t1.bra().begin(), t1.bra().end(), std::back_inserter(groups.bra), detail::not_in{t2.ket()}); - std::copy_if(t2.bra().begin(), t2.bra().end(), std::back_inserter(bra), + std::copy_if(t2.bra().begin(), t2.bra().end(), std::back_inserter(groups.bra), detail::not_in{t1.ket()}); // Ket indices - std::copy_if(t1.ket().begin(), t1.ket().end(), std::back_inserter(ket), + std::copy_if(t1.ket().begin(), t1.ket().end(), std::back_inserter(groups.ket), detail::not_in{t2.bra()}); - std::copy_if(t2.ket().begin(), t2.ket().end(), std::back_inserter(ket), + std::copy_if(t2.ket().begin(), t2.ket().end(), std::back_inserter(groups.ket), detail::not_in{t1.bra()}); // Auxiliary indices std::copy_if(t1.auxiliary().begin(), t1.auxiliary().end(), - std::back_inserter(auxiliary), detail::not_in{t2.auxiliary()}); + std::back_inserter(groups.aux), detail::not_in{t2.auxiliary()}); std::copy_if(t2.auxiliary().begin(), t2.auxiliary().end(), - std::back_inserter(auxiliary), detail::not_in{t1.auxiliary()}); + std::back_inserter(groups.aux), detail::not_in{t1.auxiliary()}); - return {std::move(bra), std::move(ket), std::move(auxiliary)}; + return groups; +} + + +/// Obtains the set of unique (non-repeated) indices used in the given tensor +template > +IndexGroups get_unique_indices(const Tensor& tensor) { + IndexGroups groups; + std::set encounteredIndices; + + for (const Index& current : tensor.bra()) { + if (encounteredIndices.find(current) == encounteredIndices.end()) { + groups.bra.push_back(current); + encounteredIndices.insert(current); + } else { + // There can't be indices in bra at this point, so we don't have to remove + // from that + detail::remove_one(groups.bra, current); + } + } + + for (const Index& current : tensor.ket()) { + if (encounteredIndices.find(current) == encounteredIndices.end()) { + groups.ket.push_back(current); + encounteredIndices.insert(current); + } else { + detail::remove_one(groups.bra, current); + detail::remove_one(groups.ket, current); + } + } + + for (const Index& current : tensor.auxiliary()) { + if (encounteredIndices.find(current) == encounteredIndices.end()) { + groups.aux.push_back(current); + encounteredIndices.insert(current); + } else { + detail::remove_one(groups.bra, current); + detail::remove_one(groups.ket, current); + detail::remove_one(groups.aux, current); + } + } + + return groups; +} + +/// Obtains the set of unique (non-repeated) indices used in the given sum +/// The assumption here is that the set of unique indices of different addends +/// is the same (which it should be anyway as otherwise it wouldn't make sense +/// to add these terms in the first place) +template > +IndexGroups get_unique_indices(const Sum& sum) { + // In order for the sum to be valid, all summands must have the same + // external indices, so it suffices to look only at the first one + return sum.summands().empty() ? IndexGroups{} + : get_unique_indices(sum.summand(0)); +} + +/// Obtains the set of unique (non-repeated) indices used in the given product +template > +IndexGroups get_unique_indices(const Product& product) { + std::set encounteredIndices; + IndexGroups groups; + + for (const ExprPtr& current : product) { + IndexGroups currentGroups = + get_unique_indices(current); + + for (Index& current : currentGroups.bra) { + if (encounteredIndices.find(current) == encounteredIndices.end()) { + encounteredIndices.insert(current); + groups.bra.push_back(std::move(current)); + } else { + detail::remove_one(groups.bra, current); + detail::remove_one(groups.ket, current); + } + } + + // Same for ket indices + for (Index& current : currentGroups.ket) { + if (encounteredIndices.find(current) == encounteredIndices.end()) { + encounteredIndices.insert(current); + groups.ket.push_back(std::move(current)); + } else { + detail::remove_one(groups.bra, current); + detail::remove_one(groups.ket, current); + } + } + + // Same for aux indices + for (Index& current : currentGroups.aux) { + if (encounteredIndices.find(current) == encounteredIndices.end()) { + encounteredIndices.insert(current); + groups.aux.push_back(std::move(current)); + } else { + detail::remove_one(groups.bra, current); + detail::remove_one(groups.ket, current); + detail::remove_one(groups.aux, current); + } + } + } + + return groups; +} + +/// Obtains the set of unique (non-repeated) indices used in the given +/// expression +template +IndexGroups get_unique_indices(const ExprPtr& expr) { + if (expr.is()) { + return get_unique_indices(expr.as()); + } else if (expr.is()) { + return get_unique_indices(expr.as()); + } else if (expr.is()) { + return get_unique_indices(expr.as()); + } else if (expr.is()) { + return get_unique_indices(expr.as()); + } else if (expr.is()) { + return get_unique_indices(expr.as()); + } else { + throw std::runtime_error( + "Encountered unsupported expression type in get_unique_indices"); + } } } // namespace sequant diff --git a/SeQuant/core/utility/string.hpp b/SeQuant/core/utility/string.hpp index ce99f6849..fc7a10383 100644 --- a/SeQuant/core/utility/string.hpp +++ b/SeQuant/core/utility/string.hpp @@ -26,7 +26,7 @@ constexpr inline bool is_wstring_or_view_v = template constexpr inline bool is_string_convertible_v = is_string_or_view_v || std::is_same_v, char[]> || - std::is_same_v, const char[]> || + std::is_same_v>, char> || std::is_same_v, char *> || std::is_same_v, const char *> || std::is_same_v, char>; @@ -34,11 +34,30 @@ constexpr inline bool is_string_convertible_v = template constexpr inline bool is_wstring_convertible_v = is_wstring_or_view_v || std::is_same_v, wchar_t[]> || - std::is_same_v, const wchar_t[]> || + std::is_same_v>, wchar_t> || std::is_same_v, wchar_t *> || std::is_same_v, const wchar_t *> || std::is_same_v, wchar_t>; +template +constexpr inline bool is_basic_string_convertible_v = + is_string_convertible_v || is_wstring_convertible_v; + +template +using EnableIfAllBasicStringConvertible = std::enable_if_t<( + is_basic_string_convertible_v> && ...)>; + +template +struct char_type; + +template +struct char_type>> { + using type = std::conditional_t, char, wchar_t>; +}; + +template +using char_t = typename char_type::type; + } // namespace meta /// Converts the given wide-string to a UTF-8 encoded narrow string @@ -48,6 +67,14 @@ std::string toUtf8(std::wstring_view str); /// wide-string std::wstring toUtf16(std::string_view str); +template > +std::basic_string_view> to_basic_string_view(S &&str) { + if constexpr (meta::is_char_v>) + return {&str, 1}; + else + return str; +} + } // namespace sequant #endif // SEQUANT_CORE_UTILITY_STRING_HPP diff --git a/SeQuant/core/wick.hpp b/SeQuant/core/wick.hpp index 47bb06d33..05f34daad 100644 --- a/SeQuant/core/wick.hpp +++ b/SeQuant/core/wick.hpp @@ -17,6 +17,16 @@ namespace sequant { +/// @brief extracts external indices of an expanded expression + +/// External indices appear only once in an expression +/// @param expr an expression +/// @return external indices +/// @pre @p expr has been expanded (i.e. cannot contain a Sum as a +/// subexpression) +/// @throw std::invalid_argument if any of @p expr subexpressions is a Sum +inline container::set extract_external_indices(const Expr &expr); + /// Applies Wick's theorem to a sequence of normal-ordered operators. /// /// @tparam S particle statistics @@ -41,16 +51,25 @@ class WickTheorem { WickTheorem &operator=(const WickTheorem &) = default; public: - explicit WickTheorem(const NormalOperatorSequence &input) { + explicit WickTheorem( + const std::shared_ptr> &input) { init_input(input); - assert(input.size() <= max_input_size); - assert(input.empty() || input.vacuum() != Vacuum::Invalid); + assert(input_->size() <= max_input_size); + assert(input_->empty() || input_->vacuum() != Vacuum::Invalid); if constexpr (statistics == Statistics::BoseEinstein) { - assert(input.empty() || input.vacuum() == Vacuum::Physical); + assert(input_->empty() || input_->vacuum() == Vacuum::Physical); } } + explicit WickTheorem(const NormalOperatorSequence &input) + : WickTheorem(std::make_shared>(input)) {} - explicit WickTheorem(ExprPtr expr_input) : expr_input_(expr_input) {} + explicit WickTheorem(ExprPtr expr_input) { + if (expr_input->is>()) { + *this = WickTheorem( + expr_input.template as_shared_ptr>()); + } else + expr_input_ = std::move(expr_input); + } /// constructs WickTheorem from @c other with expression input set to @c /// expr_input @@ -65,7 +84,7 @@ class WickTheorem { /// Controls whether next call to compute() will full contractions only or all /// (including partial) contractions. By default compute() generates full /// contractions only. - /// @param sf if false, will evaluate all contractions. + /// @param fc if false, will evaluate all contractions. /// @return reference to @c *this , for daisy-chaining WickTheorem &full_contractions(bool fc) { full_contractions_ = fc; @@ -104,7 +123,7 @@ class WickTheorem { /// This is useful to to eliminate the topologically-equivalent contractions /// when fully-contracted result (i.e. the vacuum average) is sought. /// By default the use of topology is not enabled. - /// @param sf if true, will utilize the topology to minimize work. + /// @param ut if true, will utilize the topology to minimize work. WickTheorem &use_topology(bool ut) { use_topology_ = ut; return *this; @@ -112,18 +131,33 @@ class WickTheorem { /// Specifies the external indices; by default assume all indices are summed /// over - /// @param ext_inds external (nonsummed) indices + /// @param external_indices external (nonsummed) indices + /// @throw std::logic_error if WickTheorem::set_external_indices or + /// WickTheorem::compute had already been invoked template WickTheorem &set_external_indices(IndexContainer &&external_indices) { - if constexpr (std::is_convertible_v) + if (external_indices_.has_value()) + throw std::logic_error( + "WickTheorem::set_external_indices invoked but external indices have " + "already been set/computed"); + + if constexpr (std::is_convertible_v< + IndexContainer, + typename decltype(external_indices_)::value_type>) external_indices_ = std::forward(external_indices); else { - ranges::for_each(std::forward(external_indices), - [this](auto &&v) { - auto result = this->external_indices_.emplace(v); - assert(result.second); - }); + external_indices_ = typename decltype(external_indices_)::value_type{}; + ranges::for_each( + std::forward(external_indices), [this](auto &&v) { + auto [it, inserted] = this->external_indices_->emplace(v); + if (!inserted) { + std::wstringstream ss; + ss << L"WickTheorem::set_external_indices: " + L"external index " + + to_latex(Index(v)) + L" repeated"; + throw std::invalid_argument(to_string(ss.str())); + } + }); } return *this; } @@ -160,8 +194,8 @@ class WickTheorem { constexpr bool signed_indices = std::is_signed_v::value_type::first_type>; - if (static_cast(opidx_pair.first) >= input_.size() || - static_cast(opidx_pair.second) >= input_.size()) { + if (static_cast(opidx_pair.first) >= input_->size() || + static_cast(opidx_pair.second) >= input_->size()) { throw std::invalid_argument( "WickTheorem::set_nop_connections: nop index out of range"); } @@ -173,7 +207,7 @@ class WickTheorem { } } if (op_index_pairs.size() != 0ul) { - nop_connections_.resize(input_.size()); + nop_connections_.resize(input_->size()); for (auto &v : nop_connections_) { v.set(); } @@ -221,7 +255,8 @@ class WickTheorem { /// not mentioned in @c op_partitions) will be completed in /// compute_nopseq(). /// - ///@{ + + /// @{ /// @tparam IndexListContainer a sequence of sequences of Integer types template @@ -252,7 +287,8 @@ class WickTheorem { return this->set_nop_partitions( nop_partitions); } - ///@} + + /// @} /// @name specifiers of partitions composed of topologically-equivalent /// normal operators @@ -292,13 +328,13 @@ class WickTheorem { bool done = false; while (!done) { - op_partition_idx_.resize(input_.opsize()); + op_partition_idx_.resize(input_->opsize()); ranges::fill(op_partition_idx_, 0); for (auto &&[partition_idx, partition] : ranges::views::enumerate(op_partitions_)) { for (auto &&op_ord : partition) { assert(op_ord >= 0); - assert(op_ord < input_.opsize()); + assert(op_ord < input_->opsize()); assert(op_partition_idx_[op_ord] == 0); op_partition_idx_[op_ord] = partition_idx + 1; } @@ -334,7 +370,7 @@ class WickTheorem { /// makes a default set of partitions with each Op is in its own partition auto &make_default_op_partitions() const { - return set_op_partitions(ranges::views::iota(0ul, input_.opsize()) | + return set_op_partitions(ranges::views::iota(0ul, input_->opsize()) | ranges::views::transform([](const std::size_t v) { return std::array{{v}}; }) | @@ -344,13 +380,16 @@ class WickTheorem { /// Computes and returns the result /// @param count_only if true, will return the total number of terms, as a - /// Constant. + /// Constant; the default is false. + /// @param skip_input_canonicalization whether to skip initial + /// canonicalization of the input expression; the default is false. /// @return the result of applying Wick's theorem; either a Constant, a /// Product, or a Sum /// @warning this is not reentrant, but is optionally threaded internally /// @throw std::logic_error if input's vacuum does not match the current /// context vacuum - ExprPtr compute(const bool count_only = false); + ExprPtr compute(bool count_only = false, + bool skip_input_canonicalization = false); /// Collects compute statistics class Stats { @@ -402,12 +441,14 @@ class WickTheorem { // set this is mutated by compute mutable ExprPtr expr_input_; - mutable NormalOperatorSequence input_; + mutable std::shared_ptr> input_; bool full_contractions_ = true; bool use_topology_ = false; mutable Stats stats_; - container::set external_indices_; + mutable std::optional> + external_indices_; // lack of external indices != all indices are + // internal container::svector> input_partner_indices_; //!< list of {cre,ann} pairs of Index objects in @@ -473,15 +514,22 @@ class WickTheorem { /// initializes input_ /// @param nopseq the NormalOperatorSequence to initialize input_ with - WickTheorem &init_input(const NormalOperatorSequence &nopseq) { + WickTheorem &init_input( + const std::shared_ptr> &nopseq) { input_ = nopseq; + if (input_->vacuum() != get_default_context(S).vacuum()) + throw std::logic_error( + "WickTheorem::init_input(): input vacuum " + "must match the default context vacuum"); + // need to be able to look up ordinals of ops in the input expression to // make index partitions usable using nopseq_view_type = flattened_rangenest>; - auto nopseq_view = nopseq_view_type(&input_); + auto nopseq_view = nopseq_view_type(input_.get()); std::size_t op_ord = 0; - op_to_input_ordinal_.reserve(input_.opsize()); + op_to_input_ordinal_.clear(); + op_to_input_ordinal_.reserve(input_->opsize()); ranges::for_each(nopseq_view, [&](const auto &op) { op_to_input_ordinal_.emplace(op, op_ord); ++op_ord; @@ -489,8 +537,9 @@ class WickTheorem { // populates input_partner_indices_ { + input_partner_indices_.clear(); // for each NormalOperator - for (auto &nop : input_) { + for (auto &nop : *input_) { using ranges::views::reverse; using ranges::views::zip; @@ -526,15 +575,20 @@ class WickTheorem { "WickTheorem::compute: spinfree=true supported only for physical " "vacuum and for Fermi facuum"); + // deduce external indices, if needed + if (!external_indices_) { + external_indices_ = extract_external_indices(*input_); + } + // process cached nop_connections_input_, if needed if (!nop_connections_input_.empty()) const_cast &>(*this).set_nop_connections( nop_connections_input_); // size nop_topological_partition_ to match input_, if needed - upsize_topological_partitions(input_.size(), + upsize_topological_partitions(input_->size(), TopologicalPartitionType::NormalOperator); // initialize op partitions, if not done so - if (input_.opsize() > 0 && op_npartitions_ == 0) + if (input_->opsize() > 0 && op_npartitions_ == 0) make_default_op_partitions(); // now compute @@ -957,7 +1011,7 @@ class WickTheorem { result; //!< current value of the result std::mutex mtx; // used in critical sections updating the result auto result_plus_mutex = std::make_pair(&result, &mtx); - NontensorWickState state(*this, input_); + NontensorWickState state(*this, *input_); state.count_only = count_only; // TODO extract index->particle maps @@ -981,7 +1035,7 @@ class WickTheorem { if (count_only) { ++state.count; } else { - auto [phase, normop] = normalize(input_, input_partner_indices_); + auto [phase, normop] = normalize(*input_, input_partner_indices_); result_plus_mutex.first->push_back( std::make_pair(Product(phase, {}), std::move(normop))); } @@ -1043,7 +1097,8 @@ class WickTheorem { left_op_offset); // left op to contract // optimization: can't contract fully if first op is not a qp annihilator - if (full_contractions_ && !is_qpannihilator(*op_left_iter, input_.vacuum())) + if (full_contractions_ && + !is_qpannihilator(*op_left_iter, input_->vacuum())) return; const auto op_left_iter_fence = @@ -1165,7 +1220,7 @@ class WickTheorem { // old code assumes bra/ket of each NormalOperator forms // a single partition const auto nop_right_input = - input_.at(op_right_cursor.range_ordinal()); + input_->at(op_right_cursor.range_ordinal()); const auto op_right_ord_in_nop_input = ranges::find(nop_right_input, *op_right_it) - ranges::begin(nop_right_input); @@ -1253,7 +1308,7 @@ class WickTheorem { // check if can contract these indices and // check connectivity constraints (if needed) - if (can_contract(*op_left_iter, *op_right_iter, input_.vacuum())) { + if (can_contract(*op_left_iter, *op_right_iter, input_->vacuum())) { auto &&[is_unique, nop_top_degen] = is_topologically_unique(); if (is_unique) { if (state.connect(nop_connections_, @@ -1284,7 +1339,7 @@ class WickTheorem { Product sp_copy = state.sp; state.sp.append( static_cast(nop_top_degen) * phase, - contract(*op_left_iter, *op_right_iter, input_.vacuum())); + contract(*op_left_iter, *op_right_iter, input_->vacuum())); // update the stats ++stats_.num_attempted_contractions; @@ -1421,21 +1476,23 @@ class WickTheorem { public: static bool can_contract(const Op &left, const Op &right, Vacuum vacuum = get_default_context().vacuum()) { + const auto &isr = get_default_context().index_space_registry(); // for bosons can only do Wick's theorem for physical vacuum (or similar) if constexpr (statistics == Statistics::BoseEinstein) assert(vacuum == Vacuum::Physical); - if (is_qpannihilator(left, vacuum) && is_qpcreator(right, vacuum)) { const auto qpspace_left = qpannihilator_space(left, vacuum); const auto qpspace_right = qpcreator_space(right, vacuum); - const auto qpspace_common = intersection(qpspace_left, qpspace_right); - if (qpspace_common != IndexSpace::null_instance()) return true; + const auto qpspace_common = + isr->intersection(qpspace_left, qpspace_right); + if (qpspace_common) return true; } return false; } static ExprPtr contract(const Op &left, const Op &right, Vacuum vacuum = get_default_context().vacuum()) { + const auto &isr = get_default_context().index_space_registry(); assert(can_contract(left, right, vacuum)); // assert( // !left.index().has_proto_indices() && @@ -1448,7 +1505,8 @@ class WickTheorem { else { const auto qpspace_left = qpannihilator_space(left, vacuum); const auto qpspace_right = qpcreator_space(right, vacuum); - const auto qpspace_common = intersection(qpspace_left, qpspace_right); + const auto qpspace_common = + isr->intersection(qpspace_left, qpspace_right); const auto index_common = Index::make_tmp_index(qpspace_common); // preserve bra/ket positions of left & right @@ -1474,8 +1532,8 @@ class WickTheorem { } } - /// @param[in,out] on input, Wick's theorem result, on output the result of - /// reducing the overlaps + /// @param[in,out] expr on input, Wick's theorem result, on output the result + /// of reducing the overlaps void reduce(ExprPtr &expr) const; }; diff --git a/SeQuant/core/wick.impl.hpp b/SeQuant/core/wick.impl.hpp index 9299dac05..49d697790 100644 --- a/SeQuant/core/wick.impl.hpp +++ b/SeQuant/core/wick.impl.hpp @@ -36,7 +36,8 @@ struct zero_result : public std::exception {}; /// index representing intersection of spaces of I and J, !!keep the delta!! /// @throw zero_result if @c product is zero for any reason, e.g. because /// it includes an overlap of 2 indices from nonoverlapping spaces -inline container::map compute_index_replacement_rules( +template +container::map compute_index_replacement_rules( std::shared_ptr &product, const container::set &external_indices, const std::set &all_indices) { @@ -53,8 +54,9 @@ inline container::map compute_index_replacement_rules( // computes an index in intersection of space1 and space2 auto make_intersection_index = [&idxfac](const IndexSpace &space1, const IndexSpace &space2) { - const auto intersection_space = intersection(space1, space2); - if (intersection_space == IndexSpace::null_instance()) throw zero_result{}; + auto isr = sequant::get_default_context(S).index_space_registry(); + const auto intersection_space = isr->intersection(space1, space2); + if (!intersection_space) throw zero_result{}; return idxfac.make(intersection_space); }; @@ -95,6 +97,7 @@ inline container::map compute_index_replacement_rules( // dst2 and dst auto add_rules = [&result, &idxfac, &proto, &make_intersection_index]( const Index &src1, const Index &src2, const Index &dst) { + auto isr = get_default_context(S).index_space_registry(); // are there replacement rules already for src{1,2}? auto src1_it = result.find(src1); auto src2_it = result.find(src2); @@ -139,9 +142,11 @@ inline container::map compute_index_replacement_rules( const auto &old_dst2 = src2_it->second; const auto new_dst_space = (dst.space() != old_dst1.space() || dst.space() != old_dst2.space()) - ? intersection(old_dst1.space(), old_dst2.space(), dst.space()) + ? isr->intersection( + isr->intersection(old_dst1.space(), old_dst2.space()), + dst.space()) : dst.space(); - if (new_dst_space == IndexSpace::null_instance()) throw zero_result{}; + if (!new_dst_space) throw zero_result{}; Index new_dst; if (new_dst_space == old_dst1.space()) { new_dst = old_dst1; @@ -165,6 +170,7 @@ inline container::map compute_index_replacement_rules( } }; + auto isr = get_default_context(S).index_space_registry(); /// this makes the list of replacements ... we do not mutate the expressions /// to keep the information about which indices are related for (auto it = ranges::begin(exrng); it != ranges::end(exrng); ++it) { @@ -183,10 +189,11 @@ inline container::map compute_index_replacement_rules( const auto ket_is_ext = ranges::find(external_indices, ket) != ranges::end(external_indices); - const auto intersection_space = intersection(bra.space(), ket.space()); + const auto intersection_space = + isr->intersection(bra.space(), ket.space()); // if overlap's indices are from non-overlapping spaces, return zero - if (intersection_space == IndexSpace::null_instance()) { + if (!intersection_space) { throw zero_result{}; } @@ -218,7 +225,8 @@ inline bool apply_index_replacement_rules( std::shared_ptr &product, const container::map &const_replrules, const container::set &external_indices, - std::set &all_indices) { + std::set &all_indices, + const std::shared_ptr &isr) { // to be able to use map[] auto &replrules = const_cast &>(const_replrules); @@ -266,7 +274,7 @@ inline bool apply_index_replacement_rules( #ifndef NDEBUG const auto intersection_space = - intersection(bra.space(), ket.space()); + isr->intersection(bra.space(), ket.space()); #endif if (!bra_is_ext && !ket_is_ext) { // int + int @@ -277,7 +285,8 @@ inline bool apply_index_replacement_rules( #endif erase_it = true; } else if (bra_is_ext && !ket_is_ext) { // ext + int - if (includes(ket.space(), bra.space())) { + if (isr->intersection(ket.space(), bra.space()) != + IndexSpace::null) { #ifndef NDEBUG if (replrules.find(ket) != replrules.end()) assert(replrules[ket].space() == bra.space()); @@ -290,7 +299,8 @@ inline bool apply_index_replacement_rules( #endif } } else if (!bra_is_ext && ket_is_ext) { // int + ext - if (includes(bra.space(), ket.space())) { + if (isr->intersection(bra.space(), ket.space()) != + IndexSpace::null) { #ifndef NDEBUG if (replrules.find(bra) != replrules.end()) assert(replrules[bra].space() == ket.space()); @@ -344,9 +354,10 @@ inline bool apply_index_replacement_rules( /// If using orthonormal representation, resolves Kronecker deltas (=overlaps /// between indices in orthonormal spaces) in summations /// @throw zero_result if @c expr is zero -inline void reduce_wick_impl(std::shared_ptr &expr, - const container::set &external_indices) { - if (get_default_context().metric() == IndexSpaceMetric::Unit) { +template +void reduce_wick_impl(std::shared_ptr &expr, + const container::set &external_indices) { + if (get_default_context(S).metric() == IndexSpaceMetric::Unit) { bool pass_mutated = false; do { pass_mutated = false; @@ -363,8 +374,8 @@ inline void reduce_wick_impl(std::shared_ptr &expr, } }); - const auto replacement_rules = - compute_index_replacement_rules(expr, external_indices, all_indices); + const auto replacement_rules = compute_index_replacement_rules( + expr, external_indices, all_indices); if (Logger::get_instance().wick_reduce) { std::wcout << "reduce_wick_impl(expr, external_indices):\n expr = " @@ -381,8 +392,9 @@ inline void reduce_wick_impl(std::shared_ptr &expr, } if (!replacement_rules.empty()) { + auto isr = get_default_context(S).index_space_registry(); pass_mutated = apply_index_replacement_rules( - expr, replacement_rules, external_indices, all_indices); + expr, replacement_rules, external_indices, all_indices, isr); } if (Logger::get_instance().wick_reduce) { @@ -405,16 +417,39 @@ struct NullNormalOperatorCanonicalizerDeregister { } // namespace detail -template -ExprPtr WickTheorem::compute(const bool count_only) { - if (input_.vacuum() != get_default_context().vacuum()) - throw std::logic_error( - "WickTheorem::compute(): input vacuum " - "must match the default context vacuum"); +inline container::set extract_external_indices(const Expr &expr) { + if (ranges::any_of(expr, [](auto &e) { return e.template is(); })) + throw std::invalid_argument( + "extract_external_indices(expr): expr must be expanded (i.e. no " + "subexpression can be a Sum)"); + + container::map idx_counter; + auto visitor = [&idx_counter](const auto &expr) { + auto expr_as_abstract_tensor = + std::dynamic_pointer_cast(expr); + if (expr_as_abstract_tensor) { + ranges::for_each(expr_as_abstract_tensor->_braket(), + [&idx_counter](const auto &v) { + auto it = idx_counter.find(v); + if (it == idx_counter.end()) { + idx_counter.emplace(v, 1); + } else { + it->second++; + } + }); + } + }; + expr.visit(visitor); - // this is used to detect whether this is part of compute() applied to a Sum - static bool applied_to_sum = false; + return idx_counter | + ranges::views::filter([](const auto &v) { return v.second == 1; }) | + ranges::views::transform([](const auto &v) { return v.first; }) | + ranges::to>; +} +template +ExprPtr WickTheorem::compute(const bool count_only, + const bool skip_input_canonicalization) { // need to avoid recanonicalization of operators produced by WickTheorem // by rapid canonicalization to avoid undoing all the good // the NormalOperator::normalize did ... use RAII @@ -451,14 +486,15 @@ ExprPtr WickTheorem::compute(const bool count_only) { << to_latex_align(expr_input_) << std::endl; // if sum, canonicalize and apply to each summand ... if (expr_input_->is()) { - assert(applied_to_sum == false); - applied_to_sum = true; - - // initial full canonicalization - canonicalize(expr_input_); - assert(!expr_input_->as().empty()); + if (!skip_input_canonicalization) { + // initial full canonicalization + canonicalize(expr_input_); + assert(!expr_input_->as().empty()); + } // NOW disable canonicalization of normal operators + // N.B. even if skipped initial input canonicalization need to disable + // subsequent nop canonicalization disable_nop_canonicalization(); // parallelize over summands @@ -466,6 +502,23 @@ ExprPtr WickTheorem::compute(const bool count_only) { std::mutex result_mtx; // serializes updates of result auto summands = expr_input_->as().summands(); + // find external_indices if don't have them + if (!external_indices_) { + ranges::find_if(summands, [this](const auto &summand) { + if (summand.template is()) // summands must not be a Sum + throw std::invalid_argument( + "WickTheorem::compute(expr): expr is a Sum with one of the " + "summands also a Sum, WickTheorem can only accept a fully " + "expanded Sum"); + else if (summand.template is()) { + external_indices_ = extract_external_indices( + *(summand.template as_shared_ptr())); + return true; + } else + return false; + }); + } + if (Logger::get_instance().wick_harness) std::wcout << "WickTheorem::compute: input (after canonicalize) has " << summands.size() << " terms = " << to_latex_align(result) @@ -474,7 +527,8 @@ ExprPtr WickTheorem::compute(const bool count_only) { auto wick_task = [&result, &result_mtx, this, &count_only](const ExprPtr &input) { WickTheorem wt(input->clone(), *this); - auto task_result = wt.compute(count_only); + auto task_result = wt.compute( + count_only, /* definitely skip input canonicalization */ true); stats() += wt.stats(); if (task_result) { std::scoped_lock lock(result_mtx); @@ -492,21 +546,29 @@ ExprPtr WickTheorem::compute(const bool count_only) { if (result->summands().size() == 1) result_expr = std::move(result->summands()[0]); - assert(applied_to_sum == true); - applied_to_sum = false; - return result_expr; } // ... else if a product, find NormalOperatorSequence, if any, and compute // ... else if (expr_input_->is()) { - if (!applied_to_sum) { // no need to canonicalize if this is part of - // compute() on a Sum + if (!skip_input_canonicalization) { // canonicalize, unless told to skip auto canon_byproduct = expr_input_->rapid_canonicalize(); assert(canon_byproduct == nullptr); // canonicalization of Product always returns nullptr - // NOW disable canonicalization of normal operators - disable_nop_canonicalization(); + } + // NOW disable canonicalization of normal operators + // N.B. even if skipped initial input canonicalization need to disable + // subsequent nop canonicalization + disable_nop_canonicalization(); + + // find external_indices if don't have them + if (!external_indices_) { + external_indices_ = + extract_external_indices(*(expr_input_.as_shared_ptr())); + } else { + assert( + extract_external_indices(*(expr_input_.as_shared_ptr())) == + *external_indices_); } // split off NormalOperators into input_ @@ -518,10 +580,10 @@ ExprPtr WickTheorem::compute(const bool count_only) { // extract into prefactor and op sequence ExprPtr prefactor = ex(expr_input_->as().scalar(), ExprPtrList{}); - decltype(input_) nopseq; + auto nopseq = std::make_shared>(); for (const auto &factor : *expr_input_) { if (factor->template is>()) { - nopseq.push_back(factor->template as>()); + nopseq->push_back(factor->template as>()); } else { assert(factor->is_cnumber()); *prefactor *= *factor; @@ -573,7 +635,7 @@ ExprPtr WickTheorem::compute(const bool count_only) { using opseq_view_type = flattened_rangenest>; - auto opseq_view = opseq_view_type(&input_); + auto opseq_view = opseq_view_type(input_.get()); const auto opseq_view_begin = ranges::begin(opseq_view); const auto opseq_view_end = ranges::end(opseq_view); @@ -587,7 +649,7 @@ ExprPtr WickTheorem::compute(const bool count_only) { auto insertion_result = nop_vidx_ord.emplace(v, nop_ord++); assert(insertion_result.second); } - if (vtypes[v] == VertexType::Index && !input_.empty()) { + if (vtypes[v] == VertexType::Index && !input_->empty()) { auto &idx = (tn_edges.begin() + v)->idx(); auto idx_it_in_opseq = ranges::find_if( opseq_view, @@ -625,20 +687,20 @@ ExprPtr WickTheorem::compute(const bool count_only) { } } - /// Use automorphisms to determine groups of topologically equivalent - /// NormalOperator and Op objects. - /// @param vertices maps vertex indices of the objects to their - /// ordinals in the sequence of such objects within - /// the NormalOperatorSequence - /// @param nontrivial_partitions_only if true, only partitions with - /// more than one element, are reported, else even trivial - /// partitions with a single partition will be reported - /// @param vertex_pair_exclude a callable that accepts 2 vertex - /// indices and returns true if the automorphism of this pair - /// of indices is to be ignored - /// @return the \c {vertex_to_partition_idx,npartitions} pair in - /// which \c vertex_to_partition_idx maps vertex indices that are - /// part of nontrivial partitions to their (1-based) partition indices + // Use automorphisms to determine groups of topologically equivalent + // NormalOperator and Op objects. + // @param vertices maps vertex indices of the objects to their + // ordinals in the sequence of such objects within + // the NormalOperatorSequence + // @param nontrivial_partitions_only if true, only partitions with + // more than one element, are reported, else even trivial + // partitions with a single partition will be reported + // @param vertex_pair_exclude a callable that accepts 2 vertex + // indices and returns true if the automorphism of this pair + // of indices is to be ignored + // @return the \c {vertex_to_partition_idx,npartitions} pair in + // which \c vertex_to_partition_idx maps vertex indices that are + // part of nontrivial partitions to their (1-based) partition indices auto compute_partitions = [&aut_generators]( const container::map &vertices, @@ -738,16 +800,16 @@ ExprPtr WickTheorem::compute(const bool count_only) { nop_vidx_ord, /* nontrivial_partitions_only = */ true, do_not_skip_elements); - /// converts vertex ordinal to partition key map into a sequence of - /// partitions, each composed of the corresponding ordinals of the - /// vertices in the vertex_list sequence - /// @param vidx2pidx a map from vertex index (in TN) to its - /// (1-based) partition index - /// @param npartitions the total number of partitions - /// @param vidx_ord ordered sequence of vertex indices, object - /// with vertex index `vidx` will be mapped to ordinal - /// `vidx_ord[vidx]` - /// @return sequence of partitions, sorted by the smallest ordinal + // converts vertex ordinal to partition key map into a sequence of + // partitions, each composed of the corresponding ordinals of the + // vertices in the vertex_list sequence + // @param vidx2pidx a map from vertex index (in TN) to its + // (1-based) partition index + // @param npartitions the total number of partitions + // @param vidx_ord ordered sequence of vertex indices, object + // with vertex index `vidx` will be mapped to ordinal + // `vidx_ord[vidx]` + // @return sequence of partitions, sorted by the smallest ordinal auto extract_partitions = [](const auto &vidx2pidx, const auto npartitions, const auto &vidx_ord) { @@ -876,7 +938,7 @@ ExprPtr WickTheorem::compute(const bool count_only) { } } - if (!input_.empty()) { + if (!input_->empty()) { if (Logger::get_instance().wick_contract) { std::wcout << "WickTheorem::compute: input to compute_nopseq = {\n"; @@ -900,10 +962,13 @@ ExprPtr WickTheorem::compute(const bool count_only) { } else { // product does not include ops return expr_input_; } - } + } // expr_input_->is() // ... else if NormalOperatorSequence already, compute ... else if (expr_input_->is>()) { - init_input(expr_input_->as>()); + abort(); // expr_input_ should no longer be nonnull if constructed with + // an expression that's a NormalOperatorSequence + init_input( + expr_input_.template as_shared_ptr>()); // NB no simplification possible for a bare product w/ full contractions // ... partial contractions will need simplification return compute_nopseq(count_only); @@ -925,7 +990,8 @@ void WickTheorem::reduce(ExprPtr &expr) const { if (expr->type_id() == Expr::get_type_id()) { auto expr_cast = std::static_pointer_cast(expr); try { - detail::reduce_wick_impl(expr_cast, external_indices_); + assert(external_indices_); + detail::reduce_wick_impl(expr_cast, *external_indices_); expr = expr_cast; } catch (detail::zero_result &) { expr = std::make_shared(0); @@ -936,7 +1002,8 @@ void WickTheorem::reduce(ExprPtr &expr) const { assert(subexpr->is()); auto subexpr_cast = std::static_pointer_cast(subexpr); try { - detail::reduce_wick_impl(subexpr_cast, external_indices_); + assert(external_indices_); + detail::reduce_wick_impl(subexpr_cast, *external_indices_); subexpr = subexpr_cast; } catch (detail::zero_result &) { subexpr = std::make_shared(0); diff --git a/SeQuant/core/wstring.hpp b/SeQuant/core/wstring.hpp index c3929f867..2b9b6c400 100644 --- a/SeQuant/core/wstring.hpp +++ b/SeQuant/core/wstring.hpp @@ -6,6 +6,7 @@ #define SEQUANT_WSTRING_HPP #include +#include #include #include @@ -18,8 +19,10 @@ namespace sequant { /// Converts integral type to its std::wstring representation template -std::enable_if_t>, std::wstring> to_wstring( - T&& t) { +std::enable_if_t> && + !meta::is_char_v>, + std::wstring> +to_wstring(T&& t) { return std::to_wstring(t); } @@ -36,49 +39,36 @@ to_wstring(T&& t) { /// @brief (potentially) narrowing character converter. /// -/// Converts a UTF-8 encoded std::basic_string_view to a UTF-8 encoded +/// Converts a UTF-8 encoded string (C or C++) to a UTF-8 encoded /// std::string -template -std::string to_string( - const std::basic_string_view& str_utf8_view) { +template > +std::string to_string(S&& str_utf8) { + auto str_utf8_view = to_basic_string_view(std::forward(str_utf8)); using boost::locale::conv::utf_to_utf; return utf_to_utf(str_utf8_view.data(), str_utf8_view.data() + str_utf8_view.size()); } -/// @brief (potentially) narrowing character converter. -/// -/// Converts a UTF-8 encoded std::basic_string_view to a UTF-8 encoded -/// std::string -template -std::string to_string( - const std::basic_string& str_utf8) { - using boost::locale::conv::utf_to_utf; - return utf_to_utf(str_utf8.data(), str_utf8.data() + str_utf8.size()); +/// Optimized to_string for std::string +inline std::string to_string(std::string&& str_utf8) { + return std::move(str_utf8); } /// @brief (potentially) narrowing character converter. /// /// Converts a UTF-8 encoded std::basic_string_view to a UTF-8 encoded /// std::wstring -template -std::wstring to_wstring( - const std::basic_string_view& str_utf8_view) { +template > +std::wstring to_wstring(S&& str_utf8) { + auto str_utf8_view = to_basic_string_view(std::forward(str_utf8)); using boost::locale::conv::utf_to_utf; return utf_to_utf(str_utf8_view.data(), str_utf8_view.data() + str_utf8_view.size()); } -/// @brief (potentially) narrowing character converter. -/// -/// Converts a UTF-8 encoded std::basic_string_view to a UTF-8 encoded -/// std::wstring -template -std::wstring to_wstring(const std::basic_string& str_utf8) { - using boost::locale::conv::utf_to_utf; - return utf_to_utf(str_utf8.data(), - str_utf8.data() + str_utf8.size()); +/// Optimized to_wstring for std::wstring +inline std::wstring to_wstring(std::wstring&& str_utf8) { + return std::move(str_utf8); } #if __cplusplus >= 202002L diff --git a/SeQuant/domain/eval/eval.cpp b/SeQuant/domain/eval/eval.cpp index 069300553..1f7157945 100644 --- a/SeQuant/domain/eval/eval.cpp +++ b/SeQuant/domain/eval/eval.cpp @@ -18,14 +18,14 @@ EvalExprTA::EvalExprTA(Variable const& v) : EvalExpr(v), annot_{} {} EvalExprTA::EvalExprTA(const EvalExprTA& left, const EvalExprTA& right, EvalOp op) : EvalExpr{left, right, op} { - using Tidxs = TA::expressions::IndexList; using TA::expressions::BipartiteIndexList; using TA::expressions::GEMMPermutationOptimizer; if (result_type() == ResultType::Tensor) { annot_ = braket_annot(); // clang-format off -// todo: fix the following so that it works for ToT x ToT -> T +// TODO: fix the following so that it works for ToT x ToT -> T +// using Tidxs = TA::expressions::IndexList; // if (left.result_type() == right.result_type() && // op_type() == EvalOp::Prod && !tot()) { // // tensor x tensor confirmed diff --git a/SeQuant/domain/eval/eval.hpp b/SeQuant/domain/eval/eval.hpp index 12fed532a..c7403a73e 100644 --- a/SeQuant/domain/eval/eval.hpp +++ b/SeQuant/domain/eval/eval.hpp @@ -101,7 +101,8 @@ constexpr bool IsIterableOfEvaluableNodes{}; template constexpr bool IsIterableOfEvaluableNodes< - Iterable, std::enable_if_t>>> = true; + Iterable, std::enable_if_t>>> = + true; } // namespace @@ -311,7 +312,8 @@ auto evaluate(NodeT const& node, Le&& le, Args&&... args) { /// template , bool> = true, - std::enable_if_t, Le>, bool> = true> + std::enable_if_t, Le>, + bool> = true> auto evaluate(NodesT const& nodes, Le const& le, Args&&... args) { auto iter = std::begin(nodes); auto end = std::end(nodes); @@ -372,7 +374,8 @@ auto evaluate(NodeT const& node, // /// template , bool> = true, - std::enable_if_t, Le>, bool> = true> + std::enable_if_t, Le>, + bool> = true> auto evaluate(NodesT const& nodes, // Annot const& layout, // Le const& le, Args&&... args) { diff --git a/SeQuant/domain/eval/eval_result.hpp b/SeQuant/domain/eval/eval_result.hpp index a4082d776..c4cebfd02 100644 --- a/SeQuant/domain/eval/eval_result.hpp +++ b/SeQuant/domain/eval/eval_result.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -280,11 +281,11 @@ auto index_hash(Iterable const& bk) { // The BTAS expects index types to be long by default. // There is no straight-forward way to turn the default. // Hence, here we explicitly cast the size_t values to long - // Which is a potentailly narrowing conversion leading to + // Which is a potentially narrowing conversion leading to // integral overflow. Hence, the values in the returned // container are mixed negative and positive integers (long type) // - return static_cast(sequant::hash::value(idx.label())); + return static_cast(sequant::hash::value(Index{idx}.label())); }); } diff --git a/SeQuant/domain/mbpt/antisymmetrizer.hpp b/SeQuant/domain/mbpt/antisymmetrizer.hpp index 36878fc9d..34fd7156d 100644 --- a/SeQuant/domain/mbpt/antisymmetrizer.hpp +++ b/SeQuant/domain/mbpt/antisymmetrizer.hpp @@ -90,7 +90,7 @@ class antisymm_element { int total_swaps = 0; // even # swaps produces positive and odd # of swaps // produce negative - int counter = 0; + [[maybe_unused]] int counter = 0; bool do_next_perm = true; @@ -263,7 +263,7 @@ class antisymm_element { ExprPtr result; }; -//@brief simple class to call antisymm_element on only products +/// @brief simple class to call antisymm_element on only products class antisymmetrize { public: ExprPtr result = ex(0); diff --git a/SeQuant/domain/mbpt/context.cpp b/SeQuant/domain/mbpt/context.cpp index 14755e0fd..6cb671ef2 100644 --- a/SeQuant/domain/mbpt/context.cpp +++ b/SeQuant/domain/mbpt/context.cpp @@ -12,29 +12,20 @@ bool operator!=(Context const& left, Context const& right) { return !(left == right); } -namespace detail { - -Context& default_formalism_instance() { - static Context instance_; - return instance_; -} - -} // namespace detail - -const Context& get_default_formalism() { +const Context& get_default_mbpt_context() { return sequant::detail::get_implicit_context(); } -void set_default_formalism(const Context& ctx) { +void set_default_mbpt_context(const Context& ctx) { sequant::detail::set_implicit_context(ctx); } -void reset_default_formalism() { +void reset_default_mbpt_context() { sequant::detail::reset_implicit_context(); } [[nodiscard]] sequant::detail::ImplicitContextResetter -set_scoped_default_formalism(const Context& f) { +set_scoped_default_mbpt_context(const Context& f) { return sequant::detail::set_scoped_implicit_context(f); } diff --git a/SeQuant/domain/mbpt/context.hpp b/SeQuant/domain/mbpt/context.hpp index 3aa4ec59f..ff99918ce 100644 --- a/SeQuant/domain/mbpt/context.hpp +++ b/SeQuant/domain/mbpt/context.hpp @@ -2,6 +2,8 @@ #ifndef SEQUANT_DOMAIN_MBPT_CONTEXT_HPP #define SEQUANT_DOMAIN_MBPT_CONTEXT_HPP +#include + #include namespace sequant::mbpt { @@ -27,20 +29,17 @@ class Context { CSV csv_ = Defaults::csv; }; -/// old name of Context is a deprecated alias -using Formalism [[deprecated( - "use sequant::mbpt::Context instead of sequant::mbpt::Formalism")]] = - Context; - bool operator==(Context const& left, Context const& right); bool operator!=(Context const& left, Context const& right); -const Context& get_default_formalism(); -void set_default_formalism(const Context& ctx); -void reset_default_formalism(); +const Context& get_default_mbpt_context(); + +void set_default_mbpt_context(const Context& ctx); + +void reset_default_mbpt_context(); [[nodiscard]] detail::ImplicitContextResetter -set_scoped_default_formalism(const Context& ctx); +set_scoped_default_mbpt_context(const Context& ctx); } // namespace sequant::mbpt diff --git a/SeQuant/domain/mbpt/convention.cpp b/SeQuant/domain/mbpt/convention.cpp index 59f2e407e..b88494735 100644 --- a/SeQuant/domain/mbpt/convention.cpp +++ b/SeQuant/domain/mbpt/convention.cpp @@ -4,11 +4,11 @@ #include #include +#include -#include +#include #include #include -#include #include #include @@ -20,169 +20,176 @@ namespace sequant { namespace mbpt { -namespace { -void register_index(IndexRegistry& reg, const Index& idx, long size) { - IndexSpace space = idx.space(); - reg.make(idx, [size, space](const Index& index) -> long { - assert(index.space() == space); - const auto& proto_indices = index.proto_indices(); - if (proto_indices.empty()) - return size; - else { - if (proto_indices.size() == 1) { - assert(proto_indices[0].space().type() == IndexSpace::active_occupied); - if (space == IndexSpace::active_occupied) // 1-index-specific occupied - return 20; - else if (space == IndexSpace::active_unoccupied) // OSV or PAO? - return 500; - else if (space == IndexSpace::all) - return 520; - } else if (proto_indices.size() == 2) { - assert(proto_indices[1].space().type() == IndexSpace::active_occupied); - if (space == IndexSpace::active_occupied) - return 40; - else if (space == IndexSpace::occupied) - return 50; - else if (space == IndexSpace::active_unoccupied) // CVS, e.g. PNO - return 50; - else if (space == IndexSpace::all) - return 100; - } - abort(); // what to return here by default? - } - }); -} -} // anonymous namespace - -namespace qcifs { - -namespace { -enum class qns { none = 0, alpha = 1, beta = 2 }; -auto qndecorate(qns qn, std::wstring_view label) { - switch (static_cast(qn)) { - case 0: - return std::wstring(label); - case 1: - return std::wstring(label) + L"↑"; - case 2: - return std::wstring(label) + L"↓"; - default: - assert(false && "invalid quantum number"); - } - abort(); // unreachable -}; -}; // namespace - -/// @brief registers "standard" instances of IndexSpace objects -void register_standard_instances() { - const bool do_not_throw = true; - for (int s = 0; s <= 2; ++s) { - auto qnattr = s == 0 ? IndexSpace::nullqns - : (s == 1 ? IndexSpace::alpha : IndexSpace::beta); - auto declab = [s](auto&& label) { - return qndecorate(static_cast(s), label); - }; - // these spaces are used in Fock space methods - // based on single-determinant references - // p,q,r... for OBS spstates introduced in DOI 10.1063/1.444231 (QCiFS I) - IndexSpace::register_instance(declab(L"p"), IndexSpace::all, qnattr, - do_not_throw); - // {i,j,k.../a,b,c...} for active {occupied/unoccupied} spstates introduced - // in DOI 10.1063/1.446736 (QCiFS III) - IndexSpace::register_instance(declab(L"i"), IndexSpace::active_occupied, - qnattr, do_not_throw); - IndexSpace::register_instance(declab(L"a"), IndexSpace::active_unoccupied, - qnattr, do_not_throw); - // introduced in MPQC LCAOWavefunction - IndexSpace::register_instance(declab(L"g"), IndexSpace::inactive_unoccupied, - qnattr, do_not_throw); - // {α,β.../κ,𝛌...} for complete {unoccupied/any} spstates introduced in - // DOI 10.1063/1.459921 (MP2-R12 I) - IndexSpace::register_instance(declab(L"α"), IndexSpace::complete_unoccupied, - qnattr, do_not_throw); - IndexSpace::register_instance(declab(L"κ"), IndexSpace::complete, qnattr, - do_not_throw); - // for orthogonal complement to p introduced in - // DOI 10.1016/j.cplett.2004.07.061 (CABS) - IndexSpace::register_instance(declab(L"α'"), IndexSpace::other_unoccupied, - qnattr, do_not_throw); - // m,n... for all occupied (including inactive/frozen orbitals) de facto - // introduced in [DOI 10.1016/j.cplett.2004.07.061 - // (CABS)](https://dx.doi.org/10.1016/j.cplett.2004.07.061), though formally - // not explicitly defined so - IndexSpace::register_instance(declab(L"m"), IndexSpace::occupied, qnattr, - do_not_throw); - // introduced in MPQC LCAOWavefunction - IndexSpace::register_instance(declab(L"e"), IndexSpace::unoccupied, qnattr, - do_not_throw); - // introduced in MPQC for GF, CT-F12, and other ad hoc uses - IndexSpace::register_instance(declab(L"x"), IndexSpace::all_active, qnattr, - do_not_throw); - // introduced here - IndexSpace::register_instance(declab(L"γ"), - IndexSpace::complete_inactive_unoccupied, - qnattr, do_not_throw); - // e.g. see DOI 10.1063/5.0067511 - IndexSpace::register_instance(declab(L"u"), IndexSpace::active, qnattr, - do_not_throw); - // DOI 10.1063/5.0067511 uses I,J,K... and A,B,C... for these - // although QCiFS uses capital letters for spin-free indices, using I/A this - // way seems preferable - IndexSpace::register_instance( - declab(L"I"), IndexSpace::active_maybe_occupied, qnattr, do_not_throw); - IndexSpace::register_instance(declab(L"A"), - IndexSpace::active_maybe_unoccupied, qnattr, - do_not_throw); - // introduced here - IndexSpace::register_instance(declab(L"M"), IndexSpace::maybe_occupied, - qnattr, do_not_throw); - IndexSpace::register_instance(declab(L"E"), IndexSpace::maybe_unoccupied, - qnattr, do_not_throw); - IndexSpace::register_instance(declab(L"Δ"), - IndexSpace::complete_maybe_unoccupied, qnattr, - do_not_throw); +void load(Convention conv) { + std::shared_ptr isr; + switch (conv) { + case Convention::Minimal: + isr = make_min_sr_spaces(); + break; + case Convention::SR: + isr = make_sr_spaces(); + break; + case Convention::MR: + isr = make_mr_spaces(); + break; + case Convention::F12: + isr = make_F12_sr_spaces(); + break; + case Convention::QCiFS: + isr = make_legacy_spaces(); + break; } + set_default_context(sequant::Context(isr, Vacuum::SingleProduct)); } -/// @brief creates an IndexRegistry -void make_default_indexregistry() { - auto idxreg = std::make_shared(); - auto& idxreg_ref = *idxreg; - - for (int s = 0; s <= 2; ++s) { - auto declab = [s](auto&& label) { - return qndecorate(static_cast(s), label); - }; - register_index(idxreg_ref, Index{declab(L"u")}, 20); - register_index(idxreg_ref, Index{declab(L"i")}, 100); - register_index(idxreg_ref, Index{declab(L"I")}, 120); - register_index(idxreg_ref, Index{declab(L"m")}, 110); - register_index(idxreg_ref, Index{declab(L"a")}, 200); - register_index(idxreg_ref, Index{declab(L"g")}, 800); - register_index(idxreg_ref, Index{declab(L"e")}, 1000); - register_index(idxreg_ref, Index{declab(L"A")}, 1020); - register_index(idxreg_ref, Index{declab(L"x")}, 320); - register_index(idxreg_ref, Index{declab(L"p")}, 1130); - register_index(idxreg_ref, Index{declab(L"α'")}, 3000); - register_index(idxreg_ref, Index{declab(L"γ")}, 3800); - register_index(idxreg_ref, Index{declab(L"α")}, 4000); - register_index(idxreg_ref, Index{declab(L"κ")}, 4130); +void add_fermi_spin(std::shared_ptr& isr) { + auto result = std::make_shared(); + for (auto&& space : *isr) { + if (space.base_key() != L"") { + IndexSpace spin_any(space.base_key(), space.type(), Spin::any, + space.approximate_size()); + IndexSpace spin_up(spinannotation_add(space.base_key(), Spin::alpha), + space.type(), Spin::alpha, space.approximate_size()); + IndexSpace spin_down(spinannotation_add(space.base_key(), Spin::beta), + space.type(), Spin::beta, space.approximate_size()); + result->add(spin_any); + result->add(spin_up); + result->add(spin_down); + } } + const bool nulltype_ok = true; + result->reference_occupied_space(isr->reference_occupied_space(nulltype_ok)); + result->vacuum_occupied_space(isr->vacuum_occupied_space(nulltype_ok)); + result->particle_space(isr->particle_space(nulltype_ok)); + result->hole_space(isr->hole_space(nulltype_ok)); + result->complete_space(isr->complete_space(nulltype_ok)); + isr = std::move(result); } -} // namespace qcifs +std::shared_ptr make_min_sr_spaces() { + auto isr = std::make_shared(); -/// Loads defaults for Convention @c conv -void set_default_convention(Convention conv) { - switch (conv) { - case Convention::QCiFS: { - using namespace qcifs; - register_standard_instances(); - make_default_indexregistry(); - TensorCanonicalizer::set_cardinal_tensor_labels( - mbpt::cardinal_tensor_labels()); - } - } + isr->add(L"i", 0b01, is_vacuum_occupied, is_reference_occupied, is_hole) + .add(L"a", 0b10, is_particle) + .add_union(L"p", {L"i", L"a"}, is_complete); + add_fermi_spin(isr); + + return isr; +} + +// Multireference supspace uses a subset of its occupied orbitals to define a +// vacuum occupied subspace. +// this leaves an active space which is partially occupied/unoccupied. This +// definition is convenient when coupled with SR vacuum. +std::shared_ptr make_mr_spaces() { + auto isr = std::make_shared(); + + isr->add(L"o", 0b00001) + .add(L"i", 0b00010) + .add(L"u", 0b00100) + .add(L"a", 0b01000) + .add(L"g", 0b10000) + .add_union(L"O", {L"o", L"i"}, is_vacuum_occupied) + .add_union(L"M", {L"o", L"i", L"u"}, is_reference_occupied) + .add_union(L"I", {L"i", L"u"}, is_hole) + .add_union(L"E", {L"u", L"a", L"g"}) + .add_union(L"A", {L"u", L"a"}, is_particle) + .add_union(L"p", {L"M", L"E"}, is_complete); + + add_fermi_spin(isr); + + return isr; +} + +std::shared_ptr make_sr_spaces() { + auto isr = std::make_shared(); + + isr->add(L"o", 0b0001) + .add(L"i", 0b0010, is_hole) + .add(L"a", 0b0100, is_particle) + .add(L"g", 0b1000) + .add_union(L"m", {L"o", L"i"}, is_vacuum_occupied, is_reference_occupied) + .add_union(L"e", {L"a", L"g"}) + .add_union(L"x", {L"i", L"a"}) + .add_union(L"p", {L"m", L"e"}, is_complete); + add_fermi_spin(isr); + + return isr; +} + +std::shared_ptr make_F12_sr_spaces() { + auto isr = std::make_shared(); + + isr->add(L"o", 0b00001) + .add(L"i", 0b00010, is_hole) + .add(L"a", 0b00100, is_particle) + .add(L"g", 0b01000) + .add(L"α'", 0b10000) + .add_union(L"m", {L"o", L"i"}, is_vacuum_occupied, is_reference_occupied) + .add_union(L"e", {L"a", L"g"}) + .add_union(L"x", {L"i", L"a"}) + .add_union(L"p", {L"m", L"e"}) + .add_unIon(L"h", {L"x", L"g"}) + .add_unIon(L"c", {L"g", L"α'"}) + .add_union(L"α", {L"e", L"α'"}) + .add_union(L"H", {L"i", L"α"}) + .add_union(L"κ", {L"p", L"α'"}, is_complete); + add_fermi_spin(isr); + + return isr; +} + +std::shared_ptr make_legacy_spaces(bool ignore_spin) { + auto isr = std::make_shared(); + + isr->add(L"o", 0b0000001) + .add(L"n", 0b0000010) + .add(L"i", 0b0000100, is_hole) + .add(L"u", 0b0001000) + .add(L"a", 0b0010000, is_particle) + .add(L"g", 0b0100000) + .add(L"α'", 0b1000000) + .add_union(L"m", {L"o", L"n", L"i"}, is_vacuum_occupied, + is_reference_occupied) + .add_union(L"M", {L"m", L"u"}) + .add_union(L"e", {L"a", L"g"}) + .add_union(L"E", {L"u", L"e"}) + .add_union(L"x", {L"i", L"u", L"a"}) + .add_union(L"p", {L"m", L"x", L"e"}) + .add_union(L"κ", {L"p", L"α'"}, is_complete); + + if (!ignore_spin) add_fermi_spin(isr); + + return isr; +} + +std::pair, + std::shared_ptr> +make_fermi_and_bose_spaces() { + auto isr = std::make_shared(); + + isr->add(L"i", 0b001) // fermi occupied + .add(L"a", 0b010) // fermi unoccupied + .add_union(L"p", {L"i", L"a"}) // fermi all + ; + add_fermi_spin(isr); + isr->add(L"β", 0b100); // bose + + auto fermi_isr = std::make_shared(isr->spaces()); + fermi_isr->vacuum_occupied_space(L"i"); + fermi_isr->reference_occupied_space(L"i"); + fermi_isr->hole_space(L"i"); + fermi_isr->particle_space(L"a"); + fermi_isr->complete_space(L"p"); + + auto bose_isr = std::make_shared(isr->spaces()); + bose_isr->vacuum_occupied_space(IndexSpace::null); + bose_isr->reference_occupied_space(IndexSpace::null); + bose_isr->hole_space(IndexSpace::null); + bose_isr->particle_space(L"β"); + bose_isr->complete_space(L"β"); + + return std::make_pair(std::move(fermi_isr), std::move(bose_isr)); } } // namespace mbpt diff --git a/SeQuant/domain/mbpt/convention.hpp b/SeQuant/domain/mbpt/convention.hpp index 748f66dbc..65ae1462d 100644 --- a/SeQuant/domain/mbpt/convention.hpp +++ b/SeQuant/domain/mbpt/convention.hpp @@ -5,41 +5,70 @@ #ifndef SEQUANT_CONVENTION_HPP #define SEQUANT_CONVENTION_HPP +#include + +#include + namespace sequant { namespace mbpt { -enum class Convention { QCiFS }; - -// clang-format off -/// @brief Loads defaults for Convention @c conv - -/// This registers IndexSpace objects standard for the chosen convention, -/// updates default context's IndexRegistry object, and -/// updates TensorCanonicalizer cardinal labels -/// @warning should be only called once -/// @param conv convention to load; the only supported convention at the moment -/// is QCiFS -/// @note The QCiFS convention introduced the following definitions: -/// -/// | Label | Space | Comment | -/// |-------|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| -/// | `p` | IndexSpace::all | p,q,r... for OBS spstates introduced in [DOI 10.1063/1.444231 (QCiFS I)](https://dx.doi.org/10.1063/1.444231) | -/// | `i` | IndexSpace::active_occupied | i,j,k... for active occupied spstates introduced in [DOI 10.1063/1.446736 (QCiFS III)](https://dx.doi.org/10.1063/1.446736) | -/// | `a` | IndexSpace::active_unoccupied | a,b,c... for active unoccupied spstates introduced in [DOI 10.1063/1.446736 (QCiFS III)](https://dx.doi.org/10.1063/1.446736) | -/// | `α` | IndexSpace::complete_unoccupied | α,β... for complete unoccupied spstates introduced in [DOI 10.1063/1.459921 (MP2-R12 I)](https://dx.doi.org/10.1063/1.459921) | -/// | `κ` | IndexSpace::complete | κ,𝛌... for complete spstates introduced in [DOI 10.1063/1.459921 (MP2-R12 I)](https://dx.doi.org/10.1063/1.459921) | -/// | \c α' | IndexSpace::other_unoccupied | α',β'... for orthogonal complement to OBS (CABS) introduced in [DOI 10.1016/j.cplett.2004.07.061 (CABS)](https://dx.doi.org/10.1016/j.cplett.2004.07.061) | -/// | `m` | IndexSpace::occupied | m,n.. for all occupied (including inactive/frozen orbitals) de facto introduced in [DOI 10.1016/j.cplett.2004.07.061 (CABS)](https://dx.doi.org/10.1016/j.cplett.2004.07.061), though formally not explicitly defined so | -/// | `e` | IndexSpace::unoccupied | e,f... for all unoccupied (including inactive/frozen orbitals) used internally in MPQC LCAOWavefunction | -/// | `x` | IndexSpace::all_active | used internally in MPQC for GF, CT-F12, and other ad hoc uses | -/// | `u` | IndexSpace::active | origin unknown, for recent use see [DOI 10.1063/5.0067511](https://dx.doi.org/10.1063/5.0067511) | -/// | `I` | IndexSpace::active_maybe_occupied | origin unknown, for recent use see [DOI 10.1063/5.0067511](https://dx.doi.org/10.1063/5.0067511); N.B. although QCiFS uses capital letters for spin-free indices, since there is usually no need to distinguish spin-orbital from spin-free indices (the type is deduced from the context), this use of capital letters seems preferable | -/// | `A` | IndexSpace::active_maybe_unoccupied | origin unknown, for recent use see [DOI 10.1063/5.0067511](https://dx.doi.org/10.1063/5.0067511); N.B. although QCiFS uses capital letters for spin-free indices, since there is usually no need to distinguish spin-orbital from spin-free indices (the type is deduced from the context), this use of capital letters seems preferable | -/// | `M` | IndexSpace::maybe_occupied | combination of of `I` for `m` | -/// | `E` | IndexSpace::maybe_unoccupied | combination of of `A` for `e` | -/// | `Δ` | IndexSpace::complete_maybe_unoccupied | combination of `α` and `A`; since capital Alpha looks indistinguishable from `A` use `Δ` | -// clang-format on -void set_default_convention(Convention conv = Convention::QCiFS); +/// @brief Conventions for partitioning the single-particle Hilbert space +enum class Convention { + Minimal, //!< occupied/hole + unoccupied/particle + their union + SR, //!< single determinant reference: occupied (frozen + active) + + //!< unoccupied (active + frozen) + MR, //!< multi determinant reference: occupied (frozen + active) + active + + //!< unoccupied (active + frozen) + F12, //!< SR + complement from complete basis, used for F12 methods + QCiFS //!< ``Quantum Chemistry in Fock Space'' = superset of above +}; + +void load(Convention conv = Convention::Minimal); + +/// @brief decorate IndexSpace labels with spin +std::wstring decorate_label(std::wstring label, bool up); + +/// @brief add fermionic spin spaces to registry +void add_fermi_spin(std::shared_ptr& isr); + +/// @name built-in definitions of IndexSpace +/// @{ + +/// Most standard models only need 2 base spaces, occupied and unoccupied. +/// This is minimal partitioning is sufficient for computing expectation values +/// of Coupled-Cluster type operators. +std::shared_ptr make_min_sr_spaces(); + +/// Common partitioning for single reference F12 calculations. +/// notably, this set contains an other_unoccupied space, α', commonly used to +/// construct an approximately complete representation +std::shared_ptr make_F12_sr_spaces(); + +/// Multireference partitioning contains an active space, x, which is assumed to +/// have partial density although it is considered unoccupied with respect to a +/// SingleProduct Vacuum. This leads to a variety of additional composite spaces +/// with may or may not be occupied. +std::shared_ptr make_mr_spaces(); + +/// 'Standard' choice of partitioning orbitals in a single reference. +/// Includes frozen_core, active_occupied, active_unoccupied, and +/// inactive_unoccupied orbitals as base spaces. +std::shared_ptr make_sr_spaces(); + +/// Legacy partitioning similar to previous versions of SeQuant which had +/// compile time hard coded partitioning. This is useful when verifying +/// previously obtained results which have been canonicalized in this context. +/// @param ignore_spin if true, do not add spin-specific spaces, and do not use +/// Spin::any as IndexSpace::QuantumNumbers for spin-free space +std::shared_ptr make_legacy_spaces( + bool ignore_spin = false); + +/// make fermi and bose space registries for multicomponent models +std::pair, + std::shared_ptr> +make_fermi_and_bose_spaces(); + +/// @} } // namespace mbpt } // namespace sequant diff --git a/SeQuant/domain/mbpt/fwd.hpp b/SeQuant/domain/mbpt/fwd.hpp index 72851433b..766105d31 100644 --- a/SeQuant/domain/mbpt/fwd.hpp +++ b/SeQuant/domain/mbpt/fwd.hpp @@ -15,14 +15,13 @@ namespace sequant { /// components of SeQuant namespace mbpt { -/// @brief the namespace of the MBPT formalisms with respect to the single -/// determinant reference (SR) vacuum -namespace sr {} +/// @brief the namespace containing MBPT operators +inline namespace op { -/// @brief the namespace of the MBPT formalisms with respect to the multi -/// determinant reference (MR) wave function -namespace mr {} +/// @brief the namespace containing tensor form of MBPT operators +namespace tensor {} +} // namespace op } // namespace mbpt } // namespace sequant diff --git a/SeQuant/domain/mbpt/models/cc.cpp b/SeQuant/domain/mbpt/models/cc.cpp index 8776a6940..4bd6f827e 100644 --- a/SeQuant/domain/mbpt/models/cc.cpp +++ b/SeQuant/domain/mbpt/models/cc.cpp @@ -1,8 +1,11 @@ #include #include #include +#include +#include #include -#include +#include +#include #include #include @@ -11,7 +14,7 @@ #include #include -namespace sequant::mbpt::sr { +namespace sequant::mbpt { CC::CC(std::size_t n, Ansatz a) : N(n), ansatz_(a) {} @@ -23,7 +26,6 @@ bool CC::unitary() const { ExprPtr CC::sim_tr(ExprPtr expr, size_t commutator_rank) { const bool skip_singles = ansatz_ == Ansatz::oT || ansatz_ == Ansatz::oU; - auto transform_op_op_pdt = [this, &commutator_rank, skip_singles](const ExprPtr& expr) { // TODO: find the order at which the commutator expression should truncate @@ -34,11 +36,11 @@ ExprPtr CC::sim_tr(ExprPtr expr, size_t commutator_rank) { for (size_t k = 1; k <= commutator_rank; ++k) { ExprPtr op_Sk_comm_w_S; op_Sk_comm_w_S = - op_Sk * - op::T(N, skip_singles); // traditional SR ansatz: [O,T] = (O T)_c + op_Sk * T(N, skip_singles); // traditional SR ansatz: [O,T] = (O T)_c if (unitary()) // unitary SR ansatz: [O,T-T^+] = (O T)_c + (T^+ O)_c - op_Sk_comm_w_S += adjoint(op::T(N, skip_singles)) * op_Sk; - op_Sk = simplify(ex(rational{1, k}) * op_Sk_comm_w_S); + op_Sk_comm_w_S += adjoint(T(N, skip_singles)) * op_Sk; + op_Sk = ex(rational{1, k}) * op_Sk_comm_w_S; + simplify(op_Sk); result += op_Sk; } return result; @@ -82,7 +84,7 @@ std::vector CC::t(size_t commutator_rank, size_t pmax, size_t pmin) { assert(pmax >= pmin && "pmax should be >= pmin"); // 1. construct hbar(op) in canonical form - auto hbar = sim_tr(op::H(), commutator_rank); + auto hbar = sim_tr(H(), commutator_rank); // 2. project onto each manifold, screen, lower to tensor form and wick it std::vector result(pmax + 1); @@ -95,13 +97,12 @@ std::vector CC::t(size_t commutator_rank, size_t pmax, size_t pmin) { hbar_le_p; // keeps products that can produce excitations rank <=p for (auto& term : *hbar) { assert(term->is() || term->is()); - - if (op::raises_vacuum_up_to_rank(term, p)) { + if (raises_vacuum_up_to_rank(term, p)) { if (!hbar_le_p) hbar_le_p = std::make_shared(ExprPtrList{term}); else hbar_le_p->append(term); - if (op::raises_vacuum_to_rank(term, p)) { + if (raises_vacuum_to_rank(term, p)) { if (!hbar_p) hbar_p = std::make_shared(ExprPtrList{term}); else @@ -110,9 +111,8 @@ std::vector CC::t(size_t commutator_rank, size_t pmax, size_t pmin) { } } hbar = hbar_le_p; - // 2.b project onto 0) and compute VEV - result.at(p) = op::vac_av(p != 0 ? op::P(p) * hbar_p : hbar_p); + result.at(p) = vac_av(p != 0 ? P(p) * hbar_p : hbar_p); } return result; @@ -123,20 +123,19 @@ std::vector CC::λ(std::size_t commutator_rank) { assert(!unitary() && "there is no need for CC::λ for unitary ansatz"); // construct hbar - auto hbar = sim_tr(op::H(), commutator_rank - 1); + auto hbar = sim_tr(H(), commutator_rank - 1); const auto One = ex(1); - auto lhbar = simplify((One + op::Λ(N)) * hbar); - - auto op_connect = - op::concat(op::default_op_connections(), - std::vector>{ - {OpType::h, OpType::A}, - {OpType::f, OpType::A}, - {OpType::g, OpType::A}, - {OpType::h, OpType::S}, - {OpType::f, OpType::S}, - {OpType::g, OpType::S}}); + auto lhbar = simplify((One + Λ(N)) * hbar); + + auto op_connect = concat(default_op_connections(), + std::vector>{ + {OpType::h, OpType::A}, + {OpType::f, OpType::A}, + {OpType::g, OpType::A}, + {OpType::h, OpType::S}, + {OpType::f, OpType::S}, + {OpType::g, OpType::S}}); // 2. project onto each manifold, screen, lower to tensor form and wick it std::vector result(N + 1); @@ -150,12 +149,12 @@ std::vector CC::λ(std::size_t commutator_rank) { for (auto& term : *lhbar) { // pick terms from lhbar assert(term->is() || term->is()); - if (op::lowers_rank_or_lower_to_vacuum(term, p)) { + if (lowers_rank_or_lower_to_vacuum(term, p)) { if (!hbar_le_p) hbar_le_p = std::make_shared(ExprPtrList{term}); else hbar_le_p->append(term); - if (op::lowers_rank_to_vacuum(term, p)) { + if (lowers_rank_to_vacuum(term, p)) { if (!hbar_p) hbar_p = std::make_shared(ExprPtrList{term}); else @@ -167,7 +166,7 @@ std::vector CC::λ(std::size_t commutator_rank) { // 2.b multiply by adjoint of P(p) (i.e., P(-p)) on the right side and // compute VEV - result.at(p) = op::vac_av(hbar_p * op::P(-p), op_connect); + result.at(p) = vac_av(hbar_p * P(-p), op_connect); } return result; } @@ -187,10 +186,10 @@ std::vector CC::t_pt(std::size_t order, std::size_t rank) { // operator and at rank 4 for two-body perturbation operator const auto h1_truncate_at = rank == 1 ? 2 : 4; - auto h1_bar = sim_tr(op::H_pt(1, rank), h1_truncate_at); + auto h1_bar = sim_tr(H_pt(1, rank), h1_truncate_at); // construct [hbar, T(1)] - auto hbar_pert = sim_tr(op::H(), 3) * op::T_pt(order, N); + auto hbar_pert = sim_tr(H(), 3) * T_pt(order, N); // [Eq. 34, WIREs Comput Mol Sci. 2019; 9:e1406] auto expr = simplify(h1_bar + hbar_pert); @@ -198,19 +197,17 @@ std::vector CC::t_pt(std::size_t order, std::size_t rank) { // connectivity: // connect t and t1 with {h,f,g} // connect h1 with t - auto op_connect = - op::concat(op::default_op_connections(), - std::vector>{ - {OpType::h, OpType::t_1}, - {OpType::f, OpType::t_1}, - {OpType::g, OpType::t_1}, - {OpType::h_1, OpType::t}}); + auto op_connect = concat(default_op_connections(), + std::vector>{ + {OpType::h, OpType::t_1}, + {OpType::f, OpType::t_1}, + {OpType::g, OpType::t_1}, + {OpType::h_1, OpType::t}}); std::vector result(N + 1); for (auto p = N; p >= 1; --p) { - auto freq_term = ex(L"ω") * op::P(p) * op::T_pt_(order, p); - result.at(p) = - op::vac_av(op::P(p) * expr, op_connect) - op::vac_av(freq_term); + auto freq_term = ex(L"ω") * P(p) * T_pt_(order, p); + result.at(p) = vac_av(P(p) * expr, op_connect) - vac_av(freq_term); } return result; } @@ -225,7 +222,7 @@ std::vector CC::λ_pt(size_t order, size_t rank) { assert(ansatz_ == Ansatz::T && "unitary ansatz is not yet supported"); // construct hbar - auto hbar = sim_tr(op::H(), 4); + auto hbar = sim_tr(H(), 4); // construct h1_bar @@ -233,43 +230,41 @@ std::vector CC::λ_pt(size_t order, size_t rank) { // operator and at rank 4 for two-body perturbation operator const auto h1_truncate_at = rank == 1 ? 2 : 4; - auto h1_bar = sim_tr(op::H_pt(1, rank), h1_truncate_at); + auto h1_bar = sim_tr(H_pt(1, rank), h1_truncate_at); // construct [hbar, T(1)] - auto hbar_pert = sim_tr(op::H(), 3) * op::T_pt(order, N); + auto hbar_pert = sim_tr(H(), 3) * T_pt(order, N); // [Eq. 35, WIREs Comput Mol Sci. 2019; 9:e1406] const auto One = ex(1); - auto expr = simplify((One + op::Λ(N)) * (h1_bar + hbar_pert) + - op::Λ_pt(order, N) * hbar); + auto expr = + simplify((One + Λ(N)) * (h1_bar + hbar_pert) + Λ_pt(order, N) * hbar); // connectivity: // t and t1 with {h,f,g} // projectors with {h,f,g} // h1 with t // h1 with projectors - auto op_connect = - op::concat(op::default_op_connections(), - std::vector>{ - {OpType::h, OpType::t_1}, - {OpType::f, OpType::t_1}, - {OpType::g, OpType::t_1}, - {OpType::h_1, OpType::t}, - {OpType::h, OpType::A}, - {OpType::f, OpType::A}, - {OpType::g, OpType::A}, - {OpType::h, OpType::S}, - {OpType::f, OpType::S}, - {OpType::g, OpType::S}, - {OpType::h_1, OpType::A}, - {OpType::h_1, OpType::S}}); + auto op_connect = concat(default_op_connections(), + std::vector>{ + {OpType::h, OpType::t_1}, + {OpType::f, OpType::t_1}, + {OpType::g, OpType::t_1}, + {OpType::h_1, OpType::t}, + {OpType::h, OpType::A}, + {OpType::f, OpType::A}, + {OpType::g, OpType::A}, + {OpType::h, OpType::S}, + {OpType::f, OpType::S}, + {OpType::g, OpType::S}, + {OpType::h_1, OpType::A}, + {OpType::h_1, OpType::S}}); std::vector result(N + 1); for (auto p = N; p >= 1; --p) { - auto freq_term = ex(L"ω") * op::Λ_pt_(order, p) * op::P(-p); - result.at(p) = - op::vac_av(expr * op::P(-p), op_connect) + op::vac_av(freq_term); + auto freq_term = ex(L"ω") * Λ_pt_(order, p) * P(-p); + result.at(p) = vac_av(expr * P(-p), op_connect) + vac_av(freq_term); } return result; } -} // namespace sequant::mbpt::sr +} // namespace sequant::mbpt diff --git a/SeQuant/domain/mbpt/models/cc.hpp b/SeQuant/domain/mbpt/models/cc.hpp index 12671e2da..1ce998c6e 100644 --- a/SeQuant/domain/mbpt/models/cc.hpp +++ b/SeQuant/domain/mbpt/models/cc.hpp @@ -9,12 +9,12 @@ namespace sequant { class ExprPtr; } -namespace sequant::mbpt::sr { +namespace sequant::mbpt { /// CC is a derivation engine for the coupled-cluster method class CC { public: - enum Ansatz { + enum class Ansatz { /// traditional ansatz T, /// traditional orbital-optimized (singles-free) ansatz @@ -98,6 +98,6 @@ class CC { Ansatz ansatz_ = Ansatz::T; }; // class CC -} // namespace sequant::mbpt::sr +} // namespace sequant::mbpt #endif // SEQUANT_DOMAIN_MBPT_MODELS_CC_HPP diff --git a/SeQuant/domain/mbpt/mr.cpp b/SeQuant/domain/mbpt/mr.cpp deleted file mode 100644 index b638cefe3..000000000 --- a/SeQuant/domain/mbpt/mr.cpp +++ /dev/null @@ -1,766 +0,0 @@ -// -// Created by Eduard Valeyev on 2019-02-19. -// - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sequant { -namespace mbpt { -namespace mr { - -qninterval_t ncre(qns_t qns, const IndexSpace::Type& s) { - assert(s == IndexSpace::active_occupied || s == IndexSpace::active || - s == IndexSpace::active_unoccupied); - if (s == IndexSpace::active_occupied) - return qns[0]; - else if (s == IndexSpace::active) - return qns[2]; - else // if (s == IndexSpace::active_unoccupied) - return qns[4]; -} - -qninterval_t ncre(qns_t qns, const IndexSpace& s) { - assert((s.type() == IndexSpace::active_occupied || - s.type() == IndexSpace::active || - s.type() == IndexSpace::active_unoccupied) && - s.qns() == IndexSpace::nullqns); - if (s == IndexSpace::active_occupied) - return qns[0]; - else if (s == IndexSpace::active) - return qns[2]; - else // if (s == IndexSpace::active_unoccupied) - return qns[4]; -} - -qninterval_t ncre_occ(qns_t qns) { - return ncre(qns, IndexSpace::active_occupied); -} - -qninterval_t ncre_act(qns_t qns) { return ncre(qns, IndexSpace::active); } - -qninterval_t ncre_uocc(qns_t qns) { - return ncre(qns, IndexSpace::active_unoccupied); -} - -qninterval_t ncre(qns_t qns) { return qns[0] + qns[2] + qns[4]; } - -qninterval_t nann(qns_t qns, const IndexSpace::Type& s) { - assert(s == IndexSpace::active_occupied || s == IndexSpace::active || - s == IndexSpace::active_unoccupied); - if (s == IndexSpace::active_occupied) - return qns[1]; - else if (s == IndexSpace::active) - return qns[3]; - else // if (s == IndexSpace::active_unoccupied) - return qns[5]; -} - -qninterval_t nann(qns_t qns, const IndexSpace& s) { - assert((s.type() == IndexSpace::active_occupied || - s.type() == IndexSpace::active_unoccupied) && - s.qns() == IndexSpace::nullqns); - if (s == IndexSpace::active_occupied) - return qns[1]; - else if (s == IndexSpace::active) - return qns[3]; - else // if (s == IndexSpace::active_unoccupied) - return qns[5]; -} - -qninterval_t nann_occ(qns_t qns) { - return nann(qns, IndexSpace::active_occupied); -} - -qninterval_t nann_act(qns_t qns) { return nann(qns, IndexSpace::active); } - -qninterval_t nann_uocc(qns_t qns) { - return nann(qns, IndexSpace::active_unoccupied); -} - -qninterval_t nann(qns_t qns) { return qns[1] + qns[3] + qns[5]; } - -qns_t combine(qns_t a, qns_t b) { - // particle contractions (i.e. above Fermi level; N.B. active are above - // closed-shell Fermi level) - const auto ncontr_uocc = - qninterval_t{0, std::min(ncre(b, IndexSpace::active_unoccupied).upper(), - nann(a, IndexSpace::active_unoccupied).upper())}; - const auto ncontr_act = - qninterval_t{0, std::min(ncre(b, IndexSpace::active).upper(), - nann(a, IndexSpace::active).upper())}; - // hole contractions (i.e. below Fermi level) - const auto ncontr_occ = - qninterval_t{0, std::min(nann(b, IndexSpace::active_occupied).upper(), - ncre(a, IndexSpace::active_occupied).upper())}; - const auto nc_occ = - nonnegative(ncre(a, IndexSpace::active_occupied) + - ncre(b, IndexSpace::active_occupied) - ncontr_occ); - const auto nc_act = nonnegative(ncre(a, IndexSpace::active) + - ncre(b, IndexSpace::active) - ncontr_act); - const auto nc_uocc = - nonnegative(ncre(a, IndexSpace::active_unoccupied) + - ncre(b, IndexSpace::active_unoccupied) - ncontr_uocc); - const auto na_occ = - nonnegative(nann(a, IndexSpace::active_occupied) + - nann(b, IndexSpace::active_occupied) - ncontr_occ); - const auto na_act = nonnegative(nann(a, IndexSpace::active) + - nann(b, IndexSpace::active) - ncontr_act); - const auto na_uocc = - nonnegative(nann(a, IndexSpace::active_unoccupied) + - nann(b, IndexSpace::active_unoccupied) - ncontr_uocc); - return qns_t{nc_occ, na_occ, nc_act, na_act, nc_uocc, na_uocc}; -} - -} // namespace mr -} // namespace mbpt - -mbpt::mr::qns_t adjoint(mbpt::mr::qns_t qns) { - return mbpt::mr::qns_t{nann(qns, IndexSpace::active_occupied), - ncre(qns, IndexSpace::active_occupied), - nann(qns, IndexSpace::active), - ncre(qns, IndexSpace::active), - nann(qns, IndexSpace::active_unoccupied), - ncre(qns, IndexSpace::active_unoccupied)}; -} - -namespace mbpt { -namespace mr { - -OpMaker::OpMaker(OpType op, std::size_t nbra, std::size_t nket) - : base_type(op) { - nket = nket == std::numeric_limits::max() ? nbra : nket; - assert(nbra > 0 || nket > 0); - - const auto unocc = IndexSpace::active_maybe_unoccupied; - const auto occ = IndexSpace::active_maybe_occupied; - switch (to_class(op)) { - case OpClass::ex: - bra_spaces_ = decltype(bra_spaces_)(nbra, unocc); - ket_spaces_ = decltype(ket_spaces_)(nket, occ); - break; - case OpClass::deex: - bra_spaces_ = decltype(bra_spaces_)(nbra, occ); - ket_spaces_ = decltype(ket_spaces_)(nket, unocc); - break; - case OpClass::gen: - bra_spaces_ = decltype(bra_spaces_)(nbra, IndexSpace::complete); - ket_spaces_ = decltype(ket_spaces_)(nket, IndexSpace::complete); - break; - } -} - -OpMaker::OpMaker(OpType op, - const container::svector& cre_spaces, - const container::svector& ann_spaces) - : base_type(op) { - bra_spaces_ = cre_spaces; - ket_spaces_ = ann_spaces; -} - -#include - -ExprPtr T_act_(std::size_t K) { - return OpMaker(OpType::t, - container::svector(K, IndexSpace::active), - container::svector(K, IndexSpace::active))(); -} - -ExprPtr H_(std::size_t k) { - assert(k > 0 && k <= 2); - switch (k) { - case 1: - switch (get_default_context().vacuum()) { - case Vacuum::Physical: - return OpMaker(OpType::h, 1)(); - case Vacuum::SingleProduct: - return OpMaker(OpType::f̃, 1)(); - case Vacuum::MultiProduct: - return OpMaker(OpType::f, 1)(); - default: - abort(); - } - - case 2: - return OpMaker(OpType::g, 2)(); - - default: - abort(); - } -} - -ExprPtr H(std::size_t k) { - assert(k > 0 && k <= 2); - return k == 1 ? H_(1) : H_(1) + H_(2); -} - -ExprPtr F() { - // add \bar{g}^{\kappa x}_{\lambda y} \gamma^y_x with x,y in occ_space_type - auto make_g_contribution = [](const auto occ_space_type) { - return mbpt::OpMaker::make( - {IndexSpace::complete}, {IndexSpace::complete}, - [=](auto braidxs, auto ketidxs, Symmetry opsymm) { - auto m1 = Index::make_tmp_index( - IndexSpace{occ_space_type, IndexSpace::nullqns}); - auto m2 = Index::make_tmp_index( - IndexSpace{occ_space_type, IndexSpace::nullqns}); - assert(opsymm == Symmetry::antisymm || opsymm == Symmetry::nonsymm); - if (opsymm == Symmetry::antisymm) { - braidxs.push_back(m1); - ketidxs.push_back(m2); - return ex(to_wstring(mbpt::OpType::g), braidxs, ketidxs, - std::vector{}, Symmetry::antisymm) * - ex(to_wstring(mbpt::OpType::RDM), IndexList{m2}, - IndexList{m1}, IndexList{}, Symmetry::nonsymm); - } else { // opsymm == Symmetry::nonsymm - auto braidx_J = braidxs; - braidx_J.push_back(m1); - auto ketidxs_J = ketidxs; - ketidxs_J.push_back(m2); - auto braidx_K = braidxs; - braidx_K.push_back(m1); - auto ketidxs_K = ketidxs; - ketidxs_K.emplace(begin(ketidxs_K), m2); - return (ex(to_wstring(mbpt::OpType::g), braidx_J, ketidxs_J, - std::vector{}, Symmetry::nonsymm) - - ex(to_wstring(mbpt::OpType::g), braidx_K, ketidxs_K, - std::vector{}, Symmetry::nonsymm)) * - ex(to_wstring(mbpt::OpType::RDM), IndexList{m2}, - IndexList{m1}, IndexList{}, Symmetry::nonsymm); - } - }); - }; - - switch (get_default_context().vacuum()) { - case Vacuum::Physical: - return OpMaker(OpType::h, 1)() + - make_g_contribution(IndexSpace::maybe_occupied); // all occupieds - - case Vacuum::SingleProduct: - return OpMaker(OpType::f̃, 1)() + - make_g_contribution(IndexSpace::active); // actives only - - case Vacuum::MultiProduct: - return OpMaker(OpType::f, 1)(); - default: - abort(); - } -} - -ExprPtr vac_av(ExprPtr expr, std::vector> nop_connections, - bool use_top) { - const auto spinorbital = - get_default_context().spbasis() == SPBasis::spinorbital; - // convention is to use different label for spin-orbital and spin-free RDM - const auto rdm_label = spinorbital ? optype2label.at(OpType::RDM) : L"Γ"; - - FWickTheorem wick{expr}; - wick.use_topology(use_top) - .set_nop_connections(nop_connections) - .full_contractions(false); - auto result = wick.compute(); - simplify(result); - - // if obtained nontrivial result ... - if (!result.template is()) { - // need pre-postprocessing unless used extended Wick theorem - if (get_default_context().vacuum() != Vacuum::MultiProduct) { - assert(get_default_context().vacuum() != Vacuum::Invalid); - - // replace NormalOperator with RDM in target RDM space: - // - if Vacuum::Physical: IndexSpace::maybe_occupied - // - if Vacuum::SingleProduct: IndexSpace::active - const auto target_rdm_space_type = - get_default_context().vacuum() == Vacuum::SingleProduct - ? IndexSpace::active - : IndexSpace::maybe_occupied; - - // do this in 2 steps (TODO factor out these components?) - // 1. replace NOPs by RDM - // 2. project RDM indices onto the target RDM subspace - - // STEP1. replace NOPs by RDM - auto replace_nop_with_rdm = [&rdm_label, spinorbital](ExprPtr& exptr) { - auto replace = [&rdm_label, spinorbital](const auto& nop) -> ExprPtr { - using index_container = container::svector; - auto braidxs = nop.annihilators() | - ranges::views::transform( - [](const auto& op) { return op.index(); }) | - ranges::to(); - auto ketidxs = nop.creators() | - ranges::views::transform( - [](const auto& op) { return op.index(); }) | - ranges::to(); - assert(braidxs.size() == - ketidxs.size()); // need to handle particle # violating case? - const auto rank = braidxs.size(); - return ex( - rdm_label, braidxs, ketidxs, index_container{}, - rank > 1 && spinorbital ? Symmetry::antisymm : Symmetry::nonsymm); - }; - - if (exptr.template is()) { - exptr = replace(exptr.template as()); - } else if (exptr.template is()) { - exptr = replace(exptr.template as()); - } - }; - result->visit(replace_nop_with_rdm, /* atoms_only = */ true); - - // STEP 2: project RDM indices onto the target RDM subspace - // since RDM indices only make sense within a single TN expand + flatten - // first, then do the projection individually for each TN - expand(result); - // flatten(result); // TODO where is flatten? - auto project_rdm_indices_to_target = [&](ExprPtr& exptr) { - auto impl_for_single_tn = [&](ProductPtr& product_ptr) { - // enlist all indices and count their instances - auto for_each_index_in_tn = [](const auto& product_ptr, - const auto& op) { - ranges::for_each(product_ptr->factors(), [&](auto& factor) { - auto tensor_ptr = - std::dynamic_pointer_cast(factor); - if (tensor_ptr) { - ranges::for_each(tensor_ptr->_indices(), - [&](auto& idx) { op(idx, *tensor_ptr); }); - } - }); - }; - - // compute external indices - container::map indices_w_counts; - auto retrieve_indices_with_counts = - [&indices_w_counts](const auto& idx, auto& /* unused */) { - auto found_it = indices_w_counts.find(idx); - if (found_it != indices_w_counts.end()) { - found_it->second++; - } else { - indices_w_counts.emplace(idx, 1); - } - }; - for_each_index_in_tn(product_ptr, retrieve_indices_with_counts); - - container::set external_indices = - indices_w_counts | ranges::views::filter([](auto& idx_cnt) { - auto& [idx, cnt] = idx_cnt; - return cnt == 1; - }) | - ranges::views::keys | ranges::to>; - - // extract RDM-only and all indices - container::set rdm_indices; - std::set all_indices; - auto retrieve_rdm_and_all_indices = [&rdm_indices, &all_indices, - &rdm_label](const auto& idx, - const auto& tensor) { - all_indices.insert(idx); - if (tensor._label() == rdm_label) { - rdm_indices.insert(idx); - } - }; - for_each_index_in_tn(product_ptr, retrieve_rdm_and_all_indices); - - // compute RDM->target replacement rules - container::map replacement_rules; - ranges::for_each(rdm_indices, [&](const Index& idx) { - const auto target_type = - idx.space().type().intersection(target_rdm_space_type); - if (target_type != IndexSpace::nulltype) { - Index target = Index::make_tmp_index( - IndexSpace(target_type, idx.space().qns())); - replacement_rules.emplace(idx, target); - } - }); - - if (false) { - std::wcout << "expr = " << product_ptr->to_latex() - << "\n external_indices = "; - ranges::for_each(external_indices, [](auto& index) { - std::wcout << index.label() << " "; - }); - std::wcout << "\n replrules = "; - ranges::for_each(replacement_rules, [](auto& index) { - std::wcout << to_latex(index.first) << "\\to" - << to_latex(index.second) << "\\,"; - }); - std::wcout.flush(); - } - - if (!replacement_rules.empty()) { - sequant::detail::apply_index_replacement_rules( - product_ptr, replacement_rules, external_indices, all_indices); - } - }; - - if (exptr.template is()) { - auto product_ptr = exptr.template as_shared_ptr(); - impl_for_single_tn(product_ptr); - exptr = product_ptr; - } else { - assert(exptr.template is()); - auto result = std::make_shared(); - for (auto& summand : exptr.template as().summands()) { - assert(summand.template is()); - auto result_summand = summand.template as().clone(); - auto product_ptr = result_summand.template as_shared_ptr(); - impl_for_single_tn(product_ptr); - result->append(product_ptr); - } - exptr = result; - } - }; - project_rdm_indices_to_target(result); - - // rename dummy indices that might have been generated by - // project_rdm_indices_to_target - // + may combine terms - - // TensorCanonicalizer is given a custom comparer that moves active - // indices to the front external-vs-internal trait still takes precedence - auto current_index_comparer = - TensorCanonicalizer::instance()->index_comparer(); - TensorCanonicalizer::instance()->index_comparer( - [&](const Index& idx1, const Index& idx2) -> bool { - const auto idx1_active = idx1.space().type() == IndexSpace::active; - const auto idx2_active = idx2.space().type() == IndexSpace::active; - if (idx1_active) { - if (idx2_active) - return current_index_comparer(idx1, idx2); - else - return true; - } else { - if (idx2_active) - return false; - else - return current_index_comparer(idx1, idx2); - } - }); - simplify(result); - TensorCanonicalizer::instance()->index_comparer( - std::move(current_index_comparer)); - } - } - - if (Logger::get_instance().wick_stats) { - std::wcout << "WickTheorem stats: # of contractions attempted = " - << wick.stats().num_attempted_contractions - << " # of useful contractions = " - << wick.stats().num_useful_contractions << std::endl; - } - return result; -} - -namespace op { - -ExprPtr H_(std::size_t k) { - assert(k > 0 && k <= 2); - - switch (k) { - case 1: - return ex( - [vacuum = get_default_context().vacuum()]() -> std::wstring_view { - switch (vacuum) { - case Vacuum::Physical: - return optype2label.at(OpType::h); - case Vacuum::SingleProduct: - return optype2label.at(OpType::f̃); - case Vacuum::MultiProduct: - return optype2label.at(OpType::f); - default: - abort(); - } - }, - [=]() -> ExprPtr { return mbpt::mr::H_(1); }, - [=](qnc_t& qns) { - qns = combine(qnc_t{{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}}, - qns); - }); - - case 2: - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::g); }, - [=]() -> ExprPtr { return mbpt::mr::H_(2); }, - [=](qnc_t& qns) { - qns = combine(qnc_t{{0, 2}, {0, 2}, {0, 2}, {0, 2}, {0, 2}, {0, 2}}, - qns); - }); - - default: - abort(); - } -} - -ExprPtr H(std::size_t k) { - assert(k > 0 && k <= 2); - return k == 1 ? H_(1) : H_(1) + H_(2); -} - -ExprPtr T_(std::size_t K) { - assert(K > 0); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::t); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return mr::T_(K); - }, - [=](qnc_t& qns) { - qns = combine( - qnc_t{ - {0ul, 0ul}, {0ul, K}, {0ul, K}, {0ul, K}, {0ul, K}, {0ul, 0ul}}, - qns); - }); -} - -ExprPtr T_act_(std::size_t K) { - assert(K > 0); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::t); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return mr::T_act_(K); - }, - [=](qnc_t& qns) { - qns = combine( - qnc_t{ - {0ul, 0ul}, {0ul, 0ul}, {K, K}, {K, K}, {0ul, 0ul}, {0ul, 0ul}}, - qns); - }); -} - -ExprPtr T(std::size_t K) { - assert(K > 0); - - ExprPtr result; - for (auto k = 1ul; k <= K; ++k) { - result = k > 1 ? result + T_(k) : T_(k); - } - return result; -} - -ExprPtr Λ_(std::size_t K) { - assert(K > 0); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::λ); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return mr::Λ_(K); - }, - [=](qnc_t& qns) { - qns = combine( - qnc_t{ - {0ul, K}, {0ul, 0ul}, {0ul, K}, {0ul, K}, {0ul, 0ul}, {0ul, K}}, - qns); - }); -} - -ExprPtr Λ(std::size_t K) { - assert(K > 0); - - ExprPtr result; - for (auto k = 1ul; k <= K; ++k) { - result = k > 1 ? result + Λ_(k) : Λ_(k); - } - return result; -} - -ExprPtr A(std::int64_t K) { - assert(K != 0); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::A); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return mr::A(K, K); - }, - [=](qnc_t& qns) { - const std::size_t abs_K = std::abs(K); - if (K < 0) - qns = combine(qnc_t{{0ul, abs_K}, - {0ul, 0ul}, - {0ul, abs_K}, - {0ul, abs_K}, - {0ul, 0ul}, - {0ul, abs_K}}, - qns); - else - qns = combine(qnc_t{{0ul, 0ul}, - {0ul, abs_K}, - {0ul, abs_K}, - {0ul, abs_K}, - {0ul, abs_K}, - {0ul, 0ul}}, - qns); - }); -} - -// ExprPtr S(std::int64_t K) { -// assert(K != 0); -// return ex([]() -> std::wstring_view { return L"S"; }, -// [=]() -> ExprPtr { -// using namespace sequant::mbpt::sr; -// return mr::S(K, K); -// }, -// [=](qnc_t& qns) { -// const std::size_t abs_K = std::abs(K); -// if (K < 0) -// qns = combine(qnc_t{{0ul,abs_K}, {0ul,0ul}, -// {0ul,abs_K}, {0ul,abs_K}, {0ul,0ul}, {0ul,abs_K}}, -// qns); -// else -// qns = combine(qnc_t{{0ul,0ul}, {0ul,abs_K}, -// {0ul,abs_K}, {0ul,abs_K}, {0ul,abs_K}, {0ul,0ul}}, -// qns); -// }); -// } - -// ExprPtr P(std::int64_t K) { -// return get_default_context().spbasis() == SPBasis::spinfree ? S(-K) : -// A(-K); -// } - -bool can_change_qns(const ExprPtr& op_or_op_product, const qns_t target_qns, - const qns_t source_qns) { - qns_t qns = source_qns; - if (op_or_op_product.is()) { - const auto& op_product = op_or_op_product.as(); - for (auto& op_ptr : ranges::views::reverse(op_product.factors())) { - assert(op_ptr->template is()); - const auto& op = op_ptr->template as(); - qns = op(qns); - } - return qns.overlaps_with(target_qns); - } else if (op_or_op_product.is()) { - const auto& op = op_or_op_product.as(); - qns = op(); - return qns.overlaps_with(target_qns); - } else - throw std::invalid_argument( - "sequant::mbpt::sr::contains_rank(op_or_op_product): op_or_op_product " - "must be mbpt::sr::op_t or Product thereof"); -} - -using mbpt::mr::vac_av; - -#include - -} // namespace op - -} // namespace mr - -// must be defined including op.ipp since it's used there -template <> -bool is_vacuum(mr::qns_t qns) { - return qns == mr::qns_t{}; -} - -} // namespace mbpt - -template -std::wstring to_latex(const mbpt::Operator& op) { - using namespace sequant::mbpt; - using namespace sequant::mbpt::mr; - - auto result = L"{\\hat{" + utf_to_latex(op.label()) + L"}"; - - // check if operator has adjoint label, remove if present - auto base_lbl = sequant::to_wstring(op.label()); - if (base_lbl.back() == adjoint_label) base_lbl.pop_back(); - - auto it = label2optype.find(base_lbl); - OpType optype = OpType::invalid; - if (it != label2optype.end()) { // handle special cases - optype = it->second; - if (to_class(optype) == OpClass::gen) { - result += L"}"; - return result; - } - } - - // generic operator ... can only handle definite case - const auto dN = op(); - if (!is_definite(ncre_occ(dN)) || !is_definite(nann_occ(dN)) || - !is_definite(ncre_uocc(dN)) || !is_definite(nann_uocc(dN))) { - throw std::invalid_argument( - "to_latex(const Operator& op): " - "can only handle generic operators with definite cre/ann numbers"); - } - // pure quasiparticle creator/annihilator? - const auto qprank_cre = nann_occ(dN).lower() + ncre_uocc(dN).lower(); - const auto qprank_ann = ncre_occ(dN).lower() + nann_uocc(dN).lower(); - const auto qppure = qprank_cre == 0 || qprank_ann == 0; - auto qpaction = to_class(optype); - if (qppure) { - if (qprank_cre) { - // if operator's action implied by the label and actual action agrees, use - // subscript always - std::wstring baseline_char = (qpaction != OpClass::deex ? L"_" : L"^"); - if (nann_occ(dN).lower() == ncre_uocc(dN).lower()) - result += - baseline_char + L"{" + std::to_wstring(nann_occ(dN).lower()) + L"}"; - else - result += baseline_char + L"{" + std::to_wstring(nann_occ(dN).lower()) + - L"," + std::to_wstring(ncre_uocc(dN).lower()) + L"}"; - } else { - // if operator's action implied by the label and actual action agrees, use - // subscript always - std::wstring baseline_char = (qpaction != OpClass::deex ? L"^" : L"_"); - if (nann_uocc(dN).lower() == ncre_occ(dN).lower()) { - result += - baseline_char + L"{" + std::to_wstring(ncre_occ(dN).lower()) + L"}"; - } else - result += baseline_char + L"{" + std::to_wstring(ncre_occ(dN).lower()) + - L"," + std::to_wstring(nann_uocc(dN).lower()) + L"}"; - } - } else { // not pure qp creator/annihilator - result += L"_{" + std::to_wstring(nann_occ(dN).lower()) + L"," + - std::to_wstring(ncre_uocc(dN).lower()) + L"}^{" + - std::to_wstring(ncre_occ(dN).lower()) + L"," + - std::to_wstring(nann_uocc(dN).lower()) + L"}"; - } - result += L"}"; - return result; -} - -} // namespace sequant - -#include - -namespace sequant { -namespace mbpt { -template class Operator; -template class Operator; -} // namespace mbpt -} // namespace sequant diff --git a/SeQuant/domain/mbpt/mr.hpp b/SeQuant/domain/mbpt/mr.hpp deleted file mode 100644 index 0e19bdd40..000000000 --- a/SeQuant/domain/mbpt/mr.hpp +++ /dev/null @@ -1,259 +0,0 @@ -// -// Created by Eduard Valeyev on 2019-02-19. -// - -#ifndef SEQUANT_DOMAIN_MBPT_MR_HPP -#define SEQUANT_DOMAIN_MBPT_MR_HPP - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sequant { -namespace mbpt { -namespace mr { - -struct qns_tag; - -// clang-format off -/// multireference operator algebra can be screened by tracking the number of creators and annihilators in the (active) occupied, active, and (active) unoccupied space -/// the order of of elements is {# of occupied creators, # of occupied annihilators, # of active creators, # of active annihilators, # of unoccupied creators, # of unoccupied annihilators} -/// \note use signed integer, although could use unsigned in this case, so that can represent quantum numbers and their changes by the same type -// clang-format on -using qns_t = mbpt::QuantumNumberChange<6, qns_tag, std::int64_t>; -/// changes in quantum number represented by quantum numbers themselves -using qnc_t = qns_t; -using op_t = mbpt::Operator; - -// clang-format off -/// @return the number of creators in \p qns acting on space \p s -/// @pre `(s.type()==IndexSpace::Type::active_occupied || s.type()==IndexSpace::Type::active_unoccupied)&&s.qns()==IndexSpace::null_qns` -// clang-format on -qninterval_t ncre(qns_t qns, const IndexSpace& s); - -// clang-format off -/// @return the number of creators in \p qns acting on space \p s -/// @pre `s==IndexSpace::Type::active_occupied || s==IndexSpace::Type::active_unoccupied` -// clang-format on -qninterval_t ncre(qns_t qns, const IndexSpace::Type& s); - -// clang-format off -/// @return the number of creators in \p qns acting on the occupied space -// clang-format on -qninterval_t ncre_occ(qns_t qns); - -// clang-format off -/// @return the number of creators in \p qns acting on the active space -// clang-format on -qninterval_t ncre_act(qns_t qns); - -// clang-format off -/// @return the number of creators in \p qns acting on the unoccupied space -// clang-format on -qninterval_t ncre_uocc(qns_t qns); - -// clang-format off -/// @return the total number of creators in \p qns -// clang-format on -qninterval_t ncre(qns_t qns); - -// clang-format off -/// @return the number of annihilators in \p qns acting on space \p s -/// @pre `(s.type()==IndexSpace::Type::active_occupied || s.type()==IndexSpace::Type::active_unoccupied)&&s.qns()==IndexSpace::null_qns` -// clang-format on -qninterval_t nann(qns_t qns, const IndexSpace& s); - -// clang-format off -/// @return the number of annihilators in \p qns acting on space \p s -/// @pre `s==IndexSpace::Type::active_occupied || s==IndexSpace::Type::active_unoccupied` -// clang-format on -qninterval_t nann(qns_t qns, const IndexSpace::Type& s); - -// clang-format off -/// @return the number of annihilators in \p qns acting on the occupied space -// clang-format on -qninterval_t nann_occ(qns_t qns); - -// clang-format off -/// @return the number of annihilators in \p qns acting on the active space -// clang-format on -qninterval_t nann_act(qns_t qns); - -// clang-format off -/// @return the number of annihilators in \p qns acting on the unoccupied space -// clang-format on -qninterval_t nann_uocc(qns_t qns); - -// clang-format off -/// @return the total number of annihilators in \p qns -// clang-format on -qninterval_t nann(qns_t qns); - -/// combines 2 sets of quantum numbers using Wick's theorem -qns_t combine(qns_t, qns_t); - -} // namespace mr -} // namespace mbpt - -/// @param qns the quantum numbers to adjoint -/// @return the adjoint of \p qns -mbpt::mr::qns_t adjoint(mbpt::mr::qns_t); - -namespace mbpt { -namespace mr { - -// clang-format off -/// @brief makes a tensor-level fermionic many-body operator for use in single-reference methods - -/// A many-body operator has the following generic form: -/// \f$ \frac{1}{P} T_{b_1 b_2 \dots b_B}^{k_1 k_2 \dots k_K} A^{b_1 b_2 \dots b_B}_{k_1 k_2 \dots k_K} \f$ -/// where \f$ \{B,K\} \f$ are number of bra/ket indices of \f$ T \f$ or, equivalently, the number of creators/annihilators -/// of normal-ordered (w.r.t. the default, not necessarily Fermi vacuum) operator \f$ A \f$. -/// Indices \f$ \{ b_i \} \f$ / \f$ \{ k_i \} \f$ are (active) unoccupied/occupied for (pure) excitation operators, -/// are occupied/unoccupied for deexcitation operators; for general operators complete basis indices are assumed by default, -/// unless overridden by user manually. \f$ P \f$ is the "normalization" factor and depends on the vacuum used to define \f$ A \f$, -/// and indices \f$ \{ b_i \} \f$ / \f$ \{ k_i \} \f$. -/// @note The choice of unoccupied indices/spaces can be controlled by the default Formalism: -/// - if `get_default_formalism().sum_over_uocc() == SumOverUocc::Complete` IndexSpace::complete_unoccupied will be used instead of IndexSpace::active_unoccupied -/// - if `get_default_formalism().csv() == CSVFormalism::CSV` will use cluster-specific (e.g., PNO) unoccupied indices -/// @warning Tensor \f$ T \f$ will be antisymmetrized if `get_default_context().spbasis() == SPBasis::spinorbital`, else it will be particle-symmetric; the latter is only valid if # of bra and ket indices coincide. -// clang-format on -class OpMaker : public mbpt::OpMaker { - public: - using base_type = mbpt::OpMaker; - - using base_type::base_type; - - // clang-format off - /// @param[in] op the operator type: - /// - if @p op is a (pure) excitation operator bra/ket indices - /// will be IndexSpace::active_unoccupied/IndexSpace::active_occupied, - /// - for (pure) deexcitation @p op bra/ket will be IndexSpace::active_occupied/IndexSpace::active_unoccupied - /// - for general @p op bra/ket will be IndexSpace::complete - /// @param[in] nbra number of bra indices/creators - /// @param[in] nket number of ket indices/annihilators; if not specified, will be set to @p nbra - // clang-format on - OpMaker(OpType op, std::size_t nbra, - std::size_t nket = std::numeric_limits::max()); - - // clang-format off - /// Generic constructor for arbitrary cre/ann spaces - /// @param[in] op the operator type (is not considered for assigning the cre/ann spaces, but used to assign CSV index dependencies, if needed) - /// @param[in] cre_spaces spaces of creators - /// @param[in] ann_spaces spaces of annihilators - // clang-format off - OpMaker(OpType op, const container::svector& cre_spaces, const container::svector& ann_spaces); - - using base_type::operator(); -}; - -#include - -/// @name tensor-level operators -/// @{ - -ExprPtr T_act_(std::size_t K); - -// clang-format off -/// @brief `k`-body contribution to the "generic" Hamiltonian (in normal order relative to the default vacuum) -/// @param[in] k the rank of the particle interactions; only `k<=2` is -/// supported -// clang-format on -ExprPtr H_(std::size_t k); - -/// @brief total Hamiltonian including up to `k`-body interactions -/// @param[in] k the maximum rank of the particle interactions; only `k<=2` is -/// supported -ExprPtr H(std::size_t k = 2); - -/// @brief Fock operator -/// @param use_f_tensor if true, will use Fock tensor, else will use tensors -/// used to define `H_(1)` and `H_(2)` -ExprPtr F(bool use_f_tensor = true); - -/// @} - -/// computes the vacuum expectation value (VEV) - -/// @param[in] expr input expression -/// @param[in] nop_connections specifies the pairs of normal operators to be -/// connected -/// @param[in] use_top if true, topological equivalence will be utilized -/// @return the VEV -ExprPtr vac_av(ExprPtr expr, - std::vector> nop_connections = {}, - bool use_top = true); - -// these produce operator-level expressions -namespace op { - -/// @name MR MBPT operators - -/// @{ - -// clang-format off -/// @brief `k`-body contribution to the "generic" Hamiltonian (in normal order relative to the default vacuum) -/// @param[in] k the rank of the particle interactions; only `k<=2` is -/// supported -// clang-format on -ExprPtr H_(std::size_t k); - -/// @brief total Hamiltonian including up to `k`-body interactions -/// @param[in] k the maximum rank of the particle interactions; only `k<=2` is -/// supported -ExprPtr H(std::size_t k = 2); - -/// makes particle-conserving excitation operator of rank \p K -ExprPtr T_(std::size_t K); - -/// makes particle-conserving active replacement operator of rank \p K -ExprPtr T_act_(std::size_t K); - -/// makes sum of particle-conserving excitation operators of all ranks up to \p -/// K -ExprPtr T(std::size_t K); - -/// makes particle-conserving deexcitation operator of rank \p K -ExprPtr Λ_(std::size_t K); - -/// makes sum of particle-conserving deexcitation operators of all ranks up to -/// \p K -ExprPtr Λ(std::size_t K); - -/// @} - -/// @return true if \p op_or_op_product can change quantum numbers from \p -/// source_qns to \p target_qns -bool can_change_qns(const ExprPtr& op_or_op_product, const qns_t target_qns, - const qns_t source_qns = {}); - -#include - -} // namespace op - -} // namespace mr - -extern template class Operator; -extern template class Operator; - -} // namespace mbpt -} // namespace sequant - -#endif // SEQUANT_DOMAIN_MBPT_MR_HPP diff --git a/SeQuant/domain/mbpt/mr/op.impl.cpp b/SeQuant/domain/mbpt/mr/op.impl.cpp deleted file mode 100644 index 1950959fe..000000000 --- a/SeQuant/domain/mbpt/mr/op.impl.cpp +++ /dev/null @@ -1,75 +0,0 @@ -ExprPtr T_(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0); - assert(Nket > 0); - return OpMaker(OpType::t, Nbra, Nket)(); -} - -ExprPtr Λ_(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0); - assert(Nket > 0); - return OpMaker(OpType::λ, Nbra, Nket)(); -} - -namespace detail { - -/// constructs a sum of ops up to a given bra/ket rank -class op_impl { - OpType op_; - std::size_t nbra_, nket_; - - public: - op_impl(OpType op, std::size_t nbra, - std::size_t nket = std::numeric_limits::max()) - : op_(op), - nbra_(nbra), - nket_(nket == std::numeric_limits::max() ? nbra : nket) { - assert(nbra_ > 0 && nbra_ < std::numeric_limits::max()); - assert(nket_ > 0); - } - - void operator()(ExprPtr& result) { - if (op_ == OpType::t) - result = result ? result + T_(nbra_, nket_) : T_(nbra_, nket_); - else - assert(false && "unsupported op value"); - - if ((nbra_ > 1 && nket_ > 0) || (nbra_ > 0 && nket_ > 1)) { - op_impl{op_, nbra_ - 1, nket_ - 1}(result); - } - } -}; - -} // namespace detail - -/// makes excitation operator of all bra/ket ranks up to (and including) -/// @c Nbra/Nket -ExprPtr T(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0 && Nbra < std::numeric_limits::max()); - const auto Nket_ = - Nket == std::numeric_limits::max() ? Nbra : Nket; - assert(Nket_ > 0); - ExprPtr result; - detail::op_impl{OpType::t, Nbra, Nket_}(result); - return result; -} - -/// makes geminal excitation operator for ansatz @p ansatz -ExprPtr R12(IndexSpace::Type gg_space, int ansatz) { - assert(ansatz == 1 || ansatz == 2); - if (ansatz == 2) - return OpMaker( - OpType::R12, - {IndexSpace::complete_unoccupied, IndexSpace::complete_unoccupied}, - {gg_space, gg_space})(); - else - return OpMaker(OpType::R12, - {IndexSpace::other_unoccupied, IndexSpace::other_unoccupied}, - {gg_space, gg_space})(); -} - -/// makes deexcitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr A(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0); - assert(Nket > 0); - return OpMaker(OpType::A, Nbra, Nket)(); -} diff --git a/SeQuant/domain/mbpt/mr/op.impl.hpp b/SeQuant/domain/mbpt/mr/op.impl.hpp deleted file mode 100644 index f53b07d80..000000000 --- a/SeQuant/domain/mbpt/mr/op.impl.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/// makes excitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr T_(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes lambda deexcitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr Λ_(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes excitation operator of all bra/ket ranks up to (and including) @c -/// Nbra/Nket -ExprPtr T(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes deexcitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr A(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes geminal excitation operator from @p geminal_generating_space for -/// ansatz @p ansatz -/// @param[in] geminal_generating_space the space from which the geminal -/// excitations originate from; default = IndexSpace::active_occupied -/// @param[in] ansatz 1 or 2 -ExprPtr R12(IndexSpace::Type geminal_generating_space = - IndexSpace::active_maybe_occupied, - int ansatz = 2); diff --git a/SeQuant/domain/mbpt/op.cpp b/SeQuant/domain/mbpt/op.cpp index 8d73586cd..b501e5f18 100644 --- a/SeQuant/domain/mbpt/op.cpp +++ b/SeQuant/domain/mbpt/op.cpp @@ -1,20 +1,13 @@ #include #include -#include -#include -#include -#include -#include -#include +#include #include -#include #include +#include #include -#include - namespace sequant::mbpt { std::vector cardinal_tensor_labels() { @@ -71,36 +64,243 @@ OpClass to_class(OpType op) { } } -qninterval_t ncre(qns_t qns, IndexSpace s) { - assert(s.type() == IndexSpace::nonnulltype && s.qns() == IndexSpace::nullqns); - return qns[0]; +// excitation type qns will have qp creators for every space which intersects +// with the active hole space and +// qp annihilators wherever there is intersection with the active particle +// space. the presence of additional blocks depends on whether the +// corresponding active hole or active particle space is a base space. +qns_t excitation_type_qns(std::size_t K, const IndexSpace::QuantumNumbers SQN) { + qnc_t result; + if (get_default_context().vacuum() == Vacuum::Physical) { + result[0] = {0ul, K}; + result[1] = {0ul, K}; + } else { + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + // are the active qp spaces base spaces? + bool aps_base = isr->is_base(isr->particle_space(SQN)); + bool ahs_base = isr->is_base(isr->hole_space(SQN)); + + for (int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + result[i * 2] = {0ul, 0ul}; + result[i * 2 + 1] = {0ul, 0ul}; + if (base_space.qns() != SQN) continue; + + // ex -> creators in particle space + if (includes(isr->particle_space(SQN).type(), base_space.type())) { + result[i * 2] = {aps_base ? K : 0ul, K}; + } + // ex -> annihilators in hole space + if (includes(isr->hole_space(SQN).type(), base_space.type())) { + result[i * 2 + 1] = {ahs_base ? K : 0ul, K}; + } + } + } + return result; } -qninterval_t ncre(qns_t qns, IndexSpace::Type s) { - assert(s == IndexSpace::nonnulltype); - return qns[0]; +// sometimes we want to guarantee that a qns has an interval from 0-K +// regardless of the base spaces. +qns_t interval_excitation_type_qns(std::size_t K, + const IndexSpace::QuantumNumbers SQN) { + qnc_t result; + if (get_default_context().vacuum() == Vacuum::Physical) { + result[0] = {0ul, K}; + result[1] = {0ul, K}; + } else { + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + + for (int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + result[i * 2] = {0ul, 0ul}; + result[i * 2 + 1] = {0ul, 0ul}; + if (base_space.qns() != SQN) continue; + + // ex -> creators in particle space + if (includes(isr->particle_space(SQN).type(), base_space.type())) { + result[i * 2] = {0ul, K}; + } + // ex -> annihilators in hole space + if (includes(isr->hole_space(SQN).type(), base_space.type())) { + result[i * 2 + 1] = {0ul, K}; + } + } + } + return result; } -qninterval_t ncre(qns_t qns) { return qns[0]; } +qns_t deexcitation_type_qns(std::size_t K, + const IndexSpace::QuantumNumbers SQN) { + qnc_t result; + if (get_default_context().vacuum() == Vacuum::Physical) { + result[0] = {0ul, K}; + result[1] = {0ul, K}; + } else { + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + bool aps_base = isr->is_base(isr->particle_space(SQN)); + bool ahs_base = isr->is_base(isr->hole_space(SQN)); + for (int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + result[i * 2] = {0ul, 0ul}; + result[i * 2 + 1] = {0ul, 0ul}; + if (base_space.qns() != SQN) continue; + + // deex -> annihilators in particle space + if (includes(isr->particle_space(SQN).type(), base_space.type())) { + result[i * 2 + 1] = {aps_base ? K : 0ul, K}; + } + // deex -> creators in hole space + if (includes(isr->hole_space(SQN).type(), base_space.type())) { + result[i * 2] = {ahs_base ? K : 0ul, K}; + } + } + } + return result; +} + +// sometimes we want to guarantee that a qns has an interval from 0-K +// regardless of the base spaces. +qns_t interval_deexcitation_type_qns(std::size_t K, + const IndexSpace::QuantumNumbers SQN) { + qnc_t result; + if (get_default_context().vacuum() == Vacuum::Physical) { + result[0] = {0ul, K}; + result[1] = {0ul, K}; + } else { + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + for (int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + result[i * 2] = {0ul, 0ul}; + result[i * 2 + 1] = {0ul, 0ul}; + if (base_space.qns() != SQN) continue; + + // deex -> annihilators in particle space + if (includes(isr->particle_space(SQN).type(), base_space.type())) { + result[i * 2 + 1] = {0ul, K}; + } + // deex -> creators in hole space + if (includes(isr->hole_space(SQN).type(), base_space.type())) { + result[i * 2] = {0ul, K}; + } + } + } + return result; +} -qninterval_t nann(qns_t qns, IndexSpace s) { - assert(s.type() == IndexSpace::nonnulltype && s.qns() == IndexSpace::nullqns); - return qns[1]; +qns_t general_type_qns(std::size_t K) { + qnc_t result; + for (int i = 0; i < result.size(); i++) { + result[i] = {0, K}; + } + return result; } -qninterval_t nann(qns_t qns, IndexSpace::Type s) { - assert(s == IndexSpace::nonnulltype); - return qns[1]; +qns_t generic_excitation_qns(std::size_t particle_rank, std::size_t hole_rank, + IndexSpace particle_space, IndexSpace hole_space, + const IndexSpace::QuantumNumbers SQN) { + qnc_t result; + if (get_default_context().vacuum() == Vacuum::Physical) { + result[0] = {0ul, hole_rank}; + result[1] = {0ul, particle_rank}; + } else { + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + bool aps_base = isr->is_base(isr->particle_space(SQN)); + bool ahs_base = isr->is_base(isr->hole_space(SQN)); + for (int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + result[i * 2] = {0ul, 0ul}; + result[i * 2 + 1] = {0ul, 0ul}; + if (base_space.qns() != SQN) continue; + + // ex -> creators in particle space + if (includes(particle_space.type(), base_space.type())) { + result[i * 2] = {aps_base ? particle_rank : 0ul, + particle_rank}; // creators + } + // ex -> annihilators in hole space + if (includes(hole_space.type(), base_space.type())) { + result[i * 2 + 1] = {ahs_base ? hole_rank : 0ul, + hole_rank}; // annihilators + } + } + } + return result; } -qninterval_t nann(qns_t qns) { return qns[1]; } +qns_t generic_deexcitation_qns(std::size_t particle_rank, std::size_t hole_rank, + IndexSpace particle_space, IndexSpace hole_space, + const IndexSpace::QuantumNumbers SQN) { + qnc_t result; + if (get_default_context().vacuum() == Vacuum::Physical) { + result[0] = {0ul, particle_rank}; + result[1] = {0ul, hole_rank}; + } else { + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + bool aps_base = isr->is_base(isr->particle_space(SQN)); + bool ahs_base = isr->is_base(isr->hole_space(SQN)); + for (int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + result[i * 2] = {0ul, 0ul}; + result[i * 2 + 1] = {0ul, 0ul}; + if (base_space.qns() != SQN) continue; + // deex -> creators in hole space + if (includes(hole_space.type(), base_space.type())) { + result[i * 2] = {ahs_base ? hole_rank : 0ul, hole_rank}; // creators + } + // deex -> annihilators in particle space + if (includes(particle_space.type(), base_space.type())) { + result[i * 2 + 1] = {aps_base ? particle_rank : 0ul, + particle_rank}; // annihilators + } + } + } + return result; +} + +// by counting the number of contractions between indices of proper type, we can +// know the quantum numbers +// of a combined result. qns_t combine(qns_t a, qns_t b) { - const auto ncontr = - qninterval_t{0, std::min(ncre(b).upper(), nann(a).upper())}; - const auto nc = nonnegative(ncre(a) + ncre(b) - ncontr); - const auto na = nonnegative(nann(a) + nann(b) - ncontr); - return qns_t{nc, na}; + assert(a.size() == b.size()); + qns_t result; + + if (get_default_context().vacuum() == Vacuum::Physical) { + qns_t result; + const auto ncontr = qninterval_t{0, std::min(b[0].upper(), a[1].upper())}; + const auto nc = nonnegative(a[0] + b[0] - ncontr); + const auto na = nonnegative(a[1] + b[1] - ncontr); + result[0] = nc; + result[1] = na; + return result; + } else if (get_default_context().vacuum() == Vacuum::SingleProduct) { + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + for (auto i = 0; i < base_spaces.size(); i++) { + auto cre = i * 2; + auto ann = (i * 2) + 1; + auto base_is_fermi_occupied = isr->is_pure_occupied( + base_spaces[i]); // need to distinguish particle and hole + // contractions. + auto ncontr_space = + base_is_fermi_occupied + ? qninterval_t{0, std::min(b[ann].upper(), a[cre].upper())} + : qninterval_t{0, std::min(b[cre].upper(), a[ann].upper())}; + auto nc_space = nonnegative(b[cre] + a[cre] - ncontr_space); + auto na_space = nonnegative(b[ann] + a[ann] - ncontr_space); + result[cre] = nc_space; + result[ann] = na_space; + } + return result; + } else { + throw "unsupported vacuum context."; + } } // must be defined including op.ipp since it's used there @@ -114,7 +314,17 @@ bool is_vacuum(qns_t qns) { namespace sequant { mbpt::qns_t adjoint(mbpt::qns_t qns) { - return mbpt::qns_t{nann(qns), ncre(qns)}; + mbpt::qns_t new_qnst; + new_qnst.resize(qns.size()); + auto is_even = [](int i) { return i % 2 == 0; }; + for (int i = 0; i < qns.size(); i++) { + if (is_even(i)) { + new_qnst[i + 1] = qns[i]; + } else { + new_qnst[i - 1] = qns[i]; + } + } + return new_qnst; } template @@ -125,30 +335,70 @@ std::wstring to_latex(const mbpt::Operator& op) { // check if operator has adjoint label, remove if present for base label auto base_lbl = sequant::to_wstring(op.label()); - if (base_lbl.back() == adjoint_label) base_lbl.pop_back(); + bool is_adjoint = false; + if (base_lbl.back() == adjoint_label) { + is_adjoint = true; + base_lbl.pop_back(); + } auto it = label2optype.find(base_lbl); + OpType optype = OpType::invalid; if (it != label2optype.end()) { // handle special cases - const auto optype = it->second; + optype = it->second; if (to_class(optype) == OpClass::gen) { result += L"}"; return result; } } + std::wstring baseline_char = is_adjoint ? L"^" : L"_"; + if (get_default_context().vacuum() == Vacuum::Physical) { + if (op()[0] == op()[1]) { // particle conserving + result += L"_{" + std::to_wstring(op()[0].lower()) + L"}"; + } else { // non-particle conserving + result += L"_{" + std::to_wstring(op()[1].lower()) + L"}^{" + + std::to_wstring(op()[0].lower()) + L"}"; + } + } else { // single product vacuum + auto nann_p = is_adjoint ? op().ncre_particles() : op().nann_particles(); + auto ncre_h = is_adjoint ? op().nann_holes() : op().ncre_holes(); + auto ncre_p = is_adjoint ? op().nann_particles() : op().ncre_particles(); + auto nann_h = is_adjoint ? op().ncre_holes() : op().nann_holes(); - // generic operator ... can only handle definite case - const auto dN = op(); - if (!is_definite(ncre(dN)) || !is_definite(nann(dN))) { - throw std::invalid_argument( - "to_latex(const Operator& op): " - "can only handle generic operators with definite cre/ann numbers"); - } - const auto dN_total = ncre(dN).lower() - nann(dN).lower(); - if (dN_total == 0) { // N-conserving - result += L"_{" + std::to_wstring(ncre(dN).lower()) + L"}"; - } else { // N-nonconserving - result += L"_{" + std::to_wstring(nann(dN).lower()) + L"}^{" + - std::to_wstring(ncre(dN).lower()) + L"}"; + if (!is_definite(nann_p) || !is_definite(ncre_h) || !is_definite(ncre_p) || + !is_definite(nann_h)) { + throw std::invalid_argument( + "to_latex(const Operator& op): " + "can only handle generic operators with definite cre/ann numbers"); + } + + // pure quasiparticle creator/annihilator? + const auto qprank_cre = ncre_p.lower() + nann_h.lower(); + const auto qprank_ann = nann_p.lower() + ncre_h.lower(); + const auto qppure = qprank_cre == 0 || qprank_ann == 0; + if (qppure) { + if (qprank_cre) { + if (ncre_p.lower() == nann_h.lower()) { // q-particle conserving + result += + baseline_char + L"{" + std::to_wstring(nann_h.lower()) + L"}"; + } else { // particle non-conserving + result += baseline_char + L"{" + std::to_wstring(nann_h.lower()) + + L"," + std::to_wstring(ncre_p.lower()) + L"}"; + } + } else { + if (ncre_h.lower() == nann_p.lower()) { // q-particle conserving + result += + baseline_char + L"{" + std::to_wstring(ncre_h.lower()) + L"}"; + } else { // q-particle non-conserving + result += baseline_char + L"{" + std::to_wstring(ncre_h.lower()) + + L"," + std::to_wstring(nann_p.lower()) + L"}"; + } + } + } else { // not pure qp creator/annihilator + result += L"_{" + std::to_wstring(nann_h.lower()) + L"," + + std::to_wstring(ncre_p.lower()) + L"}^{" + + std::to_wstring(ncre_h.lower()) + L"," + + std::to_wstring(nann_p.lower()) + L"}"; + } } result += L"}"; return result; @@ -161,8 +411,8 @@ std::wstring to_latex(const mbpt::Operator& op) { namespace sequant::mbpt { template -OpMaker::OpMaker(OpType op, std::initializer_list bras, - std::initializer_list kets) +OpMaker::OpMaker(OpType op, std::initializer_list bras, + std::initializer_list kets) : op_(op), bra_spaces_(bras.begin(), bras.end()), ket_spaces_(kets.begin(), kets.end()) { @@ -172,22 +422,68 @@ OpMaker::OpMaker(OpType op, std::initializer_list bras, template OpMaker::OpMaker(OpType op) : op_(op) {} +template +OpMaker::OpMaker(OpType op, std::size_t nbra, std::size_t nket, + IndexSpace particle_space, IndexSpace hole_space) { + op_ = op; + assert(nbra > 0 || nket > 0); + auto isr = get_default_context().index_space_registry(); + switch (to_class(op)) { + case OpClass::ex: + bra_spaces_ = decltype(bra_spaces_)(nbra, particle_space); + ket_spaces_ = decltype(ket_spaces_)(nket, hole_space); + break; + case OpClass::deex: + bra_spaces_ = decltype(bra_spaces_)(nbra, hole_space); + ket_spaces_ = decltype(ket_spaces_)(nket, particle_space); + break; + case OpClass::gen: + bra_spaces_ = decltype(bra_spaces_)(nbra, isr->complete_space(Spin::any)); + ket_spaces_ = decltype(ket_spaces_)(nket, isr->complete_space(Spin::any)); + break; + } +} + +template +OpMaker::OpMaker(OpType op, std::size_t nparticle) { + op_ = op; + auto isr = get_default_context().index_space_registry(); + auto current_context = get_default_context(); + const auto hole_space = isr->hole_space(Spin::any); + const auto particle_space = isr->particle_space(Spin::any); + switch (to_class(op)) { + case OpClass::ex: + bra_spaces_ = decltype(bra_spaces_)(nparticle, particle_space); + ket_spaces_ = decltype(ket_spaces_)(nparticle, hole_space); + break; + case OpClass::deex: + bra_spaces_ = decltype(bra_spaces_)(nparticle, hole_space); + ket_spaces_ = decltype(ket_spaces_)(nparticle, particle_space); + break; + case OpClass::gen: + bra_spaces_ = + decltype(bra_spaces_)(nparticle, isr->complete_space(Spin::any)); + ket_spaces_ = + decltype(ket_spaces_)(nparticle, isr->complete_space(Spin::any)); + break; + } +} + template ExprPtr OpMaker::operator()(std::optional dep, std::optional opsymm_opt) const { + auto isr = get_default_context(Statistics::FermiDirac).index_space_registry(); // if not given dep, use mbpt::Context::CSV to determine whether to use // dependent indices for pure (de)excitation ops - if (!dep && get_default_formalism().csv() == mbpt::CSV::Yes) { + if (!dep && get_default_mbpt_context().csv() == mbpt::CSV::Yes) { if (to_class(op_) == OpClass::ex) { for (auto&& s : bra_spaces_) { - assert(s == IndexSpace::complete_unoccupied || - s == IndexSpace::active_unoccupied); + assert(isr->contains_unoccupied(s)); } dep = UseDepIdx::Bra; } else if (to_class(op_) == OpClass::deex) { for (auto&& s : ket_spaces_) { - assert(s == IndexSpace::complete_unoccupied || - s == IndexSpace::active_unoccupied); + assert(isr->contains_unoccupied(s)); } dep = UseDepIdx::Ket; } else { @@ -212,4 +508,716 @@ template class OpMaker; template class Operator; template class Operator; +inline namespace op { + +namespace tensor { +ExprPtr H_(std::size_t k) { + assert(k > 0 && k <= 2); + switch (k) { + case 1: + switch (get_default_context().vacuum()) { + case Vacuum::Physical: + return OpMaker(OpType::h, 1)(); + case Vacuum::SingleProduct: + return OpMaker(OpType::f, 1)(); + case Vacuum::MultiProduct: + return OpMaker(OpType::f, 1)(); + default: + abort(); + } + + case 2: + return OpMaker(OpType::g, 2)(); + + default: + abort(); + } +} + +ExprPtr H(std::size_t k) { + assert(k > 0 && k <= 2); + return k == 1 ? tensor::H_(1) : tensor::H_(1) + tensor::H_(2); +} + +ExprPtr F(bool use_tensor, IndexSpace reference_occupied) { + if (use_tensor) { + return OpMaker(OpType::f, 1)(); + } else { // explicit density matrix construction + assert(reference_occupied); // cannot explicitly instantiate fock operator + // without providing an occupied indexspace + // add \bar{g}^{\kappa x}_{\lambda y} \gamma^y_x with x,y in occ_space_type + auto make_g_contribution = [](const auto occ_space) { + auto isr = get_default_context().index_space_registry(); + return mbpt::OpMaker::make( + {isr->complete_space(Spin::any)}, {isr->complete_space(Spin::any)}, + [=](auto braidxs, auto ketidxs, Symmetry opsymm) { + auto m1 = Index::make_tmp_index(occ_space); + auto m2 = Index::make_tmp_index(occ_space); + assert(opsymm == Symmetry::antisymm || opsymm == Symmetry::nonsymm); + if (opsymm == Symmetry::antisymm) { + braidxs.push_back(m1); + ketidxs.push_back(m2); + return ex(to_wstring(mbpt::OpType::g), braidxs, ketidxs, decltype(braidxs){}, + Symmetry::antisymm) * + ex(to_wstring(mbpt::OpType::δ), IndexList{m2}, + IndexList{m1}, IndexList{}, Symmetry::nonsymm); + } else { // opsymm == Symmetry::nonsymm + auto braidx_J = braidxs; + braidx_J.push_back(m1); + auto ketidxs_J = ketidxs; + ketidxs_J.push_back(m2); + auto braidx_K = braidxs; + braidx_K.push_back(m1); + auto ketidxs_K = ketidxs; + using std::begin; + ketidxs_K.emplace(begin(ketidxs_K), m2); + return (ex(to_wstring(mbpt::OpType::g), braidx_J, + ketidxs_J, decltype(braidx_J){}, Symmetry::nonsymm) - + ex(to_wstring(mbpt::OpType::g), braidx_K, + ketidxs_K, decltype(braidx_K){}, Symmetry::nonsymm)) * + ex(to_wstring(mbpt::OpType::δ), IndexList{m2}, + IndexList{m1}, IndexList{}, Symmetry::nonsymm); + } + }); + }; + auto isr = get_default_context().index_space_registry(); + return OpMaker(OpType::h, 1)() + + make_g_contribution(reference_occupied); + } +} + +ExprPtr T_(std::size_t K) { + return OpMaker(OpType::t, K)(); +} + +ExprPtr T(std::size_t K, bool skip1) { + assert(K > (skip1 ? 1 : 0)); + ExprPtr result; + for (auto k = skip1 ? 2ul : 1ul; k <= K; ++k) { + result += tensor::T_(k); + } + return result; +} + +ExprPtr Λ_(std::size_t K) { + return OpMaker(OpType::λ, K)(); +} + +ExprPtr Λ(std::size_t K) { + assert(K > 0); + + ExprPtr result; + for (auto k = 1ul; k <= K; ++k) { + result = k > 1 ? result + tensor::Λ_(k) : tensor::Λ_(k); + } + return result; +} + +ExprPtr R_(std::size_t nbra, std::size_t nket, IndexSpace hole_space, + IndexSpace particle_space) { + return OpMaker(OpType::R, nbra, nket, particle_space, + hole_space)(); +} + +ExprPtr L_(std::size_t nbra, std::size_t nket, IndexSpace hole_space, + IndexSpace particle_space) { + return OpMaker(OpType::L, nbra, nket, particle_space, + hole_space)(); +} + +ExprPtr P(std::int64_t K) { + return get_default_context().spbasis() == SPBasis::spinfree ? tensor::S(-K) + : tensor::A(-K); +} + +ExprPtr A(std::int64_t K) { + auto isr = get_default_context().index_space_registry(); + assert(K != 0); + container::svector creators; + container::svector annihilators; + if (K > 0) // ex + for ([[maybe_unused]] auto i : ranges::views::iota(0, K)) + annihilators.emplace_back(isr->hole_space(Spin::any)); + else // deex + for ([[maybe_unused]] auto i : ranges::views::iota(0, -K)) + creators.emplace_back(isr->hole_space(Spin::any)); + if (K > 0) // ex + for ([[maybe_unused]] auto i : ranges::views::iota(0, K)) + creators.emplace_back(isr->particle_space(Spin::any)); + else // deex + for ([[maybe_unused]] auto i : ranges::views::iota(0, -K)) + annihilators.emplace_back(isr->particle_space(Spin::any)); + + std::optional::UseDepIdx> dep; + if (get_default_mbpt_context().csv() == mbpt::CSV::Yes) + dep = K > 0 ? OpMaker::UseDepIdx::Bra + : OpMaker::UseDepIdx::Ket; + return OpMaker(OpType::A, creators, annihilators)( + dep, {Symmetry::antisymm}); +} + +ExprPtr S(std::int64_t K) { + auto isr = get_default_context().index_space_registry(); + assert(K != 0); + container::svector creators; + container::svector annihilators; + if (K > 0) // ex + for ([[maybe_unused]] auto i : ranges::views::iota(0, K)) + annihilators.emplace_back(isr->hole_space(Spin::any)); + else // deex + for ([[maybe_unused]] auto i : ranges::views::iota(0, -K)) + creators.emplace_back(isr->hole_space(Spin::any)); + if (K > 0) // ex + for ([[maybe_unused]] auto i : ranges::views::iota(0, K)) + creators.emplace_back(isr->particle_space(Spin::any)); + else // deex + for ([[maybe_unused]] auto i : ranges::views::iota(0, -K)) + annihilators.emplace_back(isr->particle_space(Spin::any)); + + std::optional::UseDepIdx> dep; + if (get_default_mbpt_context().csv() == mbpt::CSV::Yes) + dep = K > 0 ? OpMaker::UseDepIdx::Bra + : OpMaker::UseDepIdx::Ket; + return OpMaker(OpType::S, creators, annihilators)( + dep, {Symmetry::nonsymm}); +} + +ExprPtr H_pt(std::size_t order, std::size_t R) { + assert(order == 1 && + "sequant::sr::H_pt(): only supports first order perturbation"); + assert(R > 0); + return OpMaker(OpType::h_1, R)(); +} + +ExprPtr T_pt_(std::size_t order, std::size_t K) { + assert(order == 1 && + "sequant::sr::T_pt_(): only supports first order perturbation"); + return OpMaker(OpType::t_1, K)(); +} + +ExprPtr T_pt(std::size_t order, std::size_t K, bool skip1) { + assert(K > (skip1 ? 1 : 0)); + ExprPtr result; + for (auto k = (skip1 ? 2ul : 1ul); k <= K; ++k) { + result = k > 1 ? result + tensor::T_pt_(order, k) : tensor::T_pt_(order, k); + } + return result; +} + +ExprPtr Λ_pt_(std::size_t order, std::size_t K) { + assert(order == 1 && + "sequant::sr::Λ_pt_(): only supports first order perturbation"); + return OpMaker(OpType::λ_1, K)(); +} + +ExprPtr Λ_pt(std::size_t order, std::size_t K, bool skip1) { + assert(K > (skip1 ? 1 : 0)); + ExprPtr result; + for (auto k = (skip1 ? 2ul : 1ul); k <= K; ++k) { + result = k > 1 ? result + tensor::Λ_pt_(order, k) : tensor::Λ_pt_(order, k); + } + return result; +} + +} // namespace tensor + +ExprPtr H_(std::size_t k) { + assert(k > 0 && k <= 2); + switch (k) { + case 1: + return ex( + [vacuum = get_default_context().vacuum()]() -> std::wstring_view { + switch (vacuum) { + case Vacuum::Physical: + return L"h"; + case Vacuum::SingleProduct: + return L"f"; + case Vacuum::MultiProduct: + return L"f"; + default: + abort(); + } + }, + [=]() -> ExprPtr { return tensor::H_(1); }, + [=](qnc_t& qns) { + qnc_t op_qnc_t = general_type_qns(1); + qns = combine(op_qnc_t, qns); + }); + + case 2: + return ex([]() -> std::wstring_view { return L"g"; }, + [=]() -> ExprPtr { return tensor::H_(2); }, + [=](qnc_t& qns) { + qnc_t op_qnc_t = general_type_qns(2); + qns = combine(op_qnc_t, qns); + }); + + default: + abort(); + } +} + +ExprPtr H(std::size_t k) { + assert(k > 0 && k <= 2); + return k == 1 ? H_(1) : H_(1) + H_(2); +} + +ExprPtr T_(std::size_t K) { + assert(K > 0); + return ex([]() -> std::wstring_view { return L"t"; }, + [=]() -> ExprPtr { return tensor::T_(K); }, + [=](qnc_t& qns) { + qnc_t op_qnc_t = excitation_type_qns(K); + qns = combine(op_qnc_t, qns); + }); +} + +ExprPtr T(std::size_t K, bool skip1) { + assert(K > (skip1 ? 1 : 0)); + ExprPtr result; + for (auto k = skip1 ? 2ul : 1ul; k <= K; ++k) { + result += T_(k); + } + return result; +} + +ExprPtr Λ_(std::size_t K) { + assert(K > 0); + return ex([]() -> std::wstring_view { return L"λ"; }, + [=]() -> ExprPtr { return tensor::Λ_(K); }, + [=](qnc_t& qns) { + qnc_t op_qnc_t = deexcitation_type_qns(K); + qns = combine(op_qnc_t, qns); + }); +} + +ExprPtr Λ(std::size_t K) { + assert(K > 0); + ExprPtr result; + for (auto k = 1ul; k <= K; ++k) { + result = k > 1 ? result + Λ_(k) : Λ_(k); + } + return result; +} + +ExprPtr F(bool use_f_tensor, IndexSpace occupied_density) { + if (use_f_tensor) { + return ex( + []() -> std::wstring_view { return L"f"; }, + [=]() -> ExprPtr { return tensor::F(true, occupied_density); }, + [=](qnc_t& qns) { + qnc_t op_qnc_t = general_type_qns(1); + qns = combine(op_qnc_t, qns); + }); + } else { + throw "non-tensor use at operator level not yet supported"; + } +} + +ExprPtr A(std::int64_t K) { + auto isr = get_default_context().index_space_registry(); + assert(K != 0); + return ex([]() -> std::wstring_view { return L"A"; }, + [=]() -> ExprPtr { return tensor::A(K); }, + [=](qnc_t& qns) { + const std::size_t abs_K = std::abs(K); + if (K < 0) { + qnc_t op_qnc_t = deexcitation_type_qns(abs_K); + qns = combine(op_qnc_t, qns); + } else { + qnc_t op_qnc_t = excitation_type_qns(abs_K); + qns = combine(op_qnc_t, qns); + } + }); +} + +ExprPtr S(std::int64_t K) { + assert(K != 0); + return ex([]() -> std::wstring_view { return L"S"; }, + [=]() -> ExprPtr { return tensor::S(K); }, + [=](qnc_t& qns) { + const std::size_t abs_K = std::abs(K); + if (K < 0) { + qnc_t op_qnc_t = deexcitation_type_qns(abs_K); + qns = combine(op_qnc_t, qns); + } else { + qnc_t op_qnc_t = excitation_type_qns(abs_K); + qns = combine(op_qnc_t, qns); + } + }); +} + +ExprPtr P(std::int64_t K) { + return get_default_context().spbasis() == SPBasis::spinfree ? S(-K) : A(-K); +} + +ExprPtr H_pt(std::size_t order, std::size_t R) { + assert(R > 0); + assert(order == 1 && "only first order perturbation is supported now"); + return ex( + []() -> std::wstring_view { return optype2label.at(OpType::h_1); }, + [=]() -> ExprPtr { return tensor::H_pt(order, R); }, + [=](qnc_t& qns) { qns = combine(general_type_qns(R), qns); }); +} + +ExprPtr T_pt_(std::size_t order, std::size_t K) { + assert(K > 0); + assert(order == 1 && "only first order perturbation is supported now"); + return ex( + []() -> std::wstring_view { return optype2label.at(OpType::t_1); }, + [=]() -> ExprPtr { return tensor::T_pt_(order, K); }, + [=](qnc_t& qns) { qns = combine(excitation_type_qns(K), qns); }); +} + +ExprPtr T_pt(std::size_t order, std::size_t K, bool skip1) { + assert(K > (skip1 ? 1 : 0)); + ExprPtr result; + for (auto k = (skip1 ? 2ul : 1ul); k <= K; ++k) { + result = k > 1 ? result + T_pt_(order, k) : T_pt_(order, k); + } + return result; +} + +ExprPtr Λ_pt_(std::size_t order, std::size_t K) { + assert(K > 0); + assert(order == 1 && "only first order perturbation is supported now"); + return ex( + []() -> std::wstring_view { return optype2label.at(OpType::λ_1); }, + [=]() -> ExprPtr { return tensor::Λ_pt_(order, K); }, + [=](qnc_t& qns) { qns = combine(deexcitation_type_qns(K), qns); }); +} + +ExprPtr Λ_pt(std::size_t order, std::size_t K, bool skip1) { + assert(K > (skip1 ? 1 : 0)); + ExprPtr result; + for (auto k = (skip1 ? 2ul : 1ul); k <= K; ++k) { + result = k > 1 ? result + Λ_pt_(order, k) : Λ_pt_(order, k); + } + return result; +} + +ExprPtr R_(std::size_t Nbra, std::size_t Nket, IndexSpace hole_space, + IndexSpace particle_space) { + return ex( + []() -> std::wstring_view { return optype2label.at(OpType::R); }, + [=]() -> ExprPtr { + return tensor::R_(Nbra, Nket, hole_space, particle_space); + }, + [=](qnc_t& qns) { + qns = combine( + generic_excitation_qns(Nket, Nbra, particle_space, hole_space), + qns); + }); +} + +ExprPtr L_(std::size_t Nbra, std::size_t Nket, IndexSpace hole_space, + IndexSpace particle_space) { + return ex( + []() -> std::wstring_view { return optype2label.at(OpType::L); }, + [=]() -> ExprPtr { + return tensor::L_(Nbra, Nket, hole_space, particle_space); + }, + [=](qnc_t& qns) { + qns = combine( + generic_deexcitation_qns(Nbra, Nket, particle_space, hole_space), + qns); + }); +} + +bool can_change_qns(const ExprPtr& op_or_op_product, const qns_t target_qns, + const qns_t source_qns = {}) { + qns_t qns = source_qns; + if (op_or_op_product.is()) { + const auto& op_product = op_or_op_product.as(); + for (auto& op_ptr : ranges::views::reverse(op_product.factors())) { + assert(op_ptr->template is()); + const auto& op = op_ptr->template as(); + qns = op(qns); + } + return qns.overlaps_with(target_qns); + } else if (op_or_op_product.is()) { + const auto& op = op_or_op_product.as(); + qns = op(); + return qns.overlaps_with(target_qns); + } else + throw std::invalid_argument( + "sequant::mbpt::sr::contains_rank(op_or_op_product): op_or_op_product " + "must be mbpt::sr::op_t or Product thereof"); +} + +bool raises_vacuum_up_to_rank(const ExprPtr& op_or_op_product, + const unsigned long k) { + assert(op_or_op_product.is() || op_or_op_product.is()); + + return can_change_qns(op_or_op_product, interval_excitation_type_qns(k)); +} + +bool lowers_rank_or_lower_to_vacuum(const ExprPtr& op_or_op_product, + const unsigned long k) { + assert(op_or_op_product.is() || op_or_op_product.is()); + return can_change_qns(op_or_op_product, qns_t{}, + interval_excitation_type_qns(k)); +} + +bool raises_vacuum_to_rank(const ExprPtr& op_or_op_product, + const unsigned long k) { + assert(op_or_op_product.is() || op_or_op_product.is()); + return can_change_qns(op_or_op_product, excitation_type_qns(k)); +} + +bool lowers_rank_to_vacuum(const ExprPtr& op_or_op_product, + const unsigned long k) { + assert(op_or_op_product.is() || op_or_op_product.is()); + return can_change_qns(op_or_op_product, qns_t{}, excitation_type_qns(k)); +} + +#include + +namespace tensor { + +ExprPtr vac_av(ExprPtr expr, std::vector> nop_connections, + bool use_top) { + simplify(expr); + auto isr = get_default_context().index_space_registry(); + const auto spinorbital = + get_default_context().spbasis() == SPBasis::spinorbital; + // convention is to use different label for spin-orbital and spin-free RDM + const auto rdm_label = spinorbital ? optype2label.at(OpType::RDM) : L"Γ"; + + // only need full contractions if don't have any density outside of + // the orbitals occupied in the vacuum + bool full_contractions = + (isr->reference_occupied_space() == isr->vacuum_occupied_space()) ? true + : false; + // N.B. reference < vacuum is not yet supported + if (isr->reference_occupied_space().intersection( + isr->vacuum_occupied_space()) != isr->vacuum_occupied_space()) { + throw std::invalid_argument( + "mbpt::tensor::vac_av: vacuum occupied orbitals must be same as or " + "subset of the reference orbital set."); + } + + FWickTheorem wick{expr}; + wick.use_topology(use_top).set_nop_connections(nop_connections); + wick.full_contractions(full_contractions); + auto result = wick.compute(); + simplify(result); + // std::wcout << "post wick: " << to_latex_align(result,20,1) << std::endl; + if (Logger::get_instance().wick_stats) { + std::wcout << "WickTheorem stats: # of contractions attempted = " + << wick.stats().num_attempted_contractions + << " # of useful contractions = " + << wick.stats().num_useful_contractions << std::endl; + } + // only need to handle the special case where the dense(at least partially + // occupied) states, contain additional functions to the vacuum_occupied. + // including a density occupied partition using a "single-reference" method + // will replace FNOPs with RDMs. i.e. "multi-reference" RDM replacement rules + // work in the limit of one reference. + if (isr->reference_occupied_space() == IndexSpace::Type{} || + isr->reference_occupied_space(Spin::any) == + isr->vacuum_occupied_space(Spin::any)) { + return result; + } else { + const auto target_rdm_space_type = + get_default_context().vacuum() == Vacuum::SingleProduct + ? isr->intersection(isr->particle_space(Spin::any), + isr->hole_space(Spin::any)) + : isr->reference_occupied_space(Spin::any); + + // STEP1. replace NOPs by RDM + auto replace_nop_with_rdm = [&rdm_label, spinorbital](ExprPtr& exptr) { + auto replace = [&rdm_label, spinorbital](const auto& nop) -> ExprPtr { + using index_container = container::svector; + auto braidxs = nop.annihilators() | + ranges::views::transform( + [](const auto& op) { return op.index(); }) | + ranges::to(); + auto ketidxs = nop.creators() | + ranges::views::transform( + [](const auto& op) { return op.index(); }) | + ranges::to(); + assert(braidxs.size() == + ketidxs.size()); // need to handle particle # violating case? + const auto rank = braidxs.size(); + return ex( + rdm_label, braidxs, ketidxs, decltype(ketidxs){}, + rank > 1 && spinorbital ? Symmetry::antisymm : Symmetry::nonsymm); + }; + + if (exptr.template is()) { + exptr = replace(exptr.template as()); + } else if (exptr.template is()) { + exptr = replace(exptr.template as()); + } + }; + result->visit(replace_nop_with_rdm, true); + + // STEP 2: project RDM indices onto the target RDM subspace + // since RDM indices only make sense within a single TN expand + flatten + // first, then do the projection individually for each TN + expand(result); + // flatten(result); // TODO where is flatten? + auto project_rdm_indices_to_target = [&](ExprPtr& exptr) { + auto impl_for_single_tn = [&](ProductPtr& product_ptr) { + // enlist all indices and count their instances + auto for_each_index_in_tn = [](const auto& product_ptr, + const auto& op) { + ranges::for_each(product_ptr->factors(), [&](auto& factor) { + auto tensor_ptr = std::dynamic_pointer_cast(factor); + if (tensor_ptr) { + ranges::for_each(tensor_ptr->_braket(), + [&](auto& idx) { op(idx, *tensor_ptr); }); + } + }); + }; + + // compute external indices + container::map indices_w_counts; + auto retrieve_indices_with_counts = [&indices_w_counts](const auto& idx, + auto&) { + auto found_it = indices_w_counts.find(idx); + if (found_it != indices_w_counts.end()) { + found_it->second++; + } else { + indices_w_counts.emplace(idx, 1); + } + }; + for_each_index_in_tn(product_ptr, retrieve_indices_with_counts); + + container::set external_indices = + indices_w_counts | ranges::views::filter([](auto& idx_cnt) { + auto& [idx, cnt] = idx_cnt; + return cnt == 1; + }) | + ranges::views::keys | ranges::to>; + + // extract RDM-only and all indices + container::set rdm_indices; + std::set all_indices; + auto retrieve_rdm_and_all_indices = [&rdm_indices, &all_indices, + &rdm_label](const auto& idx, + const auto& tensor) { + all_indices.insert(idx); + if (tensor._label() == rdm_label) { + rdm_indices.insert(idx); + } + }; + for_each_index_in_tn(product_ptr, retrieve_rdm_and_all_indices); + + // compute RDM->target replacement rules + container::map replacement_rules; + ranges::for_each(rdm_indices, [&](const Index& idx) { + const auto target_type = + isr->intersection(idx.space(), target_rdm_space_type); + if (target_type) { + Index target = Index::make_tmp_index(target_type); + replacement_rules.emplace(idx, target); + } + }); + + if (false) { + std::wcout << "expr = " << product_ptr->to_latex() + << "\n external_indices = "; + ranges::for_each(external_indices, [](auto& index) { + std::wcout << index.label() << " "; + }); + std::wcout << "\n replrules = "; + ranges::for_each(replacement_rules, [](auto& index) { + std::wcout << to_latex(index.first) << "\\to" + << to_latex(index.second) << "\\,"; + }); + std::wcout.flush(); + } + + if (!replacement_rules.empty()) { + sequant::detail::apply_index_replacement_rules( + product_ptr, replacement_rules, external_indices, all_indices, + isr); + } + }; + + if (exptr.template is()) { + auto product_ptr = exptr.template as_shared_ptr(); + impl_for_single_tn(product_ptr); + exptr = product_ptr; + } else { + assert(exptr.template is()); + auto result = std::make_shared(); + for (auto& summand : exptr.template as().summands()) { + assert(summand.template is()); + auto result_summand = summand.template as().clone(); + auto product_ptr = result_summand.template as_shared_ptr(); + impl_for_single_tn(product_ptr); + result->append(product_ptr); + } + exptr = result; + } + }; + project_rdm_indices_to_target(result); + + // rename dummy indices that might have been generated by + // project_rdm_indices_to_target + // + may combine terms + + // TensorCanonicalizer is given a custom comparer that moves active + // indices to the front external-vs-internal trait still takes precedence + auto current_index_comparer = + TensorCanonicalizer::instance()->index_comparer(); + TensorCanonicalizer::instance()->index_comparer( + [&](const Index& idx1, const Index& idx2) -> bool { + auto active_space = isr->intersection(isr->particle_space(Spin::any), + isr->hole_space(Spin::any)); + const auto idx1_active = idx1.space().type() == active_space.type(); + const auto idx2_active = idx2.space().type() == active_space.type(); + if (idx1_active) { + if (idx2_active) + return current_index_comparer(idx1, idx2); + else + return true; + } else { + if (idx2_active) + return false; + else + return current_index_comparer(idx1, idx2); + } + }); + simplify(result); + TensorCanonicalizer::instance()->index_comparer( + std::move(current_index_comparer)); + + if (Logger::get_instance().wick_stats) { + std::wcout << "WickTheorem stats: # of contractions attempted = " + << wick.stats().num_attempted_contractions + << " # of useful contractions = " + << wick.stats().num_useful_contractions << std::endl; + } + return result; + } +} + +} // namespace tensor +} // namespace op + +bool can_change_qns(const ExprPtr& op_or_op_product, const qns_t target_qns, + const qns_t source_qns = {}) { + qns_t qns = source_qns; + if (op_or_op_product.is()) { + const auto& op_product = op_or_op_product.as(); + for (auto& op_ptr : ranges::views::reverse(op_product.factors())) { + assert(op_ptr->template is()); + const auto& op = op_ptr->template as(); + qns = op(qns); + } + return qns.overlaps_with(target_qns); + } else if (op_or_op_product.is()) { + const auto& op = op_or_op_product.as(); + qns = op(); + return qns.overlaps_with(target_qns); + } else + throw std::invalid_argument( + "sequant::mbpt::sr::contains_rank(op_or_op_product): op_or_op_product " + "must be mbpt::sr::op_t or Product thereof"); +} + } // namespace sequant::mbpt diff --git a/SeQuant/domain/mbpt/op.hpp b/SeQuant/domain/mbpt/op.hpp index 5e170519f..ff86caaef 100644 --- a/SeQuant/domain/mbpt/op.hpp +++ b/SeQuant/domain/mbpt/op.hpp @@ -5,22 +5,9 @@ #ifndef SEQUANT_DOMAIN_MBPT_OP_HPP #define SEQUANT_DOMAIN_MBPT_OP_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include #include #include @@ -42,12 +29,35 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace sequant { namespace mbpt { template bool is_vacuum(QuantumNumbers qns); +/// converts an IndexSpace::Type to IndexSpace using default quantum number set +inline IndexSpace make_space(const IndexSpace::Type& type) { + return get_default_context().index_space_registry()->retrieve(type, + Spin::any); +} + /// enumerates the known Operator types enum class OpType { h, //!< 1-body Hamiltonian @@ -163,6 +173,8 @@ class Operator : public Expr, public Labeled { using FOperatorBase = FOperator; using BOperatorBase = BOperator; +struct default_qns_tag {}; + // clang-format off /// tracks changes in \c N quantum numbers @@ -170,65 +182,83 @@ using BOperatorBase = BOperator; /// tracking the quantum numbers of a many-body operator, such as the number of particles, /// the number of quasiparticles, the number of ops (creators/annihilators) in each subspace, etc. /// For example, to operator products expressed in normal order with respect to physical vacuum it is sufficient to track -/// the number of creators and annihilators; for operator products expressed in normal order with respect to Fermi vacuum -/// it is sufficient to track the number of creators and annihilators in the occupied and unoccupied subspaces, etc. -/// \tparam N the number of quantum numbers to track +/// the number of creators and annihilators; For the fermi vacuum case, the number of creators and annihilators in each +/// subspace becomes important. the number of ops is tracked for each base space (determined by the IndexSpaceRegistry object in Context). +/// the interval representation is necessary to dictate how many creators or annihilators could be in each subspace. +/// this is pertinent when user defined hole_space or particle_space are NOT base spaces. +/// since the choice of space partitioning is up to the user, the base class must be a dynamic container. /// \tparam Tag a tag type to distinguish different instances of QuantumNumberChange /// \tparam QNV the quantum number value type, defaults to \c std::int64_t // clang-format on -template +template class QuantumNumberChange - : public std::array>, N> { + : public container::svector< + boost::numeric::interval>, 8> { public: using QNC = std::make_signed_t; // change in quantum numbers using interval_t = boost::numeric::interval; - using tag_t = Tag; - using base_type = std::array; - using this_type = QuantumNumberChange; - - constexpr auto size() const { return N; } + using base_type = + container::svector>, 8>; + using this_type = QuantumNumberChange; + + const std::size_t size() const { + if (get_default_context().vacuum() == Vacuum::Physical) { + return 2; + } else if (get_default_context().vacuum() == Vacuum::SingleProduct) { + auto isr = get_default_context().index_space_registry(); + const auto& isr_base_spaces = isr->base_spaces(); + assert(isr_base_spaces.size() > 0); + return isr_base_spaces.size() * 2; + } else { + throw std::logic_error("unknown Vacuum type"); + } + } /// initializes all values with zeroes - QuantumNumberChange() { std::fill(this->begin(), this->end(), interval_t{}); } + QuantumNumberChange() { + this->resize(this->size()); + assert(this->base().size() != 0); + std::fill(this->begin(), this->end(), interval_t{}); + } /// constructs QuantumNumberChange from a sequence of elements convertible to - /// QNV \tparam I the type of the initializer_list elements \param i the - /// sequence of QNV-convertible elements - template >> - explicit QuantumNumberChange(std::initializer_list i) { - if (i.size() == N) { - std::copy(i.begin(), i.end(), this->begin()); - } else { - throw std::runtime_error( - "QuantumNumberChange(initializer_list i): i.size() must be " + - std::to_string(N)); - } + /// QNV + /// \tparam I the type of the initializer_list elements + /// \param i the sequence of QNV-convertible elements + template > && + std::is_convertible_v>> + explicit QuantumNumberChange(Range&& i) : QuantumNumberChange() { + assert(i.size() == size()); + std::copy(i.begin(), i.end(), this->begin()); } /// constructs QuantumNumberChange from a sequence of elements convertible to - /// QNV \tparam I the type of the initializer_list elements \param i the - /// sequence of QNV-convertible elements + /// QNV + /// \tparam I the type of the initializer_list elements + /// \param i the sequence of QNV-convertible elements template , interval_t>>> explicit QuantumNumberChange( - std::initializer_list> i) { - assert(i.size() == N); + std::initializer_list> i) + : QuantumNumberChange() { + assert(i.size() == size()); #ifndef NDEBUG if (std::find_if(i.begin(), i.end(), [](const auto& ii) { return ii.size() != 2; }) != i.end()) - throw std::runtime_error( + throw std::invalid_argument( "QuantumNumberChange(initializer_list i): each " "element of i must contain 2 elements"); #endif - for (std::size_t c = 0; c != N; ++c) { + for (std::size_t c = 0; c != size(); ++c) { this->operator[](c) = interval_t{*((i.begin() + c)->begin()), *((i.begin() + c)->begin() + 1)}; } } QuantumNumberChange& operator+=(const QuantumNumberChange& other) { - for (std::size_t c = 0; c != N; ++c) this->operator[](c) += other[c]; + for (std::size_t c = 0; c != size(); ++c) this->operator[](c) += other[c]; return *this; } @@ -238,23 +268,89 @@ class QuantumNumberChange [](const auto& ia, const auto& ib) { return equal(ia, ib); }); } bool operator!=(const this_type& b) const { return !this->operator==(b); } - template >> - bool operator==(I i) const { - return boost::numeric::interval_lib::compare::possible::operator==( - this->front(), static_cast(i)); + + // determines the number of physical vacuum creators and annihilators for the + // active particle and hole space from the Context. for general operators this + // is not defined. for example: O_{e_1}^{i_1 m_1} a_{i_1 m_1}^{e_1} asking for + // the active particle annihilators in this example is nonsense and will + // return -1. + + interval_t ncre_particles() { + const auto& qnvec = this->base(); + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + interval_t result = 0; + for (unsigned int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + const auto intersect_type = + base_space.attr() + .intersection(isr->particle_space(base_space.qns()).attr()) + .type(); + if (IndexSpace::Type{} != intersect_type) { + result += qnvec[2 * i]; + } + } + return result; + } + + interval_t nann_particles() { + const auto& qnvec = this->base(); + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + interval_t result = 0; + for (unsigned int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + const auto intersect_type = + base_space.attr() + .intersection(isr->particle_space(base_space.qns()).attr()) + .type(); + if (IndexSpace::Type{} != intersect_type) { + result += qnvec[2 * i + 1]; + } + } + return result; } - template >> - bool operator!=(I i) const { - return !this->operator==(i); + + interval_t ncre_holes() { + const auto& qnvec = this->base(); + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + interval_t result = 0; + for (unsigned int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + const auto intersect_type = + base_space.attr() + .intersection(isr->hole_space(base_space.qns()).attr()) + .type(); + if (IndexSpace::Type{} != intersect_type) { + result += qnvec[2 * i]; + } + } + return result; + } + + interval_t nann_holes() { + const auto& qnvec = this->base(); + auto isr = get_default_context().index_space_registry(); + const auto& base_spaces = isr->base_spaces(); + interval_t result = 0; + for (unsigned int i = 0; i < base_spaces.size(); i++) { + const auto& base_space = base_spaces[i]; + const auto intersect_type = + base_space.attr() + .intersection(isr->hole_space(base_space.qns()).attr()) + .type(); + if (IndexSpace::Type{} != intersect_type) { + result += qnvec[2 * i + 1]; + } + } + return result; } /// tests whether particular changes in quantum number change /// @param i an integer /// @return true if \p i is in `*this[0]` - template >> + template bool in(I i) { return boost::numeric::in(static_cast(i), this->front()); } @@ -264,77 +360,66 @@ class QuantumNumberChange /// @return true if \p i is in `*this[0]` template >> bool in(std::initializer_list i) { - assert(i.size() == N); + assert(i.size() == size()); std::array i_arr; std::copy(i.begin(), i.end(), i_arr.begin()); return this->in(i_arr); } - /// @param i an array of N integers - /// @return true if `i[k]` is in `*this[k]` for all `k` - template >> - bool in(std::array i) { - for (std::size_t c = 0; c != N; ++c) { - if (!boost::numeric::in(static_cast(i[c]), this->operator[](c))) - return false; - } - return true; - } - /// @param i an array of N intervals /// @return true if `i[k]` overlaps with `*this[k]` for all `k` bool overlaps_with(base_type i) { - for (std::size_t c = 0; c != N; ++c) { - if (!boost::numeric::overlap(i[c], this->operator[](c))) return false; + for (std::size_t c = 0; c != this->size(); ++c) { + if (!boost::numeric::overlap(i[c], this->operator[](c))) { + return false; + } } return true; } auto hash_value() const { - static_assert(N > 0); + assert(size() > 0); auto val = sequant::hash::value(this->operator[](0)); - for (std::size_t c = 1; c != N; ++c) { + for (std::size_t c = 1; c != size(); ++c) { sequant::hash::combine(val, sequant::hash::value(this->operator[](c))); } return val; } + + private: + auto& base() { return static_cast(*this); } }; template -inline bool operator==(const QuantumNumberChange& a, - const QuantumNumberChange& b) { +inline bool operator==(const QuantumNumberChange& a, + const QuantumNumberChange& b) { return a.operator==(b); } template -inline bool operator!=(const QuantumNumberChange& a, - const QuantumNumberChange& b) { +inline bool operator!=(const QuantumNumberChange& a, + const QuantumNumberChange& b) { return !(a == b); } template >> -inline bool operator==(const QuantumNumberChange& a, I b) { +inline bool operator==(const QuantumNumberChange& a, I b) { return a.operator==(b); } template -inline bool equal(const QuantumNumberChange& a, - const QuantumNumberChange& b) { +inline bool equal(const QuantumNumberChange& a, + const QuantumNumberChange& b) { return operator==(a, b); } template >> -inline bool operator!=(const QuantumNumberChange& a, I b) { +inline bool operator!=(const QuantumNumberChange& a, I b) { return a.operator!=(b); } -//////////////////////// define "ovearloadable" typedefs for the physical vacuum -/// case - -struct qns_tag {}; - // clang-format off /// algebra of operators normal order with respect to physical vacuum /// can be screened by tracking the number of creators and annihilators. @@ -342,54 +427,67 @@ struct qns_tag {}; /// \note use signed integer, although could use unsigned in this case, /// so that can represent quantum numbers and their changes by the same type // clang-format on -using qns_t = mbpt::QuantumNumberChange<2, qns_tag, std::int64_t>; +using qns_t = mbpt::QuantumNumberChange<>; using qninterval_t = typename qns_t::interval_t; /// changes in quantum number represented by quantum numbers themselves using qnc_t = qns_t; using op_t = mbpt::Operator; -// clang-format off -/// @return the number of creators in \p qns acting on space \p s -/// @pre `(s.type()==IndexSpace::Type::active_occupied || s.type()==IndexSpace::Type::active_unoccupied)&&s.qns()==IndexSpace::null_qns` -// clang-format on -qninterval_t ncre(qns_t qns, IndexSpace); - -// clang-format off -/// @return the number of creators in \p qns acting on space \p s -/// @pre `s==IndexSpace::Type::active_occupied || s==IndexSpace::Type::active_unoccupied` -// clang-format on -qninterval_t ncre(qns_t qns, IndexSpace::Type); - -// clang-format off -/// @return the total number of creators in \p qns -// clang-format on -qninterval_t ncre(qns_t qns); - -// clang-format off -/// @return the number of annihilators in \p qns acting on space \p s -/// @pre `(s.type()==IndexSpace::Type::active_occupied || s.type()==IndexSpace::Type::active_unoccupied)&&s.qns()==IndexSpace::null_qns` -// clang-format on -qninterval_t nann(qns_t qns, IndexSpace); - -// clang-format off -/// @return the number of annihilators in \p qns acting on space \p s -/// @pre `s==IndexSpace::Type::active_occupied || s==IndexSpace::Type::active_unoccupied` -// clang-format on -qninterval_t nann(qns_t qns, IndexSpace::Type); - -// clang-format off -/// @return the total number of annihilators in \p qns -// clang-format on -qninterval_t nann(qns_t qns); - /// combines 2 sets of quantum numbers using Wick's theorem qns_t combine(qns_t, qns_t); +// The qns of an excitation type operator will always look the same in a given +// context +// @param is the rank of the operator. +qns_t excitation_type_qns(std::size_t k, + IndexSpace::QuantumNumbers SQN = Spin::any); + +// sometimes we want to guarantee that a qns has an interval from 0-K +// regardless of the base spaces. +qns_t interval_excitation_type_qns(std::size_t k, + IndexSpace::QuantumNumbers SQN = Spin::any); + +// The qns of a deexcitation type operator will always look the same in a given +// context +// @param is the rank of the operator. +qns_t deexcitation_type_qns(std::size_t k, + IndexSpace::QuantumNumbers SQN = Spin::any); + +// sometimes we want to guarantee that a qns has an interval from 0-K +// regardless of the base spaces. +qns_t interval_deexcitation_type_qns( + std::size_t k, IndexSpace::QuantumNumbers SQN = Spin::any); + +// The qns of a general type operator will always look the same in a given +// context +// @//param is rank of the operator. +qns_t general_type_qns(std::size_t k); + +// generic quantum number function compatible with generic excitation operators +// with the option to choose the particle and hole space. +qns_t generic_excitation_qns(std::size_t particle_rank, std::size_t hole_rank, + IndexSpace particle_space, IndexSpace hole_space, + IndexSpace::QuantumNumbers SQN = Spin::any); + +// generic quantum number function compatible with generic deexcitation +// operators with the option to choose the particle and hole space. +qns_t generic_deexcitation_qns(std::size_t particle_rank, std::size_t hole_rank, + IndexSpace particle_space, IndexSpace hole_space, + IndexSpace::QuantumNumbers SQN = Spin::any); + +inline namespace op { +namespace tensor { +ExprPtr vac_av(ExprPtr expr, + std::vector> nop_connections = {}, + bool use_top = true); +} // namespace tensor +} // namespace op + } // namespace mbpt /// @param qns the quantum numbers to adjoint /// @return the adjoint of \p qns -mbpt::qns_t adjoint(mbpt::qns_t); +mbpt::qns_t adjoint(mbpt::qns_t qns); namespace mbpt { @@ -408,9 +506,8 @@ namespace mbpt { /// /// \f$ P \f$ is the "normalization" factor and depends on the vacuum used to define \f$ A \f$, /// and indices \f$ \{ b_i \} \f$ / \f$ \{ k_i \} \f$. -/// @note The choice of computational basis can be controlled by the default Formalism: -/// - if `get_default_formalism().sum_over_uocc() == SumOverUocc::Complete` IndexSpace::complete_unoccupied will be used instead of IndexSpace::active_unoccupied -/// - if `get_default_formalism().csv() == CSVFormalism::CSV` will use cluster-specific (e.g., PNO) unoccupied indices +/// @note The choice of computational basis can be controlled by the default Context. +/// See `SeQuant/core/context.hpp` and `SeQuant/mbpt/context.hpp` /// @warning Tensor \f$ T \f$ will be antisymmetrized if `get_default_context().spbasis() == SPBasis::spinorbital`, else it will be particle-symmetric; the latter is only valid if # of bra and ket indices coincide. /// @internal bless the maker and his water // clang-format on @@ -420,8 +517,8 @@ class OpMaker { /// @param[in] op the operator type /// @param[in] bras the bra indices/creators /// @param[in] kets the ket indices/annihilators - OpMaker(OpType op, std::initializer_list bras, - std::initializer_list kets); + OpMaker(OpType op, std::initializer_list bras, + std::initializer_list kets); /// @param[in] op the operator type /// @param[in] bras the bra indices/creators @@ -434,6 +531,17 @@ class OpMaker { assert(nbra() > 0 || nket() > 0); } + OpMaker(OpType op, std::size_t Nbra, std::size_t Nket, + IndexSpace particle_space = get_default_context() + .index_space_registry() + ->particle_space(Spin::any), + IndexSpace hole_space = get_default_context() + .index_space_registry() + ->hole_space(Spin::any)); + + // construct a particle conserving operator when constructing this way. + OpMaker(OpType op, std::size_t nparticle); + enum class UseDepIdx { /// bra/cre indices depend on ket Bra, @@ -465,8 +573,8 @@ class OpMaker { /// @param[in] tensor_generator the callable that generates the tensor /// @param[in] dep whether to use dependent indices template - static ExprPtr make(const container::svector& bras, - const container::svector& kets, + static ExprPtr make(const container::svector& bras, + const container::svector& kets, TensorGenerator&& tensor_generator, UseDepIdx dep = UseDepIdx::None) { const bool symm = @@ -479,11 +587,11 @@ class OpMaker { if (!symm) assert(ranges::size(bras) == ranges::size(kets)); auto make_idx_vector = [](const auto& spacetypes) { - std::vector result; + container::svector result; const auto n = spacetypes.size(); result.reserve(n); for (size_t i = 0; i != n; ++i) { - auto space = IndexSpace::instance(spacetypes[i]); + auto space = spacetypes[i]; result.push_back(Index::make_tmp_index(space)); } return result; @@ -491,16 +599,16 @@ class OpMaker { auto make_depidx_vector = [](const auto& spacetypes, auto&& protoidxs) { const auto n = spacetypes.size(); - std::vector result; + container::svector result; result.reserve(n); for (size_t i = 0; i != n; ++i) { - auto space = IndexSpace::instance(spacetypes[i]); + auto space = spacetypes[i]; result.push_back(Index::make_tmp_index(space, protoidxs, true)); } return result; }; - std::vector braidxs, ketidxs; + container::svector braidxs, ketidxs; if (dep_bra) { ketidxs = make_idx_vector(kets); braidxs = make_depidx_vector(bras, ketidxs); @@ -530,16 +638,16 @@ class OpMaker { std::initializer_list kets, TensorGenerator&& tensor_generator, UseDepIdx csv = UseDepIdx::None) { - container::svector bra_vec(bras.begin(), bras.end()); - container::svector ket_vec(kets.begin(), kets.end()); - return OpMaker::make( - bra_vec, ket_vec, std::forward(tensor_generator), csv); + container::svector bra_vec(bras.begin(), bras.end()); + container::svector ket_vec(kets.begin(), kets.end()); + return OpMaker::make(bra_vec, ket_vec, + std::forward(tensor_generator), csv); } protected: OpType op_; - container::svector bra_spaces_; - container::svector ket_spaces_; + container::svector bra_spaces_; + container::svector ket_spaces_; OpMaker(OpType op); @@ -574,9 +682,11 @@ class Operator : public Operator { QuantumNumbers operator()(const QuantumNumbers& qns = {}) const; /// evaluates the result of applying this operator to initializer-list-encoded - /// \p qns \param qns the quantum numbers of the state to which this operator + /// \p qns + /// \param qns the quantum numbers of the state to which this operator /// is applied; if not provided, the default-constructed \c QuantumNumbers are - /// used \return the quantum numbers after applying this operator to \p qns + /// used + /// \return the quantum numbers after applying this operator to \p qns template >> QuantumNumbers operator()(std::initializer_list qns) const { QuantumNumbers result(qns); @@ -587,7 +697,8 @@ class Operator : public Operator { /// evaluates the result of applying this operator to \p qns /// \param[in,out] qns the quantum numbers of the state to which this operator /// is applied; on return contains the quantum numbers after applying this - /// operator \return a reference to `*this` + /// operator + /// \return a reference to `*this` virtual QuantumNumbers& apply_to(QuantumNumbers& qns) const; bool static_less_than(const Expr& that) const override; @@ -616,6 +727,156 @@ class Operator : public Operator { extern template class Operator; extern template class Operator; +inline namespace op { +namespace tensor { +// clang-format off +/// @brief `k`-body contribution to the "generic" Hamiltonian (in normal order relative to the default vacuum) +/// @param[in] k the rank of the particle interactions; only `k<=2` is +/// supported +// clang-format on +ExprPtr H_(std::size_t k); + +/// @brief total Hamiltonian including up to `k`-body interactions +/// @param[in] k the maximum rank of the particle interactions; only `k<=2` is +/// supported +ExprPtr H(std::size_t k = 2); + +/// fock operator implied one-body operator, optional explicit construction +/// requires user to specify the IndexSpace corresponding to all orbitals which +/// may have non-zero density. +ExprPtr F(bool use_tensor = true, IndexSpace reference_occupied = {L"", 0}); + +/// makes particle-conserving excitation operator of rank \p K +ExprPtr T_(std::size_t K); + +/// makes sum of particle-conserving excitation operators of all ranks up to \p +/// K +ExprPtr T(std::size_t K, bool skip1 = false); + +/// makes particle-conserving deexcitation operator of rank \p K +ExprPtr Λ_(std::size_t K); + +/// makes sum of particle-conserving deexcitation operators of all ranks up to +/// \p +/// K +ExprPtr Λ(std::size_t K); + +// general excitation operator +ExprPtr R_(std::size_t nbra, std::size_t nket, + IndexSpace hole_space = get_default_context() + .index_space_registry() + ->hole_space(Spin::any), + IndexSpace particle_space = get_default_context() + .index_space_registry() + ->particle_space(Spin::any)); + +// general deexcitation operator +ExprPtr L_(std::size_t nbra, std::size_t nket, + IndexSpace hole_space = get_default_context() + .index_space_registry() + ->hole_space(Spin::any), + IndexSpace particle_space = get_default_context() + .index_space_registry() + ->particle_space(Spin::any)); + +ExprPtr P(std::int64_t K); + +ExprPtr A(std::int64_t K); + +ExprPtr S(std::int64_t K); + +ExprPtr H_pt(std::size_t order, std::size_t R); + +ExprPtr T_pt_(std::size_t order, std::size_t K); + +ExprPtr T_pt(std::size_t order, std::size_t K, bool skip1 = false); + +ExprPtr Λ_pt_(std::size_t order, std::size_t K); + +ExprPtr Λ_pt(std::size_t order, std::size_t K, bool skip1 = false); +} // namespace tensor +} // namespace op + +inline namespace op { +// clang-format off +/// @brief `k`-body contribution to the "generic" Hamiltonian (in normal order relative to the default vacuum) +/// @param[in] k the rank of the particle interactions; only `k<=2` is +/// supported +// clang-format on +ExprPtr H_(std::size_t k); + +/// @brief total Hamiltonian including up to `k`-body interactions +/// @param[in] k the maximum rank of the particle interactions; only `k<=2` is +/// supported +ExprPtr H(std::size_t k = 2); + +/// fock operator implied one-body operator, optional explicit construction +/// requires user to specify the IndexSpace corresponding to all orbitals which +/// may have non-zero density. +ExprPtr F(bool use_tensor = true, IndexSpace reference_occupied = {L"", 0}); + +/// makes particle-conserving excitation operator of rank \p K +ExprPtr T_(std::size_t K); + +/// makes sum of particle-conserving excitation operators of all ranks up to \p +/// K +ExprPtr T(std::size_t K, bool skip1 = false); + +/// makes particle-conserving deexcitation operator of rank \p K +ExprPtr Λ_(std::size_t K); + +/// makes sum of particle-conserving deexcitation operators of all ranks up to +/// \p +/// K +ExprPtr Λ(std::size_t K); + +// general excitation operator +ExprPtr R_(std::size_t nbra, std::size_t nket, + IndexSpace hole_space = get_default_context() + .index_space_registry() + ->hole_space(Spin::any), + IndexSpace particle_space = get_default_context() + .index_space_registry() + ->particle_space(Spin::any)); + +// general deexcitation operator +ExprPtr L_(std::size_t nbra, std::size_t nket, + IndexSpace hole_space = get_default_context() + .index_space_registry() + ->hole_space(Spin::any), + IndexSpace particle_space = get_default_context() + .index_space_registry() + ->particle_space(Spin::any)); + +ExprPtr P(std::int64_t K); + +ExprPtr A(std::int64_t K); + +ExprPtr S(std::int64_t K); + +ExprPtr H_pt(std::size_t order, std::size_t R); + +ExprPtr T_pt_(std::size_t order, std::size_t K); + +ExprPtr T_pt(std::size_t order, std::size_t K, bool skip1 = false); + +ExprPtr Λ_pt_(std::size_t order, std::size_t K); + +ExprPtr Λ_pt(std::size_t order, std::size_t K, bool skip1 = false); + +bool raises_vacuum_up_to_rank(const ExprPtr& op_or_op_product, + const unsigned long k); + +bool lowers_rank_or_lower_to_vacuum(const ExprPtr& op_or_op_product, + const unsigned long k); + +bool raises_vacuum_to_rank(const ExprPtr& op_or_op_product, + const unsigned long k); + +bool lowers_rank_to_vacuum(const ExprPtr& op_or_op_product, + const unsigned long k); +#include +} // namespace op } // namespace mbpt } // namespace sequant diff --git a/SeQuant/domain/mbpt/op.ipp b/SeQuant/domain/mbpt/op.ipp index b829fbed3..3fc1756a7 100644 --- a/SeQuant/domain/mbpt/op.ipp +++ b/SeQuant/domain/mbpt/op.ipp @@ -94,6 +94,7 @@ bool Operator::commutes_with_atom(const Expr& that) const { // if this has annihilators/creators in same space as that has // creator/annihilators return false + auto delta_this = (*this)(); auto delta_that = (that_op)(); @@ -149,6 +150,13 @@ ExprPtr Operator::clone() const { return ex(*this); } +// Expresses general operators in human interpretable form. for example: \hat{T}_2 is a particle conserving 2-body excitation operator +// a non-particle conserving operator \hat{R}_2_1 implies that two particles are created followed by a single hole creation. +// conversely \hat{R}_1_2 implies the that only one particle is annihilated followed by two holes being created. +// The rule being, that for non-particle conserving operators, the first position indicates where the quasiparticle is going to and the second position indicates where it comes from. +// for the case of adjoint operators, the adjoint is represented by the symbol ⁺ and superscripting the quasi-particle numbers. for example: hat{R⁺}^{1,2}} +// For operators in which one or more quasi-particles has only partial coverage in the particle_space or hole_space, this notation is unsuitable, and we default to +// level printing of the operator. template std::wstring Operator::to_latex() const { return sequant::to_latex(*this); diff --git a/SeQuant/domain/mbpt/spin.cpp b/SeQuant/domain/mbpt/spin.cpp index 81c9a4976..e03c3b978 100644 --- a/SeQuant/domain/mbpt/spin.cpp +++ b/SeQuant/domain/mbpt/spin.cpp @@ -43,75 +43,36 @@ namespace sequant { -enum class SpinCase1 { Null, Alpha, Beta }; - namespace detail { -Index make_index_with_spincase(const Index& idx, SpinCase1 sc) { +Index make_index_with_spincase(const Index& idx, mbpt::Spin s) { // sanity check: make sure have only one spin label assert(!(idx.label().find(L'↑') != std::wstring::npos && idx.label().find(L'↓') != std::wstring::npos)); - std::wstring label; - - if (sc != SpinCase1::Null) { - if (sc == SpinCase1::Alpha) { - // this might be replacement of beta with alpha - if (idx.label().find(L'↓') != std::wstring::npos) { - label = idx.make_label_minus_substring(L'↓'); - label = Index::make_label_plus_suffix(label, L'↑'); - } else if (idx.label().find(L'↑') != std::wstring::npos) - label = idx.label(); - else - label = idx.make_label_plus_suffix(L'↑'); - } else if (sc == SpinCase1::Beta) { - // this might be replacement of alpha with beta - if (idx.label().find(L'↑') != std::wstring::npos) { - label = idx.make_label_minus_substring(L'↑'); - label = Index::make_label_plus_suffix(label, L'↓'); - } else if (idx.label().find(L'↓') != std::wstring::npos) - label = idx.label(); - else - label = idx.make_label_plus_suffix(L'↓'); - } else - abort(); // unreachable - } else { - assert(sc == SpinCase1::Null); - if (idx.label().find(L'↑') != std::wstring::npos) - label = idx.make_label_minus_substring(L'↑'); - else if (idx.label().find(L'↓') != std::wstring::npos) - label = idx.make_label_minus_substring(L'↓'); - else - label = idx.label(); - } - // to preserve rest of bits first unset spin bit, then set them to the desired // state - auto qns = idx.space() - .qns() - .intersection(~IndexSpace::spinmask) - .unIon((sc == SpinCase1::Null - ? IndexSpace::nullqns - : (sc == SpinCase1::Alpha ? IndexSpace::alpha - : IndexSpace::beta))); - IndexSpace space{idx.space().type(), qns}; + auto qns = mbpt::spinannotation_remove(idx.space().qns()).unIon(s); + IndexSpace space{mbpt::spinannotation_replacе(idx.space().base_key(), s), + idx.space().type(), qns}; auto protoindices = idx.proto_indices(); - for (auto& pidx : protoindices) pidx = make_index_with_spincase(pidx, sc); - return Index{label, space, protoindices}; + for (auto& pidx : protoindices) pidx = make_index_with_spincase(pidx, s); + return Index{mbpt::spinannotation_replacе(idx.label(), s), space, + protoindices}; } } // namespace detail Index make_spinalpha(const Index& idx) { - return detail::make_index_with_spincase(idx, SpinCase1::Alpha); + return detail::make_index_with_spincase(idx, mbpt::Spin::alpha); }; Index make_spinbeta(const Index& idx) { - return detail::make_index_with_spincase(idx, SpinCase1::Beta); + return detail::make_index_with_spincase(idx, mbpt::Spin::beta); }; -Index make_spinnull(const Index& idx) { - return detail::make_index_with_spincase(idx, SpinCase1::Null); +Index make_spinfree(const Index& idx) { + return detail::make_index_with_spincase(idx, mbpt::Spin::any); }; ExprPtr transform_expr(const ExprPtr& expr, @@ -234,7 +195,7 @@ ExprPtr remove_spin(const ExprPtr& expr) { container::svector ket(tensor.ket().begin(), tensor.ket().end()); { for (auto&& idx : ranges::views::concat(bra, ket)) { - idx = make_spinnull(idx); + idx = make_spinfree(idx); } } Tensor result(tensor.label(), bra, ket, tensor.auxiliary(), @@ -292,15 +253,23 @@ bool can_expand(const Tensor& tensor) { "can_expand(Tensor) failed."); if (tensor.bra_rank() != tensor.ket_rank()) return false; - // indices with non-qns are not allowed + // indices must have specific spin + [[maybe_unused]] auto all_have_spin = std::all_of( + tensor.const_braket().begin(), tensor.const_braket().end(), + [](const auto& idx) { + auto idx_spin = mbpt::to_spin(idx.space().qns()); + return idx_spin == mbpt::Spin::alpha || idx_spin == mbpt::Spin::beta; + }); assert(std::all_of(tensor.const_braket().begin(), tensor.const_braket().end(), [](const auto& idx) { - return idx.space().qns() != IndexSpace::nullqns; + auto idx_spin = mbpt::to_spin(idx.space().qns()); + return idx_spin == mbpt::Spin::alpha || + idx_spin == mbpt::Spin::beta; })); // count alpha indices in bra auto is_alpha = [](const Index& idx) { - return idx.space().qns() == IndexSpace::alpha; + return mbpt::to_spin(idx.space().qns()) == mbpt::Spin::alpha; }; // count alpha indices in bra @@ -1079,21 +1048,22 @@ auto index_list(const ExprPtr& expr) { } Tensor swap_spin(const Tensor& t) { - auto is_null_qns = [](const Index& i) { - return i.space().qns() == IndexSpace::nullqns; + auto is_any_spin = [](const Index& i) { + return mbpt::to_spin(i.space().qns()) == mbpt::Spin::any; }; // Return tensor if there are no spin labels if (std::all_of(t.const_braket().begin(), t.const_braket().end(), - is_null_qns)) { + is_any_spin)) { return t; } // Return new index where the spin-label is flipped auto spin_flipped_idx = [](const Index& idx) { - assert(idx.space().qns() != IndexSpace::nullqns); - return idx.space().qns() == IndexSpace::alpha ? make_spinbeta(idx) - : make_spinalpha(idx); + assert(mbpt::to_spin(idx.space().qns()) != mbpt::Spin::any); + return mbpt::to_spin(idx.space().qns()) == mbpt::Spin::alpha + ? make_spinbeta(idx) + : make_spinalpha(idx); }; container::svector bra(t.rank()), ket(t.rank()); @@ -1527,7 +1497,7 @@ ExprPtr spintrace( // This function is used to spin-trace a product terms with spin-specific // indices. It checks if all tensors can be expanded and spintraces individual - // tensors by call to the about lambda function. + // tensors by call to the spin_trace_tensor lambda. auto spin_trace_product = [&spin_trace_tensor](const Product& product) { Product spin_product{}; @@ -1874,7 +1844,6 @@ ExprPtr factorize_S(const ExprPtr& expression, while (std::find(i_list.begin(), i_list.end(), std::distance(expr->begin(), it)) != i_list.end()) ++it; - // Clone the summand auto new_product = (*it)->clone(); diff --git a/SeQuant/domain/mbpt/spin.hpp b/SeQuant/domain/mbpt/spin.hpp index 7c143a61a..334ac0aee 100644 --- a/SeQuant/domain/mbpt/spin.hpp +++ b/SeQuant/domain/mbpt/spin.hpp @@ -2,8 +2,10 @@ // Created by Eduard Valeyev on 2019-02-27. // -#ifndef SEQUANT_SPIN_HPP -#define SEQUANT_SPIN_HPP +#ifndef SEQUANT_DOMAIN_MBPT_SPIN_HPP +#define SEQUANT_DOMAIN_MBPT_SPIN_HPP + +#include #include #include @@ -17,6 +19,93 @@ namespace sequant { +namespace mbpt { + +/// quantum numbers tags related to spin +/// \note spin quantum number takes 2 rightmost bits +enum class Spin : bitset_t { + alpha = 0b000001, + beta = 0b000010, + any = 0b000011, // both bits set so that overlap and union work as expected + // (any & alpha = alpha, alpha | beta = any) + free = any, // syntax sugar + spinmask = 0b000011 // coincides with any +}; + +// Spin is a scoped enum, hence not implicitly convertible to +// QuantumNumbersAttr::bitset_t +static_assert(!std::is_convertible_v); +// QuantumNumbersAttr::bitset_t cannot be constructed from Spin +static_assert(!std::is_constructible_v); +// but Spin can be cast to QuantumNumbersAttr::bitset_t +static_assert(meta::is_statically_castable_v); +// Spin cannot be cast to nonsense ... +static_assert( + !meta::is_statically_castable_v); + +inline Spin operator~(Spin s) { + return static_cast(~(static_cast(s))); +} +inline Spin operator|(Spin s1, Spin s2) { + return static_cast(static_cast(s1) | + static_cast(s2)); +} +inline Spin operator&(Spin s1, Spin s2) { + return static_cast(static_cast(s1) & + static_cast(s2)); +} + +/// converts QuantumNumbersAttr to Spin +/// @note this filters out all bits not used in Spin +inline Spin to_spin(const QuantumNumbersAttr& t) { + return static_cast(static_cast(t.to_int32()) & Spin::spinmask); +} + +/// removes spin annotation in QuantumNumbersAttr by unsetting the bits used by +/// Spin +inline QuantumNumbersAttr spinannotation_remove(const QuantumNumbersAttr& t) { + return t.intersection(QuantumNumbersAttr(~Spin::spinmask)); +} + +/// removes spin annotation, if any +template >>> +std::wstring spinannotation_remove(WS&& label) { + auto [base, orbital] = Index::make_split_label(label); + if (base.back() == L'↑' || base.back() == L'↓') { + base.remove_suffix(1); + } + return Index::make_merged_label(base, orbital); +} + +/// adds spin annotation to IndexSpace base label or Index (full) label +template >>> +std::wstring spinannotation_add(WS&& label, Spin s) { + auto [base, ordinal] = Index::make_split_label(label); + switch (s) { + case Spin::any: + return std::wstring(label); + case Spin::alpha: + return Index::make_merged_label(std::wstring(base) + L"↑", ordinal); + case Spin::beta: + return Index::make_merged_label(std::wstring(base) + L"↓", ordinal); + default: + assert(false && "invalid quantum number"); + abort(); + } +} + +/// replaces spin annotation to +template >>> +std::wstring spinannotation_replacе(WS&& label, Spin s) { + auto label_sf = spinannotation_remove(std::forward(label)); + return spinannotation_add(label_sf, s); +} + +} // namespace mbpt + // make alpha-spin idx [[nodiscard]] Index make_spinalpha(const Index& idx); @@ -24,7 +113,7 @@ namespace sequant { [[nodiscard]] Index make_spinbeta(const Index& idx); // make null-spin idx -[[nodiscard]] Index make_spinnull(const Index& idx); +[[nodiscard]] Index make_spinfree(const Index& idx); /// @brief Applies index replacement rules to an ExprPtr /// @param expr ExprPtr to transform @@ -81,9 +170,10 @@ bool can_expand(const Tensor& tensor); /// @return an ExprPtr containing the sum of expanded terms, if antisymmetric ExprPtr expand_antisymm(const Tensor& tensor, bool skip_spinsymm = false); -// TODO: Correct this function -/// @brief expands all antisymmetric tensors in a product -/// @param expr an expression pointer to expand +/// @brief expands all antisymmetric tensors in an expression +/// @param expr an expression to expand +/// @param skip_spinsymm is true, will not expand tensors whose indices all have +/// same spin [default=false] /// @return an expression pointer with expanded tensors as a sum ExprPtr expand_antisymm(const ExprPtr& expr, bool skip_spinsymm = false); @@ -93,7 +183,8 @@ ExprPtr expand_antisymm(const ExprPtr& expr, bool skip_spinsymm = false); /// @return true if tensor with given label is found bool has_tensor(const ExprPtr& expr, std::wstring label); -/// @brief Generates a vector of replacement maps for Antisymmetrizer operator +/// @brief Generates a vector of replacement maps for antisymmetrization (A) +/// tensor /// @param A An antisymmetrizer tensor (A) (with > 2 particle indices) /// @return Vector of replacement maps container::svector> A_maps(const Tensor& A); @@ -110,8 +201,8 @@ ExprPtr remove_tensor(const Product& product, std::wstring label); /// @return ExprPtr with the tensor removed ExprPtr remove_tensor(const ExprPtr& expr, std::wstring label); -/// @brief Expand a product containing the Antisymmetrization (A) operator -/// @param A product term with/without A operator +/// @brief Expand a product containing the antisymmetrization (A) tensor +/// @param product a Product that may or may not include the antisymmetrizer /// @return an ExprPtr containing sum of expanded terms if A is present ExprPtr expand_A_op(const Product& product); @@ -120,30 +211,30 @@ ExprPtr expand_A_op(const Product& product); /// @return expression pointer with Symmstrizer operator ExprPtr symmetrize_expr(const Product& product); -/// @brief Expand an expression containing the Antisymmetrization (A) operator +/// @brief Expand an expression containing the antisymmetrization (A) tensor /// @param expr any ExprPtr /// @return an ExprPtr containing sum of expanded terms if A is present ExprPtr symmetrize_expr(const ExprPtr& expr); -/// @brief Expand an expression containing the Antisymmetrization (A) operator +/// @brief Expand an expression containing the antisymmetrization (A) tensor /// @param expr any ExprPtr /// @return an ExprPtr containing sum of expanded terms if A is present ExprPtr expand_A_op(const ExprPtr& expr); /// @brief Generates a vector of replacement maps for particle permutation /// operator -/// @param P A particle permutation operator (with > 2 particle indices) +/// @param P a particle permutation operator (with > 2 particle indices) /// @return Vector of replacement maps container::svector> P_maps( const Tensor& P, bool keep_canonical = true, bool pair_wise = false); -/// @brief Expand a product containing the particle permutation (P) operator -/// @param A product term with/without P operator +/// @brief Expand a product containing the particle permutation (P) tensor +/// @param product a Product that may or may not contain P tensor /// @return an ExprPtr containing sum of expanded terms if P is present ExprPtr expand_P_op(const Product& product, bool keep_canonical = true, bool pair_wise = true); -/// @brief Expand an expression containing the particle permutation (P) operator +/// @brief Expand an expression containing the particle permutation (P) tensor /// @param expr any ExprPtr /// @return an ExprPtr containing sum of expanded terms if P is present ExprPtr expand_P_op(const ExprPtr& expr, bool keep_canonical = true, @@ -163,7 +254,7 @@ ExprPtr S_maps(const ExprPtr& expr); /// @tparam Seq1 (reference to) a container type /// @param v0 first sequence; if passed as an rvalue reference, it is moved from /// @param[in] v1 second sequence -/// @param \p v0 is a permutation of \p v1 +/// @pre \p v0 is a permutation of \p v1 /// @return the number of cycles template std::size_t count_cycles(Seq0&& v0, const Seq1& v1) { @@ -211,7 +302,7 @@ std::size_t count_cycles(Seq0&& v0, const Seq1& v1) { /// @param ext_index_groups groups of external indices /// @return an expression with spin integrated/adapted ExprPtr closed_shell_spintrace( - const ExprPtr& expression, + const ExprPtr& expr, const container::svector>& ext_index_groups = {}); /// @@ -222,14 +313,6 @@ ExprPtr closed_shell_spintrace( /// container::svector> external_indices(Tensor const&); -/// -/// @param nparticles Number of indices in bra of the target tensor. That must -/// be equal to the same in the ket. -/// @deprecated not CSV-compatible, and mixes bra and ket relative to -/// external_indices(expr) , will be deprecated -//[[deprecated("use external_indices(expr)")]] container::svector< -// container::svector> external_indices(size_t nparticles); - /// @brief Transforms Coupled cluster from spin orbital to spatial orbitals /// @details The external indices are deduced from Antisymmetrization operator /// @param expr ExprPtr to Sum type with spin orbital indices @@ -301,7 +384,7 @@ std::vector open_shell_CC_spintrace(const ExprPtr& expr); /// @warning The result of this function is not simplified since this is a /// building block for more specialized spin-tracing functions ExprPtr spintrace( - const ExprPtr& expression, + const ExprPtr& expr, container::svector> ext_index_groups = {}, bool spinfree_index_spaces = true); @@ -323,4 +406,4 @@ ExprPtr biorthogonal_transform( } // namespace sequant -#endif // SEQUANT_SPIN_HPP +#endif // SEQUANT_DOMAIN_MBPT_SPIN_HPP diff --git a/SeQuant/domain/mbpt/sr.cpp b/SeQuant/domain/mbpt/sr.cpp deleted file mode 100644 index 533dda235..000000000 --- a/SeQuant/domain/mbpt/sr.cpp +++ /dev/null @@ -1,608 +0,0 @@ -// -// Created by Eduard Valeyev on 2019-02-19. -// - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace sequant { -namespace mbpt { -namespace sr { - -qninterval_t ncre(qns_t qns, const IndexSpace::Type& s) { - assert(s == IndexSpace::active_occupied || - s == IndexSpace::active_unoccupied); - return s == IndexSpace::active_occupied ? qns[0] : qns[2]; -} - -qninterval_t ncre(qns_t qns, const IndexSpace& s) { - assert((s.type() == IndexSpace::active_occupied || - s.type() == IndexSpace::active_unoccupied) && - s.qns() == IndexSpace::nullqns); - return s.type() == IndexSpace::active_occupied ? qns[0] : qns[2]; -} - -qninterval_t ncre_occ(qns_t qns) { - return ncre(qns, IndexSpace::active_occupied); -} - -qninterval_t ncre_uocc(qns_t qns) { - return ncre(qns, IndexSpace::active_unoccupied); -} - -qninterval_t ncre(qns_t qns) { return qns[0] + qns[2]; } - -qninterval_t nann(qns_t qns, const IndexSpace::Type& s) { - assert(s == IndexSpace::active_occupied || - s == IndexSpace::active_unoccupied); - return s == IndexSpace::active_occupied ? qns[1] : qns[3]; -} - -qninterval_t nann(qns_t qns, const IndexSpace& s) { - assert((s.type() == IndexSpace::active_occupied || - s.type() == IndexSpace::active_unoccupied) && - s.qns() == IndexSpace::nullqns); - return s.type() == IndexSpace::active_occupied ? qns[1] : qns[3]; -} - -qninterval_t nann_occ(qns_t qns) { - return nann(qns, IndexSpace::active_occupied); -} - -qninterval_t nann_uocc(qns_t qns) { - return nann(qns, IndexSpace::active_unoccupied); -} - -qninterval_t nann(qns_t qns) { return qns[1] + qns[3]; } - -qns_t combine(qns_t a, qns_t b) { - const auto ncontr_uocc = - qninterval_t{0, std::min(ncre(b, IndexSpace::active_unoccupied).upper(), - nann(a, IndexSpace::active_unoccupied).upper())}; - const auto ncontr_occ = - qninterval_t{0, std::min(nann(b, IndexSpace::active_occupied).upper(), - ncre(a, IndexSpace::active_occupied).upper())}; - const auto nc_occ = - nonnegative(ncre(a, IndexSpace::active_occupied) + - ncre(b, IndexSpace::active_occupied) - ncontr_occ); - const auto nc_uocc = - nonnegative(ncre(a, IndexSpace::active_unoccupied) + - ncre(b, IndexSpace::active_unoccupied) - ncontr_uocc); - const auto na_occ = - nonnegative(nann(a, IndexSpace::active_occupied) + - nann(b, IndexSpace::active_occupied) - ncontr_occ); - const auto na_uocc = - nonnegative(nann(a, IndexSpace::active_unoccupied) + - nann(b, IndexSpace::active_unoccupied) - ncontr_uocc); - return qns_t{nc_occ, na_occ, nc_uocc, na_uocc}; -} - -} // namespace sr -} // namespace mbpt - -mbpt::sr::qns_t adjoint(mbpt::sr::qns_t qns) { - return mbpt::sr::qns_t{nann(qns, IndexSpace::active_occupied), - ncre(qns, IndexSpace::active_occupied), - nann(qns, IndexSpace::active_unoccupied), - ncre(qns, IndexSpace::active_unoccupied)}; -} - -namespace mbpt { -namespace sr { - -OpMaker::OpMaker(OpType op, std::size_t nbra, std::size_t nket) - : base_type(op) { - nket = nket == std::numeric_limits::max() ? nbra : nket; - assert(nbra > 0 || nket > 0); - - const auto unocc = IndexSpace::active_unoccupied; - const auto occ = IndexSpace::active_occupied; - switch (to_class(op)) { - case OpClass::ex: - bra_spaces_ = decltype(bra_spaces_)(nbra, unocc); - ket_spaces_ = decltype(ket_spaces_)(nket, occ); - break; - case OpClass::deex: - bra_spaces_ = decltype(bra_spaces_)(nbra, occ); - ket_spaces_ = decltype(ket_spaces_)(nket, unocc); - break; - case OpClass::gen: - bra_spaces_ = decltype(bra_spaces_)(nbra, IndexSpace::complete); - ket_spaces_ = decltype(ket_spaces_)(nket, IndexSpace::complete); - break; - } -} - -#include - -ExprPtr H0mp() { return F(); } - -ExprPtr H1mp() { - assert(get_default_context().vacuum() == Vacuum::SingleProduct); - return H_(2); -} - -ExprPtr W() { - assert(get_default_context().vacuum() == Vacuum::SingleProduct); - return H1mp(); -} - -ExprPtr H_(std::size_t k) { - assert(k > 0 && k <= 2); - switch (k) { - case 1: - switch (get_default_context().vacuum()) { - case Vacuum::Physical: - return OpMaker(OpType::h, 1)(); - case Vacuum::SingleProduct: - return OpMaker(OpType::f, 1)(); - case Vacuum::MultiProduct: - abort(); - default: - abort(); - } - - case 2: - return OpMaker(OpType::g, 2)(); - - default: - abort(); - } -} - -ExprPtr H(std::size_t k) { - assert(k > 0 && k <= 2); - return k == 1 ? H_(1) : H_(1) + H_(2); -} - -ExprPtr F(bool use_f_tensor) { - if (use_f_tensor) return OpMaker(OpType::f, 1)(); - - // add \bar{g}^{\kappa x}_{\lambda y} \gamma^y_x with x,y in occ_space_type - auto make_g_contribution = [](const auto occ_space_type) { - return mbpt::OpMaker::make( - {IndexSpace::complete}, {IndexSpace::complete}, - [=](auto braidxs, auto ketidxs, Symmetry opsymm) { - auto m1 = Index::make_tmp_index( - IndexSpace{occ_space_type, IndexSpace::nullqns}); - auto m2 = Index::make_tmp_index( - IndexSpace{occ_space_type, IndexSpace::nullqns}); - assert(opsymm == Symmetry::antisymm || opsymm == Symmetry::nonsymm); - if (opsymm == Symmetry::antisymm) { - braidxs.push_back(m1); - ketidxs.push_back(m2); - return ex(to_wstring(mbpt::OpType::g), braidxs, ketidxs, - std::vector{}, Symmetry::antisymm) * - ex(to_wstring(mbpt::OpType::δ), IndexList{m2}, - IndexList{}, IndexList{m1}, Symmetry::nonsymm); - } else { // opsymm == Symmetry::nonsymm - auto braidx_J = braidxs; - braidx_J.push_back(m1); - auto ketidxs_J = ketidxs; - ketidxs_J.push_back(m2); - auto braidx_K = braidxs; - braidx_K.push_back(m1); - auto ketidxs_K = ketidxs; - ketidxs_K.emplace(begin(ketidxs_K), m2); - return (ex(to_wstring(mbpt::OpType::g), braidx_J, ketidxs_J, - std::vector{}, Symmetry::nonsymm) - - ex(to_wstring(mbpt::OpType::g), braidx_K, ketidxs_K, - std::vector{}, Symmetry::nonsymm)) * - ex(to_wstring(mbpt::OpType::δ), IndexList{m2}, - IndexList{}, IndexList{m1}, Symmetry::nonsymm); - } - }); - }; - - switch (get_default_context().vacuum()) { - case Vacuum::Physical: - return OpMaker(OpType::h, 1)() + - make_g_contribution(IndexSpace::occupied); // all occupieds - case Vacuum::SingleProduct: - return OpMaker(OpType::f, 1)(); - case Vacuum::MultiProduct: - abort(); - default: - abort(); - } -} - -ExprPtr vac_av(ExprPtr expr, std::vector> nop_connections, - bool use_top) { - FWickTheorem wick{expr}; - wick.use_topology(use_top).set_nop_connections(nop_connections); - auto result = wick.compute(); - simplify(result); - if (Logger::get_instance().wick_stats) { - std::wcout << "WickTheorem stats: # of contractions attempted = " - << wick.stats().num_attempted_contractions - << " # of useful contractions = " - << wick.stats().num_useful_contractions << std::endl; - } - return result; -} - -namespace op { - -ExprPtr H2_oo_vv() { - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::g); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return OpMaker( - OpType::g, - {IndexSpace::active_occupied, IndexSpace::active_occupied}, - {IndexSpace::active_unoccupied, IndexSpace::active_unoccupied})(); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{2, 0, 0, 2}, qns); - }); -} - -ExprPtr H2_vv_vv() { - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::g); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return OpMaker( - OpType::g, - {IndexSpace::active_unoccupied, IndexSpace::active_unoccupied}, - {IndexSpace::active_unoccupied, IndexSpace::active_unoccupied})(); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{0, 0, 2, 2}, qns); - }); -} - -ExprPtr H_(std::size_t k) { - assert(k > 0 && k <= 2); - switch (k) { - case 1: - return ex( - [vacuum = get_default_context().vacuum()]() -> std::wstring_view { - switch (vacuum) { - case Vacuum::Physical: - return optype2label.at(OpType::h); - case Vacuum::SingleProduct: - return optype2label.at(OpType::f); - case Vacuum::MultiProduct: - abort(); - default: - abort(); - } - }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::H_(1); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{{0, 1}, {0, 1}, {0, 1}, {0, 1}}, qns); - }); - - case 2: - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::g); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::H_(2); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{{0, 2}, {0, 2}, {0, 2}, {0, 2}}, qns); - }); - - default: - abort(); - } -} - -ExprPtr H(std::size_t k) { - assert(k > 0 && k <= 2); - return k == 1 ? H_(1) : H_(1) + H_(2); -} - -ExprPtr T_(std::size_t K) { - assert(K > 0); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::t); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::T_(K); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{0ul, K, K, 0ul}, qns); - }); -} - -ExprPtr T(std::size_t K, bool skip1) { - assert(K > (skip1 ? 1 : 0)); - - ExprPtr result; - for (auto k = (skip1 ? 2ul : 1ul); k <= K; ++k) { - result += T_(k); - } - return result; -} - -ExprPtr Λ_(std::size_t K) { - assert(K > 0); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::λ); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::Λ_(K); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{K, 0ul, 0ul, K}, qns); - }); -} - -ExprPtr Λ(std::size_t K, bool skip1) { - assert(K > (skip1 ? 1 : 0)); - - ExprPtr result; - for (auto k = (skip1 ? 2ul : 1ul); k <= K; ++k) { - result += Λ_(k); - } - return result; -} - -ExprPtr A(std::int64_t K) { - assert(K != 0); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::A); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::A(K, K); - }, - [=](qnc_t& qns) { - const std::size_t abs_K = std::abs(K); - if (K < 0) - qns = combine(qnc_t{abs_K, 0ul, 0ul, abs_K}, qns); - else - qns = combine(qnc_t{0ul, abs_K, abs_K, 0ul}, qns); - }); -} - -ExprPtr S(std::int64_t K) { - assert(K != 0); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::S); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::S(K, K); - }, - [=](qnc_t& qns) { - const std::size_t abs_K = std::abs(K); - if (K < 0) - qns = combine(qnc_t{abs_K, 0ul, 0ul, abs_K}, qns); - else - qns = combine(qnc_t{0ul, abs_K, abs_K, 0ul}, qns); - }); -} - -ExprPtr P(std::int64_t K) { - return get_default_context().spbasis() == SPBasis::spinfree ? S(-K) : A(-K); -} - -ExprPtr H_pt(std::size_t order, std::size_t R) { - assert(R > 0); - assert(order == 1 && "only first order perturbation is supported now"); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::h_1); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::H_pt(1, R); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{{0ul, R}, {0ul, R}, {0ul, R}, {0ul, R}}, qns); - }); -} - -ExprPtr T_pt_(std::size_t order, std::size_t K) { - assert(K > 0); - assert(order == 1 && "only first order perturbation is supported now"); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::t_1); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::T_pt_(order, K); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{0ul, K, K, 0ul}, qns); - }); -} - -ExprPtr T_pt(std::size_t order, std::size_t K, bool skip1) { - assert(K > (skip1 ? 1 : 0)); - ExprPtr result; - for (auto k = (skip1 ? 2ul : 1ul); k <= K; ++k) { - result = k > 1 ? result + T_pt_(order, k) : T_pt_(order, k); - } - return result; -} - -ExprPtr Λ_pt_(std::size_t order, std::size_t K) { - assert(K > 0); - assert(order == 1 && "only first order perturbation is supported now"); - return ex( - []() -> std::wstring_view { return optype2label.at(OpType::λ_1); }, - [=]() -> ExprPtr { - using namespace sequant::mbpt::sr; - return sr::Λ_pt_(order, K); - }, - [=](qnc_t& qns) { - qns = combine(qnc_t{K, 0ul, 0ul, K}, qns); - }); -} - -ExprPtr Λ_pt(std::size_t order, std::size_t K, bool skip1) { - assert(K > (skip1 ? 1 : 0)); - ExprPtr result; - for (auto k = (skip1 ? 2ul : 1ul); k <= K; ++k) { - result = k > 1 ? result + Λ_pt_(order, k) : Λ_pt_(order, k); - } - return result; -} - -bool can_change_qns(const ExprPtr& op_or_op_product, const qns_t target_qns, - const qns_t source_qns) { - qns_t qns = source_qns; - if (op_or_op_product.is()) { - const auto& op_product = op_or_op_product.as(); - for (auto& op_ptr : ranges::views::reverse(op_product.factors())) { - assert(op_ptr->template is()); - const auto& op = op_ptr->template as(); - qns = op(qns); - } - return qns.overlaps_with(target_qns); - } else if (op_or_op_product.is()) { - const auto& op = op_or_op_product.as(); - qns = op(); - return qns.overlaps_with(target_qns); - } else - throw std::invalid_argument( - "sequant::mbpt::sr::contains_rank(op_or_op_product): op_or_op_product " - "must be mbpt::sr::op_t or Product thereof"); -} - -bool raises_vacuum_up_to_rank(const ExprPtr& op_or_op_product, - const unsigned long k) { - assert(op_or_op_product.is() || op_or_op_product.is()); - return can_change_qns(op_or_op_product, - qns_t{{0ul, 0ul}, {0ul, k}, {0ul, k}, {0ul, 0ul}}); -} - -bool lowers_rank_or_lower_to_vacuum(const ExprPtr& op_or_op_product, - const unsigned long k) { - assert(op_or_op_product.is() || op_or_op_product.is()); - return can_change_qns(op_or_op_product, qns_t{}, - qns_t{{0ul, 0ul}, {0ul, k}, {0ul, k}, {0ul, 0ul}}); -} - -bool raises_vacuum_to_rank(const ExprPtr& op_or_op_product, - const unsigned long k) { - assert(op_or_op_product.is() || op_or_op_product.is()); - return can_change_qns(op_or_op_product, qns_t{0ul, k, k, 0ul}); -} - -bool lowers_rank_to_vacuum(const ExprPtr& op_or_op_product, - const unsigned long k) { - assert(op_or_op_product.is() || op_or_op_product.is()); - return can_change_qns(op_or_op_product, qns_t{}, qns_t{0ul, k, k, 0ul}); -} - -using mbpt::sr::vac_av; - -#include - -} // namespace op - -} // namespace sr - -// must be defined including op.ipp since it's used there -template <> -bool is_vacuum(sr::qns_t qns) { - return qns == sr::qns_t{}; -} - -} // namespace mbpt - -template -std::wstring to_latex(const mbpt::Operator& op) { - using namespace sequant::mbpt; - using namespace sequant::mbpt::sr; - - auto result = L"{\\hat{" + utf_to_latex(op.label()) + L"}"; - - // check if operator has adjoint label, remove if present for base label - auto base_lbl = sequant::to_wstring(op.label()); - if (base_lbl.back() == adjoint_label) base_lbl.pop_back(); - - auto it = label2optype.find(std::wstring(base_lbl)); - OpType optype = OpType::invalid; - if (it != label2optype.end()) { // handle special cases - optype = it->second; - if (to_class(optype) == OpClass::gen) { - result += L"}"; - return result; - } - } - - // generic operator ... can only handle definite case - const auto dN = op(); - if (!is_definite(ncre_occ(dN)) || !is_definite(nann_occ(dN)) || - !is_definite(ncre_uocc(dN)) || !is_definite(nann_uocc(dN))) { - throw std::invalid_argument( - "to_latex(const Operator& op): " - "can only handle generic operators with definite cre/ann numbers"); - } - // pure quasiparticle creator/annihilator? - const auto qprank_cre = nann_occ(dN).lower() + ncre_uocc(dN).lower(); - const auto qprank_ann = ncre_occ(dN).lower() + nann_uocc(dN).lower(); - const auto qppure = qprank_cre == 0 || qprank_ann == 0; - auto qpaction = to_class(optype); - if (qppure) { - if (qprank_cre) { - // if operator's action implied by the label and actual action agrees, use - // subscript always - std::wstring baseline_char = (qpaction != OpClass::deex ? L"_" : L"^"); - if (nann_occ(dN).lower() == ncre_uocc(dN).lower()) - result += - baseline_char + L"{" + std::to_wstring(nann_occ(dN).lower()) + L"}"; - else - result += baseline_char + L"{" + std::to_wstring(nann_occ(dN).lower()) + - L"," + std::to_wstring(ncre_uocc(dN).lower()) + L"}"; - } else { - // if operator's action implied by the label and actual action agrees, use - // subscript always - std::wstring baseline_char = (qpaction != OpClass::deex ? L"^" : L"_"); - if (nann_uocc(dN).lower() == ncre_occ(dN).lower()) { - result += - baseline_char + L"{" + std::to_wstring(ncre_occ(dN).lower()) + L"}"; - } else - result += baseline_char + L"{" + std::to_wstring(ncre_occ(dN).lower()) + - L"," + std::to_wstring(nann_uocc(dN).lower()) + L"}"; - } - } else { // not pure qp creator/annihilator - result += L"_{" + std::to_wstring(nann_occ(dN).lower()) + L"," + - std::to_wstring(ncre_uocc(dN).lower()) + L"}^{" + - std::to_wstring(ncre_occ(dN).lower()) + L"," + - std::to_wstring(nann_uocc(dN).lower()) + L"}"; - } - result += L"}"; - return result; -} - -} // namespace sequant - -#include - -namespace sequant { -namespace mbpt { -template class Operator; -template class Operator; -} // namespace mbpt -} // namespace sequant diff --git a/SeQuant/domain/mbpt/sr.hpp b/SeQuant/domain/mbpt/sr.hpp deleted file mode 100644 index d33cebd36..000000000 --- a/SeQuant/domain/mbpt/sr.hpp +++ /dev/null @@ -1,314 +0,0 @@ -// -// Created by Eduard Valeyev on 2019-02-19. -// - -#ifndef SEQUANT_DOMAIN_MBPT_SR_HPP -#define SEQUANT_DOMAIN_MBPT_SR_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace sequant { -namespace mbpt { -namespace sr { - -struct qns_tag; - -// clang-format off -/// single reference operator algebra can be screened by tracking the number of creators and annihilators in the occupied and unoccupied space -/// the order of of elements is {# of occupied creators, # of occupied annihilators, # of unoccupied creators, # of unoccupied annihilators} -/// \note use signed integer, although could use unsigned in this case, so that can represent quantum numbers and their changes by the same type -// clang-format on -using qns_t = mbpt::QuantumNumberChange<4, qns_tag, std::int64_t>; -/// changes in quantum number represented by quantum numbers themselves -using qnc_t = qns_t; -using op_t = mbpt::Operator; - -// clang-format off -/// @return the number of creators in \p qns acting on space \p s -/// @pre `(s.type()==IndexSpace::Type::active_occupied || s.type()==IndexSpace::Type::active_unoccupied)&&s.qns()==IndexSpace::null_qns` -// clang-format on -qninterval_t ncre(qns_t qns, const IndexSpace& s); - -// clang-format off -/// @return the number of creators in \p qns acting on space \p s -/// @pre `s==IndexSpace::Type::active_occupied || s==IndexSpace::Type::active_unoccupied` -// clang-format on -qninterval_t ncre(qns_t qns, const IndexSpace::Type& s); - -// clang-format off -/// @return the number of creators in \p qns acting on the occupied space -// clang-format on -qninterval_t ncre_occ(qns_t qns); - -// clang-format off -/// @return the number of creators in \p qns acting on the unoccupied space -// clang-format on -qninterval_t ncre_uocc(qns_t qns); - -// clang-format off -/// @return the total number of creators in \p qns -// clang-format on -qninterval_t ncre(qns_t qns); - -// clang-format off -/// @return the number of annihilators in \p qns acting on space \p s -/// @pre `(s.type()==IndexSpace::Type::active_occupied || s.type()==IndexSpace::Type::active_unoccupied)&&s.qns()==IndexSpace::null_qns` -// clang-format on -qninterval_t nann(qns_t qns, const IndexSpace& s); - -// clang-format off -/// @return the number of annihilators in \p qns acting on space \p s -/// @pre `s==IndexSpace::Type::active_occupied || s==IndexSpace::Type::active_unoccupied` -// clang-format on -qninterval_t nann(qns_t qns, const IndexSpace::Type& s); - -// clang-format off -/// @return the number of annihilators in \p qns acting on the occupied space -// clang-format on -qninterval_t nann_occ(qns_t qns); - -// clang-format off -/// @return the number of annihilators in \p qns acting on the unoccupied space -// clang-format on -qninterval_t nann_uocc(qns_t qns); - -// clang-format off -/// @return the total number of annihilators in \p qns -// clang-format on -qninterval_t nann(qns_t qns); - -/// combines 2 sets of quantum numbers using Wick's theorem -qns_t combine(qns_t, qns_t); - -} // namespace sr -} // namespace mbpt - -/// @param qns the quantum numbers to adjoint -/// @return the adjoint of \p qns -mbpt::sr::qns_t adjoint(mbpt::sr::qns_t); - -namespace mbpt { -namespace sr { - -// clang-format off -/// @brief makes a tensor-level fermionic many-body operator for use in single-reference methods - -/// A many-body operator has the following generic form: -/// \f$ \frac{1}{P} T_{b_1 b_2 \dots b_B}^{k_1 k_2 \dots k_K} A^{b_1 b_2 \dots b_B}_{k_1 k_2 \dots k_K} \f$ -/// where \f$ \{B,K\} \f$ are number of bra/ket indices of \f$ T \f$ or, equivalently, the number of creators/annihilators -/// of normal-ordered (w.r.t. the default, not necessarily Fermi vacuum) operator \f$ A \f$. -/// Indices \f$ \{ b_i \} \f$ / \f$ \{ k_i \} \f$ are (active) unoccupied/occupied for (pure) excitation operators, -/// are occupied/unoccupied for deexcitation operators; for general operators complete basis indices are assumed by default, -/// unless overridden by user manually. \f$ P \f$ is the "normalization" factor and depends on the vacuum used to define \f$ A \f$, -/// and indices \f$ \{ b_i \} \f$ / \f$ \{ k_i \} \f$. -/// @note The choice of unoccupied indices/spaces can be controlled by the default Formalism: -/// - if `get_default_formalism().sum_over_uocc() == SumOverUocc::Complete` IndexSpace::complete_unoccupied will be used instead of IndexSpace::active_unoccupied -/// - if `get_default_formalism().csv() == CSVFormalism::CSV` will use cluster-specific (e.g., PNO) unoccupied indices -/// @warning Tensor \f$ T \f$ will be antisymmetrized if `get_default_context().spbasis() == SPBasis::spinorbital`, else it will be particle-symmetric; the latter is only valid if # of bra and ket indices coincide. -// clang-format on -class OpMaker : public mbpt::OpMaker { - public: - using base_type = mbpt::OpMaker; - - using base_type::base_type; - - // clang-format off - /// @param[in] op the operator type: - /// - if @p op is a (pure) excitation operator bra/ket indices - /// will be IndexSpace::active_unoccupied/IndexSpace::active_occupied, - /// - for (pure) deexcitation @p op bra/ket will be IndexSpace::active_occupied/IndexSpace::active_unoccupied - /// - for general @p op bra/ket will be IndexSpace::complete - /// @param[in] nbra number of bra indices/creators - /// @param[in] nket number of ket indices/annihilators; if not specified, will be set to @p nbra - // clang-format on - OpMaker(OpType op, std::size_t nbra, - std::size_t nket = std::numeric_limits::max()); - - using base_type::operator(); -}; - -#include - -/// @name tensor-level SR MBPT operators -/// @{ - -ExprPtr H0mp(); -ExprPtr H1mp(); - -// clang-format off -/// @brief `k`-body contribution to the "generic" Hamiltonian (in normal order relative to the default vacuum) -/// @param[in] k the rank of the particle interactions; only `k<=2` is -/// supported -// clang-format on -ExprPtr H_(std::size_t k); - -/// @brief total Hamiltonian including up to `k`-body interactions -/// @param[in] k the maximum rank of the particle interactions; only `k<=2` is -/// supported -ExprPtr H(std::size_t k = 2); - -/// @brief Fock operator -/// @param use_f_tensor if true, will use Fock tensor, else will use tensors -/// used to define `H_(1)` and `H_(2)` -ExprPtr F(bool use_f_tensor = true); - -ExprPtr W(); - -/// @} - -/// computes the vacuum expectation value (VEV) - -/// @param[in] expr input expression -/// @param[in] nop_connections specifies the pairs of normal operators to be -/// connected -/// @param[in] use_top if true, topological equivalence will be utilized -/// @return the VEV -ExprPtr vac_av(ExprPtr expr, - std::vector> nop_connections = {}, - bool use_top = true); - -/// contains operator-level SR MBPT expressions -namespace op { - -/// @name SR MBPT operators - -/// @{ - -ExprPtr H2_oo_vv(); -ExprPtr H2_vv_vv(); - -// clang-format off -/// @brief `k`-body contribution to the "generic" Hamiltonian (in normal order relative to the default vacuum) -/// @param[in] k the rank of the particle interactions; only `k<=2` is -/// supported -// clang-format on -ExprPtr H_(std::size_t k); - -/// @brief total Hamiltonian including up to `k`-body interactions -/// @param[in] k the maximum rank of the particle interactions; only `k<=2` is -/// supported -ExprPtr H(std::size_t k = 2); - -/// makes traditional cluster operator of particle rank \p K -/// @param K the particle rank -/// @return \f$ \frac{1}{(K!)^2}\sum t^{i_1 \dots i_K}_{a_1 \dots a_K} a_{i_1 -/// \dots i_K}^{a_1 \dots a_K} \f$ -/// @note traditional = pure-excitation -ExprPtr T_(std::size_t K); - -/// makes sum of T_() operators of all ranks up to \p K -/// @param K the maximum particle rank of included cluster operators -/// @param skip1 if true, omit 1-body cluster `T_(1)` -/// @return sum of `T_(k)` with `k<=K` -ExprPtr T(std::size_t K, bool skip1 = false); - -/// makes ref-state left-hand eigenoperator of the CC Hamiltonian up to rank \p -/// K -/// @param K the particle rank -/// @return \f$ \frac{1}{(K!)^2}\sum \lambda_{i_1 \dots i_K}^{a_1 \dots a_K} -/// a^{i_1 \dots i_K}_{a_1 \dots a_K} \f$ -ExprPtr Λ_(std::size_t K); - -/// makes sum of Λ_() operators of all ranks up to \p K -/// @param K the maximum particle rank of included cluster operators -/// @param skip1 if true, omit 1-body cluster `Λ_(1)` -/// @return sum of `Λ_(k)` with `k<=K` -ExprPtr Λ(std::size_t K, bool skip1 = false); - -/// makes generic bra/ket-antisymmetric excitation (if \p K > 0) or -/// deexcitation (if \p K < 0) operator of rank `|K|` -ExprPtr A(std::int64_t K); - -/// makes generic particle-symmetric excitation (if \p K > 0) or -/// deexcitation (sif \p K < 0) operator of rank `|K|` -ExprPtr S(std::int64_t K); - -/// makes projector onto excited bra (if \p K > 0) or -/// ket (if \p K < 0) manifold of rank `|K|`; -/// if using spin-free basis the manifold is particle-symmetric (@sa S(K)), -/// else it is bra/ket-antisymmetric (@sa A(K)) -ExprPtr P(std::int64_t K); - -/// Hamiltonian perturbation of rank \param R and order \p o -/// @pre `order==1`, only first order perturbation is supported now -ExprPtr H_pt(std::size_t order, std::size_t R); - -/// perturbed cluster operator of rank \p K and order \p o -/// @pre `order==1`, only first order perturbation is supported now -ExprPtr T_pt_(std::size_t order, std::size_t K); - -/// makes sum of perturbed excitation operators of all ranks up to \p K and -/// order \p o -/// @param skip1 if true, omit 1-body cluster `T_pt_(1)` -/// @pre `order==1`, only first order perturbation is supported now -ExprPtr T_pt(std::size_t order, std::size_t K, bool skip1 = false); - -/// perturbed deexcitation operator of rank \p K and order \p o -/// @pre `order==1`, only first order perturbation is supported now -ExprPtr Λ_pt_(std::size_t order, std::size_t K); - -/// makes sum of perturbed deexcitation operators of all ranks up to \p K and -/// order \p o -/// @param skip1 if true, omit 1-body cluster `T_pt_(1)` -/// @pre `order==1`, only first order perturbation is supported now -ExprPtr Λ_pt(std::size_t order, std::size_t K, bool skip1 = false); - -/// @} - -/// @return true if \p op_or_op_product can change quantum numbers from \p -/// source_qns to \p target_qns -bool can_change_qns(const ExprPtr& op_or_op_product, const qns_t target_qns, - const qns_t source_qns = {}); - -/// @return true if \p op_or_op_product can produce determinant of excitation -/// rank \p k when applied to reference -bool raises_vacuum_to_rank(const ExprPtr& op_or_op_product, - const unsigned long k); - -/// @return true if \p op_or_op_product can produce determinant of excitation -/// rank up to \p k when applied to vacuum -bool raises_vacuum_up_to_rank(const ExprPtr& op_or_op_product, - const unsigned long k); - -/// @return true if \p op_or_op_product can produce vacuum from determinant of -/// excitation rank \p k -bool lowers_rank_to_vacuum(const ExprPtr& op_or_op_product, - const unsigned long k); - -/// @return true if \p op_or_op_product can produce vacuum from determinant of -/// excitation rank up to \p k -bool lowers_rank_or_lower_to_vacuum(const ExprPtr& op_or_op_product, - const unsigned long k); - -#include - -} // namespace op - -} // namespace sr - -extern template class Operator; -extern template class Operator; - -} // namespace mbpt -} // namespace sequant - -#endif // SEQUANT_DOMAIN_MBPT_SR_HPP diff --git a/SeQuant/domain/mbpt/sr/op.impl.cpp b/SeQuant/domain/mbpt/sr/op.impl.cpp deleted file mode 100644 index 670a23cbd..000000000 --- a/SeQuant/domain/mbpt/sr/op.impl.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/// makes excitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr T_(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0); - assert(Nket > 0); - return OpMaker(OpType::t, Nbra, Nket)(); -} - -/// makes lambda deexcitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr Λ_(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0); - assert(Nket > 0); - return OpMaker(OpType::λ, Nbra, Nket)(); -} - -/// makes R excitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr R_(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0 || Nket > 0); - return OpMaker(OpType::R, Nbra, Nket)(); -} - -/// makes L deexcitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr L_(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0 || Nket > 0); - return OpMaker(OpType::L, Nbra, Nket)(); -} - -ExprPtr H_pt(std::size_t o, std::size_t R) { - assert(o == 1 && - "sequant::sr::H_pt(): only supports first order perturbation"); - assert(R > 0); - return OpMaker(OpType::h_1, R)(); -} - -ExprPtr T_pt_(std::size_t o, std::size_t Nbra, std::size_t Nket) { - assert(o == 1 && - "sequant::sr::T_pt_(): only supports first order perturbation"); - assert(Nbra > 0); - assert(Nket > 0); - return OpMaker(OpType::t_1, Nbra, Nket)(); -} - -ExprPtr Λ_pt_(std::size_t o, std::size_t Nbra, std::size_t Nket) { - assert(o == 1 && - "sequant::sr::Λ_pt_(): only supports first order perturbation"); - assert(Nbra > 0); - assert(Nket > 0); - return OpMaker(OpType::λ_1, Nbra, Nket)(); -} - -namespace detail { - -/// constructs a sum of ops up to a given bra/ket rank -class op_impl { - OpType op_; - std::size_t nbra_, nket_; - - public: - op_impl(OpType op, std::size_t nbra, - std::size_t nket = std::numeric_limits::max()) - : op_(op), - nbra_(nbra), - nket_(nket == std::numeric_limits::max() ? nbra : nket) { - assert(nbra_ > 0 && nbra_ < std::numeric_limits::max()); - assert(nket_ > 0); - } - - void operator()(ExprPtr& result) { - if (op_ == OpType::t) - result = result ? result + T_(nbra_, nket_) : T_(nbra_, nket_); - else if (op_ == OpType::λ) - result = result ? result + Λ_(nbra_, nket_) : Λ_(nbra_, nket_); - else if (op_ == OpType::R) - result = result ? result + R_(nbra_, nket_) : R_(nbra_, nket_); - else if (op_ == OpType::L) - result = result ? result + L_(nbra_, nket_) : L_(nbra_, nket_); - else - assert(false && "unsupported op value"); - - if ((nbra_ > 1 && nket_ > 0) || (nbra_ > 0 && nket_ > 1)) { - op_impl{op_, nbra_ - 1, nket_ - 1}(result); - } - } -}; - -} // namespace detail - -/// makes excitation operator of all bra/ket ranks up to (and including) -/// @c Nbra/Nket -ExprPtr T(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0 && Nbra < std::numeric_limits::max()); - const auto Nket_ = - Nket == std::numeric_limits::max() ? Nbra : Nket; - assert(Nket_ > 0); - ExprPtr result; - detail::op_impl{OpType::t, Nbra, Nket_}(result); - return result; -} - -/// makes deexcitation operator of all bra/ket ranks up to (and including) -/// @c Nbra/Nket -ExprPtr Λ(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0 && Nbra < std::numeric_limits::max()); - const auto Nket_ = - Nket == std::numeric_limits::max() ? Nbra : Nket; - assert(Nket_ > 0); - ExprPtr result; - detail::op_impl{OpType::λ, Nbra, Nket_}(result); - return result; -} - -/// makes geminal excitation operator for ansatz @p ansatz -ExprPtr R12(IndexSpace::Type gg_space, int ansatz) { - assert(ansatz == 1 || ansatz == 2); - if (ansatz == 2) - return OpMaker( - OpType::R12, - {IndexSpace::complete_unoccupied, IndexSpace::complete_unoccupied}, - {gg_space, gg_space})(); - else - return OpMaker(OpType::R12, - {IndexSpace::other_unoccupied, IndexSpace::other_unoccupied}, - {gg_space, gg_space})(); -} - -ExprPtr A(std::int64_t Kh, std::int64_t Kp) { - assert(Kh != 0); - if (Kp == std::numeric_limits::max()) Kp = Kh; - assert(Kp != 0); - - // Kh and Kp should have same sign - assert((Kh > 0 && Kp > 0) || (Kh < 0 && Kp < 0)); - - container::svector creators; - container::svector annihilators; - if (Kh > 0) { - for ([[maybe_unused]] auto i : ranges::views::iota(0, Kh)) - annihilators.emplace_back(IndexSpace::active_occupied); - } else { - for ([[maybe_unused]] auto i : ranges::views::iota(0, -Kh)) - creators.emplace_back(IndexSpace::active_occupied); - } - if (Kp > 0) { - for ([[maybe_unused]] auto i : ranges::views::iota(0, Kp)) - creators.emplace_back(IndexSpace::active_unoccupied); - } else { - for ([[maybe_unused]] auto i : ranges::views::iota(0, -Kp)) - annihilators.emplace_back(IndexSpace::active_unoccupied); - } - - std::optional dep; - if (get_default_formalism().csv() == mbpt::CSV::Yes) - dep = Kh > 0 ? OpMaker::UseDepIdx::Bra : OpMaker::UseDepIdx::Ket; - return OpMaker(OpType::A, creators, annihilators)(dep, {Symmetry::antisymm}); -} - -ExprPtr S(std::int64_t Kh, std::int64_t Kp) { - assert(Kh != 0); - if (Kp == std::numeric_limits::max()) Kp = Kh; - assert(Kp != 0); - - // Kh and Kp should have same sign - assert((Kh > 0 && Kp > 0) || (Kh < 0 && Kp < 0)); - - container::svector creators; - container::svector annihilators; - if (Kh > 0) { - for ([[maybe_unused]] auto i : ranges::views::iota(0, Kh)) - annihilators.emplace_back(IndexSpace::active_occupied); - } else { - for ([[maybe_unused]] auto i : ranges::views::iota(0, -Kh)) - creators.emplace_back(IndexSpace::active_occupied); - } - if (Kp > 0) { - for ([[maybe_unused]] auto i : ranges::views::iota(0, Kp)) - creators.emplace_back(IndexSpace::active_unoccupied); - } else { - for ([[maybe_unused]] auto i : ranges::views::iota(0, -Kp)) - annihilators.emplace_back(IndexSpace::active_unoccupied); - } - - std::optional dep; - if (get_default_formalism().csv() == mbpt::CSV::Yes) - dep = Kh > 0 ? OpMaker::UseDepIdx::Bra : OpMaker::UseDepIdx::Ket; - return OpMaker(OpType::S, creators, annihilators)(dep, {Symmetry::nonsymm}); -} - -/// makes excitation operator of all bra/ket ranks up to (and including) -/// @c Nbra/Nket -ExprPtr R(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0 && Nbra < std::numeric_limits::max()); - const auto Nket_ = - Nket == std::numeric_limits::max() ? Nbra : Nket; - assert(Nket_ > 0); - ExprPtr result; - detail::op_impl{OpType::R, Nbra, Nket_}(result); - return result; -} - -/// makes deexcitation operator of all bra/ket ranks up to (and including) -/// @c Nbra/Nket -ExprPtr L(std::size_t Nbra, std::size_t Nket) { - assert(Nbra > 0 && Nbra < std::numeric_limits::max()); - const auto Nket_ = - Nket == std::numeric_limits::max() ? Nbra : Nket; - assert(Nket_ > 0); - ExprPtr result; - detail::op_impl{OpType::L, Nbra, Nket_}(result); - return result; -} diff --git a/SeQuant/domain/mbpt/sr/op.impl.hpp b/SeQuant/domain/mbpt/sr/op.impl.hpp deleted file mode 100644 index 508c1bf10..000000000 --- a/SeQuant/domain/mbpt/sr/op.impl.hpp +++ /dev/null @@ -1,75 +0,0 @@ -/// makes excitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr T_(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes lambda deexcitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr Λ_(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes generic excitation (right-hand eigenvector) operator of bra/ket ranks -/// @c Nbra/Nket -ExprPtr R_(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes generic deexcitation (left-hand eigenvector) of bra/ket ranks @c -/// Nbra/Nket -ExprPtr L_(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes excitation operator of all bra/ket ranks up to (and including) @c -/// Nbra/Nket -ExprPtr T(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes deexcitation operator of all bra/ket ranks up to (and including) @c -/// Nbra/Nket -ExprPtr Λ(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes generic bra/ket-antisymmetrizer -/// \param Kh if <0, annihilates this many holes, else creates this many -/// \param Kp if <0, annihilates this many particles, else creates this many -/// (default is to set \p Kp to \p Kh) -ExprPtr A(std::int64_t Kh, - std::int64_t Kp = std::numeric_limits::max()); - -/// makes generic particle-symmetrizer -/// \param Kh if <0, annihilates this many holes, else creates this many -/// \param Kp if <0, annihilates this many particles, else creates this many -/// (default is to set \p Kp to \p Kh) -ExprPtr S(std::int64_t Kh, - std::int64_t Kp = std::numeric_limits::max()); - -/// makes L deexcitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr L(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes R excitation operator of bra/ket ranks @c Nbra/Nket -ExprPtr R(std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes geminal excitation operator from @p geminal_generating_space for -/// ansatz @p ansatz -/// @param[in] geminal_generating_space the space from which the geminal -/// excitations originate from; default = IndexSpace::active_occupied -/// @param[in] ansatz 1 or 2 -ExprPtr R12( - IndexSpace::Type geminal_generating_space = IndexSpace::active_occupied, - int ansatz = 2); - -/// makes perturbation operator of rank @p R -/// \param o order of perturbation -/// \pre `o==1`, only first order perturbation is supported now -ExprPtr H_pt(std::size_t o, std::size_t R); - -/// makes perturbed excitation operator of bra/ket ranks @c Nbra/Nket -/// \param o order of perturbation -/// \pre `o==1`, only first order perturbation is supported now -ExprPtr T_pt_(std::size_t o, std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); - -/// makes perturbed deexcitation operator of bra/ket ranks @c Nbra/Nket -/// \param o order of perturbation -/// \pre `o==1`, only first order perturbation is supported now -ExprPtr Λ_pt_(std::size_t o, std::size_t Nbra, - std::size_t Nket = std::numeric_limits::max()); diff --git a/SeQuant/domain/mbpt/vac_av.hpp b/SeQuant/domain/mbpt/vac_av.hpp index 518c64c04..b3ecd9966 100644 --- a/SeQuant/domain/mbpt/vac_av.hpp +++ b/SeQuant/domain/mbpt/vac_av.hpp @@ -1,8 +1,7 @@ // // Created by Eduard Valeyev on 2023-10-30. // - -// operator-level vac_av is same for SR and MR, to be included from {sr,mr}.hpp +using namespace sequant::mbpt; /// defines the default op connections inline std::vector> diff --git a/SeQuant/domain/mbpt/vac_av.ipp b/SeQuant/domain/mbpt/vac_av.ipp index bab278b94..02f5feea1 100644 --- a/SeQuant/domain/mbpt/vac_av.ipp +++ b/SeQuant/domain/mbpt/vac_av.ipp @@ -94,7 +94,7 @@ ExprPtr vac_av( expr = simplify(product); // compute VEV - auto vev = vac_av(product, connections, /* use_topology = */ true); + auto vev = tensor::vac_av(product, connections, /* use_topology = */ true); // restore Variable types to the Product if (!variables.empty()) ranges::for_each(variables, [&vev](const auto& var) { vev *= var; }); @@ -131,7 +131,7 @@ ExprPtr vac_av( return expr; // vacuum is normalized } throw std::invalid_argument( - "mpbt::*::op::vac_av(expr): unknown expression type"); + "mpbt::*::vac_av(expr): unknown expression type"); } ExprPtr vac_av( diff --git a/cmake/modules/FindOrFetchCatch2.cmake b/cmake/modules/FindOrFetchCatch2.cmake index 328d4e5fc..3bb329215 100644 --- a/cmake/modules/FindOrFetchCatch2.cmake +++ b/cmake/modules/FindOrFetchCatch2.cmake @@ -4,6 +4,7 @@ if (NOT TARGET Catch2::Catch2) VRGFindOrFetchPackage(Catch2 "https://github.com/catchorg/Catch2.git" "${SEQUANT_TRACKED_CATCH2_TAG}" ADD_SUBDIR CONFIG_SUBDIR + FIND_PACKAGE_ARGS 3 ) if (TARGET Catch2 AND NOT TARGET Catch2::Catch2) add_library(Catch2::Catch2 ALIAS Catch2) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 9bd854b8e..4553e3142 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -5,4 +5,10 @@ if(DOXYGEN_FOUND) COMMAND ${DOXYGEN_EXECUTABLE} ${PROJECT_BINARY_DIR}/doc/Doxyfile SOURCES ${PROJECT_BINARY_DIR}/doc/Doxyfile) add_custom_target_subproject(sequant doc DEPENDS html-sequant) + + # if DOT found obtain DOXYGEN_DOT_PATH + if (DOXYGEN_DOT_EXECUTABLE) + get_filename_component(DOXYGEN_DOT_DIRECTORY ${DOXYGEN_DOT_EXECUTABLE} DIRECTORY) + file(TO_NATIVE_PATH ${DOXYGEN_DOT_DIRECTORY} DOXYGEN_DOT_PATH) + endif() endif() diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index fd0535977..1d998af44 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -1,4 +1,4 @@ -# Doxyfile 1.9.3 +# Doxyfile 1.10.0 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -53,6 +63,12 @@ PROJECT_BRIEF = PROJECT_LOGO = +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If @@ -60,16 +76,28 @@ PROJECT_LOGO = OUTPUT_DIRECTORY = @PROJECT_BINARY_DIR@/doc -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode @@ -81,14 +109,14 @@ ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English @@ -341,6 +369,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 0 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = GITHUB + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -452,7 +491,7 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing @@ -465,6 +504,14 @@ LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = YES + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -546,7 +593,8 @@ HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO @@ -577,14 +625,15 @@ INTERNAL_DOCS = NO # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with +# are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. -# The default value is: system dependent. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = NO @@ -836,11 +885,26 @@ WARN_IF_INCOMPLETE_DOC = YES WARN_NO_PARAMDOC = NO +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO @@ -851,10 +915,21 @@ WARN_AS_ERROR = NO # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). In case the file specified cannot be opened for writing the @@ -882,10 +957,21 @@ INPUT = @PROJECT_SOURCE_DIR@/SeQuant \ # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. @@ -897,12 +983,12 @@ INPUT_ENCODING = UTF-8 # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, -# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C -# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -972,9 +1058,6 @@ EXCLUDE_PATTERNS = # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = @@ -1019,6 +1102,11 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. @@ -1060,6 +1148,15 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -1074,7 +1171,8 @@ USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO @@ -1157,10 +1255,11 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1239,7 +1338,12 @@ HTML_STYLESHEET = # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @PROJECT_SOURCE_DIR@/doc/customdoxygen.css @@ -1257,6 +1361,19 @@ HTML_EXTRA_FILES = @PROJECT_SOURCE_DIR@/doc/doxy-boot.js \ @PROJECT_SOURCE_DIR@/doc/addons/bootstrap/jquery.smartmenus.bootstrap.js \ @PROJECT_SOURCE_DIR@/doc/addons/bootstrap/jquery.smartmenus.bootstrap.css +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see @@ -1287,15 +1404,6 @@ HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1315,6 +1423,33 @@ HTML_DYNAMIC_MENUS = NO HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1445,6 +1580,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1620,17 +1765,6 @@ HTML_FORMULA_FORMAT = png FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -1646,7 +1780,7 @@ FORMULA_MACROFILE = # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -USE_MATHJAX = NO +USE_MATHJAX = YES # With MATHJAX_VERSION it is possible to specify the MathJax version to be used. # Note that the different versions of MathJax have different requirements with @@ -1692,8 +1826,8 @@ MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example -# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html -# #tex-and-latex-extensions): +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): @@ -1863,7 +1997,7 @@ COMPACT_LATEX = NO # The default value is: a4. # This tag requires that the tag GENERATE_LATEX is set to YES. -PAPER_TYPE = a4wide +PAPER_TYPE = letter # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names # that should be included in the LaTeX output. The package can be specified just @@ -1944,9 +2078,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1967,14 +2108,6 @@ LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2140,13 +2273,39 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2221,10 +2380,11 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = @PROJECT_SOURCE_DIR@ # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -2288,15 +2448,15 @@ TAGFILES = GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2310,16 +2470,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2328,7 +2481,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. @@ -2345,37 +2498,55 @@ HAVE_DOT = @DOXYGEN_DOT_FOUND@ DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = FreeSans +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a -# graph for each documented class showing the direct and indirect inheritance -# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, -# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set -# to TEXT the direct and indirect inheritance relations will be shown as texts / -# links. -# Possible values are: NO, YES, TEXT and GRAPH. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = YES @@ -2383,14 +2554,21 @@ CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2432,8 +2610,8 @@ DOT_UML_DETAILS = NO # The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters # to display on a single line. If the actual line length exceeds this threshold -# significantly it will wrapped across multiple lines. Some heuristics are apply -# to avoid ugly line breaks. +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. # Minimum value: 0, maximum value: 1000, default value: 17. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2450,7 +2628,9 @@ TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2459,7 +2639,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2499,7 +2682,10 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2515,7 +2701,7 @@ DIR_GRAPH_MAX_DEPTH = 1 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2543,7 +2729,7 @@ INTERACTIVE_SVG = NO # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_PATH = @DOXYGEN_DOT_EXECUTABLE@ +DOT_PATH = @DOXYGEN_DOT_PATH@ # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile @@ -2552,11 +2738,12 @@ DOT_PATH = @DOXYGEN_DOT_EXECUTABLE@ DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2606,18 +2793,6 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support @@ -2645,3 +2820,19 @@ GENERATE_LEGEND = YES # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/doc/images/tut-expr1-result1.svg b/doc/images/tut-expr1-result1.svg deleted file mode 100644 index e773f21f9..000000000 --- a/doc/images/tut-expr1-result1.svg +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/images/tut-expr1.svg b/doc/images/tut-expr1.svg deleted file mode 100644 index 1fd32a846..000000000 --- a/doc/images/tut-expr1.svg +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/images/tut-expr2-result1.svg b/doc/images/tut-expr2-result1.svg deleted file mode 100644 index aab199f7e..000000000 --- a/doc/images/tut-expr2-result1.svg +++ /dev/null @@ -1,283 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/images/tut-expr3-result1.svg b/doc/images/tut-expr3-result1.svg deleted file mode 100644 index 60071c9cb..000000000 --- a/doc/images/tut-expr3-result1.svg +++ /dev/null @@ -1,505 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/images/tut-expr4-result1.svg b/doc/images/tut-expr4-result1.svg deleted file mode 100644 index 32a1ec208..000000000 --- a/doc/images/tut-expr4-result1.svg +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/images/tut-notation-eq1.svg b/doc/images/tut-notation-eq1.svg deleted file mode 100644 index 848e65033..000000000 --- a/doc/images/tut-notation-eq1.svg +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/images/tut-notation-eq2.svg b/doc/images/tut-notation-eq2.svg deleted file mode 100644 index 7b9e19218..000000000 --- a/doc/images/tut-notation-eq2.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/images/tut-notation-eq3.svg b/doc/images/tut-notation-eq3.svg deleted file mode 100644 index 7a2ed0dc6..000000000 --- a/doc/images/tut-notation-eq3.svg +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/antisymmetrizer_test/antisymmetrizer_test.cpp b/examples/antisymmetrizer_test/antisymmetrizer_test.cpp index 6a6f5b767..b178f1434 100644 --- a/examples/antisymmetrizer_test/antisymmetrizer_test.cpp +++ b/examples/antisymmetrizer_test/antisymmetrizer_test.cpp @@ -1,8 +1,8 @@ #include #include +#include #include -#include #include #include @@ -92,10 +92,10 @@ int main(int argc, char* argv[]) { std::wcout.sync_with_stdio(true); std::wcerr.sync_with_stdio(true); sequant::detail::OpIdRegistrar op_id_registrar; - sequant::set_default_context(Context(Vacuum::Physical, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, - SPBasis::spinfree)); - mbpt::set_default_convention(); + sequant::set_default_context(Context( + sequant::mbpt::make_min_sr_spaces(), Vacuum::Physical, + IndexSpaceMetric::Unit, BraKetSymmetry::conjugate, SPBasis::spinfree)); + // mbpt::set_default_convention(); TensorCanonicalizer::register_instance( std::make_shared()); diff --git a/examples/eval/btas/main.cpp b/examples/eval/btas/main.cpp index baaebb2c5..c6ced0cd6 100644 --- a/examples/eval/btas/main.cpp +++ b/examples/eval/btas/main.cpp @@ -51,13 +51,24 @@ int main(int argc, char* argv[]) { using namespace sequant; detail::OpIdRegistrar op_id_registrar; - sequant::set_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); - mbpt::set_default_convention(); + sequant::set_default_context(Context( + mbpt::make_min_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); TensorCanonicalizer::register_instance( std::make_shared()); + // for optimization tests, set occupied and unoccupied index extents + { + auto reg = get_default_context().mutable_index_space_registry(); + auto occ = reg->retrieve_ptr(L"i"); + auto uocc = reg->retrieve_ptr(L"a"); + assert(occ); + assert(uocc); + occ->approximate_size(10); + uocc->approximate_size(100); + assert(uocc->approximate_size() == 100); + } + std::string calc_config = argc > 1 ? argv[1] : "calc.inp"; std::string fock_file = argc > 2 ? argv[2] : "fock_so.dat"; std::string eri_file = argc > 3 ? argv[3] : "eri_so.dat"; diff --git a/examples/eval/calc_info.cpp b/examples/eval/calc_info.cpp index dbf8f1e93..3d8b98fd3 100644 --- a/examples/eval/calc_info.cpp +++ b/examples/eval/calc_info.cpp @@ -8,21 +8,8 @@ namespace sequant::eval { -const size_t IndexToSize::nocc = 10; - -const size_t IndexToSize::nvirt = 10; - -size_t IndexToSize::operator()(Index const& idx) const { - if (idx.space() == IndexSpace::active_occupied) - return nocc; - else if (idx.space() == IndexSpace::active_unoccupied) - return nvirt; - else - throw std::runtime_error("Unsupported IndexSpace type encountered"); -} - container::vector CalcInfo::exprs() const { - auto exprs = mbpt::sr::CC{eqn_opts.excit}.t(); + auto exprs = mbpt::CC{eqn_opts.excit}.t(); container::vector result{}; for (auto r = 1; r < exprs.size(); ++r) result.emplace_back(eqn_opts.spintrace ? closed_shell_CC_spintrace(exprs[r]) diff --git a/examples/eval/calc_info.hpp b/examples/eval/calc_info.hpp index c325ea107..903823fde 100644 --- a/examples/eval/calc_info.hpp +++ b/examples/eval/calc_info.hpp @@ -28,12 +28,6 @@ struct NoCacheAmplitudeTensor { } }; -struct IndexToSize { - static const size_t nocc; - static const size_t nvirt; - size_t operator()(Index const& idx) const; -}; - struct CalcInfo { OptionsEquations const eqn_opts; @@ -79,7 +73,7 @@ struct CalcInfo { using ranges::views::transform; auto trimmed = opt::tail_factor(expr); return *trimmed | transform([st = optm_opts.single_term](auto expr) { - return eval_node(st ? optimize(expr, IndexToSize{}) : expr); + return eval_node(st ? optimize(expr) : expr); }) | ranges::to_vector; } }; diff --git a/examples/eval/eval_utils.hpp b/examples/eval/eval_utils.hpp index 161b4ec33..5b3745bea 100644 --- a/examples/eval/eval_utils.hpp +++ b/examples/eval/eval_utils.hpp @@ -52,8 +52,9 @@ void cartesian_foreach(const std::vector& rs, F f) { /// \return View of an iterable with size_t-type elements. /// auto range1_limits(sequant::Tensor const& tensor, size_t nocc, size_t nvirt) { - static auto const ao = sequant::IndexSpace::active_occupied; - static auto const au = sequant::IndexSpace::active_unoccupied; + auto isr = get_default_context().index_space_registry(); + static auto const ao = isr->retrieve(L"i"); + static auto const au = isr->retrieve(L"a"); return tensor.const_braket() | ranges::views::transform([nocc, nvirt](auto const& idx) { const auto& sp = idx.space(); diff --git a/examples/eval/scf.cpp b/examples/eval/scf.cpp index cec72676e..cdce509fe 100644 --- a/examples/eval/scf.cpp +++ b/examples/eval/scf.cpp @@ -28,7 +28,7 @@ void SequantEvalScf::scf(std::basic_ostream& log) { size_t const norm_precision = 10; size_t const norm_print_width = double_print_width; - static std::wstring const header_str = [this, iter_print_width]() { + static std::wstring const header_str = [iter_print_width]() { auto oss = std::wostringstream{}; oss << std::setw(iter_print_width) << std::right << "Iter" @@ -59,8 +59,6 @@ void SequantEvalScf::scf(std::basic_ostream& log) { << std::endl; }; - auto t_beg_scf = HRC::now(); - if (info_.log_opts.level > 0) log << header_str << std::endl; double ediff = 0; @@ -88,7 +86,6 @@ void SequantEvalScf::scf(std::basic_ostream& log) { log_iter(iter, energy_, ediff, norm_diff, t_beg_iter, t_end_iter); } while (!converged_ && iter < info_.scf_opts.max_iter); // reset_cache_all(); - auto t_end_scf = HRC::now(); if (info_.log_opts.level > 0) log << std::wstring(20, L'-') << "\n" << "Delta(CC) = " << std::setprecision(double_precision) diff --git a/examples/eval/ta/main.cpp b/examples/eval/ta/main.cpp index 943436b3b..187c53745 100644 --- a/examples/eval/ta/main.cpp +++ b/examples/eval/ta/main.cpp @@ -70,13 +70,24 @@ int main(int argc, char* argv[]) { auto& world = TA::initialize(argc, argv); using namespace sequant; detail::OpIdRegistrar op_id_registrar; - mbpt::set_default_convention(); - sequant::set_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); + sequant::set_default_context(Context( + mbpt::make_min_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); TensorCanonicalizer::register_instance( std::make_shared()); + // for optimization tests, set occupied and unoccupied index extents + { + auto reg = get_default_context().mutable_index_space_registry(); + auto occ = reg->retrieve_ptr(L"i"); + auto uocc = reg->retrieve_ptr(L"a"); + assert(occ); + assert(uocc); + occ->approximate_size(10); + uocc->approximate_size(100); + assert(uocc->approximate_size() == 100); + } + std::string calc_config = argc > 1 ? argv[1] : "calc.inp"; std::string fock_file = argc > 2 ? argv[2] : "fock_so.dat"; std::string eri_file = argc > 3 ? argv[3] : "eri_so.dat"; diff --git a/examples/eval/ta/scf_ta.hpp b/examples/eval/ta/scf_ta.hpp index 3ade4fcdf..ec22b895c 100644 --- a/examples/eval/ta/scf_ta.hpp +++ b/examples/eval/ta/scf_ta.hpp @@ -126,8 +126,6 @@ class SequantEvalScfTA final : public SequantEvalScf { assert(info_.eqn_opts.excit >= 2 && "At least double excitation (CCSD) is required!"); - using HRC = std::chrono::high_resolution_clock; - auto const exprs = info_.exprs(); nodes_ = info_.nodes(exprs); diff --git a/examples/osstcc/osstcc.cpp b/examples/osstcc/osstcc.cpp index 91bd038a9..51a0742f8 100644 --- a/examples/osstcc/osstcc.cpp +++ b/examples/osstcc/osstcc.cpp @@ -29,10 +29,9 @@ int main(int argc, char* argv[]) { std::wcout.sync_with_stdio(true); std::wcerr.sync_with_stdio(true); - sequant::set_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); - mbpt::set_default_convention(); + sequant::set_default_context(Context( + mbpt::make_min_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); TensorCanonicalizer::register_instance( std::make_shared()); @@ -44,7 +43,7 @@ int main(int argc, char* argv[]) { const size_t NMAX = argc > 1 ? std::atoi(argv[1]) : DEFAULT_NMAX; // Spin-orbital coupled cluster - auto cc_r = sequant::mbpt::sr::CC{NMAX}.t(); + auto cc_r = sequant::mbpt::CC{NMAX}.t(); for (auto i = 1; i < cc_r.size(); ++i) { std::cout << "Spin-orbital CC R" << i << " size: " << cc_r[i]->size() << "\n"; diff --git a/examples/srcc/srcc.cpp b/examples/srcc/srcc.cpp index 0a1733e20..b1c9d34fd 100644 --- a/examples/srcc/srcc.cpp +++ b/examples/srcc/srcc.cpp @@ -11,7 +11,7 @@ #include using namespace sequant; -using namespace sequant::mbpt::sr; +using namespace sequant::mbpt; namespace { @@ -79,8 +79,9 @@ class compute_cceqvec { std::vector eqvec_sf_ref; if (get_default_context().spbasis() == SPBasis::spinfree) { auto context_resetter = sequant::set_scoped_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); + sequant::Context(make_min_sr_spaces(), Vacuum::SingleProduct, + IndexSpaceMetric::Unit, BraKetSymmetry::conjugate, + SPBasis::spinorbital)); std::vector eqvec_so; switch (type) { case EqnType::t: @@ -111,7 +112,7 @@ class compute_cceqvec { for (size_t R = PMIN; R <= P; ++R) { std::wcout << "R" << R << "(expS" << N << ") has " << eqvec[R]->size() << " terms:" << std::endl; - if (print) std::wcout << to_latex_align(eqvec[R], 20, 3) << std::endl; + if (print) std::wcout << to_latex_align(eqvec[R], 20, 1) << std::endl; // validate known sizes of some CC residuals // N.B. # of equations depends on whether we use symmetric or @@ -187,7 +188,7 @@ class compute_cceqvec { std::wcout << "biorthogonal spin-free R" << R << "(expS" << N << ") has " << eqvec[R]->size() << " terms:" << std::endl; - if (print) std::wcout << to_latex_align(eqvec[R], 20, 3) << std::endl; + if (print) std::wcout << to_latex_align(eqvec[R], 20, 1) << std::endl; if (R == 1 && N == 2) runtime_assert(eqvec[R]->size() == 26); if (R == 2 && N == 2) runtime_assert(eqvec[R]->size() == 55); @@ -246,21 +247,21 @@ int main(int argc, char* argv[]) { const std::string uocc_type_str = argc > 3 ? argv[3] : "std"; const mbpt::CSV uocc_type = str2uocc.at(uocc_type_str); - auto resetter = set_scoped_default_formalism(mbpt::Context(uocc_type)); + auto mbpt_ctx = set_scoped_default_mbpt_context(mbpt::Context(uocc_type)); const std::string spbasis_str = argc > 4 ? argv[4] : "so"; const SPBasis spbasis = str2spbasis.at(spbasis_str); + const std::string print_str = argc > 5 ? argv[5] : "noprint"; + const bool print = print_str == "print"; + sequant::detail::OpIdRegistrar op_id_registrar; - sequant::set_default_context(Context(Vacuum::SingleProduct, - IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, spbasis)); - mbpt::set_default_convention(); + sequant::set_default_context(sequant::Context( + make_min_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, spbasis)); TensorCanonicalizer::register_instance( std::make_shared()); - // change to true to print out the resulting equations - constexpr bool print = false; // change to true to print stats Logger::get_instance().wick_stats = false; diff --git a/examples/stcc/stcc.cpp b/examples/stcc/stcc.cpp index bebd45d68..551371769 100644 --- a/examples/stcc/stcc.cpp +++ b/examples/stcc/stcc.cpp @@ -31,10 +31,9 @@ int main(int argc, char* argv[]) { std::wcout.sync_with_stdio(true); std::wcerr.sync_with_stdio(true); - sequant::set_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); - mbpt::set_default_convention(); + sequant::set_default_context(Context( + mbpt::make_min_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); TensorCanonicalizer::register_instance( std::make_shared()); @@ -46,7 +45,7 @@ int main(int argc, char* argv[]) { const size_t NMAX = argc > 1 ? std::atoi(argv[1]) : DEFAULT_NMAX; // Spin-orbital coupled cluster - auto cc_r = sequant::mbpt::sr::CC{NMAX}.t(); + auto cc_r = sequant::mbpt::CC{NMAX}.t(); for (auto i = 1; i < cc_r.size(); ++i) { std::cout << "Spin-orbital CC R" << i << " size: " << cc_r[i]->size() << "\n"; diff --git a/examples/stcc_rigorous/stcc_rigorous.cpp b/examples/stcc_rigorous/stcc_rigorous.cpp index 4150b82db..b1573d963 100644 --- a/examples/stcc_rigorous/stcc_rigorous.cpp +++ b/examples/stcc_rigorous/stcc_rigorous.cpp @@ -30,10 +30,9 @@ int main(int argc, char* argv[]) { std::wcout.sync_with_stdio(true); std::wcerr.sync_with_stdio(true); - sequant::set_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); - mbpt::set_default_convention(); + sequant::set_default_context(Context( + mbpt::make_min_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); TensorCanonicalizer::register_instance( std::make_shared()); @@ -45,17 +44,15 @@ int main(int argc, char* argv[]) { const size_t NMAX = argc > 1 ? std::atoi(argv[1]) : DEFAULT_NMAX; /// Make external index - auto ext_idx_list = [](const int i_max) { + [[maybe_unused]] auto ext_idx_list = [](const int i_max) { container::svector> ext_idx_list; - + auto isr = get_default_context().index_space_registry(); for (size_t i = 1; i <= i_max; ++i) { auto label = std::to_wstring(i); - auto occ_space = IndexSpace::instance(IndexSpace::active_occupied); - auto occ_i = - Index(IndexSpace::base_key(occ_space) + L'_' + label, occ_space); - auto uocc_space = IndexSpace::instance(IndexSpace::active_unoccupied); - auto virt_i = - Index(IndexSpace::base_key(uocc_space) + L'_' + label, uocc_space); + auto occ_space = isr->retrieve(L"i"); + auto occ_i = Index(occ_space.base_key() + L'_' + label, occ_space); + auto uocc_space = isr->retrieve(L"a"); + auto virt_i = Index(uocc_space.base_key() + L'_' + label, uocc_space); decltype(ext_idx_list)::value_type pair = {occ_i, virt_i}; ext_idx_list.push_back(pair); } @@ -63,7 +60,7 @@ int main(int argc, char* argv[]) { }; // Spin-orbital coupled cluster - auto cc_r = sequant::mbpt::sr::CC{NMAX}.t(); + auto cc_r = sequant::mbpt::CC{NMAX}.t(); for (auto i = 1; i < cc_r.size(); ++i) { std::cout << "Spin-orbital CC R" << i << " size: " << cc_r[i]->size() << "\n"; diff --git a/examples/synopsis/synopsis1.cpp b/examples/synopsis/synopsis1.cpp index 67de69711..fae305687 100644 --- a/examples/synopsis/synopsis1.cpp +++ b/examples/synopsis/synopsis1.cpp @@ -7,41 +7,47 @@ int main() { using namespace sequant; - IndexSpace sp; - Index p1(L"p_1", sp), p2(L"p_2", sp), p3(L"p_3", sp), p4(L"p_4", sp); + Index p1(L"p_1"), p2(L"p_2"), p3(L"p_3"), p4(L"p_4"); auto cp1 = fcrex(p1), cp2 = fcrex(p2); auto ap3 = fannx(p3), ap4 = fannx(p4); std::wcout << to_latex(ap3 * ap4 * cp1 * cp2) << " = " << to_latex(FWickTheorem{ap3 * ap4 * cp1 * cp2} - .set_external_indices(std::array{p1, p2, p3, p4}) .full_contractions(false) .compute()) << std::endl; - Index p5(L"p_5", sp), p6(L"p_6", sp), p7(L"p_7", sp); + assert(FWickTheorem{ap3 * ap4 * cp1 * cp2} + .full_contractions(false) + .compute() + ->size() == 7); + auto nop1 = ex(std::array{p1, p2}, std::array{p3, p4}); - auto nop2 = ex(std::array{p5}, std::array{p6, p7}); + // auto nop1 = ex(std::vector{p1, p2}, std::array{L"p3", L"p4"}); + // auto nop1 = ex(std::set{"p1", "p2"}, std::vector{L"p3", + // L"p4"}); + // auto nop1 = ex(WstrList{L"p1", L"p2"}, WstrList{L"p3", L"p4"}); + auto nop2 = ex(std::array{L"p_5"}, std::array{L"p_6", L"p_7"}); - std::wcout << to_latex(nop1 * nop2) << " = " - << to_latex(FWickTheorem{nop1 * nop2} - .set_external_indices( - std::array{p1, p2, p3, p4, p5, p6, p7}) - .full_contractions(false) - .compute()) - << std::endl; + std::wcout + << to_latex(nop1 * nop2) << " = " + << to_latex(FWickTheorem{nop1 * nop2}.full_contractions(false).compute()) + << std::endl; + + assert(FWickTheorem{nop1 * nop2}.full_contractions(false).compute()->size() == + 3); auto nop3 = ex(std::array{p1, p2}, std::array{p3, p4}); - auto nop4 = ex(std::array{p5, p6}, std::array{p7}); + auto nop4 = ex(std::array{L"p_5", L"p_6"}, std::array{L"p_7"}); - std::wcout << to_latex(nop3 * nop4) << " = " - << to_latex(BWickTheorem{nop3 * nop4} - .set_external_indices( - std::array{p1, p2, p3, p4, p5, p6, p7}) - .full_contractions(false) - .compute()) - << std::endl; + std::wcout + << to_latex(nop3 * nop4) << " = " + << to_latex(BWickTheorem{nop3 * nop4}.full_contractions(false).compute()) + << std::endl; + + assert(BWickTheorem{nop3 * nop4}.full_contractions(false).compute()->size() == + 7); return 0; } diff --git a/examples/synopsis/synopsis2.cpp b/examples/synopsis/synopsis2.cpp index b0b1df630..5f8fb618b 100644 --- a/examples/synopsis/synopsis2.cpp +++ b/examples/synopsis/synopsis2.cpp @@ -7,9 +7,11 @@ int main() { // the default is to use genuine vacuum assert(get_default_context().vacuum() == Vacuum::Physical); + // make default IndexSpaceRegistry + IndexSpaceRegistry ISR; // now set the context to a single product of SP states - set_default_context(Context{Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::symm}); + set_default_context(Context{ISR, Vacuum::SingleProduct, + IndexSpaceMetric::Unit, BraKetSymmetry::symm}); assert(get_default_context().vacuum() == Vacuum::SingleProduct); // reset the context back to the default reset_default_context(); diff --git a/examples/synopsis/synopsis3.cpp b/examples/synopsis/synopsis3.cpp index 2fe68e47a..d56c2481d 100644 --- a/examples/synopsis/synopsis3.cpp +++ b/examples/synopsis/synopsis3.cpp @@ -1,36 +1,23 @@ #include -#include -#include -#include -#include #include int main() { using namespace sequant; - set_default_context(Context{Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::symm}); + set_default_context(Context{IndexSpaceRegistry{} + .add(L"y", 0b01, is_vacuum_occupied) + .add(L"z", 0b10) + .add(L"p", 0b11, is_complete), + Vacuum::SingleProduct}); - IndexSpace::register_instance(L"y", IndexSpace::occupied); - IndexSpace::register_instance(L"z", IndexSpace::complete_maybe_unoccupied); - - IndexSpace sp; - Index p1(L"p_1", sp), p2(L"p_2", sp), p3(L"p_3", sp), p4(L"p_4", sp); - - auto cp1 = fcrex(p1), cp2 = fcrex(p2); - auto ap3 = fannx(p3), ap4 = fannx(p4); + auto cp1 = fcrex(L"p_1"), cp2 = fcrex(L"p_2"); + auto ap3 = fannx(L"p_3"), ap4 = fannx(L"p_4"); std::wcout << to_latex(ap3 * cp1 * ap4 * cp2) << " = " << to_latex(FWickTheorem{ap3 * cp1 * ap4 * cp2} - .set_external_indices(std::array{p1, p2, p3, p4}) .full_contractions(false) .compute()) << std::endl; - Index y21(L"y_21"); // <- represents IndexSpace::occupied - Index z1(L"z_1"); // <- represents IndexSpace::complete_maybe_unoccupied - auto op_oo_oo = - ex(WstrList{L"y_1", L"y_2"}, WstrList{L"y_3", L"y_4"}); - return 0; } diff --git a/examples/synopsis/synopsis4.cpp b/examples/synopsis/synopsis4.cpp index 4f5af0346..948cadb9c 100644 --- a/examples/synopsis/synopsis4.cpp +++ b/examples/synopsis/synopsis4.cpp @@ -3,13 +3,11 @@ int main() { using namespace sequant; + using namespace sequant::mbpt; + load(Convention::Minimal); - // load the QCiFS convention - mbpt:: - set_default_convention(); // =mbpt::set_default_convention(mbpt::Convention::QCiFS) - - Index i1(L"i_1"); // active occupied - Index a1(L"a_1"); // active unoccupied + Index i1(L"i_1"); // (active) occupied/hole + Index a1(L"a_1"); // (active) unoccupied/particle Index p1(L"p_1"); // any state in computational basis // etc. } diff --git a/examples/synopsis/synopsis5.cpp b/examples/synopsis/synopsis5.cpp new file mode 100644 index 000000000..a25692bec --- /dev/null +++ b/examples/synopsis/synopsis5.cpp @@ -0,0 +1,79 @@ +// +// Created by Conner Masteran on 5/1/24. +// + +#include +#include +#include +#include + +void test0() { + using namespace sequant; + IndexSpaceRegistry isr; + + // base spaces + isr.add(L"i", 0b01).add(L"a", 0b10); + // union of 2 base spaces + // can create manually, as isr.add(L"p", 0b11) , or explicitly ... + isr.add_union(L"p", {L"i", L"a"}); // union of i and a + + // can access unions and intersections of base and composite spaces + assert(isr.unIon(L"i", L"a") == isr.retrieve(L"p")); + assert(isr.intersection(L"p", L"i") == isr.retrieve(L"i")); + + // to use the vocabulary defined by isr use it to make a Context object and + // make it the default + set_default_context(Context(std::move(isr))); + + // now can use space labels to construct Index objects representing said + // spaces + Index i1(L"i_1"); + Index a1(L"a_1"); + Index p1(L"p_1"); + + // set theoretic operations on spaces + assert(i1.space().attr().intersection(a1.space().attr()) == + IndexSpace::Attr::null); +} + +void test1() { + using namespace sequant; + IndexSpaceRegistry isr; + + isr.add(L"x", 0b001) + .add(L"y", 0b010) + .add(L"z", 0b100) + .add(L"xy", 0b011) // union of x and y + .add_union(L"yz", {L"y", L"z"}) // union of y and z, explicit + .add_union(L"xyz", {L"xy", L"yz"}); // union of x, y, and z + + assert(isr.unIon(L"x", L"y") == isr.retrieve(L"xy")); + assert(isr.intersection(L"xyz", L"y") == isr.retrieve(L"y")); + + // use the registry in global context to streamline composition + set_default_context(Context(std::move(isr))); + Index xy1(L"xy_1"); // now can use space labels to define indices +} + +void test2() { + using namespace sequant; + using namespace sequant::mbpt; + // makes 2 base spaces, i and a, and their union + auto isr = make_min_sr_spaces(); + set_default_context(Context(isr)); + + // set theoretic operations on spaces + auto i1 = Index(L"i_1"); + auto a1 = Index(L"a_1"); + assert(i1.space().attr().intersection(a1.space().attr()).type() == + IndexSpace::Type::null); + assert(i1.space().attr().intersection(a1.space().attr()).qns() == Spin::any); +} + +int main() { + test0(); + test1(); + test2(); + + return 0; +} diff --git a/examples/synopsis/synopsis6.cpp b/examples/synopsis/synopsis6.cpp new file mode 100644 index 000000000..e07e90fc6 --- /dev/null +++ b/examples/synopsis/synopsis6.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 202002L +inline auto commutator(auto op1, auto op2) { return op1 * op2 - op2 * op1; } +#else +inline auto commutator(sequant::ExprPtr op1, sequant::ExprPtr op2) { + return op1 * op2 - op2 * op1; +} +#endif + +int main() { + using namespace sequant; + using namespace sequant::mbpt; + set_default_context(Context(make_min_sr_spaces(), Vacuum::SingleProduct)); + + auto hbar = + H(2) + commutator(H(2), T_(2)) + + ex(rational(1, 2)) * commutator(commutator(H(2), T_(2)), T_(2)); + auto ccd_eq = op::vac_av(P(2) * hbar); + std::wcout << "<" << to_latex(P(2) * hbar) << "> = " << to_latex(ccd_eq) + << std::endl; + + return 0; +} diff --git a/examples/tensor_network_graphs/tensor_network_graphs.cpp b/examples/tensor_network_graphs/tensor_network_graphs.cpp index 47b9a34df..4070a19ee 100644 --- a/examples/tensor_network_graphs/tensor_network_graphs.cpp +++ b/examples/tensor_network_graphs/tensor_network_graphs.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,9 @@ void print_help() { int main(int argc, char **argv) { set_locale(); - mbpt::set_default_convention(); + sequant::set_default_context(Context( + mbpt::make_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); bool use_named_indices = true; const TensorNetwork::named_indices_t empty_named_indices; diff --git a/external/boost.cmake b/external/boost.cmake index 50f6fd103..ec949d2dc 100644 --- a/external/boost.cmake +++ b/external/boost.cmake @@ -21,6 +21,7 @@ set(required_components container_hash core fusion + hana locale multiprecision # numeric_conversion diff --git a/external/versions.cmake b/external/versions.cmake index 3fadc60d4..348672399 100644 --- a/external/versions.cmake +++ b/external/versions.cmake @@ -1,7 +1,7 @@ # for each dependency track both current and previous id (the variable for the latter must contain PREVIOUS) # to be able to auto-update them -set(SEQUANT_TRACKED_VGCMAKEKIT_TAG 76773f375c7c429ce57d034512bc8e8ffc4c66c8) +set(SEQUANT_TRACKED_VGCMAKEKIT_TAG 4f40440dcbda2a5a005fd15f27c582a5587ee779) set(SEQUANT_TRACKED_RANGEV3_TAG 0.12.0) set(SEQUANT_TRACKED_RANGEV3_PREVIOUS_TAG d800a032132512a54c291ce55a2a43e0460591c7) diff --git a/python/src/sequant/mbpt.h b/python/src/sequant/mbpt.h index d19c38ef6..77db07d04 100644 --- a/python/src/sequant/mbpt.h +++ b/python/src/sequant/mbpt.h @@ -2,7 +2,7 @@ #define SEQUANT_PYTHON_MBPT_H #include -#include +#include #include #include @@ -32,9 +32,6 @@ ExprPtr VacuumAverage(const ExprPtr& e, const Args&... args) { inline void __init__(py::module m) { sequant::mbpt::set_default_convention(); - sequant::set_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); sequant::TensorCanonicalizer::register_instance( std::make_shared()); diff --git a/tests/unit/test_cache_manager.cpp b/tests/unit/test_cache_manager.cpp index c7b594f68..a0595058b 100644 --- a/tests/unit/test_cache_manager.cpp +++ b/tests/unit/test_cache_manager.cpp @@ -103,4 +103,4 @@ TEST_CASE("TEST_CACHE_MANAGER", "[cache_manager]") { REQUIRE_FALSE(man.access(k)); // nullptr to data returned } } -} \ No newline at end of file +} diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 232b03908..5567c7849 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { TensorCanonicalizer::register_instance( std::make_shared()); + auto ctx_resetter = set_scoped_default_context( + Context(sequant::mbpt::make_legacy_spaces(), Vacuum::SingleProduct)); SECTION("Tensors") { { diff --git a/tests/unit/test_eval_btas.cpp b/tests/unit/test_eval_btas.cpp index 39f5093ca..73d11baa7 100644 --- a/tests/unit/test_eval_btas.cpp +++ b/tests/unit/test_eval_btas.cpp @@ -49,17 +49,18 @@ class rand_tensor_yield { using ranges::views::repeat_n; using ranges::views::transform; using sequant::IndexSpace; + auto isr = sequant::get_default_context().index_space_registry(); assert(ranges::all_of(tnsr.const_braket(), - [](auto const& idx) { - return idx.space() == IndexSpace::active_occupied || - idx.space() == IndexSpace::active_unoccupied; + [&isr](auto const& idx) { + return idx.space() == isr->retrieve(L"i") || + idx.space() == isr->retrieve(L"a"); }) && "Unsupported IndexSpace type found while generating tensor."); auto rng = btas::Range{ - tnsr.const_braket() | transform([this](auto const& idx) { - return idx.space() == IndexSpace::active_occupied ? nocc_ : nvirt_; + tnsr.const_braket() | transform([this, &isr](auto const& idx) { + return idx.space() == isr->retrieve(L"i") ? nocc_ : nvirt_; }) | ranges::to_vector}; @@ -121,9 +122,15 @@ class rand_tensor_yield { using namespace sequant; -template , Index>, - bool> = true> +template < + typename Iterable, + std::enable_if_t< + std::is_convertible_v, Index> && + !sequant::meta::is_statically_castable_v< + Iterable const&, std::wstring> // prefer the ctor taking the + // std::wstring + , + bool> = true> container::svector tidxs(Iterable const& indices) noexcept { return sequant::index_hash(indices) | ranges::to>; } @@ -141,15 +148,6 @@ container::svector tidxs( return tidxs(tnsr_p->as()); } -template < - typename Iterable, - std::enable_if_t, std::wstring>, - bool> = true> -container::svector tidxs(Iterable const& strings) noexcept { - return tidxs(strings | ranges::views::transform( - [](auto const& s) { return Index{s}; })); -} - auto terse_index = [](std::wstring const& spec) { auto formatter = [](boost::wsmatch mo) -> std::wstring { return mo[1].str() + mo[2].str() + L"_" + mo[3].str(); diff --git a/tests/unit/test_eval_expr.cpp b/tests/unit/test_eval_expr.cpp index d35c02356..aa5d6b974 100644 --- a/tests/unit/test_eval_expr.cpp +++ b/tests/unit/test_eval_expr.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/test_eval_node.cpp b/tests/unit/test_eval_node.cpp index 1f619c212..5182952a0 100644 --- a/tests/unit/test_eval_node.cpp +++ b/tests/unit/test_eval_node.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -129,11 +130,7 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { L"Y^{i1,i2}_{a1,a2}"); REQUIRE(node1.right()->op_type() == EvalOp::Prod); - if constexpr (hash_version() == hash::Impl::BoostPre181) { - REQUIRE_TENSOR_EQUAL(node1.right()->as_tensor(), L"I_{a2,a1}^{i1,i2}"); - } else { - REQUIRE_TENSOR_EQUAL(node1.right()->as_tensor(), L"I_{a1,a2}^{i1,i2}"); - } + REQUIRE_TENSOR_EQUAL(node1.right()->as_tensor(), L"I_{a2,a1}^{i1,i2}"); REQUIRE_TENSOR_EQUAL(node1.right().left()->as_tensor(), L"g_{i3,a1}^{i1,i2}"); REQUIRE_TENSOR_EQUAL(node1.right().right()->as_tensor(), L"t_{a2}^{i3}"); diff --git a/tests/unit/test_eval_ta.cpp b/tests/unit/test_eval_ta.cpp index da9f8d44c..d360ef5f5 100644 --- a/tests/unit/test_eval_ta.cpp +++ b/tests/unit/test_eval_ta.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -131,13 +132,13 @@ class rand_tensor_yield { } ERPtr result{nullptr}; + auto isr = get_default_context().index_space_registry(); - auto make_extents = [this](auto&& ixs) -> container::svector { - return ixs | transform([this](auto const& ix) -> size_t { - assert(ix.space() == IndexSpace::active_occupied || - ix.space() == IndexSpace::active_unoccupied); - return ix.space() == IndexSpace::active_occupied ? nocc_ - : nvirt_; + auto make_extents = [this, &isr](auto&& ixs) -> container::svector { + return ixs | transform([this, &isr](auto const& ix) -> size_t { + assert(ix.space() == isr->retrieve(L"i") || + ix.space() == isr->retrieve(L"a")); + return ix.space() == isr->retrieve(L"i") ? nocc_ : nvirt_; }) | ranges::to>; }; diff --git a/tests/unit/test_export.cpp b/tests/unit/test_export.cpp new file mode 100644 index 000000000..704603e7e --- /dev/null +++ b/tests/unit/test_export.cpp @@ -0,0 +1,233 @@ +#include + +#include +#include + +#include +#include +#include + +namespace Catch { + +// Note: Again, template specialization doesn't seem to be used from inside +// ::Catch::Details::stringify for some reason +template <> +struct StringMaker { + static std::string convert(const sequant::ExprPtr &expr) { + using convert_type = std::codecvt_utf8; + std::wstring_convert converter; + + return converter.to_bytes(sequant::deparse_expr(expr, false)); + } +}; + +} // namespace Catch + +std::vector> twoElectronIntegralSymmetries() { + // Symmetries of spin-summed (skeleton) two-electron integrals + return { + // g^{pq}_{rs} + {0, 1, 2, 3}, + // g^{ps}_{rq} + {0, 3, 2, 1}, + // g^{rq}_{ps} + {2, 1, 0, 3}, + // g^{rs}_{pq} + {2, 3, 0, 1}, + + // g^{qp}_{sr} + {1, 0, 3, 2}, + // g^{qr}_{sp} + {1, 2, 3, 0}, + // g^{sp}_{qr} + {3, 0, 1, 2}, + // g^{sr}_{qp} + {3, 2, 1, 0}, + }; +} + +#define CAPTURE_EXPR(expr) \ + INFO(#expr " := " << ::Catch::StringMaker::convert(expr)) + +TEST_CASE("Itf export", "[exports]") { + using namespace sequant; + + SECTION("remap_integrals") { + using namespace sequant::itf::detail; + SECTION("Unchanged") { + auto expr = parse_expr(L"t{i1;a1}"); + auto remapped = expr; + remap_integrals(expr); + REQUIRE(remapped == expr); + + expr = parse_expr(L"t{i1;a1} f{a1;i1} + first{a1;i1} second{i1;a1}"); + remapped = expr; + remap_integrals(expr); + REQUIRE(remapped == expr); + } + + SECTION("K") { + SECTION("occ,occ,occ,occ") { + std::vector indices = {L"i_1", L"i_2", L"i_3", L"i_4"}; + REQUIRE(indices.size() == 4); + const ExprPtr expected = parse_expr(L"K{i1,i2;i3,i4}"); + + for (const std::vector &indexPerm : + twoElectronIntegralSymmetries()) { + REQUIRE(indexPerm.size() == 4); + + ExprPtr integralExpr = ex( + L"g", + std::vector{indices[indexPerm[0]], indices[indexPerm[1]]}, + std::vector{indices[indexPerm[2]], indices[indexPerm[3]]}, + std::vector{}); + + auto transformed = integralExpr; + remap_integrals(transformed); + + CAPTURE(indexPerm); + CAPTURE_EXPR(integralExpr); + CAPTURE_EXPR(transformed); + CAPTURE_EXPR(expected); + + REQUIRE(transformed == expected); + } + } + + SECTION("virt,virt,occ,occ") { + std::vector indices = {L"a_1", L"a_2", L"i_1", L"i_2"}; + REQUIRE(indices.size() == 4); + const ExprPtr expected = parse_expr(L"K{a1,a2;i1,i2}"); + + for (const std::vector &indexPerm : + twoElectronIntegralSymmetries()) { + REQUIRE(indexPerm.size() == 4); + + ExprPtr integralExpr = ex( + L"g", + std::vector{indices[indexPerm[0]], indices[indexPerm[1]]}, + std::vector{indices[indexPerm[2]], indices[indexPerm[3]]}, + std::vector{}); + + auto transformed = integralExpr; + remap_integrals(transformed); + + CAPTURE(indexPerm); + CAPTURE_EXPR(integralExpr); + CAPTURE_EXPR(transformed); + CAPTURE_EXPR(expected); + + REQUIRE(transformed == expected); + } + } + + SECTION("virt,virt,virt,virt") { + std::vector indices = {L"a_1", L"a_2", L"a_3", L"a_4"}; + REQUIRE(indices.size() == 4); + const ExprPtr expected = parse_expr(L"K{a1,a2;a3,a4}"); + + for (const std::vector &indexPerm : + twoElectronIntegralSymmetries()) { + REQUIRE(indexPerm.size() == 4); + + ExprPtr integralExpr = ex( + L"g", + std::vector{indices[indexPerm[0]], indices[indexPerm[1]]}, + std::vector{indices[indexPerm[2]], indices[indexPerm[3]]}, + std::vector{}); + + auto transformed = integralExpr; + remap_integrals(transformed); + + CAPTURE(indexPerm); + CAPTURE_EXPR(integralExpr); + CAPTURE_EXPR(transformed); + CAPTURE_EXPR(expected); + + REQUIRE(transformed == expected); + } + } + } + + SECTION("J") { + SECTION("virt,occ,virt,occ") { + std::vector indices = {L"a_1", L"i_1", L"a_2", L"i_2"}; + REQUIRE(indices.size() == 4); + const ExprPtr expected = parse_expr(L"J{a1,a2;i1,i2}"); + + for (const std::vector &indexPerm : + twoElectronIntegralSymmetries()) { + REQUIRE(indexPerm.size() == 4); + + ExprPtr integralExpr = ex( + L"g", + std::vector{indices[indexPerm[0]], indices[indexPerm[1]]}, + std::vector{indices[indexPerm[2]], indices[indexPerm[3]]}, + std::vector{}); + + auto transformed = integralExpr; + remap_integrals(transformed); + + CAPTURE(indexPerm); + CAPTURE_EXPR(integralExpr); + CAPTURE_EXPR(transformed); + CAPTURE_EXPR(expected); + + REQUIRE(transformed == expected); + } + } + + SECTION("virt,occ,virt,virt") { + std::vector indices = {L"a_1", L"i_1", L"a_2", L"a_3"}; + REQUIRE(indices.size() == 4); + const ExprPtr expected = parse_expr(L"J{a1,a2;a3,i1}"); + + for (const std::vector &indexPerm : + twoElectronIntegralSymmetries()) { + REQUIRE(indexPerm.size() == 4); + + ExprPtr integralExpr = ex( + L"g", + std::vector{indices[indexPerm[0]], indices[indexPerm[1]]}, + std::vector{indices[indexPerm[2]], indices[indexPerm[3]]}, + std::vector{}); + + auto transformed = integralExpr; + remap_integrals(transformed); + + CAPTURE(indexPerm); + CAPTURE_EXPR(integralExpr); + CAPTURE_EXPR(transformed); + CAPTURE_EXPR(expected); + + REQUIRE(transformed == expected); + } + } + } + + SECTION("f") { + SECTION("same_space") { + ExprPtr expr = parse_expr(L"f{i2;i1} + f{a1;a2}"); + const ExprPtr expected = parse_expr(L"f{i1;i2} + f{a1;a2}"); + + remap_integrals(expr); + + CAPTURE_EXPR(expr); + CAPTURE_EXPR(expected); + + REQUIRE(expr == expected); + } + SECTION("different_space") { + ExprPtr expr = parse_expr(L"f{a1;i1} + f{i1;a1}"); + const ExprPtr expected = parse_expr(L"f{a1;i1} + f{a1;i1}"); + + remap_integrals(expr); + + CAPTURE_EXPR(expr); + CAPTURE_EXPR(expected); + + REQUIRE(expr == expected); + } + } + } +} diff --git a/tests/unit/test_expr.cpp b/tests/unit/test_expr.cpp index 054e57443..cee9e4db0 100644 --- a/tests/unit/test_expr.cpp +++ b/tests/unit/test_expr.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -144,7 +145,6 @@ struct latex_visitor { TEST_CASE("Expr", "[elements]") { using namespace sequant; - SECTION("constructors") { REQUIRE_NOTHROW(std::make_shared(2)); const auto ex2 = std::make_shared(2); diff --git a/tests/unit/test_fusion.cpp b/tests/unit/test_fusion.cpp index 9b1d416da..99ee9e823 100644 --- a/tests/unit/test_fusion.cpp +++ b/tests/unit/test_fusion.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -10,7 +9,7 @@ #include #include -TEST_CASE("TEST_FUSION", "[Fusion]") { +TEST_CASE("TEST_FUSION", "[optimize]") { using sequant::opt::Fusion; using namespace sequant; std::vector> fused_terms{ @@ -22,7 +21,7 @@ TEST_CASE("TEST_FUSION", "[Fusion]") { {L"1/8 g{a1,a2;a3,a4} t{a3,a4;i1,i2}", L"1/4 g{a1,a2;a3,a4} t{a3;i1} t{a4;i2}", - L"g{a1,a2;a3,a4}(1/8 t{a3,a4;i1,i2} + 1/4 t{a3;i1} t{a4;i2})"}, + L"1/8 g{a1,a2;a3,a4}(t{a3,a4;i1,i2} + 2 t{a3;i1} t{a4;i2})"}, {L"1/4 g{a1,a2;a3,a4} t{a3;i1} t{a4;i2}", L"1/4 g{i3,i4;a3,a4} t{a1;i3} t{a2;i4} t{a3;i1} t{a4;i2}", @@ -31,13 +30,20 @@ TEST_CASE("TEST_FUSION", "[Fusion]") { {L"1/8 g{i3,i4;a3,a4} t{a1;i3} t{a2;i4} t{a3,a4;i1,i2}", L"1/4 g{i3,i4;a3,a4} t{a1;i3} t{a2;i4} t{a3;i1} t{a4;i2}", - L"g{i3,i4;a3,a4} t{a1;i3} t{a2;i4} " - L" (1/8 t{a3,a4;i1,i2} + 1/4 t{a3;i1} t{a4;i2})"}}; + L"1/8 g{i3,i4;a3,a4} t{a1;i3} t{a2;i4} " + L" (t{a3,a4;i1,i2} + 2 t{a3;i1} t{a4;i2})"}, + + {L"-1/8 g{i3,i4;a3,a4} t{a1;i3} t{a2;i4} t{a3,a4;i1,i2}", + L"-1/4 g{i3,i4;a3,a4} t{a1;i3} t{a2;i4} t{a3;i1} t{a4;i2}", + L"-1/8 g{i3,i4;a3,a4} t{a1;i3} t{a2;i4} " + L" (t{a3,a4;i1,i2} + 2 t{a3;i1} t{a4;i2})"} + + }; for (auto&& [l, r, f] : fused_terms) { - auto const le = parse_expr(l, Symmetry::nonsymm); - auto const re = parse_expr(r, Symmetry::nonsymm); - auto const fe = parse_expr(f, Symmetry::nonsymm); + auto const le = parse_expr(l); + auto const re = parse_expr(r); + auto const fe = parse_expr(f); auto fu = Fusion{le->as(), re->as()}; REQUIRE((fu.left() || fu.right())); diff --git a/tests/unit/test_index.cpp b/tests/unit/test_index.cpp index 05620dced..23715a413 100644 --- a/tests/unit/test_index.cpp +++ b/tests/unit/test_index.cpp @@ -1,17 +1,20 @@ // // Created by Eduard Valeyev on 3/20/18. // + #include -#include #include #include -#include +#include + +#include TEST_CASE("Index", "[elements][index]") { using namespace sequant; SECTION("constructors") { + auto isr = get_default_context().index_space_registry(); Index i{}; REQUIRE_NOTHROW( @@ -21,21 +24,20 @@ TEST_CASE("Index", "[elements][index]") { Index i1(L"i_1"); REQUIRE(i1.label() == L"i_1"); - REQUIRE(i1.space() == IndexSpace::instance(IndexSpace::active_occupied)); + REQUIRE(i1.space() == isr->retrieve(L"i")); - Index i2(L"i_2", IndexSpace::instance(IndexSpace::active_occupied)); + Index i2(L"i_2", + isr->retrieve(L'i')); // N.B. using retrieve(char) REQUIRE(i2.label() == L"i_2"); - REQUIRE(i2.space() == IndexSpace::instance(IndexSpace::active_occupied)); + REQUIRE(i2.space() == isr->retrieve(L"i_1")); // examples with proto indices { - REQUIRE_NOTHROW(Index( - L"i_3", IndexSpace::instance(IndexSpace::active_occupied), {i1, i2})); - Index i3(L"i_3", IndexSpace::instance(IndexSpace::active_occupied), - {i1, i2}); + REQUIRE_NOTHROW(Index(L"i_3", isr->retrieve(L"i_3"), {i1, i2})); + Index i3(L"i_3", isr->retrieve(L"i_3"), {i1, i2}); REQUIRE(i3.label() == L"i_3"); REQUIRE(i3.to_string() == "i_3"); - REQUIRE(i3.space() == IndexSpace::instance(IndexSpace::active_occupied)); + REQUIRE(i3.space() == isr->retrieve(L"i_3")); REQUIRE(i3.has_proto_indices()); REQUIRE(i3.proto_indices().size() == 2); REQUIRE(i3.proto_indices()[0] == i1); @@ -45,7 +47,7 @@ TEST_CASE("Index", "[elements][index]") { Index i4(L"i_4", {L"i_1", L"i_2"}); REQUIRE(i4.label() == L"i_4"); REQUIRE(i4.to_string() == "i_4"); - REQUIRE(i4.space() == IndexSpace::instance(IndexSpace::active_occupied)); + REQUIRE(i4.space() == isr->retrieve(L"i_4")); REQUIRE(i4.has_proto_indices()); REQUIRE(i4.proto_indices().size() == 2); REQUIRE(i4.proto_indices()[0] == i1); @@ -55,7 +57,7 @@ TEST_CASE("Index", "[elements][index]") { REQUIRE_NOTHROW(Index(L"i_5", {i2, i1}, false)); Index i5(L"i_5", {i2, i1}, false); REQUIRE(i5.label() == L"i_5"); - REQUIRE(i5.space() == IndexSpace::instance(IndexSpace::active_occupied)); + REQUIRE(i5.space() == isr->retrieve(L"i_5")); REQUIRE(i5.has_proto_indices()); REQUIRE(i5.proto_indices().size() == 2); REQUIRE(i5.proto_indices()[0] == i2); @@ -65,7 +67,7 @@ TEST_CASE("Index", "[elements][index]") { REQUIRE_NOTHROW(Index(L"i_6", {i1, i5}, false)); Index i6(L"i_6", {i1, i5}, false); REQUIRE(i6.label() == L"i_6"); - REQUIRE(i6.space() == IndexSpace::instance(IndexSpace::active_occupied)); + REQUIRE(i6.space() == isr->retrieve(L"i_6")); REQUIRE(i6.has_proto_indices()); REQUIRE(i6.proto_indices().size() == 2); REQUIRE(i6.proto_indices()[0] == i1); @@ -75,60 +77,39 @@ TEST_CASE("Index", "[elements][index]") { REQUIRE_NOTHROW(Index(L"i_7", {i2, i1})); Index i7(L"i_7", {i2, i1}); REQUIRE(i7.label() == L"i_7"); - REQUIRE(i7.space() == IndexSpace::instance(IndexSpace::active_occupied)); + REQUIRE(i7.space() == isr->retrieve(L"i_7")); REQUIRE(i7.has_proto_indices()); REQUIRE(i7.proto_indices().size() == 2); REQUIRE(i7.proto_indices()[0] == i1); // !! REQUIRE(i7.proto_indices()[1] == i2); // !! #ifndef NDEBUG - REQUIRE_THROWS(Index( - L"i_4", IndexSpace::instance(IndexSpace::active_occupied), {i1, i1})); + REQUIRE_THROWS(Index(L"i_4", isr->retrieve(L"i_4"), {i1, i1})); REQUIRE_THROWS(Index(L"i_5", {L"i_1", L"i_1"})); #endif } - // 'g' is not a standard base key, but we can associate it with an existing - // space to be able to extend the index vocabulary -#ifndef NDEBUG - REQUIRE_THROWS(Index{L"h"}); -#endif - REQUIRE_NOTHROW(IndexSpace::register_key( - L"h", - IndexSpace::all)); // can assign additional key to a space already - // registered, this does not redefine base key - // and now ... - REQUIRE_NOTHROW(Index{L"h"}); - - // copy constructor - REQUIRE_NOTHROW(Index(i1)); - - // ctor that replaces the space + // can use bytestrings also { - Index j1, j2; - REQUIRE_NOTHROW(j1 = Index(L"j_1", IndexSpace::instance( - IndexSpace::active_unoccupied))); - REQUIRE_NOTHROW( - j2 = Index(j1, IndexSpace::instance(IndexSpace::active_occupied))); - REQUIRE(j1.space() == IndexSpace::active_unoccupied); - REQUIRE(j2.label() == j1.label()); - REQUIRE(j2.space() == IndexSpace::active_occupied); - - // same but using replace_space - REQUIRE_NOTHROW( - j1.replace_space(IndexSpace::instance(IndexSpace::active_occupied))); - j2 = j1.replace_space(IndexSpace::instance(IndexSpace::active_occupied)); - REQUIRE(j2.label() == j1.label()); - REQUIRE(j2.space() == IndexSpace::active_occupied); - - // same but using replace_space - REQUIRE_NOTHROW(j1.replace_qns(IndexSpace::alpha)); - j2 = j1.replace_qns(IndexSpace::alpha); - REQUIRE(j2.label() == j1.label()); - REQUIRE(j2.space() == IndexSpace::active_unoccupied); - REQUIRE(j2.space().type() == j1.space().type()); - REQUIRE(j2.space().qns() == IndexSpace::alpha); - REQUIRE(j2.space().qns() != j1.space().qns()); + Index i1("i_1"); + REQUIRE(i1.label() == L"i_1"); + REQUIRE(i1.space() == isr->retrieve("i")); + + Index i2('i'); + REQUIRE(i2.label() == L"i"); + REQUIRE(i2.space() == isr->retrieve("i")); + + // to make things interesting use F12 registry with greek letters + Context cxt(sequant::mbpt::make_F12_sr_spaces(), Vacuum::Physical, + get_default_context().metric(), + get_default_context().braket_symmetry(), + get_default_context().spbasis()); + auto cxt_resetter = set_scoped_default_context(cxt); + Index α("α_2", + get_default_context().index_space_registry()->retrieve("α")); + REQUIRE(α.label() == L"α_2"); + REQUIRE(α.space() == + get_default_context().index_space_registry()->retrieve("α_1")); } } @@ -148,6 +129,7 @@ TEST_CASE("Index", "[elements][index]") { } SECTION("ordering") { + // compare by qns, then tag, then space, then label, then proto indices Index i1(L"i_1"); Index i2(L"i_2"); Index i3(L"i_11"); @@ -162,25 +144,45 @@ TEST_CASE("Index", "[elements][index]") { Index a1(L"a_2"); REQUIRE(i1 < a1); REQUIRE(!(a1 < i1)); - } - SECTION("qns ordering") { - auto p1A = Index(L"p↑_1", - IndexSpace::instance(IndexSpace::all, IndexSpace::alpha)); - auto p1B = - Index(L"p↓_1", IndexSpace::instance(IndexSpace::all, IndexSpace::beta)); - auto p2A = Index(L"p↑_2", - IndexSpace::instance(IndexSpace::all, IndexSpace::alpha)); - auto p2B = - Index(L"p↓_2", IndexSpace::instance(IndexSpace::all, IndexSpace::beta)); - REQUIRE(p1A.space().qns() == IndexSpace::alpha); - REQUIRE(p2A.space().qns() == IndexSpace::alpha); - REQUIRE(p1B.space().qns() == IndexSpace::beta); - REQUIRE(p2B.space().qns() == IndexSpace::beta); + // tags override rest, but ignored if defined for one and not the other + i2.tag().assign(1); + REQUIRE(i1 < i2); + i1.tag().assign(2); + REQUIRE(!(i1 < i2)); + REQUIRE(i2 < i1); + a1.tag().assign(1); + REQUIRE(!(i1 < a1)); + REQUIRE(a1 < i1); + REQUIRE(i2 < a1); + a1.tag().reset().assign(0); + REQUIRE(a1 < i2); + + // qns override rest + IndexSpace p_upspace(L"p", 0b11, 0b01); + IndexSpace p_downspace(L"p", 0b11, 0b10); + auto p1A = Index(L"p↑_1", p_upspace); + auto p1B = Index(L"p↓_1", p_downspace); + auto p2A = Index(L"p↑_2", p_upspace); + auto p2B = Index(L"p↓_2", p_downspace); + REQUIRE(p1A.space().qns() == 0b01); + REQUIRE(p2A.space().qns() == 0b01); + REQUIRE(p1B.space().qns() == 0b10); + REQUIRE(p2B.space().qns() == 0b10); REQUIRE(p1A < p1B); REQUIRE(p2A < p1B); REQUIRE(p1A < p2A); REQUIRE(p1B < p2B); + REQUIRE(!(p1B < p1A)); + REQUIRE(!(p1B < p2A)); + REQUIRE(!(p2A < p1A)); + REQUIRE(!(p2B < p1B)); + REQUIRE(!(p1A < p1A)); + REQUIRE(!(p1B < p1B)); + p2A.tag().assign(1); + REQUIRE(p1A < p2A); + p1A.tag().assign(2); + REQUIRE(p2A < p1A); } SECTION("hashing") { @@ -214,6 +216,10 @@ TEST_CASE("Index", "[elements][index]") { } SECTION("to_string") { + auto old_cxt = get_default_context(); + Context cxt(sequant::mbpt::make_F12_sr_spaces(), Vacuum::Physical, + old_cxt.metric(), old_cxt.braket_symmetry(), old_cxt.spbasis()); + auto cxt_resetter = set_scoped_default_context(cxt); Index alpha(L"α"); REQUIRE(alpha.to_string() == "α"); @@ -229,10 +235,13 @@ TEST_CASE("Index", "[elements][index]") { } SECTION("label manipulation") { - Index alpha(L"α"); - Index alpha1(L"α_1"); - Index alpha_up(L"α↑"); - Index alpha1_up(L"α↑_1"); + auto context_resetter = set_scoped_default_context( + Context(sequant::mbpt::make_F12_sr_spaces(), Vacuum::SingleProduct)); + auto isr = get_default_context().index_space_registry(); + Index alpha(L"α", isr->retrieve(L"α")); + Index alpha1(L"α_1", isr->retrieve(L"α")); + Index alpha_up(L"α↑", isr->retrieve(L"α")); + Index alpha1_up(L"α↑_1", isr->retrieve(L"α")); REQUIRE_NOTHROW(alpha.make_label_plus_suffix(L'↑')); REQUIRE(alpha.make_label_plus_suffix(L'↑') == L"α↑"); REQUIRE_NOTHROW(alpha.make_label_minus_substring(L'↑')); @@ -259,20 +268,18 @@ TEST_CASE("Index", "[elements][index]") { std::wstring a1_str = to_latex(a1); REQUIRE(a1_str == L"{a_1^{{i_1}{i_2^{{i_3}{i_4}}}}}"); - Index a1_up(L"a↑_1", {i1, i2}); - std::wstring a1_up_str = to_latex(a1_up); - REQUIRE(a1_up_str == L"{a↑_1^{{i_1}{i_2^{{i_3}{i_4}}}}}"); - - Index alpha1_up(L"α↑_1", {i1, i2}); - std::wstring alpha1_up_str = to_latex(alpha1_up); - REQUIRE(alpha1_up_str == L"{\\alpha↑_1^{{i_1}{i_2^{{i_3}{i_4}}}}}"); - - Index a12_up(L"a↑_12", {i1, i2}); - std::wstring a12_up_str = to_latex(a12_up); - REQUIRE(a12_up_str == L"{a↑_{12}^{{i_1}{i_2^{{i_3}{i_4}}}}}"); + // a good test of adding new indices to the registry + IndexSpace ar( + L"a→", + get_default_context().index_space_registry()->retrieve(L"a").type()); + get_default_context().mutable_index_space_registry()->add(ar); + Index a1_r(L"a→_1", {i1, i2}); + std::wstring a1_r_str = to_latex(a1_r); + REQUIRE(a1_r_str == L"{a→_1^{{i_1}{i_2^{{i_3}{i_4}}}}}"); + get_default_context().mutable_index_space_registry()->remove(L"a→"); } - SECTION("wolfram") { + /*SECTION("wolfram") { Index i1(L"i_1"); std::wstring i1_str; REQUIRE_NOTHROW(i1_str = i1.to_wolfram()); @@ -307,6 +314,6 @@ TEST_CASE("Index", "[elements][index]") { REQUIRE(Index(L"κ_1").to_wolfram() == L"particleIndex[\"\\!\\(\\*SubscriptBox[\\(κ\\), " L"\\(1\\)]\\)\",particleSpace[occupied,virtual,othervirtual]]"); - } + }*/ } // TEST_CASE("Index") diff --git a/tests/unit/test_iterator.cpp b/tests/unit/test_iterator.cpp index dd9612496..242eed49d 100644 --- a/tests/unit/test_iterator.cpp +++ b/tests/unit/test_iterator.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/test_main.cpp b/tests/unit/test_main.cpp index 03db6212b..b21b03132 100644 --- a/tests/unit/test_main.cpp +++ b/tests/unit/test_main.cpp @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #ifdef SEQUANT_HAS_TILEDARRAY #include @@ -30,11 +32,11 @@ int main(int argc, char* argv[]) { std::wcerr.precision(std::numeric_limits::max_digits10); sequant::set_locale(); sequant::detail::OpIdRegistrar op_id_registrar; - sequant::set_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); - mbpt::set_default_convention(); - + sequant::set_default_context(Context( + sequant::mbpt::make_sr_spaces(), Vacuum::SingleProduct, + IndexSpaceMetric::Unit, BraKetSymmetry::conjugate, SPBasis::spinorbital)); + TensorCanonicalizer::set_cardinal_tensor_labels( + sequant::mbpt::cardinal_tensor_labels()); // uncomment to enable verbose output ... // Logger::set_instance(1); // ... or can instead selectively set/unset particular logging flags diff --git a/tests/unit/test_mbpt.cpp b/tests/unit/test_mbpt.cpp index b264a2fd7..3ff223abe 100644 --- a/tests/unit/test_mbpt.cpp +++ b/tests/unit/test_mbpt.cpp @@ -3,7 +3,6 @@ // #include -#include #include #include #include @@ -15,16 +14,12 @@ #include #include #include -#include +#include #include -#include -#include #include #include "test_config.hpp" -#include -#include #include #include #include @@ -33,8 +28,6 @@ #include #include -#include - TEST_CASE("NBodyOp", "[mbpt]") { using namespace sequant; @@ -49,24 +42,26 @@ TEST_CASE("NBodyOp", "[mbpt]") { WstrList{}) * ex(WstrList{L"p_1"}, WstrList{L"p_2"}); }, - [](qns_t& qns) { - qns += qns_t{1, 1}; - }); + [](qns_t& qns) { qns += general_type_qns(1); }); REQUIRE(f1.label() == L"f"); - { // exact compare + { // exact compare of intervals using namespace boost::numeric::interval_lib::compare::possible; - REQUIRE(operator==(f1(), qns_t{1, 1})); // produces single replacement - REQUIRE(operator!=(f1(), - qns_t{2, 2})); // cannot produce double replacement - REQUIRE(operator==(f1(qns_t{5, 0}), qns_t{{5, 6}, {0, 1}})); + REQUIRE(operator==( + f1()[0], general_type_qns(1)[0])); // produces single replacement + REQUIRE(operator!=( + f1()[0], + general_type_qns(2)[0])); // cannot produce double replacement + /// TODO clearly this test does not make sense for context implicit size + /// of qns. Need help to reimagine this test. + // REQUIRE(operator==(f1(qns_t{5, 0}), qns_t{{5, 6}, {0, 1}})); // } } // tests 2-space quantum number case { - using namespace sequant::mbpt::sr; + using namespace sequant::mbpt; // this is fock operator in terms of general spaces op_t f_gg([]() -> std::wstring_view { return L"f"; }, @@ -75,9 +70,7 @@ TEST_CASE("NBodyOp", "[mbpt]") { WstrList{}) * ex(WstrList{L"p_1"}, WstrList{L"p_2"}); }, - [](qns_t& qns) { - qns += qns_t{{0, 1}, {0, 1}, {0, 1}, {0, 1}}; - }); + [](qns_t& qns) { qns += mbpt::general_type_qns(1); }); // excitation part of the Fock operator op_t f_uo([]() -> std::wstring_view { return L"f"; }, []() -> ExprPtr { @@ -85,9 +78,7 @@ TEST_CASE("NBodyOp", "[mbpt]") { WstrList{}) * ex(WstrList{L"a_1"}, WstrList{L"i_2"}); }, - [](qns_t& qns) { - qns += qns_t{0, 1, 1, 0}; - }); + [](qns_t& qns) { qns += mbpt::excitation_type_qns(1); }); REQUIRE(f_gg.label() == L"f"); REQUIRE(f_uo.label() == L"f"); @@ -95,39 +86,36 @@ TEST_CASE("NBodyOp", "[mbpt]") { { // comparison // exact - REQUIRE( - (f_uo() == qns_t{0, 1, 1, 0})); // f_uo produces single excitations - REQUIRE((f_gg() != - qns_t{0, 1, 1, - 0})); // f_gg does not produce just single excitations + REQUIRE((f_uo() == + excitation_type_qns(1))); // f_uo produces single excitations REQUIRE((f_gg() != - qns_t{0, 1, 1, 0})); // f_gg cannot produce double excitations - REQUIRE( - f_gg().in({0, 1, 1, 0})); // f_gg can produce single excitations - REQUIRE(f_gg().in( - {1, 0, 0, 1})); // f_gg can also produce single de-excitations - REQUIRE(f_gg().in( - {1, 1, 0, 0})); // f_gg can produce replacements withing occupieds - REQUIRE(f_gg().in( - {0, 0, 1, 1})); // f_gg can produce replacements withing virtuals - REQUIRE(f_gg().in( - {1, 1, 1, 1})); // f_gg cannot produce this double replacements, - // but this returns true TODO introduce constraints - // on the total number of creators/annihilators, - // the interval logic does not constrain it - REQUIRE(f_gg().in( - {0, 0, 0, 0})); // f_gg cannot produce a null replacement, but this - // returns true TODO introduce constraints on the - // total number of creators/annihilators, the - // interval logic does not constrain it - - REQUIRE( - f_uo().in({0, 1, 1, 0})); // f_uo can produce single excitations - REQUIRE(!f_uo().in( - {1, 0, 0, 1})); // f_uo cannot produce single de-excitations - REQUIRE(!f_uo().in( - {1, 1, 0, 0})); // f_uo can produce replacements withing occupieds - REQUIRE(!f_uo().in( + excitation_type_qns( + 1))); // f_gg does not produce just single excitations + /* REQUIRE(f_gg().in(excitation_type_qns(1))); // f_gg can produce + single excitations REQUIRE(f_gg().in(deexcitation_type_qns(1))); // + f_gg can also produce single de-excitations REQUIRE(f_gg().in( {1, 1, + 0, 0})); // f_gg can produce replacements within occupieds + REQUIRE(f_gg().in( + {0, 0, 1, 1})); // f_gg can produce replacements within virtuals + REQUIRE(f_gg().in( + {1, 1, 1, 1})); // f_gg cannot produce this double replacements, + // but this returns true TODO introduce + constraints + // on the total number of creators/annihilators, + // the interval logic does not constrain it + REQUIRE(f_gg().in( + {0, 0, 0, 0})); // f_gg cannot produce a null replacement, but + this + // returns true TODO introduce constraints on the + // total number of creators/annihilators, the + // interval logic does not constrain it + */ //most of these seem like artifacts of fixed interval logic. we can add them back if needed + + /*REQUIRE( + f_uo().in(excitation_type_qns(1))); // f_uo can produce single + excitations REQUIRE(!f_uo().in( deexcitation_type_qns(1))); // f_uo + cannot produce single de-excitations REQUIRE(!f_uo().in( {1, 1, 0, 0})); + // f_uo can produce replacements withing occupieds REQUIRE(!f_uo().in( {0, 0, 1, 1})); // f_uo can produce replacements withing virtuals REQUIRE(!f_uo().in( {1, 1, 1, 1})); // f_uo cannot produce double replacements @@ -153,6 +141,7 @@ TEST_CASE("NBodyOp", "[mbpt]") { // REQUIRE(!f1(qns_t{2, 2}).in(0)); // can't produce reference // when // // acting on doubly-excited + */ } { // equal compare // using namespace @@ -168,64 +157,15 @@ TEST_CASE("NBodyOp", "[mbpt]") { } // SECTION("constructor") SECTION("to_latex") { - using qns_t = mbpt::sr::qns_t; - using op_t = mbpt::Operator; - auto f = ex([]() -> std::wstring_view { return L"f"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return F(); - }, - [](qns_t& qns) { - qns += qns_t{{0, 1}, {0, 1}, {0, 1}, {0, 1}}; - }); - auto t1 = ex([]() -> std::wstring_view { return L"t"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return T_(1); - }, - [](qns_t& qns) { - qns += qns_t{0, 1, 1, 0}; - }); - auto t2 = ex([]() -> std::wstring_view { return L"t"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return T_(2); - }, - [](qns_t& qns) { - qns += qns_t{0, 2, 2, 0}; - }); - auto lambda1 = ex([]() -> std::wstring_view { return L"λ"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return Λ_(1); - }, - [](qns_t& qns) { - qns += qns_t{1, 0, 0, 1}; - }); - auto lambda2 = ex([]() -> std::wstring_view { return L"λ"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return Λ_(2); - }, - [](qns_t& qns) { - qns += qns_t{2, 0, 0, 2}; - }); - auto r_2_1 = ex([]() -> std::wstring_view { return L"R"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return R_(1, 2); - }, - [](qns_t& qns) { - qns += qns_t{0, 2, 1, 0}; - }); - auto r_1_2 = ex([]() -> std::wstring_view { return L"R"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return R_(2, 1); - }, - [](qns_t& qns) { - qns += qns_t{0, 1, 2, 0}; - }); + using qns_t [[maybe_unused]] = mbpt::qns_t; + using namespace sequant::mbpt; + auto f = F(); + auto t1 = T(1); + auto t2 = T_(2); + auto lambda1 = Λ_(1); + auto lambda2 = Λ_(2); + auto r_2_1 = R_(2, 1); + auto r_1_2 = R_(1, 2); REQUIRE(to_latex(f) == L"{\\hat{f}}"); REQUIRE(to_latex(t1) == L"{\\hat{t}_{1}}"); @@ -238,49 +178,13 @@ TEST_CASE("NBodyOp", "[mbpt]") { } // SECTION("to_latex") SECTION("canonicalize") { - using qns_t = mbpt::sr::qns_t; - using op_t = mbpt::Operator; - auto f = ex([]() -> std::wstring_view { return L"f"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return F(); - }, - [](qns_t& qns) { - qns += qns_t{{0, 1}, {0, 1}, {0, 1}, {0, 1}}; - }); - auto t1 = ex([]() -> std::wstring_view { return L"t"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return T_(1); - }, - [](qns_t& qns) { - qns += qns_t{0, 1, 1, 0}; - }); - auto l1 = ex([]() -> std::wstring_view { return L"λ"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return Λ_(1); - }, - [](qns_t& qns) { - qns += qns_t{1, 0, 0, 1}; - }); - auto t2 = ex([]() -> std::wstring_view { return L"t"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return T_(2); - }, - [](qns_t& qns) { - qns += qns_t{0, 2, 2, 0}; - }); - auto l2 = ex([]() -> std::wstring_view { return L"λ"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return Λ_(2); - }, - [](qns_t& qns) { - qns += qns_t{2, 0, 0, 2}; - }); - + using qns_t [[maybe_unused]] = mbpt::qns_t; + using namespace sequant::mbpt; + auto f = F(); + auto t1 = T_(1); + auto l1 = Λ_(1); + auto t2 = T_(2); + auto l2 = Λ_(2); REQUIRE(to_latex(f * t1 * t2) == to_latex(canonicalize(f * t2 * t1))); REQUIRE(to_latex(canonicalize(f * t1 * t2)) == to_latex(canonicalize(f * t2 * t1))); @@ -300,66 +204,43 @@ TEST_CASE("NBodyOp", "[mbpt]") { auto t = t1 + t2; - // std::wcout << "to_latex(simplify(f * t * t)): " - // << to_latex(simplify(f * t * t)) << std::endl; - REQUIRE( - to_latex(simplify(f * t * t)) == - to_latex(ex(2) * f * t1 * t2 + f * t2 * t2 + f * t1 * t1)); + { + // std::wcout << "to_latex(simplify(f * t * t)): " + // << to_latex(simplify(f * t * t)) << std::endl; + CHECK( + to_latex(simplify(f * t * t)) == + to_latex(f * t2 * t2 + ex(2) * f * t1 * t2 + f * t1 * t1)); + } - // std::wcout << "to_latex(simplify(f * t * t * t): " - // << to_latex(simplify(f * t * t * t)) << std::endl; - REQUIRE(to_latex(simplify(f * t * t * t)) == - to_latex(f * t2 * t2 * t2 + f * t1 * t1 * t1 + - ex(3) * f * t1 * t1 * t2 + - ex(3) * f * t1 * t2 * t2)); + { + // std::wcout << "to_latex(simplify(f * t * t * t): " + // << to_latex(simplify(f * t * t * t)) << std::endl; + CHECK(to_latex(simplify(f * t * t * t)) == + to_latex(f * t1 * t1 * t1 + f * t2 * t2 * t2 + + ex(3) * f * t1 * t2 * t2 + + ex(3) * f * t1 * t1 * t2)); + } } // SECTION("canonicalize") SECTION("adjoint") { - using qns_t = mbpt::sr::qns_t; + using qns_t = mbpt::qns_t; using op_t = mbpt::Operator; - op_t f([]() -> std::wstring_view { return L"f"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return F(); - }, - [](qns_t& qns) { - qns += qns_t{{0, 1}, {0, 1}, {0, 1}, {0, 1}}; - }); - op_t t1([]() -> std::wstring_view { return L"t"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return T_(1); - }, - [](qns_t& qns) { - qns += qns_t{0, 1, 1, 0}; - }); - op_t lambda2([]() -> std::wstring_view { return L"λ"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return Λ_(2); - }, - [](qns_t& qns) { - qns += qns_t{2, 0, 0, 2}; - }); - op_t r_1_2([]() -> std::wstring_view { return L"R"; }, - []() -> ExprPtr { - using namespace sequant::mbpt::sr; - return R_(2, 1); - }, - [](qns_t& qns) { - qns += qns_t{0, 1, 2, 0}; - }); + using namespace mbpt; + op_t f = F()->as(); + op_t t1 = T_(1)->as(); + op_t lambda2 = Λ_(2)->as(); + op_t r_1_2 = R_(1, 2)->as(); REQUIRE_NOTHROW(adjoint(f)); REQUIRE_NOTHROW(adjoint(t1)); REQUIRE_NOTHROW(adjoint(lambda2)); REQUIRE_NOTHROW(adjoint(r_1_2)); - REQUIRE(adjoint(f)() == qns_t{{0, 1}, {0, 1}, {0, 1}, {0, 1}}); - REQUIRE(adjoint(t1)() == qns_t{{1, 1}, {0, 0}, {0, 0}, {1, 1}}); - REQUIRE(adjoint(lambda2)() == qns_t{{0, 0}, {2, 2}, {2, 2}, {0, 0}}); - REQUIRE(adjoint(r_1_2)() == qns_t{{1, 1}, {0, 0}, {0, 0}, {2, 2}}); + REQUIRE(adjoint(f)() == mbpt::general_type_qns(1)); + REQUIRE(adjoint(t1)() == mbpt::deexcitation_type_qns(1)); + REQUIRE(adjoint(lambda2)() == mbpt::excitation_type_qns(2)); + REQUIRE(adjoint(r_1_2)() == L_(2, 1)->as()()); // adjoint(adjoint(Op)) = Op REQUIRE(adjoint(adjoint(t1))() == t1()); @@ -392,39 +273,23 @@ TEST_CASE("NBodyOp", "[mbpt]") { } // SECTION("adjoint") SECTION("screen") { - // SR - { - using namespace sequant::mbpt::sr::op; - using sequant::mbpt::sr::qns_t; - - auto g_t2_t2 = H_(2) * T_(2) * T_(2); - REQUIRE(!can_change_qns(g_t2_t2, qns_t{0, 0, 0, 0})); - REQUIRE(raises_vacuum_up_to_rank(g_t2_t2, 2)); - - auto g_t2 = H_(2) * T_(2); - REQUIRE(raises_vacuum_to_rank(g_t2, 3)); - - auto lambda2_f = Λ_(2) * H_(1); - REQUIRE(lowers_rank_to_vacuum(lambda2_f, 2)); - } - - // MR - { - using namespace sequant::mbpt::mr::op; - using sequant::mbpt::mr::qns_t; - - auto g_t2 = H_(2) * T_(2); - REQUIRE(can_change_qns(g_t2, qns_t{0, 0, 0, 0, 0, 0})); - REQUIRE(can_change_qns(g_t2, qns_t{0, 0, 4, 4, 0, 0})); - - auto g_t2_t2 = H_(2) * T_(2) * T_(2); - // unlike SR, this can produce vacuum since T2^2 can combine ij -> uv and - // uv -> ab to produce a double excitation - REQUIRE(can_change_qns(g_t2_t2, qns_t{0, 0, 0, 0, 0, 0})); - - auto g_t2a_t2a = H_(2) * T_act_(2) * T_act_(2); - REQUIRE(can_change_qns(g_t2a_t2a, qns_t{0, 0, 0, 0, 0, 0})); - } + using namespace sequant::mbpt; + auto sr_registry = sequant::mbpt::make_sr_spaces(); + auto old_context = get_default_context(); + sequant::Context new_context( + sr_registry, old_context.vacuum(), old_context.metric(), + old_context.braket_symmetry(), old_context.spbasis(), + old_context.first_dummy_index_ordinal()); + auto cxt_resetter = set_scoped_default_context(new_context); + auto g_t2_t2 = H_(2) * T_(2) * T_(2); + REQUIRE(raises_vacuum_to_rank(g_t2_t2, 2)); + REQUIRE(raises_vacuum_up_to_rank(g_t2_t2, 2)); + + auto g_t2 = H_(2) * T_(2); + REQUIRE(raises_vacuum_to_rank(g_t2, 3)); + + auto lambda2_f = Λ_(2) * H_(1); + REQUIRE(lowers_rank_to_vacuum(lambda2_f, 2)); } // SECTION("screen") @@ -436,18 +301,20 @@ TEST_CASE("MBPT", "[mbpt]") { std::make_shared()); SECTION("SRSO") { - using namespace sequant::mbpt::sr; + using namespace sequant::mbpt::tensor; // H**T12**T12 -> R2 SEQUANT_PROFILE_SINGLE("wick(H**T12**T12 -> R2)", { - auto result = vac_av(A(-2) * H() * T(2) * T(2), {{1, 2}, {1, 3}}); + auto result = vac_av(A(-2) * H(2) * T(2) * T(2), {{1, 2}, {1, 3}}); // std::wcout << "H*T12*T12 -> R2 = " << to_latex_align(result, 20) // << std::endl; REQUIRE(result->size() == 15); { // check against op - auto result_op = op::vac_av(op::P(2) * op::H() * op::T(2) * op::T(2)); + auto result_op = + sequant::mbpt::vac_av(sequant::mbpt::P(2) * sequant::mbpt::H() * + sequant::mbpt::T(2) * sequant::mbpt::T(2)); REQUIRE(result_op->size() == result->size()); // as compact as result .. REQUIRE(simplify(result_op - result) == @@ -473,16 +340,14 @@ TEST_CASE("MBPT", "[mbpt]") { { ExprPtr ref_result; SEQUANT_PROFILE_SINGLE("wick(H2**T2**T2**T3 -> R5)", { - ref_result = op::vac_av( - op::A(-5) * op::H_(2) * op::T_(2) * op::T_(2) * op::T_(3), - new_op_connect); + ref_result = + vac_av(A(-5) * H_(2) * T_(2) * T_(2) * T_(3), new_op_connect); REQUIRE(ref_result->size() == 7); }); // now computed using specific component of H2 SEQUANT_PROFILE_SINGLE("wick(H2(oo;vv)**T2**T2**T3 -> R5)", { - auto result = op::vac_av( - op::A(-5) * op::H2_oo_vv() * op::T_(2) * op::T_(2) * op::T_(3), - new_op_connect); + auto result = + vac_av(A(-5) * H2_oo_vv() * T_(2) * T_(2) * T_(3), new_op_connect); REQUIRE(result->size() == ref_result->size()); }); } @@ -490,7 +355,7 @@ TEST_CASE("MBPT", "[mbpt]") { } // SECTION ("SRSO") SECTION("SRSO Fock") { - using namespace sequant::mbpt::sr; + using namespace sequant::mbpt::tensor; // <2p1h|H2|1p> -> SEQUANT_PROFILE_SINGLE("wick(<2p1h|H2|1p>)", ({ @@ -516,10 +381,10 @@ TEST_CASE("MBPT", "[mbpt]") { } // SECTION("SRSO Fock") SECTION("SRSO-PNO") { - using namespace sequant::mbpt; - using namespace sequant::mbpt::sr; + using namespace sequant::mbpt::tensor; using sequant::mbpt::Context; - auto resetter = set_scoped_default_formalism(Context(CSV::Yes)); + auto mbpt_ctx = + sequant::mbpt::set_scoped_default_mbpt_context(Context(mbpt::CSV::Yes)); // H2**T2**T2 -> R2 SEQUANT_PROFILE_SINGLE("wick(H2**T2**T2 -> R2)", { @@ -532,11 +397,11 @@ TEST_CASE("MBPT", "[mbpt]") { } // SECTION("SRSO-PNO") SECTION("SRSF") { - using namespace sequant::mbpt::sr; + using namespace sequant::mbpt::tensor; - auto ctx_resetter = set_scoped_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinfree)); + auto ctx_resetter = set_scoped_default_context(sequant::Context( + mbpt::make_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinfree)); // H2 -> R2 SEQUANT_PROFILE_SINGLE("wick(H2 -> R2)", { @@ -560,48 +425,58 @@ TEST_CASE("MBPT", "[mbpt]") { } // SECTION("SRSF") SECTION("MRSO") { - using namespace sequant::mbpt::mr; + using namespace sequant::mbpt::tensor; + auto ctx_resetter = set_scoped_default_context(sequant::Context( + mbpt::make_mr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); // H2**T2 -> 0 // std::wcout << "H_(2) * T_(2) = " << to_latex(H_(2) * T_(2)) << std::endl; SEQUANT_PROFILE_SINGLE("wick(H2**T2 -> 0)", { - auto result = vac_av(H_(2) * T_(2), {{0, 1}}); - { - std::wcout << "H2*T2 -> 0 = " << to_latex_align(result, 0, 1) - << std::endl; - } - auto result_wo_top = - vac_av(H_(2) * T_(2), {{0, 1}}, /* use_topology = */ false); +std::wcout << "multireference start" << std::endl; +auto result = vac_av(H_(2) * T_(2), {{0, 1}}); - REQUIRE(simplify(result - result_wo_top) == ex(0)); +{ + std::wcout << " multireference H2*T2 -> 0 = " + << to_latex_align(result, 0, 1) << std::endl; +} - // now compute using physical vacuum - { - auto ctx_resetter = set_scoped_default_context( - Context(Vacuum::Physical, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinorbital)); - auto result_phys = vac_av(H_(2) * T_(2), {{0, 1}}); - - { - std::wcout << "H2*T2 -> 0 using phys vacuum = " - << to_latex_align(result_phys, 0, 1) << std::endl; - } - } - }); +auto result_wo_top = + vac_av(H_(2) * T_(2), {{0, 1}}, /* use_topology = */ false); - // H2 ** T2 ** T2 -> 0 - SEQUANT_PROFILE_SINGLE("wick(H2**T2**T2 -> 0)", { - // first without use of topology - auto result = vac_av(H_(2) * T_(2) * T_(2), {{0, 1}, {0, 2}}, - /* use_topology = */ false); - // now with topology use - auto result_top = vac_av(H_(2) * T_(2) * T_(2), {{0, 1}, {0, 2}}, - /* use_topology = */ true); +auto dif = simplify(result - result_wo_top); +std::wcout <<" multireference topology difference" << to_latex(dif) << std::endl; - REQUIRE(simplify(result - result_top) == ex(0)); - }); +REQUIRE(simplify(result - result_wo_top) == ex(0)); + } + + // now compute using physical vacuum + { + auto ctx_resetter = set_scoped_default_context(sequant::Context( + mbpt::make_mr_spaces(), Vacuum::Physical, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); + auto result_phys = vac_av(H_(2) * T_(2), {{0, 1}}); + + { + std::wcout << "H2*T2 -> 0 using phys vacuum = " + << to_latex_align(result_phys, 0, 1) << std::endl; + } + } +}); + +// H2 ** T2 ** T2 -> 0 +SEQUANT_PROFILE_SINGLE("wick(H2**T2**T2 -> 0)", { + // first without use of topology + auto result = vac_av(H_(2) * T_(2) * T_(2), {{0, 1}}, + /* use_topology = */ false); + // now with topology use + auto result_top = vac_av(H_(2) * T_(2) * T_(2), {{0, 1}}, + /* use_topology = */ true); + + REQUIRE(simplify(result - result_top) == ex(0)); +}); #if 0 // H**T12 -> R2 @@ -615,43 +490,41 @@ TEST_CASE("MBPT", "[mbpt]") { }); #endif - } // SECTION("MRSO") +} // SECTION("MRSO") - SECTION("MRSF") { - using namespace sequant::mbpt::mr; +SECTION("MRSF") { + using namespace sequant::mbpt::tensor; - // now compute using (closed) Fermi vacuum + spinfree basis - auto ctx_resetter = set_scoped_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::conjugate, SPBasis::spinfree)); + // now compute using (closed) Fermi vacuum + spinfree basis + auto ctx_resetter = set_scoped_default_context(sequant::Context( + mbpt::make_mr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinfree)); - // H2**T2 -> 0 - std::wcout << "H_(2) * T_(2) = " << to_latex(H_(2) * T_(2)) << std::endl; - SEQUANT_PROFILE_SINGLE("wick(H2**T2 -> 0)", { - auto result = vac_av(H_(2) * T_(2), {{0, 1}}); + // H2**T2 -> 0 + std::wcout << "H_(2) * T_(2) = " << to_latex(H_(2) * T_(2)) << std::endl; + SEQUANT_PROFILE_SINGLE("wick(H2**T2 -> 0)", { + auto result = vac_av(H_(2) * T_(2), {{0, 1}}); - // { - // std::wcout << "H2*T2 -> 0 = " << to_latex_align(result, 0, 1) - // << std::endl; - // } + // { + // std::wcout << "H2*T2 -> 0 = " << to_latex_align(result, 0, 1) + // << std::endl; + // } - { // make sure get same result without use of topology - auto result_wo_top = - vac_av(H_(2) * T_(2), {{0, 1}}, /* use_topology = */ false); + { // make sure get same result without use of topology + auto result_wo_top = + vac_av(H_(2) * T_(2), {{0, 1}}, /* use_topology = */ false); - REQUIRE(simplify(result - result_wo_top) == ex(0)); - } + REQUIRE(simplify(result - result_wo_top) == ex(0)); + } - { // make sure get same result using operators - auto result_op = op::vac_av(op::H_(2) * op::T_(2)); + { // make sure get same result using operators + auto result_op = mbpt::vac_av(mbpt::H_(2) * mbpt::T_(2)); - REQUIRE(result_op->size() == result->size()); - auto diff = simplify(result - result_op); - std::wcout << "Diff: " << deparse_expr(diff) << std::endl; - REQUIRE(diff == ex(0)); - } - }); + REQUIRE(result_op->size() == result->size()); + REQUIRE(simplify(result - result_op) == ex(0)); + } + }); - } // SECTION("MRSF") +} // SECTION("MRSF") } // TEST_CASE("MBPT") diff --git a/tests/unit/test_mbpt_cc.cpp b/tests/unit/test_mbpt_cc.cpp index 084d003de..4f00dba2f 100644 --- a/tests/unit/test_mbpt_cc.cpp +++ b/tests/unit/test_mbpt_cc.cpp @@ -10,11 +10,13 @@ #include "test_config.hpp" TEST_CASE("SR-TCC", "[mbpt/cc]") { - using namespace sequant::mbpt::sr; + using namespace sequant::mbpt; SECTION("t") { // TCC R1 SEQUANT_PROFILE_SINGLE("CCSD t", { + [[maybe_unused]] auto& l = sequant::Logger::get_instance(); + // l.canonicalize = true; const auto N = 2; auto t_eqs = CC{N}.t(); REQUIRE(t_eqs.size() == N + 1); @@ -30,12 +32,12 @@ TEST_CASE("SR-TCC", "[mbpt/cc]") { } // TEST_CASE("SR-TCC") TEST_CASE("SR-UCC", "[mbpt/cc]") { - using namespace sequant::mbpt::sr; + using namespace sequant::mbpt; SECTION("t") { const auto N = 2; const std::size_t C = 3; - CC::Ansatz ansatz = CC::U; + CC::Ansatz ansatz = CC::Ansatz::U; // oUCC energy, up to third commutator auto t_eqs = CC{N, ansatz}.t(C); diff --git a/tests/unit/test_op.cpp b/tests/unit/test_op.cpp index bd4972b18..c838492c7 100644 --- a/tests/unit/test_op.cpp +++ b/tests/unit/test_op.cpp @@ -11,6 +11,7 @@ #include #include +#include TEST_CASE("Op", "[elements]") { using namespace sequant; @@ -198,19 +199,13 @@ TEST_CASE("Op", "[elements]") { REQUIRE(!is_pure_qpannihilator(fann(L"i_1"), V)); REQUIRE(is_pure_qpannihilator(fann(L"a_1"), V)); REQUIRE(!is_pure_qpannihilator(fann(L"p_1"), V)); - - REQUIRE(qpannihilator_space(fann(L"i_1"), V) == - IndexSpace::null_instance()); - REQUIRE(qpannihilator_space(fcre(L"i_1"), V) == - IndexSpace::instance(IndexSpace::active_occupied)); - REQUIRE(qpannihilator_space(fcre(L"a_1"), V) == - IndexSpace::null_instance()); - REQUIRE(qpannihilator_space(fann(L"a_1"), V) == - IndexSpace::instance(IndexSpace::active_unoccupied)); - REQUIRE(qpannihilator_space(fcre(L"p_1"), V) == - IndexSpace::instance(IndexSpace::occupied)); - REQUIRE(qpannihilator_space(fann(L"p_1"), V) == - IndexSpace::instance(IndexSpace::maybe_unoccupied)); + auto isr = get_default_context().index_space_registry(); + REQUIRE(!qpannihilator_space(fann(L"i_1"), V)); + REQUIRE(qpannihilator_space(fcre(L"i_1"), V) == isr->retrieve(L"i_1")); + REQUIRE(!qpannihilator_space(fcre(L"a_1"), V)); + REQUIRE(qpannihilator_space(fann(L"a_1"), V) == isr->retrieve(L"a_1")); + REQUIRE(qpannihilator_space(fcre(L"p_1"), V) == isr->retrieve(L"m_1")); + REQUIRE(qpannihilator_space(fann(L"p_1"), V) == isr->retrieve(L"e_1")); } { constexpr const Vacuum V = Vacuum::Physical; diff --git a/tests/unit/test_optimize.cpp b/tests/unit/test_optimize.cpp index da0f0b15b..dc8ba0d32 100644 --- a/tests/unit/test_optimize.cpp +++ b/tests/unit/test_optimize.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -26,17 +27,27 @@ sequant::ExprPtr extract(sequant::ExprPtr expr, TEST_CASE("TEST_OPTIMIZE", "[optimize]") { using namespace sequant; - auto idx2size = [nocc = 10, nvirt = 140, nact = 4](Index const& idx) { - if (idx.space() == IndexSpace::active_occupied) return nocc; - if (idx.space() == IndexSpace::active_unoccupied) return nvirt; - if (idx.space() == IndexSpace::all_active) - return nact; - else - throw std::runtime_error("Unsupported IndexSpace type encountered"); - }; + // for optimization tests, set index space sizes + { + auto reg = get_default_context().mutable_index_space_registry(); + auto occ = reg->retrieve_ptr(L"i"); + auto uocc = reg->retrieve_ptr(L"a"); + auto aux = reg->retrieve_ptr(L"x"); + assert(occ); + assert(uocc); + assert(aux); + occ->approximate_size(10); + uocc->approximate_size(100); + aux->approximate_size(4); + assert(uocc->approximate_size() == 100); + } - auto single_term_opt = [&idx2size](Product const& prod) { - return opt::single_term_opt(prod, idx2size); + auto single_term_opt = [](Product const& prod) { + return opt::single_term_opt(prod, [](Index const& ix) { + auto lbl = to_string(ix.label()); + auto sz = ix.space().approximate_size(); + return ix.space().approximate_size(); + }); }; auto parse_expr_antisymm = [](auto const& xpr) { @@ -172,7 +183,7 @@ TEST_CASE("TEST_OPTIMIZE", "[optimize]") { auto sum = ex(); sum->as().append(ex(ExprPtrList{parse_expr(L"f{a_1;i_1}")})); REQUIRE(sum->as().summand(0).as().factors().size() == 1); - auto optimized = optimize(sum, idx2size); + auto optimized = optimize(sum); REQUIRE(optimized->is()); REQUIRE(optimized->as().summands().size() == 1); REQUIRE(sum->as().summand(0).as().factors().size() == 1); diff --git a/tests/unit/test_parse_expr.cpp b/tests/unit/test_parse_expr.cpp index 78081a5ec..babf42396 100644 --- a/tests/unit/test_parse_expr.cpp +++ b/tests/unit/test_parse_expr.cpp @@ -3,12 +3,16 @@ #include #include +#include #include #include #include #include #include +#include +#include + #include #include #include @@ -75,6 +79,10 @@ ParseErrorMatcher parseErrorMatches(std::size_t offset, std::size_t length, TEST_CASE("TEST_PARSE_EXPR", "[parse_expr]") { using namespace sequant; + + auto ctx_resetter = set_scoped_default_context( + Context(mbpt::make_sr_spaces(), Vacuum::SingleProduct)); + SECTION("Scalar tensor") { auto expr = parse_expr(L"t{}"); REQUIRE(expr->is()); diff --git a/tests/unit/test_runtime.cpp b/tests/unit/test_runtime.cpp index 70cf94744..beb05fb13 100644 --- a/tests/unit/test_runtime.cpp +++ b/tests/unit/test_runtime.cpp @@ -6,6 +6,9 @@ #include #include +#include + +#include TEST_CASE("Context", "[runtime]") { using namespace sequant; @@ -14,15 +17,29 @@ TEST_CASE("Context", "[runtime]") { auto initial_ctx = get_default_context(); // basic set_default_context test - CHECK_NOTHROW( - set_default_context(Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::symm, SPBasis::spinfree))); + CHECK_NOTHROW(set_default_context(Context( + mbpt::make_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::symm, SPBasis::spinfree))); CHECK(get_default_context().vacuum() == Vacuum::SingleProduct); CHECK(get_default_context().metric() == IndexSpaceMetric::Unit); CHECK(get_default_context().braket_symmetry() == BraKetSymmetry::symm); CHECK(get_default_context().spbasis() == SPBasis::spinfree); - // default default context + // set distinct contexts for fermi and bose statistics + auto [fermi_isr, bose_isr] = mbpt::make_fermi_and_bose_spaces(); + CHECK(fermi_isr->spaces() == + bose_isr->spaces()); // fermi_isr and bose_isr share the space set + CHECK_NOTHROW(set_default_context( + {{Statistics::FermiDirac, Context(fermi_isr, Vacuum::SingleProduct)}, + {Statistics::BoseEinstein, Context(bose_isr, Vacuum::Physical)}})); + CHECK(get_default_context(Statistics::Arbitrary).vacuum() == + Vacuum::SingleProduct); + CHECK(get_default_context(Statistics::FermiDirac).vacuum() == + Vacuum::SingleProduct); + CHECK(get_default_context(Statistics::BoseEinstein).vacuum() == + Vacuum::Physical); + + // reset back to default CHECK_NOTHROW(reset_default_context()); CHECK(get_default_context().vacuum() == Vacuum::Physical); CHECK(get_default_context().metric() == IndexSpaceMetric::Unit); @@ -37,17 +54,18 @@ TEST_CASE("Context", "[runtime]") { // scoped changes to default context { // if we do not save the resetter context is reset back immediately - CHECK_NOTHROW(set_scoped_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::symm, SPBasis::spinfree))); + CHECK_NOTHROW(set_scoped_default_context(Context( + mbpt::make_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::symm, SPBasis::spinfree))); CHECK(get_default_context() == initial_ctx); - auto resetter = set_scoped_default_context( - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::symm, SPBasis::spinfree)); + auto resetter = set_scoped_default_context(Context( + mbpt::make_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::symm, SPBasis::spinfree)); CHECK(get_default_context() == - Context(Vacuum::SingleProduct, IndexSpaceMetric::Unit, - BraKetSymmetry::symm, SPBasis::spinfree)); + Context(mbpt::make_sr_spaces(), Vacuum::SingleProduct, + IndexSpaceMetric::Unit, BraKetSymmetry::symm, + SPBasis::spinfree)); } // leaving scope resets the context back CHECK(get_default_context() == initial_ctx); diff --git a/tests/unit/test_space.cpp b/tests/unit/test_space.cpp index 3e7ac28ff..b6cf69f0e 100644 --- a/tests/unit/test_space.cpp +++ b/tests/unit/test_space.cpp @@ -5,92 +5,94 @@ #include #include +#include +#include TEST_CASE("IndexSpace", "[elements]") { using namespace sequant; - SECTION("register_instance") { - REQUIRE_THROWS(IndexSpace::register_instance( - L"p", IndexSpace::all)); // already registered standard instances - REQUIRE_THROWS( - IndexSpace::register_instance(L"p_1", IndexSpace::all)); // ditto - // since convention does not include ALL standard spaces - // (see https://github.com/ValeevGroup/SeQuant/issues/97 ) - // user can register additional instances - REQUIRE_NOTHROW( - IndexSpace::register_instance(L"m'", IndexSpace::frozen_occupied)); - REQUIRE(IndexSpace::instance_exists(L"m'")); + SECTION("registry synopsis") { + auto sr_isr = sequant::mbpt::make_sr_spaces(); + REQUIRE_NOTHROW(sr_isr->retrieve(L"i")); + REQUIRE_NOTHROW(sr_isr->remove(L"i")); + IndexSpace active_occupied(L"i", 0b0010); + REQUIRE_NOTHROW(sr_isr->add(active_occupied)); + REQUIRE_THROWS(sr_isr->add( + active_occupied)); // cannot add a space that is already present + IndexSpace jactive_occupied(L"j", 0b0010); + REQUIRE_THROWS(sr_isr->add( + jactive_occupied)); // cannot add a space with duplicate attr + + // can use bytestrings too + REQUIRE_NOTHROW(sr_isr->retrieve("a")); // N.B. string + REQUIRE_NOTHROW(sr_isr->remove('a')); // N.B. char } - // these are loaded in test_main.cpp - SECTION("register_standard_instances") { - REQUIRE(IndexSpace::instance_exists(L"i")); - REQUIRE(IndexSpace::instance_exists(L"m")); - REQUIRE(IndexSpace::instance_exists(L"p")); - REQUIRE(IndexSpace::instance_exists(L"a_17")); - REQUIRE(IndexSpace::instance_exists(L"e_19")); - REQUIRE(IndexSpace::instance_exists(L"α_21")); - REQUIRE(IndexSpace::instance_exists(L"α'_32")); - REQUIRE(IndexSpace::instance_exists(L"κ_48")); - } + SECTION("registry construction") { + auto isr = std::make_shared(); - SECTION("register_key") { - REQUIRE_NOTHROW(IndexSpace::register_key( - L"w", - IndexSpace::all)); // can assign additional key to a space already - // registered, this does not redefine base key - REQUIRE(IndexSpace::instance(L"w") == IndexSpace::instance(L"p")); + // similar make_sr_spaces, but no spin AND occupied and unoccupied bits are + // NOT contiguous! + REQUIRE_NOTHROW( + isr->add(L"o", 0b0001, 3) // approximate size + .add("i", 0b0100, is_hole) // N.B. narrow string + .add(L'a', 0b0010, is_particle, QuantumNumbersAttr{}, + 50) // N.B. wchar_t + explicit quantum numbers + size + .add('g', 0b1000) + .add_union(L"m", {L"o", L"i"}, is_vacuum_occupied, + is_reference_occupied) + .add_union(L"e", {L"a", L"g"}) + .add_unIon(L"p", {L"m", L"e"}, is_complete) // N.B. unIon + ); + REQUIRE(isr->retrieve(L"o").approximate_size() == 3); + REQUIRE(isr->retrieve(L"m").type().to_int32() == 0b0101); + REQUIRE(isr->retrieve(L"m").approximate_size() == + isr->retrieve(L"o").approximate_size() + + isr->retrieve(L"i").approximate_size()); + REQUIRE(isr->retrieve(L"p").type().to_int32() == 0b1111); + REQUIRE(isr->retrieve(L"p").approximate_size() == + isr->retrieve(L"m").approximate_size() + + isr->retrieve(L"e").approximate_size()); + REQUIRE(isr->retrieve(L"p").approximate_size() == 73); + REQUIRE_NOTHROW( + isr->add_union(L"iag", {L"i", L"a", L"g"}) + .add_union(L"oia", {"o", "i", "a"}) // N.B. narrow strings + .add_intersection(L"x", {L"oia", L"iag"})); + REQUIRE(isr->retrieve(L"x").type().to_int32() == 0b0110); + REQUIRE(isr->retrieve(L"x").approximate_size() == + isr->retrieve(L"i").approximate_size() + + isr->retrieve(L"a").approximate_size()); + REQUIRE(isr->vacuum_occupied_space(IndexSpace::QuantumNumbers::null) == + isr->retrieve(L"m")); + REQUIRE(isr->vacuum_unoccupied_space(IndexSpace::QuantumNumbers::null) == + isr->retrieve(L"e")); } SECTION("equality") { - REQUIRE(IndexSpace::instance(L"i") == IndexSpace::instance(L"i")); - REQUIRE(IndexSpace::instance(L"i") != IndexSpace::instance(L"p")); - - REQUIRE(IndexSpace::null_instance() == - IndexSpace::instance(IndexSpace::null_key())); - - REQUIRE(IndexSpace::instance(L"i").type() == IndexSpace::active_occupied); - REQUIRE(IndexSpace::instance(L"i") == IndexSpace::active_occupied); - REQUIRE(IndexSpace::active_occupied == IndexSpace::instance(L"i").type()); - REQUIRE(IndexSpace::active_occupied == IndexSpace::instance(L"i")); - REQUIRE(IndexSpace::instance(L"i").qns() == IndexSpace::nullqns); - REQUIRE(IndexSpace::instance(L"i") == IndexSpace::nullqns); - REQUIRE(IndexSpace::nullqns == IndexSpace::instance(L"i").qns()); - REQUIRE(IndexSpace::nullqns == IndexSpace::instance(L"i")); - - // use nondefault mask - TypeAttr::used_bits = 0b100; - REQUIRE(IndexSpace::active_occupied == IndexSpace::occupied); - REQUIRE(IndexSpace::active_occupied != IndexSpace::inactive_occupied); - REQUIRE(IndexSpace::active_occupied != IndexSpace::frozen_occupied); - REQUIRE(IndexSpace::active_occupied == IndexSpace::all); - REQUIRE(IndexSpace::active_occupied == IndexSpace::all_active); - TypeAttr::used_bits = 0xffff; + auto sr_isr = sequant::mbpt::make_sr_spaces(); + REQUIRE(sr_isr->retrieve(L"i") == sr_isr->retrieve(L"i")); + REQUIRE(sr_isr->retrieve(L"i") != sr_isr->retrieve(L"a")); } SECTION("ordering") { - REQUIRE(!(IndexSpace::instance(L"i") < IndexSpace::instance(L"i"))); - REQUIRE(IndexSpace::instance(L"i") < IndexSpace::instance(L"a")); - REQUIRE(!(IndexSpace::instance(L"a") < IndexSpace::instance(L"i"))); - REQUIRE(IndexSpace::instance(L"i") < IndexSpace::instance(L"m")); - REQUIRE(!(IndexSpace::instance(L"m") < IndexSpace::instance(L"m"))); - REQUIRE(IndexSpace::instance(L"m") < IndexSpace::instance(L"a")); - REQUIRE(IndexSpace::instance(L"m") < IndexSpace::instance(L"p")); - REQUIRE(IndexSpace::instance(L"a") < IndexSpace::instance(L"p")); - REQUIRE(IndexSpace::instance(L"p") < IndexSpace::instance(L"α")); + auto sr_isr = sequant::mbpt::make_sr_spaces(); + REQUIRE(!(sr_isr->retrieve(L"i") < sr_isr->retrieve(L"i"))); + REQUIRE(sr_isr->retrieve(L"i") < sr_isr->retrieve(L"a")); + REQUIRE(!(sr_isr->retrieve(L"a") < sr_isr->retrieve(L"i"))); + REQUIRE(sr_isr->retrieve(L"i") < sr_isr->retrieve(L"m")); + REQUIRE(!(sr_isr->retrieve(L"m") < sr_isr->retrieve(L"m"))); + REQUIRE(sr_isr->retrieve(L"m") < sr_isr->retrieve(L"a")); + REQUIRE(sr_isr->retrieve(L"m") < sr_isr->retrieve(L"p")); + REQUIRE(sr_isr->retrieve(L"a") < sr_isr->retrieve(L"p")); // test ordering with quantum numbers { - auto i = IndexSpace::instance(L"i"); - [[maybe_unused]] auto a = IndexSpace::instance(L"a"); - auto iA = - IndexSpace::instance(IndexSpace::active_occupied, IndexSpace::alpha); - auto iB = - IndexSpace::instance(IndexSpace::active_occupied, IndexSpace::beta); - auto aA = IndexSpace::instance(IndexSpace::active_unoccupied, - IndexSpace::alpha); - auto aB = - IndexSpace::instance(IndexSpace::active_unoccupied, IndexSpace::beta); + auto i = IndexSpace(L"i", 0b01); + auto a = IndexSpace(L"a", 0b10); + auto iA = IndexSpace(L"i", 0b01, 0b01); + auto iB = IndexSpace(L"i", 0b01, 0b10); + auto aA = IndexSpace(L"a", 0b10, 0b01); + auto aB = IndexSpace(L"a", 0b10, 0b10); REQUIRE(iA < aA); REQUIRE(iB < aB); @@ -102,40 +104,74 @@ TEST_CASE("IndexSpace", "[elements]") { REQUIRE(i < iA); REQUIRE(i < iB); } - - // use nondefault mask - TypeAttr::used_bits = 0b100; - REQUIRE(!(IndexSpace::active_occupied < IndexSpace::occupied)); - REQUIRE(!(IndexSpace::inactive_occupied < IndexSpace::frozen_occupied)); - REQUIRE(IndexSpace::inactive_occupied < IndexSpace::active_occupied); - REQUIRE(!(IndexSpace::active_occupied < IndexSpace::all)); - REQUIRE(!(IndexSpace::active_occupied < IndexSpace::all_active)); - TypeAttr::used_bits = 0xffff; } SECTION("set operations") { - REQUIRE( - IndexSpace::instance(L"i") == - intersection(IndexSpace::instance(L"i"), IndexSpace::instance(L"p"))); - REQUIRE( - IndexSpace::null_instance() == - intersection(IndexSpace::instance(L"a"), IndexSpace::instance(L"i"))); - REQUIRE( - IndexSpace::null_instance() == - intersection(IndexSpace::instance(L"a"), IndexSpace::instance(L"α'"))); - - REQUIRE(IndexSpace::instance(L"κ") == - unIon(IndexSpace::instance(L"p"), IndexSpace::instance(L"α'"))); - - REQUIRE(includes(IndexSpace::instance(L"κ"), IndexSpace::instance(L"m"))); - REQUIRE(!includes(IndexSpace::instance(L"m"), IndexSpace::instance(L"κ"))); - REQUIRE(includes(IndexSpace::instance(L"α"), IndexSpace::instance(L"a"))); + auto isr = sequant::mbpt::make_F12_sr_spaces(); + REQUIRE(isr->retrieve(L"i") == + isr->intersection(isr->retrieve(L"i"), isr->retrieve(L"p"))); + REQUIRE(!isr->intersection(isr->retrieve(L"p↑"), isr->retrieve(L"p↓"))); + REQUIRE(isr->intersection(isr->retrieve(L"i↑"), isr->retrieve(L"p")) == + isr->retrieve(L"i↑")); + REQUIRE(!isr->intersection(isr->retrieve(L"a"), isr->retrieve(L"i"))); + REQUIRE(!isr->intersection(isr->retrieve(L"a"), isr->retrieve(L"α'"))); + + REQUIRE(isr->retrieve(L"κ") == + isr->unIon(isr->retrieve(L"p"), isr->retrieve(L"α'"))); + + REQUIRE(includes(isr->retrieve(L"κ"), isr->retrieve(L"m"))); + REQUIRE(!includes(isr->retrieve(L"m"), isr->retrieve(L"κ"))); + REQUIRE(includes(isr->retrieve(L"α"), isr->retrieve(L"a"))); + + REQUIRE(isr->valid_intersection(isr->retrieve(L"i"), isr->retrieve(L"p"))); + + REQUIRE(isr->valid_unIon(isr->retrieve(L"i"), isr->retrieve(L"a"))); + REQUIRE(isr->valid_unIon(isr->retrieve(L"i↑"), isr->retrieve(L"i↓"))); + REQUIRE(!isr->valid_unIon(isr->retrieve(L"i↑"), isr->retrieve(L"i↑"))); + REQUIRE(!isr->valid_unIon(isr->retrieve(L"p"), isr->retrieve(L"a"))); + REQUIRE(!isr->valid_unIon(isr->retrieve(L"p↑"), isr->retrieve(L"a↓"))); + } + + SECTION("occupancy_validation") { + auto sr_isr = sequant::mbpt::make_sr_spaces(); + auto mr_isr = sequant::mbpt::make_mr_spaces(); + + REQUIRE(sr_isr->is_pure_occupied(sr_isr->retrieve(L"i"))); + REQUIRE(sr_isr->is_pure_unoccupied(sr_isr->retrieve(L"a"))); + + REQUIRE(mr_isr->is_pure_occupied(mr_isr->retrieve(L"i"))); + REQUIRE(mr_isr->is_pure_unoccupied(mr_isr->retrieve(L"a"))); + REQUIRE(!mr_isr->is_pure_occupied(mr_isr->retrieve(L"I"))); + // REQUIRE(!mr_isr->is_pure_unoccupied(mr_isr->retrieve(L"E"))); + + REQUIRE(sr_isr->contains_occupied(sr_isr->retrieve(L"i"))); + REQUIRE(sr_isr->contains_unoccupied(sr_isr->retrieve(L"a"))); + + REQUIRE(mr_isr->contains_occupied(mr_isr->retrieve(L"M"))); + REQUIRE(mr_isr->contains_unoccupied(mr_isr->retrieve(L"E"))); + REQUIRE(!mr_isr->contains_occupied(mr_isr->retrieve(L"a"))); + REQUIRE(!mr_isr->contains_unoccupied(mr_isr->retrieve(L"i"))); } - SECTION("occupancy_class") { - REQUIRE(occupancy_class(IndexSpace::instance(L"i")) == -1); - REQUIRE(occupancy_class(IndexSpace::instance(L"a")) == +1); - REQUIRE(occupancy_class(IndexSpace::instance(L"p")) == 0); - REQUIRE(occupancy_class(IndexSpace::instance(L"u")) == +1); + SECTION("base_space") { + auto isr = sequant::mbpt::make_F12_sr_spaces(); + const auto& f12_base_space_types = isr->base_space_types(); + REQUIRE(f12_base_space_types.size() == 5); + REQUIRE(f12_base_space_types[0] == 0b00001); + REQUIRE(f12_base_space_types[1] == 0b00010); + REQUIRE(f12_base_space_types[2] == 0b00100); + REQUIRE(f12_base_space_types[3] == 0b01000); + REQUIRE(f12_base_space_types[4] == 0b10000); + const auto& f12_base_spaces = isr->base_spaces(); + auto f12_base_spaces_sf = f12_base_spaces | + ranges::views::filter([](const auto& space) { + return space.qns() == mbpt::Spin::any; + }) | + ranges::to_vector; + REQUIRE(f12_base_spaces_sf[0].base_key() == L"o"); + REQUIRE(f12_base_spaces_sf[1].base_key() == L"i"); + REQUIRE(f12_base_spaces_sf[2].base_key() == L"a"); + REQUIRE(f12_base_spaces_sf[3].base_key() == L"g"); + REQUIRE(f12_base_spaces_sf[4].base_key() == L"α'"); } } diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index e37dc5e93..b924579ee 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -2,6 +2,9 @@ // Created by Nakul Teke on 12/20/19. // +#include +#include "test_config.hpp" + #include #include #include @@ -14,6 +17,7 @@ #include #include #include +#include #include #include @@ -45,8 +49,9 @@ TEST_CASE("Spin", "[spin]") { }; SECTION("protoindices supported") { - Index i1(L"i_1", IndexSpace::instance(IndexSpace::active_occupied)); - Index a1(L"a_1", IndexSpace::instance(IndexSpace::active_unoccupied), {i1}); + auto isr = get_default_context().index_space_registry(); + Index i1(L"i_1", isr->retrieve(L"i")); + Index a1(L"a_1", isr->retrieve(L"a"), {i1}); const auto expr = ex(L"t", IndexList{i1}, IndexList{a1}, IndexList{}) * @@ -61,27 +66,27 @@ TEST_CASE("Spin", "[spin]") { { // assume spin-dependent spaces auto expr_st = spintrace(expr, {}, /* assume_spin_free_spaces */ false); simplify(expr_st); + std::wcout << expr_st->to_latex() << std::endl; REQUIRE(expr_st->to_latex() == L"{ " L"\\bigl({{t^{{a↓_1^{{i↓_1}}}}_{{i↓_1}}}{F^{{i↓_1}}_{{a↓_1^{{i↓_" - L"1}}}}}} + " + L"1}}}}}}" + L" + " L"{{t^{{a↑_1^{{i↑_1}}}}_{{i↑_1}}}{F^{{i↑_1}}_{{a↑_1^{{i↑_1}}}}}}" L"\\bigr) }"); } } SECTION("ASCII label") { - auto p1 = Index(L"p↑_1", - IndexSpace::instance(IndexSpace::all, IndexSpace::alpha)); - auto p2 = - Index(L"p↓_2", IndexSpace::instance(IndexSpace::all, IndexSpace::beta)); - auto p3 = Index(L"p↑_3", - IndexSpace::instance(IndexSpace::all, IndexSpace::alpha)); - auto p4 = - Index(L"p↓_4", IndexSpace::instance(IndexSpace::all, IndexSpace::beta)); - auto alpha1 = - Index(L"α↑_1", IndexSpace::instance(IndexSpace::complete_unoccupied, - IndexSpace::alpha)); + IndexSpace pup(L"p", 0b011, mbpt::Spin::alpha); + IndexSpace pdown(L"p", 0b011, mbpt::Spin::beta); + IndexSpace alphaup(L"α", 0b110, mbpt::Spin::alpha); + + auto p1 = Index(L"p↑_1", pup); + auto p2 = Index(L"p↓_2", pdown); + auto p3 = Index(L"p↑_3", pup); + auto p4 = Index(L"p↓_4", pdown); + auto alpha1 = Index(L"α↑_1", alphaup); SEQUANT_PRAGMA_CLANG(diagnostic push) SEQUANT_PRAGMA_CLANG(diagnostic ignored "-Wdeprecated-declarations") @@ -97,39 +102,26 @@ TEST_CASE("Spin", "[spin]") { } SECTION("Index: add/remove spin") { - auto i = Index(L"i", IndexSpace::instance(IndexSpace::active_occupied, - IndexSpace::nullqns)); - auto i1 = Index(L"i_1", IndexSpace::instance(IndexSpace::active_occupied, - IndexSpace::nullqns)); - auto p = - Index(L"p", IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns)); - auto p1 = Index(L"p_1", - IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns)); - auto p1_a = Index(L"p↑_1", - IndexSpace::instance(IndexSpace::all, IndexSpace::alpha)); - auto p2 = Index(L"p_2", - IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns)); - auto p2_b = - Index(L"p↓_2", IndexSpace::instance(IndexSpace::all, IndexSpace::beta)); - - auto p_i = Index( - L"p", IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns), {i}); - auto p1_i = - Index(L"p_1", - IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns), {i}); - auto p_i1 = Index( - L"p", IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns), {i1}); - auto p1_i1 = - Index(L"p_1", - IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns), {i1}); + auto i = Index(L"i", {L"i", 0b01, mbpt::Spin::any}); + auto i1 = Index(L"i_1", {L"i", 0b01, mbpt::Spin::any}); + auto p = Index(L"p", {L"p", 0b11, mbpt::Spin::any}); + auto p1 = Index(L"p_1", {L"p", 0b11, mbpt::Spin::any}); + auto p1_a = Index(L"p↑_1", {L"p↑", 0b11, mbpt::Spin::alpha}); + auto p2 = Index(L"p_2", {L"p", 0b11, mbpt::Spin::any}); + auto p2_b = Index(L"p↓_2", {L"p↓", 0b11, mbpt::Spin::beta}); + + auto p_i = Index(L"p", {L"p", 0b11, mbpt::Spin::any}, {i}); + auto p1_i = Index(L"p_1", {L"p", 0b11, mbpt::Spin::any}, {i}); + auto p_i1 = Index(L"p", {L"p", 0b11, mbpt::Spin::any}, {i1}); + auto p1_i1 = Index(L"p_1", {L"p", 0b11, mbpt::Spin::any}, {i1}); // make_spinalpha { // plain REQUIRE_NOTHROW(make_spinalpha(p)); REQUIRE(make_spinalpha(p).label() == L"p↑"); - REQUIRE(make_spinalpha(p).space() == - IndexSpace::instance(IndexSpace::all, IndexSpace::alpha)); + IndexSpace p_a(L"p↑", 0b11, mbpt::Spin::alpha); + REQUIRE(make_spinalpha(p).space() == p_a); REQUIRE_NOTHROW(make_spinalpha(p1)); REQUIRE(make_spinalpha(p1) == p1_a); // idempotent @@ -180,32 +172,28 @@ TEST_CASE("Spin", "[spin]") { // make spinnull { // plain - REQUIRE_NOTHROW(make_spinnull(p1_a)); - REQUIRE(make_spinnull(p1_a) == p1); - REQUIRE_NOTHROW(make_spinnull(p2_b)); - REQUIRE(make_spinnull(p2_b) == p2); - REQUIRE_NOTHROW(make_spinnull(p1)); - REQUIRE(make_spinnull(p1) == p1); + REQUIRE_NOTHROW(make_spinfree(p1_a)); + REQUIRE(make_spinfree(p1_a) == p1); + REQUIRE_NOTHROW(make_spinfree(p2_b)); + REQUIRE(make_spinfree(p2_b) == p2); + REQUIRE_NOTHROW(make_spinfree(p1)); + REQUIRE(make_spinfree(p1) == p1); // idempotent - REQUIRE_NOTHROW(make_spinnull(p2)); - REQUIRE(make_spinnull(p2) == p2); + REQUIRE_NOTHROW(make_spinfree(p2)); + REQUIRE(make_spinfree(p2) == p2); // proto - REQUIRE_NOTHROW(make_spinnull(make_spinalpha(p1_i1))); - REQUIRE(make_spinnull(make_spinalpha(p1_i1)) == p1_i1); - REQUIRE(make_spinnull(make_spinalpha(p1_i1)) == make_spinnull(p1_i1)); + REQUIRE_NOTHROW(make_spinfree(make_spinalpha(p1_i1))); + REQUIRE(make_spinfree(make_spinalpha(p1_i1)) == p1_i1); + REQUIRE(make_spinfree(make_spinalpha(p1_i1)) == make_spinfree(p1_i1)); } } SECTION("Tensor: can_expand, spin_symm_tensor, remove_spin") { - auto p1 = Index(L"p↑_1", - IndexSpace::instance(IndexSpace::all, IndexSpace::alpha)); - auto p2 = - Index(L"p↓_2", IndexSpace::instance(IndexSpace::all, IndexSpace::beta)); - auto p3 = Index(L"p↑_3", - IndexSpace::instance(IndexSpace::all, IndexSpace::alpha)); - auto p4 = - Index(L"p↓_4", IndexSpace::instance(IndexSpace::all, IndexSpace::beta)); + auto p1 = Index(L"p↑_1"); + auto p2 = Index(L"p↓_2"); + auto p3 = Index(L"p↑_3"); + auto p4 = Index(L"p↓_4"); auto input = ex(L"t", IndexList{p1, p2}, IndexList{p3, p4}, IndexList{}); @@ -217,8 +205,7 @@ TEST_CASE("Spin", "[spin]") { auto result = remove_spin(input); for (auto& i : result->as().const_braket()) - REQUIRE(i.space() == - IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns)); + REQUIRE(i.space().base_key() == L"p"); input = ex(L"t", IndexList{p1, p3}, IndexList{p2, p4}, IndexList{}); REQUIRE(to_latex(swap_spin(input)) == L"{t^{{p↑_2}{p↑_4}}_{{p↓_1}{p↓_3}}}"); @@ -268,6 +255,8 @@ TEST_CASE("Spin", "[spin]") { SECTION("Constant") { auto exprPtr = ex(rational{1, 4}); auto result = spintrace(exprPtr); + REQUIRE(result->is()); + REQUIRE(result->is_atom()); REQUIRE(to_latex(result) == L"{{{\\frac{1}{4}}}}"); REQUIRE(to_latex(swap_spin(exprPtr)) == L"{{{\\frac{1}{4}}}}"); } @@ -410,6 +399,8 @@ TEST_CASE("Spin", "[spin]") { ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}, WstrList{}) * ex(L"t", WstrList{L"a_4"}, WstrList{L"i_2"}, WstrList{}); result = expand_A_op(input); + REQUIRE(result->is()); + REQUIRE(result->size() == 4); REQUIRE(to_latex(result) == L"{ " L"\\bigl({{{\\frac{1}{4}}}{\\bar{g}^{{a_3}{a_4}}_{{a_1}{a_2}}}{t^" @@ -602,6 +593,9 @@ SECTION("Expand Symmetrizer") { } SECTION("Symmetrize expression") { + auto ctx_resetter = set_scoped_default_context( + Context(sequant::mbpt::make_legacy_spaces(/* ignore_spin= */ true), + Vacuum::SingleProduct)); { // g * t1 + g * t1 auto input = @@ -631,8 +625,7 @@ SECTION("Symmetrize expression") { ex(L"t", WstrList{L"a_2"}, WstrList{L"i_3"}, WstrList{}) * ex(L"t", WstrList{L"a_1"}, WstrList{L"i_4"}, WstrList{}) * ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}, WstrList{}); - auto result = - factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); + auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}); REQUIRE(to_latex(result) == L"{{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{i_1}{a_3}}_{{i_3}{i_4}}}{t^{{" L"i_3}}_{{a_1}}}{t^{{i_4}}_{{a_2}}}{t^{{i_2}}_{{a_3}}}}"); @@ -750,12 +743,10 @@ SECTION("Closed-shell spintrace CCD") { Symmetry::nonsymm))); } { // CSV (aka PNO) - Index i1(L"i_1", IndexSpace::instance(IndexSpace::active_occupied)); - Index i2(L"i_2", IndexSpace::instance(IndexSpace::active_occupied)); - Index a1(L"a_1", IndexSpace::instance(IndexSpace::active_unoccupied), - {i1, i2}); - Index a2(L"a_2", IndexSpace::instance(IndexSpace::active_unoccupied), - {i1, i2}); + Index i1(L"i_1", {L"i", 0b01}); + Index i2(L"i_2", {L"i", 0b01}); + Index a1(L"a_1", {L"a", 0b10}, {i1, i2}); + Index a2(L"a_2", {L"a", 0b10}, {i1, i2}); const auto pno_ccd_energy_so = ex(rational(1, 4)) * ex(L"g", IndexList{a1, a2}, IndexList{i1, i2}, IndexList{}, @@ -768,18 +759,23 @@ SECTION("Closed-shell spintrace CCD") { ex(ExprPtrList{pno_ccd_energy_so}); auto pno_ccd_energy_sf = closed_shell_CC_spintrace(pno_ccd_energy_so_as_sum); - REQUIRE( - pno_ccd_energy_sf.to_latex() == - L"{ " - L"\\bigl({{{2}}{g^{{i_1}{i_2}}_{{a_1^{{i_1}{i_2}}}{a_2^{{i_1}{i_2}}}}" - L"}{t^{{a_1^{{i_1}{i_2}}}{a_2^{{i_1}{i_2}}}}_{{i_1}{i_2}}}} - " - L"{{g^{{i_1}{i_2}}_{{a_1^{{i_1}{i_2}}}{a_2^{{i_1}{i_2}}}}}{t^{{a_2^{{" - L"i_1}{i_2}}}{a_1^{{i_1}{i_2}}}}_{{i_1}{i_2}}}}\\bigr) }"); + REQUIRE(pno_ccd_energy_sf.to_latex() == + L"{ " + L"\\bigl({{{2}}{g^{{i_1}{i_2}}_{{a_1^{{i_1}{i_2}}}{a_2^{{i_1}{" + L"i_2}}}}" + L"}{t^{{a_1^{{i_1}{i_2}}}{a_2^{{i_1}{i_2}}}}_{{i_1}{i_2}}}} - " + L"{{g^{{i_1}{i_2}}_{{a_1^{{i_1}{i_2}}}{a_2^{{i_1}{i_2}}}}}{t^{{" + L"a_2^{{" + L"i_1}{i_2}}}{a_1^{{i_1}{i_2}}}}_{{i_1}{i_2}}}}\\bigr) }"); } } } SECTION("Closed-shell spintrace CCSD") { + auto ctx_resetter = set_scoped_default_context( + Context(sequant::mbpt::make_legacy_spaces(/* ignore_spin = */ true), + Vacuum::SingleProduct)); + // These terms from CCSD R1 equations { // A * f @@ -1395,29 +1391,25 @@ SECTION("Expand P operator pair-wise") { } SECTION("Open-shell spin-tracing") { - auto occA = - IndexSpace::instance(IndexSpace::active_occupied, IndexSpace::alpha); - auto virA = - IndexSpace::instance(IndexSpace::active_unoccupied, IndexSpace::alpha); - auto occB = - IndexSpace::instance(IndexSpace::active_occupied, IndexSpace::beta); - auto virB = - IndexSpace::instance(IndexSpace::active_unoccupied, IndexSpace::beta); - const auto i1A = Index(L"i↑_1", occA); - const auto i2A = Index(L"i↑_2", occA); - const auto i3A = Index(L"i↑_3", occA); - const auto i4A = Index(L"i↑_4", occA); - const auto i5A = Index(L"i↑_5", occA); - const auto i1B = Index(L"i↓_1", occB); - const auto i2B = Index(L"i↓_2", occB); - const auto i3B = Index(L"i↓_3", occB); - - const auto a1A = Index(L"a↑_1", virA); - const auto a2A = Index(L"a↑_2", virA); - const auto a3A = Index(L"a↑_3", virA); - const auto a1B = Index(L"a↓_1", virB); - const auto a2B = Index(L"a↓_2", virB); - const auto a3B = Index(L"a↓_3", virB); + // checks depend on the legacy subspaces + auto ctx_resetter = set_scoped_default_context( + Context(sequant::mbpt::make_legacy_spaces(), Vacuum::SingleProduct)); + + const auto i1A = Index(L"i↑_1"); + const auto i2A = Index(L"i↑_2"); + const auto i3A = Index(L"i↑_3"); + const auto i4A = Index(L"i↑_4"); + const auto i5A = Index(L"i↑_5"); + const auto i1B = Index(L"i↓_1"); + const auto i2B = Index(L"i↓_2"); + const auto i3B = Index(L"i↓_3"); + + const auto a1A = Index(L"a↑_1"); + const auto a2A = Index(L"a↑_2"); + const auto a3A = Index(L"a↑_3"); + const auto a1B = Index(L"a↓_1"); + const auto a2B = Index(L"a↓_2"); + const auto a3B = Index(L"a↓_3"); // Tensor canonicalize { @@ -1480,9 +1472,10 @@ SECTION("Open-shell spin-tracing") { auto result = open_shell_spintrace(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}); REQUIRE(result.size() == 3); - REQUIRE(to_latex(result[0]) == - L"{{{\\frac{1}{2}}}{\\bar{g}^{{i↑_1}{i↑_2}}_{{i↑_3}{a↑_1}}}{t^{{" - L"i↑_3}}_{{a↑_2}}}}"); + REQUIRE( + toUtf8(to_latex(result[0])) == + toUtf8(L"{{{\\frac{1}{2}}}{\\bar{g}^{{i↑_1}{i↑_2}}_{{i↑_3}{a↑_1}}}{t^{{" + L"i↑_3}}_{{a↑_2}}}}")); REQUIRE(to_latex(result[1]) == L"{{{-\\frac{1}{2}}}{g^{{i↑_1}{i↓_2}}_{{a↑_1}{i↓_1}}}{t^{{i↓_1}}_" L"{{a↓_2}}}}"); @@ -1572,7 +1565,6 @@ SECTION("Open-shell spin-tracing") { auto result = open_shell_spintrace( input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}, {L"i_3", L"a_3"}}); REQUIRE(result[0]->size() == 3); - auto A3_aaa = Tensor(L"A", {i1A, i2A, i3A}, {a1A, a2A, a3A}, {}, Symmetry::antisymm); auto result2 = ex(A3_aaa) * result[0]; diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index 143375af3..a9f526b77 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 57c6ae242..9732e235e 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -38,6 +37,13 @@ #include #include +#include +#include +#include +#include +#include + +#include #include std::string to_utf8(const std::wstring& wstr) { @@ -81,8 +87,11 @@ class TensorNetworkAccessor { TEST_CASE("TensorNetwork", "[elements]") { using namespace sequant; + using namespace sequant::mbpt::tensor; - using namespace sequant::mbpt::sr; + sequant::set_default_context(Context( + mbpt::make_sr_spaces(), Vacuum::SingleProduct, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital)); SECTION("Edges") { using Vertex = TensorNetwork::Vertex; @@ -314,10 +323,10 @@ TEST_CASE("TensorNetwork", "[elements]") { L"t{a_1,a_2,a_3;i_4,i_3,i_1}:N", L"S{i_1,i_2,i_3;a_1,a_2,a_3}:N * f{i_4;i_1}:N * " L"t{a_1,a_2,a_3;i_2,i_3,i_4}:N"}, - {L"Γ{u_2,u_4;u_1,u_3}:N * g{i_1,u_1;u_2,A_1}:N * " - L"t{u_3,A_1;u_4,i_1}:N", - L"Γ{u_2,u_4;u_1,u_3}:N * g{i_1,u_3;u_4,A_1}:N * " - L"t{u_1,A_1;u_2,i_1}:N"}}; + {L"Γ{o_2,o_4;o_1,o_3}:N * g{i_1,o_1;o_2,e_1}:N * " + L"t{o_3,e_1;o_4,i_1}:N", + L"Γ{o_2,o_4;o_1,o_3}:N * g{i_1,o_3;o_4,e_1}:N * " + L"t{o_1,e_1;o_2,i_1}:N"}}; for (const auto& pair : pairs) { const auto first = parse_expr(pair.first).as().factors(); const auto second = parse_expr(pair.second).as().factors(); diff --git a/tests/unit/test_utilities.cpp b/tests/unit/test_utilities.cpp index 4dbaf886e..837443e38 100644 --- a/tests/unit/test_utilities.cpp +++ b/tests/unit/test_utilities.cpp @@ -1,16 +1,36 @@ #include #include +#include #include #include #include #include +#include +#include +#include #include #include #include #include +namespace Catch { + +// Note: For some reason this template specialization is never used. It works +// for custom types but not for sequant::Index. +template <> +struct StringMaker { + static std::string convert(const sequant::Index &idx) { + using convert_type = std::codecvt_utf8; + std::wstring_convert converter; + + return converter.to_bytes(sequant::to_latex(idx)); + } +}; + +} // namespace Catch + sequant::Tensor parse_tensor(std::wstring_view str) { return sequant::parse_expr(str)->as(); } @@ -53,3 +73,90 @@ TEST_CASE("TEST GET_UNCONCTRACTED_INDICES", "[utilities]") { REQUIRE_THAT(aux, Catch::Matchers::UnorderedEquals(expectedAux)); } } + +TEST_CASE("get_unique_indices", "[utilities]") { + using namespace sequant; + using namespace Catch::Matchers; + + SECTION("Constant") { + auto const expression = parse_expr(L"5"); + + auto const indices = get_unique_indices(expression); + + REQUIRE(indices.bra.empty()); + REQUIRE(indices.ket.empty()); + REQUIRE(indices == get_unique_indices(expression->as())); + } + SECTION("Tensor") { + auto expression = parse_expr(L"t{i1;a1,a2;x1}"); + + auto indices = get_unique_indices(expression); + + REQUIRE_THAT(indices.bra, UnorderedEquals(std::vector{{L"i_1"}})); + REQUIRE_THAT(indices.ket, + UnorderedEquals(std::vector{{L"a_1", L"a_2"}})); + REQUIRE_THAT(indices.aux, UnorderedEquals(std::vector{{L"x_1"}})); + REQUIRE(indices == get_unique_indices(expression->as())); + + expression = parse_expr(L"t{i1,i2;a1,a2}"); + + indices = get_unique_indices(expression); + + REQUIRE_THAT(indices.bra, + UnorderedEquals(std::vector{{L"i_1"}, {L"i_2"}})); + REQUIRE_THAT(indices.ket, + UnorderedEquals(std::vector{{L"a_1", L"a_2"}})); + REQUIRE(indices.aux.size() == 0); + REQUIRE(indices == get_unique_indices(expression->as())); + + expression = parse_expr(L"t{i1,i2;a1,i1}"); + + indices = get_unique_indices(expression); + + REQUIRE_THAT(indices.bra, UnorderedEquals(std::vector{{L"i_2"}})); + REQUIRE_THAT(indices.ket, UnorderedEquals(std::vector{{L"a_1"}})); + REQUIRE(indices == get_unique_indices(expression->as())); + } + SECTION("Product") { + auto expression = parse_expr(L"t{i1;a1,a2} p{a2;i2;x1}"); + + auto indices = get_unique_indices(expression); + + REQUIRE_THAT(indices.bra, UnorderedEquals(std::vector{{L"i_1"}})); + REQUIRE_THAT(indices.ket, + UnorderedEquals(std::vector{{L"a_1", L"i_2"}})); + REQUIRE_THAT(indices.aux, UnorderedEquals(std::vector{{L"x_1"}})); + REQUIRE(indices == get_unique_indices(expression->as())); + + expression = parse_expr(L"1/8 g{a3,a4;i3,i4;x1} t{a1,a4;i1,i4;x1}"); + + indices = get_unique_indices(expression); + + REQUIRE_THAT(indices.bra, + UnorderedEquals(std::vector{{L"a_3"}, {L"a_1"}})); + REQUIRE_THAT(indices.ket, + UnorderedEquals(std::vector{{L"i_3", L"i_1"}})); + REQUIRE(indices.aux.size() == 0); + REQUIRE(indices == get_unique_indices(expression->as())); + } + SECTION("Sum") { + auto expression = parse_expr(L"t{i1;a2;x1} + g{i1;a2;x1}"); + + auto indices = get_unique_indices(expression); + + REQUIRE_THAT(indices.bra, UnorderedEquals(std::vector{{L"i_1"}})); + REQUIRE_THAT(indices.ket, UnorderedEquals(std::vector{{L"a_2"}})); + REQUIRE_THAT(indices.aux, UnorderedEquals(std::vector{{L"x_1"}})); + REQUIRE(indices == get_unique_indices(expression->as())); + + expression = parse_expr(L"t{i1;a2} t{i1;a1} + t{i1;a1} g{i1;a2}"); + + indices = get_unique_indices(expression); + + REQUIRE(indices.bra.empty()); + REQUIRE_THAT(indices.ket, + UnorderedEquals(std::vector{{L"a_1"}, {L"a_2"}})); + REQUIRE(indices.aux.empty()); + REQUIRE(indices == get_unique_indices(expression->as())); + } +} diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index 9bb8e07cb..8603ec81a 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "test_config.hpp" @@ -127,9 +128,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { REQUIRE_NOTHROW(BWickTheorem{BNOperatorSeq{}}); { - auto opseq1 = FNOperatorSeq({FNOperator({L"i_1"}, {L"i_2"}), - FNOperator({L"i_3"}, {L"i_4"}), - FNOperator({L"i_5"}, {L"i_6"})}); + auto opseq1 = ex(FNOperator({L"i_1"}, {L"i_2"}), + FNOperator({L"i_3"}, {L"i_4"}), + FNOperator({L"i_5"}, {L"i_6"})); REQUIRE_NOTHROW(FWickTheorem{opseq1}); auto wick1 = FWickTheorem{opseq1}; @@ -156,8 +157,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { SECTION("physical vacuum") { constexpr Vacuum V = Vacuum::Physical; auto raii_tmp = set_scoped_default_context( - Context{V, IndexSpaceMetric::Unit, BraKetSymmetry::conjugate, - SPBasis::spinorbital}); + Context{sequant::mbpt::make_sr_spaces(), V, IndexSpaceMetric::Unit, + BraKetSymmetry::conjugate, SPBasis::spinorbital}); auto switch_to_spinfree_context = detail::NoDiscard([&]() { auto context_sf = get_default_context(); @@ -168,8 +169,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // number operator { { - auto opseq1 = - FNOperatorSeq({FNOperator({L"i_1"}, {}), FNOperator({}, {L"i_2"})}); + auto opseq1 = ex(FNOperator({L"i_1"}, {}), + FNOperator({}, {L"i_2"})); auto wick1 = FWickTheorem{opseq1}; REQUIRE_NOTHROW(wick1.compute()); // full contractions = null (N is already in normal form) @@ -186,8 +187,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { REQUIRE(partial_contractions->as().size() == 1); } { - auto opseq1 = - BNOperatorSeq({BNOperator({L"i_1"}, {}), BNOperator({}, {L"i_2"})}); + auto opseq1 = ex(BNOperator({L"i_1"}, {}), + BNOperator({}, {L"i_2"})); auto wick1 = BWickTheorem{opseq1}; REQUIRE_NOTHROW(wick1.compute()); // full contractions = null @@ -208,8 +209,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // hole number operator { { - auto opseq1 = - FNOperatorSeq({FNOperator({}, {L"i_1"}), FNOperator({L"i_2"}, {})}); + auto opseq1 = ex(FNOperator({}, {L"i_1"}), + FNOperator({L"i_2"}, {})); auto wick1 = FWickTheorem{opseq1}; REQUIRE_NOTHROW(wick1.compute()); // full contractions = delta @@ -228,8 +229,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { L"{ \\bigl({{s^{{i_2}}_{{i_1}}}} - {{a^{{i_2}}_{{i_1}}}}\\bigr) }"); } { - auto opseq1 = - BNOperatorSeq({BNOperator({}, {L"i_1"}), BNOperator({L"i_2"}, {})}); + auto opseq1 = ex(BNOperator({}, {L"i_1"}), + BNOperator({L"i_2"}, {})); auto wick1 = BWickTheorem{opseq1}; REQUIRE_NOTHROW(wick1.compute()); // full contractions = delta @@ -251,9 +252,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // three 1-body operators { - auto opseq1 = FNOperatorSeq({FNOperator({L"i_1"}, {L"i_2"}), - FNOperator({L"i_3"}, {L"i_4"}), - FNOperator({L"i_5"}, {L"i_6"})}); + auto opseq1 = ex(FNOperator({L"i_1"}, {L"i_2"}), + FNOperator({L"i_3"}, {L"i_4"}), + FNOperator({L"i_5"}, {L"i_6"})); auto wick1 = FWickTheorem{opseq1}; REQUIRE_NOTHROW(wick1.compute()); auto result = FWickTheorem{opseq1}.compute(); @@ -263,8 +264,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two 2-body operators { - auto opseq = FNOperatorSeq( - {FNOperator({}, {L"i_1", L"i_2"}), FNOperator({L"i_3", L"i_4"}, {})}); + auto opseq = ex(FNOperator({}, {L"i_1", L"i_2"}), + FNOperator({L"i_3", L"i_4"}, {})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -274,8 +275,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two 3-body operators { - auto opseq = FNOperatorSeq({FNOperator({}, {L"i_1", L"i_2", L"i_3"}), - FNOperator({L"i_4", L"i_5", L"i_6"}, {})}); + auto opseq = ex(FNOperator({}, {L"i_1", L"i_2", L"i_3"}), + FNOperator({L"i_4", L"i_5", L"i_6"}, {})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -286,8 +287,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two 4-body operators { auto opseq = - FNOperatorSeq({FNOperator({}, {L"i_1", L"i_2", L"i_3", L"i_4"}), - FNOperator({L"i_5", L"i_6", L"i_7", L"i_8"}, {})}); + ex(FNOperator({}, {L"i_1", L"i_2", L"i_3", L"i_4"}), + FNOperator({L"i_5", L"i_6", L"i_7", L"i_8"}, {})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -297,9 +298,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 1/2 * 1 * 1/2 body ops, full contraction { - auto opseq = FNOperatorSeq({FNOperator({}, {L"i_1"}), - FNOperator({L"i_2"}, {L"i_3"}), - FNOperator({L"i_4"}, {})}); + auto opseq = ex(FNOperator({}, {L"i_1"}), + FNOperator({L"i_2"}, {L"i_3"}), + FNOperator({L"i_4"}, {})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -309,9 +310,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 1/2 * 1 * 1/2 body ops, partial contraction { - auto opseq = FNOperatorSeq({FNOperator({}, {L"i_1"}), - FNOperator({L"i_2"}, {L"i_3"}), - FNOperator({L"i_4"}, {})}); + auto opseq = ex(FNOperator({}, {L"i_1"}), + FNOperator({L"i_2"}, {L"i_3"}), + FNOperator({L"i_4"}, {})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.full_contractions(false).compute()); auto result = wick.full_contractions(false).compute(); @@ -327,9 +328,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // three 1-body operators, partial contraction { - auto opseq1 = FNOperatorSeq({FNOperator({L"i_1"}, {L"i_2"}), - FNOperator({L"i_3"}, {L"i_4"}), - FNOperator({L"i_5"}, {L"i_6"})}); + auto opseq1 = ex(FNOperator({L"i_1"}, {L"i_2"}), + FNOperator({L"i_3"}, {L"i_4"}), + FNOperator({L"i_5"}, {L"i_6"})); auto wick1 = FWickTheorem{opseq1}; REQUIRE_NOTHROW(wick1.full_contractions(false).compute()); auto result = FWickTheorem{opseq1}.full_contractions(false).compute(); @@ -346,8 +347,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two 2-body operators, partial contraction: Eq. 9b of DOI 10.1063/1.474405 { auto opseq = - FNOperatorSeq({FNOperator({L"i_1", L"i_2"}, {L"i_3", L"i_4"}), - FNOperator({L"i_5", L"i_6"}, {L"i_7", L"i_8"})}); + ex(FNOperator({L"i_1", L"i_2"}, {L"i_3", L"i_4"}), + FNOperator({L"i_5", L"i_6"}, {L"i_7", L"i_8"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.full_contractions(false).compute()); auto result = wick.full_contractions(false).compute(); @@ -396,8 +397,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two (pure qp) 1-body operators { - auto opseq = FNOperatorSeq( - {FNOperator({L"i_1"}, {L"a_1"}), FNOperator({L"a_2"}, {L"i_2"})}); + auto opseq = ex(FNOperator({L"i_1"}, {L"a_1"}), + FNOperator({L"a_2"}, {L"i_2"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -413,8 +414,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two (pure qp) N-nonconserving 2-body operators { - auto opseq = FNOperatorSeq({FNOperator({L"i_1", L"i_2"}, {L"a_1"}), - FNOperator({L"a_2"}, {L"i_3", L"i_4"})}); + auto opseq = ex(FNOperator({L"i_1", L"i_2"}, {L"a_1"}), + FNOperator({L"a_2"}, {L"i_3", L"i_4"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -424,8 +425,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two general 1-body operators { - auto opseq = FNOperatorSeq( - {FNOperator({L"p_1"}, {L"p_2"}), FNOperator({L"p_3"}, {L"p_4"})}); + auto opseq = ex(FNOperator({L"p_1"}, {L"p_2"}), + FNOperator({L"p_3"}, {L"p_4"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -434,14 +435,14 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { 2 * 2); // product of 4 terms (since each contraction of 2 // *general* indices produces 2 overlaps) REQUIRE(to_latex(result) == - L"{{s^{{p_1}}_{{m_{102}}}}{s^{{m_{102}}}_{{p_4}}}{s^{{E_{103}}}_{" - L"{p_2}}}{s^{{p_3}}_{{E_{103}}}}}"); + L"{{s^{{p_1}}_{{m_{102}}}}{s^{{m_{102}}}_{{p_4}}}{s^{{e_{103}}}_{" + L"{p_2}}}{s^{{p_3}}_{{e_{103}}}}}"); } // two general 1-body operators, partial contractions: Eq. 21a of // DOI 10.1063/1.474405 { - auto opseq = FNOperatorSeq( - {FNOperator({L"p_1"}, {L"p_2"}), FNOperator({L"p_3"}, {L"p_4"})}); + auto opseq = ex(FNOperator({L"p_1"}, {L"p_2"}), + FNOperator({L"p_3"}, {L"p_4"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.full_contractions(false).compute()); auto result = wick.full_contractions(false).compute(); @@ -452,17 +453,17 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { L"{ \\bigl( - " L"{{s^{{p_1}}_{{m_{107}}}}{s^{{m_{107}}}_{{p_4}}}{\\tilde{a}^{{p_3}}_" L"{{p_2}}}} + " - L"{{s^{{p_1}}_{{m_{107}}}}{s^{{m_{107}}}_{{p_4}}}{s^{{E_{108}}}_{{p_" - L"2}}}{s^{{p_3}}_{{E_{108}}}}} + " - L"{{s^{{E_{109}}}_{{p_2}}}{s^{{p_3}}_{{E_{109}}}}{\\tilde{a}^{{p_1}}_" + L"{{s^{{p_1}}_{{m_{107}}}}{s^{{m_{107}}}_{{p_4}}}{s^{{e_{108}}}_{{p_" + L"2}}}{s^{{p_3}}_{{e_{108}}}}} + " + L"{{s^{{e_{109}}}_{{p_2}}}{s^{{p_3}}_{{e_{109}}}}{\\tilde{a}^{{p_1}}_" L"{{p_4}}}} + {{\\tilde{a}^{{p_1}{p_3}}_{{p_2}{p_4}}}}\\bigr) }"); } // two (pure qp) 2-body operators { auto opseq = - FNOperatorSeq({FNOperator({L"i_1", L"i_2"}, {L"a_1", L"a_2"}), - FNOperator({L"a_3", L"a_4"}, {L"i_3", L"i_4"})}); + ex(FNOperator({L"i_1", L"i_2"}, {L"a_1", L"a_2"}), + FNOperator({L"a_3", L"a_4"}, {L"i_3", L"i_4"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -507,9 +508,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { } // two (pure qp) 3-body operators { - auto opseq = FNOperatorSeq( - {FNOperator({L"i_1", L"i_2", L"i_3"}, {L"a_1", L"a_2", L"a_3"}), - FNOperator({L"a_4", L"a_5", L"a_6"}, {L"i_4", L"i_5", L"i_6"})}); + auto opseq = ex( + FNOperator({L"i_1", L"i_2", L"i_3"}, {L"a_1", L"a_2", L"a_3"}), + FNOperator({L"a_4", L"a_5", L"a_6"}, {L"i_4", L"i_5", L"i_6"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -521,8 +522,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // contraction: Eq. 9 of DOI 10.1063/1.474405 { auto opseq = - FNOperatorSeq({FNOperator({L"p_1"}, {L"p_2"}), - FNOperator({L"p_3", L"p_4"}, {L"p_5", L"p_6"})}); + ex(FNOperator({L"p_1"}, {L"p_2"}), + FNOperator({L"p_3", L"p_4"}, {L"p_5", L"p_6"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.full_contractions(false).compute()); auto result = wick.full_contractions(false).compute(); @@ -533,8 +534,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two general 2-body operators { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), - FNOperator({L"p_5", L"p_6"}, {L"p_7", L"p_8"})}); + ex(FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), + FNOperator({L"p_5", L"p_6"}, {L"p_7", L"p_8"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -545,8 +546,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // DOI 10.1063/1.474405 { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), - FNOperator({L"p_5", L"p_6"}, {L"p_7", L"p_8"})}); + ex(FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), + FNOperator({L"p_5", L"p_6"}, {L"p_7", L"p_8"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.full_contractions(false).compute()); auto result = wick.full_contractions(false).compute(); @@ -557,8 +558,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // one general 2-body operator and one 2-body excitation operator { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), - FNOperator({L"a_3", L"a_4"}, {L"i_3", L"i_4"})}); + ex(FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), + FNOperator({L"a_3", L"a_4"}, {L"i_3", L"i_4"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -603,9 +604,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two general 3-body operators { - auto opseq = FNOperatorSeq( - {FNOperator({L"p_1", L"p_2", L"p_3"}, {L"p_4", L"p_5", L"p_6"}), - FNOperator({L"p_7", L"p_8", L"p_9"}, {L"p_10", L"p_11", L"p_12"})}); + auto opseq = ex( + FNOperator({L"p_1", L"p_2", L"p_3"}, {L"p_4", L"p_5", L"p_6"}), + FNOperator({L"p_7", L"p_8", L"p_9"}, {L"p_10", L"p_11", L"p_12"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -615,9 +616,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // two N-nonconserving operators { - auto opseq = FNOperatorSeq( - {FNOperator({L"p_1", L"p_2", L"p_3"}, {L"p_4", L"p_5"}), - FNOperator({L"p_7", L"p_8"}, {L"p_10", L"p_11", L"p_12"})}); + auto opseq = ex( + FNOperator({L"p_1", L"p_2", L"p_3"}, {L"p_4", L"p_5"}), + FNOperator({L"p_7", L"p_8"}, {L"p_10", L"p_11", L"p_12"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -646,9 +647,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // odd number of ops -> full contraction is 0 { - auto opseq = FNOperatorSeq( - {FNOperator({L"p_1", L"p_2"}, {L"p_4", L"p_5"}), - FNOperator({L"p_7", L"p_8"}, {L"p_10", L"p_11", L"p_12"})}); + auto opseq = ex( + FNOperator({L"p_1", L"p_2"}, {L"p_4", L"p_5"}), + FNOperator({L"p_7", L"p_8"}, {L"p_10", L"p_11", L"p_12"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -660,11 +661,11 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { SEQUANT_PROFILE_SINGLE( "wick(4^4)", { - auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, - {L"p_5", L"p_6", L"p_7", L"p_8"}), - FNOperator({L"p_21", L"p_22", L"p_23", L"p_24"}, - {L"p_25", L"p_26", L"p_27", L"p_28"})}); + auto opseq = ex( + FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, + {L"p_5", L"p_6", L"p_7", L"p_8"}), + FNOperator({L"p_21", L"p_22", L"p_23", L"p_24"}, + {L"p_25", L"p_26", L"p_27", L"p_28"})); auto wick = FWickTheorem{opseq}; auto result = wick.compute(true); REQUIRE(result->is()); @@ -673,9 +674,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // three general 1-body operators { - auto opseq = FNOperatorSeq({FNOperator({L"p_1"}, {L"p_2"}), - FNOperator({L"p_3"}, {L"p_4"}), - FNOperator({L"p_5"}, {L"p_6"})}); + auto opseq = ex(FNOperator({L"p_1"}, {L"p_2"}), + FNOperator({L"p_3"}, {L"p_4"}), + FNOperator({L"p_5"}, {L"p_6"})); auto wick = FWickTheorem{opseq}; REQUIRE_NOTHROW(wick.compute()); auto result = wick.compute(); @@ -685,9 +686,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 4 general 1-body operators { - auto opseq = FNOperatorSeq( - {FNOperator({L"p_1"}, {L"p_2"}), FNOperator({L"p_3"}, {L"p_4"}), - FNOperator({L"p_5"}, {L"p_6"}), FNOperator({L"p_7"}, {L"p_8"})}); + auto opseq = ex( + FNOperator({L"p_1"}, {L"p_2"}), FNOperator({L"p_3"}, {L"p_4"}), + FNOperator({L"p_5"}, {L"p_6"}), FNOperator({L"p_7"}, {L"p_8"})); auto ext_indices = make_indices>(WstrList{ L"p_1", L"p_2", L"p_3", L"p_4", L"p_5", L"p_6", L"p_7", L"p_8"}); auto wick1 = FWickTheorem{opseq}; @@ -705,10 +706,10 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 4-body ^ 2-body ^ 2-body { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, - {L"p_5", L"p_6", L"p_7", L"p_8"}), - FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), - FNOperator({L"p_13", L"p_14"}, {L"p_15", L"p_16"})}); + ex(FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, + {L"p_5", L"p_6", L"p_7", L"p_8"}), + FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), + FNOperator({L"p_13", L"p_14"}, {L"p_15", L"p_16"})); auto wick = FWickTheorem{opseq}; auto result = wick.compute(); REQUIRE(result->is()); @@ -718,9 +719,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 2-body ^ 2-body ^ 2-body { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2"}, {L"p_5", L"p_6"}), - FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), - FNOperator({L"p_17", L"p_18"}, {L"p_19", L"p_20"})}); + ex(FNOperator({L"p_1", L"p_2"}, {L"p_5", L"p_6"}), + FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), + FNOperator({L"p_17", L"p_18"}, {L"p_19", L"p_20"})); auto wick = FWickTheorem{opseq}; auto result = wick.compute(); REQUIRE(result->is()); @@ -730,10 +731,10 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 2-body ^ 2-body ^ 2-body ^ 2-body SEQUANT_PROFILE_SINGLE("wick(2^2^2^2)", { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2"}, {L"p_5", L"p_6"}), - FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), - FNOperator({L"p_13", L"p_14"}, {L"p_15", L"p_16"}), - FNOperator({L"p_17", L"p_18"}, {L"p_19", L"p_20"})}); + ex(FNOperator({L"p_1", L"p_2"}, {L"p_5", L"p_6"}), + FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), + FNOperator({L"p_13", L"p_14"}, {L"p_15", L"p_16"}), + FNOperator({L"p_17", L"p_18"}, {L"p_19", L"p_20"})); auto wick = FWickTheorem{opseq}; auto result = wick.compute(true); REQUIRE(result->is()); @@ -744,11 +745,11 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 4-body ^ 2-body ^ 2-body ^ 2-body SEQUANT_PROFILE_SINGLE("wick(4^2^2^2)", { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, - {L"p_5", L"p_6", L"p_7", L"p_8"}), - FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), - FNOperator({L"p_13", L"p_14"}, {L"p_15", L"p_16"}), - FNOperator({L"p_17", L"p_18"}, {L"p_19", L"p_20"})}); + ex(FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, + {L"p_5", L"p_6", L"p_7", L"p_8"}), + FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), + FNOperator({L"p_13", L"p_14"}, {L"p_15", L"p_16"}), + FNOperator({L"p_17", L"p_18"}, {L"p_19", L"p_20"})); auto wick = FWickTheorem{opseq}; auto result = wick.use_topology(true).compute(true); REQUIRE(result->is()); @@ -757,12 +758,12 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 3-body ^ 2-body ^ 2-body ^ 3-body SEQUANT_PROFILE_SINGLE("wick(3^2^2^3)", { - auto opseq = FNOperatorSeq( - {FNOperator({L"p_1", L"p_2", L"p_3"}, {L"p_5", L"p_6", L"p_7"}), - FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), - FNOperator({L"p_13", L"p_14"}, {L"p_15", L"p_16"}), - FNOperator({L"p_17", L"p_18", L"p_19"}, {L"p_20", L"p_21", L"p_22"}, - V)}); + auto opseq = ex( + FNOperator({L"p_1", L"p_2", L"p_3"}, {L"p_5", L"p_6", L"p_7"}), + FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), + FNOperator({L"p_13", L"p_14"}, {L"p_15", L"p_16"}), + FNOperator({L"p_17", L"p_18", L"p_19"}, {L"p_20", L"p_21", L"p_22"}, + V)); auto wick = FWickTheorem{opseq}; auto result = wick.use_topology(true).compute(true); REQUIRE(result->is()); @@ -772,11 +773,11 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 4-body ^ 2-body ^ 4-body SEQUANT_PROFILE_SINGLE("wick(4^2^4)", { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, - {L"p_5", L"p_6", L"p_7", L"p_8"}), - FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), - FNOperator({L"p_21", L"p_22", L"p_23", L"p_24"}, - {L"p_25", L"p_26", L"p_27", L"p_28"})}); + ex(FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, + {L"p_5", L"p_6", L"p_7", L"p_8"}), + FNOperator({L"p_9", L"p_10"}, {L"p_11", L"p_12"}), + FNOperator({L"p_21", L"p_22", L"p_23", L"p_24"}, + {L"p_25", L"p_26", L"p_27", L"p_28"})); auto wick = FWickTheorem{opseq}; auto result = wick.use_topology(true).compute(true); REQUIRE(result->is()); @@ -786,12 +787,12 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 4-body ^ 4-body ^ 4-body SEQUANT_PROFILE_SINGLE("wick(4^4^4)", { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, - {L"p_5", L"p_6", L"p_7", L"p_8"}), - FNOperator({L"p_11", L"p_12", L"p_13", L"p_14"}, - {L"p_15", L"p_16", L"p_17", L"p_18"}), - FNOperator({L"p_21", L"p_22", L"p_23", L"p_24"}, - {L"p_25", L"p_26", L"p_27", L"p_28"})}); + ex(FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, + {L"p_5", L"p_6", L"p_7", L"p_8"}), + FNOperator({L"p_11", L"p_12", L"p_13", L"p_14"}, + {L"p_15", L"p_16", L"p_17", L"p_18"}), + FNOperator({L"p_21", L"p_22", L"p_23", L"p_24"}, + {L"p_25", L"p_26", L"p_27", L"p_28"})); auto wick = FWickTheorem{opseq}; auto result = wick.use_topology(true).compute(true); REQUIRE(result->is()); @@ -803,13 +804,13 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // impossible: 4-body ^ 4-body ^ 4-body ^ 4-body ^ 4-body ^ 4-body { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, {L"p_5", L"p_6", L"p_7", L"p_8"}), + ex(FNOperator({L"p_1", L"p_2", L"p_3", L"p_4"}, {L"p_5", L"p_6", L"p_7", L"p_8"}), FNOperator({L"p_11", L"p_12", L"p_13", L"p_14"}, {L"p_15", L"p_16", L"p_17", L"p_18"}), FNOperator({L"p_21", L"p_22", L"p_23", L"p_24"}, {L"p_25", L"p_26", L"p_27", L"p_28"}), FNOperator({L"p_31", L"p_32", L"p_33", L"p_34"}, {L"p_35", L"p_36", L"p_37", L"p_38"}), FNOperator({L"p_41", L"p_42", L"p_43", L"p_44"}, {L"p_45", L"p_46", L"p_47", L"p_48"}), FNOperator({L"p_51", L"p_52", L"p_53", L"p_54"}, {L"p_55", L"p_56", L"p_57", L"p_58"}) - }); + ); auto wick = FWickTheorem{opseq}; auto result = wick.use_topology(true).compute(true); } @@ -829,8 +830,8 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 2-body ^ 2-body SEQUANT_PROFILE_SINGLE("wick(H2*T2)", { auto opseq = - FNOperatorSeq({FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), - FNOperator({L"a_4", L"a_5"}, {L"i_4", L"i_5"})}); + ex(FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), + FNOperator({L"a_4", L"a_5"}, {L"i_4", L"i_5"})); auto wick = FWickTheorem{opseq}; auto wick_result = wick.compute(); REQUIRE(wick_result->is()); @@ -870,7 +871,7 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { rapid_simplify(wick_result_2); std::wcout << L"H2*T2 = " << to_latex(wick_result_2) << std::endl; - std::wcout << L"H2*T2 = " << to_wolfram(wick_result_2) << std::endl; + // std::wcout << L"H2*T2 = " << to_wolfram(wick_result_2) << std::endl; REQUIRE(to_latex(wick_result_2) == L"{{{4}}" L"{\\bar{g}^{{a_1}{a_2}}_{{i_1}{i_2}}}{\\bar{t}^{{i_1}{i_2}}_{{a_" @@ -922,9 +923,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { std::wostringstream oss; oss << "use_op_partitions=" << use_op_partitions << "}: H2*T1*T1 = "; - auto opseq = FNOperatorSeq( - {FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), - FNOperator({L"a_4"}, {L"i_4"}), FNOperator({L"a_5"}, {L"i_5"})}); + auto opseq = ex( + FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), + FNOperator({L"a_4"}, {L"i_4"}), FNOperator({L"a_5"}, {L"i_5"})); auto wick = FWickTheorem{opseq}; wick.use_topology(use_nop_partitions || use_op_partitions); // if (use_nop_partitions) wick.set_nop_partitions({{1, 2}}); @@ -972,14 +973,15 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // 2=body ^ 1-body ^ 2-body with dependent (PNO) indices SEQUANT_PROFILE_SINGLE("wick(P2*H1*T2)", { - auto opseq = FNOperatorSeq({FNOperator(IndexList{L"i_1", L"i_2"}, - {Index(L"a_1", {L"i_1", L"i_2"}), - Index(L"a_2", {L"i_1", L"i_2"})}, - V), - FNOperator({L"p_1"}, {L"p_2"}), - FNOperator({Index(L"a_3", {L"i_3", L"i_4"}), - Index(L"a_4", {L"i_3", L"i_4"})}, - IndexList{L"i_3", L"i_4"})}); + auto opseq = + ex(FNOperator(IndexList{L"i_1", L"i_2"}, + {Index(L"a_1", {L"i_1", L"i_2"}), + Index(L"a_2", {L"i_1", L"i_2"})}, + V), + FNOperator({L"p_1"}, {L"p_2"}), + FNOperator({Index(L"a_3", {L"i_3", L"i_4"}), + Index(L"a_4", {L"i_3", L"i_4"})}, + IndexList{L"i_3", L"i_4"})); auto wick = FWickTheorem{opseq}; auto wick_result = wick.compute(); REQUIRE(wick_result->is()); @@ -1031,17 +1033,17 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { << use_op_partitions << "}: P2*H2*T2*T2(PNO) = "; auto opseq = - FNOperatorSeq({FNOperator(IndexList{L"i_1", L"i_2"}, - {Index(L"a_1", {L"i_1", L"i_2"}), - Index(L"a_2", {L"i_1", L"i_2"})}, - V), - FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), - FNOperator({Index(L"a_3", {L"i_3", L"i_4"}), - Index(L"a_4", {L"i_3", L"i_4"})}, - IndexList{L"i_3", L"i_4"}), - FNOperator({Index(L"a_5", {L"i_5", L"i_6"}), - Index(L"a_6", {L"i_5", L"i_6"})}, - IndexList{L"i_5", L"i_6"})}); + ex(FNOperator(IndexList{L"i_1", L"i_2"}, + {Index(L"a_1", {L"i_1", L"i_2"}), + Index(L"a_2", {L"i_1", L"i_2"})}, + V), + FNOperator({L"p_1", L"p_2"}, {L"p_3", L"p_4"}), + FNOperator({Index(L"a_3", {L"i_3", L"i_4"}), + Index(L"a_4", {L"i_3", L"i_4"})}, + IndexList{L"i_3", L"i_4"}), + FNOperator({Index(L"a_5", {L"i_5", L"i_6"}), + Index(L"a_6", {L"i_5", L"i_6"})}, + IndexList{L"i_5", L"i_6"})); auto wick = FWickTheorem{opseq}; wick.set_nop_connections({{1, 2}, {1, 3}}).use_topology(true);