From 14accf25310e1f1c782df4eae8da4be25fd6b189 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 8 Sep 2023 16:51:01 +0200 Subject: [PATCH 01/85] Separate Tensor and TensorCanonicalizer in different headers --- CMakeLists.txt | 3 +- SeQuant/core/abstract_tensor.hpp | 196 ---------------- SeQuant/core/expr.cpp | 1 + SeQuant/core/tensor.cpp | 1 + ...ct_tensor.cpp => tensor_canonicalizer.cpp} | 1 + SeQuant/core/tensor_canonicalizer.hpp | 215 ++++++++++++++++++ SeQuant/core/tensor_network.cpp | 1 + SeQuant/core/wick.impl.hpp | 1 + SeQuant/domain/mbpt/convention.cpp | 1 + SeQuant/domain/mbpt/mr.cpp | 1 + SeQuant/domain/mbpt/op.ipp | 2 + examples/eval/btas/main.cpp | 1 + examples/eval/ta/main.cpp | 1 + examples/osstcc/osstcc.cpp | 1 + examples/stcc/stcc.cpp | 1 + examples/stcc_rigorous/stcc_rigorous.cpp | 1 + python/src/sequant/mbpt.h | 1 + tests/unit/test_canonicalize.cpp | 1 + tests/unit/test_eval_expr.cpp | 1 + tests/unit/test_mbpt.cpp | 1 + tests/unit/test_spin.cpp | 1 + tests/unit/test_tensor_network.cpp | 1 + 22 files changed, 237 insertions(+), 197 deletions(-) rename SeQuant/core/{abstract_tensor.cpp => tensor_canonicalizer.cpp} (98%) create mode 100644 SeQuant/core/tensor_canonicalizer.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d9cbb0d9..c8c071e49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,7 +171,6 @@ add_library(SeQuant-bliss set(SeQuant_src ${PROJECT_BINARY_DIR}/SeQuant/version.hpp SeQuant/version.cpp - SeQuant/core/abstract_tensor.cpp SeQuant/core/abstract_tensor.hpp SeQuant/core/algorithm.hpp SeQuant/core/any.hpp @@ -220,6 +219,8 @@ set(SeQuant_src SeQuant/core/tag.hpp SeQuant/core/tensor.cpp SeQuant/core/tensor.hpp + SeQuant/core/tensor_canonicalizer.cpp + SeQuant/core/tensor_canonicalizer.hpp SeQuant/core/tensor_network.cpp SeQuant/core/tensor_network.hpp SeQuant/core/timer.hpp diff --git a/SeQuant/core/abstract_tensor.hpp b/SeQuant/core/abstract_tensor.hpp index b0f0cfea6..f45149685 100644 --- a/SeQuant/core/abstract_tensor.hpp +++ b/SeQuant/core/abstract_tensor.hpp @@ -201,202 +201,6 @@ inline void reset_tags(AbstractTensor& t) { t._reset_tags(); } using AbstractTensorPtr = std::shared_ptr; -/// @brief Base class for Tensor canonicalizers -/// To make custom canonicalizer make a derived class and register an instance -/// of that class with TensorCanonicalizer::register_instance -class TensorCanonicalizer { - public: - virtual ~TensorCanonicalizer(); - - /// @return ptr to the TensorCanonicalizer object, if any, that had been - /// previously registered via TensorCanonicalizer::register_instance() - /// with @c label , or to the default canonicalizer, if any - static std::shared_ptr instance_ptr( - std::wstring_view label = L""); - - /// @return ptr to the TensorCanonicalizer object, if any, that had been - /// previously registered via TensorCanonicalizer::register_instance() - /// with @c label - /// @sa instance_ptr - static std::shared_ptr nondefault_instance_ptr( - std::wstring_view label); - - /// @return a TensorCanonicalizer previously registered via - /// TensorCanonicalizer::register_instance() with @c label or to the default - /// canonicalizer - /// @throw std::runtime_error if no canonicalizer has been registered - static std::shared_ptr instance( - std::wstring_view label = L""); - - /// registers @c canonicalizer to be applied to Tensor objects with label - /// @c label ; leave the label empty if @c canonicalizer is to apply to Tensor - /// objects with any label - /// @note if a canonicalizer registered with label @c label exists, it is - /// replaced - static void register_instance( - std::shared_ptr canonicalizer, - std::wstring_view label = L""); - - /// tries to register @c canonicalizer to be applied to Tensor objects - /// with label @c label ; leave the label empty if @c canonicalizer is to - /// apply to Tensor objects with any label - /// @return false if there is already a canonicalizer registered with @c label - /// @sa regiter_instance - static bool try_register_instance( - std::shared_ptr canonicalizer, - std::wstring_view label = L""); - - /// deregisters canonicalizer (if any) registered previously - /// to be applied to tensors with label @c label - static void deregister_instance(std::wstring_view label = L""); - - /// @return a list of Tensor labels with lexicographic preference (in order) - static const auto& cardinal_tensor_labels() { - return cardinal_tensor_labels_accessor(); - } - - /// @param cardinal_tensor_labels a list of Tensor labels with lexicographic - /// preference (in order) - static void set_cardinal_tensor_labels( - const container::vector& labels) { - cardinal_tensor_labels_accessor() = labels; - } - - /// @return a side effect of canonicalization (e.g. phase), or nullptr if none - /// @internal what should be returned if canonicalization requires - /// complex conjugation? Special ExprPtr type (e.g. ConjOp)? Or the actual - /// return of the canonicalization? - /// @note canonicalization compared indices returned by index_comparer - // TODO generalize for complex tensors - virtual ExprPtr apply(AbstractTensor&) = 0; - - /// @return reference to the object used to compare Index objects - /// @note the default is to use an object of type `std::less` - static const std::function& - index_comparer(); - - /// @param comparer the compare object to be used by this - static void index_comparer( - std::function comparer); - - protected: - inline auto bra_range(AbstractTensor& t) { return t._bra_mutable(); } - inline auto ket_range(AbstractTensor& t) { return t._ket_mutable(); } - - /// the object used to compare indices - static std::function index_comparer_; - - private: - static std::pair< - container::map>*, - std::unique_lock> - instance_map_accessor(); // map* + locked recursive mutex - static container::vector& cardinal_tensor_labels_accessor(); -}; - -/// @brief null Tensor canonicalizer does nothing -class NullTensorCanonicalizer : public TensorCanonicalizer { - public: - virtual ~NullTensorCanonicalizer() = default; - - ExprPtr apply(AbstractTensor&) override; -}; - -class DefaultTensorCanonicalizer : public TensorCanonicalizer { - public: - DefaultTensorCanonicalizer() = default; - - /// @tparam IndexContainer a Container of Index objects such that @c - /// IndexContainer::value_type is convertible to Index (e.g. this can be - /// std::vector or std::set , but not std::map) - /// @param external_indices container of external Index objects - /// @warning @c external_indices is assumed to be immutable during the - /// lifetime of this object - template - DefaultTensorCanonicalizer(IndexContainer&& external_indices) { - ranges::for_each(external_indices, [this](const Index& idx) { - this->external_indices_.emplace(idx.label(), idx); - }); - } - virtual ~DefaultTensorCanonicalizer() = default; - - /// Implements TensorCanonicalizer::apply - /// @note Canonicalizes @c t by sorting its bra (if @c - /// t.symmetry()==Symmetry::nonsymm ) or its bra and ket (if @c - /// t.symmetry()!=Symmetry::nonsymm ), - /// with the external indices appearing "before" (smaller particle - /// indices) than the internal indices - ExprPtr apply(AbstractTensor& t) override; - - /// Core of DefaultTensorCanonicalizer::apply, only does the canonicalization, - /// i.e. no tagging/untagging - template - ExprPtr apply(AbstractTensor& t, const Compare& comp) { - // std::wcout << "abstract tensor: " << to_latex(t) << "\n"; - auto s = symmetry(t); - auto is_antisymm = (s == Symmetry::antisymm); - const auto _bra_rank = bra_rank(t); - const auto _ket_rank = ket_rank(t); - const auto _rank = std::min(_bra_rank, _ket_rank); - - // nothing to do for rank-1 tensors - if (_bra_rank == 1 && _ket_rank == 1) return nullptr; - - using ranges::begin; - using ranges::end; - using ranges::views::counted; - using ranges::views::take; - using ranges::views::zip; - - bool even = true; - switch (s) { - case Symmetry::antisymm: - case Symmetry::symm: { - auto _bra = bra_range(t); - auto _ket = ket_range(t); - // std::wcout << "canonicalizing " << to_latex(t); - IndexSwapper::thread_instance().reset(); - // std::{stable_}sort does not necessarily use swap! so must implement - // sort outselves .. thankfully ranks will be low so can stick with - // bubble - bubble_sort(begin(_bra), end(_bra), comp); - bubble_sort(begin(_ket), end(_ket), comp); - if (is_antisymm) - even = IndexSwapper::thread_instance().even_num_of_swaps(); - // std::wcout << " is " << (even ? "even" : "odd") << " and - // produces " << to_latex(t) << std::endl; - } break; - - case Symmetry::nonsymm: { - // sort particles with bra and ket functions first, - // then the particles with either bra or ket index - auto _bra = bra_range(t); - auto _ket = ket_range(t); - auto _zip_braket = zip(take(_bra, _rank), take(_ket, _rank)); - bubble_sort(begin(_zip_braket), end(_zip_braket), comp); - if (_bra_rank > _rank) { - auto size_of_rest = _bra_rank - _rank; - auto rest_of = counted(begin(_bra) + _rank, size_of_rest); - bubble_sort(begin(rest_of), end(rest_of), comp); - } else if (_ket_rank > _rank) { - auto size_of_rest = _ket_rank - _rank; - auto rest_of = counted(begin(_ket) + _rank, size_of_rest); - bubble_sort(begin(rest_of), end(rest_of), comp); - } - } break; - - default: - abort(); - } - - ExprPtr result = - is_antisymm ? (even == false ? ex(-1) : nullptr) : nullptr; - return result; - } - - private: - container::map external_indices_; -}; } // namespace sequant diff --git a/SeQuant/core/expr.cpp b/SeQuant/core/expr.cpp index 835478f5e..b78f46733 100644 --- a/SeQuant/core/expr.cpp +++ b/SeQuant/core/expr.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include diff --git a/SeQuant/core/tensor.cpp b/SeQuant/core/tensor.cpp index 45c710c0e..55ef88004 100644 --- a/SeQuant/core/tensor.cpp +++ b/SeQuant/core/tensor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace sequant { diff --git a/SeQuant/core/abstract_tensor.cpp b/SeQuant/core/tensor_canonicalizer.cpp similarity index 98% rename from SeQuant/core/abstract_tensor.cpp rename to SeQuant/core/tensor_canonicalizer.cpp index 0fa484f85..bf98d1462 100644 --- a/SeQuant/core/abstract_tensor.cpp +++ b/SeQuant/core/tensor_canonicalizer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp new file mode 100644 index 000000000..a633dc9f0 --- /dev/null +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -0,0 +1,215 @@ +// +// Created by Robert Adam on 2023-09-08 +// + +#ifndef SEQUANT_CORE_TENSOR_CANONICALIZER_HPP +#define SEQUANT_CORE_TENSOR_CANONICALIZER_HPP + +#include "abstract_tensor.hpp" +#include "expr.hpp" + +#include +#include + +namespace sequant { + +/// @brief Base class for Tensor canonicalizers +/// To make custom canonicalizer make a derived class and register an instance +/// of that class with TensorCanonicalizer::register_instance +class TensorCanonicalizer { + public: + virtual ~TensorCanonicalizer(); + + /// @return ptr to the TensorCanonicalizer object, if any, that had been + /// previously registered via TensorCanonicalizer::register_instance() + /// with @c label , or to the default canonicalizer, if any + static std::shared_ptr instance_ptr( + std::wstring_view label = L""); + + /// @return ptr to the TensorCanonicalizer object, if any, that had been + /// previously registered via TensorCanonicalizer::register_instance() + /// with @c label + /// @sa instance_ptr + static std::shared_ptr nondefault_instance_ptr( + std::wstring_view label); + + /// @return a TensorCanonicalizer previously registered via + /// TensorCanonicalizer::register_instance() with @c label or to the default + /// canonicalizer + /// @throw std::runtime_error if no canonicalizer has been registered + static std::shared_ptr instance( + std::wstring_view label = L""); + + /// registers @c canonicalizer to be applied to Tensor objects with label + /// @c label ; leave the label empty if @c canonicalizer is to apply to Tensor + /// objects with any label + /// @note if a canonicalizer registered with label @c label exists, it is + /// replaced + static void register_instance( + std::shared_ptr canonicalizer, + std::wstring_view label = L""); + + /// tries to register @c canonicalizer to be applied to Tensor objects + /// with label @c label ; leave the label empty if @c canonicalizer is to + /// apply to Tensor objects with any label + /// @return false if there is already a canonicalizer registered with @c label + /// @sa regiter_instance + static bool try_register_instance( + std::shared_ptr canonicalizer, + std::wstring_view label = L""); + + /// deregisters canonicalizer (if any) registered previously + /// to be applied to tensors with label @c label + static void deregister_instance(std::wstring_view label = L""); + + /// @return a list of Tensor labels with lexicographic preference (in order) + static const auto& cardinal_tensor_labels() { + return cardinal_tensor_labels_accessor(); + } + + /// @param cardinal_tensor_labels a list of Tensor labels with lexicographic + /// preference (in order) + static void set_cardinal_tensor_labels( + const container::vector& labels) { + cardinal_tensor_labels_accessor() = labels; + } + + /// @return a side effect of canonicalization (e.g. phase), or nullptr if none + /// @internal what should be returned if canonicalization requires + /// complex conjugation? Special ExprPtr type (e.g. ConjOp)? Or the actual + /// return of the canonicalization? + /// @note canonicalization compared indices returned by index_comparer + // TODO generalize for complex tensors + virtual ExprPtr apply(AbstractTensor&) = 0; + + /// @return reference to the object used to compare Index objects + /// @note the default is to use an object of type `std::less` + static const std::function& + index_comparer(); + + /// @param comparer the compare object to be used by this + static void index_comparer( + std::function comparer); + + protected: + inline auto bra_range(AbstractTensor& t) { return t._bra_mutable(); } + inline auto ket_range(AbstractTensor& t) { return t._ket_mutable(); } + + /// the object used to compare indices + static std::function index_comparer_; + + private: + static std::pair< + container::map>*, + std::unique_lock> + instance_map_accessor(); // map* + locked recursive mutex + static container::vector& cardinal_tensor_labels_accessor(); +}; + +/// @brief null Tensor canonicalizer does nothing +class NullTensorCanonicalizer : public TensorCanonicalizer { + public: + virtual ~NullTensorCanonicalizer() = default; + + ExprPtr apply(AbstractTensor&) override; +}; + +class DefaultTensorCanonicalizer : public TensorCanonicalizer { + public: + DefaultTensorCanonicalizer() = default; + + /// @tparam IndexContainer a Container of Index objects such that @c + /// IndexContainer::value_type is convertible to Index (e.g. this can be + /// std::vector or std::set , but not std::map) + /// @param external_indices container of external Index objects + /// @warning @c external_indices is assumed to be immutable during the + /// lifetime of this object + template + DefaultTensorCanonicalizer(IndexContainer&& external_indices) { + ranges::for_each(external_indices, [this](const Index& idx) { + this->external_indices_.emplace(idx.label(), idx); + }); + } + virtual ~DefaultTensorCanonicalizer() = default; + + /// Implements TensorCanonicalizer::apply + /// @note Canonicalizes @c t by sorting its bra (if @c + /// t.symmetry()==Symmetry::nonsymm ) or its bra and ket (if @c + /// t.symmetry()!=Symmetry::nonsymm ), + /// with the external indices appearing "before" (smaller particle + /// indices) than the internal indices + ExprPtr apply(AbstractTensor& t) override; + + /// Core of DefaultTensorCanonicalizer::apply, only does the canonicalization, + /// i.e. no tagging/untagging + template + ExprPtr apply(AbstractTensor& t, const Compare& comp) { + // std::wcout << "abstract tensor: " << to_latex(t) << "\n"; + auto s = symmetry(t); + auto is_antisymm = (s == Symmetry::antisymm); + const auto _bra_rank = bra_rank(t); + const auto _ket_rank = ket_rank(t); + const auto _rank = std::min(_bra_rank, _ket_rank); + + // nothing to do for rank-1 tensors + if (_bra_rank == 1 && _ket_rank == 1) return nullptr; + + using ranges::begin; + using ranges::end; + using ranges::views::counted; + using ranges::views::take; + using ranges::views::zip; + + bool even = true; + switch (s) { + case Symmetry::antisymm: + case Symmetry::symm: { + auto _bra = bra_range(t); + auto _ket = ket_range(t); + // std::wcout << "canonicalizing " << to_latex(t); + IndexSwapper::thread_instance().reset(); + // std::{stable_}sort does not necessarily use swap! so must implement + // sort outselves .. thankfully ranks will be low so can stick with + // bubble + bubble_sort(begin(_bra), end(_bra), comp); + bubble_sort(begin(_ket), end(_ket), comp); + if (is_antisymm) + even = IndexSwapper::thread_instance().even_num_of_swaps(); + // std::wcout << " is " << (even ? "even" : "odd") << " and + // produces " << to_latex(t) << std::endl; + } break; + + case Symmetry::nonsymm: { + // sort particles with bra and ket functions first, + // then the particles with either bra or ket index + auto _bra = bra_range(t); + auto _ket = ket_range(t); + auto _zip_braket = zip(take(_bra, _rank), take(_ket, _rank)); + bubble_sort(begin(_zip_braket), end(_zip_braket), comp); + if (_bra_rank > _rank) { + auto size_of_rest = _bra_rank - _rank; + auto rest_of = counted(begin(_bra) + _rank, size_of_rest); + bubble_sort(begin(rest_of), end(rest_of), comp); + } else if (_ket_rank > _rank) { + auto size_of_rest = _ket_rank - _rank; + auto rest_of = counted(begin(_ket) + _rank, size_of_rest); + bubble_sort(begin(rest_of), end(rest_of), comp); + } + } break; + + default: + abort(); + } + + ExprPtr result = + is_antisymm ? (even == false ? ex(-1) : nullptr) : nullptr; + return result; + } + + private: + container::map external_indices_; +}; + +} + +#endif // SEQUANT_CORE_TENSOR_CANONICALIZER_HPP diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 7e4d634b1..6999d0e9a 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -3,6 +3,7 @@ // #include +#include #include #include #include diff --git a/SeQuant/core/wick.impl.hpp b/SeQuant/core/wick.impl.hpp index 335374f0b..af2015378 100644 --- a/SeQuant/core/wick.impl.hpp +++ b/SeQuant/core/wick.impl.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef SEQUANT_HAS_EXECUTION_HEADER #include diff --git a/SeQuant/domain/mbpt/convention.cpp b/SeQuant/domain/mbpt/convention.cpp index f5f28ec1a..59f2e407e 100644 --- a/SeQuant/domain/mbpt/convention.cpp +++ b/SeQuant/domain/mbpt/convention.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/SeQuant/domain/mbpt/mr.cpp b/SeQuant/domain/mbpt/mr.cpp index a9e5fda89..e22cbf020 100644 --- a/SeQuant/domain/mbpt/mr.cpp +++ b/SeQuant/domain/mbpt/mr.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include diff --git a/SeQuant/domain/mbpt/op.ipp b/SeQuant/domain/mbpt/op.ipp index 54b513ec8..d7a267477 100644 --- a/SeQuant/domain/mbpt/op.ipp +++ b/SeQuant/domain/mbpt/op.ipp @@ -6,6 +6,8 @@ #define SEQUANT_DOMAIN_MBPT_OP_IPP #include +#include + namespace sequant { namespace mbpt { diff --git a/examples/eval/btas/main.cpp b/examples/eval/btas/main.cpp index e779bb351..baaebb2c5 100644 --- a/examples/eval/btas/main.cpp +++ b/examples/eval/btas/main.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/examples/eval/ta/main.cpp b/examples/eval/ta/main.cpp index de873f7e6..943436b3b 100644 --- a/examples/eval/ta/main.cpp +++ b/examples/eval/ta/main.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/examples/osstcc/osstcc.cpp b/examples/osstcc/osstcc.cpp index a2ae82efc..91bd038a9 100644 --- a/examples/osstcc/osstcc.cpp +++ b/examples/osstcc/osstcc.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include diff --git a/examples/stcc/stcc.cpp b/examples/stcc/stcc.cpp index fb85f16bd..bebd45d68 100644 --- a/examples/stcc/stcc.cpp +++ b/examples/stcc/stcc.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/examples/stcc_rigorous/stcc_rigorous.cpp b/examples/stcc_rigorous/stcc_rigorous.cpp index 3785566fb..4150b82db 100644 --- a/examples/stcc_rigorous/stcc_rigorous.cpp +++ b/examples/stcc_rigorous/stcc_rigorous.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/python/src/sequant/mbpt.h b/python/src/sequant/mbpt.h index 24e48828d..d3099a9ee 100644 --- a/python/src/sequant/mbpt.h +++ b/python/src/sequant/mbpt.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "python.h" diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 1dad8ad8a..83c0c7c0d 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/test_eval_expr.cpp b/tests/unit/test_eval_expr.cpp index 831e6cb09..d185236dd 100644 --- a/tests/unit/test_eval_expr.cpp +++ b/tests/unit/test_eval_expr.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/test_mbpt.cpp b/tests/unit/test_mbpt.cpp index fbbd1fe5c..0ef6ca15b 100644 --- a/tests/unit/test_mbpt.cpp +++ b/tests/unit/test_mbpt.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "catch.hpp" #include "test_config.hpp" diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 6167a05d0..8303d29cb 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "catch.hpp" #include "test_config.hpp" diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index d0ad705dc..f20a0761f 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include From f7d1fa0f508887982dc80eb6765739853506e603 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 19 Sep 2023 10:53:30 +0200 Subject: [PATCH 02/85] Introduced Tensor type trait This type trait allows to e.g. static_assert that all requirements of the Tensor interface are fulfilled for a given class. --- SeQuant/core/abstract_tensor.hpp | 85 +++++++++++++++++++++----------- SeQuant/core/op.hpp | 9 ++++ SeQuant/core/tensor.hpp | 4 ++ 3 files changed, 69 insertions(+), 29 deletions(-) diff --git a/SeQuant/core/abstract_tensor.hpp b/SeQuant/core/abstract_tensor.hpp index f45149685..5ce56a956 100644 --- a/SeQuant/core/abstract_tensor.hpp +++ b/SeQuant/core/abstract_tensor.hpp @@ -27,36 +27,15 @@ #include - namespace sequant { -/// This interface class defines a Tensor concept. Object @c t of a type that -/// meets the concept must satisfy the following: -/// - @c bra(t) , @c ket(t) , and @c braket(t) are valid expressions and -/// evaluate to a range of Index objects; -/// - @c bra_rank(t) and @c ket_rank(t) are valid expression and return -/// sizes of the @c bra(t) and @c ket(t) ranges, respectively; -/// - @c symmetry(t) is a valid expression and evaluates to a Symmetry -/// object that describes the symmetry of bra/ket of a -/// _particle-symmetric_ @c t ; -/// - @c braket_symmetry(t) is a valid expression and evaluates to a -/// BraKetSymmetry object that describes the bra-ket symmetry of @c t ; -/// - @c particle_symmetry(t) is a valid expression and evaluates to a -/// ParticleSymmetry object that describes the symmetry of @c t with -/// respect to permutations of particles; -/// - @c color(t) is a valid expression and returns whether a -/// nonnegative integer that identifies the type of a tensor; tensors -/// with different colors can be reordered in a Product at will -/// - @c is_cnumber(t) is a valid expression and returns whether t -/// commutes with other tensor of same color (tensors of different -/// colors are, for now, always assumed to commute) -/// - @c label(t) is a valid expression and its return is convertible to -/// a std::wstring; -/// - @c to_latex(t) is a valid expression and its return is convertible -/// to a std::wstring. -/// To adapt an existing class intrusively derive it from AbstractTensor and -/// implement all member functions. This allows to implememnt heterogeneous -/// containers of objects that meet the Tensor concept. +class TensorCanonicalizer; + +/// This interface class defines a Tensor concept. All Tensor objects must +/// fulfill the is_tensor trait (see below). To adapt an existing class +/// intrusively derive it from AbstractTensor and implement all member +/// functions. This allows to implemement heterogeneous containers of objects +/// that meet the Tensor concept. class AbstractTensor { inline auto missing_instantiation_for(const char* fn_name) const { std::ostringstream oss; @@ -167,6 +146,55 @@ inline auto color(const AbstractTensor& t) { return t._color(); } inline auto is_cnumber(const AbstractTensor& t) { return t._is_cnumber(); } inline auto label(const AbstractTensor& t) { return t._label(); } inline auto to_latex(const AbstractTensor& t) { return t._to_latex(); } + +/// Type trait for checking whether a given class fulfills the Tensor interface +/// requirements Object @c t of a type that meets the concept must satisfy the +/// following: +/// - @c bra(t) , @c ket(t) , and @c braket(t) are valid expressions and +/// evaluate to a range of Index objects; +/// - @c bra_rank(t) and @c ket_rank(t) are valid expression and return +/// sizes of the @c bra(t) and @c ket(t) ranges, respectively; +/// - @c symmetry(t) is a valid expression and evaluates to a Symmetry +/// object that describes the symmetry of bra/ket of a +/// _particle-symmetric_ @c t ; +/// - @c braket_symmetry(t) is a valid expression and evaluates to a +/// BraKetSymmetry object that describes the bra-ket symmetry of @c t ; +/// - @c particle_symmetry(t) is a valid expression and evaluates to a +/// ParticleSymmetry object that describes the symmetry of @c t with +/// respect to permutations of particles; +/// - @c color(t) is a valid expression and returns whether a +/// nonnegative integer that identifies the type of a tensor; tensors +/// with different colors can be reordered in a Product at will +/// - @c is_cnumber(t) is a valid expression and returns whether t +/// commutes with other tensor of same color (tensors of different +/// colors are, for now, always assumed to commute) +/// - @c label(t) is a valid expression and its return is convertible to +/// a std::wstring; +/// - @c to_latex(t) is a valid expression and its return is convertible +/// to a std::wstring. +template +struct is_tensor + : std::bool_constant< + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v && + std::is_invocable_v< + decltype(static_cast(to_latex)), T>> { +}; +template +constexpr bool is_tensor_v = is_tensor::value; +static_assert(is_tensor_v, + "The AbstractTensor class does not fulfill the requirements of " + "the Tensor interface"); + /// @tparam IndexMap a {source Index -> target Index} map type; if it is not @c /// container::map /// will need to make a copy. @@ -201,7 +229,6 @@ inline void reset_tags(AbstractTensor& t) { t._reset_tags(); } using AbstractTensorPtr = std::shared_ptr; - } // namespace sequant #endif // SEQUANT_ABSTRACT_TENSOR_HPP diff --git a/SeQuant/core/op.hpp b/SeQuant/core/op.hpp index 8b8490e21..215fbcd92 100644 --- a/SeQuant/core/op.hpp +++ b/SeQuant/core/op.hpp @@ -865,6 +865,15 @@ class NormalOperator : public Operator, } }; +static_assert( + is_tensor_v>, + "The NormalOperator class does not fulfill the " + "requirements of the Tensor interface"); +static_assert( + is_tensor_v>, + "The NormalOperator class does not fulfill the " + "requirements of the Tensor interface"); + template bool operator==(const NormalOperator &op1, const NormalOperator &op2) { using base_type = Operator; diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index 12cc886e3..5ef5d7b27 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -390,6 +390,10 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { }; // class Tensor +static_assert(is_tensor_v, + "The Tensor class does not fulfill the requirements of the " + "Tensor interface"); + using TensorPtr = std::shared_ptr; /// make_overlap tensor label is reserved since it is used by low-level SeQuant From ec222f1bb3319562cadbda5806129647721a949e Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 20 Sep 2023 13:44:05 +0200 Subject: [PATCH 03/85] Introduce auxiliary indices --- CMakeLists.txt | 1 + SeQuant/core/abstract_tensor.hpp | 35 +- SeQuant/core/attr.hpp | 10 +- SeQuant/core/eval_expr.cpp | 18 +- SeQuant/core/op.hpp | 10 + SeQuant/core/optimize.hpp | 2 +- SeQuant/core/parse/ast.hpp | 6 +- SeQuant/core/parse/ast_conversions.hpp | 17 +- SeQuant/core/parse/deparse.cpp | 22 +- SeQuant/core/parse/parse.cpp | 7 +- SeQuant/core/tensor.hpp | 77 ++- SeQuant/core/tensor_canonicalizer.hpp | 9 +- SeQuant/core/utility/indices.hpp | 66 ++ SeQuant/domain/mbpt/antisymmetrizer.hpp | 9 +- SeQuant/domain/mbpt/mr.cpp | 12 +- SeQuant/domain/mbpt/op.cpp | 1 + SeQuant/domain/mbpt/rdm.hpp | 68 +- SeQuant/domain/mbpt/spin.cpp | 55 +- SeQuant/domain/mbpt/sr.cpp | 10 +- .../antisymmetrizer_test.cpp | 15 +- examples/srcc/srcc.cpp | 7 +- python/src/sequant/_sequant.cc | 11 +- tests/unit/test_canonicalize.cpp | 259 +++---- tests/unit/test_mbpt.cpp | 13 +- tests/unit/test_optimize.cpp | 32 +- tests/unit/test_parse_expr.cpp | 39 +- tests/unit/test_spin.cpp | 642 ++++++++++-------- tests/unit/test_tensor.cpp | 57 +- tests/unit/test_tensor_network.cpp | 36 +- tests/unit/test_utilities.cpp | 55 ++ tests/unit/test_wick.cpp | 152 ++--- 31 files changed, 1121 insertions(+), 632 deletions(-) create mode 100644 SeQuant/core/utility/indices.hpp create mode 100644 tests/unit/test_utilities.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c8c071e49..0c1d34123 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,6 +417,7 @@ if (BUILD_TESTING) tests/unit/test_math.cpp tests/unit/test_string.cpp tests/unit/test_latex.cpp + tests/unit/test_utilities.cpp ) if (TARGET tiledarray) diff --git a/SeQuant/core/abstract_tensor.hpp b/SeQuant/core/abstract_tensor.hpp index 5ce56a956..99a7306f8 100644 --- a/SeQuant/core/abstract_tensor.hpp +++ b/SeQuant/core/abstract_tensor.hpp @@ -66,16 +66,27 @@ class AbstractTensor { virtual const_any_view_randsz _ket() const { throw missing_instantiation_for("_ket"); } + /// view of a contiguous range of Index objects + virtual const_any_view_randsz _auxiliary() const { + throw missing_instantiation_for("_auxiliary"); + } /// view of a not necessarily contiguous range of Index objects virtual const_any_view_rand _braket() const { throw missing_instantiation_for("_braket"); } + /// view of a not necessarily contiguous range of Index objects + virtual const_any_view_rand _indices() const { + throw missing_instantiation_for("_indices"); + } virtual std::size_t _bra_rank() const { throw missing_instantiation_for("_bra_rank"); } virtual std::size_t _ket_rank() const { throw missing_instantiation_for("_ket_rank"); } + virtual std::size_t _auxiliary_rank() const { + throw missing_instantiation_for("_auxiliary_rank"); + } virtual Symmetry _symmetry() const { throw missing_instantiation_for("_symmetry"); } @@ -123,6 +134,12 @@ class AbstractTensor { virtual any_view_randsz _ket_mutable() { throw missing_instantiation_for("_ket_mutable"); } + /// @return mutable view of auxiliary indices + /// @warning this is used for mutable access, flush memoized state before + /// returning! + virtual any_view_randsz _auxiliary_mutable() { + throw missing_instantiation_for("_auxiliary_mutable"); + } friend class TensorCanonicalizer; }; @@ -132,9 +149,14 @@ class AbstractTensor { /// @{ inline auto bra(const AbstractTensor& t) { return t._bra(); } inline auto ket(const AbstractTensor& t) { return t._ket(); } +inline auto auxiliary(const AbstractTensor& t) { return t._auxiliary(); } inline auto braket(const AbstractTensor& t) { return t._braket(); } +inline auto indices(const AbstractTensor& t) { return t._indices(); } inline auto bra_rank(const AbstractTensor& t) { return t._bra_rank(); } inline auto ket_rank(const AbstractTensor& t) { return t._ket_rank(); } +inline auto auxiliary_rank(const AbstractTensor& t) { + return t._auxiliary_rank(); +} inline auto symmetry(const AbstractTensor& t) { return t._symmetry(); } inline auto braket_symmetry(const AbstractTensor& t) { return t._braket_symmetry(); @@ -150,10 +172,12 @@ inline auto to_latex(const AbstractTensor& t) { return t._to_latex(); } /// Type trait for checking whether a given class fulfills the Tensor interface /// requirements Object @c t of a type that meets the concept must satisfy the /// following: -/// - @c bra(t) , @c ket(t) , and @c braket(t) are valid expressions and -/// evaluate to a range of Index objects; -/// - @c bra_rank(t) and @c ket_rank(t) are valid expression and return -/// sizes of the @c bra(t) and @c ket(t) ranges, respectively; +/// - @c bra(t) , @c ket(t), @c auxiliary(t), @c braket(t) and +/// @c indices(t) are valid expressions and evaluate to a range of Index +/// objects; +/// - @c bra_rank(t), @c ket_rank(t) and @c auxiliary_rank(t) are valid +/// expression and return sizes of the @c bra(t), @c ket(t) and @c +/// auxiliary(t) ranges, respectively; /// - @c symmetry(t) is a valid expression and evaluates to a Symmetry /// object that describes the symmetry of bra/ket of a /// _particle-symmetric_ @c t ; @@ -177,9 +201,12 @@ struct is_tensor : std::bool_constant< std::is_invocable_v && std::is_invocable_v && + std::is_invocable_v && std::is_invocable_v && + std::is_invocable_v && std::is_invocable_v && std::is_invocable_v && + std::is_invocable_v && std::is_invocable_v && std::is_invocable_v && std::is_invocable_v && diff --git a/SeQuant/core/attr.hpp b/SeQuant/core/attr.hpp index aa56a94da..f112c23a2 100644 --- a/SeQuant/core/attr.hpp +++ b/SeQuant/core/attr.hpp @@ -52,8 +52,14 @@ inline std::wstring to_wolfram(const Symmetry& symmetry) { enum class BraKetPos { bra, ket, none }; inline std::wstring to_wolfram(BraKetPos a) { - using namespace std::literals; - return L"indexType["s + (a == BraKetPos::bra ? L"bra" : L"ket") + L"]"; + switch (a) { + case BraKetPos::bra: + return L"indexType[bra]"; + case BraKetPos::ket: + return L"indexType[ket]"; + case BraKetPos::none: + return L"indexType[none]"; + } } enum class Statistics { BoseEinstein, FermiDirac }; diff --git a/SeQuant/core/eval_expr.cpp b/SeQuant/core/eval_expr.cpp index c10e113db..61fb46663 100644 --- a/SeQuant/core/eval_expr.cpp +++ b/SeQuant/core/eval_expr.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,8 @@ #include #include #include +#include +#include namespace sequant { @@ -245,6 +248,7 @@ size_t hash_imed(EvalExpr const& left, EvalExpr const& right, return h; } +// TODO: Remove? std::pair, // bra container::svector // ket > @@ -380,7 +384,7 @@ ExprPtr make_sum(EvalExpr const& left, EvalExpr const& right) noexcept { auto ts = tensor_symmetry_sum(left, right); auto ps = particle_symmetry(ts); auto bks = get_default_context().braket_symmetry(); - return ex(L"I", t1.bra(), t1.ket(), ts, bks, ps); + return ex(L"I", t1.bra(), t1.ket(), t1.auxiliary(), ts, bks, ps); } ExprPtr make_prod(EvalExpr const& left, EvalExpr const& right) noexcept { @@ -389,8 +393,8 @@ ExprPtr make_prod(EvalExpr const& left, EvalExpr const& right) noexcept { auto const& t1 = left.as_tensor(); auto const& t2 = right.as_tensor(); - auto [bra, ket] = target_braket(t1, t2); - if (bra.empty() && ket.empty()) { + auto [bra, ket, auxiliary] = get_uncontracted_indices(t1, t2); + if (bra.empty() && ket.empty() && auxiliary.empty()) { // dot product return ex(var_label); } else { @@ -398,7 +402,8 @@ ExprPtr make_prod(EvalExpr const& left, EvalExpr const& right) noexcept { auto ts = tensor_symmetry_prod(left, right); auto ps = particle_symmetry(ts); auto bks = get_default_context().braket_symmetry(); - return ex(L"I", bra, ket, ts, bks, ps); + return ex(L"I", std::move(bra), std::move(ket), + std::move(auxiliary), ts, bks, ps); } } @@ -419,8 +424,9 @@ ExprPtr make_imed(EvalExpr const& left, EvalExpr const& right, assert(op == EvalOp::Prod && "scalar + tensor not supported"); auto const& t = right.expr()->as(); - return ex(Tensor{L"I", t.bra(), t.ket(), t.symmetry(), - t.braket_symmetry(), t.particle_symmetry()}); + return ex(Tensor{L"I", t.bra(), t.ket(), t.auxiliary(), + t.symmetry(), t.braket_symmetry(), + t.particle_symmetry()}); } else if (lres == ResultType::Tensor && rres == ResultType::Scalar) { // tensor (*) scalar diff --git a/SeQuant/core/op.hpp b/SeQuant/core/op.hpp index 215fbcd92..2e6b3a73e 100644 --- a/SeQuant/core/op.hpp +++ b/SeQuant/core/op.hpp @@ -809,13 +809,20 @@ class NormalOperator : public Operator, ranges::views::transform( [](auto &&op) -> const Index & { return op.index(); }); } + AbstractTensor::const_any_view_randsz _auxiliary() const override final { + return {}; + } AbstractTensor::const_any_view_rand _braket() const override final { return ranges::views::concat(annihilators(), creators()) | ranges::views::transform( [](auto &&op) -> const Index & { return op.index(); }); } + AbstractTensor::const_any_view_rand _indices() const override final { + return _braket(); + } std::size_t _bra_rank() const override final { return nannihilators(); } std::size_t _ket_rank() const override final { return ncreators(); } + 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 @@ -863,6 +870,9 @@ class NormalOperator : public Operator, ranges::views::transform( [](auto &&op) -> Index & { return op.index(); }); } + AbstractTensor::any_view_randsz _auxiliary_mutable() override final { + return {}; + } }; static_assert( diff --git a/SeQuant/core/optimize.hpp b/SeQuant/core/optimize.hpp index 9916c2a02..b3a759a73 100644 --- a/SeQuant/core/optimize.hpp +++ b/SeQuant/core/optimize.hpp @@ -201,7 +201,7 @@ eval_seq_t single_term_opt(TensorNetwork const& network, IdxToSz const& idxsz) { auto const& tnsr = *network.tensors().at(i); auto bk = container::svector{}; bk.reserve(bra_rank(tnsr) + ket_rank(tnsr)); - for (auto&& idx : braket(tnsr)) bk.push_back(idx); + for (auto&& idx : indices(tnsr)) bk.push_back(idx); ranges::sort(bk, Index::LabelCompare{}); nth_tensor_indices.emplace_back(std::move(bk)); diff --git a/SeQuant/core/parse/ast.hpp b/SeQuant/core/parse/ast.hpp index dbb58d55d..4c8f42063 100644 --- a/SeQuant/core/parse/ast.hpp +++ b/SeQuant/core/parse/ast.hpp @@ -55,12 +55,14 @@ struct Variable : boost::spirit::x3::position_tagged { struct IndexGroups : boost::spirit::x3::position_tagged { std::vector bra; std::vector ket; + std::vector auxiliaries; bool reverse_bra_ket; IndexGroups(std::vector bra = {}, std::vector ket = {}, - bool reverse_bra_ket = {}) + std::vector auxiliaries = {}, bool reverse_bra_ket = {}) : bra(std::move(bra)), ket(std::move(ket)), + auxiliaries(std::move(auxiliaries)), reverse_bra_ket(reverse_bra_ket) {} }; @@ -114,7 +116,7 @@ BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Index, label, protoLabels); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Number, numerator, denominator); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Variable, name, conjugated); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::IndexGroups, bra, ket, - reverse_bra_ket); + auxiliaries, reverse_bra_ket); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Tensor, name, indices, symmetry); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Product, factors); diff --git a/SeQuant/core/parse/ast_conversions.hpp b/SeQuant/core/parse/ast_conversions.hpp index 2e37f75b2..b0f388143 100644 --- a/SeQuant/core/parse/ast_conversions.hpp +++ b/SeQuant/core/parse/ast_conversions.hpp @@ -75,11 +75,13 @@ Index to_index(const parse::ast::Index &index, } template -std::tuple, container::vector> make_indices( - const parse::ast::IndexGroups &groups, const PositionCache &position_cache, - const Iterator &begin) { +std::tuple, container::vector, + container::vector> +make_indices(const parse::ast::IndexGroups &groups, + const PositionCache &position_cache, const Iterator &begin) { container::vector braIndices; container::vector ketIndices; + container::vector auxiliaries; static_assert(std::is_same_v, "Types for bra and ket indices must be equal for pointer " @@ -101,8 +103,11 @@ std::tuple, container::vector> make_indices( for (const parse::ast::Index ¤t : *ket) { ketIndices.push_back(to_index(current, position_cache, begin)); } + for (const parse::ast::Index ¤t : groups.auxiliaries) { + auxiliaries.push_back(to_index(current, position_cache, begin)); + } - return {std::move(braIndices), std::move(ketIndices)}; + return {std::move(braIndices), std::move(ketIndices), std::move(auxiliaries)}; } template @@ -173,14 +178,14 @@ ExprPtr ast_to_expr(const parse::ast::NullaryValue &value, } ExprPtr operator()(const parse::ast::Tensor &tensor) const { - auto [braIndices, ketIndices] = + auto [braIndices, ketIndices, auxiliaries] = make_indices(tensor.indices, position_cache.get(), begin.get()); auto [offset, length] = get_pos(tensor, position_cache.get(), begin.get()); return ex(tensor.name, std::move(braIndices), - std::move(ketIndices), + std::move(ketIndices), std::move(auxiliaries), to_symmetry(tensor.symmetry, offset + length - 1, begin.get(), default_symmetry)); } diff --git a/SeQuant/core/parse/deparse.cpp b/SeQuant/core/parse/deparse.cpp index ba6173a5c..c1b71fa1e 100644 --- a/SeQuant/core/parse/deparse.cpp +++ b/SeQuant/core/parse/deparse.cpp @@ -73,10 +73,24 @@ std::wstring deparse_sym(Symmetry sym) { } std::wstring deparse_expr(Tensor const& tensor, bool annot_sym) { - return std::wstring(tensor.label()) + L"{" + deparse_indices(tensor.bra()) + - L";" + deparse_indices(tensor.ket()) + L"}" + - (annot_sym ? std::wstring(L":") + deparse_sym(tensor.symmetry()) - : std::wstring{}); + std::wstring deparsed(tensor.label()); + deparsed += L"{" + deparse_indices(tensor.bra()); + if (tensor.ket_rank() > 0) { + deparsed += L";" + deparse_indices(tensor.ket()); + } + if (tensor.auxiliary_rank() > 0) { + if (tensor.ket_rank() == 0) { + deparsed += L";"; + } + deparsed += L";" + deparse_indices(tensor.auxiliary()); + } + deparsed += L"}"; + + if (annot_sym) { + deparsed += L":" + deparse_sym(tensor.symmetry()); + } + + return deparsed; } std::wstring deparse_scalar(const Constant::scalar_type& scalar) { diff --git a/SeQuant/core/parse/parse.cpp b/SeQuant/core/parse/parse.cpp index 28d9fc845..5f0abeb9f 100644 --- a/SeQuant/core/parse/parse.cpp +++ b/SeQuant/core/parse/parse.cpp @@ -101,9 +101,10 @@ auto index_def = x3::lexeme[ SEQUANT_PRAGMA_GCC(diagnostic push) SEQUANT_PRAGMA_GCC(diagnostic ignored "-Wparentheses") -auto index_groups_def = L"_{" > -(index % ',') > L"}^{" > -(index % ',') > L"}" >> x3::attr(false) - | L"^{" > -(index % ',') > L"}_{" > -(index % ',') > L"}" >> x3::attr(true) - | '{' > -(index % ',') > ';' > -(index % ',') > '}' >> x3::attr(false); +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) + | '{' > -(index % ',') > -( ';' > -(index % ',')) > -(';' > -(index % ',')) > '}' >> x3::attr(false); auto tensor_def = x3::lexeme[ name >> x3::skip[index_groups] >> -(':' >> x3::upper) diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index 5ef5d7b27..6c6706c87 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -81,15 +81,16 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { // list of friends who can make Tensor objects with reserved labels friend ExprPtr make_overlap(const Index &bra_index, const Index &ket_index); - template + template Tensor(std::wstring_view label, IndexRange1 &&bra_indices, - IndexRange2 &&ket_indices, reserved_tag, - Symmetry s = Symmetry::nonsymm, + IndexRange2 &&ket_indices, IndexRange3 &&auxiliary_indices, + reserved_tag, Symmetry s = Symmetry::nonsymm, BraKetSymmetry bks = get_default_context().braket_symmetry(), ParticleSymmetry ps = ParticleSymmetry::symm) : label_(label), bra_(make_indices(bra_indices)), ket_(make_indices(ket_indices)), + auxiliary_(make_indices(auxiliary_indices)), symmetry_(s), braket_symmetry_(bks), particle_symmetry_(ps) { @@ -107,39 +108,49 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// to indices) /// @param ket_indices list of ket indices (or objects that can be converted /// 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 - template > && - !meta::is_initializer_list_v>>> + !meta::is_initializer_list_v> && + !meta::is_initializer_list_v>>> Tensor(std::wstring_view label, IndexRange1 &&bra_indices, - IndexRange2 &&ket_indices, Symmetry s = Symmetry::nonsymm, + IndexRange2 &&ket_indices, IndexRange3 &&auxiliary_indices, + Symmetry s = Symmetry::nonsymm, BraKetSymmetry bks = get_default_context().braket_symmetry(), ParticleSymmetry ps = ParticleSymmetry::symm) : Tensor(label, std::forward(bra_indices), - std::forward(ket_indices), reserved_tag{}, s, bks, - ps) { + std::forward(ket_indices), + std::forward(auxiliary_indices), reserved_tag{}, s, + bks, ps) { assert_nonreserved_label(label_); } /// @tparam I1 any type convertible to Index) /// @tparam I2 any type convertible to Index - /// @note I1 and I2 default to Index to allow empty lists + /// @tparam I3 any type convertible to Index + /// @note I1, I2 and I3 default to Index to allow empty lists /// @param label the tensor label /// @param bra_indices list of bra indices (or objects that can be converted /// to indices) /// @param ket_indices list of ket indices (or objects that can be converted /// 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 - template + template Tensor(std::wstring_view label, std::initializer_list bra_indices, - std::initializer_list ket_indices, Symmetry s = Symmetry::nonsymm, + std::initializer_list ket_indices, + std::initializer_list auxiliary_indices, + Symmetry s = Symmetry::nonsymm, BraKetSymmetry bks = get_default_context().braket_symmetry(), ParticleSymmetry ps = ParticleSymmetry::symm) : Tensor(label, make_indices(bra_indices), make_indices(ket_indices), - reserved_tag{}, s, bks, ps) { + make_indices(auxiliary_indices), reserved_tag{}, s, bks, ps) { assert_nonreserved_label(label_); } @@ -154,8 +165,11 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { std::wstring_view label() const override { return label_; } const auto &bra() const { return bra_; } const auto &ket() const { return ket_; } + const auto &auxiliary() const { return auxiliary_; } /// @return joined view of the bra and ket index ranges auto braket() const { return ranges::views::concat(bra_, ket_); } + /// @return joined view of all indices of this tensor (bra, ket and auxiliary) + auto indices() const { return ranges::views::concat(bra_, ket_, auxiliary_); } /// @return view of the bra+ket index ranges /// @note this is to work around broken lookup rules auto const_braket() const { return this->braket(); } @@ -180,6 +194,8 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { std::size_t bra_rank() const { return bra_.size(); } /// @return number of ket indices std::size_t ket_rank() const { return ket_.size(); } + /// @return number of auxiliary indices + auto auxiliary_rank() const { return auxiliary_.size(); } /// @return number of indices in bra/ket /// @throw std::logic_error if bra and ket ranks do not match std::size_t rank() const { @@ -204,7 +220,20 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { for (const auto &i : this->ket()) result += sequant::to_latex(i); result += L"}_{"; for (const auto &i : this->bra()) result += sequant::to_latex(i); - result += L"}}"; + result += L"}"; + if (!this->auxiliary_.empty()) { + result += L"("; + const index_container_type &aux = auxiliary(); + for (std::size_t i = 0; i < auxiliary_rank(); ++i) { + result += sequant::to_latex(aux[i]); + + if (i + 1 < auxiliary_rank()) { + result += L","; + } + } + result += L")"; + } + result += L"}"; return result; } @@ -221,6 +250,9 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { 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; @@ -263,6 +295,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { std::wstring label_{}; index_container_type bra_{}; index_container_type ket_{}; + index_container_type auxiliary_{}; Symmetry symmetry_ = Symmetry::invalid; BraKetSymmetry braket_symmetry_ = BraKetSymmetry::invalid; ParticleSymmetry particle_symmetry_ = ParticleSymmetry::invalid; @@ -283,6 +316,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { auto val = hash::range(begin(bra()), end(bra())); bra_hash_value_ = val; hash::range(val, begin(ket()), end(ket())); + hash::range(val, begin(auxiliary()), end(auxiliary())); hash::combine(val, label_); hash::combine(val, symmetry_); hash_value_ = val; @@ -347,11 +381,21 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { return ranges::counted_view( ket_.empty() ? nullptr : &(ket_[0]), ket_.size()); } + AbstractTensor::const_any_view_randsz _auxiliary() const override final { + return ranges::counted_view( + auxiliary_.empty() ? nullptr : &(auxiliary_[0]), auxiliary_.size()); + } AbstractTensor::const_any_view_rand _braket() const override final { return braket(); } + AbstractTensor::const_any_view_rand _indices() const override final { + return indices(); + } std::size_t _bra_rank() const override final { return bra_rank(); } std::size_t _ket_rank() const override final { return ket_rank(); } + std::size_t _auxiliary_rank() const override final { + return auxiliary_rank(); + } Symmetry _symmetry() const override final { return symmetry_; } BraKetSymmetry _braket_symmetry() const override final { return braket_symmetry_; @@ -387,6 +431,11 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { return ranges::counted_view(ket_.empty() ? nullptr : &(ket_[0]), ket_.size()); } + AbstractTensor::any_view_randsz _auxiliary_mutable() override final { + this->reset_hash_value(); + return ranges::counted_view( + auxiliary_.empty() ? nullptr : &(auxiliary_[0]), auxiliary_.size()); + } }; // class Tensor @@ -403,7 +452,7 @@ inline std::wstring overlap_label() { return L"s"; } inline ExprPtr make_overlap(const Index &bra_index, const Index &ket_index) { return ex(Tensor(overlap_label(), std::array{{bra_index}}, std::array{{ket_index}}, - Tensor::reserved_tag{})); + std::array{}, Tensor::reserved_tag{})); } } // namespace sequant diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp index a633dc9f0..bc6fd5054 100644 --- a/SeQuant/core/tensor_canonicalizer.hpp +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -94,6 +94,7 @@ class TensorCanonicalizer { protected: inline auto bra_range(AbstractTensor& t) { return t._bra_mutable(); } inline auto ket_range(AbstractTensor& t) { return t._ket_mutable(); } + inline auto auxiliary_range(AbstractTensor& t) { return t._auxiliary_mutable(); } /// the object used to compare indices static std::function index_comparer_; @@ -149,10 +150,11 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { auto is_antisymm = (s == Symmetry::antisymm); const auto _bra_rank = bra_rank(t); const auto _ket_rank = ket_rank(t); + const auto _aux_rank = auxiliary_rank(t); const auto _rank = std::min(_bra_rank, _ket_rank); // nothing to do for rank-1 tensors - if (_bra_rank == 1 && _ket_rank == 1) return nullptr; + if (_bra_rank == 1 && _ket_rank == 1 && _aux_rank) return nullptr; using ranges::begin; using ranges::end; @@ -201,6 +203,11 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { abort(); } + // For now we treat auxiliary indices as being unrelated to one another. This makes their + // order insignificant, allowing us to simply sort them. + auto _aux = auxiliary_range(t); + ranges::sort(_aux, comp); + ExprPtr result = is_antisymm ? (even == false ? ex(-1) : nullptr) : nullptr; return result; diff --git a/SeQuant/core/utility/indices.hpp b/SeQuant/core/utility/indices.hpp new file mode 100644 index 000000000..d3ced0135 --- /dev/null +++ b/SeQuant/core/utility/indices.hpp @@ -0,0 +1,66 @@ +// +// 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 + +namespace sequant { + +namespace detail { +template +struct not_in { + const Range ⦥ + + not_in(const Range &range) : range(range) {} + + template + bool operator()(const T &element) const { + return std::find(range.begin(), range.end(), element) == range.end(); + } +}; +} // namespace detail + +/// @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( + const Tensor &t1, const Tensor &t2) { + static_assert(std::is_same_v); + + Container bra; + Container ket; + Container auxiliary; + + // Bra indices + std::copy_if(t1.bra().begin(), t1.bra().end(), std::back_inserter(bra), + detail::not_in{t2.ket()}); + std::copy_if(t2.bra().begin(), t2.bra().end(), std::back_inserter(bra), + detail::not_in{t1.ket()}); + + // Ket indices + std::copy_if(t1.ket().begin(), t1.ket().end(), std::back_inserter(ket), + detail::not_in{t2.bra()}); + std::copy_if(t2.ket().begin(), t2.ket().end(), std::back_inserter(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::copy_if(t2.auxiliary().begin(), t2.auxiliary().end(), + std::back_inserter(auxiliary), detail::not_in{t1.auxiliary()}); + + return {std::move(bra), std::move(ket), std::move(auxiliary)}; +} + +} // namespace sequant + +#endif // SEQUANT_CORE_UTILITY_INDICES_HPP diff --git a/SeQuant/domain/mbpt/antisymmetrizer.hpp b/SeQuant/domain/mbpt/antisymmetrizer.hpp index 8d774f1c3..36878fc9d 100644 --- a/SeQuant/domain/mbpt/antisymmetrizer.hpp +++ b/SeQuant/domain/mbpt/antisymmetrizer.hpp @@ -220,7 +220,8 @@ class antisymm_element { new_kets.push_back(unique_kets_list[j].second[index_label_pos]); index_label_pos++; } - auto new_tensor = ex(label, new_bras, new_kets); + auto new_tensor = + ex(label, new_bras, new_kets, std::vector{}); new_product = new_tensor * new_product; new_product->canonicalize(); } @@ -419,7 +420,8 @@ ExprPtr max_similarity(const std::vector& original_upper, } if (new_pairs > og_pairs) { factor = ex(-1) * ex(factor->as().label(), - current_lower, current_upper); + current_lower, current_upper, + std::vector{}); } } else if (factor->is()) { std::vector current_upper; @@ -500,7 +502,8 @@ ExprPtr spin_sum(std::vector original_upper, new_lower.push_back(factor->as().bra()[i]); } factor = ex(L"Γ", factor->as().bra(), - factor->as().ket()); + factor->as().ket(), + factor->as().auxiliary()); } else if (factor->is()) { // prefactor = ex(-0.5) * // ex(factor->as().rank()) * prefactor; diff --git a/SeQuant/domain/mbpt/mr.cpp b/SeQuant/domain/mbpt/mr.cpp index e22cbf020..fae232b75 100644 --- a/SeQuant/domain/mbpt/mr.cpp +++ b/SeQuant/domain/mbpt/mr.cpp @@ -224,10 +224,10 @@ ExprPtr F() { if (opsymm == Symmetry::antisymm) { braidxs.push_back(m1); ketidxs.push_back(m2); - return ex(to_wstring(mbpt::OpType::g), braidxs, ketidxs, + return ex(to_wstring(mbpt::OpType::g), braidxs, ketidxs, std::vector{}, Symmetry::antisymm) * ex(to_wstring(mbpt::OpType::RDM), IndexList{m2}, - IndexList{m1}, Symmetry::nonsymm); + IndexList{m1}, IndexList{}, Symmetry::nonsymm); } else { // opsymm == Symmetry::nonsymm auto braidx_J = braidxs; braidx_J.push_back(m1); @@ -237,12 +237,12 @@ ExprPtr F() { 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, + 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, + ex(to_wstring(mbpt::OpType::g), braidx_K, ketidxs_K, std::vector{}, Symmetry::nonsymm)) * ex(to_wstring(mbpt::OpType::RDM), IndexList{m2}, - IndexList{m1}, Symmetry::nonsymm); + IndexList{m1}, IndexList{}, Symmetry::nonsymm); } }); }; @@ -311,7 +311,7 @@ ExprPtr vac_av(ExprPtr expr, std::vector> nop_connections, ketidxs.size()); // need to handle particle # violating case? const auto rank = braidxs.size(); return ex( - rdm_label, braidxs, ketidxs, + rdm_label, braidxs, ketidxs, index_container{}, rank > 1 && spinorbital ? Symmetry::antisymm : Symmetry::nonsymm); }; diff --git a/SeQuant/domain/mbpt/op.cpp b/SeQuant/domain/mbpt/op.cpp index e0584ebf3..5196df1e4 100644 --- a/SeQuant/domain/mbpt/op.cpp +++ b/SeQuant/domain/mbpt/op.cpp @@ -201,6 +201,7 @@ ExprPtr OpMaker::operator()(std::optional dep, [this, opsymm_opt](const auto& braidxs, const auto& ketidxs, Symmetry opsymm) { return ex(to_wstring(op_), braidxs, ketidxs, + std::vector{}, opsymm_opt ? *opsymm_opt : opsymm); }, dep ? *dep : UseDepIdx::None); diff --git a/SeQuant/domain/mbpt/rdm.hpp b/SeQuant/domain/mbpt/rdm.hpp index 3e7743534..7de0d8ff5 100644 --- a/SeQuant/domain/mbpt/rdm.hpp +++ b/SeQuant/domain/mbpt/rdm.hpp @@ -19,9 +19,9 @@ ExprPtr cumu_to_density(ExprPtr ex_) { auto down_0 = ex_->as().ket()[0]; auto up_0 = ex_->as().bra()[0]; - auto density = ex(optype2label.at(OpType::RDM), - std::initializer_list{up_0}, - std::initializer_list{down_0}); + auto density = ex( + optype2label.at(OpType::RDM), std::initializer_list{up_0}, + std::initializer_list{down_0}, std::initializer_list{}); return density; } @@ -38,11 +38,14 @@ ExprPtr cumu2_to_density(ExprPtr ex_) { const auto rdm_label = optype2label.at(OpType::RDM); auto density2 = ex(rdm_label, std::initializer_list{up_0, up_1}, - std::initializer_list{down_0, down_1}); + std::initializer_list{down_0, down_1}, + std::initializer_list{}); auto density_1 = ex(rdm_label, std::initializer_list{up_0}, - std::initializer_list{down_0}); + std::initializer_list{down_0}, + std::initializer_list{}); auto density_2 = ex(rdm_label, std::initializer_list{up_1}, - std::initializer_list{down_1}); + std::initializer_list{down_1}, + std::initializer_list{}); auto d1_d2 = antisymmetrize(density_1 * density_2); return density2 + ex(-1) * d1_d2.result; @@ -63,16 +66,21 @@ ExprPtr cumu3_to_density(ExprPtr ex_) { const auto rdm_label = optype2label.at(OpType::RDM); auto cumulant2 = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{up_1, up_2}, - std::initializer_list{down_1, down_2}); + std::initializer_list{down_1, down_2}, + std::initializer_list{}); auto density_1 = ex(rdm_label, std::initializer_list{up_0}, - std::initializer_list{down_0}); + std::initializer_list{down_0}, + std::initializer_list{}); auto density_2 = ex(rdm_label, std::initializer_list{up_1}, - std::initializer_list{down_1}); + std::initializer_list{down_1}, + std::initializer_list{}); auto density_3 = ex(rdm_label, std::initializer_list{up_2}, - std::initializer_list{down_2}); + std::initializer_list{down_2}, + std::initializer_list{}); auto density3 = ex(rdm_label, std::initializer_list{up_0, up_1, up_2}, - std::initializer_list{down_0, down_1, down_2}); + std::initializer_list{down_0, down_1, down_2}, + std::initializer_list{}); auto d1_d2 = antisymmetrize(density_1 * density_2 * density_3 + density_1 * cumulant2); @@ -113,7 +121,8 @@ ExprPtr one_body_sub( std::initializer_list{down_0}); const auto cumu1 = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{down_0}, - std::initializer_list{up_0}); + std::initializer_list{up_0}, + std::initializer_list{}); auto result = a + (ex(-1) * cumu1); return (result); @@ -134,18 +143,20 @@ ExprPtr two_body_decomp( const auto cumu1 = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{down_0}, - std::initializer_list{up_0}); + std::initializer_list{up_0}, + std::initializer_list{}); const auto cumu2 = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{down_1}, - std::initializer_list{up_1}); + std::initializer_list{up_1}, + std::initializer_list{}); const auto a = ex(std::initializer_list{up_1}, std::initializer_list{down_1}); const auto a2 = ex(std::initializer_list{up_0, up_1}, std::initializer_list{down_0, down_1}); - const auto double_cumu = - ex(optype2label.at(OpType::RDMCumulant), - std::initializer_list{down_0, down_1}, - std::initializer_list{up_0, up_1}); + const auto double_cumu = ex( + optype2label.at(OpType::RDMCumulant), + std::initializer_list{down_0, down_1}, + std::initializer_list{up_0, up_1}, std::initializer_list{}); auto term1 = cumu1 * a; auto term2 = cumu1 * cumu2; @@ -178,17 +189,20 @@ three_body_decomp(ExprPtr ex_, bool approx = true) { const auto cumulant = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{down_0}, - std::initializer_list{up_0}); + std::initializer_list{up_0}, + std::initializer_list{}); const auto a = ex(std::initializer_list{up_1, up_2}, std::initializer_list{down_1, down_2}); auto a_cumulant = cumulant * a; auto cumulant2 = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{down_1}, - std::initializer_list{up_1}); + std::initializer_list{up_1}, + std::initializer_list{}); auto cumulant3 = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{down_2}, - std::initializer_list{up_2}); + std::initializer_list{up_2}, + std::initializer_list{}); auto cumulant_3x = cumulant * cumulant2 * cumulant3; auto a1 = ex(std::initializer_list{up_0}, @@ -197,7 +211,8 @@ three_body_decomp(ExprPtr ex_, bool approx = true) { auto two_body_cumu = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{down_1, down_2}, - std::initializer_list{up_1, up_2}); + std::initializer_list{up_1, up_2}, + std::initializer_list{}); auto a1_cumu2 = a1 * two_body_cumu; auto cumu1_cumu2 = cumulant * two_body_cumu; @@ -208,7 +223,8 @@ three_body_decomp(ExprPtr ex_, bool approx = true) { auto cumu3 = ex(optype2label.at(OpType::RDMCumulant), std::initializer_list{down_0, down_1, down_2}, - std::initializer_list{up_0, up_1, up_2}); + std::initializer_list{up_0, up_1, up_2}, + std::initializer_list{}); sum_of_terms.result = cumu3 + sum_of_terms.result; } @@ -304,9 +320,9 @@ three_body_decomposition(ExprPtr ex_, int rank, bool fast = false) { std::vector initial_upper{up_0, up_1, up_2}; initial_pairing.first = initial_lower; initial_pairing.second = initial_upper; // make tensors which can be decomposed into the constituent pieces later in the procedure. - auto DE2 = ex(L"DE2",std::initializer_list{down_0,down_1,down_2},std::initializer_list{up_0,up_1,up_2}); - auto DDE = ex(L"DDE",std::initializer_list{down_0,down_1,down_2},std::initializer_list{up_0,up_1,up_2}); - auto D2E = ex(L"D2E",std::initializer_list{down_0,down_1,down_2},std::initializer_list{up_0,up_1,up_2}); + auto DE2 = ex(L"DE2", IndexList{down_0,down_1,down_2}, IndexList{up_0,up_1,up_2}, IndexList{}); + auto DDE = ex(L"DDE", IndexList{down_0,down_1,down_2}, IndexList{up_0,up_1,up_2}, IndexList{}); + auto D2E = ex(L"D2E", IndexList{down_0,down_1,down_2}, IndexList{up_0,up_1,up_2}, IndexList{}); auto result = DE2 + D2E - ex(2) * DDE; return {result,initial_pairing}; } diff --git a/SeQuant/domain/mbpt/spin.cpp b/SeQuant/domain/mbpt/spin.cpp index 62e5b901f..1b8462e74 100644 --- a/SeQuant/domain/mbpt/spin.cpp +++ b/SeQuant/domain/mbpt/spin.cpp @@ -165,9 +165,9 @@ ExprPtr swap_bra_ket(const ExprPtr& expr) { // Lambda for tensor auto tensor_swap = [](const Tensor& tensor) { - auto result = - Tensor(tensor.label(), tensor.ket(), tensor.bra(), tensor.symmetry(), - tensor.braket_symmetry(), tensor.particle_symmetry()); + auto result = Tensor(tensor.label(), tensor.ket(), tensor.bra(), + tensor.auxiliary(), tensor.symmetry(), + tensor.braket_symmetry(), tensor.particle_symmetry()); return ex(result); }; @@ -238,8 +238,8 @@ ExprPtr remove_spin(const ExprPtr& expr) { idx = make_spinnull(idx); } } - Tensor result(tensor.label(), bra, ket, tensor.symmetry(), - tensor.braket_symmetry()); + Tensor result(tensor.label(), bra, ket, tensor.auxiliary(), + tensor.symmetry(), tensor.braket_symmetry()); return std::make_shared(std::move(result)); }; @@ -320,8 +320,8 @@ ExprPtr expand_antisymm(const Tensor& tensor, bool skip_spinsymm) { // Return non-symmetric tensor if rank is 1 if (tensor.bra_rank() == 1) { Tensor new_tensor(tensor.label(), tensor.bra(), tensor.ket(), - Symmetry::nonsymm, tensor.braket_symmetry(), - tensor.particle_symmetry()); + tensor.auxiliary(), Symmetry::nonsymm, + tensor.braket_symmetry(), tensor.particle_symmetry()); return std::make_shared(new_tensor); } @@ -350,8 +350,8 @@ ExprPtr expand_antisymm(const Tensor& tensor, bool skip_spinsymm) { container::set ket_list(tensor.ket().begin(), tensor.ket().end()); auto expr_sum = std::make_shared(); do { - auto new_tensor = - Tensor(tensor.label(), bra_list, ket_list, Symmetry::nonsymm); + auto new_tensor = Tensor(tensor.label(), bra_list, ket_list, + tensor.auxiliary(), Symmetry::nonsymm); if (spin_symm_tensor(new_tensor)) { auto new_tensor_product = std::make_shared(); @@ -551,14 +551,16 @@ ExprPtr symmetrize_expr(const Product& product) { auto S = Tensor{}; if (A_is_nconserving) { - S = Tensor(L"S", A_tensor.bra(), A_tensor.ket(), Symmetry::nonsymm); + S = Tensor(L"S", A_tensor.bra(), A_tensor.ket(), A_tensor.auxiliary(), + Symmetry::nonsymm); } else { // A is N-nonconserving auto n = std::min(A_tensor.bra_rank(), A_tensor.ket_rank()); container::svector bra_list(A_tensor.bra().begin(), A_tensor.bra().begin() + n); container::svector ket_list(A_tensor.ket().begin(), A_tensor.ket().begin() + n); - S = Tensor(L"S", bra_list, ket_list, Symmetry::nonsymm); + S = Tensor(L"S", bra_list, ket_list, A_tensor.auxiliary(), + Symmetry::nonsymm); } // Generate replacement maps from a list of Index type (could be a bra or a @@ -1030,7 +1032,8 @@ ExprPtr closed_shell_CC_spintrace(ExprPtr const& expr) { auto bixs = ext_idxs | transform([](auto&& vec) { return vec[0]; }); auto kixs = ext_idxs | transform([](auto&& vec) { return vec[1]; }); - st_expr = ex(Tensor{L"S", bixs, kixs}) * st_expr; + st_expr = + ex(Tensor{L"S", bixs, kixs, std::vector{}}) * st_expr; } simplify(st_expr); @@ -1057,7 +1060,8 @@ ExprPtr closed_shell_CC_spintrace_rigorous(ExprPtr const& expr) { auto bixs = ext_idxs | transform([](auto&& vec) { return vec[0]; }); auto kixs = ext_idxs | transform([](auto&& vec) { return vec[1]; }); - st_expr = ex(Tensor{L"S", bixs, kixs}) * st_expr; + st_expr = + ex(Tensor{L"S", bixs, kixs, std::vector{}}) * st_expr; } simplify(st_expr); @@ -1107,6 +1111,7 @@ Tensor swap_spin(const Tensor& t) { return {t.label(), bra, ket, + t.auxiliary(), t.symmetry(), t.braket_symmetry(), t.particle_symmetry()}; @@ -1146,7 +1151,8 @@ ExprPtr merge_tensors(const Tensor& O1, const Tensor& O2) { assert(O1.symmetry() == O2.symmetry()); auto bra = ranges::views::concat(O1.bra(), O2.bra()); auto ket = ranges::views::concat(O1.ket(), O2.ket()); - return ex(Tensor(O1.label(), bra, ket, O1.symmetry())); + auto aux = ranges::views::concat(O1.auxiliary(), O2.auxiliary()); + return ex(Tensor(O1.label(), bra, ket, aux, O1.symmetry())); } std::vector open_shell_A_op(const Tensor& A) { @@ -1171,8 +1177,8 @@ std::vector open_shell_A_op(const Tensor& A) { make_spinbeta); ranges::for_each(spin_bra, [](const Index& i) { i.reset_tag(); }); ranges::for_each(spin_ket, [](const Index& i) { i.reset_tag(); }); - result.at(i) = - ex(Tensor(L"A", spin_bra, spin_ket, Symmetry::antisymm)); + result.at(i) = ex( + Tensor(L"A", spin_bra, spin_ket, A.auxiliary(), Symmetry::antisymm)); // std::wcout << to_latex(result.at(i)) << " "; } // std::wcout << "\n" << std::endl; @@ -1206,8 +1212,8 @@ std::vector open_shell_P_op_vector(const Tensor& A) { for (auto& j : alpha_spin) { for (auto& k : beta_spin) { if (!alpha_spin.empty() && !beta_spin.empty()) { - P_bra_list.emplace_back(Tensor(L"P", {bra.at(j), bra.at(k)}, {})); - P_ket_list.emplace_back(Tensor(L"P", {}, {ket.at(j), ket.at(k)})); + P_bra_list.emplace_back(Tensor(L"P", {bra.at(j), bra.at(k)}, {}, {})); + P_ket_list.emplace_back(Tensor(L"P", {}, {ket.at(j), ket.at(k)}, {})); } } } @@ -1222,10 +1228,12 @@ std::vector open_shell_P_op_vector(const Tensor& A) { auto i3 = beta_spin[c]; for (std::size_t d = c + 1; d != beta_spin.size(); ++d) { auto i4 = beta_spin[d]; - P_bra_list.emplace_back(Tensor( - L"P", {bra.at(i1), bra.at(i3), bra.at(i2), bra.at(i4)}, {})); - P_ket_list.emplace_back(Tensor( - L"P", {}, {ket.at(i1), ket.at(i3), ket.at(i2), ket.at(i4)})); + P_bra_list.emplace_back( + Tensor(L"P", {bra.at(i1), bra.at(i3), bra.at(i2), bra.at(i4)}, + {}, {})); + P_ket_list.emplace_back( + Tensor(L"P", {}, + {ket.at(i1), ket.at(i3), ket.at(i2), ket.at(i4)}, {})); } } } @@ -1718,7 +1726,8 @@ ExprPtr factorize_S(const ExprPtr& expression, ket_list.push_back(*it); }); assert(bra_list.size() == ket_list.size()); - S = Tensor(L"S", bra_list, ket_list, Symmetry::nonsymm); + S = Tensor(L"S", bra_list, ket_list, container::svector{}, + Symmetry::nonsymm); } // For any order CC residual equation: diff --git a/SeQuant/domain/mbpt/sr.cpp b/SeQuant/domain/mbpt/sr.cpp index d5ea582c6..0172e0e8d 100644 --- a/SeQuant/domain/mbpt/sr.cpp +++ b/SeQuant/domain/mbpt/sr.cpp @@ -195,9 +195,9 @@ ExprPtr F(bool use_f_tensor) { braidxs.push_back(m1); ketidxs.push_back(m2); return ex(to_wstring(mbpt::OpType::g), braidxs, ketidxs, - Symmetry::antisymm) * + std::vector{}, Symmetry::antisymm) * ex(to_wstring(mbpt::OpType::δ), IndexList{m2}, - IndexList{m1}, Symmetry::nonsymm); + IndexList{}, IndexList{m1}, Symmetry::nonsymm); } else { // opsymm == Symmetry::nonsymm auto braidx_J = braidxs; braidx_J.push_back(m1); @@ -208,11 +208,11 @@ ExprPtr F(bool use_f_tensor) { auto ketidxs_K = ketidxs; ketidxs_K.emplace(begin(ketidxs_K), m2); return (ex(to_wstring(mbpt::OpType::g), braidx_J, ketidxs_J, - Symmetry::nonsymm) - + std::vector{}, Symmetry::nonsymm) - ex(to_wstring(mbpt::OpType::g), braidx_K, ketidxs_K, - Symmetry::nonsymm)) * + std::vector{}, Symmetry::nonsymm)) * ex(to_wstring(mbpt::OpType::δ), IndexList{m2}, - IndexList{m1}, Symmetry::nonsymm); + IndexList{}, IndexList{m1}, Symmetry::nonsymm); } }); }; diff --git a/examples/antisymmetrizer_test/antisymmetrizer_test.cpp b/examples/antisymmetrizer_test/antisymmetrizer_test.cpp index 9e9a2b896..6a6f5b767 100644 --- a/examples/antisymmetrizer_test/antisymmetrizer_test.cpp +++ b/examples/antisymmetrizer_test/antisymmetrizer_test.cpp @@ -12,10 +12,11 @@ using namespace sequant; void try_main() { using namespace sequant::mbpt; std::wcout << "START ANTISYMM_TEST: " << std::endl; - const auto cumulant = ex(optype2label.at(OpType::RDMCumulant), - WstrList{L"a_1"}, WstrList{L"i_1"}); + const auto cumulant = + ex(optype2label.at(OpType::RDMCumulant), WstrList{L"a_1"}, + WstrList{L"i_1"}, WstrList{}); // const auto a =ex(L"a",WstrList{L"i_2", L"i_3"},WstrList{L"a_2", - // L"a_3"}); + // L"a_3"}, WstrList{}); const auto a = ex( std::initializer_list({Index(L"i_2"), Index(L"i_3")}), std::initializer_list({Index(L"a_2"), Index(L"a_3")})); @@ -25,9 +26,9 @@ void try_main() { std::wcout << to_latex_align(_a_cumulant.result) << std::endl; auto cumulant2 = ex(optype2label.at(OpType::RDMCumulant), - WstrList{L"a_2"}, WstrList{L"i_2"}); + WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}); auto cumulant3 = ex(optype2label.at(OpType::RDMCumulant), - WstrList{L"a_3"}, WstrList{L"i_3"}); + WstrList{L"a_3"}, WstrList{L"i_3"}, WstrList{}); auto cumulant_3x = cumulant * cumulant2 * cumulant3; std::wcout << "cumulant_3x " << to_latex_align(cumulant_3x) << std::endl; antisymmetrize _cumulant_3x(cumulant_3x); @@ -42,7 +43,7 @@ void try_main() { auto two_body_cumu = ex(optype2label.at(OpType::RDMCumulant), WstrList{L"a_2", L"a_3"}, - WstrList{L"i_2", L"i_3"}); + WstrList{L"i_2", L"i_3"}, WstrList{}); auto a1_cumu2 = a1 * two_body_cumu; std::wcout << " a1 y2 " << to_latex_align(a1_cumu2) << std::endl; antisymmetrize _a1_cumu2(a1_cumu2); @@ -55,7 +56,7 @@ void try_main() { auto cumu3 = ex(optype2label.at(OpType::RDMCumulant), WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_1", L"i_2", L"i_3"}); + WstrList{L"i_1", L"i_2", L"i_3"}, WstrList{}); std::wcout << " y3 " << to_latex_align(cumu3) << std::endl; antisymmetrize _cumu3(cumu3); std::wcout << to_latex_align(_cumu3.result) << std::endl; diff --git a/examples/srcc/srcc.cpp b/examples/srcc/srcc.cpp index 8b968934e..0a1733e20 100644 --- a/examples/srcc/srcc.cpp +++ b/examples/srcc/srcc.cpp @@ -100,7 +100,8 @@ class compute_cceqvec { using ranges::views::transform; auto bixs = ext_idxs | transform([](auto&& vec) { return vec[0]; }); auto kixs = ext_idxs | transform([](auto&& vec) { return vec[1]; }); - auto s_tensor = ex(Tensor{L"S", kixs, bixs}); + auto s_tensor = + ex(Tensor{L"S", kixs, bixs, std::vector{}}); eqvec_sf_ref[R] = s_tensor * eqvec_sf_ref[R]; expand(eqvec_sf_ref[R]); } @@ -178,7 +179,9 @@ class compute_cceqvec { auto kixs = ext_idxs | ranges::views::transform( [](auto&& vec) { return vec[1]; }); // N.B. external_indices(expr) confuses bra and ket - eqvec[R] = ex(Tensor{L"S", kixs, bixs}) * eqvec[R]; + eqvec[R] = + ex(Tensor{L"S", kixs, bixs, std::vector{}}) * + eqvec[R]; eqvec[R] = expand(eqvec[R]); simplify(eqvec[R]); diff --git a/python/src/sequant/_sequant.cc b/python/src/sequant/_sequant.cc index 5990c1eb8..b1ec2bdf6 100644 --- a/python/src/sequant/_sequant.cc +++ b/python/src/sequant/_sequant.cc @@ -25,8 +25,10 @@ inline std::vector make_index(std::vector labels) { std::shared_ptr make_tensor(std::wstring label, std::vector bra, - std::vector ket) { - return std::make_shared(label, make_index(bra), make_index(ket)); + std::vector ket, + std::vector auxiliary) { + return std::make_shared(label, make_index(bra), make_index(ket), + make_index(auxiliary)); } std::shared_ptr make_constant(py::float_ number) { @@ -135,9 +137,14 @@ PYBIND11_MODULE(_sequant, m) { .def_property_readonly("label", &Tensor::label) .def_property_readonly("bra", &Tensor::bra) .def_property_readonly("ket", &Tensor::ket) + .def_property_readonly("auxikiary", &Tensor::auxiliary) .def_property_readonly("braket", [](const Tensor &t) { auto braket = t.braket(); return std::vector(braket.begin(), braket.end()); + }) + .def_property_readonly("indices", [](const Tensor &t) { + auto indices = t.indices(); + return std::vector(indices.begin(), indices.end()); }); py::class_>(m, "zRational") diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 83c0c7c0d..d5ea49eae 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -16,6 +16,8 @@ #include +// TODO: Add test cases with auxiliary indices + TEST_CASE("Canonicalizer", "[algorithms]") { using namespace sequant; @@ -24,26 +26,28 @@ TEST_CASE("Canonicalizer", "[algorithms]") { SECTION("Tensors") { { - auto op = ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_3", L"p_4"}, Symmetry::nonsymm); + auto op = + ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}, + WstrList{}, Symmetry::nonsymm); canonicalize(op); REQUIRE(to_latex(op) == L"{g^{{p_3}{p_4}}_{{p_1}{p_2}}}"); } { - auto op = ex(L"g", WstrList{L"p_2", L"p_1"}, - WstrList{L"p_3", L"p_4"}, Symmetry::nonsymm); + auto op = + ex(L"g", WstrList{L"p_2", L"p_1"}, WstrList{L"p_3", L"p_4"}, + WstrList{}, Symmetry::nonsymm); canonicalize(op); REQUIRE(to_latex(op) == L"{g^{{p_4}{p_3}}_{{p_1}{p_2}}}"); } { auto op = ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_4", L"p_3"}, Symmetry::nonsymm); + WstrList{L"p_4", L"p_3"}, WstrList{}, Symmetry::nonsymm); canonicalize(op); REQUIRE(to_latex(op) == L"{g^{{p_4}{p_3}}_{{p_1}{p_2}}}"); } { auto op = ex(L"g", WstrList{L"p_2", L"p_1"}, - WstrList{L"p_4", L"p_3"}, Symmetry::nonsymm); + WstrList{L"p_4", L"p_3"}, WstrList{}, Symmetry::nonsymm); canonicalize(op); REQUIRE(to_latex(op) == L"{g^{{p_3}{p_4}}_{{p_1}{p_2}}}"); } @@ -51,28 +55,30 @@ TEST_CASE("Canonicalizer", "[algorithms]") { SECTION("Products") { { - auto input = ex(L"S", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::nonsymm) * - ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, - Symmetry::nonsymm) * - ex(L"t", IndexList{{L"i_5"}}, IndexList{{L"a_1"}}, - Symmetry::nonsymm) * - ex(L"t", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_5", L"a_2"}, Symmetry::nonsymm); + auto input = + ex(L"S", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, + IndexList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"i_5"}}, IndexList{{L"a_1"}}, WstrList{}, + Symmetry::nonsymm) * + ex(L"t", WstrList{L"i_1", L"i_2"}, WstrList{L"a_5", L"a_2"}, + WstrList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" L"a_2}}}{t^{{a_3}}_{{i_3}}}{t^{{a_1}{a_2}}_{{i_1}{i_2}}}}"); } { - auto input = ex(L"S", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::nonsymm) * - ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, - Symmetry::nonsymm) * - ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, - Symmetry::nonsymm) * - ex(L"t", WstrList{L"i_5", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::nonsymm); + auto input = + ex(L"S", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, WstrList{}, + Symmetry::nonsymm) * + ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, IndexList{}, + Symmetry::nonsymm) * + ex(L"t", WstrList{L"i_5", L"i_2"}, + WstrList{L"a_1", L"a_2"}, WstrList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" @@ -81,17 +87,18 @@ TEST_CASE("Canonicalizer", "[algorithms]") { { // Product containing Variables auto q2 = ex(L"q2"); q2->adjoint(); - auto input = ex(L"S", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::nonsymm) * - q2 * - ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, - Symmetry::nonsymm) * - ex(L"p") * - ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, - Symmetry::nonsymm) * - ex(L"q1") * - ex(L"t", WstrList{L"i_5", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::nonsymm); + auto input = + ex(L"S", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::nonsymm) * + q2 * + ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, WstrList{}, + Symmetry::nonsymm) * + ex(L"p") * + ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, WstrList{}, + Symmetry::nonsymm) * + ex(L"q1") * + ex(L"t", WstrList{L"i_5", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{{p}{q1}{{q2}^*}{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" @@ -99,14 +106,14 @@ TEST_CASE("Canonicalizer", "[algorithms]") { } { // Product containing adjoint of a Tensor auto f2 = - ex(L"f", WstrList{L"i_5", L"i_2"}, WstrList{L"a_1", L"a_2"}, + ex(L"f", WstrList{L"i_5", L"i_2"}, WstrList{L"a_1", L"a_2"}, WstrList{}, Symmetry::nonsymm, BraKetSymmetry::nonsymm); f2->adjoint(); auto input1 = ex(L"S", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::nonsymm) * - ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, + WstrList{L"i_1", L"i_2"}, WstrList{}, Symmetry::nonsymm) * + ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, IndexList{}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, + ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, IndexList{}, Symmetry::nonsymm) * f2; canonicalize(input1); @@ -114,10 +121,10 @@ TEST_CASE("Canonicalizer", "[algorithms]") { L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{a_2}}}{f⁺^{{i_1}{i_" L"3}}_{{a_1}{a_3}}}{t^{{a_2}}_{{i_2}}}}"); auto input2 = ex(L"S", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::nonsymm) * - ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, + WstrList{L"i_1", L"i_2"}, WstrList{}, Symmetry::nonsymm) * + ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, IndexList{}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, + ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, IndexList{}, Symmetry::nonsymm) * f2 * ex(L"w") * ex(rational{1, 2}); canonicalize(input2); @@ -130,20 +137,23 @@ TEST_CASE("Canonicalizer", "[algorithms]") { SECTION("sum of products") { { // CASE 1: Non-symmetric tensors - auto input = ex(rational{1, 2}) * - ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_3", L"p_4"}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"p_3"}}, - IndexList{{L"p_1"}}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"p_4"}}, - IndexList{{L"p_2"}}, Symmetry::nonsymm) + - ex(rational{1, 2}) * - ex(L"g", WstrList{L"p_2", L"p_1"}, - WstrList{L"p_4", L"p_3"}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"p_3"}}, - IndexList{{L"p_1"}}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"p_4"}}, - IndexList{{L"p_2"}}, Symmetry::nonsymm); + auto input = + ex(rational{1, 2}) * + ex(L"g", WstrList{L"p_1", L"p_2"}, + WstrList{L"p_3", L"p_4"}, WstrList{}, + Symmetry::nonsymm) * + ex(L"t", IndexList{{L"p_3"}}, IndexList{{L"p_1"}}, + IndexList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"p_4"}}, IndexList{{L"p_2"}}, + IndexList{}, Symmetry::nonsymm) + + ex(rational{1, 2}) * + ex(L"g", WstrList{L"p_2", L"p_1"}, + WstrList{L"p_4", L"p_3"}, IndexList{}, + Symmetry::nonsymm) * + ex(L"t", IndexList{{L"p_3"}}, IndexList{{L"p_1"}}, + IndexList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"p_4"}}, IndexList{{L"p_2"}}, + IndexList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{ " @@ -153,20 +163,22 @@ TEST_CASE("Canonicalizer", "[algorithms]") { // CASE 2: Symmetric tensors { - auto input = ex(rational{1, 2}) * - ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_3", L"p_4"}, Symmetry::symm) * - ex(L"t", IndexList{{L"p_3"}}, - IndexList{{L"p_1"}}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"p_4"}}, - IndexList{{L"p_2"}}, Symmetry::nonsymm) + - ex(rational{1, 2}) * - ex(L"g", WstrList{L"p_2", L"p_1"}, - WstrList{L"p_4", L"p_3"}, Symmetry::symm) * - ex(L"t", IndexList{{L"p_3"}}, - IndexList{{L"p_1"}}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"p_4"}}, - IndexList{{L"p_2"}}, Symmetry::nonsymm); + auto input = + ex(rational{1, 2}) * + ex(L"g", WstrList{L"p_1", L"p_2"}, + WstrList{L"p_3", L"p_4"}, WstrList{}, Symmetry::symm) * + ex(L"t", IndexList{{L"p_3"}}, IndexList{{L"p_1"}}, + IndexList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"p_4"}}, IndexList{{L"p_2"}}, + IndexList{}, Symmetry::nonsymm) + + ex(rational{1, 2}) * + ex(L"g", WstrList{L"p_2", L"p_1"}, + WstrList{L"p_4", L"p_3"}, IndexList{}, + Symmetry::symm) * + ex(L"t", IndexList{{L"p_3"}}, IndexList{{L"p_1"}}, + IndexList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"p_4"}}, IndexList{{L"p_2"}}, + IndexList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{ " @@ -179,18 +191,20 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto input = ex(rational{1, 2}) * ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_3", L"p_4"}, Symmetry::antisymm) * + WstrList{L"p_3", L"p_4"}, WstrList{}, + Symmetry::antisymm) * ex(L"t", IndexList{{L"p_3"}}, IndexList{{L"p_1"}}, - Symmetry::nonsymm) * + IndexList{}, Symmetry::nonsymm) * ex(L"t", IndexList{{L"p_4"}}, IndexList{{L"p_2"}}, - Symmetry::nonsymm) + + IndexList{}, Symmetry::nonsymm) + ex(rational{1, 2}) * ex(L"g", WstrList{L"p_2", L"p_1"}, - WstrList{L"p_4", L"p_3"}, Symmetry::antisymm) * + WstrList{L"p_4", L"p_3"}, WstrList{}, + Symmetry::antisymm) * ex(L"t", IndexList{{L"p_3"}}, IndexList{{L"p_1"}}, - Symmetry::nonsymm) * + IndexList{}, Symmetry::nonsymm) * ex(L"t", IndexList{{L"p_4"}}, IndexList{{L"p_2"}}, - Symmetry::nonsymm); + IndexList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{ " @@ -204,18 +218,22 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto input = ex(rational{4, 3}) * ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"a_3", L"i_1"}, Symmetry::antisymm) * + WstrList{L"a_3", L"i_1"}, WstrList{}, + Symmetry::antisymm) * ex(L"t", IndexList{{L"a_2"}}, IndexList{{L"i_3"}}, - Symmetry::nonsymm) * + IndexList{}, Symmetry::nonsymm) * ex(L"t", IndexList{L"a_1", L"a_3"}, - IndexList{L"i_4", L"i_2"}, Symmetry::antisymm) - + IndexList{L"i_4", L"i_2"}, IndexList{}, + Symmetry::antisymm) - ex(rational{1, 3}) * ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"i_1", L"a_3"}, Symmetry::antisymm) * + WstrList{L"i_1", L"a_3"}, WstrList{}, + Symmetry::antisymm) * ex(L"t", IndexList{{L"a_2"}}, IndexList{{L"i_4"}}, - Symmetry::nonsymm) * + IndexList{}, Symmetry::nonsymm) * ex(L"t", IndexList{L"a_1", L"a_3"}, - IndexList{L"i_3", L"i_2"}, Symmetry::antisymm); + IndexList{L"i_3", L"i_2"}, IndexList{}, + Symmetry::antisymm); canonicalize(input); REQUIRE(input->size() == 1); REQUIRE(to_latex(input) == @@ -229,18 +247,22 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto input = ex(rational{4, 3}) * ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"a_3", L"i_1"}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"a_2"}}, IndexList{{L"i_3"}}, + WstrList{L"a_3", L"i_1"}, WstrList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"a_2"}}, IndexList{{L"i_3"}}, + IndexList{}, Symmetry::nonsymm) * ex(L"t", IndexList{L"a_1", L"a_3"}, - IndexList{L"i_4", L"i_2"}, Symmetry::nonsymm) - + IndexList{L"i_4", L"i_2"}, IndexList{}, + Symmetry::nonsymm) - ex(rational{1, 3}) * ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"i_1", L"a_3"}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"a_2"}}, IndexList{{L"i_4"}}, + WstrList{L"i_1", L"a_3"}, WstrList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"a_2"}}, IndexList{{L"i_4"}}, + IndexList{}, Symmetry::nonsymm) * ex(L"t", IndexList{L"a_1", L"a_3"}, - IndexList{L"i_3", L"i_2"}, Symmetry::nonsymm); + IndexList{L"i_3", L"i_2"}, IndexList{}, + Symmetry::nonsymm); canonicalize(input); REQUIRE(input->size() == 1); @@ -253,22 +275,24 @@ TEST_CASE("Canonicalizer", "[algorithms]") { { // Case 5: CCSDT R3: S3 * F * T3 { // Terms 1 and 6 from spin-traced result - auto input = - ex(-4) * - ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, - Symmetry::nonsymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_3", L"i_2", L"i_4"}, - Symmetry::nonsymm) + - ex(-4) * - ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, - Symmetry::nonsymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_2", L"i_4", L"i_3"}, Symmetry::nonsymm); + auto input = ex(-4) * + ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, + WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, + WstrList{}) * + ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{L"i_3", L"i_2", L"i_4"}, + WstrList{}, Symmetry::nonsymm) + + ex(-4) * + ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, + WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, + WstrList{}) * + ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{L"i_2", L"i_4", L"i_3"}, + WstrList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{ \\bigl( - " @@ -280,17 +304,21 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto term1 = ex(-4) * ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::nonsymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}) * + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, + Symmetry::nonsymm) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, WstrList{}) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_3", L"i_2", L"i_4"}, Symmetry::nonsymm); + WstrList{L"i_3", L"i_2", L"i_4"}, WstrList{}, + Symmetry::nonsymm); auto term2 = ex(-4) * ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::nonsymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}) * + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, + Symmetry::nonsymm) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, WstrList{}) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_2", L"i_4", L"i_3"}, Symmetry::nonsymm); + WstrList{L"i_2", L"i_4", L"i_3"}, WstrList{}, + Symmetry::nonsymm); canonicalize(term1); canonicalize(term2); REQUIRE(to_latex(term1) == @@ -310,19 +338,22 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto input = ex(2) * ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, Symmetry::nonsymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, + WstrList{}) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_3", L"i_4", L"i_2"}, + WstrList{L"i_3", L"i_4", L"i_2"}, WstrList{}, Symmetry::nonsymm) + ex(2) * ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, Symmetry::nonsymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, + WstrList{}) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_2", L"i_3", L"i_4"}, Symmetry::nonsymm); + WstrList{L"i_2", L"i_3", L"i_4"}, WstrList{}, + Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{ " diff --git a/tests/unit/test_mbpt.cpp b/tests/unit/test_mbpt.cpp index 0ef6ca15b..4ab71bb62 100644 --- a/tests/unit/test_mbpt.cpp +++ b/tests/unit/test_mbpt.cpp @@ -44,7 +44,8 @@ TEST_CASE("NBodyOp", "[mbpt]") { op_t f1([]() -> std::wstring_view { return L"f"; }, []() -> ExprPtr { - return ex(L"f", WstrList{L"p_1"}, WstrList{L"p_2"}) * + return ex(L"f", WstrList{L"p_1"}, WstrList{L"p_2"}, + WstrList{}) * ex(WstrList{L"p_1"}, WstrList{L"p_2"}); }, [](qns_t& qns) { @@ -56,8 +57,8 @@ TEST_CASE("NBodyOp", "[mbpt]") { { // exact compare 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{2, 2})); // cannot produce double replacement REQUIRE(operator==(f1(qns_t{5, 0}), qns_t{{5, 6}, {0, 1}})); } } @@ -69,7 +70,8 @@ TEST_CASE("NBodyOp", "[mbpt]") { // this is fock operator in terms of general spaces op_t f_gg([]() -> std::wstring_view { return L"f"; }, []() -> ExprPtr { - return ex(L"f", WstrList{L"p_1"}, WstrList{L"p_2"}) * + return ex(L"f", WstrList{L"p_1"}, WstrList{L"p_2"}, + WstrList{}) * ex(WstrList{L"p_1"}, WstrList{L"p_2"}); }, [](qns_t& qns) { @@ -78,7 +80,8 @@ TEST_CASE("NBodyOp", "[mbpt]") { // excitation part of the Fock operator op_t f_uo([]() -> std::wstring_view { return L"f"; }, []() -> ExprPtr { - return ex(L"f", WstrList{L"a_2"}, WstrList{L"i_2"}) * + return ex(L"f", WstrList{L"a_2"}, WstrList{L"i_2"}, + WstrList{}) * ex(WstrList{L"a_1"}, WstrList{L"i_2"}); }, [](qns_t& qns) { diff --git a/tests/unit/test_optimize.cpp b/tests/unit/test_optimize.cpp index d5713fb81..d5c9af66c 100644 --- a/tests/unit/test_optimize.cpp +++ b/tests/unit/test_optimize.cpp @@ -26,10 +26,12 @@ sequant::ExprPtr extract(sequant::ExprPtr expr, TEST_CASE("TEST_OPTIMIZE", "[optimize]") { using namespace sequant; - auto idx2size = [nocc = 4, nvirt = 140](Index const& idx) { + 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"); }; @@ -134,6 +136,34 @@ TEST_CASE("TEST_OPTIMIZE", "[optimize]") { REQUIRE(extract(res6, {3, 0}) == prod6.at(3)); REQUIRE(extract(res6, {3, 1}) == prod6.at(5)); REQUIRE(extract(res6, {4}) == prod6.at(4)); + + // + // single-term optimization including tensors with auxiliary indices + // + auto prod7 = parse_expr( + L"DF{a_1;a_3;x_1} " // T1 + "DF{a_2;i_1;x_1} " // T2 + "t{a_3;i_2}" // T3 + )->as(); + auto res7 = single_term_opt(prod7); + + // this is the one we want to find + // (T1 T3) T2: V^2 O^1 A^1 + V^2 O^2 A^1 best if nvirt > nocc and nvirt > nact + REQUIRE(extract(res7, {0, 0}) == prod7.at(0)); + REQUIRE(extract(res7, {0, 1}) == prod7.at(2)); + REQUIRE(extract(res7, {1}) == prod7.at(1)); + + auto prod8 = parse_expr( + L"T1{i_1;i_2;x_1,x_2,x_3,x_4} T2{i_2;i_1;x_5,x_6,x_7,x_8} T3{i_3;;x_1,x_2,x_3,x_4} T4{i_4;;x_5,x_6,x_7,x_8}" + )->as(); + auto res8 = single_term_opt(prod8); + + // this is the one we want to find + // (T1 T3)(T2 T4) + REQUIRE(extract(res8, {0, 0}) == prod8.at(0)); + REQUIRE(extract(res8, {0, 1}) == prod8.at(2)); + REQUIRE(extract(res8, {1, 0}) == prod8.at(1)); + REQUIRE(extract(res8, {1, 1}) == prod8.at(3)); } SECTION("Ensure single-value sums/products are not discarded") { diff --git a/tests/unit/test_parse_expr.cpp b/tests/unit/test_parse_expr.cpp index 6db2743cf..aff22d0db 100644 --- a/tests/unit/test_parse_expr.cpp +++ b/tests/unit/test_parse_expr.cpp @@ -74,6 +74,18 @@ ParseErrorMatcher parseErrorMatches(std::size_t offset, std::size_t length, TEST_CASE("TEST_PARSE_EXPR", "[parse_expr]") { using namespace sequant; + SECTION("Scalar tensor") { + auto expr = parse_expr(L"t{}"); + REQUIRE(expr->is()); + REQUIRE(expr->as().bra().empty()); + REQUIRE(expr->as().ket().empty()); + REQUIRE(expr->as().auxiliary().empty()); + + REQUIRE(expr == parse_expr(L"t{;}")); + REQUIRE(expr == parse_expr(L"t{;;}")); + REQUIRE(expr == parse_expr(L"t^{}_{}")); + REQUIRE(expr == parse_expr(L"t_{}^{}")); + } SECTION("Tensor") { auto expr = parse_expr(L"t{i1;a1}"); REQUIRE(expr->is()); @@ -82,10 +94,12 @@ TEST_CASE("TEST_PARSE_EXPR", "[parse_expr]") { REQUIRE(expr->as().bra().at(0).label() == L"i_1"); REQUIRE(expr->as().ket().size() == 1); REQUIRE(expr->as().ket().at(0) == L"a_1"); + REQUIRE(expr->as().auxiliary().empty()); REQUIRE(expr == parse_expr(L"t_{i1}^{a1}")); REQUIRE(expr == parse_expr(L"t^{a1}_{i1}")); REQUIRE(expr == parse_expr(L"t{i_1; a_1}")); + REQUIRE(expr == parse_expr(L"t{i_1; a_1;}")); REQUIRE(expr == parse_expr(L"t_{i_1}^{a_1}")); expr = parse_expr(L"t{i1,i2;a1,a2}"); @@ -95,6 +109,7 @@ TEST_CASE("TEST_PARSE_EXPR", "[parse_expr]") { REQUIRE(expr->as().ket().size() == 2); REQUIRE(expr->as().ket().at(0).label() == L"a_1"); REQUIRE(expr->as().ket().at(1).label() == L"a_2"); + REQUIRE(expr->as().auxiliary().empty()); REQUIRE(expr == parse_expr(L"+t{i1, i2; a1, a2}")); REQUIRE(parse_expr(L"-t{i1;a1}")->is()); @@ -120,6 +135,26 @@ TEST_CASE("TEST_PARSE_EXPR", "[parse_expr]") { auto expr1 = parse_expr(L"t{a↓1;i↑1}"); REQUIRE(expr1->as().bra().at(0).label() == L"a↓_1"); REQUIRE(expr1->as().ket().at(0).label() == L"i↑_1"); + + // Auxiliary indices + expr = parse_expr(L"t{;;i1}"); + REQUIRE(expr->is()); + REQUIRE(expr->as().bra().empty()); + REQUIRE(expr->as().ket().empty()); + REQUIRE(expr->as().auxiliary().size() == 1); + REQUIRE(expr->as().auxiliary()[0].label() == L"i_1"); + + // All index groups at once + expr = parse_expr(L"t{i1,i2;a1;x1,x2}"); + REQUIRE(expr->is()); + REQUIRE(expr->as().bra().size() == 2); + REQUIRE(expr->as().bra().at(0).label() == L"i_1"); + REQUIRE(expr->as().bra().at(1).label() == L"i_2"); + REQUIRE(expr->as().ket().size() == 1); + REQUIRE(expr->as().ket().at(0).label() == L"a_1"); + REQUIRE(expr->as().auxiliary().size() == 2); + REQUIRE(expr->as().auxiliary().at(0).label() == L"x_1"); + REQUIRE(expr->as().auxiliary().at(1).label() == L"x_2"); } SECTION("Tensor with symmetry annotation") { @@ -322,7 +357,9 @@ TEST_CASE("TEST_DEPARSE_EXPR", "[parse_expr]") { L"-1/4 t{a_1,i_1;a_2,i_2}:S", L"a + b - 4 specialVariable", L"variable + A{a_1;i_1}:N * B{i_1;a_1}:A", - L"1/2 (a + b) * c"}; + L"1/2 (a + b) * c", + L"T1{}:N + T2{;;x_1}:N * T3{;;x_1}:N + T4{a_1;;x_2}:S * T5{;a_1;x_2}:S" + }; for (const std::wstring& current : expressions) { ExprPtr expression = parse_expr(current); diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 8303d29cb..0f8f2e950 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -47,8 +47,8 @@ TEST_CASE("Spin", "[spin]") { Index i1(L"i_1", IndexSpace::instance(IndexSpace::active_occupied)); Index a1(L"a_1", IndexSpace::instance(IndexSpace::active_unoccupied), {i1}); - const auto expr = ex(L"t", IndexList{i1}, IndexList{a1}) * - ex(L"F", IndexList{a1}, IndexList{i1}); + const auto expr = ex(L"t", IndexList{i1}, IndexList{a1}, IndexList{}) * + ex(L"F", IndexList{a1}, IndexList{i1}, IndexList{}); REQUIRE_NOTHROW(spintrace(expr)); { // assume spin-free spaces auto expr_st = spintrace(expr); @@ -205,7 +205,8 @@ TEST_CASE("Spin", "[spin]") { auto p4 = Index(L"p↓_4", IndexSpace::instance(IndexSpace::all, IndexSpace::beta)); - auto input = ex(L"t", IndexList{p1, p2}, IndexList{p3, p4}); + auto input = + ex(L"t", IndexList{p1, p2}, IndexList{p3, p4}, IndexList{}); REQUIRE(can_expand(input->as()) == true); REQUIRE(spin_symm_tensor(input->as()) == true); @@ -217,7 +218,7 @@ TEST_CASE("Spin", "[spin]") { REQUIRE(i.space() == IndexSpace::instance(IndexSpace::all, IndexSpace::nullqns)); - input = ex(L"t", IndexList{p1, p3}, IndexList{p2, p4}); + 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}}}"); REQUIRE(can_expand(input->as()) == false); REQUIRE(spin_symm_tensor(input->as()) == false); @@ -225,14 +226,15 @@ TEST_CASE("Spin", "[spin]") { SECTION("Tensor: expand_antisymm") { // 1-body - auto input = ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}); + auto input = + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, WstrList{}); auto result = expand_antisymm(input->as()); REQUIRE(input->as() == result->as()); REQUIRE(!result->is()); REQUIRE(to_latex(result) == L"{t^{{i_1}}_{{a_1}}}"); // 1-body - input = ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, + input = ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, WstrList{}, Symmetry::antisymm); result = expand_antisymm(input->as()); REQUIRE(input->as().symmetry() == Symmetry::antisymm); @@ -241,7 +243,7 @@ TEST_CASE("Spin", "[spin]") { // 2-body input = ex(L"g", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); result = expand_antisymm(input->as()); REQUIRE(to_latex(result) == L"{ \\bigl({{g^{{a_1}{a_2}}_{{i_1}{i_2}}}} - " @@ -249,7 +251,8 @@ TEST_CASE("Spin", "[spin]") { // 3-body input = ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_1", L"i_2", L"i_3"}, Symmetry::antisymm); + WstrList{L"i_1", L"i_2", L"i_3"}, WstrList{}, + Symmetry::antisymm); result = expand_antisymm(input->as()); REQUIRE(to_latex(result) == L"{ \\bigl({{t^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}} - " @@ -268,9 +271,10 @@ TEST_CASE("Spin", "[spin]") { } SECTION("Tensor") { - const auto expr = ex(rational{1, 4}) * - ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_3", L"p_4"}, Symmetry::antisymm); + const auto expr = + ex(rational{1, 4}) * + ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}, + WstrList{}, Symmetry::antisymm); auto result = spintrace(expr); REQUIRE(result->is()); canonicalize(result); @@ -280,8 +284,9 @@ TEST_CASE("Spin", "[spin]") { } SECTION("Product") { - const auto expr = ex(L"f", WstrList{L"i_1"}, WstrList{L"a_1"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}); + const auto expr = + ex(L"f", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, WstrList{}); auto result = spintrace(expr, {{L"i_1", L"a_1"}}); canonicalize(result); REQUIRE(to_latex(result) == @@ -294,9 +299,9 @@ TEST_CASE("Spin", "[spin]") { const auto expr = ex(rational{1, 2}) * ex(L"g", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}); + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}); auto result = spintrace(expr, {{L"i_1", L"a_1"}}); canonicalize(result); REQUIRE( @@ -311,18 +316,21 @@ TEST_CASE("Spin", "[spin]") { SECTION("Sum") { // f * t1 + 1/2 * g * t1 * t1 + 1/4 * g * t2 - const auto ex1 = ex(L"f", WstrList{L"i_1"}, WstrList{L"a_1"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}); - const auto ex2 = ex(rational{1, 2}) * - ex(L"g", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::antisymm) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}); - const auto ex3 = ex(rational{1, 4}) * - ex(L"g", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::antisymm) * - ex(L"t", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::antisymm); + const auto ex1 = + ex(L"f", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, WstrList{}); + const auto ex2 = + ex(rational{1, 2}) * + ex(L"g", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}); + const auto ex3 = + ex(rational{1, 4}) * + ex(L"g", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::antisymm); auto expr = ex1 + ex2 + ex3; auto result = ex(rational{1, 2}) * spintrace(expr); @@ -347,7 +355,7 @@ TEST_CASE("Spin", "[spin]") { REQUIRE(result->is_atom()); input = ex(1) * ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); result = expand_A_op(input); REQUIRE(result->size() == 0); REQUIRE(result->is_atom()); @@ -355,9 +363,10 @@ TEST_CASE("Spin", "[spin]") { // 1-body { - auto input = - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, Symmetry::antisymm) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, Symmetry::antisymm); + auto input = ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}, + Symmetry::antisymm) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_1"}, WstrList{}, + Symmetry::antisymm); auto result = expand_A_op(input); REQUIRE(result->size() == 1); REQUIRE(!result->is()); @@ -365,18 +374,19 @@ TEST_CASE("Spin", "[spin]") { // 2-body { - auto input = ex(rational{1, 4}) * - ex(L"g", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::antisymm); + auto input = + ex(rational{1, 4}) * ex(L"g", WstrList{L"i_1", L"i_2"}, + WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::antisymm); auto result = expand_A_op(input); REQUIRE(to_latex(result) == L"{{{\\frac{1}{4}}}{\\bar{g}^{{a_1}{a_2}}_{{i_1}{i_2}}}}"); input = ex(rational{1, 4}) * ex(L"A", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"g", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); result = expand_A_op(input); REQUIRE(to_latex(result) == L"{ \\bigl({{{\\frac{1}{4}}}{\\bar{g}^{{a_1}{a_2}}_{{i_1}{i_2}}}} - " @@ -387,11 +397,11 @@ TEST_CASE("Spin", "[spin]") { // 1/4 * A * g * t1 * t1 input = ex(rational{1, 4}) * ex(L"A", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"g", WstrList{L"a_1", L"a_2"}, WstrList{L"a_3", L"a_4"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_4"}, WstrList{L"i_2"}); + WstrList{}, Symmetry::antisymm) * + 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(to_latex(result) == L"{ " @@ -407,13 +417,13 @@ TEST_CASE("Spin", "[spin]") { // 1/4 * A * g * t1 * t1 * t1 * t1 input = ex(rational{1, 4}) * ex(L"A", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"g", WstrList{L"i_3", L"i_4"}, WstrList{L"a_3", L"a_4"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_4"}, WstrList{L"i_2"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_3"}) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_4"}); + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_4"}, WstrList{L"i_2"}, WstrList{}) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_3"}, WstrList{}) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_4"}, WstrList{}); result = expand_A_op(input); REQUIRE(to_latex(result) == L"{ " @@ -432,26 +442,30 @@ TEST_CASE("Spin", "[spin]") { // 3-body { auto input = ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_1", L"i_2", L"i_3"}, Symmetry::antisymm); + WstrList{L"i_1", L"i_2", L"i_3"}, WstrList{}, + Symmetry::antisymm); auto result = expand_A_op(input); REQUIRE(to_latex(result) == L"{\\bar{t}^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}"); input = ex(L"A", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::antisymm) * + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, + Symmetry::antisymm) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_1", L"i_2", L"i_3"}, Symmetry::antisymm); + WstrList{L"i_1", L"i_2", L"i_3"}, WstrList{}, + Symmetry::antisymm); result = expand_A_op(input); REQUIRE(result->is()); REQUIRE(result->size() == 36); } { // 4-body - const auto input = - ex(L"A", WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, - WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, Symmetry::antisymm) * - ex(L"t", WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, - WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, Symmetry::antisymm); + const auto input = ex(L"A", WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, + WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, + WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, + WstrList{}, Symmetry::antisymm); auto asm_input = expand_A_op(input); REQUIRE(asm_input->size() == 576); REQUIRE(asm_input->is()); @@ -461,10 +475,10 @@ TEST_CASE("Spin", "[spin]") { { // 5-body const auto input = ex(L"A", WstrList{L"i_1", L"i_2", L"i_3", L"i_4", L"i_5"}, - WstrList{L"a_1", L"a_2", L"a_3", L"a_4", L"a_5"}, + WstrList{L"a_1", L"a_2", L"a_3", L"a_4", L"a_5"}, WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3", L"a_4", L"a_5"}, - WstrList{L"i_1", L"i_2", L"i_3", L"i_4", L"i_5"}, + WstrList{L"i_1", L"i_2", L"i_3", L"i_4", L"i_5"}, WstrList{}, Symmetry::antisymm); auto asm_input = expand_A_op(input); REQUIRE(asm_input->size() == 14400); @@ -475,10 +489,11 @@ TEST_CASE("Spin", "[spin]") { SECTION("Expand Symmetrizer") { { // 2-body - const auto input = ex(L"S", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::nonsymm) * - ex(L"t", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::antisymm); + const auto input = + ex(L"S", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"t", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::antisymm); auto result = S_maps(input); REQUIRE(result->size() == 2); REQUIRE(result->is()); @@ -488,11 +503,12 @@ SECTION("Expand Symmetrizer") { } { // 3-body - const auto input = - ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::nonsymm) * - ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_1", L"i_2", L"i_3"}, Symmetry::antisymm); + const auto input = ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, + Symmetry::nonsymm) * + ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{L"i_1", L"i_2", L"i_3"}, WstrList{}, + Symmetry::antisymm); auto result = S_maps(input); REQUIRE(to_latex(result) == L"{ \\bigl({{\\bar{t}^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}} + " @@ -506,10 +522,10 @@ SECTION("Expand Symmetrizer") { { // 4-body const auto input = ex(L"S", WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, - WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, + WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, WstrList{}, Symmetry::nonsymm) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, - WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, + WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, WstrList{}, Symmetry::antisymm); auto result = S_maps(input); REQUIRE(to_latex(result) == @@ -546,13 +562,15 @@ SECTION("Expand Symmetrizer") { const auto input = ex(4) * ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::nonsymm) * - ex(L"g", WstrList{L"i_4", L"i_5"}, WstrList{L"a_4", L"a_5"}, + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, Symmetry::nonsymm) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_4"}) * - ex(L"t", WstrList{L"a_5"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_4"}, WstrList{L"i_2"}) * - ex(L"t", WstrList{L"a_1", L"a_2"}, WstrList{L"i_5", L"i_3"}); + ex(L"g", WstrList{L"i_4", L"i_5"}, WstrList{L"a_4", L"a_5"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"t", WstrList{L"a_3"}, WstrList{L"i_4"}, WstrList{}) * + ex(L"t", WstrList{L"a_5"}, WstrList{L"i_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_4"}, WstrList{L"i_2"}, WstrList{}) * + ex(L"t", WstrList{L"a_1", L"a_2"}, WstrList{L"i_5", L"i_3"}, + WstrList{}); auto result = S_maps(input); REQUIRE(result->is()); REQUIRE(result->size() == 6); @@ -579,12 +597,13 @@ SECTION("Expand Symmetrizer") { SECTION("Symmetrize expression") { { // g * t1 + g * t1 - auto input = ex(L"g", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"a_3"}, Symmetry::symm) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_2"}) + - ex(L"g", WstrList{L"a_2", L"a_1"}, - WstrList{L"i_2", L"a_3"}, Symmetry::symm) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}); + auto input = + ex(L"g", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"a_3"}, + WstrList{}, Symmetry::symm) * + ex(L"t", WstrList{L"a_3"}, WstrList{L"i_2"}, WstrList{}) + + ex(L"g", WstrList{L"a_2", L"a_1"}, WstrList{L"i_2", L"a_3"}, + WstrList{}, Symmetry::symm) * + 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); REQUIRE(to_latex(result) == @@ -594,16 +613,17 @@ SECTION("Symmetrize expression") { { // g * t1 * t1 * t1 + g * t1 * t1 * t1 - auto input = ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"i_1", L"a_3"}, Symmetry::symm) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_3"}) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_4"}) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_2"}) + - ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"i_2", L"a_3"}, Symmetry::symm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_3"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_4"}) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}); + auto input = + ex(L"g", WstrList{L"i_3", L"i_4"}, WstrList{L"i_1", L"a_3"}, + WstrList{}, Symmetry::symm) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_3"}, WstrList{}) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_4"}, WstrList{}) * + ex(L"t", WstrList{L"a_3"}, WstrList{L"i_2"}, WstrList{}) + + ex(L"g", WstrList{L"i_3", L"i_4"}, WstrList{L"i_2", L"a_3"}, + WstrList{}, Symmetry::symm) * + 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); REQUIRE(to_latex(result) == @@ -613,20 +633,21 @@ SECTION("Symmetrize expression") { { // g * t1 * t1 * t2 + g * t1 * t1 * t2 - auto input = ex(2) * - ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"a_3", L"a_4"}, Symmetry::symm) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_3"}) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_4"}) * - ex(L"t", WstrList{L"a_1", L"a_4"}, - WstrList{L"i_1", L"i_2"}) + - ex(2) * - ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"a_3", L"a_4"}, Symmetry::symm) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_3"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_4"}) * - ex(L"t", WstrList{L"a_2", L"a_4"}, - WstrList{L"i_2", L"i_1"}); + auto input = + ex(2) * + ex(L"g", WstrList{L"i_3", L"i_4"}, WstrList{L"a_3", L"a_4"}, + WstrList{}, Symmetry::symm) * + ex(L"t", WstrList{L"a_3"}, WstrList{L"i_3"}, WstrList{}) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_4"}, WstrList{}) * + ex(L"t", WstrList{L"a_1", L"a_4"}, WstrList{L"i_1", L"i_2"}, + WstrList{}) + + ex(2) * + ex(L"g", WstrList{L"i_3", L"i_4"}, WstrList{L"a_3", L"a_4"}, + WstrList{}, Symmetry::symm) * + ex(L"t", WstrList{L"a_3"}, WstrList{L"i_3"}, WstrList{}) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_4"}, WstrList{}) * + ex(L"t", WstrList{L"a_2", L"a_4"}, WstrList{L"i_2", L"i_1"}, + WstrList{}); auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); REQUIRE( @@ -638,11 +659,12 @@ SECTION("Symmetrize expression") { SECTION("Transform expression") { // - A * g * t1 - const auto input = ex(-1) * - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * - ex(L"g", WstrList{L"i_2", L"a_1"}, - WstrList{L"i_1", L"a_2"}, Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}); + const auto input = + ex(-1) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * + ex(L"g", WstrList{L"i_2", L"a_1"}, WstrList{L"i_1", L"a_2"}, + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -670,17 +692,19 @@ SECTION("Swap bra kets") { // Tensor { - auto input = ex(L"g", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::nonsymm); + auto input = + ex(L"g", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::nonsymm); auto result = swap_bra_ket(input); REQUIRE(result->to_latex() == L"{g^{{i_1}{i_2}}_{{a_1}{a_2}}}"); } // Product { - auto input = ex(L"g", WstrList{L"a_5", L"a_6"}, - WstrList{L"i_5", L"i_6"}, Symmetry::nonsymm) * - ex(L"t", WstrList{L"i_2"}, WstrList{L"a_6"}); + auto input = + ex(L"g", WstrList{L"a_5", L"a_6"}, WstrList{L"i_5", L"i_6"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"t", WstrList{L"i_2"}, WstrList{L"a_6"}, WstrList{}); auto result = swap_bra_ket(input); REQUIRE(result->to_latex() == L"{{g^{{a_5}{a_6}}_{{i_5}{i_6}}}{t^{{i_2}}_{{a_6}}}}"); @@ -688,10 +712,11 @@ SECTION("Swap bra kets") { // Sum { - auto input = ex(L"f", WstrList{L"i_1"}, WstrList{L"i_5"}) + - ex(L"g", WstrList{L"a_5", L"a_6"}, - WstrList{L"i_5", L"i_6"}, Symmetry::nonsymm) * - ex(L"t", WstrList{L"i_2"}, WstrList{L"a_6"}); + auto input = + ex(L"f", WstrList{L"i_1"}, WstrList{L"i_5"}, WstrList{}) + + ex(L"g", WstrList{L"a_5", L"a_6"}, WstrList{L"i_5", L"i_6"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"t", WstrList{L"i_2"}, WstrList{L"a_6"}, WstrList{}); auto result = swap_bra_ket(input); REQUIRE(result->to_latex() == L"{ \\bigl({f^{{i_1}}_{{i_5}}} + " @@ -720,9 +745,9 @@ SECTION("Closed-shell spintrace CCD") { const auto pno_ccd_energy_so = ex(rational(1, 4)) * ex(L"g", IndexList{a1, a2}, IndexList{i1, i2}, - Symmetry::antisymm) * + IndexList{}, Symmetry::antisymm) * ex(L"t", IndexList{i1, i2}, IndexList{a1, a2}, - Symmetry::antisymm); + IndexList{}, Symmetry::antisymm); // why??? const auto pno_ccd_energy_so_as_sum = @@ -744,8 +769,9 @@ SECTION("Closed-shell spintrace CCSD") { // These terms from CCSD R1 equations { // A * f - const auto input = ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * - ex(L"f", WstrList{L"a_1"}, WstrList{L"i_1"}); + const auto input = + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * + ex(L"f", WstrList{L"a_1"}, WstrList{L"i_1"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -759,10 +785,10 @@ SECTION("Closed-shell spintrace CCSD") { // - A * g * t1 const auto input = ex(-1) * - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * ex(L"g", WstrList{L"i_2", L"a_1"}, WstrList{L"i_1", L"a_2"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}); + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); simplify(result); @@ -783,10 +809,11 @@ SECTION("Closed-shell spintrace CCSD") { { // - A * f * t1 - const auto input = ex(-1) * - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * - ex(L"f", WstrList{L"i_2"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_2"}); + const auto input = + ex(-1) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * + ex(L"f", WstrList{L"i_2"}, WstrList{L"i_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_2"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -798,9 +825,10 @@ SECTION("Closed-shell spintrace CCSD") { { // A * f * t1 - const auto input = ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * - ex(L"f", WstrList{L"a_1"}, WstrList{L"a_2"}) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_1"}); + const auto input = + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * + ex(L"f", WstrList{L"a_1"}, WstrList{L"a_2"}, WstrList{}) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_1"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -814,11 +842,11 @@ SECTION("Closed-shell spintrace CCSD") { // -1/2 * A * g * t2 const auto input = ex(rational{-1, 2}) * - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * ex(L"g", WstrList{L"i_2", L"i_3"}, WstrList{L"i_1", L"a_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_1", L"a_2"}, WstrList{L"i_2", L"i_3"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -836,11 +864,11 @@ SECTION("Closed-shell spintrace CCSD") { // -1/2 * A * g * t2 const auto input = ex(rational{-1, 2}) * - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * ex(L"g", WstrList{L"i_2", L"a_1"}, WstrList{L"a_2", L"a_3"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_2", L"a_3"}, WstrList{L"i_1", L"i_2"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -856,11 +884,12 @@ SECTION("Closed-shell spintrace CCSD") { { // A * f * t2 - const auto input = ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * - ex(L"f", WstrList{L"i_2"}, WstrList{L"a_2"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::antisymm); + const auto input = + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * + ex(L"f", WstrList{L"i_2"}, WstrList{L"a_2"}, WstrList{}, + Symmetry::antisymm) * + ex(L"t", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::antisymm); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -875,11 +904,11 @@ SECTION("Closed-shell spintrace CCSD") { { // A * g * t1 * t1 const auto input = - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * ex(L"g", WstrList{L"i_2", L"a_1"}, WstrList{L"a_2", L"a_3"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}); + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}) * + ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -896,11 +925,11 @@ SECTION("Closed-shell spintrace CCSD") { { // A * g * t2 * t2 const auto input = - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * ex(L"g", WstrList{L"i_2", L"i_3"}, WstrList{L"i_1", L"a_2"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_3"}); + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_3"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -916,11 +945,12 @@ SECTION("Closed-shell spintrace CCSD") { { // A * f * t1 * t1 - const auto input = ex(-1) * - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * - ex(L"f", WstrList{L"i_2"}, WstrList{L"a_2"}) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_2"}); + const auto input = + ex(-1) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * + ex(L"f", WstrList{L"i_2"}, WstrList{L"a_2"}, WstrList{}) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_2"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -936,12 +966,12 @@ SECTION("Closed-shell spintrace CCSD") { // -1/2 * A * g * t1 * t2 const auto input = ex(rational{-1, 2}) * - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * ex(L"g", WstrList{L"i_2", L"i_3"}, WstrList{L"a_2", L"a_3"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_2"}) * + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_2"}, WstrList{}) * ex(L"t", WstrList{L"a_2", L"a_3"}, WstrList{L"i_1", L"i_3"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -959,12 +989,12 @@ SECTION("Closed-shell spintrace CCSD") { // -1/2 * A * g * t1 * t2 const auto input = ex(rational{-1, 2}) * - ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * ex(L"g", WstrList{L"i_2", L"i_3"}, WstrList{L"a_2", L"a_3"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_1"}) * + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_1"}, WstrList{}) * ex(L"t", WstrList{L"a_1", L"a_3"}, WstrList{L"i_2", L"i_3"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -981,12 +1011,13 @@ SECTION("Closed-shell spintrace CCSD") { { // A * g * t1 * t2 const auto input = - ex(1) * ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}) * + ex(1) * + ex(L"A", WstrList{L"i_1"}, WstrList{L"a_1"}, WstrList{}) * ex(L"g", WstrList{L"i_2", L"i_3"}, WstrList{L"a_2", L"a_3"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}) * + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}) * ex(L"t", WstrList{L"a_1", L"a_3"}, WstrList{L"i_1", L"i_3"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -1006,12 +1037,13 @@ SECTION("Closed-shell spintrace CCSD") { { // - A * g * t1 * t1 * t1 - auto input = ex(-1) * - ex(L"g", WstrList{L"i_2", L"i_3"}, - WstrList{L"a_2", L"a_3"}, Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}) * - ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_1"}, WstrList{L"i_3"}); + auto input = + ex(-1) * + ex(L"g", WstrList{L"i_2", L"i_3"}, WstrList{L"a_2", L"a_3"}, + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_2"}, WstrList{}) * + ex(L"t", WstrList{L"a_3"}, WstrList{L"i_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_1"}, WstrList{L"i_3"}, WstrList{}); auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); @@ -1031,10 +1063,12 @@ SECTION("Closed-shell spintrace CCSDT terms") { auto input = ex(rational{1, 12}) * ex(L"A", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::antisymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}) * + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, + Symmetry::antisymm) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, WstrList{}) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_2", L"i_3", L"i_4"}, Symmetry::antisymm); + WstrList{L"i_2", L"i_3", L"i_4"}, WstrList{}, + Symmetry::antisymm); auto result = expand_A_op(input); REQUIRE(result->size() == 36); @@ -1056,9 +1090,10 @@ SECTION("Closed-shell spintrace CCSDT terms") { { // f * t3 auto input = - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, WstrList{}) * ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_2", L"i_3", L"i_4"}, Symmetry::antisymm); + WstrList{L"i_2", L"i_3", L"i_4"}, WstrList{}, + Symmetry::antisymm); auto result = expand_A_op(input); REQUIRE(result->size() == 2); @@ -1074,11 +1109,13 @@ SECTION("Closed-shell spintrace CCSDT terms") { auto input = ex(rational{-1, 4}) * ex(L"A", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::antisymm) * - ex(L"g", WstrList{L"i_4", L"a_1"}, WstrList{L"i_1", L"a_4"}, + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, Symmetry::antisymm) * + ex(L"g", WstrList{L"i_4", L"a_1"}, WstrList{L"i_1", L"a_4"}, + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_2", L"a_3", L"a_4"}, - WstrList{L"i_2", L"i_3", L"i_4"}, Symmetry::antisymm); + WstrList{L"i_2", L"i_3", L"i_4"}, WstrList{}, + Symmetry::antisymm); auto result = expand_A_op(input); REQUIRE(result->size() == 36); result = expand_antisymm(result); @@ -1090,9 +1127,10 @@ SECTION("Closed-shell spintrace CCSDT terms") { { // g * t3 auto input = ex(L"g", WstrList{L"i_4", L"a_1"}, WstrList{L"i_1", L"a_4"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_2", L"a_3", L"a_4"}, - WstrList{L"i_2", L"i_3", L"i_4"}, Symmetry::antisymm); + WstrList{L"i_2", L"i_3", L"i_4"}, WstrList{}, + Symmetry::antisymm); auto result = expand_A_op(input); REQUIRE(result->size() == 2); result = expand_antisymm(result); @@ -1105,10 +1143,11 @@ SECTION("Closed-shell spintrace CCSDT terms") { } SECTION("Merge P operators") { - auto P1 = Tensor(L"P", WstrList{L"i_1", L"i_2"}, {}); - auto P2 = Tensor(L"P", {}, WstrList{L"a_1", L"a_2"}); - auto P3 = Tensor(L"P", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}); - auto P4 = Tensor(L"P", {}, {}); + auto P1 = Tensor(L"P", WstrList{L"i_1", L"i_2"}, {}, {}, {}); + auto P2 = Tensor(L"P", {}, WstrList{L"a_1", L"a_2"}, {}, {}); + auto P3 = Tensor(L"P", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, {}); + auto P4 = Tensor(L"P", {}, {}, {}, {}); auto P12 = merge_tensors(P1, P2); auto P34 = merge_tensors(P3, P4); auto P11 = merge_tensors(P1, P1); @@ -1118,20 +1157,23 @@ SECTION("Merge P operators") { } SECTION("Permutation operators") { - auto A_12 = ex(L"A", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::antisymm); - auto A_23 = ex(L"A", WstrList{L"i_2", L"i_3"}, - WstrList{L"a_2", L"a_3"}, Symmetry::antisymm); + auto A_12 = + ex(L"A", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::antisymm); + auto A_23 = + ex(L"A", WstrList{L"i_2", L"i_3"}, WstrList{L"a_2", L"a_3"}, + WstrList{}, Symmetry::antisymm); auto A2 = ex(L"A", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); auto A3 = ex(L"A", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::antisymm); - auto A4 = - ex(L"A", WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, - WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, Symmetry::antisymm); + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, + Symmetry::antisymm); + auto A4 = ex(L"A", WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, + WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, WstrList{}, + Symmetry::antisymm); auto A5 = ex(L"A", WstrList{L"i_1", L"i_2", L"i_3", L"i_4", L"i_5"}, WstrList{L"a_1", L"a_2", L"a_3", L"a_4", L"a_5"}, - Symmetry::antisymm); + WstrList{}, Symmetry::antisymm); auto Avec2 = open_shell_A_op(A2->as()); auto P3 = open_shell_P_op_vector(A3->as()); @@ -1160,40 +1202,48 @@ SECTION("Permutation operators") { } SECTION("Relation in spin P operators") { - auto input = ex(L"g", WstrList{L"i_4", L"a_1"}, - WstrList{L"i_1", L"i_2"}, Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2", L"a_3"}, - WstrList{L"i_3", L"i_4"}, Symmetry::antisymm); - - auto P13_b = - ex(L"P", WstrList{}, WstrList{L"a_1", L"a_3"}, Symmetry::nonsymm); - auto P13_k = - ex(L"P", WstrList{L"i_1", L"i_3"}, WstrList{}, Symmetry::nonsymm); - auto P12_b = - ex(L"P", WstrList{}, WstrList{L"a_1", L"a_2"}, Symmetry::nonsymm); - auto P12_k = - ex(L"P", WstrList{L"i_1", L"i_2"}, WstrList{}, Symmetry::nonsymm); - - auto P23_b = - ex(L"P", WstrList{}, WstrList{L"a_2", L"a_3"}, Symmetry::nonsymm); - auto P23_k = - ex(L"P", WstrList{L"i_2", L"i_3"}, WstrList{}, Symmetry::nonsymm); - - auto P4_1313 = ex(L"P", WstrList{L"i_1", L"i_3"}, - WstrList{L"a_1", L"a_3"}, Symmetry::nonsymm); - auto P4_1323 = ex(L"P", WstrList{L"i_1", L"i_3"}, - WstrList{L"a_2", L"a_3"}, Symmetry::nonsymm); - auto P4_2313 = ex(L"P", WstrList{L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_3"}, Symmetry::nonsymm); - auto P4_2323 = ex(L"P", WstrList{L"i_2", L"i_3"}, - WstrList{L"a_2", L"a_3"}, Symmetry::nonsymm); - - auto P4_1212 = ex(L"P", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::nonsymm); - auto P4_1213 = ex(L"P", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_3"}, Symmetry::nonsymm); - auto P4_1312 = ex(L"P", WstrList{L"i_1", L"i_3"}, - WstrList{L"a_1", L"a_2"}, Symmetry::nonsymm); + auto input = + ex(L"g", WstrList{L"i_4", L"a_1"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2", L"a_3"}, WstrList{L"i_3", L"i_4"}, + WstrList{}, Symmetry::antisymm); + + auto P13_b = ex(L"P", WstrList{}, WstrList{L"a_1", L"a_3"}, + WstrList{}, Symmetry::nonsymm); + auto P13_k = ex(L"P", WstrList{L"i_1", L"i_3"}, WstrList{}, + WstrList{}, Symmetry::nonsymm); + auto P12_b = ex(L"P", WstrList{}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::nonsymm); + auto P12_k = ex(L"P", WstrList{L"i_1", L"i_2"}, WstrList{}, + WstrList{}, Symmetry::nonsymm); + + auto P23_b = ex(L"P", WstrList{}, WstrList{L"a_2", L"a_3"}, + WstrList{}, Symmetry::nonsymm); + auto P23_k = ex(L"P", WstrList{L"i_2", L"i_3"}, WstrList{}, + WstrList{}, Symmetry::nonsymm); + + auto P4_1313 = + ex(L"P", WstrList{L"i_1", L"i_3"}, WstrList{L"a_1", L"a_3"}, + WstrList{}, Symmetry::nonsymm); + auto P4_1323 = + ex(L"P", WstrList{L"i_1", L"i_3"}, WstrList{L"a_2", L"a_3"}, + WstrList{}, Symmetry::nonsymm); + auto P4_2313 = + ex(L"P", WstrList{L"i_2", L"i_3"}, WstrList{L"a_1", L"a_3"}, + WstrList{}, Symmetry::nonsymm); + auto P4_2323 = + ex(L"P", WstrList{L"i_2", L"i_3"}, WstrList{L"a_2", L"a_3"}, + WstrList{}, Symmetry::nonsymm); + + auto P4_1212 = + ex(L"P", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::nonsymm); + auto P4_1213 = + ex(L"P", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_3"}, + WstrList{}, Symmetry::nonsymm); + auto P4_1312 = + ex(L"P", WstrList{L"i_1", L"i_3"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::nonsymm); auto p_aab = ex(1) - P13_b - P23_b - P13_k - P23_k + P4_1313 + P4_1323 + P4_2313 + P4_2323; @@ -1207,12 +1257,15 @@ SECTION("Relation in spin P operators") { p6_result->visit(reset_idx_tags); simplify(p6_result); - auto A_12 = ex(L"A", WstrList{L"i_1", L"i_2"}, - WstrList{L"a_1", L"a_2"}, Symmetry::antisymm); - auto A_23 = ex(L"A", WstrList{L"i_2", L"i_3"}, - WstrList{L"a_2", L"a_3"}, Symmetry::antisymm); + auto A_12 = + ex(L"A", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::antisymm); + auto A_23 = + ex(L"A", WstrList{L"i_2", L"i_3"}, WstrList{L"a_2", L"a_3"}, + WstrList{}, Symmetry::antisymm); auto A3 = ex(L"A", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::antisymm); + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, + Symmetry::antisymm); p6_result = A_12 * p6_result; expand(p6_result); @@ -1242,18 +1295,21 @@ SECTION("Relation in spin P operators") { } SECTION("Expand P operator pair-wise") { - auto P1 = Tensor(L"P", WstrList{L"i_1", L"i_2"}, {}); - auto P2 = Tensor(L"P", WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, {}); - auto P3 = Tensor(L"P", {}, WstrList{L"a_1", L"a_2"}); - auto P4 = Tensor(L"P", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}); + auto P1 = Tensor(L"P", WstrList{L"i_1", L"i_2"}, {}, {}, {}); + auto P2 = Tensor(L"P", WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, {}, {}, {}); + auto P3 = Tensor(L"P", {}, WstrList{L"a_1", L"a_2"}, {}, {}); + auto P4 = + Tensor(L"P", WstrList{L"i_1", L"i_2"}, WstrList{L"a_1", L"a_2"}, {}, {}); auto P5 = Tensor(L"P", WstrList{L"i_1", L"i_2", L"i_3", L"i_4"}, - WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}); + WstrList{L"a_1", L"a_2", L"a_3", L"a_4"}, {}, {}); // g* t3 - auto input = ex(L"g", WstrList{L"i_4", L"a_1"}, - WstrList{L"i_1", L"a_4"}, Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2", L"a_3", L"a_4"}, - WstrList{L"i_2", L"i_3", L"i_4"}, Symmetry::antisymm); + auto input = + ex(L"g", WstrList{L"i_4", L"a_1"}, WstrList{L"i_1", L"a_4"}, + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2", L"a_3", L"a_4"}, + WstrList{L"i_2", L"i_3", L"i_4"}, WstrList{}, + Symmetry::antisymm); size_t n_p = 0; for (auto& P : {P1, P2, P3, P4, P5}) { @@ -1297,8 +1353,9 @@ SECTION("Expand P operator pair-wise") { ++n_p; } - auto input2 = ex(L"g", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::antisymm); + auto input2 = + ex(L"g", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::antisymm); auto term = ex(P2) * input2; expand(term); auto result = expand_P_op(term); @@ -1333,8 +1390,9 @@ SECTION("Open-shell spin-tracing") { // Tensor canonicalize { - auto t3 = ex(Tensor(L"t", {a3A, a2B, a2A}, {i1A, i2B, i3A})); - auto f = ex(Tensor(L"f", {a1A}, {a2A})); + auto t3 = + ex(Tensor(L"t", {a3A, a2B, a2A}, {i1A, i2B, i3A}, {})); + auto f = ex(Tensor(L"f", {a1A}, {a2A}, {})); auto ft3 = f * t3; ft3->canonicalize(); REQUIRE(to_latex(ft3) == @@ -1344,9 +1402,10 @@ SECTION("Open-shell spin-tracing") { // g { - auto input = ex(rational{1, 4}) * - ex(L"g", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, Symmetry::antisymm); + auto input = + ex(rational{1, 4}) * + ex(L"g", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::antisymm); auto result = open_shell_spintrace(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}); REQUIRE(result.size() == 3); @@ -1360,10 +1419,11 @@ SECTION("Open-shell spin-tracing") { // f_oo * t2 { - auto input = ex(rational{1, 2}) * - ex(L"f", WstrList{L"i_3"}, WstrList{L"i_1"}) * - ex(L"t", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_2", L"i_3"}, Symmetry::antisymm); + auto input = + ex(rational{1, 2}) * + ex(L"f", WstrList{L"i_3"}, WstrList{L"i_1"}, WstrList{}) * + ex(L"t", WstrList{L"a_1", L"a_2"}, WstrList{L"i_2", L"i_3"}, + WstrList{}, Symmetry::antisymm); auto result = open_shell_spintrace(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}); @@ -1384,8 +1444,9 @@ SECTION("Open-shell spin-tracing") { auto input = ex(rational{1, 2}) * ex(L"g", WstrList{L"i_3", L"a_1"}, WstrList{L"i_1", L"i_2"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_2"}, WstrList{L"i_3"}, Symmetry::nonsymm); + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_2"}, WstrList{L"i_3"}, WstrList{}, + Symmetry::nonsymm); auto result = open_shell_spintrace(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}); REQUIRE(result.size() == 3); @@ -1404,9 +1465,10 @@ SECTION("Open-shell spin-tracing") { { auto input = ex(rational{1, 12}) * - ex(L"f", WstrList{L"a_1"}, WstrList{L"a_4"}) * + ex(L"f", WstrList{L"a_1"}, WstrList{L"a_4"}, WstrList{}) * ex(L"t", WstrList{L"a_2", L"a_3", L"a_4"}, - WstrList{L"i_1", L"i_2", L"i_3"}, Symmetry::antisymm); + WstrList{L"i_1", L"i_2", L"i_3"}, WstrList{}, + Symmetry::antisymm); 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.size() == 4); @@ -1432,11 +1494,12 @@ SECTION("Open-shell spin-tracing") { // aab: g*t3 (CCSDT R3 4) { - auto A2_aab = Tensor(L"A", {i1A, i2A}, {a1A, a2A}, Symmetry::antisymm); - auto A2_abb = Tensor(L"A", {i2B, i3B}, {a2B, a3B}, Symmetry::antisymm); + auto A2_aab = Tensor(L"A", {i1A, i2A}, {a1A, a2A}, {}, Symmetry::antisymm); + auto A2_abb = Tensor(L"A", {i2B, i3B}, {a2B, a3B}, {}, Symmetry::antisymm); - auto g = Tensor(L"g", {i3A, i4A}, {i1A, i2A}, Symmetry::antisymm); - auto t3 = Tensor(L"t", {a1A, a2A, a3B}, {i3A, i4A, i3B}, Symmetry::nonsymm); + auto g = Tensor(L"g", {i3A, i4A}, {i1A, i2A}, {}, Symmetry::antisymm); + auto t3 = + Tensor(L"t", {a1A, a2A, a3B}, {i3A, i4A, i3B}, {}, Symmetry::nonsymm); auto input = ex(rational{1, 12}) * ex(A2_aab) * ex(g) * ex(t3); @@ -1448,8 +1511,8 @@ SECTION("Open-shell spin-tracing") { L"{{{\\frac{1}{3}}}{\\bar{g}^{{i↑_1}{i↑_2}}_{{i↑_3}{i↑_4}}}{t^{{" L"i↑_3}{i↑_4}{i↓_3}}_{{a↑_1}{a↑_2}{a↓_3}}}}"); - g = Tensor(L"g", {i4A, i5A}, {i1A, i2A}, Symmetry::antisymm); - t3 = Tensor(L"t", {a1A, a2A, a3B}, {i4A, i5A, i3B}, Symmetry::nonsymm); + g = Tensor(L"g", {i4A, i5A}, {i1A, i2A}, {}, Symmetry::antisymm); + t3 = Tensor(L"t", {a1A, a2A, a3B}, {i4A, i5A, i3B}, {}, Symmetry::nonsymm); input = ex(rational{1, 12}) * ex(A2_aab) * ex(g) * ex(t3); @@ -1467,18 +1530,19 @@ SECTION("Open-shell spin-tracing") { auto input = ex(rational{1, 8}) * ex(L"g", WstrList{L"i_4", L"i_5"}, WstrList{L"a_4", L"a_5"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_1", L"a_4"}, WstrList{L"i_1", L"i_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_2", L"a_3", L"a_5"}, - WstrList{L"i_3", L"i_4", L"i_5"}, Symmetry::antisymm); + WstrList{L"i_3", L"i_4", L"i_5"}, WstrList{}, + Symmetry::antisymm); 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); + Tensor(L"A", {i1A, i2A, i3A}, {a1A, a2A, a3A}, {}, Symmetry::antisymm); auto result2 = ex(A3_aaa) * result[0]; expand(result2); result2 = expand_A_op(result2); @@ -1488,7 +1552,7 @@ SECTION("Open-shell spin-tracing") { REQUIRE(result2->size() == 27); auto A3_bbb = - Tensor(L"A", {i1B, i2B, i3B}, {a1B, a2B, a3B}, Symmetry::antisymm); + Tensor(L"A", {i1B, i2B, i3B}, {a1B, a2B, a3B}, {}, Symmetry::antisymm); auto result3 = ex(A3_bbb) * result[3]; expand(result3); result3 = expand_A_op(result3); @@ -1503,31 +1567,33 @@ SECTION("Open-shell spin-tracing") { auto input = ex(rational{1, 8}) * ex(L"P", WstrList{L"i_1", L"i_3"}, WstrList{L"a_1", L"a_3"}, - Symmetry::nonsymm) * + WstrList{}, Symmetry::nonsymm) * ex(L"g", WstrList{L"i_4", L"i_5"}, WstrList{L"a_4", L"a_5"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_1", L"a_4"}, WstrList{L"i_1", L"i_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_2", L"a_3", L"a_5"}, - WstrList{L"i_3", L"i_4", L"i_5"}, Symmetry::antisymm) + + WstrList{L"i_3", L"i_4", L"i_5"}, WstrList{}, + Symmetry::antisymm) + ex(rational{1, 8}) * ex(L"P", WstrList{L"i_2", L"i_3"}, WstrList{L"a_2", L"a_3"}, - Symmetry::nonsymm) * + WstrList{}, Symmetry::nonsymm) * ex(L"g", WstrList{L"i_4", L"i_5"}, WstrList{L"a_4", L"a_5"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_1", L"a_4"}, WstrList{L"i_1", L"i_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_2", L"a_3", L"a_5"}, - WstrList{L"i_3", L"i_4", L"i_5"}, Symmetry::antisymm); + WstrList{L"i_3", L"i_4", L"i_5"}, WstrList{}, + Symmetry::antisymm); input = expand_P_op(input); input->visit(reset_idx_tags); auto result = open_shell_spintrace( input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}, {L"i_3", L"a_3"}}); - auto result_aab = - ex(Tensor(L"A", {i1A, i2A}, {a1A, a2A}, Symmetry::antisymm)) * - result[1]; + auto result_aab = ex(Tensor(L"A", {i1A, i2A}, {a1A, a2A}, {}, + Symmetry::antisymm)) * + result[1]; expand(result_aab); result_aab = expand_A_op(result_aab); result_aab->visit(reset_idx_tags); @@ -1538,13 +1604,15 @@ SECTION("Open-shell spin-tracing") { auto input2 = ex(rational{1, 8}) * ex(L"A", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::antisymm) * - ex(L"g", WstrList{L"i_4", L"i_5"}, WstrList{L"a_4", L"a_5"}, + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, Symmetry::antisymm) * + ex(L"g", WstrList{L"i_4", L"i_5"}, WstrList{L"a_4", L"a_5"}, + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_1", L"a_4"}, WstrList{L"i_1", L"i_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_2", L"a_3", L"a_5"}, - WstrList{L"i_3", L"i_4", L"i_5"}, Symmetry::antisymm); + WstrList{L"i_3", L"i_4", L"i_5"}, WstrList{}, + Symmetry::antisymm); auto result2 = open_shell_spintrace( input2, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}, {L"i_3", L"a_3"}}); diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index b541b2509..b1a0e4b09 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -31,44 +31,53 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(!t1); REQUIRE(t1.bra_rank() == 0); REQUIRE(t1.ket_rank() == 0); + REQUIRE(t1.auxiliary_rank() == 0); REQUIRE(t1.rank() == 0); REQUIRE(t1.symmetry() == Symmetry::invalid); REQUIRE(t1.braket_symmetry() == BraKetSymmetry::invalid); REQUIRE(t1.particle_symmetry() == ParticleSymmetry::invalid); REQUIRE(t1.label() == L""); - REQUIRE_NOTHROW(Tensor(L"F", {L"i_1"}, {L"i_1"})); - auto t2 = Tensor(L"F", {L"i_1"}, {L"i_1"}); + REQUIRE_NOTHROW(Tensor(L"F", {L"i_1"}, {L"i_1"}, {})); + auto t2 = Tensor(L"F", {L"i_1"}, {L"i_1"}, {}); REQUIRE(t2); REQUIRE(t2.bra_rank() == 1); REQUIRE(t2.ket_rank() == 1); + REQUIRE(t2.auxiliary_rank() == 0); REQUIRE(t2.rank() == 1); + REQUIRE(t2.indices().size() == 2); REQUIRE(t2.symmetry() == Symmetry::nonsymm); REQUIRE(t2.braket_symmetry() == BraKetSymmetry::conjugate); REQUIRE(t2.particle_symmetry() == ParticleSymmetry::symm); REQUIRE(t2.label() == L"F"); - REQUIRE_NOTHROW(Tensor(L"N", {L"i_1"}, {})); - auto t3 = Tensor(L"N", {L"i_1"}, {}); + REQUIRE_NOTHROW(Tensor(L"N", {L"i_1"}, {}, {L"a_1"})); + auto t3 = Tensor(L"N", {L"i_1"}, {}, {L"a_1"}); REQUIRE(t3); REQUIRE(t3.bra_rank() == 1); REQUIRE(t3.ket_rank() == 0); + REQUIRE(t3.auxiliary_rank() == 1); REQUIRE_THROWS(t3.rank()); + REQUIRE(t3.indices().size() == 2); REQUIRE(t3.symmetry() == Symmetry::nonsymm); REQUIRE(t3.braket_symmetry() == BraKetSymmetry::conjugate); REQUIRE(t3.particle_symmetry() == ParticleSymmetry::symm); REQUIRE(t3.label() == L"N"); REQUIRE_NOTHROW(Tensor(L"g", {Index{L"i_1"}, Index{L"i_2"}}, - {Index{L"i_3"}, Index{L"i_4"}}, Symmetry::nonsymm, - BraKetSymmetry::symm, ParticleSymmetry::nonsymm)); + {Index{L"i_3"}, Index{L"i_4"}}, {Index{L"i_5"}}, + Symmetry::nonsymm, BraKetSymmetry::symm, + ParticleSymmetry::nonsymm)); auto t4 = Tensor(L"g", {Index{L"i_1"}, Index{L"i_2"}}, - {Index{L"i_3"}, Index{L"i_4"}}, Symmetry::nonsymm, - BraKetSymmetry::symm, ParticleSymmetry::nonsymm); + {Index{L"i_3"}, Index{L"i_4"}}, {Index{L"i_5"}}, + Symmetry::nonsymm, BraKetSymmetry::symm, + ParticleSymmetry::nonsymm); REQUIRE(t4); REQUIRE(t4.bra_rank() == 2); REQUIRE(t4.ket_rank() == 2); + REQUIRE(t4.auxiliary_rank() == 1); REQUIRE(t4.rank() == 2); + REQUIRE(t4.indices().size() == 5); REQUIRE(t4.symmetry() == Symmetry::nonsymm); REQUIRE(t4.braket_symmetry() == BraKetSymmetry::symm); REQUIRE(t4.particle_symmetry() == ParticleSymmetry::nonsymm); @@ -77,7 +86,8 @@ TEST_CASE("Tensor", "[elements]") { SECTION("index transformation") { auto t = Tensor(L"g", {Index{L"i_1"}, Index{L"i_2"}}, - {Index{L"i_3"}, Index{L"i_4"}}, Symmetry::antisymm); + {Index{L"i_3"}, Index{L"i_4"}}, {Index{L"i_5"}}, + Symmetry::antisymm); std::map idxmap = {{Index{L"i_1"}, Index{L"i_2"}}, {Index{L"i_2"}, Index{L"i_1"}}}; REQUIRE(t.transform_indices(idxmap)); @@ -90,13 +100,15 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(!t.ket()[0].tag().has_value()); REQUIRE(!t.ket()[1].tag().has_value()); REQUIRE(t == Tensor(L"g", {Index{L"i_2"}, Index{L"i_1"}}, - {Index{L"i_3"}, Index{L"i_4"}}, Symmetry::antisymm)); + {Index{L"i_3"}, Index{L"i_4"}}, {Index{L"i_5"}}, + Symmetry::antisymm)); // tagged indices are protected, so no replacements the second goaround REQUIRE(!t.transform_indices(idxmap)); t.reset_tags(); REQUIRE(t.transform_indices(idxmap)); REQUIRE(t == Tensor(L"g", {Index{L"i_1"}, Index{L"i_2"}}, - {Index{L"i_3"}, Index{L"i_4"}}, Symmetry::antisymm)); + {Index{L"i_3"}, Index{L"i_4"}}, {Index{L"i_5"}}, + Symmetry::antisymm)); t.reset_tags(); REQUIRE(!t.bra()[0].tag().has_value()); REQUIRE(!t.bra()[1].tag().has_value()); @@ -105,22 +117,31 @@ TEST_CASE("Tensor", "[elements]") { } // SECTION("index transformation") SECTION("hash") { - auto t1 = Tensor(L"F", {L"i_1"}, {L"i_2"}); + auto t1 = Tensor(L"F", {L"i_1"}, {L"i_2"}, {}); size_t t1_hash; REQUIRE_NOTHROW(t1_hash = hash_value(t1)); - auto t2 = Tensor(L"F", {L"i_2"}, {L"i_1"}); + auto t2 = Tensor(L"F", {L"i_2"}, {L"i_1"}, {}); size_t t2_hash; REQUIRE_NOTHROW(t2_hash = hash_value(t2)); REQUIRE(t1_hash != t2_hash); + auto t3 = Tensor(L"F", {L"i_2"}, {L"i_1"}, {L"i_3"}); + size_t t3_hash; + REQUIRE_NOTHROW(t3_hash = hash_value(t3)); + REQUIRE(t2_hash != t3_hash); + REQUIRE(t1_hash != t3_hash); + } // SECTION("hash") SECTION("latex") { - auto t1 = Tensor(L"F", {L"i_1"}, {L"i_2"}); + auto t1 = Tensor(L"F", {L"i_1"}, {L"i_2"}, {}); REQUIRE(to_latex(t1) == L"{F^{{i_2}}_{{i_1}}}"); - auto h1 = ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}) * + auto t2 = Tensor(L"F", {L"i_1"}, {L"i_2"}, {L"i_3"}); + REQUIRE(to_latex(t2) == L"{F^{{i_2}}_{{i_1}}({i_3})}"); + + auto h1 = ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}, WstrList{}) * ex(WstrList{L"i_1"}, WstrList{L"i_2"}); REQUIRE(to_latex(h1) == L"{{F^{{i_2}}_{{i_1}}}{\\tilde{a}^{{i_1}}_{{i_2}}}}"); @@ -128,18 +149,18 @@ TEST_CASE("Tensor", "[elements]") { } // SECTION("latex") SECTION("adjoint") { - auto f1 = Tensor(L"F", {L"i_1", L"i_2"}, {L"i_3", L"i_4"}); + auto f1 = Tensor(L"F", {L"i_1", L"i_2"}, {L"i_3", L"i_4"}, {}); REQUIRE_NOTHROW(f1.adjoint()); REQUIRE(to_latex(f1) == L"{F^{{i_1}{i_2}}_{{i_3}{i_4}}}"); - auto t1 = Tensor(L"t", {L"a_1"}, {L"i_1"}, Symmetry::nonsymm, + auto t1 = Tensor(L"t", {L"a_1"}, {L"i_1"}, {}, Symmetry::nonsymm, BraKetSymmetry::nonsymm); REQUIRE_NOTHROW(t1.adjoint()); REQUIRE(to_latex(t1) == L"{t⁺^{{a_1}}_{{i_1}}}"); t1.adjoint(); REQUIRE(to_latex(t1) == L"{t^{{i_1}}_{{a_1}}}"); - auto h1 = ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}) * + auto h1 = ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}, WstrList{}) * ex(WstrList{L"i_1"}, WstrList{L"i_2"}); h1 = adjoint(h1); REQUIRE(to_latex(h1) == diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index f20a0761f..fabb7ba4d 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -36,6 +36,8 @@ #include +// TODO: Add test cases with auxiliary indices + TEST_CASE("TensorNetwork", "[elements]") { using namespace sequant; @@ -43,8 +45,10 @@ TEST_CASE("TensorNetwork", "[elements]") { SECTION("constructors") { { // with Tensors - auto t1 = ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}); - auto t2 = ex(L"t", WstrList{L"i_1"}, WstrList{L"i_2"}); + auto t1 = + ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}, WstrList{}); + auto t2 = + ex(L"t", WstrList{L"i_1"}, WstrList{L"i_2"}, WstrList{}); auto t1_x_t2 = t1 * t2; REQUIRE_NOTHROW(TensorNetwork(*t1_x_t2)); @@ -70,7 +74,8 @@ TEST_CASE("TensorNetwork", "[elements]") { SECTION("accessors") { { constexpr const auto V = Vacuum::SingleProduct; - auto t1 = ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}); + auto t1 = + ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}, WstrList{}); auto t2 = ex(WstrList{L"i_1"}, WstrList{L"i_3"}, V); auto t1_x_t2 = t1 * t2; REQUIRE_NOTHROW(TensorNetwork(*t1_x_t2)); @@ -104,7 +109,8 @@ TEST_CASE("TensorNetwork", "[elements]") { { // with no external indices, hence no named indices whatsoever Index::reset_tmp_index(); constexpr const auto V = Vacuum::SingleProduct; - auto t1 = ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}); + auto t1 = + ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}, WstrList{}); auto t2 = ex(WstrList{L"i_1"}, WstrList{L"i_2"}, V); auto t1_x_t2 = t1 * t2; TensorNetwork tn(*t1_x_t2); @@ -128,7 +134,8 @@ TEST_CASE("TensorNetwork", "[elements]") { { Index::reset_tmp_index(); constexpr const auto V = Vacuum::SingleProduct; - auto t1 = ex(L"F", WstrList{L"i_2"}, WstrList{L"i_17"}); + auto t1 = + ex(L"F", WstrList{L"i_2"}, WstrList{L"i_17"}, WstrList{}); auto t2 = ex(WstrList{L"i_2"}, WstrList{L"i_3"}, V); auto t1_x_t2 = t1 * t2; @@ -376,12 +383,15 @@ TEST_CASE("TensorNetwork", "[elements]") { if (false) { Index::reset_tmp_index(); // TN1 from manuscript - auto g = ex(L"g", WstrList{L"i_3", L"i_4"}, - WstrList{L"a_3", L"a_4"}, Symmetry::antisymm); - auto ta = ex(L"t", WstrList{L"a_1", L"a_3"}, - WstrList{L"i_1", L"i_2"}, Symmetry::antisymm); - auto tb = ex(L"t", WstrList{L"a_2", L"a_4"}, - WstrList{L"i_3", L"i_4"}, Symmetry::antisymm); + auto g = + ex(L"g", WstrList{L"i_3", L"i_4"}, WstrList{L"a_3", L"a_4"}, + WstrList{}, Symmetry::antisymm); + auto ta = + ex(L"t", WstrList{L"a_1", L"a_3"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::antisymm); + auto tb = + ex(L"t", WstrList{L"a_2", L"a_4"}, WstrList{L"i_3", L"i_4"}, + WstrList{}, Symmetry::antisymm); auto tmp = g * ta * tb; // std::wcout << "TN1 = " << to_latex(tmp) << std::endl; @@ -505,7 +515,7 @@ TEST_CASE("TensorNetwork", "[elements]") { covariant_indices | ranges::views::chunk(N / n) | ranges::views::transform([&](const auto& idxs) { return ex( - L"u", idxs, std::vector{}, + L"u", idxs, std::vector{}, std::vector{}, (testcase == 3 ? Symmetry::nonsymm : ((n == 1) ? Symmetry::symm : Symmetry::nonsymm))); @@ -514,7 +524,7 @@ TEST_CASE("TensorNetwork", "[elements]") { CHECK(utensors.size() == static_cast(n)); auto dtensors = contravariant_indices | ranges::views::chunk(N) | ranges::views::transform([&](const auto& idxs) { - return ex(L"d", std::vector{}, + return ex(L"d", std::vector{}, std::vector{}, idxs, Symmetry::nonsymm); }) | ranges::to_vector; diff --git a/tests/unit/test_utilities.cpp b/tests/unit/test_utilities.cpp new file mode 100644 index 000000000..92962aeea --- /dev/null +++ b/tests/unit/test_utilities.cpp @@ -0,0 +1,55 @@ +#include "catch.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +sequant::Tensor parse_tensor(std::wstring_view str) { + return sequant::parse_expr(str)->as(); +} + +TEST_CASE("TEST GET_UNCONCTRACTED_INDICES", "[utilities]") { + using namespace sequant; + + SECTION("dot_product") { + std::vector> inputs = { + {L"t{}", L"t{}"}, + {L"t{i1}", L"t{;i1}"}, + {L"t{;i1}", L"t{i1}"}, + {L"t{i1;a1}", L"t{a1;i1}"}, + {L"t{i1;a1;x1}", L"t{a1;i1;x1}"}, + {L"t{;i1;x1}", L"t{i1;;x1}"}, + {L"t{i1;;x1}", L"t{;i1;x1}"}, + {L"t{;;x1}", L"t{;;x1}"}, + }; + + for (auto [left, right] : inputs) { + auto [bra, ket, aux] = + get_uncontracted_indices(parse_tensor(left), parse_tensor(right)); + + REQUIRE(bra.size() == 0); + REQUIRE(ket.size() == 0); + REQUIRE(aux.size() == 0); + } + } + + SECTION("partial_contraction") { + auto [bra, ket, aux] = get_uncontracted_indices>( + parse_tensor(L"t{i1,i2;a1,a2;x1,x2}"), parse_tensor(L"t{a1;i2;x2}") + ); + + std::vector expectedBra = {Index(L"i_1")}; + std::vector expectedKet = {Index(L"a_2")}; + std::vector expectedAux = {Index(L"x_1")}; + + REQUIRE_THAT(bra, Catch::UnorderedEquals(expectedBra)); + REQUIRE_THAT(ket, Catch::UnorderedEquals(expectedKet)); + REQUIRE_THAT(aux, Catch::UnorderedEquals(expectedAux)); + } +} diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index cc79a8d06..54b236376 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -630,7 +630,7 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { ex(WstrList{L"i_1"}, WstrList{L"a_3", L"a_4"}) * (ex(rational{1, 4}) * ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"})) * ex(WstrList{L"a_2"}, WstrList{}); auto wick = FWickTheorem{input}; @@ -819,65 +819,65 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { constexpr Vacuum V = Vacuum::SingleProduct; // default vacuum is already spin-orbital Fermi vacuum - auto switch_to_spinfree_context = detail::NoDiscard([&]() { - auto context_sf = get_default_context(); - context_sf.set(SPBasis::spinfree); - return set_scoped_default_context(context_sf); - }); - - // 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"})}); - auto wick = FWickTheorem{opseq}; - auto wick_result = wick.compute(); - REQUIRE(wick_result->is()); - REQUIRE(wick_result->size() == 4); - - // multiply tensor factors and expand - auto wick_result_2 = - ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}, - Symmetry::antisymm) * - ex(L"t", WstrList{L"a_4", L"a_5"}, WstrList{L"i_4", L"i_5"}, - Symmetry::antisymm) * - wick_result; - expand(wick_result_2); - REQUIRE(to_latex(wick_result_2) == - L"{ " - L"\\bigl({{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{t}^{{i_4}{i_" - L"5}}_{{a_4}{a_" - L"5}}}{s^{{p_1}}_{{i_5}}}{s^{{p_2}}_{{i_4}}}{s^{{a_4}}_{{p_4}}}{" - L"s^{{a_5}}_{{p_3}}}} - {" - L"{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{t}^{{i_4}{i_5}}_{{a_" - L"4}{a_5}}}{s^{{" - L"p_1}}_{{i_5}}}{s^{{p_2}}_{{i_4}}}{s^{{a_5}}_{{p_4}}}{s^{{a_4}}_" - L"{{p_3}}}} - {" - L"{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{t}^{{i_4}{i_5}}_{{a_" - L"4}{a_5}}}{s^{{" - L"p_1}}_{{i_4}}}{s^{{p_2}}_{{i_5}}}{s^{{a_4}}_{{p_4}}}{s^{{a_5}}_" - L"{{p_3}}}} + " - L"{{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{t}^{{i_4}{i_5}}_{{" - L"a_4}{a_5}}}{s^{" - L"{p_1}}_{{i_4}}}{s^{{p_2}}_{{i_5}}}{s^{{a_5}}_{{p_4}}}{s^{{a_4}}" - L"_{{p_3}}}}\\bigr) }"); - wick.reduce(wick_result_2); - rapid_simplify(wick_result_2); - TensorCanonicalizer::register_instance( - std::make_shared()); - canonicalize(wick_result_2); - 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; - 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_" - L"1}{a_2}}}}"); - - // spin-free case will produce 2 terms - { - auto raii_tmp = switch_to_spinfree_context(); + auto switch_to_spinfree_context = detail::NoDiscard([&]() { + auto context_sf = get_default_context(); + context_sf.set(SPBasis::spinfree); + return set_scoped_default_context(context_sf); + }); + + // 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"})}); + auto wick = FWickTheorem{opseq}; + auto wick_result = wick.compute(); + REQUIRE(wick_result->is()); + REQUIRE(wick_result->size() == 4); + + // multiply tensor factors and expand + auto wick_result_2 = + ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}, + WstrList{}, Symmetry::antisymm) * + ex(L"t", WstrList{L"a_4", L"a_5"}, WstrList{L"i_4", L"i_5"}, + WstrList{}, Symmetry::antisymm) * + wick_result; + expand(wick_result_2); + REQUIRE(to_latex(wick_result_2) == + L"{ " + L"\\bigl({{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{t}^{{i_4}{i_" + L"5}}_{{a_4}{a_" + L"5}}}{s^{{p_1}}_{{i_5}}}{s^{{p_2}}_{{i_4}}}{s^{{a_4}}_{{p_4}}}{" + L"s^{{a_5}}_{{p_3}}}} - {" + L"{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{t}^{{i_4}{i_5}}_{{a_" + L"4}{a_5}}}{s^{{" + L"p_1}}_{{i_5}}}{s^{{p_2}}_{{i_4}}}{s^{{a_5}}_{{p_4}}}{s^{{a_4}}_" + L"{{p_3}}}} - {" + L"{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{t}^{{i_4}{i_5}}_{{a_" + L"4}{a_5}}}{s^{{" + L"p_1}}_{{i_4}}}{s^{{p_2}}_{{i_5}}}{s^{{a_4}}_{{p_4}}}{s^{{a_5}}_" + L"{{p_3}}}} + " + L"{{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{t}^{{i_4}{i_5}}_{{" + L"a_4}{a_5}}}{s^{" + L"{p_1}}_{{i_4}}}{s^{{p_2}}_{{i_5}}}{s^{{a_5}}_{{p_4}}}{s^{{a_4}}" + L"_{{p_3}}}}\\bigr) }"); + wick.reduce(wick_result_2); + rapid_simplify(wick_result_2); + TensorCanonicalizer::register_instance( + std::make_shared()); + canonicalize(wick_result_2); + 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; + 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_" + L"1}{a_2}}}}"); + + // spin-free case will produce 2 terms + { + auto raii_tmp = switch_to_spinfree_context(); auto wick = FWickTheorem{opseq}; auto wick_result = wick.compute(); @@ -887,9 +887,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // multiply tensor factors and expand auto wick_result_2 = ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}, - Symmetry::nonsymm) * + WstrList{}, Symmetry::nonsymm) * ex(L"t", WstrList{L"a_4", L"a_5"}, WstrList{L"i_4", L"i_5"}, - Symmetry::nonsymm) * + WstrList{}, Symmetry::nonsymm) * wick_result; expand(wick_result_2); REQUIRE(wick_result_2->size() == 4); // still 4 terms @@ -940,11 +940,11 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { // multiply tensor factors and expand auto wick_result_2 = ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_3", L"p_4"}, Symmetry::antisymm) * + WstrList{L"p_3", L"p_4"}, WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_4"}, WstrList{L"i_4"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(L"t", WstrList{L"a_5"}, WstrList{L"i_5"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * wick_result; expand(wick_result_2); wick.reduce(wick_result_2); @@ -987,13 +987,13 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { ex( L"A", IndexList{L"i_1", L"i_2"}, IndexList{{L"a_1", {L"i_1", L"i_2"}}, {L"a_2", {L"i_1", L"i_2"}}}, - Symmetry::antisymm) * + IndexList{}, Symmetry::antisymm) * ex(L"f", WstrList{L"p_1"}, WstrList{L"p_2"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex( L"t", IndexList{{L"a_3", {L"i_3", L"i_4"}}, {L"a_4", {L"i_3", L"i_4"}}}, - IndexList{L"i_3", L"i_4"}, Symmetry::antisymm) * + IndexList{L"i_3", L"i_4"}, IndexList{}, Symmetry::antisymm) * wick_result; expand(wick_result_2); wick.reduce(wick_result_2); @@ -1066,17 +1066,17 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { ex(L"A", IndexList{L"i_1", L"i_2"}, IndexList{{L"a_1", {L"i_1", L"i_2"}}, {L"a_2", {L"i_1", L"i_2"}}}, - Symmetry::antisymm) * + IndexList{}, Symmetry::antisymm) * ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_3", L"p_4"}, Symmetry::antisymm) * + WstrList{L"p_3", L"p_4"}, IndexList{}, Symmetry::antisymm) * ex(L"t", IndexList{{L"a_3", {L"i_3", L"i_4"}}, {L"a_4", {L"i_3", L"i_4"}}}, - IndexList{L"i_3", L"i_4"}, Symmetry::antisymm) * + IndexList{L"i_3", L"i_4"}, IndexList{}, Symmetry::antisymm) * ex(L"t", IndexList{{L"a_5", {L"i_5", L"i_6"}}, {L"a_6", {L"i_5", L"i_6"}}}, - IndexList{L"i_5", L"i_6"}, Symmetry::antisymm) * + IndexList{L"i_5", L"i_6"}, IndexList{}, Symmetry::antisymm) * wick_result; expand(wick_result_2); wick.reduce(wick_result_2); @@ -1104,23 +1104,23 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { auto P3 = ex(rational{1, 36}) * ex(L"A", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, Symmetry::antisymm) * + WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, Symmetry::antisymm) * ex(WstrList{L"i_1", L"i_2", L"i_3"}, WstrList{L"a_1", L"a_2", L"a_3"}); auto H2 = ex(rational{1, 4}) * ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}); auto T2 = ex(rational{1, 4}) * ex(L"t", WstrList{L"a_4", L"a_5"}, WstrList{L"i_4", L"i_5"}, - Symmetry::antisymm) * + WstrList{}, Symmetry::antisymm) * ex(WstrList{L"a_4", L"a_5"}, WstrList{L"i_4", L"i_5"}); auto T3 = ex(rational{1, 36}) * ex(L"t", WstrList{L"a_6", L"a_7", L"a_8"}, - WstrList{L"i_6", L"i_7", L"i_8"}, Symmetry::antisymm) * + WstrList{L"i_6", L"i_7", L"i_8"}, WstrList{}, Symmetry::antisymm) * ex(WstrList{L"a_6", L"a_7", L"a_8"}, WstrList{L"i_6", L"i_7", L"i_8"}); FWickTheorem wick{P3 * H2 * T2 * T3}; From b021d1f4b0907176ba68b9b146f4b2476dac9036 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 25 Jan 2024 16:55:17 +0100 Subject: [PATCH 04/85] Make comp impl more readable --- SeQuant/core/index.hpp | 29 +++++++++++++----------- SeQuant/core/tensor.hpp | 49 ++++++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/SeQuant/core/index.hpp b/SeQuant/core/index.hpp index e248a4024..908494c9b 100644 --- a/SeQuant/core/index.hpp +++ b/SeQuant/core/index.hpp @@ -705,26 +705,29 @@ class Index : public Taggable { assert(i1.space().attr().is_valid()); assert(i2.space().attr().is_valid()); - auto i1_Q = i1.space().qns(); - auto i2_Q = i2.space().qns(); - const bool have_qns = - i1_Q != IndexSpace::nullqns || i2_Q != IndexSpace::nullqns; - auto compare_space = [&i1, &i2]() { - if (i1.space() == i2.space()) { - if (i1.label() == i2.label()) { - return i1.proto_indices() < i2.proto_indices(); - } else { - return i1.label() < i2.label(); - } - } else { + if (i1.space() != i2.space()) { return i1.space() < i2.space(); } + + if (i1.label() != i2.label()) { + // TODO: This won't yield the desired result for e.g. i_2 < i_13 + return i1.label() < i2.label(); + } + + return i1.proto_indices() < i2.proto_indices(); }; - if (have_qns || (i1_Q != i2_Q)) { + 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(); } + const bool have_tags = i1.tag().has_value() && i2.tag().has_value(); if (!have_tags || i1.tag() == i2.tag()) { diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index 6c6706c87..280d7b60e 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -344,32 +344,35 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { } bool static_less_than(const Expr &that) const override { - const auto &that_cast = static_cast(that); if (this == &that) return false; - if (this->label() == that_cast.label()) { - if (this->bra_rank() == that_cast.bra_rank()) { - if (this->ket_rank() == that_cast.ket_rank()) { - // v1: compare hashes only - // return Expr::static_less_than(that); - // v2: compare fully - if (this->bra_hash_value() == that_cast.bra_hash_value()) { - return std::lexicographical_compare( - this->ket().begin(), this->ket().end(), that_cast.ket().begin(), - that_cast.ket().end()); - } else { - return std::lexicographical_compare( - this->bra().begin(), this->bra().end(), that_cast.bra().begin(), - that_cast.bra().end()); - } - } else { - return this->ket_rank() < that_cast.ket_rank(); - } - } else { - return this->bra_rank() < that_cast.bra_rank(); - } - } else { + + const auto &that_cast = static_cast(that); + if (this->label() != that_cast.label()) { return this->label() < that_cast.label(); } + + if (this->bra_rank() != that_cast.bra_rank()) { + return this->bra_rank() < that_cast.bra_rank(); + } + + if (this->ket_rank() != that_cast.ket_rank()) { + return this->ket_rank() < that_cast.ket_rank(); + } + + // TODO: account for aux indices + + // v1: compare hashes only + // return Expr::static_less_than(that); + // v2: compare fully + if (this->bra_hash_value() != that_cast.bra_hash_value()) { + return std::lexicographical_compare( + this->bra().begin(), this->bra().end(), that_cast.bra().begin(), + that_cast.bra().end()); + } + + return std::lexicographical_compare(this->ket().begin(), this->ket().end(), + that_cast.ket().begin(), + that_cast.ket().end()); } // these implement the AbstractTensor interface From e6d86035ecfcab8739d8c0d2fcb3f8ba179e2e55 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 26 Jan 2024 16:16:44 +0100 Subject: [PATCH 05/85] tensor canonicalizer fix for aux indices --- SeQuant/core/tensor_canonicalizer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp index bc6fd5054..3877dbfdf 100644 --- a/SeQuant/core/tensor_canonicalizer.hpp +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -154,7 +154,7 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { const auto _rank = std::min(_bra_rank, _ket_rank); // nothing to do for rank-1 tensors - if (_bra_rank == 1 && _ket_rank == 1 && _aux_rank) return nullptr; + if (_bra_rank == 1 && _ket_rank == 1 && _aux_rank == 1) return nullptr; using ranges::begin; using ranges::end; From 9f02e7dd61a0aba56892063d5071deff6d517ba1 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 26 Jan 2024 16:42:19 +0100 Subject: [PATCH 06/85] Add Wick benchmark --- CMakeLists.txt | 2 + benchmarks/CMakeLists.txt | 18 ++++++++ benchmarks/wick_benchmark.cpp | 83 +++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 benchmarks/CMakeLists.txt create mode 100644 benchmarks/wick_benchmark.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c1d34123..d43f6fad3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -569,6 +569,8 @@ else (BUILD_TESTING) add_custom_target_subproject(sequant check USES_TERMINAL COMMAND echo "WARNING: SeQuant testing disabled. To enable, give -DBUILD_TESTING=ON to cmake") endif (BUILD_TESTING) +add_subdirectory(benchmarks) + ####### Python ######## if (SEQUANT_PYTHON) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 000000000..772aa9d10 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,18 @@ +include(FetchContent) + +FetchContent_Declare( + googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark + GIT_TAG v1.8.3 + GIT_SHALLOW ON +) + +set(BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "") +set(BENCHMARK_ENABLE_LTO OFF CACHE INTERNAL "${CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE}") +set(BENCHMARK_INSTALL OFF CACHE INTERNAL "") +set(BENCHMARK_INSTALL_DOCS OFF CACHE INTERNAL "") + +FetchContent_MakeAvailable(googlebenchmark) + +add_executable(wick_benchmark wick_benchmark.cpp) +target_link_libraries(wick_benchmark PRIVATE benchmark::benchmark SeQuant::SeQuant) diff --git a/benchmarks/wick_benchmark.cpp b/benchmarks/wick_benchmark.cpp new file mode 100644 index 000000000..68d6e07ff --- /dev/null +++ b/benchmarks/wick_benchmark.cpp @@ -0,0 +1,83 @@ +#include +#include "SeQuant/core/op.hpp" +#include "SeQuant/core/runtime.hpp" +#include "SeQuant/core/space.hpp" +#include "SeQuant/core/tensor.hpp" +#include "SeQuant/domain/mbpt/convention.hpp" + +#include + +#include + +using namespace sequant; + +auto do_wick(bool use_nop_partitions, bool use_op_partitions) { + constexpr Vacuum V = Vacuum::SingleProduct; + + 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"})}); + auto wick = FWickTheorem{opseq}; + wick.set_nop_connections({{1, 2}, {1, 3}}).use_topology(true); + + if (use_nop_partitions) wick.set_nop_partitions({{2, 3}}); + if (use_op_partitions) + wick.set_op_partitions( + {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}}); + auto wick_result = wick.compute(); + + // multiply tensor factors and expand + auto wick_result_2 = + ex(rational{1, 256}) * + ex( + L"A", IndexList{L"i_1", L"i_2"}, + IndexList{{L"a_1", {L"i_1", L"i_2"}}, {L"a_2", {L"i_1", L"i_2"}}}, + IndexList{}, Symmetry::antisymm) * + ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_3", L"p_4"}, + WstrList{}, Symmetry::antisymm) * + ex( + L"t", + IndexList{{L"a_3", {L"i_3", L"i_4"}}, {L"a_4", {L"i_3", L"i_4"}}}, + IndexList{L"i_3", L"i_4"}, IndexList{}, Symmetry::antisymm) * + ex( + L"t", + IndexList{{L"a_5", {L"i_5", L"i_6"}}, {L"a_6", {L"i_5", L"i_6"}}}, + IndexList{L"i_5", L"i_6"}, IndexList{}, Symmetry::antisymm) * + wick_result; + expand(wick_result_2); + wick.reduce(wick_result_2); + rapid_simplify(wick_result_2); + + return std::make_pair(wick_result, wick_result_2); +} + +static void BM_wick(benchmark::State &state) { + for (auto _ : state) { + benchmark::DoNotOptimize(do_wick(state.range(0), state.range(1))); + } +} + +BENCHMARK(BM_wick)->ArgsProduct({{0, 1}, {0, 1}}); + +int main(int argc, char **argv) { + 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(); + TensorCanonicalizer::register_instance( + std::make_shared()); + set_num_threads(1); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); +} From ae9ac02e9f3f7bd09a5a7bb178fbb86972a0920d Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 29 Jan 2024 08:30:09 +0100 Subject: [PATCH 07/85] Generalize tensor canonicalization to work with aux indices --- SeQuant/core/tensor_network.cpp | 1360 ++++++++++++++++------------ SeQuant/core/tensor_network.hpp | 262 +++--- tests/unit/test_tensor_network.cpp | 674 +++++++++----- 3 files changed, 1377 insertions(+), 919 deletions(-) diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 6999d0e9a..71bb8aca7 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -33,11 +35,438 @@ namespace sequant { +bool tensors_commute(const AbstractTensor &lhs, const AbstractTensor &rhs) { + // tensors commute if their colors are different or either one of them + // is a c-number + return !(color(lhs) == color(rhs) && !is_cnumber(lhs) && !is_cnumber(rhs)); +} + +struct TensorBlockCompare { + bool operator()(const AbstractTensor &lhs, const AbstractTensor &rhs) const { + if (label(lhs) != label(rhs)) { + return label(lhs) < label(rhs); + } + + if (bra_rank(lhs) != bra_rank(rhs)) { + return bra_rank(lhs) < bra_rank(rhs); + } + if (ket_rank(lhs) != ket_rank(rhs)) { + return ket_rank(lhs) < ket_rank(rhs); + } + if (auxiliary_rank(lhs) != auxiliary_rank(rhs)) { + return auxiliary_rank(lhs) < auxiliary_rank(rhs); + } + + auto lhs_indices = indices(lhs); + auto rhs_indices = indices(rhs); + + for (auto lhs_it = lhs_indices.begin(), rhs_it = rhs_indices.begin(); + lhs_it != lhs_indices.end() && rhs_it != rhs_indices.end(); + ++lhs_it, ++rhs_it) { + if (lhs_it->space() != rhs_it->space()) { + return lhs_it->space() < rhs_it->space(); + } + } + + // Tensors are identical + return false; + } +}; + +/// Compares tensors based on their label and orders them according to the order +/// of the given cardinal tensor labels. If two tensors can't be discriminated +/// via their label, they are compared based on their tensor block (the spaces +/// of their indices). If this doesn't discriminate the tensors, they are +/// considered equal (note: explicit indexing is NOT compared) +template +struct CanonicalTensorCompare { + const CardinalLabels &labels; + + CanonicalTensorCompare(const CardinalLabels &labels) : labels(labels) {} + + bool operator()(const AbstractTensorPtr &lhs_ptr, + const AbstractTensorPtr &rhs_ptr) const { + assert(lhs_ptr); + assert(rhs_ptr); + const AbstractTensor &lhs = *lhs_ptr; + const AbstractTensor &rhs = *rhs_ptr; + + if (!tensors_commute(lhs, rhs)) { + return false; + } + + const auto get_label = [](const auto &t) { + if (label(t).back() == adjoint_label) { + // grab base label if adjoint label is present + return label(t).substr(0, label(t).size() - 1); + } + return label(t); + }; + + const auto lhs_it = std::find(labels.begin(), labels.end(), get_label(lhs)); + const auto rhs_it = std::find(labels.begin(), labels.end(), get_label(rhs)); + + if (lhs_it != rhs_it) { + // At least one of the tensors is a cardinal one + // -> Order by the occurrence in the cardinal label list + return std::distance(labels.begin(), lhs_it) < + std::distance(labels.begin(), rhs_it); + } + + // Either both are the same cardinal tensor or none is a cardinal tensor + // -> Discriminate via tensor block comparison + TensorBlockCompare cmp; + return cmp(lhs, rhs); + } +}; + +TensorNetwork::Vertex::Vertex(Origin origin, std::size_t terminal_idx, + std::size_t index_slot, Symmetry terminal_symm) + : origin(origin), + terminal_idx(terminal_idx), + index_slot(index_slot), + terminal_symm(terminal_symm) {} + +TensorNetwork::Origin TensorNetwork::Vertex::getOrigin() const { + return origin; +} + +std::size_t TensorNetwork::Vertex::getTerminalIndex() const { + return terminal_idx; +} + +std::size_t TensorNetwork::Vertex::getIndexSlot() const { return index_slot; } + +Symmetry TensorNetwork::Vertex::getTerminalSymmetry() const { + return terminal_symm; +} + +bool TensorNetwork::Vertex::operator<(const Vertex &rhs) const { + if (terminal_idx != rhs.terminal_idx) { + return terminal_idx < rhs.terminal_idx; + } + + // Both vertices belong to same tensor -> they must have same symmetry + assert(terminal_symm == rhs.terminal_symm); + + // We only take the index slot into account for non-symmetric tensors + if (terminal_symm == Symmetry::nonsymm && index_slot != rhs.index_slot) { + return index_slot < rhs.index_slot; + } + + // Note: The ordering of index groups must be consistent with the canonical + // (Abstract)Tensor::operator< + return origin < rhs.origin; +} + +bool TensorNetwork::Vertex::operator==(const Vertex &rhs) const { + // Slot position is only taken into account for non_symmetric tensors + const std::size_t lhs_slot = + (terminal_symm == Symmetry::nonsymm) * index_slot; + const std::size_t rhs_slot = + (rhs.terminal_symm == Symmetry::nonsymm) * rhs.index_slot; + + assert(terminal_idx != rhs.terminal_idx || + terminal_symm == rhs.terminal_symm); + + return terminal_idx == rhs.terminal_idx && lhs_slot == rhs_slot && + origin == rhs.origin; +} + +std::size_t TensorNetwork::Graph::vertex_to_index_idx( + std::size_t vertex) const { + assert(vertex_types.at(vertex) == VertexType::Index); + + std::size_t index_idx = 0; + for (std::size_t i = 0; i <= vertex; ++i) { + if (vertex_types[i] == VertexType::Index) { + ++index_idx; + } + } + + assert(index_idx > 0); + + return index_idx - 1; +} + +std::size_t TensorNetwork::Graph::vertex_to_tensor_idx( + std::size_t vertex) const { + assert(vertex_types.at(vertex) == VertexType::TensorCore); + + std::size_t tensor_idx = 0; + for (std::size_t i = 0; i <= vertex; ++i) { + if (vertex_types[i] == VertexType::TensorCore) { + ++tensor_idx; + } + } + + assert(tensor_idx > 0); + + return tensor_idx - 1; +} + +template +auto permute(const ArrayLike &vector, const Permutation &perm) { + using std::size; + auto sz = size(vector); + std::decay_t pvector(sz); + for (size_t i = 0; i != sz; ++i) pvector[perm[i]] = vector[i]; + return pvector; +} + +template +void apply_index_replacements(ArrayLike &tensors, + const ReplacementMap &replacements) { +#ifndef NDEBUG + // assert that tensors' indices are not tagged since going to tag indices + { + for (const auto &tensor : tensors) { + assert(ranges::none_of(braket(*tensor), [](const Index &idx) { + return idx.tag().has_value(); + })); + } + } +#endif + + bool pass_mutated = false; + do { + pass_mutated = false; + for (auto &tensor : tensors) { + pass_mutated |= transform_indices(*tensor, replacements); + } + } while (pass_mutated); // transform till stops changing + + // untag transformed indices (if any) + { + for (auto &tensor : tensors) { + reset_tags(*tensor); + } + } +} + +void TensorNetwork::canonicalize_graph(const named_indices_t &named_indices) { + if (Logger::get_instance().canonicalize) { + std::wcout << "TensorNetwork::canonicalize_graph: input tensors\n"; + size_t cnt = 0; + ranges::for_each(tensors_, [&](const auto &t) { + std::wcout << "tensor " << cnt++ << ": " << to_latex(*t) << std::endl; + }); + std::wcout << std::endl; + } + + if (!have_edges_) { + init_edges(); + } + // - canonize indices + // - canonize tensors using canonical list of indices + // Algorithm sketch: + // - to canonize indices make a graph whose vertices are the indices as + // well as the tensors and their terminals. + // - Indices with protoindices are connected to their protoindices, + // either directly or (if protoindices are symmetric) via a protoindex + // vertex. + // - Indices are colored by their space, which in general encodes also + // the space of the protoindices. + // - An anti/symmetric n-body tensor has 2 terminals, each connected to + // each other + to n index vertices. + // - A nonsymmetric n-body tensor has n terminals, each connected to 2 + // indices and 1 tensor vertex which is connected to all n terminal + // indices. + // - tensor vertices are colored by the label+rank+symmetry of the + // tensor; terminal vertices are colored by the color of its tensor, + // with the color of symm/antisymm terminals augmented by the + // terminal's type (bra/ket). + // - canonize the graph + + auto is_anonymous_index = [named_indices](const Index &idx) { + return named_indices.find(idx) == named_indices.end(); + }; + + // index factory to generate anonymous indices + IndexFactory idxfac(is_anonymous_index, 1); + + // Clear any potential prior replacements + idxrepl_.clear(); + + // make the graph + Graph graph = create_graph(&named_indices); + // graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); + + // TODO: Add logging var for this + if (false) { + std::wcout << "Input graph for canonicalization:\n"; + graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); + } + + // canonize the graph + bliss::Stats stats; + graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + const unsigned int *cl = + graph.bliss_graph->canonical_form(stats, nullptr, nullptr); + + if (Logger::get_instance().canonicalize_dot) { + std::wcout << "Canonicalization permutation:\n"; + for (std::size_t i = 0; i < graph.vertex_labels.size(); ++i) { + std::wcout << i << " -> " << cl[i] << "\n"; + } + std::wcout << "Canonicalized graph:\n"; + bliss::Graph *cgraph = graph.bliss_graph->permute(cl); + cgraph->write_dot(std::wcout, {}, true); + auto cvlabels = permute(graph.vertex_labels, cl); + std::wcout << "with our labels:\n"; + cgraph->write_dot(std::wcout, cvlabels); + delete cgraph; + } + + // make anonymous index replacement list + { + // for each color make a replacement list for bringing the indices to + // the canonical order + container::set colors; + // maps color to the ordinals of the corresponding indices in edges_ and + // their canonical ordinals collect colors and anonymous indices sorted by + // colors + container::multimap> color2idx; + + std::size_t idx_cnt = 0; + auto edge_it = edges_.begin(); + for (std::size_t vertex = 0; vertex < graph.vertex_types.size(); ++vertex) { + if (graph.vertex_types[vertex] != VertexType::Index) { + continue; + } + + auto color = graph.vertex_colors[vertex]; + colors.insert(color); + + // We rely on the fact that the order of index vertices corresponds to + // the order of corresponding edges + assert(edge_it != edges_.end()); + const Index &idx = edge_it->idx(); + + if (is_anonymous_index(idx)) { + color2idx.emplace(color, std::make_pair(idx_cnt, cl[vertex])); + } + + ++idx_cnt; + ++edge_it; + } + + // for each color sort anonymous indices by canonical order + + // canonically-ordered list of + // {index ordinal in edges_, canonical ordinal} + container::svector> idx_can; + for (const std::size_t color : colors) { + auto beg = color2idx.lower_bound(color); + auto end = color2idx.upper_bound(color); + + const auto sz = std::distance(beg, end); + + if (sz > 1) { + idx_can.resize(sz); + + size_t cnt = 0; + for (auto it = beg; it != end; ++it, ++cnt) { + idx_can[cnt] = it->second; + } + + using std::begin; + using std::end; + std::sort(begin(idx_can), end(idx_can), + [](const std::pair &a, + const std::pair &b) { + return a.second < b.second; + }); + + // make a replacement list by generating new indices in canonical + // order + for (auto [orig_idx, _] : idx_can) { + assert(orig_idx < edges_.size()); + auto edge_it = edges_.begin(); + std::advance(edge_it, orig_idx); + + const auto &idx = edge_it->idx(); + + idxrepl_.emplace(std::make_pair(idx, idxfac.make(idx))); + } + } else if (sz == 1) { + // no need for resorting of colors with 1 index only, but still need + // to replace the index + const auto edge_it = edges_.begin() + beg->second.first; + const auto &idx = edge_it->idx(); + idxrepl_.emplace(std::make_pair(idx, idxfac.make(idx))); + } + // sz == 0 is possible since some colors in colors refer to tensors + } + } // index repl + + // Bring tensors into canonical order, but ensure to respect commutativity! + + std::vector tensor_indices; + tensor_indices.resize(tensors_.size()); + std::iota(tensor_indices.begin(), tensor_indices.end(), 0); + + // TODO: initialize this while iterating over vertices for index (colors) + container::map tensor_idx_to_vertex; + for (std::size_t tensor_idx = 0, vertex = 0; + vertex < graph.vertex_types.size(); ++vertex) { + if (graph.vertex_types[vertex] != VertexType::TensorCore) { + continue; + } + tensor_idx_to_vertex[tensor_idx] = vertex; + tensor_idx++; + } + + const auto tensor_sorter = [this, &cl, &tensor_idx_to_vertex]( + std::size_t lhs_idx, std::size_t rhs_idx) { + const AbstractTensor &lhs = *tensors_[lhs_idx]; + const AbstractTensor &rhs = *tensors_[rhs_idx]; + + if (!tensors_commute(lhs, rhs)) { + return false; + } + + const std::size_t lhs_vertex = tensor_idx_to_vertex.at(lhs_idx); + const std::size_t rhs_vertex = tensor_idx_to_vertex.at(rhs_idx); + + // Commuting tensors are sorted based on their canonical order which is + // given by the order of the corresponding vertices in the canonical graph + // representation + return cl[lhs_vertex] < cl[rhs_vertex]; + }; + + std::stable_sort(tensor_indices.begin(), tensor_indices.end(), tensor_sorter); + + assert(tensor_indices.size() == tensors_.size()); + + decltype(tensors_) tensors_canonized(tensors_.size()); + for (std::size_t i = 0; i < tensors_.size(); ++i) { + tensors_canonized[i] = tensors_[tensor_indices[i]]; + } + + tensors_ = std::move(tensors_canonized); + + // Apply the canonical reindexing + apply_index_replacements(tensors_, idxrepl_); + + have_edges_ = false; + + if (Logger::get_instance().canonicalize) { + std::wcout << "TensorNetwork::canonicalize_graph: tensors after " + "canonicalization\n"; + size_t cnt = 0; + ranges::for_each(tensors_, [&](const auto &t) { + std::wcout << "tensor " << cnt++ << ": " << to_latex(*t) << std::endl; + }); + } +} + ExprPtr TensorNetwork::canonicalize( const container::vector &cardinal_tensor_labels, bool fast, const named_indices_t *named_indices_ptr) { - ExprPtr canon_biproduct = ex(1); - container::svector idx_terminals_sorted; // to avoid memory allocs + // initialize named_indices by default to all external indices + const auto &named_indices = + named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; if (Logger::get_instance().canonicalize) { std::wcout << "TensorNetwork::canonicalize(" << (fast ? "fast" : "slow") @@ -52,53 +481,23 @@ ExprPtr TensorNetwork::canonicalize( std::wcout << std::endl; } - // - resort tensors (this cannot be done in Product::canonicalize since that - // requires analysis of commutativity ... here we are dealing with tensors - // only and are free to reorder) - using std::begin; - using std::end; - bubble_sort( - begin(tensors_), end(tensors_), - [&cardinal_tensor_labels](const auto &first_ptr, const auto &second_ptr) { - const auto &first = *first_ptr; - const auto &second = *second_ptr; - // grab base label if adjoint label is present - auto base_label = [](const auto &t) { - if (label(t).back() == adjoint_label) { - return label(t).substr(0, label(t).size() - 1); - } else { - return label(t); - } - }; - // tensors commute if their colors are different or either one of them - // is a c-number - if ((color(first) != color(second)) || is_cnumber(first) || - is_cnumber(second)) { - const auto cardinal_tensor_labels_end = end(cardinal_tensor_labels); - const auto first_cardinal_it = - std::find(begin(cardinal_tensor_labels), - end(cardinal_tensor_labels), base_label(first)); - const auto second_cardinal_it = - std::find(begin(cardinal_tensor_labels), - end(cardinal_tensor_labels), base_label(second)); - const auto first_is_cardinal = - first_cardinal_it != cardinal_tensor_labels_end; - const auto second_is_cardinal = - second_cardinal_it != cardinal_tensor_labels_end; - if (first_is_cardinal && second_is_cardinal) { - if (first_cardinal_it == second_cardinal_it) - return first < second; - else - return first_cardinal_it < second_cardinal_it; - } else if (first_is_cardinal) - return true; - else if (second_is_cardinal) - return false; - else // neither is cardinal - return first < second; - } else - return false; - }); + if (!fast) { + // The graph-based canonization is required in call cases in which there are + // indistinguishable tensors present in the expression. Their order and + // indexing can only be determined via this rigorous canonization. + canonicalize_graph(named_indices); + } + + // Ensure each individual tensor is canonical with the current indexing in + // order to properly be able to identify tensor blocks + ExprPtr byproduct = canonicalize_individual_tensors(named_indices); + + const CanonicalTensorCompare tensor_sorter( + cardinal_tensor_labels); + + std::stable_sort(tensors_.begin(), tensors_.end(), tensor_sorter); + + init_edges(); if (Logger::get_instance().canonicalize) { std::wcout << "TensorNetwork::canonicalize(" << (fast ? "fast" : "slow") @@ -109,13 +508,6 @@ ExprPtr TensorNetwork::canonicalize( }); } - if (edges_.empty()) init_edges(); - - // initialize named_indices by default to all external indices (these HAVE - // been computed in init_edges) - const auto &named_indices = - named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; - // helpers to filter named ("external" in traditional use case) / anonymous // ("internal" in traditional use case) auto is_named_index = [&](const Index &idx) { @@ -124,594 +516,363 @@ ExprPtr TensorNetwork::canonicalize( auto is_anonymous_index = [&](const Index &idx) { return named_indices.find(idx) == named_indices.end(); }; - auto namedness = [&](const Index &idx) { - return is_named_index(idx) ? 1 : 0; - }; - // fast and slow canonizations produce index replacements for anonymous - // indices - idxrepl_.clear(); - auto &idxrepl = idxrepl_; + // Sort edges based on the order of the tensors they connect (instead of based + // on their (full) label) + container::svector resorted_edges(edges_.begin(), edges_.end()); + std::stable_sort(resorted_edges.begin(), resorted_edges.end(), + [&is_named_index](const Edge &lhs, const Edge &rhs) { + // Sort first by index's character (named < anonymous), + // then by Edge (not by Index's full label) ... this + // automatically puts named indices first + const bool lhs_is_named = is_named_index(lhs.idx()); + const bool rhs_is_named = is_named_index(rhs.idx()); + + if (lhs_is_named == rhs_is_named) { + return lhs < rhs; + } else { + return lhs_is_named; + } + }); // index factory to generate anonymous indices - IndexFactory idxfac(is_anonymous_index, - 1); // start reindexing anonymous indices from 1 - - if (!fast) { - // - canonize indices - // - canonize tensors using canonical list of indices - // Algorithm sketch: - // - to canonize indices make a graph whose vertices are the indices as - // well as the tensors and their terminals. - // - Indices with protoindices are connected to their protoindices, - // either directly or (if protoindices are symmetric) via a protoindex - // vertex. - // - Indices are colored by their space, which in general encodes also - // the space of the protoindices. - // - An anti/symmetric n-body tensor has 2 terminals, each connected to - // each other + to n index vertices. - // - A nonsymmetric n-body tensor has n terminals, each connected to 2 - // indices and 1 tensor vertex which is connected to all n terminal - // indices. - // - tensor vertices are colored by the label+rank+symmetry of the - // tensor; terminal vertices are colored by the color of its tensor, - // with the color of symm/antisymm terminals augmented by the - // terminal's type (bra/ket). - // - canonize the graph - - auto permute = [](const auto &vector, auto &&perm) { - using std::size; - auto sz = size(vector); - std::decay_t pvector(sz); - for (size_t i = 0; i != sz; ++i) pvector[perm[i]] = vector[i]; - return pvector; - }; - - // make the graph - auto [graph, vlabels, vcolors, vtypes] = make_bliss_graph(&named_indices); - // graph->write_dot(std::wcout, vlabels); + // -> start reindexing anonymous indices from 1 + IndexFactory idxfac(is_anonymous_index, 1); - // canonize the graph - bliss::Stats stats; - graph->set_splitting_heuristic(bliss::Graph::shs_fsm); - const unsigned int *cl = graph->canonical_form(stats, nullptr, nullptr); + idxrepl_.clear(); - if (Logger::get_instance().canonicalize_dot) { - bliss::Graph *cgraph = graph->permute(cl); - auto cvlabels = permute(vlabels, cl); - cgraph->write_dot(std::wcout, cvlabels); - delete cgraph; - } + // Use the new order of edges as the canonical order of indices and relabel + // accordingly (but only anonymous indices, of course) + for (std::size_t i = named_indices.size(); i < resorted_edges.size(); ++i) { + const Index &index = resorted_edges[i].idx(); + assert(is_anonymous_index(index)); + Index replacement = idxfac.make(index); + idxrepl_.emplace(std::make_pair(index, replacement)); + } - // make anonymous index replacement list - { - // for each color make a replacement list for bringing the indices to - // the canonical order - container::set colors; - container::multimap> - color2idx; // maps color to the ordinals of the corresponding - // indices in edges_ + their canonical ordinals - // collect colors and anonymous indices sorted by colors - size_t idx_cnt = 0; - for (auto &&ttpair : edges_) { - auto color = vcolors[idx_cnt]; - if (colors.find(color) == colors.end()) colors.insert(color); - if (is_anonymous_index(ttpair.idx())) { - color2idx.emplace(color, std::make_pair(idx_cnt, cl[idx_cnt])); - } - ++idx_cnt; - } - // for each color sort anonymous indices by canonical order - container::svector> - idx_can; // canonically-ordered list of {index ordinal in edges_, - // canonical ordinal} - for (auto &&color : colors) { - auto beg = color2idx.lower_bound(color); - auto end = color2idx.upper_bound(color); - const auto sz = end - beg; - if (sz > 1) { - idx_can.resize(sz); - size_t cnt = 0; - for (auto it = beg; it != end; ++it, ++cnt) { - idx_can[cnt] = it->second; - } - using std::begin; - using std::end; - std::sort(begin(idx_can), end(idx_can), - [](const std::pair &a, - const std::pair &b) { - return a.second < b.second; - }); - // make a replacement list by generating new indices in canonical - // order - for (auto &&p : idx_can) { - const auto &idx = (edges_.begin() + p.first)->idx(); - idxrepl.emplace(std::make_pair(idx, idxfac.make(idx))); - } - } else if (sz == 1) { // no need for resorting of colors with 1 index - // only, but still need to replace the index - const auto edge_it = edges_.begin() + beg->second.first; - const auto &idx = edge_it->idx(); - idxrepl.emplace(std::make_pair(idx, idxfac.make(idx))); - } - // sz == 0 is possible since some colors in colors refer to tensors - } - } // index repl - - // reorder *commuting* tensors_ to canonical order (defined by the core - // indices) - { - decltype(tensors_) tensors_canonized(tensors_.size(), nullptr); - - container::set colors; - container::multimap> - color2idx; // maps color to the ordinals of the corresponding - // tensors in tensors_ + their canonical ordinals given by cl - // collect colors and tensors sorted by colors - size_t vtx_cnt = 0; - size_t tensor_cnt = 0; - for (auto &&type : vtypes) { - if (type == VertexType::TensorCore) { // tensor core vertices were - // created in the order of their - // appearance in tensors_ - auto color = vcolors[vtx_cnt]; - if (colors.find(color) == colors.end()) colors.insert(color); - color2idx.emplace(color, std::make_pair(tensor_cnt, cl[vtx_cnt])); - ++tensor_cnt; - } - ++vtx_cnt; - } - // for each color sort tensors by canonical order - // this assumes that tensors of different colors always commute - // (reasonable) this only reorders tensors if they are c-numbers! - container::svector> - ord_can; // canonically-ordered list of {ordinal in tensors_, - // canonical ordinal} - container::svector - ord_orig; // originaly-ordered list of {canonical ordinal} - for (auto &&color : colors) { - auto beg = color2idx.lower_bound(color); - auto end = color2idx.upper_bound(color); - const auto sz = end - beg; - assert(sz > 0); - if (sz > 1) { - // all tensors of same color are c-numbers or all q-numbers ... - // inspect the first to determine the type - const bool cnumber = is_cnumber(*(tensors_.at(beg->second.first))); - - ord_can.resize(sz); - ord_orig.resize(sz); - - size_t cnt = 0; - for (auto it = beg; it != end; ++it, ++cnt) { - ord_can[cnt] = it->second; - ord_orig[cnt] = it->second.first; - // assert that all tensors of same color are all c-numbers or all - // q-numbers - assert(cnumber == is_cnumber(*(tensors_.at(ord_orig[cnt])))); - } - using std::begin; - using std::end; - if (cnumber) // only resort if these are cnumbers - std::sort(begin(ord_can), end(ord_can), - [](const std::pair &a, - const std::pair &b) { - return a.second < b.second; - }); - std::sort(begin(ord_orig), end(ord_orig)); - // write (potentially reordered) tensors to tensors_canonized - for (std::ptrdiff_t t = 0; t != sz; ++t) { - tensors_canonized.at(ord_orig[t]) = tensors_.at(ord_can[t].first); - } - - } else { // sz = 1 - auto tidx = beg->second.first; - tensors_canonized.at(tidx) = tensors_.at(tidx); - } - } // colors - - // commit the canonically-ordered list of tensors to tensors_ - using std::swap; - swap(tensors_canonized, tensors_); - - } // tensors canonizing - - } else { // fast approach uses heuristic canonization - // simpler approach that will work perfectly as long as tensors are - // distinguishable - - // - reindex anonymous indices using ordering of Edge as the - // canonical definition of the anonymous index list - { - // resort edges_ first by index's character (named named, 0 -> anonymous - const auto n2 = namedness(edge2.idx()); - if (n1 == n2) - return edge1 < edge2; - else - return n1 > n2; - }); - - // make index replacement list for anonymous indices only - const auto num_named_indices = named_indices.size(); - std::for_each( - begin(idx_terminals_sorted) + num_named_indices, - end(idx_terminals_sorted), - [&idxrepl, &idxfac, &is_anonymous_index](const auto &terminals) { - const auto &idx = terminals.idx(); - assert(is_anonymous_index( - idx)); // should only encounter anonymous indices here - idxrepl.emplace(std::make_pair(idx, idxfac.make(idx))); - }); - } - } // canonical index replacement list computed + // Done computing canonical index replacement list if (Logger::get_instance().canonicalize) { - for (const auto &idxpair : idxrepl) { + for (const auto &idxpair : idxrepl_) { std::wcout << "TensorNetwork::canonicalize(" << (fast ? "fast" : "slow") << "): replacing " << to_latex(idxpair.first) << " with " << to_latex(idxpair.second) << std::endl; } } -#ifndef NDEBUG - // assert that tensors' indices are not tagged since going to tag indices - { - for (const auto &tensor : tensors_) { - assert(ranges::none_of(braket(*tensor), [](const Index &idx) { - return idx.tag().has_value(); - })); - } - } -#endif - bool pass_mutated = false; - [[maybe_unused]] bool mutated = false; - do { - pass_mutated = false; - for (auto &tensor : tensors_) { - pass_mutated |= transform_indices(*tensor, idxrepl); - } - mutated |= pass_mutated; - } while (pass_mutated); // transform till stops changing + apply_index_replacements(tensors_, idxrepl_); - // untag transformed indices (if any) - { - for (auto &tensor : tensors_) { - reset_tags(*tensor); - } - } + byproduct *= canonicalize_individual_tensors(named_indices); - // - re-canonize tensors - { - // override the default canonicalizer - DefaultTensorCanonicalizer default_tensor_canonizer(named_indices); - for (auto &tensor : tensors_) { - auto nondefault_canonizer_ptr = - TensorCanonicalizer::nondefault_instance_ptr(tensor->_label()); - TensorCanonicalizer *tensor_canonizer = - nondefault_canonizer_ptr ? nondefault_canonizer_ptr.get() - : &default_tensor_canonizer; - auto bp = tensor_canonizer->apply(*tensor); - if (bp) *canon_biproduct *= *bp; - } - } - edges_.clear(); - ext_indices_.clear(); + // We assume that re-indexing did not change the canonical order of tensors + // If it did, then most likely the (Abstract)Tensor::operator< compares index + // groups in a different order than Vertex::operator< + assert(std::is_sorted(tensors_.begin(), tensors_.end(), tensor_sorter)); + + have_edges_ = false; - assert(canon_biproduct->is()); - return (canon_biproduct->as().value() == 1) ? nullptr - : canon_biproduct; + assert(byproduct->is()); + return (byproduct->as().value() == 1) ? nullptr : byproduct; } -std::tuple, std::vector, - std::vector, - std::vector> -TensorNetwork::make_bliss_graph( +TensorNetwork::Graph TensorNetwork::create_graph( const named_indices_t *named_indices_ptr) const { - // must call init_edges() prior to calling this - if (edges_.empty()) { - init_edges(); - } + assert(have_edges_); - // initialize named_indices by default to all external indices (these HAVE - // been computed in init_edges) - const auto &named_indices = + // initialize named_indices by default to all external indices + const named_indices_t &named_indices = named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; - // results - std::shared_ptr graph; - std::vector vertex_labels( - edges_.size()); // the size will be updated - std::vector vertex_color(edges_.size(), - 0); // the size will be updated - std::vector vertex_type( - edges_.size()); // the size will be updated - - // N.B. Colors [0, 2 max rank + named_indices.size()) are reserved: - // 0 - the bra vertex (for particle 0, if bra is nonsymm, or for the entire - // bra, if (anti)symm) 1 - the bra vertex for particle 1, if bra is nonsymm - // ... - // max_rank - the ket vertex (for particle 0, if particle-asymmetric, or for - // the entire ket, if particle-symmetric) max_rank+1 - the ket vertex for - // particle 1, if particle-asymmetric - // ... - // 2 max_rank - first named index - // 2 max_rank + 1 - second named index - // ... - // N.B. For braket-symmetric tensors the ket vertices use the same indices as - // the bra vertices - auto nonreserved_color = [&named_indices](size_t color) -> bool { - return color >= 2 * max_rank + named_indices.size(); - }; + // Colors in the range [ 0, 3 * max_rank + named_indices.size() ) are + // reserved: Up to max_rank colors can be used for bra indices Up to + // max_rank colors can be used for ket indices Up to max_rank colors can be + // used for auxiliary indices Every named index is identified by a unique + // color + constexpr std::size_t named_idx_color_start = 3 * max_rank; + const std::size_t max_reserved_color = + named_idx_color_start + named_indices.size() - 1; + + // core, bra, ket, auxiliary + constexpr std::size_t num_tensor_components = 4; - // compute # of vertices - size_t nv = 0; - size_t index_cnt = 0; - size_t spbundle_cnt = 0; - // first count vertex indices ... the only complication are symmetric - // protoindex bundles this will keep track of unique symmetric protoindex - // bundles - using protoindex_bundle_t = + // results + Graph graph; + // We know that at the very least all indices and all tensors will yield + // vertex representations + std::size_t vertex_count_estimate = + edges_.size() + num_tensor_components * tensors_.size(); + graph.vertex_labels.reserve(vertex_count_estimate); + graph.vertex_colors.reserve(vertex_count_estimate); + graph.vertex_types.reserve(vertex_count_estimate); + + using proto_bundle_t = std::decay_t().proto_indices())>; - container::set symmetric_protoindex_bundles; - const size_t spbundle_vertex_offset = - edges_.size(); // where spbundle vertices will start - ranges::for_each(edges_, [&](const Edge &ttpair) { - const Index &idx = ttpair.idx(); - ++nv; // each index is a vertex - vertex_labels.at(index_cnt) = idx.to_latex(); - vertex_type.at(index_cnt) = VertexType::Index; - // assign color: named indices use reserved colors - const auto named_index_it = named_indices.find(idx); - if (named_index_it == - named_indices.end()) { // anonymous index? use Index::color - const auto idx_color = idx.color(); - assert(nonreserved_color(idx_color)); - vertex_color.at(index_cnt) = idx_color; - } else { - const auto named_index_rank = named_index_it - named_indices.begin(); - vertex_color.at(index_cnt) = 2 * max_rank + named_index_rank; - } - // each symmetric proto index bundle will have a vertex ... - // for now only store the unique protoindex bundles in - // symmetric_protoindex_bundles, then commit their data to - // vertex_{labels,type,color} later - if (idx.has_proto_indices()) { - assert(idx.symmetric_proto_indices()); // only symmetric protoindices are - // supported right now - if (symmetric_protoindex_bundles.find(idx.proto_indices()) == - symmetric_protoindex_bundles - .end()) { // new bundle? make a vertex for it - auto graph = symmetric_protoindex_bundles.insert(idx.proto_indices()); - assert(graph.second); + container::map proto_bundles; + + container::map tensor_vertices; + tensor_vertices.reserve(tensors_.size()); + + container::vector> edges; + edges.reserve(edges_.size() + tensors_.size()); + + // Add vertices for tensors + for (std::size_t tensor_idx = 0; tensor_idx < tensors_.size(); ++tensor_idx) { + assert(tensor_vertices.find(tensor_idx) == tensor_vertices.end()); + assert(tensors_.at(tensor_idx)); + const AbstractTensor &tensor = *tensors_.at(tensor_idx); + + // Tensor core + std::wstring_view tensor_label = label(tensor); + graph.vertex_labels.emplace_back(tensor_label); + graph.vertex_types.emplace_back(VertexType::TensorCore); + const std::size_t tensor_color = + hash::value(tensor_label) + max_reserved_color; + assert(tensor_color > max_reserved_color); + graph.vertex_colors.push_back(tensor_color); + + const std::size_t tensor_vertex = graph.vertex_labels.size() - 1; + tensor_vertices.insert(std::make_pair(tensor_idx, tensor_vertex)); + + // Create vertices to group indices + const Symmetry tensor_sym = symmetry(tensor); + if (tensor_sym == Symmetry::nonsymm) { + // Create separate vertices for every index + assert(bra_rank(tensor) <= max_rank); + for (std::size_t i = 0; i < bra_rank(tensor); ++i) { + graph.vertex_labels.emplace_back(L"bra_" + std::to_wstring(i + 1)); + graph.vertex_types.push_back(VertexType::TensorBra); + graph.vertex_colors.push_back(i); } - } - index_cnt++; - }); - // now commit protoindex bundle metadata - ranges::for_each(symmetric_protoindex_bundles, [&](const auto &bundle) { - ++nv; // each symmetric protoindex bundle is a vertex - std::wstring spbundle_label = L"{"; - for (auto &&pi : bundle) { - spbundle_label += pi.to_latex(); - } - spbundle_label += L"}"; - vertex_labels.push_back(spbundle_label); - vertex_type.push_back(VertexType::SPBundle); - const auto idx_proto_indices_color = Index::proto_indices_color(bundle); - assert(nonreserved_color(idx_proto_indices_color)); - vertex_color.push_back(idx_proto_indices_color); - spbundle_cnt++; - }); - // now account for vertex representation of tensors - size_t tensor_cnt = 0; - // this will map to tensor index to the first (core) vertex in its - // representation - container::svector tensor_vertex_offset(tensors_.size()); - ranges::for_each(tensors_, [&](const auto &t) { - tensor_vertex_offset.at(tensor_cnt) = nv; - // each tensor has a core vertex (to be colored by its label) - ++nv; - const auto tlabel = label(*t); - vertex_labels.emplace_back(tlabel); - vertex_type.emplace_back(VertexType::TensorCore); - const auto t_color = hash::value(tlabel); - static_assert(sizeof(t_color) == sizeof(unsigned long int)); - assert(nonreserved_color(t_color)); - vertex_color.push_back(t_color); - // symmetric/antisymmetric tensors are represented by 3 more vertices: - // - bra - // - ket - // - braket (connecting bra and ket to the core) - auto &tref = *t; - if (symmetry(tref) != Symmetry::nonsymm) { - nv += 3; - vertex_labels.push_back( - std::wstring(L"bra") + to_wstring(bra_rank(tref)) + - ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); - vertex_type.push_back(VertexType::TensorBra); - vertex_color.push_back(0); - vertex_labels.push_back( - std::wstring(L"ket") + to_wstring(ket_rank(tref)) + - ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); - vertex_type.push_back(VertexType::TensorKet); - vertex_color.push_back( - braket_symmetry(tref) == BraKetSymmetry::symm ? 0 : max_rank); - vertex_labels.push_back( - std::wstring(L"bk") + - ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); - vertex_type.push_back(VertexType::TensorBraKet); - vertex_color.push_back(t_color); - } - // nonsymmetric tensors are represented by 3*rank more vertices (with rank = - // max(bra_rank(),ket_rank()) - else { - const auto rank = std::max(bra_rank(tref), ket_rank(tref)); - assert(rank <= max_rank); - for (size_t p = 0; p != rank; ++p) { - nv += 3; - auto pstr = to_wstring(p + 1); - vertex_labels.push_back(std::wstring(L"bra") + pstr); - vertex_type.push_back(VertexType::TensorBra); - const bool t_is_particle_symmetric = - particle_symmetry(tref) == ParticleSymmetry::nonsymm; - const auto bra_color = t_is_particle_symmetric ? p : 0; - vertex_color.push_back(bra_color); - vertex_labels.push_back(std::wstring(L"ket") + pstr); - vertex_type.push_back(VertexType::TensorKet); - vertex_color.push_back(braket_symmetry(tref) == BraKetSymmetry::symm - ? bra_color - : bra_color + max_rank); - vertex_labels.push_back(std::wstring(L"bk") + pstr); - vertex_type.push_back(VertexType::TensorBraKet); - vertex_color.push_back(t_color); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + + assert(ket_rank(tensor) <= max_rank); + for (std::size_t i = 0; i < ket_rank(tensor); ++i) { + graph.vertex_labels.emplace_back(L"ket_" + std::to_wstring(i + 1)); + graph.vertex_types.push_back(VertexType::TensorKet); + graph.vertex_colors.push_back(max_rank + i); } + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + } else { + // Shared set of bra/ket vertices for all indices + std::wstring suffix = tensor_sym == Symmetry::symm ? L"_s" : L"_a"; + + const std::size_t bra_color = 0; + graph.vertex_labels.push_back(L"bra" + suffix); + graph.vertex_types.push_back(VertexType::TensorBra); + graph.vertex_colors.push_back(bra_color); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + + // TODO: figure out how to handle BraKetSymmetry::conjugate + const std::size_t ket_color = + braket_symmetry(tensor) == BraKetSymmetry::symm + ? bra_color + : bra_color + max_rank; + graph.vertex_labels.push_back(L"ket" + suffix); + graph.vertex_types.push_back(VertexType::TensorKet); + graph.vertex_colors.push_back(ket_color); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); } - ++tensor_cnt; - }); - - // allocate graph - graph = std::make_shared(nv); - - // add edges - // - each index's degree <= 2 + # of protoindex terminals - index_cnt = 0; - ranges::for_each(edges_, [&](const Edge &ttpair) { - for (int t = 0; t != 2; ++t) { - const auto terminal_index = t == 0 ? ttpair.first() : ttpair.second(); - const auto terminal_position = - t == 0 ? ttpair.first_position() : ttpair.second_position(); - if (terminal_index) { - const auto tidx = std::abs(terminal_index) - 1; - const auto ttpos = terminal_position; - const bool bra = terminal_index > 0; - const size_t braket_vertex_index = tensor_vertex_offset[tidx] + - /* core */ 1 + 3 * ttpos + - (bra ? 0 : 1); - graph->add_edge(index_cnt, braket_vertex_index); - } + + // TODO: handle aux indices permutation symmetries + // for now, auxiliary indices are considered to always be asymmetric + for (std::size_t i = 0; i < auxiliary_rank(tensor); ++i) { + graph.vertex_labels.emplace_back(L"aux_" + std::to_wstring(i + 1)); + graph.vertex_types.push_back(VertexType::TensorAux); + graph.vertex_colors.push_back(2 * max_rank + i); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); } - // if this index has symmetric protoindex bundles - if (ttpair.idx().has_proto_indices()) { - if (ttpair.idx().symmetric_proto_indices()) { - assert( - symmetric_protoindex_bundles.find(ttpair.idx().proto_indices()) != - symmetric_protoindex_bundles.end()); - const auto spbundle_idx = - symmetric_protoindex_bundles.find(ttpair.idx().proto_indices()) - - symmetric_protoindex_bundles.begin(); - graph->add_edge(index_cnt, spbundle_vertex_offset + spbundle_idx); + + // TODO: Add vertex for total or braket grouping? + } + + // Now add all indices (edges) to the graph + for (const Edge ¤t_edge : edges_) { + const Index &index = current_edge.idx(); + graph.vertex_labels.push_back(index.to_latex()); + graph.vertex_types.push_back(VertexType::Index); + + // Assign index color + std::size_t idx_color; + auto named_idx_iter = named_indices.find(index); + if (named_idx_iter == named_indices.end()) { + // This is an anonymous index + idx_color = index.color(); + assert(idx_color > max_reserved_color); + } else { + idx_color = static_cast( + std::distance(named_indices.begin(), named_idx_iter)); + idx_color += named_idx_color_start; + } + graph.vertex_colors.push_back(idx_color); + + const std::size_t index_vertex = graph.vertex_labels.size() - 1; + + // Handle proto indices + if (index.has_proto_indices()) { + // For now we assume that all proto indices are symmetric + assert(index.symmetric_proto_indices()); + + std::size_t proto_vertex; + if (auto it = proto_bundles.find(index.proto_indices()); + it != proto_bundles.end()) { + proto_vertex = it->second; } else { - abort(); // nonsymmetric proto indices not supported yet + // Create a new vertex for this bundle of proto indices + std::wstring spbundle_label = L"{"; + for (const Index &proto : index.proto_indices()) { + spbundle_label += proto.to_latex(); + } + spbundle_label += L"}"; + graph.vertex_labels.push_back(std::move(spbundle_label)); + graph.vertex_types.push_back(VertexType::SPBundle); + const std::size_t bundle_color = + Index::proto_indices_color(index.proto_indices()) + + max_reserved_color; + assert(bundle_color); + graph.vertex_colors.push_back(bundle_color); + + proto_vertex = graph.vertex_labels.size() - 1; + proto_bundles.insert( + std::make_pair(index.proto_indices(), proto_vertex)); } + + edges.push_back(std::make_pair(index_vertex, proto_vertex)); } - ++index_cnt; - }); - // - link up proto indices, if any ... only symmetric protobundles are - // supported now - spbundle_cnt = spbundle_vertex_offset; - ranges::for_each(symmetric_protoindex_bundles, [&graph, this, &spbundle_cnt]( - const auto &bundle) { - for (auto &&proto_index : bundle) { - assert(edges_.find(proto_index.full_label()) != edges_.end()); - const auto proto_index_vertex = - edges_.find(proto_index.full_label()) - edges_.begin(); - graph->add_edge(spbundle_cnt, proto_index_vertex); + + // Connect index to the tensor(s) it is connected to + for (std::size_t i = 0; i < current_edge.vertex_count(); ++i) { + assert(i <= 1); + const Vertex &vertex = + i == 0 ? current_edge.first_vertex() : current_edge.second_vertex(); + + assert(tensor_vertices.find(vertex.getTerminalIndex()) != + tensor_vertices.end()); + const std::size_t tensor_vertex = + tensor_vertices.find(vertex.getTerminalIndex())->second; + + // Store an edge connecting the index vertex to the corresponding tensor + // vertex + // TODO: if we re-introduce a braket-like vertex, we have to add 1 to + // the tensor vertex + static_assert(static_cast(Origin::Bra) == 1); + static_assert(static_cast(Origin::Ket) == 2); + static_assert(static_cast(Origin::Aux) == 3); + const bool tensor_is_nonsymm = + vertex.getTerminalSymmetry() == Symmetry::nonsymm; + // Determine the index of the vertex for the tensor component we want to + // connect the current index to. For (anti)symmetric tensors there + // exists only a single bra, ket or aux vertex per tensor and thus the + // origin of the index is the only factor to account for. Non-symmetric + // tensors on the other hand have a bra, ket or aux vertex for each + // individual index and thus the index's position within the bra, ket or + // aux group of the tensor has to be accounted for in order to selected + // the correct vertex to connect to. N.B. conversion from bool to int is + // always: true -> 1, false -> 0 + const std::size_t tensor_component_vertex = + tensor_vertex + static_cast(vertex.getOrigin()) + + tensor_is_nonsymm * vertex.getIndexSlot() * + (num_tensor_components - /* core */ 1); + assert(tensor_component_vertex < graph.vertex_labels.size()); + edges.push_back(std::make_pair(index_vertex, tensor_component_vertex)); } - ++spbundle_cnt; - }); - // - link up tensors - tensor_cnt = 0; - ranges::for_each( - tensors_, [&graph, &tensor_cnt, &tensor_vertex_offset](const auto &t) { - const auto vertex_offset = tensor_vertex_offset.at(tensor_cnt); - // for each braket terminal linker - auto &tref = *t; - const size_t nbk = symmetry(tref) == Symmetry::nonsymm - ? std::max(bra_rank(tref), ket_rank(tref)) - : 1; - for (size_t bk = 1; bk <= nbk; ++bk) { - const int bk_vertex = vertex_offset + 3 * bk; - graph->add_edge(vertex_offset, bk_vertex); // core - graph->add_edge(bk_vertex - 2, bk_vertex); // bra - graph->add_edge(bk_vertex - 1, bk_vertex); // ket - } - ++tensor_cnt; - }); + } + + assert(graph.vertex_labels.size() == graph.vertex_colors.size()); + assert(graph.vertex_labels.size() == graph.vertex_types.size()); + + // Create the actual BLISS graph object + graph.bliss_graph = + std::make_unique(graph.vertex_labels.size()); + + for (const std::pair ¤t_edge : edges) { + graph.bliss_graph->add_edge(current_edge.first, current_edge.second); + } // compress vertex colors to 32 bits, as required by Bliss, by hashing - size_t v_cnt = 0; - for (auto &&color : vertex_color) { - auto hash6432shift = [](size_t key) { - static_assert(sizeof(key) == 8); - key = (~key) + (key << 18); // key = (key << 18) - key - 1; - key = key ^ (key >> 31); - key = key * 21; // key = (key + (key << 2)) + (key << 4); - key = key ^ (key >> 11); - key = key + (key << 6); - key = key ^ (key >> 22); - return static_cast(key); - }; - graph->change_color(v_cnt, hash6432shift(color)); - ++v_cnt; + for (std::size_t vertex = 0; vertex < graph.vertex_colors.size(); ++vertex) { + auto color = graph.vertex_colors[vertex]; + static_assert(sizeof(color) == 8); + + color = (~color) + (color << 18); // color = (color << 18) - color - 1; + color = color ^ (color >> 31); + color = color * 21; // color = (color + (color << 2)) + (color << 4); + color = color ^ (color >> 11); + color = color + (color << 6); + color = color ^ (color >> 22); + + graph.bliss_graph->change_color(vertex, static_cast(color)); } - return {graph, vertex_labels, vertex_color, vertex_type}; + return graph; } -void TensorNetwork::init_edges() const { - if (have_edges_) return; +void TensorNetwork::init_edges() { + edges_.clear(); + ext_indices_.clear(); - auto idx_insert = [this](const Index &idx, int tensor_idx, int pos) { + auto idx_insert = [this](const Index &idx, Vertex vertex) { if (Logger::get_instance().tensor_network) { std::wcout << "TensorNetwork::init_edges: idx=" << to_latex(idx) - << " attached to tensor " << std::abs(tensor_idx) << "'s " - << ((tensor_idx > 0) ? "bra" : "ket") << " at position " << pos + << " attached to tensor " << vertex.getTerminalIndex() << " (" + << vertex.getOrigin() << ") at position " + << vertex.getIndexSlot() + << " (sym: " << to_wstring(vertex.getTerminalSymmetry()) << ")" << std::endl; } - decltype(edges_) &indices = this->edges_; - auto it = indices.find(idx.full_label()); - if (it == indices.end()) { - indices.emplace(Edge(tensor_idx, &idx, pos)); + + auto it = edges_.find(idx.full_label()); + if (it == edges_.end()) { + edges_.emplace(Edge(std::move(vertex), idx)); } else { - const_cast(*it).connect_to(tensor_idx, pos); + it->connect_to(std::move(vertex)); } }; - int t_idx = 1; - for (auto &&t : tensors_) { - const auto t_is_nonsymm = symmetry(*t) == Symmetry::nonsymm; - size_t cnt = 0; - for (const Index &idx : bra(*t)) { - idx_insert(idx, t_idx, t_is_nonsymm ? cnt : 0); - ++cnt; + for (std::size_t tensor_idx = 0; tensor_idx < tensors_.size(); ++tensor_idx) { + assert(tensors_[tensor_idx]); + const AbstractTensor &tensor = *tensors_[tensor_idx]; + const Symmetry tensor_symm = symmetry(tensor); + + auto bra_indices = bra(tensor); + for (std::size_t index_idx = 0; index_idx < bra_indices.size(); + ++index_idx) { + idx_insert(bra_indices[index_idx], + Vertex(Origin::Bra, tensor_idx, index_idx, tensor_symm)); } - cnt = 0; - for (const Index &idx : ket(*t)) { - idx_insert(idx, -t_idx, t_is_nonsymm ? cnt : 0); - ++cnt; + + auto ket_indices = ket(tensor); + for (std::size_t index_idx = 0; index_idx < ket_indices.size(); + ++index_idx) { + idx_insert(ket_indices[index_idx], + Vertex(Origin::Ket, tensor_idx, index_idx, tensor_symm)); + } + + auto aux_indices = auxiliary(tensor); + for (std::size_t index_idx = 0; index_idx < aux_indices.size(); + ++index_idx) { + idx_insert(aux_indices[index_idx], + Vertex(Origin::Aux, tensor_idx, index_idx, tensor_symm)); } - ++t_idx; } // extract external indices - for (const auto &terminals : edges_) { - assert(terminals.size() != 0); - if (terminals.size() == 1) { // external? + for (const Edge ¤t : edges_) { + assert(current.vertex_count() > 0); + if (current.vertex_count() == 1) { + // External index (== Edge only connected to a single vertex in the + // network) if (Logger::get_instance().tensor_network) { - std::wcout << "idx " << to_latex(terminals.idx()) << " is external" + std::wcout << "idx " << to_latex(current.idx()) << " is external" << std::endl; } - auto insertion_result = ext_indices_.emplace(terminals.idx()); - assert(insertion_result.second); + + bool inserted = ext_indices_.insert(current.idx()).second; + assert(inserted); } } @@ -722,4 +883,27 @@ container::svector> TensorNetwork::factorize() { abort(); // not yet implemented } +ExprPtr TensorNetwork::canonicalize_individual_tensors( + const named_indices_t &named_indices) { + ExprPtr byproduct = ex(1); + + // override the default canonicalizer + DefaultTensorCanonicalizer default_tensor_canonizer(named_indices); + for (auto &tensor : tensors_) { + auto nondefault_canonizer_ptr = + TensorCanonicalizer::nondefault_instance_ptr(tensor->_label()); + TensorCanonicalizer *tensor_canonizer = nondefault_canonizer_ptr + ? nondefault_canonizer_ptr.get() + : &default_tensor_canonizer; + + auto bp = tensor_canonizer->apply(*tensor); + + if (bp) { + byproduct *= bp; + } + } + + return byproduct; +} + } // namespace sequant diff --git a/SeQuant/core/tensor_network.hpp b/SeQuant/core/tensor_network.hpp index a9043d150..dd003ccc6 100644 --- a/SeQuant/core/tensor_network.hpp +++ b/SeQuant/core/tensor_network.hpp @@ -20,6 +20,10 @@ #include #include +#include +#include +#include + // forward declarations namespace bliss { class Graph; @@ -35,8 +39,36 @@ namespace sequant { /// graph), with Tensor objects represented by one or more vertices. class TensorNetwork { public: + friend class TensorNetworkAccessor; + constexpr static size_t max_rank = 256; + enum class Origin { + Bra = 1, + Ket, + Aux, + }; + + class Vertex { + public: + Vertex(Origin origin, std::size_t terminal_idx, std::size_t index_slot, + Symmetry terminal_symm); + + Origin getOrigin() const; + std::size_t getTerminalIndex() const; + std::size_t getIndexSlot() const; + Symmetry getTerminalSymmetry() const; + + bool operator<(const Vertex &rhs) const; + bool operator==(const Vertex &rhs) const; + + private: + Origin origin; + std::size_t terminal_idx; + std::size_t index_slot; + Symmetry terminal_symm; + }; + // clang-format off /// @brief Edge in a TensorNetwork = the Index annotating it + a pair of indices to identify which Tensor terminals it's connected to @@ -53,83 +85,58 @@ class TensorNetwork { class Edge { public: Edge() = default; - explicit Edge(int terminal_idx, int position = 0) - : first_(0), second_(terminal_idx), second_position_(position) {} - Edge(int terminal_idx, const Index *idxptr, int position = 0) - : first_(0), - second_(terminal_idx), - idxptr_(idxptr), - second_position_(position) {} - // Edge(const Edge&) = default; - // Edge(Edge&&) = default; - // Edge& operator=(const Edge&) = default; - // Edge& operator=(Edge&&) = default; - - Edge &connect_to(int terminal_idx, int position = 0) { - assert(first_ == 0 || second_ == 0); // not connected yet - assert(terminal_idx != 0); // valid idx - if (second_ == 0) { // unconnected Edge - second_ = terminal_idx; - second_position_ = position; - } else if (std::abs(second_) < - std::abs(terminal_idx)) { // connected to 2 Edges? ensure - // first_ < second_ - assert(first_ == 0); // there are slots left - first_ = second_; - first_position_ = second_position_; - second_ = terminal_idx; - second_position_ = position; - } else { // put into first slot - first_ = terminal_idx; - first_position_ = position; + explicit Edge(Vertex vertex) : first(std::move(vertex)), second() {} + Edge(Vertex vertex, Index index) + : first(std::move(vertex)), second(), index(std::move(index)) {} + + Edge &connect_to(Vertex vertex) { + assert(!second.has_value()); + + if (!first.has_value()) { + // unconnected Edge + first = std::move(vertex); + } else { + second = std::move(vertex); + if (second < first) { + // Ensure first <= second + std::swap(first, second); + } } return *this; } bool operator<(const Edge &other) const { - if (std::abs(first_) == std::abs(other.first_)) { - if (first_position_ == other.first_position_) { - if (std::abs(second_) == std::abs(other.second_)) { - return second_position_ < other.second_position_; - } else { - return std::abs(second_) < std::abs(other.second_); - } - } else { - return first_position_ < other.first_position_; - } - } else { - return std::abs(first_) < std::abs(other.first_); + if (vertex_count() != other.vertex_count()) { + // Ensure external indices (edges that are only attached to a tensor on + // one side) always come before internal ones + return vertex_count() < other.vertex_count(); } + + if (!(first == other.first)) { + return first < other.first; + } + + return second < other.second; } bool operator==(const Edge &other) const { - return std::abs(first_) == std::abs(other.first_) && - std::abs(second_) == std::abs(other.second_) && - first_position_ == other.first_position_ && - second_position_ == other.second_position_; + return first == other.first && second == other.second; } - auto first() const { return first_; } - auto second() const { return second_; } - auto first_position() const { return first_position_; } - auto second_position() const { return second_position_; } + const Vertex &first_vertex() const { return first.value(); } + const Vertex &second_vertex() const { return second.value(); } /// @return the number of attached terminals (0, 1, or 2) - auto size() const { return (first_ != 0) ? 2 : ((second_ != 0) ? 1 : 0); } - - const Index &idx() const { - assert(idxptr_ != nullptr); - return *idxptr_; + std::size_t vertex_count() const { + return second.has_value() ? 2 : (first.has_value() ? 1 : 0); } + const Index &idx() const { return index; } + private: - // if only connected to 1 terminal, this is always 0 - // otherwise first_ <= second_ - int first_ = 0; - int second_ = 0; - const Index *idxptr_ = nullptr; - int first_position_ = 0; - int second_position_ = 0; + std::optional first; + std::optional second; + Index index; }; enum class VertexType { @@ -137,25 +144,39 @@ class TensorNetwork { SPBundle, TensorBra, TensorKet, - TensorBraKet, + TensorAux, TensorCore }; - public: + struct Graph { + std::unique_ptr bliss_graph; + std::vector vertex_labels; + std::vector vertex_colors; + std::vector vertex_types; + + Graph() = default; + + std::size_t vertex_to_index_idx(std::size_t vertex) const; + std::size_t vertex_to_tensor_idx(std::size_t vertex) const; + }; + /// @throw std::logic_error if exprptr_range contains a non-tensor /// @note uses RTTI template - TensorNetwork(ExprPtrRange &exprptr_range) { - for (auto &&ex : exprptr_range) { - auto t = std::dynamic_pointer_cast(ex); + TensorNetwork(const ExprPtrRange &exprptr_range) { + for (const auto &ex : exprptr_range) { + ExprPtr clone = ex.clone(); + auto t = std::dynamic_pointer_cast(clone); if (t) { - tensors_.emplace_back(t); + tensors_.emplace_back(std::move(t)); } else { throw std::logic_error( "TensorNetwork::TensorNetwork: non-tensors in the given expression " "range"); } } + + init_edges(); } /// @return const reference to the sequence of tensors @@ -172,7 +193,7 @@ class TensorNetwork { /// @param named_indices specifies the indices that cannot be renamed, i.e. /// their labels are meaningful; default is nullptr, which results in external /// indices treated as named indices - /// @return biproduct of canonicalization (e.g. phase); if none, returns + /// @return byproduct of canonicalization (e.g. phase); if none, returns /// nullptr ExprPtr canonicalize( const container::vector &cardinal_tensor_labels = {}, @@ -189,46 +210,12 @@ class TensorNetwork { /// @c (((T3*T1)*T2)*T0) . container::svector> factorize(); - private: - // source tensors and indices - container::svector tensors_; - - struct FullLabelCompare { - using is_transparent = void; - bool operator()(const Edge &first, const Edge &second) const { - return first.idx().full_label() < second.idx().full_label(); - } - bool operator()(const Edge &first, const std::wstring_view &second) const { - return first.idx().full_label() < second; - } - bool operator()(const std::wstring_view &first, const Edge &second) const { - return first < second.idx().full_label(); - } - }; - // Index -> Edge, sorted by full label - mutable container::set edges_; - // set to true by init_edges(); - mutable bool have_edges_ = false; - // ext indices do not connect tensors - // sorted by *label* (not full label) of the corresponding value (Index) - // this ensures that proto indices are not considered and all internal indices - // have unique labels (not full labels) - mutable named_indices_t ext_indices_; - - // replacements of anonymous indices produced by the last call to - // canonicalize() - container::map idxrepl_; - - /// initializes edges_ and ext_indices_ - void init_edges() const; - - public: /// accessor for the Edge object sequence /// @return const reference to the sequence container of Edge objects, sorted /// by their Index's full label /// @sa Edge const auto &edges() const { - init_edges(); + assert(have_edges_); return edges_; } @@ -238,7 +225,7 @@ class TensorNetwork { /// @note The external indices are sorted by *label* (not full label) of the /// corresponding value (Index) const auto &ext_indices() const { - if (edges_.empty()) init_edges(); + assert(have_edges_); return ext_indices_; } @@ -248,15 +235,13 @@ class TensorNetwork { /// canonicalize() const auto &idxrepl() const { return idxrepl_; }; - public: /// @brief converts the network into a Bliss graph whose vertices are indices /// and tensor vertex representations /// @param[in] named_indices pointer to the set of named indices (ordinarily, /// this includes all external indices); /// default is nullptr, which means use all external indices for /// named indices - /// @return {shared_ptr to Graph, vector of vertex labels, vector of vertex - /// colors, vector of vertex types} + /// @return The created Graph object /// @note Rules for constructing the graph: /// - Indices with protoindices are connected to their protoindices, @@ -273,11 +258,68 @@ class TensorNetwork { /// tensor; terminal vertices are colored by the color of its tensor, /// with the color of symm/antisymm terminals augmented by the /// terminal's type (bra/ket). - std::tuple, std::vector, - std::vector, std::vector> - make_bliss_graph(const named_indices_t *named_indices = nullptr) const; + Graph create_graph(const named_indices_t *named_indices = nullptr) const; + + private: + // source tensors and indices + container::svector tensors_; + + struct FullLabelCompare { + using is_transparent = void; + bool operator()(const Edge &first, const Edge &second) const { + return first.idx().full_label() < second.idx().full_label(); + } + bool operator()(const Edge &first, const std::wstring_view &second) const { + return first.idx().full_label() < second; + } + bool operator()(const std::wstring_view &first, const Edge &second) const { + return first < second.idx().full_label(); + } + }; + // Index -> Edge, sorted by full label + container::set edges_; + // set to true by init_edges(); + bool have_edges_ = false; + // ext indices do not connect tensors + // sorted by *label* (not full label) of the corresponding value (Index) + // this ensures that proto indices are not considered and all internal indices + // have unique labels (not full labels) + named_indices_t ext_indices_; + + // replacements of anonymous indices produced by the last call to + // canonicalize() + container::map idxrepl_; + + /// initializes edges_ and ext_indices_ + void init_edges(); + + /// Canonicalizes the network graph representation + /// Note: The explicit order of tensors and labelling of indices + /// remains undefined. + void canonicalize_graph(const named_indices_t &named_indices); + + /// Canonicalizes every individual tensor for itself + /// @returns The byproduct of the canonicalizations + ExprPtr canonicalize_individual_tensors(const named_indices_t &named_indices); }; +template +std::basic_ostream &operator<<( + std::basic_ostream &stream, TensorNetwork::Origin origin) { + switch (origin) { + case TensorNetwork::Origin::Bra: + stream << "Bra"; + break; + case TensorNetwork::Origin::Ket: + stream << "Ket"; + break; + case TensorNetwork::Origin::Aux: + stream << "Aux"; + break; + } + return stream; +} + } // namespace sequant #endif // SEQUANT_TENSOR_NETWORK_H diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index fabb7ba4d..44a4d5f54 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -4,20 +4,21 @@ #include "catch.hpp" -#include -#include -#include -#include -#include -#include #include #include #include +#include #include #include #include #include +#include +#include +#include #include +#include +#include +#include #include #include @@ -33,16 +34,106 @@ #include #include #include +#include +#include #include // TODO: Add test cases with auxiliary indices +std::string to_utf8(const std::wstring& wstr) { + using convert_type = std::codecvt_utf8; + std::wstring_convert converter; + return converter.to_bytes(wstr); +} + +namespace sequant { +class TensorNetworkAccessor { + public: + auto get_canonical_bliss_graph( + sequant::TensorNetwork tn, + const sequant::TensorNetwork::named_indices_t* named_indices = nullptr) { + tn.canonicalize_graph(named_indices ? *named_indices : tn.ext_indices_); + tn.init_edges(); + auto graph = tn.create_graph(named_indices); + return std::make_pair(std::move(graph.bliss_graph), graph.vertex_labels); + } +}; +} // namespace sequant + TEST_CASE("TensorNetwork", "[elements]") { using namespace sequant; using namespace sequant::mbpt::sr; + /* + SECTION("dummy") { + using Edge = TensorNetwork::Edge; + using Vertex = TensorNetwork::Vertex; + using Origin = TensorNetwork::Origin; + + Vertex v1(Origin::Bra, 0, 0, Symmetry::antisymm); + Vertex v2(Origin::Ket, 0, 0, Symmetry::antisymm); + Vertex v3(Origin::Ket, 2, 0, Symmetry::antisymm); + Vertex v4(Origin::Ket, 1, 0, Symmetry::antisymm); + + Edge e1(v1, {}); + e1.connect_to(v3); + + Edge e2(v2, {}); + e2.connect_to(v3); + + std::wcout << std::boolalpha << (e1 < e2) << " reverse " << (e2 < e1) + << " with self " << (e1 < e1) << std::endl; std::abort(); + } + */ + + SECTION("Edges") { + using Vertex = TensorNetwork::Vertex; + using Edge = TensorNetwork::Edge; + using Origin = TensorNetwork::Origin; + + Vertex v1(Origin::Bra, 0, 1, Symmetry::antisymm); + Vertex v2(Origin::Bra, 0, 0, Symmetry::antisymm); + Vertex v3(Origin::Ket, 1, 0, Symmetry::symm); + Vertex v4(Origin::Ket, 1, 3, Symmetry::symm); + Vertex v5(Origin::Bra, 3, 0, Symmetry::nonsymm); + Vertex v6(Origin::Bra, 3, 2, Symmetry::nonsymm); + + const Index dummy; + + Edge e1(v1, dummy); + e1.connect_to(v4); + Edge e2(v2, dummy); + e2.connect_to(v3); + Edge e3(v3, dummy); + e3.connect_to(v5); + Edge e4(v4, dummy); + e4.connect_to(v6); + + Edge e5(v4, dummy); + + // Due to tensor symmetries, these edges are considered equal + REQUIRE(e1 == e2); + REQUIRE(!(e1 < e2)); + REQUIRE(!(e2 < e1)); + + // Smallest terminal index wins + REQUIRE(!(e1 == e3)); + REQUIRE(e1 < e3); + REQUIRE(!(e3 < e1)); + + // For non-symmetric tensors the connection slot is taken into account + REQUIRE(!(e3 == e4)); + REQUIRE(e3 < e4); + REQUIRE(!(e4 < e3)); + + // Unconnected edges always come before fully connected ones + REQUIRE(!(e5 == e1)); + REQUIRE(e5 < e1); + REQUIRE(!(e1 < e5)); + } + SECTION("constructors") { { // with Tensors auto t1 = @@ -105,83 +196,214 @@ TEST_CASE("TensorNetwork", "[elements]") { } // SECTION("accessors") SECTION("canonicalizer") { - { - { // with no external indices, hence no named indices whatsoever - Index::reset_tmp_index(); - constexpr const auto V = Vacuum::SingleProduct; - auto t1 = - ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}, WstrList{}); - auto t2 = ex(WstrList{L"i_1"}, WstrList{L"i_2"}, V); - auto t1_x_t2 = t1 * t2; + SECTION("no externals") { + Index::reset_tmp_index(); + constexpr const auto V = Vacuum::SingleProduct; + auto t1 = + ex(L"F", WstrList{L"i_1"}, WstrList{L"i_2"}, WstrList{}); + auto t2 = ex(WstrList{L"i_1"}, WstrList{L"i_2"}, V); + auto t1_x_t2 = t1 * t2; + TensorNetwork tn(*t1_x_t2); + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); + + REQUIRE(size(tn.tensors()) == 2); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[0])); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[1])); + // std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) << + // std::endl; std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) << + // std::endl; + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == + L"{F^{{i_1}}_{{i_2}}}"); + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == + L"{\\tilde{a}^{{i_2}}_{{i_1}}}"); + REQUIRE(tn.idxrepl().size() == 2); + } + + SECTION("with externals") { + Index::reset_tmp_index(); + constexpr const auto V = Vacuum::SingleProduct; + auto t1 = + ex(L"F", WstrList{L"i_2"}, WstrList{L"i_17"}, WstrList{}); + auto t2 = ex(WstrList{L"i_2"}, WstrList{L"i_3"}, V); + auto t1_x_t2 = t1 * t2; + + // with all external named indices + SECTION("implicit") { TensorNetwork tn(*t1_x_t2); tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); REQUIRE(size(tn.tensors()) == 2); REQUIRE(std::dynamic_pointer_cast(tn.tensors()[0])); REQUIRE(std::dynamic_pointer_cast(tn.tensors()[1])); - // std::wcout << - // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) << - // std::endl; std::wcout << - // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) << - // std::endl; + // std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) << + // std::endl; std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) << + // std::endl; + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == + L"{\\tilde{a}^{{i_1}}_{{i_3}}}"); REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == - L"{F^{{i_1}}_{{i_2}}}"); + L"{F^{{i_{17}}}_{{i_1}}}"); + } + + // with explicit named indices + SECTION("explicit") { + Index::reset_tmp_index(); + TensorNetwork tn(*t1_x_t2); + + using named_indices_t = TensorNetwork::named_indices_t; + named_indices_t indices{Index{L"i_17"}}; + std::wcout << "Canonicalize " << to_latex(t1_x_t2) << std::endl; + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false, + &indices); + + REQUIRE(size(tn.tensors()) == 2); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[0])); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[1])); + // std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) + // << std::endl; std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) + // << std::endl; REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == L"{\\tilde{a}^{{i_2}}_{{i_1}}}"); - REQUIRE(tn.idxrepl().size() == 2); + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == + L"{F^{{i_{17}}}_{{i_2}}}"); } + } - { - Index::reset_tmp_index(); - constexpr const auto V = Vacuum::SingleProduct; - auto t1 = - ex(L"F", WstrList{L"i_2"}, WstrList{L"i_17"}, WstrList{}); - auto t2 = ex(WstrList{L"i_2"}, WstrList{L"i_3"}, V); - auto t1_x_t2 = t1 * t2; - - // with all external named indices - { - TensorNetwork tn(*t1_x_t2); - tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); - - REQUIRE(size(tn.tensors()) == 2); - REQUIRE(std::dynamic_pointer_cast(tn.tensors()[0])); - REQUIRE(std::dynamic_pointer_cast(tn.tensors()[1])); - // std::wcout << - // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) << - // std::endl; std::wcout << - // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) << - // std::endl; - REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == - L"{\\tilde{a}^{{i_1}}_{{i_3}}}"); - REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == - L"{F^{{i_{17}}}_{{i_1}}}"); + SECTION("Exhaustive SRCC example") { + // Note: the exact canonical form written here is implementation-defined + // and doesn't actually matter What does, is that all equivalent ways of + // writing it down, canonicalizes to the same exact form + const Product expectedExpr = + parse_expr( + L"A{i1,i2;a1,a2} g{i3,i4;a3,a4} t{a1,a3;i3,i4} t{a2,a4;i1,i2}", + Symmetry::antisymm) + .as(); + + const auto expected = expectedExpr.factors(); + + TensorNetworkAccessor accessor; + const auto [canonical_graph, canonical_graph_labels] = + accessor.get_canonical_bliss_graph(TensorNetwork(expected)); + + std::wcout << "Canonical graph:\n"; + canonical_graph->write_dot(std::wcout, canonical_graph_labels); + std::wcout << std::endl; + + std::vector indices; + for (std::size_t i = 0; i < expected.size(); ++i) { + const Tensor& tensor = expected[i].as(); + for (const Index& idx : tensor.indices()) { + if (std::find(indices.begin(), indices.end(), idx) == indices.end()) { + indices.push_back(idx); + } } + } + std::sort(indices.begin(), indices.end()); - // with explicit named indices - { - Index::reset_tmp_index(); - TensorNetwork tn(*t1_x_t2); - - using named_indices_t = TensorNetwork::named_indices_t; - named_indices_t indices{Index{L"i_17"}}; - tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false, - &indices); - - REQUIRE(size(tn.tensors()) == 2); - REQUIRE(std::dynamic_pointer_cast(tn.tensors()[0])); - REQUIRE(std::dynamic_pointer_cast(tn.tensors()[1])); - // std::wcout << - // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) - // << std::endl; std::wcout << - // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) - // << std::endl; - REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == - L"{\\tilde{a}^{{i_2}}_{{i_1}}}"); - REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == - L"{F^{{i_{17}}}_{{i_2}}}"); - } + const auto original_indices = indices; + + // Make sure to clone all expressions in order to not accidentally modify + // the ones in expected (even though they are const... the pointer-like + // semantics of expressions messes with const semantics) + std::remove_const_t factors; + for (const auto& factor : expected) { + factors.push_back(factor.clone()); } + std::sort(factors.begin(), factors.end()); + + const auto is_occ = [](const Index& idx) { + return idx.space() == Index(L"i_1").space(); + }; + + // Iterate over all tensor permutations and all permutations of possible + // index name swaps + REQUIRE(std::is_sorted(factors.begin(), factors.end())); + REQUIRE(std::is_sorted(indices.begin(), indices.end())); + REQUIRE(std::is_partitioned(indices.begin(), indices.end(), is_occ)); + REQUIRE(std::partition_point(indices.begin(), indices.end(), is_occ) == + indices.begin() + 4); + std::size_t total_variations = 0; + do { + do { + do { + total_variations++; + + // Compute index replacements + container::map idxrepl; + for (std::size_t i = 0; i < indices.size(); ++i) { + REQUIRE(original_indices[i].space() == indices[i].space()); + + idxrepl.insert( + std::make_pair(original_indices.at(i), indices.at(i))); + } + + // Apply index replacements to a copy of the current tensor + // permutation + auto copy = factors; + for (ExprPtr& expr : copy) { + expr.as().transform_indices(idxrepl); + reset_tags(expr.as()); + } + + TensorNetwork tn(copy); + + // At the heart of our canonicalization lies the fact that we can + // always create the uniquely defined canonical graph for a given + // network + const auto [current_graph, current_graph_labels] = + accessor.get_canonical_bliss_graph(tn); + if (current_graph->cmp(*canonical_graph) != 0) { + std::wcout << "Canonical graph for " + << deparse_expr(ex(copy)) << ":\n"; + current_graph->write_dot(std::wcout, current_graph_labels); + std::wcout << std::endl; + } + REQUIRE(current_graph->cmp(*canonical_graph) == 0); + + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), + false); + + std::vector actual; + std::transform(tn.tensors().begin(), tn.tensors().end(), + std::back_inserter(actual), [](const auto& t) { + assert(std::dynamic_pointer_cast(t)); + return std::dynamic_pointer_cast(t); + }); + + // The canonical graph must not change due to the other + // canonicalization steps we perform + REQUIRE(accessor.get_canonical_bliss_graph(TensorNetwork(actual)) + .first->cmp(*canonical_graph) == 0); + + REQUIRE(actual.size() == expected.size()); + + if (!std::equal(expected.begin(), expected.end(), actual.begin())) { + std::wostringstream sstream; + sstream + << "Expected all tensors to be equal (actual == expected), " + "but got:\n"; + for (std::size_t i = 0; i < expected.size(); ++i) { + std::wstring equality = + actual[i] == expected[i] ? L" == " : L" != "; + + sstream << deparse_expr(actual[i]) << equality + << deparse_expr(expected[i]) << "\n"; + } + sstream << "\nInput was " << deparse_expr(ex(factors)) + << "\n"; + FAIL(to_utf8(sstream.str())); + } + } while (std::next_permutation(indices.begin() + 4, indices.end())); + } while (std::next_permutation(indices.begin(), indices.begin() + 4)); + } while (std::next_permutation(factors.begin(), factors.end())); + + // 4! (tensors) * 4! (internal indices) * 4! (external indices) + REQUIRE(total_variations == 24 * 24 * 24); } } // SECTION("canonicalizer") @@ -194,166 +416,174 @@ TEST_CASE("TensorNetwork", "[elements]") { {A(-2), H_(2), T_(2), T_(2), T_(2)}); // canonicalize to avoid dependence on the implementation details of // mbpt::sr::make_op + std::wcout << "Here it comes" << std::endl; canonicalize(tmp); + std::wcout << "That was it" << std::endl; // std::wcout << "A2*H2*T2*T2*T2 = " << to_latex(tmp) << std::endl; TensorNetwork tn(tmp->as().factors()); + std::wcout << "As equation: " + << to_latex(canonicalize(ex(ExprPtrList{tmp}))) + << std::endl; + // make graph - REQUIRE_NOTHROW(tn.make_bliss_graph()); - auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(); + REQUIRE_NOTHROW(tn.create_graph()); + TensorNetwork::Graph graph = tn.create_graph(); // create dot std::basic_ostringstream oss; - REQUIRE_NOTHROW(graph->write_dot(oss, vlabels)); + REQUIRE_NOTHROW(graph.bliss_graph->write_dot(oss, graph.vertex_labels)); + std::wcout << ">>>>>>>>>>>>>>>> This one" << std::endl; std::wcout << "oss.str() = " << std::endl << oss.str() << std::endl; + std::wcout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; REQUIRE(oss.str() == - L"graph g {\n" - "v0 [label=\"{a_1}\"; color=\"#9e3,ba0\"];\n" - "v0 -- v29\n" - "v0 -- v58\n" - "v1 [label=\"{a_2}\"; color=\"#9e3,ba0\"];\n" - "v1 -- v29\n" - "v1 -- v58\n" - "v2 [label=\"{a_3}\"; color=\"#9e3,ba0\"];\n" - "v2 -- v33\n" - "v2 -- v54\n" - "v3 [label=\"{a_4}\"; color=\"#9e3,ba0\"];\n" - "v3 -- v33\n" - "v3 -- v54\n" - "v4 [label=\"{a_5}\"; color=\"#9e3,ba0\"];\n" - "v4 -- v37\n" - "v4 -- v50\n" - "v5 [label=\"{a_6}\"; color=\"#9e3,ba0\"];\n" - "v5 -- v37\n" - "v5 -- v50\n" - "v6 [label=\"{a_7}\"; color=\"#9e3,ba0\"];\n" - "v6 -- v22\n" - "v6 -- v41\n" - "v7 [label=\"{a_8}\"; color=\"#9e3,ba0\"];\n" - "v7 -- v22\n" - "v7 -- v41\n" - "v8 [label=\"{i_1}\"; color=\"#a78,ee8\"];\n" - "v8 -- v30\n" - "v8 -- v57\n" - "v9 [label=\"{i_2}\"; color=\"#a78,ee8\"];\n" - "v9 -- v30\n" - "v9 -- v57\n" - "v10 [label=\"{i_3}\"; color=\"#a78,ee8\"];\n" - "v10 -- v34\n" - "v10 -- v53\n" - "v11 [label=\"{i_4}\"; color=\"#a78,ee8\"];\n" - "v11 -- v34\n" - "v11 -- v53\n" - "v12 [label=\"{i_5}\"; color=\"#a78,ee8\"];\n" - "v12 -- v38\n" - "v12 -- v49\n" - "v13 [label=\"{i_6}\"; color=\"#a78,ee8\"];\n" - "v13 -- v38\n" - "v13 -- v49\n" - "v14 [label=\"{i_7}\"; color=\"#a78,ee8\"];\n" - "v14 -- v21\n" - "v14 -- v42\n" - "v15 [label=\"{i_8}\"; color=\"#a78,ee8\"];\n" - "v15 -- v21\n" - "v15 -- v42\n" - "v16 [label=\"{\\kappa_1}\"; color=\"#703,062\"];\n" - "v16 -- v25\n" - "v16 -- v46\n" - "v17 [label=\"{\\kappa_2}\"; color=\"#703,062\"];\n" - "v17 -- v25\n" - "v17 -- v46\n" - "v18 [label=\"{\\kappa_3}\"; color=\"#703,062\"];\n" - "v18 -- v26\n" - "v18 -- v45\n" - "v19 [label=\"{\\kappa_4}\"; color=\"#703,062\"];\n" - "v19 -- v26\n" - "v19 -- v45\n" - "v20 [label=\"A\"; color=\"#518,020\"];\n" - "v20 -- v23\n" - "v21 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v21 -- v23\n" - "v22 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v22 -- v23\n" - "v23 [label=\"bka\"; color=\"#518,020\"];\n" - "v24 [label=\"g\"; color=\"#2e0,351\"];\n" - "v24 -- v27\n" - "v25 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v25 -- v27\n" - "v26 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v26 -- v27\n" - "v27 [label=\"bka\"; color=\"#2e0,351\"];\n" - "v28 [label=\"t\"; color=\"#43,e44\"];\n" - "v28 -- v31\n" - "v29 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v29 -- v31\n" - "v30 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v30 -- v31\n" - "v31 [label=\"bka\"; color=\"#43,e44\"];\n" - "v32 [label=\"t\"; color=\"#43,e44\"];\n" - "v32 -- v35\n" - "v33 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v33 -- v35\n" - "v34 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v34 -- v35\n" - "v35 [label=\"bka\"; color=\"#43,e44\"];\n" - "v36 [label=\"t\"; color=\"#43,e44\"];\n" - "v36 -- v39\n" - "v37 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v37 -- v39\n" - "v38 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v38 -- v39\n" - "v39 [label=\"bka\"; color=\"#43,e44\"];\n" - "v40 [label=\"ã\"; color=\"#cbf,be5\"];\n" - "v40 -- v43\n" - "v41 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v41 -- v43\n" - "v42 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v42 -- v43\n" - "v43 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "v44 [label=\"ã\"; color=\"#cbf,be5\"];\n" - "v44 -- v47\n" - "v45 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v45 -- v47\n" - "v46 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v46 -- v47\n" - "v47 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "v48 [label=\"ã\"; color=\"#cbf,be5\"];\n" - "v48 -- v51\n" - "v49 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v49 -- v51\n" - "v50 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v50 -- v51\n" - "v51 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "v52 [label=\"ã\"; color=\"#cbf,be5\"];\n" - "v52 -- v55\n" - "v53 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v53 -- v55\n" - "v54 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v54 -- v55\n" - "v55 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "v56 [label=\"ã\"; color=\"#cbf,be5\"];\n" - "v56 -- v59\n" - "v57 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v57 -- v59\n" - "v58 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v58 -- v59\n" - "v59 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "}\n"); + L"graph g {\n" + "v0 [label=\"{a_1}\"; color=\"#9e3,ba0\"];\n" + "v0 -- v29\n" + "v0 -- v58\n" + "v1 [label=\"{a_2}\"; color=\"#9e3,ba0\"];\n" + "v1 -- v29\n" + "v1 -- v58\n" + "v2 [label=\"{a_3}\"; color=\"#9e3,ba0\"];\n" + "v2 -- v33\n" + "v2 -- v54\n" + "v3 [label=\"{a_4}\"; color=\"#9e3,ba0\"];\n" + "v3 -- v33\n" + "v3 -- v54\n" + "v4 [label=\"{a_5}\"; color=\"#9e3,ba0\"];\n" + "v4 -- v37\n" + "v4 -- v50\n" + "v5 [label=\"{a_6}\"; color=\"#9e3,ba0\"];\n" + "v5 -- v37\n" + "v5 -- v50\n" + "v6 [label=\"{a_7}\"; color=\"#9e3,ba0\"];\n" + "v6 -- v22\n" + "v6 -- v41\n" + "v7 [label=\"{a_8}\"; color=\"#9e3,ba0\"];\n" + "v7 -- v22\n" + "v7 -- v41\n" + "v8 [label=\"{i_1}\"; color=\"#a78,ee8\"];\n" + "v8 -- v30\n" + "v8 -- v57\n" + "v9 [label=\"{i_2}\"; color=\"#a78,ee8\"];\n" + "v9 -- v30\n" + "v9 -- v57\n" + "v10 [label=\"{i_3}\"; color=\"#a78,ee8\"];\n" + "v10 -- v34\n" + "v10 -- v53\n" + "v11 [label=\"{i_4}\"; color=\"#a78,ee8\"];\n" + "v11 -- v34\n" + "v11 -- v53\n" + "v12 [label=\"{i_5}\"; color=\"#a78,ee8\"];\n" + "v12 -- v38\n" + "v12 -- v49\n" + "v13 [label=\"{i_6}\"; color=\"#a78,ee8\"];\n" + "v13 -- v38\n" + "v13 -- v49\n" + "v14 [label=\"{i_7}\"; color=\"#a78,ee8\"];\n" + "v14 -- v21\n" + "v14 -- v42\n" + "v15 [label=\"{i_8}\"; color=\"#a78,ee8\"];\n" + "v15 -- v21\n" + "v15 -- v42\n" + "v16 [label=\"{\\kappa_1}\"; color=\"#703,062\"];\n" + "v16 -- v25\n" + "v16 -- v46\n" + "v17 [label=\"{\\kappa_2}\"; color=\"#703,062\"];\n" + "v17 -- v25\n" + "v17 -- v46\n" + "v18 [label=\"{\\kappa_3}\"; color=\"#703,062\"];\n" + "v18 -- v26\n" + "v18 -- v45\n" + "v19 [label=\"{\\kappa_4}\"; color=\"#703,062\"];\n" + "v19 -- v26\n" + "v19 -- v45\n" + "v20 [label=\"A\"; color=\"#2ef,7ff\"];\n" + "v20 -- v23\n" + "v21 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v21 -- v23\n" + "v22 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v22 -- v23\n" + "v23 [label=\"bka\"; color=\"#2ef,7ff\"];\n" + "v24 [label=\"g\"; color=\"#96c,060\"];\n" + "v24 -- v27\n" + "v25 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v25 -- v27\n" + "v26 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v26 -- v27\n" + "v27 [label=\"bka\"; color=\"#96c,060\"];\n" + "v28 [label=\"t\"; color=\"#0f,016\"];\n" + "v28 -- v31\n" + "v29 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v29 -- v31\n" + "v30 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v30 -- v31\n" + "v31 [label=\"bka\"; color=\"#0f,016\"];\n" + "v32 [label=\"t\"; color=\"#0f,016\"];\n" + "v32 -- v35\n" + "v33 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v33 -- v35\n" + "v34 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v34 -- v35\n" + "v35 [label=\"bka\"; color=\"#0f,016\"];\n" + "v36 [label=\"t\"; color=\"#0f,016\"];\n" + "v36 -- v39\n" + "v37 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v37 -- v39\n" + "v38 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v38 -- v39\n" + "v39 [label=\"bka\"; color=\"#0f,016\"];\n" + "v40 [label=\"ã\"; color=\"#116,f93\"];\n" + "v40 -- v43\n" + "v41 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v41 -- v43\n" + "v42 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v42 -- v43\n" + "v43 [label=\"bka\"; color=\"#116,f93\"];\n" + "v44 [label=\"ã\"; color=\"#116,f93\"];\n" + "v44 -- v47\n" + "v45 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v45 -- v47\n" + "v46 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v46 -- v47\n" + "v47 [label=\"bka\"; color=\"#116,f93\"];\n" + "v48 [label=\"ã\"; color=\"#116,f93\"];\n" + "v48 -- v51\n" + "v49 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v49 -- v51\n" + "v50 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v50 -- v51\n" + "v51 [label=\"bka\"; color=\"#116,f93\"];\n" + "v52 [label=\"ã\"; color=\"#116,f93\"];\n" + "v52 -- v55\n" + "v53 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v53 -- v55\n" + "v54 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v54 -- v55\n" + "v55 [label=\"bka\"; color=\"#116,f93\"];\n" + "v56 [label=\"ã\"; color=\"#116,f93\"];\n" + "v56 -- v59\n" + "v57 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v57 -- v59\n" + "v58 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v58 -- v59\n" + "v59 [label=\"bka\"; color=\"#116,f93\"];\n" + "}\n"); // compute automorphism group { bliss::Stats stats; - graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); std::vector> aut_generators; auto save_aut = [&aut_generators](const unsigned int n, const unsigned int* aut) { aut_generators.emplace_back(aut, aut + n); }; - graph->find_automorphisms(stats, &bliss::aut_hook, - &save_aut); + graph.bliss_graph->find_automorphisms( + stats, &bliss::aut_hook, &save_aut); std::basic_ostringstream oss; - bliss::print_auts(aut_generators, oss, decltype(vlabels){}); + bliss::print_auts(aut_generators, oss, decltype(graph.vertex_labels){}); REQUIRE(oss.str() == L"(18,19)\n" "(16,17)\n" @@ -372,7 +602,7 @@ TEST_CASE("TensorNetwork", "[elements]") { // change to 1 to user vertex labels rather than indices if (0) { std::basic_ostringstream oss2; - bliss::print_auts(aut_generators, oss2, vlabels); + bliss::print_auts(aut_generators, oss2, graph.vertex_labels); std::wcout << oss2.str() << std::endl; } } @@ -401,31 +631,31 @@ TEST_CASE("TensorNetwork", "[elements]") { // N.B. treat all indices as dummy so that the automorphism ignores the using named_indices_t = TensorNetwork::named_indices_t; named_indices_t indices{}; - REQUIRE_NOTHROW(tn.make_bliss_graph(&indices)); - auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(&indices); + REQUIRE_NOTHROW(tn.create_graph(&indices)); + TensorNetwork::Graph graph = tn.create_graph(&indices); // create dot { std::basic_ostringstream oss; - REQUIRE_NOTHROW(graph->write_dot(oss, vlabels)); + REQUIRE_NOTHROW(graph.bliss_graph->write_dot(oss, graph.vertex_labels)); // std::wcout << "oss.str() = " << std::endl << oss.str() << std::endl; } bliss::Stats stats; - graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); std::vector> aut_generators; auto save_aut = [&aut_generators](const unsigned int n, const unsigned int* aut) { aut_generators.emplace_back(aut, aut + n); }; - graph->find_automorphisms(stats, &bliss::aut_hook, - &save_aut); + graph.bliss_graph->find_automorphisms( + stats, &bliss::aut_hook, &save_aut); CHECK(aut_generators.size() == 2); // there are 2 generators, i1<->i2, i3<->i4 std::basic_ostringstream oss; - bliss::print_auts(aut_generators, oss, vlabels); + bliss::print_auts(aut_generators, oss, graph.vertex_labels); CHECK(oss.str() == L"({i_3},{i_4})\n({i_1},{i_2})\n"); // std::wcout << oss.str() << std::endl; } @@ -524,8 +754,9 @@ TEST_CASE("TensorNetwork", "[elements]") { CHECK(utensors.size() == static_cast(n)); auto dtensors = contravariant_indices | ranges::views::chunk(N) | ranges::views::transform([&](const auto& idxs) { - return ex(L"d", std::vector{}, std::vector{}, - idxs, Symmetry::nonsymm); + return ex(L"d", std::vector{}, + std::vector{}, idxs, + Symmetry::nonsymm); }) | ranges::to_vector; CHECK(dtensors.size() == 1); @@ -546,12 +777,13 @@ TEST_CASE("TensorNetwork", "[elements]") { << expr->to_latex() << std::endl; // make graph - REQUIRE_NOTHROW(tn.make_bliss_graph()); - auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(); + REQUIRE_NOTHROW(tn.create_graph()); + TensorNetwork::Graph graph = tn.create_graph(); // create dot std::basic_ostringstream oss; - REQUIRE_NOTHROW(graph->write_dot(oss, vlabels)); + REQUIRE_NOTHROW( + graph.bliss_graph->write_dot(oss, graph.vertex_labels)); std::wcout << "bliss graph:" << std::endl << oss.str() << std::endl; } From 4fb866a54bdbda43e773b375bc5908c24f1d948c Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 29 Jan 2024 08:30:35 +0100 Subject: [PATCH 08/85] Try to adapt wick impl to new graph repr --- SeQuant/core/wick.impl.hpp | 94 +++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/SeQuant/core/wick.impl.hpp b/SeQuant/core/wick.impl.hpp index af2015378..7f884f295 100644 --- a/SeQuant/core/wick.impl.hpp +++ b/SeQuant/core/wick.impl.hpp @@ -403,6 +403,14 @@ struct NullNormalOperatorCanonicalizerDeregister { } // namespace detail +inline bool edges_share_origin(const TensorNetwork::Edge &e1, + const TensorNetwork::Edge &e2) { + return e1.first_vertex() == e2.first_vertex() || + e1.second_vertex() == e2.first_vertex() || + e1.first_vertex() == e2.second_vertex() || + e1.second_vertex() == e2.second_vertex(); +} + template ExprPtr WickTheorem::compute(const bool count_only) { if (input_.vacuum() != get_default_context().vacuum()) @@ -537,15 +545,15 @@ ExprPtr WickTheorem::compute(const bool count_only) { // construct graph representation of the tensor product TensorNetwork tn(expr_input_->as().factors()); - auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(); - const auto n = vlabels.size(); - assert(vtypes.size() == n); + auto graph = tn.create_graph(); + const auto n = graph.vertex_labels.size(); + assert(graph.vertex_types.size() == n); const auto &tn_edges = tn.edges(); const auto &tn_tensors = tn.tensors(); if (Logger::get_instance().wick_topology) { std::basic_ostringstream oss; - graph->write_dot(oss, vlabels); + graph.bliss_graph->write_dot(oss, graph.vertex_labels); std::wcout << "WickTheorem::compute: colored graph produced from TN = " << std::endl @@ -578,16 +586,24 @@ ExprPtr WickTheorem::compute(const bool count_only) { // NormalOperators are not reordered by canonicalization, hence the // ordinal can be computed by counting std::size_t nop_ord = 0; + std::size_t edge_idx = 0; for (size_t v = 0; v != n; ++v) { - if (vtypes[v] == TensorNetwork::VertexType::TensorCore && - (std::find(nop_labels_begin, nop_labels_end, vlabels[v]) != - nop_labels_end)) { + if (graph.vertex_types[v] == + TensorNetwork::VertexType::TensorCore && + (std::find(nop_labels_begin, nop_labels_end, + graph.vertex_labels[v]) != nop_labels_end)) { auto insertion_result = nop_vidx_ord.emplace(v, nop_ord++); assert(insertion_result.second); } - if (vtypes[v] == TensorNetwork::VertexType::Index && + if (graph.vertex_types[v] == TensorNetwork::VertexType::Index && !input_.empty()) { - auto &idx = (tn_edges.begin() + v)->idx(); + // Underlying assumption: The order of index vertices + // corresponds to the order of associated edges in tn_edges + assert(edge_idx < tn_edges.size()); + auto edge_it = tn_edges.begin(); + std::advance(edge_it, edge_idx); + + auto &idx = edge_it->idx(); auto idx_it_in_opseq = ranges::find_if( opseq_view, [&idx](const auto &v) { return v.index() == idx; }); @@ -597,6 +613,8 @@ ExprPtr WickTheorem::compute(const bool count_only) { auto insertion_result = index_vidx_ord.emplace(v, ord); assert(insertion_result.second); } + + edge_idx++; } } } @@ -605,19 +623,19 @@ ExprPtr WickTheorem::compute(const bool count_only) { std::vector> aut_generators; { bliss::Stats stats; - graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); auto save_aut = [&aut_generators](const unsigned int n, const unsigned int *aut) { aut_generators.emplace_back(aut, aut + n); }; - graph->find_automorphisms( + graph.bliss_graph->find_automorphisms( stats, &bliss::aut_hook, &save_aut); if (Logger::get_instance().wick_topology) { std::basic_ostringstream oss2; - bliss::print_auts(aut_generators, oss2, vlabels); + bliss::print_auts(aut_generators, oss2, graph.vertex_labels); std::wcout << "WickTheorem::compute: colored graph " "automorphism generators = \n" << oss2.str() << std::endl; @@ -818,29 +836,37 @@ ExprPtr WickTheorem::compute(const bool count_only) { // Index partitions are constructed to *only* include Index // objects attached to the bra/ket of any NormalOperator! hence // need to use filter in computing partitions - auto exclude_index_vertex_pair = [&tn_tensors, &tn_edges](size_t v1, + auto exclude_index_vertex_pair = [&tn_tensors, &tn_edges, &graph](size_t v1, size_t v2) { - // v1 and v2 are vertex indices and also index the edges in the - // TensorNetwork - assert(v1 < tn_edges.size()); - assert(v2 < tn_edges.size()); - const auto &edge1 = *(tn_edges.begin() + v1); - const auto &edge2 = *(tn_edges.begin() + v2); - auto connected_to_same_nop = [&tn_tensors](int term1, int term2) { - if (term1 == term2 && term1 != 0) { - auto tensor_idx = std::abs(term1) - 1; + std::size_t first_edge_idx = graph.vertex_to_index_idx(v1); + std::size_t second_edge_idx = graph.vertex_to_index_idx(v2); + assert(first_edge_idx < tn_edges.size()); + assert(second_edge_idx < tn_edges.size()); + + auto edge_iter = tn_edges.begin(); + std::advance(edge_iter, first_edge_idx); + const auto &edge1 = *edge_iter; + + edge_iter = tn_edges.begin(); + std::advance(edge_iter, second_edge_idx); + const auto &edge2 = *edge_iter; + + auto connected_to_same_nop = [&tn_tensors]( + const TensorNetwork::Edge &lhs, + const TensorNetwork::Edge &rhs) { + if (lhs.vertex_count() != 2 || rhs.vertex_count() != 2) { + return false; + } + + if (edges_share_origin(lhs, rhs)) { const std::shared_ptr &tensor_ptr = - tn_tensors.at(tensor_idx); + tn_tensors.at(lhs.first_vertex().getTerminalIndex()); if (std::dynamic_pointer_cast>(tensor_ptr)) return true; } return false; }; - const bool exclude = - !(connected_to_same_nop(edge1.first(), edge2.first()) || - connected_to_same_nop(edge1.first(), edge2.second()) || - connected_to_same_nop(edge1.second(), edge2.first()) || - connected_to_same_nop(edge1.second(), edge2.second())); + const bool exclude = !connected_to_same_nop(edge1, edge2); return exclude; }; @@ -861,10 +887,16 @@ ExprPtr WickTheorem::compute(const bool count_only) { if (Logger::get_instance().wick_topology) { std::wcout << "WickTheorem::compute: topological index " "partitions:{\n"; - ranges::for_each(index_vidx2pidx, [&tn_edges](auto &&vidx_pidx) { + ranges::for_each(index_vidx2pidx, [&tn_edges, &graph](auto &&vidx_pidx) { auto &&[vidx, pidx] = vidx_pidx; - assert(vidx < tn_edges.size()); - auto &idx = (tn_edges.begin() + vidx)->idx(); + + auto edge_idx = graph.vertex_to_index_idx(vidx); + assert(edge_idx < tn_edges.size()); + + auto edge_it = tn_edges.begin(); + std::advance(edge_it, edge_idx); + auto &idx = edge_it->idx(); + std::wcout << "Index " << idx.full_label() << " -> partition " << pidx << "\n"; }); From 3d1f50f7361ff169b71b776e4dd5cb79213cf610 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 29 Jan 2024 08:30:57 +0100 Subject: [PATCH 09/85] Revert "Try to adapt wick impl to new graph repr" This reverts commit 4c5e8ea6fd934419e6052b11868dcd0ed38581f3. --- SeQuant/core/wick.impl.hpp | 94 +++++++++++++------------------------- 1 file changed, 31 insertions(+), 63 deletions(-) diff --git a/SeQuant/core/wick.impl.hpp b/SeQuant/core/wick.impl.hpp index 7f884f295..af2015378 100644 --- a/SeQuant/core/wick.impl.hpp +++ b/SeQuant/core/wick.impl.hpp @@ -403,14 +403,6 @@ struct NullNormalOperatorCanonicalizerDeregister { } // namespace detail -inline bool edges_share_origin(const TensorNetwork::Edge &e1, - const TensorNetwork::Edge &e2) { - return e1.first_vertex() == e2.first_vertex() || - e1.second_vertex() == e2.first_vertex() || - e1.first_vertex() == e2.second_vertex() || - e1.second_vertex() == e2.second_vertex(); -} - template ExprPtr WickTheorem::compute(const bool count_only) { if (input_.vacuum() != get_default_context().vacuum()) @@ -545,15 +537,15 @@ ExprPtr WickTheorem::compute(const bool count_only) { // construct graph representation of the tensor product TensorNetwork tn(expr_input_->as().factors()); - auto graph = tn.create_graph(); - const auto n = graph.vertex_labels.size(); - assert(graph.vertex_types.size() == n); + auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(); + const auto n = vlabels.size(); + assert(vtypes.size() == n); const auto &tn_edges = tn.edges(); const auto &tn_tensors = tn.tensors(); if (Logger::get_instance().wick_topology) { std::basic_ostringstream oss; - graph.bliss_graph->write_dot(oss, graph.vertex_labels); + graph->write_dot(oss, vlabels); std::wcout << "WickTheorem::compute: colored graph produced from TN = " << std::endl @@ -586,24 +578,16 @@ ExprPtr WickTheorem::compute(const bool count_only) { // NormalOperators are not reordered by canonicalization, hence the // ordinal can be computed by counting std::size_t nop_ord = 0; - std::size_t edge_idx = 0; for (size_t v = 0; v != n; ++v) { - if (graph.vertex_types[v] == - TensorNetwork::VertexType::TensorCore && - (std::find(nop_labels_begin, nop_labels_end, - graph.vertex_labels[v]) != nop_labels_end)) { + if (vtypes[v] == TensorNetwork::VertexType::TensorCore && + (std::find(nop_labels_begin, nop_labels_end, vlabels[v]) != + nop_labels_end)) { auto insertion_result = nop_vidx_ord.emplace(v, nop_ord++); assert(insertion_result.second); } - if (graph.vertex_types[v] == TensorNetwork::VertexType::Index && + if (vtypes[v] == TensorNetwork::VertexType::Index && !input_.empty()) { - // Underlying assumption: The order of index vertices - // corresponds to the order of associated edges in tn_edges - assert(edge_idx < tn_edges.size()); - auto edge_it = tn_edges.begin(); - std::advance(edge_it, edge_idx); - - auto &idx = edge_it->idx(); + auto &idx = (tn_edges.begin() + v)->idx(); auto idx_it_in_opseq = ranges::find_if( opseq_view, [&idx](const auto &v) { return v.index() == idx; }); @@ -613,8 +597,6 @@ ExprPtr WickTheorem::compute(const bool count_only) { auto insertion_result = index_vidx_ord.emplace(v, ord); assert(insertion_result.second); } - - edge_idx++; } } } @@ -623,19 +605,19 @@ ExprPtr WickTheorem::compute(const bool count_only) { std::vector> aut_generators; { bliss::Stats stats; - graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + graph->set_splitting_heuristic(bliss::Graph::shs_fsm); auto save_aut = [&aut_generators](const unsigned int n, const unsigned int *aut) { aut_generators.emplace_back(aut, aut + n); }; - graph.bliss_graph->find_automorphisms( + graph->find_automorphisms( stats, &bliss::aut_hook, &save_aut); if (Logger::get_instance().wick_topology) { std::basic_ostringstream oss2; - bliss::print_auts(aut_generators, oss2, graph.vertex_labels); + bliss::print_auts(aut_generators, oss2, vlabels); std::wcout << "WickTheorem::compute: colored graph " "automorphism generators = \n" << oss2.str() << std::endl; @@ -836,37 +818,29 @@ ExprPtr WickTheorem::compute(const bool count_only) { // Index partitions are constructed to *only* include Index // objects attached to the bra/ket of any NormalOperator! hence // need to use filter in computing partitions - auto exclude_index_vertex_pair = [&tn_tensors, &tn_edges, &graph](size_t v1, + auto exclude_index_vertex_pair = [&tn_tensors, &tn_edges](size_t v1, size_t v2) { - std::size_t first_edge_idx = graph.vertex_to_index_idx(v1); - std::size_t second_edge_idx = graph.vertex_to_index_idx(v2); - assert(first_edge_idx < tn_edges.size()); - assert(second_edge_idx < tn_edges.size()); - - auto edge_iter = tn_edges.begin(); - std::advance(edge_iter, first_edge_idx); - const auto &edge1 = *edge_iter; - - edge_iter = tn_edges.begin(); - std::advance(edge_iter, second_edge_idx); - const auto &edge2 = *edge_iter; - - auto connected_to_same_nop = [&tn_tensors]( - const TensorNetwork::Edge &lhs, - const TensorNetwork::Edge &rhs) { - if (lhs.vertex_count() != 2 || rhs.vertex_count() != 2) { - return false; - } - - if (edges_share_origin(lhs, rhs)) { + // v1 and v2 are vertex indices and also index the edges in the + // TensorNetwork + assert(v1 < tn_edges.size()); + assert(v2 < tn_edges.size()); + const auto &edge1 = *(tn_edges.begin() + v1); + const auto &edge2 = *(tn_edges.begin() + v2); + auto connected_to_same_nop = [&tn_tensors](int term1, int term2) { + if (term1 == term2 && term1 != 0) { + auto tensor_idx = std::abs(term1) - 1; const std::shared_ptr &tensor_ptr = - tn_tensors.at(lhs.first_vertex().getTerminalIndex()); + tn_tensors.at(tensor_idx); if (std::dynamic_pointer_cast>(tensor_ptr)) return true; } return false; }; - const bool exclude = !connected_to_same_nop(edge1, edge2); + const bool exclude = + !(connected_to_same_nop(edge1.first(), edge2.first()) || + connected_to_same_nop(edge1.first(), edge2.second()) || + connected_to_same_nop(edge1.second(), edge2.first()) || + connected_to_same_nop(edge1.second(), edge2.second())); return exclude; }; @@ -887,16 +861,10 @@ ExprPtr WickTheorem::compute(const bool count_only) { if (Logger::get_instance().wick_topology) { std::wcout << "WickTheorem::compute: topological index " "partitions:{\n"; - ranges::for_each(index_vidx2pidx, [&tn_edges, &graph](auto &&vidx_pidx) { + ranges::for_each(index_vidx2pidx, [&tn_edges](auto &&vidx_pidx) { auto &&[vidx, pidx] = vidx_pidx; - - auto edge_idx = graph.vertex_to_index_idx(vidx); - assert(edge_idx < tn_edges.size()); - - auto edge_it = tn_edges.begin(); - std::advance(edge_it, edge_idx); - auto &idx = edge_it->idx(); - + assert(vidx < tn_edges.size()); + auto &idx = (tn_edges.begin() + vidx)->idx(); std::wcout << "Index " << idx.full_label() << " -> partition " << pidx << "\n"; }); From 975f8a832b25a325c589d6d070f08fb668446aa9 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 1 Feb 2024 17:05:19 +0100 Subject: [PATCH 10/85] Use old graph for Wick --- CMakeLists.txt | 2 + SeQuant/core/vertex_type.hpp | 18 ++ SeQuant/core/wick.impl.hpp | 16 +- SeQuant/core/wick_graph.cpp | 318 +++++++++++++++++++++++++++++++++++ SeQuant/core/wick_graph.hpp | 229 +++++++++++++++++++++++++ 5 files changed, 576 insertions(+), 7 deletions(-) create mode 100644 SeQuant/core/vertex_type.hpp create mode 100644 SeQuant/core/wick_graph.cpp create mode 100644 SeQuant/core/wick_graph.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d43f6fad3..34fd661d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,6 +232,8 @@ set(SeQuant_src SeQuant/core/utility/string.cpp SeQuant/core/wick.hpp SeQuant/core/wick.impl.hpp + SeQuant/core/wick_graph.cpp + SeQuant/core/wick_graph.hpp SeQuant/core/wolfram.hpp SeQuant/core/wstring.hpp SeQuant/domain/mbpt/antisymmetrizer.hpp diff --git a/SeQuant/core/vertex_type.hpp b/SeQuant/core/vertex_type.hpp new file mode 100644 index 000000000..cdd182555 --- /dev/null +++ b/SeQuant/core/vertex_type.hpp @@ -0,0 +1,18 @@ +#ifndef SEQUANT_VERTEX_TYPE_H +#define SEQUANT_VERTEX_TYPE_H + +namespace sequant { + +enum class VertexType { + Index, + SPBundle, + TensorBra, + TensorKet, + TensorAux, + TensorCore, + TensorBraKet, +}; + +} + +#endif diff --git a/SeQuant/core/wick.impl.hpp b/SeQuant/core/wick.impl.hpp index af2015378..bf700e448 100644 --- a/SeQuant/core/wick.impl.hpp +++ b/SeQuant/core/wick.impl.hpp @@ -7,8 +7,10 @@ #include #include +#include #include #include +#include #ifdef SEQUANT_HAS_EXECUTION_HEADER #include @@ -536,12 +538,12 @@ ExprPtr WickTheorem::compute(const bool count_only) { << to_latex(expr_input_) << std::endl; // construct graph representation of the tensor product - TensorNetwork tn(expr_input_->as().factors()); - auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(); + WickGraph network(expr_input_->as().factors()); + auto [graph, vlabels, vcolors, vtypes] = network.make_bliss_graph(); const auto n = vlabels.size(); assert(vtypes.size() == n); - const auto &tn_edges = tn.edges(); - const auto &tn_tensors = tn.tensors(); + const auto &tn_edges = network.edges(); + const auto &tn_tensors = network.tensors(); if (Logger::get_instance().wick_topology) { std::basic_ostringstream oss; @@ -579,13 +581,13 @@ ExprPtr WickTheorem::compute(const bool count_only) { // ordinal can be computed by counting std::size_t nop_ord = 0; for (size_t v = 0; v != n; ++v) { - if (vtypes[v] == TensorNetwork::VertexType::TensorCore && + if (vtypes[v] == VertexType::TensorCore && (std::find(nop_labels_begin, nop_labels_end, vlabels[v]) != nop_labels_end)) { auto insertion_result = nop_vidx_ord.emplace(v, nop_ord++); assert(insertion_result.second); } - if (vtypes[v] == TensorNetwork::VertexType::Index && + if (vtypes[v] == VertexType::Index && !input_.empty()) { auto &idx = (tn_edges.begin() + v)->idx(); auto idx_it_in_opseq = ranges::find_if( @@ -821,7 +823,7 @@ ExprPtr WickTheorem::compute(const bool count_only) { auto exclude_index_vertex_pair = [&tn_tensors, &tn_edges](size_t v1, size_t v2) { // v1 and v2 are vertex indices and also index the edges in the - // TensorNetwork + // WickGraph assert(v1 < tn_edges.size()); assert(v2 < tn_edges.size()); const auto &edge1 = *(tn_edges.begin() + v1); diff --git a/SeQuant/core/wick_graph.cpp b/SeQuant/core/wick_graph.cpp new file mode 100644 index 000000000..8c5123b0f --- /dev/null +++ b/SeQuant/core/wick_graph.cpp @@ -0,0 +1,318 @@ +// +// Created by Eduard Valeyev on 2019-02-26. +// + +#include "wick_graph.hpp" +#include "bliss.hpp" +#include "logger.hpp" + +namespace sequant { + +std::tuple, std::vector, + std::vector, + std::vector> +WickGraph::make_bliss_graph( + const named_indices_t *named_indices_ptr) const { + // must call init_edges() prior to calling this + if (edges_.empty()) { + init_edges(); + } + + // initialize named_indices by default to all external indices (these HAVE + // been computed in init_edges) + const auto &named_indices = + named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; + + // results + std::shared_ptr graph; + std::vector vertex_labels( + edges_.size()); // the size will be updated + std::vector vertex_color(edges_.size(), + 0); // the size will be updated + std::vector vertex_type( + edges_.size()); // the size will be updated + + // N.B. Colors [0, 2 max rank + named_indices.size()) are reserved: + // 0 - the bra vertex (for particle 0, if bra is nonsymm, or for the entire + // bra, if (anti)symm) 1 - the bra vertex for particle 1, if bra is nonsymm + // ... + // max_rank - the ket vertex (for particle 0, if particle-asymmetric, or for + // the entire ket, if particle-symmetric) max_rank+1 - the ket vertex for + // particle 1, if particle-asymmetric + // ... + // 2 max_rank - first named index + // 2 max_rank + 1 - second named index + // ... + // N.B. For braket-symmetric tensors the ket vertices use the same indices as + // the bra vertices + auto nonreserved_color = [&named_indices](size_t color) -> bool { + return color >= 2 * max_rank + named_indices.size(); + }; + + // compute # of vertices + size_t nv = 0; + size_t index_cnt = 0; + size_t spbundle_cnt = 0; + // first count vertex indices ... the only complication are symmetric + // protoindex bundles this will keep track of unique symmetric protoindex + // bundles + using protoindex_bundle_t = + std::decay_t().proto_indices())>; + container::set symmetric_protoindex_bundles; + const size_t spbundle_vertex_offset = + edges_.size(); // where spbundle vertices will start + ranges::for_each(edges_, [&](const Edge &ttpair) { + const Index &idx = ttpair.idx(); + ++nv; // each index is a vertex + vertex_labels.at(index_cnt) = idx.to_latex(); + vertex_type.at(index_cnt) = VertexType::Index; + // assign color: named indices use reserved colors + const auto named_index_it = named_indices.find(idx); + if (named_index_it == + named_indices.end()) { // anonymous index? use Index::color + const auto idx_color = idx.color(); + assert(nonreserved_color(idx_color)); + vertex_color.at(index_cnt) = idx_color; + } else { + const auto named_index_rank = named_index_it - named_indices.begin(); + vertex_color.at(index_cnt) = 2 * max_rank + named_index_rank; + } + // each symmetric proto index bundle will have a vertex ... + // for now only store the unique protoindex bundles in + // symmetric_protoindex_bundles, then commit their data to + // vertex_{labels,type,color} later + if (idx.has_proto_indices()) { + assert(idx.symmetric_proto_indices()); // only symmetric protoindices are + // supported right now + if (symmetric_protoindex_bundles.find(idx.proto_indices()) == + symmetric_protoindex_bundles + .end()) { // new bundle? make a vertex for it + auto graph = symmetric_protoindex_bundles.insert(idx.proto_indices()); + assert(graph.second); + } + } + index_cnt++; + }); + // now commit protoindex bundle metadata + ranges::for_each(symmetric_protoindex_bundles, [&](const auto &bundle) { + ++nv; // each symmetric protoindex bundle is a vertex + std::wstring spbundle_label = L"{"; + for (auto &&pi : bundle) { + spbundle_label += pi.to_latex(); + } + spbundle_label += L"}"; + vertex_labels.push_back(spbundle_label); + vertex_type.push_back(VertexType::SPBundle); + const auto idx_proto_indices_color = Index::proto_indices_color(bundle); + assert(nonreserved_color(idx_proto_indices_color)); + vertex_color.push_back(idx_proto_indices_color); + spbundle_cnt++; + }); + // now account for vertex representation of tensors + size_t tensor_cnt = 0; + // this will map to tensor index to the first (core) vertex in its + // representation + container::svector tensor_vertex_offset(tensors_.size()); + ranges::for_each(tensors_, [&](const auto &t) { + tensor_vertex_offset.at(tensor_cnt) = nv; + // each tensor has a core vertex (to be colored by its label) + ++nv; + const auto tlabel = label(*t); + vertex_labels.emplace_back(tlabel); + vertex_type.emplace_back(VertexType::TensorCore); + const auto t_color = hash::value(tlabel); + static_assert(sizeof(t_color) == sizeof(unsigned long int)); + assert(nonreserved_color(t_color)); + vertex_color.push_back(t_color); + // symmetric/antisymmetric tensors are represented by 3 more vertices: + // - bra + // - ket + // - braket (connecting bra and ket to the core) + auto &tref = *t; + if (symmetry(tref) != Symmetry::nonsymm) { + nv += 3; + vertex_labels.push_back( + std::wstring(L"bra") + to_wstring(bra_rank(tref)) + + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); + vertex_type.push_back(VertexType::TensorBra); + vertex_color.push_back(0); + vertex_labels.push_back( + std::wstring(L"ket") + to_wstring(ket_rank(tref)) + + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); + vertex_type.push_back(VertexType::TensorKet); + vertex_color.push_back( + braket_symmetry(tref) == BraKetSymmetry::symm ? 0 : max_rank); + vertex_labels.push_back( + std::wstring(L"bk") + + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); + vertex_type.push_back(VertexType::TensorBraKet); + vertex_color.push_back(t_color); + } + // nonsymmetric tensors are represented by 3*rank more vertices (with rank = + // max(bra_rank(),ket_rank()) + else { + const auto rank = std::max(bra_rank(tref), ket_rank(tref)); + assert(rank <= max_rank); + for (size_t p = 0; p != rank; ++p) { + nv += 3; + auto pstr = to_wstring(p + 1); + vertex_labels.push_back(std::wstring(L"bra") + pstr); + vertex_type.push_back(VertexType::TensorBra); + const bool t_is_particle_symmetric = + particle_symmetry(tref) == ParticleSymmetry::nonsymm; + const auto bra_color = t_is_particle_symmetric ? p : 0; + vertex_color.push_back(bra_color); + vertex_labels.push_back(std::wstring(L"ket") + pstr); + vertex_type.push_back(VertexType::TensorKet); + vertex_color.push_back(braket_symmetry(tref) == BraKetSymmetry::symm + ? bra_color + : bra_color + max_rank); + vertex_labels.push_back(std::wstring(L"bk") + pstr); + vertex_type.push_back(VertexType::TensorBraKet); + vertex_color.push_back(t_color); + } + } + ++tensor_cnt; + }); + + // allocate graph + graph = std::make_shared(nv); + + // add edges + // - each index's degree <= 2 + # of protoindex terminals + index_cnt = 0; + ranges::for_each(edges_, [&](const Edge &ttpair) { + for (int t = 0; t != 2; ++t) { + const auto terminal_index = t == 0 ? ttpair.first() : ttpair.second(); + const auto terminal_position = + t == 0 ? ttpair.first_position() : ttpair.second_position(); + if (terminal_index) { + const auto tidx = std::abs(terminal_index) - 1; + const auto ttpos = terminal_position; + const bool bra = terminal_index > 0; + const size_t braket_vertex_index = tensor_vertex_offset[tidx] + + /* core */ 1 + 3 * ttpos + + (bra ? 0 : 1); + graph->add_edge(index_cnt, braket_vertex_index); + } + } + // if this index has symmetric protoindex bundles + if (ttpair.idx().has_proto_indices()) { + if (ttpair.idx().symmetric_proto_indices()) { + assert( + symmetric_protoindex_bundles.find(ttpair.idx().proto_indices()) != + symmetric_protoindex_bundles.end()); + const auto spbundle_idx = + symmetric_protoindex_bundles.find(ttpair.idx().proto_indices()) - + symmetric_protoindex_bundles.begin(); + graph->add_edge(index_cnt, spbundle_vertex_offset + spbundle_idx); + } else { + abort(); // nonsymmetric proto indices not supported yet + } + } + ++index_cnt; + }); + // - link up proto indices, if any ... only symmetric protobundles are + // supported now + spbundle_cnt = spbundle_vertex_offset; + ranges::for_each(symmetric_protoindex_bundles, [&graph, this, &spbundle_cnt]( + const auto &bundle) { + for (auto &&proto_index : bundle) { + assert(edges_.find(proto_index.full_label()) != edges_.end()); + const auto proto_index_vertex = + edges_.find(proto_index.full_label()) - edges_.begin(); + graph->add_edge(spbundle_cnt, proto_index_vertex); + } + ++spbundle_cnt; + }); + // - link up tensors + tensor_cnt = 0; + ranges::for_each( + tensors_, [&graph, &tensor_cnt, &tensor_vertex_offset](const auto &t) { + const auto vertex_offset = tensor_vertex_offset.at(tensor_cnt); + // for each braket terminal linker + auto &tref = *t; + const size_t nbk = symmetry(tref) == Symmetry::nonsymm + ? std::max(bra_rank(tref), ket_rank(tref)) + : 1; + for (size_t bk = 1; bk <= nbk; ++bk) { + const int bk_vertex = vertex_offset + 3 * bk; + graph->add_edge(vertex_offset, bk_vertex); // core + graph->add_edge(bk_vertex - 2, bk_vertex); // bra + graph->add_edge(bk_vertex - 1, bk_vertex); // ket + } + ++tensor_cnt; + }); + + // compress vertex colors to 32 bits, as required by Bliss, by hashing + size_t v_cnt = 0; + for (auto &&color : vertex_color) { + auto hash6432shift = [](size_t key) { + static_assert(sizeof(key) == 8); + key = (~key) + (key << 18); // key = (key << 18) - key - 1; + key = key ^ (key >> 31); + key = key * 21; // key = (key + (key << 2)) + (key << 4); + key = key ^ (key >> 11); + key = key + (key << 6); + key = key ^ (key >> 22); + return static_cast(key); + }; + graph->change_color(v_cnt, hash6432shift(color)); + ++v_cnt; + } + + return {graph, vertex_labels, vertex_color, vertex_type}; +} + +void WickGraph::init_edges() const { + if (have_edges_) return; + + auto idx_insert = [this](const Index &idx, int tensor_idx, int pos) { + if (Logger::get_instance().tensor_network) { + std::wcout << "WickGraph::init_edges: idx=" << to_latex(idx) + << " attached to tensor " << std::abs(tensor_idx) << "'s " + << ((tensor_idx > 0) ? "bra" : "ket") << " at position " << pos + << std::endl; + } + decltype(edges_) &indices = this->edges_; + auto it = indices.find(idx.full_label()); + if (it == indices.end()) { + indices.emplace(Edge(tensor_idx, &idx, pos)); + } else { + const_cast(*it).connect_to(tensor_idx, pos); + } + }; + + int t_idx = 1; + for (auto &&t : tensors_) { + const auto t_is_nonsymm = symmetry(*t) == Symmetry::nonsymm; + size_t cnt = 0; + for (const Index &idx : bra(*t)) { + idx_insert(idx, t_idx, t_is_nonsymm ? cnt : 0); + ++cnt; + } + cnt = 0; + for (const Index &idx : ket(*t)) { + idx_insert(idx, -t_idx, t_is_nonsymm ? cnt : 0); + ++cnt; + } + ++t_idx; + } + + // extract external indices + for (const auto &terminals : edges_) { + assert(terminals.size() != 0); + if (terminals.size() == 1) { // external? + if (Logger::get_instance().tensor_network) { + std::wcout << "idx " << to_latex(terminals.idx()) << " is external" + << std::endl; + } + auto insertion_result = ext_indices_.emplace(terminals.idx()); + assert(insertion_result.second); + } + } + + have_edges_ = true; +} + +} // namespace sequant diff --git a/SeQuant/core/wick_graph.hpp b/SeQuant/core/wick_graph.hpp new file mode 100644 index 000000000..9d45ed3ff --- /dev/null +++ b/SeQuant/core/wick_graph.hpp @@ -0,0 +1,229 @@ +#ifndef SEQUANT_WICK_GRAPH_H +#define SEQUANT_WICK_GRAPH_H + +#include "abstract_tensor.hpp" +#include "container.hpp" +#include "tensor.hpp" +#include "vertex_type.hpp" + +// forward declarations +namespace bliss { +class Graph; +} + +namespace sequant { + +/// @brief A (non-directed) graph view of a sequence of AbstractTensor objects + +/// @note The main role of this is to canonize itself. Since Tensor objects can +/// be connected by multiple Index'es (thus edges are colored), what is +/// canonized is actually the graph of indices (roughly the dual of the tensor +/// graph), with Tensor objects represented by one or more vertices. +class WickGraph { + public: + constexpr static size_t max_rank = 256; + + // clang-format off + /// @brief Edge in a TensorNetwork = the Index annotating it + a pair of indices to identify which Tensor terminals it's connected to + + /// @note tensor terminals in a sequence of tensors are indexed as follows: + /// - >0 for bra terminals (i.e. "+7" indicated connection to a bra terminal + /// of 7th tensor object in the sequence) + /// - <0 for ket terminals + /// - 0 if free (not attached to any tensor objects) + /// - position records the terminal's location in the sequence of bra/ket + /// terminals (always 0 for symmetric/antisymmetric tensors) Terminal indices + /// are sorted by the tensor index (i.e. by the absolute value of the terminal + /// index), followed by position + // clang-format on + class Edge { + public: + Edge() = default; + explicit Edge(int terminal_idx, int position = 0) + : first_(0), second_(terminal_idx), second_position_(position) {} + Edge(int terminal_idx, const Index *idxptr, int position = 0) + : first_(0), + second_(terminal_idx), + idxptr_(idxptr), + second_position_(position) {} + // Edge(const Edge&) = default; + // Edge(Edge&&) = default; + // Edge& operator=(const Edge&) = default; + // Edge& operator=(Edge&&) = default; + + Edge &connect_to(int terminal_idx, int position = 0) { + assert(first_ == 0 || second_ == 0); // not connected yet + assert(terminal_idx != 0); // valid idx + if (second_ == 0) { // unconnected Edge + second_ = terminal_idx; + second_position_ = position; + } else if (std::abs(second_) < + std::abs(terminal_idx)) { // connected to 2 Edges? ensure + // first_ < second_ + assert(first_ == 0); // there are slots left + first_ = second_; + first_position_ = second_position_; + second_ = terminal_idx; + second_position_ = position; + } else { // put into first slot + first_ = terminal_idx; + first_position_ = position; + } + return *this; + } + + bool operator<(const Edge &other) const { + if (std::abs(first_) == std::abs(other.first_)) { + if (first_position_ == other.first_position_) { + if (std::abs(second_) == std::abs(other.second_)) { + return second_position_ < other.second_position_; + } else { + return std::abs(second_) < std::abs(other.second_); + } + } else { + return first_position_ < other.first_position_; + } + } else { + return std::abs(first_) < std::abs(other.first_); + } + } + + bool operator==(const Edge &other) const { + return std::abs(first_) == std::abs(other.first_) && + std::abs(second_) == std::abs(other.second_) && + first_position_ == other.first_position_ && + second_position_ == other.second_position_; + } + + auto first() const { return first_; } + auto second() const { return second_; } + auto first_position() const { return first_position_; } + auto second_position() const { return second_position_; } + + /// @return the number of attached terminals (0, 1, or 2) + auto size() const { return (first_ != 0) ? 2 : ((second_ != 0) ? 1 : 0); } + + const Index &idx() const { + assert(idxptr_ != nullptr); + return *idxptr_; + } + + private: + // if only connected to 1 terminal, this is always 0 + // otherwise first_ <= second_ + int first_ = 0; + int second_ = 0; + const Index *idxptr_ = nullptr; + int first_position_ = 0; + int second_position_ = 0; + }; + + public: + /// @throw std::logic_error if exprptr_range contains a non-tensor + /// @note uses RTTI + template + WickGraph(ExprPtrRange &exprptr_range) { + for (auto &&ex : exprptr_range) { + auto t = std::dynamic_pointer_cast(ex); + if (t) { + tensors_.emplace_back(t); + } else { + throw std::logic_error( + "TensorNetwork::TensorNetwork: non-tensors in the given expression " + "range"); + } + } + } + + /// @return const reference to the sequence of tensors + /// @note the order of tensors may be different from that provided as input + const auto &tensors() const { return tensors_; } + + using named_indices_t = container::set; + + private: + // source tensors and indices + container::svector tensors_; + + struct FullLabelCompare { + using is_transparent = void; + bool operator()(const Edge &first, const Edge &second) const { + return first.idx().full_label() < second.idx().full_label(); + } + bool operator()(const Edge &first, const std::wstring_view &second) const { + return first.idx().full_label() < second; + } + bool operator()(const std::wstring_view &first, const Edge &second) const { + return first < second.idx().full_label(); + } + }; + // Index -> Edge, sorted by full label + mutable container::set edges_; + // set to true by init_edges(); + mutable bool have_edges_ = false; + // ext indices do not connect tensors + // sorted by *label* (not full label) of the corresponding value (Index) + // this ensures that proto indices are not considered and all internal indices + // have unique labels (not full labels) + mutable named_indices_t ext_indices_; + + // replacements of anonymous indices produced by the last call to + // canonicalize() + container::map idxrepl_; + + /// initializes edges_ and ext_indices_ + void init_edges() const; + + public: + /// accessor for the Edge object sequence + /// @return const reference to the sequence container of Edge objects, sorted + /// by their Index's full label + /// @sa Edge + const auto &edges() const { + init_edges(); + return edges_; + } + + /// @brief Returns a range of external indices, i.e. those indices that do not + /// connect tensors + + /// @note The external indices are sorted by *label* (not full label) of the + /// corresponding value (Index) + const auto &ext_indices() const { + if (edges_.empty()) init_edges(); + return ext_indices_; + } + + public: + /// @brief converts the network into a Bliss graph whose vertices are indices + /// and tensor vertex representations + /// @param[in] named_indices pointer to the set of named indices (ordinarily, + /// this includes all external indices); + /// default is nullptr, which means use all external indices for + /// named indices + /// @return {shared_ptr to Graph, vector of vertex labels, vector of vertex + /// colors, vector of vertex types} + + /// @note Rules for constructing the graph: + /// - Indices with protoindices are connected to their protoindices, + /// either directly or (if protoindices are symmetric) via a protoindex + /// vertex. + /// - Indices are colored by their space, which in general encodes also + /// the space of the protoindices. + /// - An anti/symmetric n-body tensor has 2 terminals, each connected to + /// each other + to n index vertices. + /// - A nonsymmetric n-body tensor has n terminals, each connected to 2 + /// indices and 1 tensor vertex which is connected to all n terminal + /// indices. + /// - tensor vertices are colored by the label+rank+symmetry of the + /// tensor; terminal vertices are colored by the color of its tensor, + /// with the color of symm/antisymm terminals augmented by the + /// terminal's type (bra/ket). + std::tuple, std::vector, + std::vector, std::vector> + make_bliss_graph(const named_indices_t *named_indices = nullptr) const; +}; + +} // namespace sequant + +#endif // SEQUANT_WICK_GRAPH_H From 4bf174a73eb8716d9a9c673ca13be0e1028dccd5 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 1 Feb 2024 17:05:34 +0100 Subject: [PATCH 11/85] Fix canonicalization issues --- SeQuant/core/tensor.hpp | 10 +- SeQuant/core/tensor_network.cpp | 502 +++++++++++++++++------------ SeQuant/core/tensor_network.hpp | 50 +-- tests/unit/test_canonicalize.cpp | 130 ++++---- tests/unit/test_mbpt.cpp | 21 +- tests/unit/test_spin.cpp | 19 +- tests/unit/test_tensor.cpp | 6 +- tests/unit/test_tensor_network.cpp | 396 +++++++++++------------ 8 files changed, 584 insertions(+), 550 deletions(-) diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index 280d7b60e..7ea59cb94 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -70,6 +70,9 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// @return view of the bra+ket index ranges auto braket() { return ranges::views::concat(bra_, ket_); } + /// @return view of all indices + auto indices() { return ranges::views::concat(bra_, ket_, auxiliary_); } + /// asserts that @p label is not reserved /// @note Tensor with reserved labels are constructed using friends of Tensor /// @param label a Tensor label candidate @@ -173,6 +176,9 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// @return view of the bra+ket index ranges /// @note this is to work around broken lookup rules auto const_braket() const { return this->braket(); } + /// @return view of all indices + /// @note this is to work around broken lookup rules + 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; @@ -270,7 +276,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { typename... Args> bool transform_indices(const Map &index_map) { bool mutated = false; - ranges::for_each(braket(), [&](auto &idx) { + ranges::for_each(indices(), [&](auto &idx) { if (idx.transform(index_map)) mutated = true; }); if (mutated) this->reset_hash_value(); @@ -282,7 +288,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { ExprPtr clone() const override { return ex(*this); } void reset_tags() const { - ranges::for_each(braket(), [](const auto &idx) { idx.reset_tag(); }); + ranges::for_each(indices(), [](const auto &idx) { idx.reset_tag(); }); } hash_type bra_hash_value() const { diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 71bb8aca7..89a3629cd 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -21,10 +21,11 @@ #include #include #include +#include +#include #include +#include #include -#include -#include #include #include @@ -35,6 +36,17 @@ namespace sequant { +struct FullLabelIndexLocator { + std::wstring_view label; + FullLabelIndexLocator(std::wstring_view label) : label(std::move(label)) {} + + bool operator()(const TensorNetwork::Edge &edge) const { + return edge.idx().full_label() == label; + } + + bool operator()(const Index &idx) const { return idx.full_label() == label; } +}; + bool tensors_commute(const AbstractTensor &lhs, const AbstractTensor &rhs) { // tensors commute if their colors are different or either one of them // is a c-number @@ -75,14 +87,19 @@ struct TensorBlockCompare { /// Compares tensors based on their label and orders them according to the order /// of the given cardinal tensor labels. If two tensors can't be discriminated -/// via their label, they are compared based on their tensor block (the spaces -/// of their indices). If this doesn't discriminate the tensors, they are -/// considered equal (note: explicit indexing is NOT compared) +/// via their label, they are compared based on regular +/// AbstractTensor::operator< or based on their tensor block (the spaces of +/// their indices) - depending on the configuration. If this doesn't +/// discriminate the tensors, they are considered equal template struct CanonicalTensorCompare { const CardinalLabels &labels; + bool blocks_only; - CanonicalTensorCompare(const CardinalLabels &labels) : labels(labels) {} + CanonicalTensorCompare(const CardinalLabels &labels, bool blocks_only) + : labels(labels), blocks_only(blocks_only) {} + + void set_blocks_only(bool blocks_only) { this->blocks_only = blocks_only; } bool operator()(const AbstractTensorPtr &lhs_ptr, const AbstractTensorPtr &rhs_ptr) const { @@ -114,9 +131,12 @@ struct CanonicalTensorCompare { } // Either both are the same cardinal tensor or none is a cardinal tensor - // -> Discriminate via tensor block comparison - TensorBlockCompare cmp; - return cmp(lhs, rhs); + if (blocks_only) { + TensorBlockCompare cmp; + return cmp(lhs, rhs); + } else { + return lhs < rhs; + } } }; @@ -149,14 +169,16 @@ bool TensorNetwork::Vertex::operator<(const Vertex &rhs) const { // Both vertices belong to same tensor -> they must have same symmetry assert(terminal_symm == rhs.terminal_symm); + if (origin != rhs.origin) { + return origin < rhs.origin; + } + // We only take the index slot into account for non-symmetric tensors - if (terminal_symm == Symmetry::nonsymm && index_slot != rhs.index_slot) { + if (terminal_symm == Symmetry::nonsymm) { return index_slot < rhs.index_slot; + } else { + return false; } - - // Note: The ordering of index groups must be consistent with the canonical - // (Abstract)Tensor::operator< - return origin < rhs.origin; } bool TensorNetwork::Vertex::operator==(const Vertex &rhs) const { @@ -214,33 +236,79 @@ auto permute(const ArrayLike &vector, const Permutation &perm) { return pvector; } -template -void apply_index_replacements(ArrayLike &tensors, +template +void apply_index_replacements(AbstractTensor &tensor, const ReplacementMap &replacements) { #ifndef NDEBUG // assert that tensors' indices are not tagged since going to tag indices - { - for (const auto &tensor : tensors) { - assert(ranges::none_of(braket(*tensor), [](const Index &idx) { - return idx.tag().has_value(); - })); - } - } + assert(ranges::none_of( + indices(tensor), [](const Index &idx) { return idx.tag().has_value(); })); #endif - bool pass_mutated = false; + bool pass_mutated; do { - pass_mutated = false; - for (auto &tensor : tensors) { - pass_mutated |= transform_indices(*tensor, replacements); - } + pass_mutated = transform_indices(tensor, replacements); } while (pass_mutated); // transform till stops changing - // untag transformed indices (if any) - { - for (auto &tensor : tensors) { - reset_tags(*tensor); + reset_tags(tensor); +} + +template +void apply_index_replacements(ArrayLike &tensors, + const ReplacementMap &replacements) { + for (auto &tensor : tensors) { + apply_index_replacements(*tensor, replacements); + } +} + +template +void order_to_indices(Container &container) { + std::vector indices; + indices.resize(container.size()); + std::iota(indices.begin(), indices.end(), 0); + + std::sort(indices.begin(), indices.end(), + [&container](std::size_t lhs, std::size_t rhs) { + return container[lhs] < container[rhs]; + }); + // Overwrite container contents with indices + std::copy(indices.begin(), indices.end(), container.begin()); +} + +template +void sort_via_indices(Container &container, const Comparator &cmp) { + std::vector indices; + indices.resize(container.size()); + std::iota(indices.begin(), indices.end(), 0); + + if constexpr (stable) { + std::stable_sort(indices.begin(), indices.end(), cmp); + } else { + std::sort(indices.begin(), indices.end(), cmp); + } + + // Bring elements in container into the order given by indices + // (the association is container[k] = container[indices[k]]) + // -> implementation from https://stackoverflow.com/a/838789 + + for (std::size_t i = 0; i < container.size(); ++i) { + if (indices[i] == i) { + // This element is already where it is supposed to be + continue; + } + + // Find the offset of the index pointing to i + // -> since we are going to change the content of the vector at position i, + // we have to update the index-mapping referencing i to point to the new + // location of the element that used to be at position i + std::size_t k; + for (k = i + 1; k < container.size(); ++k) { + if (indices[k] == i) { + break; + } } + std::swap(container[i], container[indices[i]]); + std::swap(indices[i], indices[k]); } } @@ -257,37 +325,14 @@ void TensorNetwork::canonicalize_graph(const named_indices_t &named_indices) { if (!have_edges_) { init_edges(); } - // - canonize indices - // - canonize tensors using canonical list of indices - // Algorithm sketch: - // - to canonize indices make a graph whose vertices are the indices as - // well as the tensors and their terminals. - // - Indices with protoindices are connected to their protoindices, - // either directly or (if protoindices are symmetric) via a protoindex - // vertex. - // - Indices are colored by their space, which in general encodes also - // the space of the protoindices. - // - An anti/symmetric n-body tensor has 2 terminals, each connected to - // each other + to n index vertices. - // - A nonsymmetric n-body tensor has n terminals, each connected to 2 - // indices and 1 tensor vertex which is connected to all n terminal - // indices. - // - tensor vertices are colored by the label+rank+symmetry of the - // tensor; terminal vertices are colored by the color of its tensor, - // with the color of symm/antisymm terminals augmented by the - // terminal's type (bra/ket). - // - canonize the graph - - auto is_anonymous_index = [named_indices](const Index &idx) { + + const auto is_anonymous_index = [named_indices](const Index &idx) { return named_indices.find(idx) == named_indices.end(); }; // index factory to generate anonymous indices IndexFactory idxfac(is_anonymous_index, 1); - // Clear any potential prior replacements - idxrepl_.clear(); - // make the graph Graph graph = create_graph(&named_indices); // graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); @@ -301,123 +346,130 @@ void TensorNetwork::canonicalize_graph(const named_indices_t &named_indices) { // canonize the graph bliss::Stats stats; graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); - const unsigned int *cl = + const unsigned int *canonize_perm = graph.bliss_graph->canonical_form(stats, nullptr, nullptr); if (Logger::get_instance().canonicalize_dot) { std::wcout << "Canonicalization permutation:\n"; for (std::size_t i = 0; i < graph.vertex_labels.size(); ++i) { - std::wcout << i << " -> " << cl[i] << "\n"; + std::wcout << i << " -> " << canonize_perm[i] << "\n"; } std::wcout << "Canonicalized graph:\n"; - bliss::Graph *cgraph = graph.bliss_graph->permute(cl); + bliss::Graph *cgraph = graph.bliss_graph->permute(canonize_perm); cgraph->write_dot(std::wcout, {}, true); - auto cvlabels = permute(graph.vertex_labels, cl); + auto cvlabels = permute(graph.vertex_labels, canonize_perm); std::wcout << "with our labels:\n"; cgraph->write_dot(std::wcout, cvlabels); delete cgraph; } - // make anonymous index replacement list - { - // for each color make a replacement list for bringing the indices to - // the canonical order - container::set colors; - // maps color to the ordinals of the corresponding indices in edges_ and - // their canonical ordinals collect colors and anonymous indices sorted by - // colors - container::multimap> color2idx; - - std::size_t idx_cnt = 0; - auto edge_it = edges_.begin(); - for (std::size_t vertex = 0; vertex < graph.vertex_types.size(); ++vertex) { - if (graph.vertex_types[vertex] != VertexType::Index) { - continue; + container::map tensor_idx_to_vertex; + container::map> + tensor_idx_to_particle_order; + container::map index_idx_to_vertex; + std::size_t tensor_idx = 0; + std::size_t index_idx = 0; + + for (std::size_t vertex = 0; vertex < graph.vertex_types.size(); ++vertex) { + switch (graph.vertex_types[vertex]) { + case VertexType::Index: + index_idx_to_vertex[index_idx] = vertex; + index_idx++; + break; + case VertexType::TensorBraKet: { + assert(tensor_idx > 0); + const std::size_t base_tensor_idx = tensor_idx - 1; + assert(symmetry(*tensors_.at(base_tensor_idx)) == Symmetry::nonsymm); + tensor_idx_to_particle_order[base_tensor_idx].push_back( + canonize_perm[vertex]); + break; } + case VertexType::TensorCore: + tensor_idx_to_vertex[tensor_idx] = vertex; + tensor_idx++; + break; + case VertexType::TensorBra: + case VertexType::TensorKet: + case VertexType::TensorAux: + case VertexType::SPBundle: + break; + } + } - auto color = graph.vertex_colors[vertex]; - colors.insert(color); + assert(index_idx_to_vertex.size() == edges_.size()); + assert(tensor_idx_to_vertex.size() == tensors_.size()); + assert(tensor_idx_to_particle_order.size() <= tensors_.size()); - // We rely on the fact that the order of index vertices corresponds to - // the order of corresponding edges - assert(edge_it != edges_.end()); - const Index &idx = edge_it->idx(); + // order_to_indices(index_order); + for (auto ¤t : tensor_idx_to_particle_order) { + order_to_indices(current.second); + } - if (is_anonymous_index(idx)) { - color2idx.emplace(color, std::make_pair(idx_cnt, cl[vertex])); - } + container::map idxrepl; + // Sort edges so that their order corresponds to the order of indices in the + // canonical graph + // Use this ordering to relabel anonymous indices + const auto index_sorter = [&index_idx_to_vertex, &canonize_perm]( + std::size_t lhs_idx, std::size_t rhs_idx) { + const std::size_t lhs_vertex = index_idx_to_vertex.at(lhs_idx); + const std::size_t rhs_vertex = index_idx_to_vertex.at(rhs_idx); - ++idx_cnt; - ++edge_it; - } + return canonize_perm[lhs_vertex] < canonize_perm[rhs_vertex]; + }; - // for each color sort anonymous indices by canonical order + sort_via_indices(edges_, index_sorter); - // canonically-ordered list of - // {index ordinal in edges_, canonical ordinal} - container::svector> idx_can; - for (const std::size_t color : colors) { - auto beg = color2idx.lower_bound(color); - auto end = color2idx.upper_bound(color); + for (const Edge ¤t : edges_) { + const Index &idx = current.idx(); - const auto sz = std::distance(beg, end); + if (!is_anonymous_index(idx)) { + continue; + } - if (sz > 1) { - idx_can.resize(sz); + idxrepl.insert(std::make_pair(idx, idxfac.make(idx))); + } - size_t cnt = 0; - for (auto it = beg; it != end; ++it, ++cnt) { - idx_can[cnt] = it->second; - } + apply_index_replacements(tensors_, idxrepl); - using std::begin; - using std::end; - std::sort(begin(idx_can), end(idx_can), - [](const std::pair &a, - const std::pair &b) { - return a.second < b.second; - }); + // Perform particle-1,2-swaps as indicated by the graph canonization + for (std::size_t i = 0; i < tensors_.size(); ++i) { + AbstractTensor &tensor = *tensors_[i]; + const std::size_t num_particles = + std::min(bra_rank(tensor), ket_rank(tensor)); - // make a replacement list by generating new indices in canonical - // order - for (auto [orig_idx, _] : idx_can) { - assert(orig_idx < edges_.size()); - auto edge_it = edges_.begin(); - std::advance(edge_it, orig_idx); + auto it = tensor_idx_to_particle_order.find(i); + if (it == tensor_idx_to_particle_order.end()) { + assert(num_particles == 0 || symmetry(*tensors_[i]) != Symmetry::nonsymm); + continue; + } + + const auto &particle_order = it->second; + auto bra_indices = bra(tensor); + auto ket_indices = ket(tensor); - const auto &idx = edge_it->idx(); + assert(num_particles == particle_order.size()); - idxrepl_.emplace(std::make_pair(idx, idxfac.make(idx))); - } - } else if (sz == 1) { - // no need for resorting of colors with 1 index only, but still need - // to replace the index - const auto edge_it = edges_.begin() + beg->second.first; - const auto &idx = edge_it->idx(); - idxrepl_.emplace(std::make_pair(idx, idxfac.make(idx))); + // Swap indices column-wise + idxrepl.clear(); + for (std::size_t col = 0; col < num_particles; ++col) { + if (particle_order[col] == col) { + continue; } - // sz == 0 is possible since some colors in colors refer to tensors - } - } // index repl - // Bring tensors into canonical order, but ensure to respect commutativity! - - std::vector tensor_indices; - tensor_indices.resize(tensors_.size()); - std::iota(tensor_indices.begin(), tensor_indices.end(), 0); + idxrepl.insert( + std::make_pair(bra_indices[col], bra_indices[particle_order[col]])); + idxrepl.insert( + std::make_pair(ket_indices[col], ket_indices[particle_order[col]])); + } - // TODO: initialize this while iterating over vertices for index (colors) - container::map tensor_idx_to_vertex; - for (std::size_t tensor_idx = 0, vertex = 0; - vertex < graph.vertex_types.size(); ++vertex) { - if (graph.vertex_types[vertex] != VertexType::TensorCore) { - continue; + if (!idxrepl.empty()) { + apply_index_replacements(tensor, idxrepl); } - tensor_idx_to_vertex[tensor_idx] = vertex; - tensor_idx++; } - const auto tensor_sorter = [this, &cl, &tensor_idx_to_vertex]( + // Bring tensors into canonical order (analogously to how we reordered + // indices), but ensure to respect commutativity! + const auto tensor_sorter = [this, &canonize_perm, &tensor_idx_to_vertex]( std::size_t lhs_idx, std::size_t rhs_idx) { const AbstractTensor &lhs = *tensors_[lhs_idx]; const AbstractTensor &rhs = *tensors_[rhs_idx]; @@ -432,23 +484,14 @@ void TensorNetwork::canonicalize_graph(const named_indices_t &named_indices) { // Commuting tensors are sorted based on their canonical order which is // given by the order of the corresponding vertices in the canonical graph // representation - return cl[lhs_vertex] < cl[rhs_vertex]; + return canonize_perm[lhs_vertex] < canonize_perm[rhs_vertex]; }; - std::stable_sort(tensor_indices.begin(), tensor_indices.end(), tensor_sorter); - - assert(tensor_indices.size() == tensors_.size()); - - decltype(tensors_) tensors_canonized(tensors_.size()); - for (std::size_t i = 0; i < tensors_.size(); ++i) { - tensors_canonized[i] = tensors_[tensor_indices[i]]; - } - - tensors_ = std::move(tensors_canonized); - - // Apply the canonical reindexing - apply_index_replacements(tensors_, idxrepl_); + sort_via_indices(tensors_, tensor_sorter); + // The tensor reordering and index relabelling made the current set of edges + // invalid + edges_.clear(); have_edges_ = false; if (Logger::get_instance().canonicalize) { @@ -464,10 +507,6 @@ void TensorNetwork::canonicalize_graph(const named_indices_t &named_indices) { ExprPtr TensorNetwork::canonicalize( const container::vector &cardinal_tensor_labels, bool fast, const named_indices_t *named_indices_ptr) { - // initialize named_indices by default to all external indices - const auto &named_indices = - named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; - if (Logger::get_instance().canonicalize) { std::wcout << "TensorNetwork::canonicalize(" << (fast ? "fast" : "slow") << "): input tensors\n"; @@ -481,6 +520,14 @@ ExprPtr TensorNetwork::canonicalize( std::wcout << std::endl; } + if (!have_edges_) { + init_edges(); + } + + // initialize named_indices by default to all external indices + const auto &named_indices = + named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; + if (!fast) { // The graph-based canonization is required in call cases in which there are // indistinguishable tensors present in the expression. Their order and @@ -492,8 +539,8 @@ ExprPtr TensorNetwork::canonicalize( // order to properly be able to identify tensor blocks ExprPtr byproduct = canonicalize_individual_tensors(named_indices); - const CanonicalTensorCompare tensor_sorter( - cardinal_tensor_labels); + CanonicalTensorCompare tensor_sorter( + cardinal_tensor_labels, true); std::stable_sort(tensors_.begin(), tensors_.end(), tensor_sorter); @@ -517,10 +564,8 @@ ExprPtr TensorNetwork::canonicalize( return named_indices.find(idx) == named_indices.end(); }; - // Sort edges based on the order of the tensors they connect (instead of based - // on their (full) label) - container::svector resorted_edges(edges_.begin(), edges_.end()); - std::stable_sort(resorted_edges.begin(), resorted_edges.end(), + // Sort edges based on the order of the tensors they connect + std::stable_sort(edges_.begin(), edges_.end(), [&is_named_index](const Edge &lhs, const Edge &rhs) { // Sort first by index's character (named < anonymous), // then by Edge (not by Index's full label) ... this @@ -539,35 +584,38 @@ ExprPtr TensorNetwork::canonicalize( // -> start reindexing anonymous indices from 1 IndexFactory idxfac(is_anonymous_index, 1); - idxrepl_.clear(); + container::map idxrepl; // Use the new order of edges as the canonical order of indices and relabel // accordingly (but only anonymous indices, of course) - for (std::size_t i = named_indices.size(); i < resorted_edges.size(); ++i) { - const Index &index = resorted_edges[i].idx(); + for (std::size_t i = named_indices.size(); i < edges_.size(); ++i) { + const Index &index = edges_[i].idx(); assert(is_anonymous_index(index)); Index replacement = idxfac.make(index); - idxrepl_.emplace(std::make_pair(index, replacement)); + idxrepl.emplace(std::make_pair(index, replacement)); } // Done computing canonical index replacement list if (Logger::get_instance().canonicalize) { - for (const auto &idxpair : idxrepl_) { + for (const auto &idxpair : idxrepl) { std::wcout << "TensorNetwork::canonicalize(" << (fast ? "fast" : "slow") << "): replacing " << to_latex(idxpair.first) << " with " << to_latex(idxpair.second) << std::endl; } } - apply_index_replacements(tensors_, idxrepl_); + apply_index_replacements(tensors_, idxrepl); byproduct *= canonicalize_individual_tensors(named_indices); // We assume that re-indexing did not change the canonical order of tensors - // If it did, then most likely the (Abstract)Tensor::operator< compares index - // groups in a different order than Vertex::operator< assert(std::is_sorted(tensors_.begin(), tensors_.end(), tensor_sorter)); + // However, in order to produce the most aesthetically pleasing result, we now + // reorder tensors based on the regular AbstractTensor::operator<, which takes + // the explicit index labelling of tensors into account. + tensor_sorter.set_blocks_only(false); + std::stable_sort(tensors_.begin(), tensors_.end(), tensor_sorter); have_edges_ = false; @@ -592,8 +640,9 @@ TensorNetwork::Graph TensorNetwork::create_graph( const std::size_t max_reserved_color = named_idx_color_start + named_indices.size() - 1; - // core, bra, ket, auxiliary - constexpr std::size_t num_tensor_components = 4; + // core, bra, ket, auxiliary and optionally (for non-symmetric tensors) a + // particle vertex + constexpr std::size_t num_tensor_components = 5; // results Graph graph; @@ -637,23 +686,55 @@ TensorNetwork::Graph TensorNetwork::create_graph( const Symmetry tensor_sym = symmetry(tensor); if (tensor_sym == Symmetry::nonsymm) { // Create separate vertices for every index + // Additionally, we need particle vertices to group indices that belong to + // the same particle (are in the same "column" in the usual tensor + // notation) + const std::size_t num_particle_vertices = + std::min(bra_rank(tensor), ket_rank(tensor)); + const bool is_part_symm = + particle_symmetry(tensor) == ParticleSymmetry::symm; + // TODO: How to handle BraKetSymmetry::conjugate? + const bool is_braket_symm = + braket_symmetry(tensor) == BraKetSymmetry::symm; + + for (std::size_t i = 0; i < num_particle_vertices; ++i) { + graph.vertex_labels.emplace_back(L"p_" + std::to_wstring(i + 1)); + graph.vertex_types.push_back(VertexType::TensorBraKet); + graph.vertex_colors.push_back(tensor_color); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + } + assert(bra_rank(tensor) <= max_rank); for (std::size_t i = 0; i < bra_rank(tensor); ++i) { + const bool is_unpaired_idx = i >= num_particle_vertices; + const bool color_idx = is_unpaired_idx || !is_part_symm; + graph.vertex_labels.emplace_back(L"bra_" + std::to_wstring(i + 1)); graph.vertex_types.push_back(VertexType::TensorBra); - graph.vertex_colors.push_back(i); + graph.vertex_colors.push_back(color_idx ? i : 0); + + const std::size_t connect_vertex = + tensor_vertex + (is_unpaired_idx ? 0 : (i + 1)); + edges.push_back( + std::make_pair(connect_vertex, graph.vertex_labels.size() - 1)); } - edges.push_back( - std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); assert(ket_rank(tensor) <= max_rank); for (std::size_t i = 0; i < ket_rank(tensor); ++i) { + const bool is_unpaired_idx = i >= num_particle_vertices; + const bool color_idx = is_unpaired_idx || !is_part_symm; + graph.vertex_labels.emplace_back(L"ket_" + std::to_wstring(i + 1)); graph.vertex_types.push_back(VertexType::TensorKet); - graph.vertex_colors.push_back(max_rank + i); + graph.vertex_colors.push_back((color_idx ? i : 0) + + (is_braket_symm ? 0 : max_rank)); + + const std::size_t connect_vertex = + tensor_vertex + (is_unpaired_idx ? 0 : (i + 1)); + edges.push_back( + std::make_pair(connect_vertex, graph.vertex_labels.size() - 1)); } - edges.push_back( - std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); } else { // Shared set of bra/ket vertices for all indices std::wstring suffix = tensor_sym == Symmetry::symm ? L"_s" : L"_a"; @@ -686,8 +767,6 @@ TensorNetwork::Graph TensorNetwork::create_graph( edges.push_back( std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); } - - // TODO: Add vertex for total or braket grouping? } // Now add all indices (edges) to the graph @@ -757,26 +836,37 @@ TensorNetwork::Graph TensorNetwork::create_graph( // Store an edge connecting the index vertex to the corresponding tensor // vertex - // TODO: if we re-introduce a braket-like vertex, we have to add 1 to - // the tensor vertex - static_assert(static_cast(Origin::Bra) == 1); - static_assert(static_cast(Origin::Ket) == 2); - static_assert(static_cast(Origin::Aux) == 3); const bool tensor_is_nonsymm = vertex.getTerminalSymmetry() == Symmetry::nonsymm; - // Determine the index of the vertex for the tensor component we want to - // connect the current index to. For (anti)symmetric tensors there - // exists only a single bra, ket or aux vertex per tensor and thus the - // origin of the index is the only factor to account for. Non-symmetric - // tensors on the other hand have a bra, ket or aux vertex for each - // individual index and thus the index's position within the bra, ket or - // aux group of the tensor has to be accounted for in order to selected - // the correct vertex to connect to. N.B. conversion from bool to int is - // always: true -> 1, false -> 0 - const std::size_t tensor_component_vertex = - tensor_vertex + static_cast(vertex.getOrigin()) + - tensor_is_nonsymm * vertex.getIndexSlot() * - (num_tensor_components - /* core */ 1); + const AbstractTensor &tensor = *tensors_[vertex.getTerminalIndex()]; + std::size_t offset; + if (tensor_is_nonsymm) { + // We have to find the correct vertex to connect this index to (for + // non-symmetric tensors each index has its dedicated "group" vertex) + + // Move off the tensor core's vertex + offset = 1; + // Move past the explicit particle vertices + offset += std::min(bra_rank(tensor), ket_rank(tensor)); + + if (vertex.getOrigin() > Origin::Bra) { + offset += bra_rank(tensor); + } + + offset += vertex.getIndexSlot(); + } else { + static_assert(static_cast(Origin::Bra) == 1); + static_assert(static_cast(Origin::Ket) == 2); + static_assert(static_cast(Origin::Aux) == 3); + offset = static_cast(vertex.getOrigin()); + } + + if (vertex.getOrigin() > Origin::Ket) { + offset += ket_rank(tensor); + } + + const std::size_t tensor_component_vertex = tensor_vertex + offset; + assert(tensor_component_vertex < graph.vertex_labels.size()); edges.push_back(std::make_pair(index_vertex, tensor_component_vertex)); } @@ -825,9 +915,10 @@ void TensorNetwork::init_edges() { << std::endl; } - auto it = edges_.find(idx.full_label()); + auto it = std::find_if(edges_.begin(), edges_.end(), + FullLabelIndexLocator(idx.full_label())); if (it == edges_.end()) { - edges_.emplace(Edge(std::move(vertex), idx)); + edges_.emplace_back(std::move(vertex), idx); } else { it->connect_to(std::move(vertex)); } @@ -855,8 +946,11 @@ void TensorNetwork::init_edges() { auto aux_indices = auxiliary(tensor); for (std::size_t index_idx = 0; index_idx < aux_indices.size(); ++index_idx) { + // Note: for the time being we don't have a way of expressing + // permutational symmetry of auxiliary indices so we just assume there is + // no such symmetry idx_insert(aux_indices[index_idx], - Vertex(Origin::Aux, tensor_idx, index_idx, tensor_symm)); + Vertex(Origin::Aux, tensor_idx, index_idx, Symmetry::nonsymm)); } } diff --git a/SeQuant/core/tensor_network.hpp b/SeQuant/core/tensor_network.hpp index dd003ccc6..4d5db6383 100644 --- a/SeQuant/core/tensor_network.hpp +++ b/SeQuant/core/tensor_network.hpp @@ -9,9 +9,11 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -20,10 +22,6 @@ #include #include -#include -#include -#include - // forward declarations namespace bliss { class Graph; @@ -116,7 +114,11 @@ class TensorNetwork { return first < other.first; } - return second < other.second; + if(second < other.second) { + return second < other.second; + } + + return index.space() < other.index.space(); } bool operator==(const Edge &other) const { @@ -139,15 +141,6 @@ class TensorNetwork { Index index; }; - enum class VertexType { - Index, - SPBundle, - TensorBra, - TensorKet, - TensorAux, - TensorCore - }; - struct Graph { std::unique_ptr bliss_graph; std::vector vertex_labels; @@ -221,20 +214,11 @@ class TensorNetwork { /// @brief Returns a range of external indices, i.e. those indices that do not /// connect tensors - - /// @note The external indices are sorted by *label* (not full label) of the - /// corresponding value (Index) const auto &ext_indices() const { assert(have_edges_); return ext_indices_; } - /// accessor for the list of anonymous index replacements performed by the - /// last call to canonicalize() - /// @return replacements of anonymous indices performed by the last call to - /// canonicalize() - const auto &idxrepl() const { return idxrepl_; }; - /// @brief converts the network into a Bliss graph whose vertices are indices /// and tensor vertex representations /// @param[in] named_indices pointer to the set of named indices (ordinarily, @@ -264,21 +248,7 @@ class TensorNetwork { // source tensors and indices container::svector tensors_; - struct FullLabelCompare { - using is_transparent = void; - bool operator()(const Edge &first, const Edge &second) const { - return first.idx().full_label() < second.idx().full_label(); - } - bool operator()(const Edge &first, const std::wstring_view &second) const { - return first.idx().full_label() < second; - } - bool operator()(const std::wstring_view &first, const Edge &second) const { - return first < second.idx().full_label(); - } - }; - // Index -> Edge, sorted by full label - container::set edges_; - // set to true by init_edges(); + container::vector edges_; bool have_edges_ = false; // ext indices do not connect tensors // sorted by *label* (not full label) of the corresponding value (Index) @@ -286,10 +256,6 @@ class TensorNetwork { // have unique labels (not full labels) named_indices_t ext_indices_; - // replacements of anonymous indices produced by the last call to - // canonicalize() - container::map idxrepl_; - /// initializes edges_ and ext_indices_ void init_edges(); diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index d5ea49eae..2661e8124 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -40,14 +40,16 @@ TEST_CASE("Canonicalizer", "[algorithms]") { REQUIRE(to_latex(op) == L"{g^{{p_4}{p_3}}_{{p_1}{p_2}}}"); } { - auto op = ex(L"g", WstrList{L"p_1", L"p_2"}, - WstrList{L"p_4", L"p_3"}, WstrList{}, Symmetry::nonsymm); + auto op = + ex(L"g", WstrList{L"p_1", L"p_2"}, WstrList{L"p_4", L"p_3"}, + WstrList{}, Symmetry::nonsymm); canonicalize(op); REQUIRE(to_latex(op) == L"{g^{{p_4}{p_3}}_{{p_1}{p_2}}}"); } { - auto op = ex(L"g", WstrList{L"p_2", L"p_1"}, - WstrList{L"p_4", L"p_3"}, WstrList{}, Symmetry::nonsymm); + auto op = + ex(L"g", WstrList{L"p_2", L"p_1"}, WstrList{L"p_4", L"p_3"}, + WstrList{}, Symmetry::nonsymm); canonicalize(op); REQUIRE(to_latex(op) == L"{g^{{p_3}{p_4}}_{{p_1}{p_2}}}"); } @@ -66,8 +68,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { WstrList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == - L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" - L"a_2}}}{t^{{a_3}}_{{i_3}}}{t^{{a_1}{a_2}}_{{i_1}{i_2}}}}"); + L"{{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{" + L"a_3}}}{t^{{a_2}}_{{i_3}}}{t^{{a_1}{a_3}}_{{i_1}{i_2}}}}"); } { auto input = @@ -75,14 +77,14 @@ TEST_CASE("Canonicalizer", "[algorithms]") { WstrList{}, Symmetry::nonsymm) * ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, WstrList{}, Symmetry::nonsymm) * - ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, IndexList{}, - Symmetry::nonsymm) * - ex(L"t", WstrList{L"i_5", L"i_2"}, - WstrList{L"a_1", L"a_2"}, WstrList{}, Symmetry::nonsymm); + ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, + IndexList{}, Symmetry::nonsymm) * + ex(L"t", WstrList{L"i_5", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == - L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" - L"a_2}}}{t^{{a_2}}_{{i_2}}}{t^{{a_1}{a_3}}_{{i_1}{i_3}}}}"); + L"{{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{" + L"a_3}}}{t^{{a_3}}_{{i_2}}}{t^{{a_1}{a_2}}_{{i_1}{i_3}}}}"); } { // Product containing Variables auto q2 = ex(L"q2"); @@ -101,36 +103,38 @@ TEST_CASE("Canonicalizer", "[algorithms]") { WstrList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == - L"{{p}{q1}{{q2}^*}{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" - L"a_2}}}{t^{{a_2}}_{{i_2}}}{t^{{a_1}{a_3}}_{{i_1}{i_3}}}}"); + L"{{p}{q1}{{q2}^*}{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{" + L"a_3}}}{t^{{a_3}}_{{i_2}}}{t^{{a_1}{a_2}}_{{i_1}{i_3}}}}"); } { // Product containing adjoint of a Tensor auto f2 = - ex(L"f", WstrList{L"i_5", L"i_2"}, WstrList{L"a_1", L"a_2"}, WstrList{}, - Symmetry::nonsymm, BraKetSymmetry::nonsymm); + ex(L"f", WstrList{L"i_5", L"i_2"}, WstrList{L"a_1", L"a_2"}, + WstrList{}, Symmetry::nonsymm, BraKetSymmetry::nonsymm); f2->adjoint(); - auto input1 = ex(L"S", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, WstrList{}, Symmetry::nonsymm) * - ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, IndexList{}, - Symmetry::nonsymm) * - ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, IndexList{}, - Symmetry::nonsymm) * - f2; + auto input1 = + ex(L"S", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, + IndexList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, + IndexList{}, Symmetry::nonsymm) * + f2; canonicalize(input1); REQUIRE(to_latex(input1) == - L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{a_2}}}{f⁺^{{i_1}{i_" - L"3}}_{{a_1}{a_3}}}{t^{{a_2}}_{{i_2}}}}"); - auto input2 = ex(L"S", WstrList{L"a_1", L"a_2"}, - WstrList{L"i_1", L"i_2"}, WstrList{}, Symmetry::nonsymm) * - ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, IndexList{}, - Symmetry::nonsymm) * - ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, IndexList{}, - Symmetry::nonsymm) * - f2 * ex(L"w") * ex(rational{1, 2}); + L"{{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{a_3}}}{f⁺^{{i_1}{i_" + L"3}}_{{a_1}{a_2}}}{t^{{a_3}}_{{i_2}}}}"); + auto input2 = + ex(L"S", WstrList{L"a_1", L"a_2"}, WstrList{L"i_1", L"i_2"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"f", IndexList{{L"a_5"}}, IndexList{{L"i_5"}}, + IndexList{}, Symmetry::nonsymm) * + ex(L"t", IndexList{{L"i_1"}}, IndexList{{L"a_5"}}, + IndexList{}, Symmetry::nonsymm) * + f2 * ex(L"w") * ex(rational{1, 2}); canonicalize(input2); REQUIRE(to_latex(input2) == - L"{{{\\frac{1}{2}}}{w}{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" - L"a_2}}}{f⁺^{{i_1}{i_3}}_{{a_1}{a_3}}}{t^{{a_2}}_{{i_2}}}}"); + L"{{{\\frac{1}{2}}}{w}{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{" + L"a_3}}}{f⁺^{{i_1}{i_3}}_{{a_1}{a_2}}}{t^{{a_3}}_{{i_2}}}}"); } } @@ -157,8 +161,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(to_latex(input) == L"{ " - L"\\bigl({{g^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}{t^{{" - L"p_3}}_{{p_4}}}}\\bigr) }"); + L"\\bigl({{g^{{p_3}{p_4}}_{{p_1}{p_2}}}{t^{{p_1}}_{{p_3}}}{t^{{p_" + L"2}}_{{p_4}}}}\\bigr) }"); } // CASE 2: Symmetric tensors @@ -182,8 +186,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(to_latex(input) == L"{ " - L"\\bigl({{g^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}{t^{{p_" - L"3}}_{{p_4}}}}\\bigr) }"); + L"\\bigl({{g^{{p_3}{p_4}}_{{p_1}{p_2}}}{t^{{p_1}}_{{p_3}}}{t^{{p_" + L"2}}_{{p_4}}}}\\bigr) }"); } // Case 3: Anti-symmetric tensors @@ -208,9 +212,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(to_latex(input) == L"{ " - L"\\bigl({{\\bar{g}^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}" - L"{t^{{p_" - L"3}}_{{p_4}}}}\\bigr) }"); + L"\\bigl({{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{t^{{p_1}}_{{p_3}}}" + L"{t^{{p_2}}_{{p_4}}}}\\bigr) }"); } // Case 4: permuted indices @@ -322,11 +325,11 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(term1); canonicalize(term2); REQUIRE(to_latex(term1) == - L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_3}{i_4}}}{f^{{i_4}}_{{i_" - L"2}}}{t^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}}"); + L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_" + L"4}}}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}}"); REQUIRE(to_latex(term2) == - L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_3}{i_4}}}{f^{{i_4}}_{{i_" - L"2}}}{t^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}}"); + L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_" + L"4}}}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}}"); auto sum_of_terms = term1 + term2; simplify(sum_of_terms); REQUIRE(to_latex(sum_of_terms) == @@ -335,25 +338,24 @@ TEST_CASE("Canonicalizer", "[algorithms]") { } { // Terms 2 and 4 from spin-traced result - auto input = - ex(2) * - ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, - Symmetry::nonsymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, - WstrList{}) * - ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_3", L"i_4", L"i_2"}, WstrList{}, - Symmetry::nonsymm) + - ex(2) * - ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, - WstrList{L"a_1", L"a_2", L"a_3"}, WstrList{}, - Symmetry::nonsymm) * - ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, - WstrList{}) * - ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, - WstrList{L"i_2", L"i_3", L"i_4"}, WstrList{}, - Symmetry::nonsymm); + auto input = ex(2) * + ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, + WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, + WstrList{}) * + ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{L"i_3", L"i_4", L"i_2"}, + WstrList{}, Symmetry::nonsymm) + + ex(2) * + ex(L"S", WstrList{L"i_1", L"i_2", L"i_3"}, + WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{}, Symmetry::nonsymm) * + ex(L"f", WstrList{L"i_4"}, WstrList{L"i_1"}, + WstrList{}) * + ex(L"t", WstrList{L"a_1", L"a_2", L"a_3"}, + WstrList{L"i_2", L"i_3", L"i_4"}, + WstrList{}, Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == L"{ " diff --git a/tests/unit/test_mbpt.cpp b/tests/unit/test_mbpt.cpp index 4ab71bb62..7455ad820 100644 --- a/tests/unit/test_mbpt.cpp +++ b/tests/unit/test_mbpt.cpp @@ -2,14 +2,6 @@ // Created by Eduard Valeyev on 2019-02-19. // -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -17,7 +9,16 @@ #include #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include "catch.hpp" #include "test_config.hpp" @@ -623,7 +624,9 @@ TEST_CASE("MBPT", "[mbpt]") { auto result_op = op::vac_av(op::H_(2) * op::T_(2)); REQUIRE(result_op->size() == result->size()); - REQUIRE(simplify(result - result_op) == ex(0)); + auto diff = simplify(result - result_op); + std::wcout << "Diff: " << deparse_expr(diff) << std::endl; + REQUIRE(diff == ex(0)); } }); diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 0f8f2e950..96692d3ff 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -627,8 +627,8 @@ SECTION("Symmetrize expression") { auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); REQUIRE(to_latex(result) == - L"{{S^{{a_2}{a_1}}_{{i_3}{i_4}}}{g^{{i_4}{a_3}}_{{i_1}{i_2}}}{t^{" - L"{i_1}}_{{a_1}}}{t^{{i_2}}_{{a_2}}}{t^{{i_3}}_{{a_3}}}}"); + L"{{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{i_2}{a_3}}_{{i_3}{i_4}}}{t^{{" + L"i_4}}_{{a_1}}}{t^{{i_3}}_{{a_2}}}{t^{{i_1}}_{{a_3}}}}"); } { @@ -650,10 +650,10 @@ SECTION("Symmetrize expression") { WstrList{}); auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); + REQUIRE(result->is() == false); REQUIRE( to_latex(result) == - L"{{{2}}{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{a_3}{a_4}}_{{i_3}{i_4}}}{t^" - L"{{i_3}}_{{a_4}}}{t^{{i_4}}_{{a_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_3}}}}"); + L"{{{2}}{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{a_3}{a_4}}_{{i_3}{i_4}}}{t^{{i_4}}_{{a_2}}}{t^{{i_3}}_{{a_3}}}{t^{{i_1}{i_2}}_{{a_1}{a_4}}}}"); } } @@ -853,11 +853,7 @@ SECTION("Closed-shell spintrace CCSD") { rapid_simplify(result); canonicalize(result); REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}" - L"}} + " - L"{{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}}}" - L"\\bigr) }"); + L"{ \\bigl( - {{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}" L"}} + {{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}}}\\bigr) }"); } { @@ -1076,6 +1072,8 @@ SECTION("Closed-shell spintrace CCSDT terms") { result = closed_shell_spintrace( input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}, {L"i_3", L"a_3"}}); simplify(result); + std::wcout << "This one: " << deparse_expr(result) << std::endl; + REQUIRE(result->size() == 4); REQUIRE(to_latex(result) == L"{ \\bigl( - " L"{{{2}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" @@ -1390,8 +1388,7 @@ SECTION("Open-shell spin-tracing") { // Tensor canonicalize { - auto t3 = - ex(Tensor(L"t", {a3A, a2B, a2A}, {i1A, i2B, i3A}, {})); + auto t3 = ex(Tensor(L"t", {a3A, a2B, a2A}, {i1A, i2B, i3A}, {})); auto f = ex(Tensor(L"f", {a1A}, {a2A}, {})); auto ft3 = f * t3; ft3->canonicalize(); diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index b1a0e4b09..be63d524a 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -45,7 +45,7 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(t2.ket_rank() == 1); REQUIRE(t2.auxiliary_rank() == 0); REQUIRE(t2.rank() == 1); - REQUIRE(t2.indices().size() == 2); + REQUIRE(t2.const_indices().size() == 2); REQUIRE(t2.symmetry() == Symmetry::nonsymm); REQUIRE(t2.braket_symmetry() == BraKetSymmetry::conjugate); REQUIRE(t2.particle_symmetry() == ParticleSymmetry::symm); @@ -58,7 +58,7 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(t3.ket_rank() == 0); REQUIRE(t3.auxiliary_rank() == 1); REQUIRE_THROWS(t3.rank()); - REQUIRE(t3.indices().size() == 2); + REQUIRE(t3.const_indices().size() == 2); REQUIRE(t3.symmetry() == Symmetry::nonsymm); REQUIRE(t3.braket_symmetry() == BraKetSymmetry::conjugate); REQUIRE(t3.particle_symmetry() == ParticleSymmetry::symm); @@ -77,7 +77,7 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(t4.ket_rank() == 2); REQUIRE(t4.auxiliary_rank() == 1); REQUIRE(t4.rank() == 2); - REQUIRE(t4.indices().size() == 5); + REQUIRE(t4.const_indices().size() == 5); REQUIRE(t4.symmetry() == Symmetry::nonsymm); REQUIRE(t4.braket_symmetry() == BraKetSymmetry::symm); REQUIRE(t4.particle_symmetry() == ParticleSymmetry::nonsymm); diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 44a4d5f54..6bd58ff0e 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -47,6 +48,25 @@ std::string to_utf8(const std::wstring& wstr) { return converter.to_bytes(wstr); } +template +std::vector to_tensors(const Container& cont) { + std::vector tensors; + + std::transform(cont.begin(), cont.end(), std::back_inserter(tensors), + [](const auto& tensor) { + auto casted = + std::dynamic_pointer_cast(tensor); + REQUIRE(casted != nullptr); + return casted; + }); + return tensors; +} + +template +sequant::ExprPtr to_product(const Container& container) { + return sequant::ex(to_tensors(container)); +} + namespace sequant { class TensorNetworkAccessor { public: @@ -99,8 +119,10 @@ TEST_CASE("TensorNetwork", "[elements]") { Vertex v4(Origin::Ket, 1, 3, Symmetry::symm); Vertex v5(Origin::Bra, 3, 0, Symmetry::nonsymm); Vertex v6(Origin::Bra, 3, 2, Symmetry::nonsymm); + Vertex v7(Origin::Ket, 3, 1, Symmetry::nonsymm); + Vertex v8(Origin::Ket, 5, 0, Symmetry::symm); - const Index dummy; + const Index dummy(L"a_1"); Edge e1(v1, dummy); e1.connect_to(v4); @@ -111,7 +133,12 @@ TEST_CASE("TensorNetwork", "[elements]") { Edge e4(v4, dummy); e4.connect_to(v6); - Edge e5(v4, dummy); + Edge e5(v8, dummy); + e5.connect_to(v6); + Edge e6(v8, dummy); + e6.connect_to(v7); + + Edge e7(v4, dummy); // Due to tensor symmetries, these edges are considered equal REQUIRE(e1 == e2); @@ -128,10 +155,14 @@ TEST_CASE("TensorNetwork", "[elements]") { REQUIRE(e3 < e4); REQUIRE(!(e4 < e3)); + REQUIRE(!(e5 == e6)); + REQUIRE(e5 < e6); + REQUIRE(!(e6 < e5)); + // Unconnected edges always come before fully connected ones - REQUIRE(!(e5 == e1)); - REQUIRE(e5 < e1); - REQUIRE(!(e1 < e5)); + REQUIRE(!(e7 == e1)); + REQUIRE(e7 < e1); + REQUIRE(!(e1 < e7)); } SECTION("constructors") { @@ -187,11 +218,6 @@ TEST_CASE("TensorNetwork", "[elements]") { REQUIRE(std::dynamic_pointer_cast(tensors[1])); REQUIRE(*std::dynamic_pointer_cast(tensors[0]) == *t1); REQUIRE(*std::dynamic_pointer_cast(tensors[1]) == *t2); - - // index replacements performed by canonicalize() ... since canonicalize() - // not invoked this is empty - auto idxrepl = tn.idxrepl(); - REQUIRE(idxrepl.size() == 0); } } // SECTION("accessors") @@ -215,10 +241,9 @@ TEST_CASE("TensorNetwork", "[elements]") { // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) << // std::endl; REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == - L"{F^{{i_1}}_{{i_2}}}"); + L"{F^{{i_2}}_{{i_1}}}"); REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == - L"{\\tilde{a}^{{i_2}}_{{i_1}}}"); - REQUIRE(tn.idxrepl().size() == 2); + L"{\\tilde{a}^{{i_1}}_{{i_2}}}"); } SECTION("with externals") { @@ -255,7 +280,6 @@ TEST_CASE("TensorNetwork", "[elements]") { using named_indices_t = TensorNetwork::named_indices_t; named_indices_t indices{Index{L"i_17"}}; - std::wcout << "Canonicalize " << to_latex(t1_x_t2) << std::endl; tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false, &indices); @@ -274,6 +298,115 @@ TEST_CASE("TensorNetwork", "[elements]") { } } + SECTION("particle non-conserving") { + const auto input1 = parse_expr(L"P{;a1,a3}"); + const auto input2 = parse_expr(L"P{a1,a3;}"); + const std::wstring expected1 = L"{{P^{{a_1}{a_3}}_{}}}"; + const std::wstring expected2 = L"{{P^{}_{{a_1}{a_3}}}}"; + + for (int variant : {1, 2}) { + for (bool fast : {true, false}) { + TensorNetwork tn( + std::vector{variant == 1 ? input1 : input2}); + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), fast); + REQUIRE(tn.tensors().size() == 1); + auto result = ex(to_tensors(tn.tensors())); + REQUIRE(to_latex(result) == (variant == 1 ? expected1 : expected2)); + } + } + } + + SECTION("non-symmetric") { + const auto input = + parse_expr(L"A{i7,i3;i9,i12}:A I1{i7,i3;;x5}:N I2{;i9,i12;x5}:N") + .as() + .factors(); + const std::wstring expected = + L"A{i_1,i_2;i_3,i_4}:A * I1{i_1,i_2;;x_1}:N * I2{;i_3,i_4;x_1}:N"; + + for (bool fast : {true, false}) { + TensorNetwork tn(input); + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), fast); + const auto result = ex(to_tensors(tn.tensors())); + REQUIRE(deparse_expr(result) == expected); + } + } + + SECTION("particle-1,2-symmetry") { + const std::vector> pairs = { + {L"S{i_1,i_2,i_3;a_1,a_2,a_3}:N * f{i_4;i_2}:N * " + 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"}}; + for (const auto& pair : pairs) { + const auto first = parse_expr(pair.first).as().factors(); + const auto second = parse_expr(pair.second).as().factors(); + + TensorNetworkAccessor accessor; + auto [first_graph, first_labels] = + accessor.get_canonical_bliss_graph(TensorNetwork(first)); + auto [second_graph, second_labels] = + accessor.get_canonical_bliss_graph(TensorNetwork(second)); + if (first_graph->cmp(*second_graph) != 0) { + std::wstringstream stream; + stream << "First graph:\n"; + first_graph->write_dot(stream, first_labels, true); + stream << "Second graph:\n"; + second_graph->write_dot(stream, second_labels, true); + stream << "Wick graph:\n"; + auto [wick_graph, labels, d1, d2] = + WickGraph(first).make_bliss_graph(); + wick_graph->write_dot(stream, labels, true); + + FAIL(to_utf8(stream.str())); + } + + TensorNetwork tn1(first); + TensorNetwork tn2(second); + + tn1.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); + tn2.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); + + REQUIRE(tn1.tensors().size() == tn2.tensors().size()); + for (std::size_t i = 0; i < tn1.tensors().size(); ++i) { + auto t1 = std::dynamic_pointer_cast(tn1.tensors()[i]); + auto t2 = std::dynamic_pointer_cast(tn2.tensors()[i]); + REQUIRE(t1); + REQUIRE(t2); + REQUIRE(to_latex(t1) == to_latex(t2)); + } + } + } + + SECTION("miscellaneous") { + const std::vector> inputs = { + {L"g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A", L"g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A"}, + {L"g{a_1,i_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A", L"-1 g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A"}, + + {L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N"}, + {L"g{a_1,i_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", L"g{i_1,a_1;i_2,i_3}:N * I{i_3,i_2;i_1,a_1}:N"}, + }; + + for (const auto& [input, expected] : inputs) { + const auto input_tensors = parse_expr(input).as().factors(); + + TensorNetwork tn(input_tensors); + ExprPtr factor = tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), true); + + ExprPtr prod = to_product(tn.tensors()); + if (factor) { + prod = ex(prod.as().scale(factor.as().value())); + } + + REQUIRE(deparse_expr(prod) == expected); + } + } + +#ifndef SEQUANT_SKIP_LONG_TESTS SECTION("Exhaustive SRCC example") { // Note: the exact canonical form written here is implementation-defined // and doesn't actually matter What does, is that all equivalent ways of @@ -402,212 +535,45 @@ TEST_CASE("TensorNetwork", "[elements]") { } while (std::next_permutation(indices.begin(), indices.begin() + 4)); } while (std::next_permutation(factors.begin(), factors.end())); - // 4! (tensors) * 4! (internal indices) * 4! (external indices) - REQUIRE(total_variations == 24 * 24 * 24); + // 4! (tensors) * 4! (internal indices) * 4! (external indices) + REQUIRE(total_variations == 24 * 24 * 24); } - } // SECTION("canonicalizer") +#endif + + SECTION("idempotency") { + const std::vector inputs = { + L"F{i1;i8} g{i8,i9;i1,i7}", + L"A{i7,i3;i9,i12}:A I1{i7,i3;;x5}:N I2{;i9,i12;x5}:N", + L"f{i4;i1}:N t{a1,a2,a3;i2,i3,i4}:N S{i1,i2,i3;a1,a2,a3}:N", + L"P{a1,a3;} k{i8;i2}", + L"L{x6;;x2} P{;a1,a3}", + }; - SECTION("bliss graph") { - Index::reset_tmp_index(); - // to generate expressions in specified (i.e., platform-independent) manner - // can't use operator expression (due to unspecified order of evaluation of - // function arguments), must use initializer list - auto tmp = ex>( - {A(-2), H_(2), T_(2), T_(2), T_(2)}); - // canonicalize to avoid dependence on the implementation details of - // mbpt::sr::make_op - std::wcout << "Here it comes" << std::endl; - canonicalize(tmp); - std::wcout << "That was it" << std::endl; - // std::wcout << "A2*H2*T2*T2*T2 = " << to_latex(tmp) << std::endl; - TensorNetwork tn(tmp->as().factors()); - - std::wcout << "As equation: " - << to_latex(canonicalize(ex(ExprPtrList{tmp}))) - << std::endl; - - // make graph - REQUIRE_NOTHROW(tn.create_graph()); - TensorNetwork::Graph graph = tn.create_graph(); - - // create dot - std::basic_ostringstream oss; - REQUIRE_NOTHROW(graph.bliss_graph->write_dot(oss, graph.vertex_labels)); - std::wcout << ">>>>>>>>>>>>>>>> This one" << std::endl; - std::wcout << "oss.str() = " << std::endl << oss.str() << std::endl; - std::wcout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; - REQUIRE(oss.str() == - L"graph g {\n" - "v0 [label=\"{a_1}\"; color=\"#9e3,ba0\"];\n" - "v0 -- v29\n" - "v0 -- v58\n" - "v1 [label=\"{a_2}\"; color=\"#9e3,ba0\"];\n" - "v1 -- v29\n" - "v1 -- v58\n" - "v2 [label=\"{a_3}\"; color=\"#9e3,ba0\"];\n" - "v2 -- v33\n" - "v2 -- v54\n" - "v3 [label=\"{a_4}\"; color=\"#9e3,ba0\"];\n" - "v3 -- v33\n" - "v3 -- v54\n" - "v4 [label=\"{a_5}\"; color=\"#9e3,ba0\"];\n" - "v4 -- v37\n" - "v4 -- v50\n" - "v5 [label=\"{a_6}\"; color=\"#9e3,ba0\"];\n" - "v5 -- v37\n" - "v5 -- v50\n" - "v6 [label=\"{a_7}\"; color=\"#9e3,ba0\"];\n" - "v6 -- v22\n" - "v6 -- v41\n" - "v7 [label=\"{a_8}\"; color=\"#9e3,ba0\"];\n" - "v7 -- v22\n" - "v7 -- v41\n" - "v8 [label=\"{i_1}\"; color=\"#a78,ee8\"];\n" - "v8 -- v30\n" - "v8 -- v57\n" - "v9 [label=\"{i_2}\"; color=\"#a78,ee8\"];\n" - "v9 -- v30\n" - "v9 -- v57\n" - "v10 [label=\"{i_3}\"; color=\"#a78,ee8\"];\n" - "v10 -- v34\n" - "v10 -- v53\n" - "v11 [label=\"{i_4}\"; color=\"#a78,ee8\"];\n" - "v11 -- v34\n" - "v11 -- v53\n" - "v12 [label=\"{i_5}\"; color=\"#a78,ee8\"];\n" - "v12 -- v38\n" - "v12 -- v49\n" - "v13 [label=\"{i_6}\"; color=\"#a78,ee8\"];\n" - "v13 -- v38\n" - "v13 -- v49\n" - "v14 [label=\"{i_7}\"; color=\"#a78,ee8\"];\n" - "v14 -- v21\n" - "v14 -- v42\n" - "v15 [label=\"{i_8}\"; color=\"#a78,ee8\"];\n" - "v15 -- v21\n" - "v15 -- v42\n" - "v16 [label=\"{\\kappa_1}\"; color=\"#703,062\"];\n" - "v16 -- v25\n" - "v16 -- v46\n" - "v17 [label=\"{\\kappa_2}\"; color=\"#703,062\"];\n" - "v17 -- v25\n" - "v17 -- v46\n" - "v18 [label=\"{\\kappa_3}\"; color=\"#703,062\"];\n" - "v18 -- v26\n" - "v18 -- v45\n" - "v19 [label=\"{\\kappa_4}\"; color=\"#703,062\"];\n" - "v19 -- v26\n" - "v19 -- v45\n" - "v20 [label=\"A\"; color=\"#2ef,7ff\"];\n" - "v20 -- v23\n" - "v21 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v21 -- v23\n" - "v22 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v22 -- v23\n" - "v23 [label=\"bka\"; color=\"#2ef,7ff\"];\n" - "v24 [label=\"g\"; color=\"#96c,060\"];\n" - "v24 -- v27\n" - "v25 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v25 -- v27\n" - "v26 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v26 -- v27\n" - "v27 [label=\"bka\"; color=\"#96c,060\"];\n" - "v28 [label=\"t\"; color=\"#0f,016\"];\n" - "v28 -- v31\n" - "v29 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v29 -- v31\n" - "v30 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v30 -- v31\n" - "v31 [label=\"bka\"; color=\"#0f,016\"];\n" - "v32 [label=\"t\"; color=\"#0f,016\"];\n" - "v32 -- v35\n" - "v33 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v33 -- v35\n" - "v34 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v34 -- v35\n" - "v35 [label=\"bka\"; color=\"#0f,016\"];\n" - "v36 [label=\"t\"; color=\"#0f,016\"];\n" - "v36 -- v39\n" - "v37 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v37 -- v39\n" - "v38 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v38 -- v39\n" - "v39 [label=\"bka\"; color=\"#0f,016\"];\n" - "v40 [label=\"ã\"; color=\"#116,f93\"];\n" - "v40 -- v43\n" - "v41 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v41 -- v43\n" - "v42 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v42 -- v43\n" - "v43 [label=\"bka\"; color=\"#116,f93\"];\n" - "v44 [label=\"ã\"; color=\"#116,f93\"];\n" - "v44 -- v47\n" - "v45 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v45 -- v47\n" - "v46 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v46 -- v47\n" - "v47 [label=\"bka\"; color=\"#116,f93\"];\n" - "v48 [label=\"ã\"; color=\"#116,f93\"];\n" - "v48 -- v51\n" - "v49 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v49 -- v51\n" - "v50 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v50 -- v51\n" - "v51 [label=\"bka\"; color=\"#116,f93\"];\n" - "v52 [label=\"ã\"; color=\"#116,f93\"];\n" - "v52 -- v55\n" - "v53 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v53 -- v55\n" - "v54 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v54 -- v55\n" - "v55 [label=\"bka\"; color=\"#116,f93\"];\n" - "v56 [label=\"ã\"; color=\"#116,f93\"];\n" - "v56 -- v59\n" - "v57 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" - "v57 -- v59\n" - "v58 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" - "v58 -- v59\n" - "v59 [label=\"bka\"; color=\"#116,f93\"];\n" - "}\n"); - - // compute automorphism group - { - bliss::Stats stats; - graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + for (const std::wstring& current : inputs) { + auto factors1 = parse_expr(current).as().factors(); + auto factors2 = parse_expr(current).as().factors(); - std::vector> aut_generators; - auto save_aut = [&aut_generators](const unsigned int n, - const unsigned int* aut) { - aut_generators.emplace_back(aut, aut + n); - }; - graph.bliss_graph->find_automorphisms( - stats, &bliss::aut_hook, &save_aut); - std::basic_ostringstream oss; - bliss::print_auts(aut_generators, oss, decltype(graph.vertex_labels){}); - REQUIRE(oss.str() == - L"(18,19)\n" - "(16,17)\n" - "(6,7)\n" - "(14,15)\n" - "(0,1)\n" - "(8,9)\n" - "(2,3)\n" - "(4,5)\n" - "(10,11)\n" - "(12,13)\n" - "(2,4)(3,5)(10,12)(11,13)(32,36)(33,37)(34,38)(35,39)(48,52)(49," - "53)(50,54)(51,55)\n" - "(0,2)(1,3)(8,10)(9,11)(28,32)(29,33)(30,34)(31,35)(52,56)(53,57)" - "(54,58)(55,59)\n"); - // change to 1 to user vertex labels rather than indices - if (0) { - std::basic_ostringstream oss2; - bliss::print_auts(aut_generators, oss2, graph.vertex_labels); - std::wcout << oss2.str() << std::endl; + TensorNetwork reference_tn(factors1); + reference_tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), + false); + + TensorNetwork check_tn(factors2); + check_tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), + false); + + REQUIRE(to_latex(to_product(reference_tn.tensors())) == + to_latex(to_product(check_tn.tensors()))); + + for (bool fast : {true, false, true, true, false, false, true}) { + reference_tn.canonicalize( + TensorCanonicalizer::cardinal_tensor_labels(), fast); + + REQUIRE(to_latex(to_product(reference_tn.tensors())) == + to_latex(to_product(check_tn.tensors()))); + } } } - - } // SECTION("bliss graph") + } // SECTION("canonicalizer") SECTION("misc1") { if (false) { From 8b2ff3c8974c35df46b3150d173e4dc223060929 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 2 Feb 2024 10:16:23 +0100 Subject: [PATCH 12/85] Fix bubble_sort ignoring provided comp for zipped ranges --- SeQuant/core/algorithm.hpp | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/SeQuant/core/algorithm.hpp b/SeQuant/core/algorithm.hpp index f162872c8..85193adbc 100644 --- a/SeQuant/core/algorithm.hpp +++ b/SeQuant/core/algorithm.hpp @@ -5,6 +5,8 @@ #ifndef SEQUANT_ALGORITHM_HPP #define SEQUANT_ALGORITHM_HPP +#include + #include #include #include @@ -13,6 +15,10 @@ namespace sequant { +template +using suitable_call_operator = + decltype(std::declval()(std::declval()...)); + /// @brief bubble sort that uses swap exclusively template void bubble_sort(ForwardIter begin, Sentinel end, Compare comp) { @@ -22,12 +28,14 @@ void bubble_sort(ForwardIter begin, Sentinel end, Compare comp) { swapped = false; auto i = begin; auto inext = i; - // iterators either dereference to a reference or to a composite of - // references - constexpr const bool iter_deref_to_ref = - std::is_reference_v()))>; + + using deref_type = decltype(*(std::declval())); + constexpr const bool comp_works_for_range_type = + meta::is_detected_v; + for (++inext; inext != end; ++i, ++inext) { - if constexpr (iter_deref_to_ref) { + if constexpr (comp_works_for_range_type) { auto& val0 = *inext; auto& val1 = *i; if (comp(val0, val1)) { @@ -40,11 +48,22 @@ void bubble_sort(ForwardIter begin, Sentinel end, Compare comp) { auto val1 = *i; static_assert(std::tuple_size_v == 2, "need to generalize comparer to handle tuples"); - auto composite_compare = [](auto&& c0, auto&& c1) { - if (std::get<0>(c0) < std::get<0>(c1)) { // c0[0] < c1[0] + + using lhs_type = decltype(std::get<0>(val0)); + using rhs_type = decltype(std::get<1>(val0)); + constexpr const bool comp_works_for_tuple_entries = + meta::is_detected_v; + static_assert(comp_works_for_tuple_entries, + "Provided comparator not suitable for entries in " + "tuple-like objects (in zipped range?)"); + + auto composite_compare = [&comp](auto&& c0, auto&& c1) { + if (comp(std::get<0>(c0), std::get<0>(c1))) { // c0[0] < c1[0] return true; - } else if (!(std::get<0>(c1) < std::get<0>(c0))) { // c0[0] == c1[0] - return std::get<1>(c0) < std::get<1>(c1); + } else if (!comp(std::get<0>(c1), + std::get<0>(c0))) { // c0[0] == c1[0] + return comp(std::get<1>(c0), std::get<1>(c1)); } else { // c0[0] > c1[0] return false; } From 3c5270e050d94d15569f10f0846e86f02ac9c616 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 2 Feb 2024 20:30:42 +0100 Subject: [PATCH 13/85] Support comparators on zipped elements in bubble_sort --- SeQuant/core/algorithm.hpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/SeQuant/core/algorithm.hpp b/SeQuant/core/algorithm.hpp index 85193adbc..e8ff90c4c 100644 --- a/SeQuant/core/algorithm.hpp +++ b/SeQuant/core/algorithm.hpp @@ -36,17 +36,22 @@ void bubble_sort(ForwardIter begin, Sentinel end, Compare comp) { for (++inext; inext != end; ++i, ++inext) { if constexpr (comp_works_for_range_type) { - auto& val0 = *inext; - auto& val1 = *i; + const auto& val0 = *inext; + const auto& val1 = *i; if (comp(val0, val1)) { - using std::swap; - swap(val1, val0); + // current assumption: whenever iter_wap from below does not fall back + // to std::iter_swap, we are handling zipped ranges where the the + // tuple sizes is two (even) -> thus using a non-std swap + // implementation won't mess with the information of whether or not an + // even amount of swaps has occurred. + using ranges::iter_swap; + iter_swap(i, inext); swapped = true; } } else { - auto val0 = *inext; - auto val1 = *i; - static_assert(std::tuple_size_v == 2, + const auto &val0 = *inext; + 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)); From fb1c4702bf5a0ffe61805b034ff4535923861604 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 2 Feb 2024 20:43:28 +0100 Subject: [PATCH 14/85] More canonicalization --- SeQuant/core/tensor_canonicalizer.cpp | 178 +++++++++++++++++++++++++- SeQuant/core/tensor_canonicalizer.hpp | 44 +++++-- SeQuant/core/tensor_network.cpp | 25 ++-- SeQuant/core/tensor_network.hpp | 6 + 4 files changed, 224 insertions(+), 29 deletions(-) diff --git a/SeQuant/core/tensor_canonicalizer.cpp b/SeQuant/core/tensor_canonicalizer.cpp index bf98d1462..5037069ef 100644 --- a/SeQuant/core/tensor_canonicalizer.cpp +++ b/SeQuant/core/tensor_canonicalizer.cpp @@ -3,17 +3,162 @@ // #include +#include #include #include #include #include #include +#include #include namespace sequant { +template +using get_support = decltype(std::get<0>(std::declval())); + +template +constexpr bool is_tuple_like_v = meta::is_detected_v; + +struct TensorBlockIndexComparer { + template + bool operator()(const T& lhs, const T& rhs) const { + return compare(lhs, rhs) < 0; + } + + template + int compare(const T& lhs, const T& rhs) const { + if constexpr (is_tuple_like_v) { + static_assert( + std::tuple_size_v == 2, + "TensorBlockIndexComparer can only deal with tuple-like objects " + "of size 2"); + const auto& lhs_first = std::get<0>(lhs); + const auto& lhs_second = std::get<1>(lhs); + const auto& rhs_first = std::get<0>(rhs); + const auto& rhs_second = std::get<1>(rhs); + + static_assert(std::is_same_v, Index>, + "TensorBlockIndexComparer can only work with indices"); + static_assert(std::is_same_v, Index>, + "TensorBlockIndexComparer can only work with indices"); + static_assert(std::is_same_v, Index>, + "TensorBlockIndexComparer can only work with indices"); + static_assert(std::is_same_v, Index>, + "TensorBlockIndexComparer can only work with indices"); + + // First compare only index spaces of equivalent pairs + int res = compare_spaces(lhs_first, rhs_first); + if (res != 0) { + return res; + } + + res = compare_spaces(lhs_second, rhs_second); + if (res != 0) { + return res; + } + + // Then consider tags of equivalent pairs + res = compare_tags(lhs_first, rhs_first); + if (res != 0) { + return res; + } + + res = compare_tags(lhs_second, rhs_second); + return res; + } else { + static_assert(std::is_same_v, Index>, + "TensorBlockIndexComparer can only work with indices"); + + int res = compare_spaces(lhs, rhs); + if (res != 0) { + return res; + } + + res = compare_tags(lhs, rhs); + return res; + } + } + + int compare_spaces(const Index& lhs, const Index& rhs) const { + if (lhs.space() != rhs.space()) { + return lhs.space() < rhs.space() ? -1 : 1; + } + + if (lhs.has_proto_indices() != rhs.has_proto_indices()) { + return lhs.has_proto_indices() ? -1 : 1; + } + + if (lhs.proto_indices().size() != rhs.proto_indices().size()) { + return lhs.proto_indices().size() < rhs.proto_indices().size() ? -1 : 1; + } + + for (std::size_t i = 0; i < lhs.proto_indices().size(); ++i) { + const auto& lhs_proto = lhs.proto_indices()[i]; + const auto& rhs_proto = rhs.proto_indices()[i]; + + int res = compare_spaces(lhs_proto, rhs_proto); + if (res != 0) { + return res; + } + } + + // Index spaces are equal + return 0; + } + + int compare_tags(const Index& lhs, const Index& rhs) const { + if (!lhs.tag().has_value() || !rhs.tag().has_value()) { + // We only compare tags if both indices have a tag + return 0; + } + + const int lhs_tag = lhs.tag().value(); + const int rhs_tag = rhs.tag().value(); + + if (lhs_tag != rhs_tag) { + return lhs_tag < rhs_tag ? -1 : 1; + } + + return 0; + } +}; + +struct TensorIndexComparer { + template + bool operator()(const T& lhs, const T& rhs) const { + TensorBlockIndexComparer block_comp; + + int res = block_comp.compare(lhs, rhs); + + if (res != 0) { + return res < 0; + } + + // Fall back to regular index compare to break the tie + if constexpr (is_tuple_like_v) { + static_assert(std::tuple_size_v == 2, + "TensorIndexComparer can only deal with tuple-like objects " + "of size 2"); + + const Index& lhs_first = std::get<0>(lhs); + const Index& lhs_second = std::get<1>(lhs); + const Index& rhs_first = std::get<0>(rhs); + const Index& rhs_second = std::get<1>(rhs); + + if (lhs_first != rhs_first) { + return lhs_first < rhs_first; + } + + return lhs_second < rhs_second; + } else { + return lhs < rhs; + } + } +}; + TensorCanonicalizer::~TensorCanonicalizer() = default; std::pair>*, @@ -85,7 +230,7 @@ void TensorCanonicalizer::deregister_instance(std::wstring_view label) { } std::function - TensorCanonicalizer::index_comparer_ = std::less{}; + TensorCanonicalizer::index_comparer_ = TensorIndexComparer{}; const std::function& TensorCanonicalizer::index_comparer() { @@ -97,19 +242,40 @@ void TensorCanonicalizer::index_comparer( index_comparer_ = std::move(comparer); } -ExprPtr NullTensorCanonicalizer::apply(AbstractTensor&) { return {}; } +ExprPtr NullTensorCanonicalizer::apply(AbstractTensor&) const { return {}; } -ExprPtr DefaultTensorCanonicalizer::apply(AbstractTensor& t) { +void DefaultTensorCanonicalizer::tag_indices(AbstractTensor& t) const { // tag all indices as ext->true/ind->false - auto braket_view = braket(t); - ranges::for_each(braket_view, [this](auto& idx) { + ranges::for_each(indices(t), [this](auto& idx) { auto it = external_indices_.find(std::wstring(idx.label())); auto is_ext = it != external_indices_.end(); idx.tag().assign( is_ext ? 0 : 1); // ext -> 0, int -> 1, so ext will come before }); +} + +ExprPtr DefaultTensorCanonicalizer::apply(AbstractTensor& t) const { + tag_indices(t); + + // TODO: Shove TensorIndexComparer into this->index_comparer_ (which makes it impossible for that to be a std::function) + //auto result = this->apply(t, this->index_comparer_); + auto result = this->apply(t, TensorIndexComparer{}); + + reset_tags(t); + + return result; +} + +template +using suitable_call_operator = + decltype(std::declval()(std::declval()...)); + +ExprPtr TensorBlockCanonicalizer::apply(AbstractTensor& t) const { + tag_indices(t); + + auto result = + DefaultTensorCanonicalizer::apply(t, TensorBlockIndexComparer{}); - auto result = this->apply(t, this->index_comparer_); reset_tags(t); return result; diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp index 3877dbfdf..6015282a2 100644 --- a/SeQuant/core/tensor_canonicalizer.hpp +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -80,10 +80,9 @@ class TensorCanonicalizer { /// return of the canonicalization? /// @note canonicalization compared indices returned by index_comparer // TODO generalize for complex tensors - virtual ExprPtr apply(AbstractTensor&) = 0; + virtual ExprPtr apply(AbstractTensor&) const = 0; /// @return reference to the object used to compare Index objects - /// @note the default is to use an object of type `std::less` static const std::function& index_comparer(); @@ -92,9 +91,11 @@ class TensorCanonicalizer { std::function comparer); protected: - inline auto bra_range(AbstractTensor& t) { return t._bra_mutable(); } - inline auto ket_range(AbstractTensor& t) { return t._ket_mutable(); } - inline auto auxiliary_range(AbstractTensor& t) { return t._auxiliary_mutable(); } + inline auto bra_range(AbstractTensor& t) const { return t._bra_mutable(); } + inline auto ket_range(AbstractTensor& t) const { return t._ket_mutable(); } + inline auto auxiliary_range(AbstractTensor& t) const { + return t._auxiliary_mutable(); + } /// the object used to compare indices static std::function index_comparer_; @@ -112,7 +113,7 @@ class NullTensorCanonicalizer : public TensorCanonicalizer { public: virtual ~NullTensorCanonicalizer() = default; - ExprPtr apply(AbstractTensor&) override; + ExprPtr apply(AbstractTensor&) const override; }; class DefaultTensorCanonicalizer : public TensorCanonicalizer { @@ -139,12 +140,12 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { /// t.symmetry()!=Symmetry::nonsymm ), /// with the external indices appearing "before" (smaller particle /// indices) than the internal indices - ExprPtr apply(AbstractTensor& t) override; + ExprPtr apply(AbstractTensor& t) const override; /// Core of DefaultTensorCanonicalizer::apply, only does the canonicalization, /// i.e. no tagging/untagging template - ExprPtr apply(AbstractTensor& t, const Compare& comp) { + ExprPtr apply(AbstractTensor& t, const Compare& comp) const { // std::wcout << "abstract tensor: " << to_latex(t) << "\n"; auto s = symmetry(t); auto is_antisymm = (s == Symmetry::antisymm); @@ -203,10 +204,10 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { abort(); } - // For now we treat auxiliary indices as being unrelated to one another. This makes their - // order insignificant, allowing us to simply sort them. - auto _aux = auxiliary_range(t); - ranges::sort(_aux, comp); + // For now we treat auxiliary indices as being unrelated to one another. + // This makes their order insignificant, allowing us to simply sort them. + auto _aux = auxiliary_range(t); + // ranges::sort(_aux, comp); ExprPtr result = is_antisymm ? (even == false ? ex(-1) : nullptr) : nullptr; @@ -215,8 +216,23 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { private: container::map external_indices_; + + protected: + void tag_indices(AbstractTensor& t) const; +}; + +class TensorBlockCanonicalizer : public DefaultTensorCanonicalizer { + public: + TensorBlockCanonicalizer() = default; + ~TensorBlockCanonicalizer() = default; + + template + TensorBlockCanonicalizer(const IndexContainer& external_indices) + : DefaultTensorCanonicalizer(external_indices) {} + + ExprPtr apply(AbstractTensor& t) const override; }; -} +} // namespace sequant -#endif // SEQUANT_CORE_TENSOR_CANONICALIZER_HPP +#endif // SEQUANT_CORE_TENSOR_CANONICALIZER_HPP diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 89a3629cd..a65e226ea 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -535,9 +535,9 @@ ExprPtr TensorNetwork::canonicalize( canonicalize_graph(named_indices); } - // Ensure each individual tensor is canonical with the current indexing in - // order to properly be able to identify tensor blocks - ExprPtr byproduct = canonicalize_individual_tensors(named_indices); + // Ensure each individual tensor is written in the way that its tensor + // block (== order of index spaces) is canonical + ExprPtr byproduct = canonicalize_individual_tensor_blocks(named_indices); CanonicalTensorCompare tensor_sorter( cardinal_tensor_labels, true); @@ -977,20 +977,27 @@ container::svector> TensorNetwork::factorize() { abort(); // not yet implemented } +ExprPtr TensorNetwork::canonicalize_individual_tensor_blocks(const named_indices_t &named_indices) { + return do_individual_canonicalization(TensorBlockCanonicalizer(named_indices)); +} + ExprPtr TensorNetwork::canonicalize_individual_tensors( const named_indices_t &named_indices) { + return do_individual_canonicalization( + DefaultTensorCanonicalizer(named_indices)); +} + +ExprPtr TensorNetwork::do_individual_canonicalization( + const TensorCanonicalizer &canonicalizer) { ExprPtr byproduct = ex(1); - // override the default canonicalizer - DefaultTensorCanonicalizer default_tensor_canonizer(named_indices); for (auto &tensor : tensors_) { auto nondefault_canonizer_ptr = TensorCanonicalizer::nondefault_instance_ptr(tensor->_label()); - TensorCanonicalizer *tensor_canonizer = nondefault_canonizer_ptr - ? nondefault_canonizer_ptr.get() - : &default_tensor_canonizer; + const TensorCanonicalizer &tensor_canonizer = + nondefault_canonizer_ptr ? *nondefault_canonizer_ptr : canonicalizer; - auto bp = tensor_canonizer->apply(*tensor); + auto bp = canonicalizer.apply(*tensor); if (bp) { byproduct *= bp; diff --git a/SeQuant/core/tensor_network.hpp b/SeQuant/core/tensor_network.hpp index 4d5db6383..12f72f464 100644 --- a/SeQuant/core/tensor_network.hpp +++ b/SeQuant/core/tensor_network.hpp @@ -264,9 +264,15 @@ class TensorNetwork { /// remains undefined. void canonicalize_graph(const named_indices_t &named_indices); + /// Canonicalizes every individual tensor for itself, taking into account only tensor blocks + /// @returns The byproduct of the canonicalizations + ExprPtr canonicalize_individual_tensor_blocks(const named_indices_t &named_indices); + /// Canonicalizes every individual tensor for itself /// @returns The byproduct of the canonicalizations ExprPtr canonicalize_individual_tensors(const named_indices_t &named_indices); + + ExprPtr do_individual_canonicalization(const TensorCanonicalizer &canonicalizer); }; template From 6c2ec80d2800df1fcc8f7e7686fbfcc4dfed4943 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 2 Feb 2024 20:43:40 +0100 Subject: [PATCH 15/85] Adapt test cases to use new canonical form --- tests/unit/test_canonicalize.cpp | 4 +-- tests/unit/test_spin.cpp | 52 ++++++++++++++---------------- tests/unit/test_tensor_network.cpp | 1 + 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 2661e8124..28b9cedee 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -271,8 +271,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { REQUIRE(input->size() == 1); REQUIRE(to_latex(input) == L"{ " - L"\\bigl({{g^{{a_3}{i_1}}_{{i_3}{i_4}}}{t^{{i_3}}_{{a_2}}}{t^{{i_" - L"4}{i_2}}_{{a_1}{a_3}}}}\\bigr) }"); + L"\\bigl({{g^{{i_1}{a_3}}_{{i_3}{i_4}}}{t^{{i_4}}_{{a_2}}}{t^{{i_" + L"3}{i_2}}_{{a_1}{a_3}}}}\\bigr) }"); } { // Case 5: CCSDT R3: S3 * F * T3 diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 96692d3ff..dfd8f5c50 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -627,8 +627,7 @@ SECTION("Symmetrize expression") { auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); REQUIRE(to_latex(result) == - L"{{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{i_2}{a_3}}_{{i_3}{i_4}}}{t^{{" - L"i_4}}_{{a_1}}}{t^{{i_3}}_{{a_2}}}{t^{{i_1}}_{{a_3}}}}"); + L"{{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{i_1}{a_3}}_{{i_3}{i_4}}}{t^{{i_3}}_{{a_1}}}{t^{{i_4}}_{{a_2}}}{t^{{i_2}}_{{a_3}}}}"); } { @@ -670,16 +669,18 @@ SECTION("Transform expression") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - {{g^{{a_2}{i_1}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}} + " - L"{{{2}}{g^{{i_1}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); + REQUIRE( + to_latex(result) == + L"{ \\bigl( - {{g^{{a_2}{i_1}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}} + " + L"{{{2}}{g^{{i_1}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); container::map idxmap = {{Index{L"i_1"}, Index{L"i_2"}}, {Index{L"i_2"}, Index{L"i_1"}}}; auto transformed_result = transform_expr(result, idxmap); - REQUIRE(to_latex(transformed_result) == - L"{ \\bigl( - {{g^{{a_2}{i_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}} + " - L"{{{2}}{g^{{i_2}{a_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); + REQUIRE( + to_latex(transformed_result) == + L"{ \\bigl( - {{g^{{a_2}{i_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}} + " + L"{{{2}}{g^{{i_2}{a_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); } SECTION("Swap bra kets") { @@ -801,6 +802,8 @@ SECTION("Closed-shell spintrace CCSD") { container::map idxmap = {{Index{L"i_1"}, Index{L"i_2"}}, {Index{L"i_2"}, Index{L"i_1"}}}; auto transformed_result = transform_expr(result, idxmap); + REQUIRE(transformed_result->is()); + REQUIRE(transformed_result->size() == 2); REQUIRE( to_latex(transformed_result) == L"{ \\bigl( - {{g^{{a_2}{i_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}} + " @@ -852,8 +855,12 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - {{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}" L"}} + {{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}}}\\bigr) }"); + REQUIRE(to_latex(result) == + L"{ \\bigl( - " + L"{{{2}}{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}" + L"}} + " + L"{{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}}}" + L"\\bigr) }"); } { @@ -912,10 +919,8 @@ SECTION("Closed-shell spintrace CCSD") { canonicalize(result); REQUIRE(to_latex(result) == L"{ " - L"\\bigl({{{2}}{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}{" - L"t^{{i_1}}_{{a_3}}}} - " - L"{{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_3}}}{t^{{i_1}}_{{" - L"a_2}}}}\\bigr) }"); + L"\\bigl({{{2}}{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}{t^{{i_1}}_{{a_3}}}} - " + L"{{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_1}}_{{a_2}}}{t^{{i_2}}_{{a_3}}}}\\bigr) }"); } { @@ -932,11 +937,7 @@ SECTION("Closed-shell spintrace CCSD") { rapid_simplify(result); canonicalize(result); REQUIRE(to_latex(result) == - L"{ " - L"\\bigl({{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_" - L"3}}_{{a_2}}}} - " - L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}" - L"}_{{a_2}}}}\\bigr) }"); + L"{ \\bigl( - {{{2}}{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}}_{{a_2}}}} + {{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); } { @@ -997,11 +998,10 @@ SECTION("Closed-shell spintrace CCSD") { rapid_simplify(result); canonicalize(result); REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}" - L"{i_2}}_{{a_1}{a_2}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_2}{i_3}" - L"}_{{a_1}{a_2}}}}\\bigr) }"); + L"{ \\bigl(" + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_2}}}{t^{{i_3}{i_2}}_{{a_1}{a_3}}}} - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}}}" + L"\\bigr) }"); } { @@ -1072,7 +1072,6 @@ SECTION("Closed-shell spintrace CCSDT terms") { result = closed_shell_spintrace( input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}, {L"i_3", L"a_3"}}); simplify(result); - std::wcout << "This one: " << deparse_expr(result) << std::endl; REQUIRE(result->size() == 4); REQUIRE(to_latex(result) == L"{ \\bigl( - " @@ -1393,8 +1392,7 @@ SECTION("Open-shell spin-tracing") { auto ft3 = f * t3; ft3->canonicalize(); REQUIRE(to_latex(ft3) == - L"{{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_3}{i↑_1}{i↓_2}}_{{a↑_2}{a↑_3}{a↓_" - L"2}}}}"); + L"{{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↑_3}{i↓_2}}_{{a↑_3}{a↑_2}{a↓_2}}}}"); } // g diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 6bd58ff0e..c5c329901 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -391,6 +391,7 @@ TEST_CASE("TensorNetwork", "[elements]") { {L"g{a_1,i_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", L"g{i_1,a_1;i_2,i_3}:N * I{i_3,i_2;i_1,a_1}:N"}, }; + for (const auto& [input, expected] : inputs) { const auto input_tensors = parse_expr(input).as().factors(); From cd14c64cb92f3d00e9c08defa5bbe866377bba11 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 5 Feb 2024 13:06:27 +0100 Subject: [PATCH 16/85] Generalize more code parts to use indices() instead of braket() --- SeQuant/core/eval_expr.cpp | 69 ++++++------------------------------ SeQuant/core/wick.impl.hpp | 4 +-- SeQuant/domain/eval/eval.cpp | 4 +-- SeQuant/domain/mbpt/mr.cpp | 2 +- SeQuant/domain/mbpt/spin.cpp | 22 ++++-------- 5 files changed, 21 insertions(+), 80 deletions(-) diff --git a/SeQuant/core/eval_expr.cpp b/SeQuant/core/eval_expr.cpp index 61fb46663..f4e85c47b 100644 --- a/SeQuant/core/eval_expr.cpp +++ b/SeQuant/core/eval_expr.cpp @@ -38,7 +38,7 @@ size_t hash_imed(EvalExpr const&, EvalExpr const&, EvalOp) noexcept; ExprPtr make_imed(EvalExpr const&, EvalExpr const&, EvalOp) noexcept; bool is_tot(Tensor const& t) noexcept { - return ranges::any_of(t.const_braket(), &Index::has_proto_indices); + return ranges::any_of(t.const_indices(), &Index::has_proto_indices); } std::wstring_view const var_label = L"Z"; @@ -153,7 +153,7 @@ InnerOuterIndices EvalExpr::inner_outer_indices() const noexcept { container::svector inner; container::svector outer; - for (auto const& idx : t.const_braket()) { + for (auto const& idx : t.const_indices()) { inner.emplace_back(idx); for (auto const& pidx : idx.proto_indices()) outer.emplace_back(pidx); } @@ -194,9 +194,9 @@ namespace { /// the hash. /// template -size_t hash_braket(T const& bk) noexcept { +size_t hash_indices(T const& indices) noexcept { size_t h = 0; - for (auto const& idx : bk) { + for (auto const& idx : indices) { hash::combine(h, hash::value(idx.space().type().to_int32())); hash::combine(h, hash::value(idx.space().qns().to_int32())); } @@ -207,7 +207,7 @@ size_t hash_braket(T const& bk) noexcept { /// \return hash value to identify the connectivity between a pair of tensors. /// /// @note Let [(i,j)] be the list of ordered pair of index positions that are -/// connected. i is the position in the braket of the first tensor (T1) +/// connected. i is the position in the indices of the first tensor (T1) /// and j is that of the second tensor (T2). Then this function combines /// the hash values of the elements of this list. /// @@ -216,8 +216,8 @@ size_t hash_braket(T const& bk) noexcept { size_t hash_tensor_pair_topology(Tensor const& t1, Tensor const& t2) noexcept { using ranges::views::enumerate; size_t h = 0; - for (auto&& [pos1, idx1] : t1.const_braket() | enumerate) - for (auto&& [pos2, idx2] : t2.const_braket() | enumerate) + for (auto&& [pos1, idx1] : t1.const_indices() | enumerate) + for (auto&& [pos2, idx2] : t2.const_indices() | enumerate) if (idx1.label() == idx2.label()) hash::combine(h, hash::value(std::pair(pos1, pos2))); return h; @@ -226,7 +226,7 @@ size_t hash_tensor_pair_topology(Tensor const& t1, Tensor const& t2) noexcept { size_t hash_terminal_tensor(Tensor const& tnsr) noexcept { size_t h = 0; hash::combine(h, hash::value(tnsr.label())); - hash::combine(h, hash_braket(tnsr.const_braket())); + hash::combine(h, hash_indices(tnsr.const_indices())); return h; } @@ -248,55 +248,6 @@ size_t hash_imed(EvalExpr const& left, EvalExpr const& right, return h; } -// TODO: Remove? -std::pair, // bra - container::svector // ket - > -target_braket(Tensor const& t1, Tensor const& t2) noexcept { - using ranges::views::keys; - using ranges::views::values; - using ranges::views::zip; - using index_container = container::svector; - - auto remove_item = [](auto& vec, size_t pos) -> void { - std::swap(vec[pos], vec.back()); - vec.pop_back(); - }; - - auto left = zip(t1.bra(), t1.ket()) | ranges::to_vector; - auto right = zip(t2.bra(), t2.ket()) | ranges::to_vector; - - while (!right.empty()) { - for (std::size_t rr = 0; rr < right.size(); ++rr) { - auto& [rb, rk] = right[rr]; - for (std::size_t ll = 0; ll < left.size(); ++ll) { - auto& [lb, lk] = left[ll]; - if (lb == rk && rb == lk) { - remove_item(left, ll); - remove_item(right, rr); - goto next_contract; - } else if (lb == rk) { - std::swap(lk, rk); - remove_item(left, ll); - goto next_contract; - } else if (lk == rb) { - std::swap(lb, rb); - remove_item(left, ll); - goto next_contract; - } - } // ll - left.emplace_back(rb, rk); - remove_item(right, rr); - } // rr - next_contract: - /* just point to the start of the while loop */; - } - // the result is now in left - - return {keys(left) | ranges::to, - values(left) | ranges::to}; -} - Symmetry tensor_symmetry_sum(EvalExpr const& left, EvalExpr const& right) noexcept { auto const& t1 = left.expr()->as(); @@ -333,11 +284,11 @@ Symmetry tensor_symmetry_prod(EvalExpr const& left, if (hash::value(left) == hash::value(right)) { // potential outer product of the same tensor auto const uniq_idxs = - ranges::views::concat(tnsr1.const_braket(), tnsr2.const_braket()) | + ranges::views::concat(tnsr1.const_indices(), tnsr2.const_indices()) | ranges::to; if (static_cast(ranges::distance(uniq_idxs)) == - tnsr1.const_braket().size() + tnsr2.const_braket().size()) { + tnsr1.const_indices().size() + tnsr2.const_indices().size()) { // outer product confirmed return Symmetry::antisymm; } diff --git a/SeQuant/core/wick.impl.hpp b/SeQuant/core/wick.impl.hpp index bf700e448..1d95bc8b3 100644 --- a/SeQuant/core/wick.impl.hpp +++ b/SeQuant/core/wick.impl.hpp @@ -233,7 +233,7 @@ inline bool apply_index_replacement_rules( const auto &factor = *it; if (factor->is()) { auto &tensor = factor->as(); - assert(ranges::none_of(tensor.const_braket(), [](const Index &idx) { + assert(ranges::none_of(tensor.const_indices(), [](const Index &idx) { return idx.tag().has_value(); })); } @@ -355,7 +355,7 @@ inline void reduce_wick_impl(std::shared_ptr &expr, std::set all_indices; ranges::for_each(*expr, [&all_indices](const auto &factor) { if (factor->template is()) { - ranges::for_each(factor->template as().braket(), + ranges::for_each(factor->template as().indices(), [&all_indices](const Index &idx) { [[maybe_unused]] auto result = all_indices.insert(idx); diff --git a/SeQuant/domain/eval/eval.cpp b/SeQuant/domain/eval/eval.cpp index 1f3382442..56ee44f6d 100644 --- a/SeQuant/domain/eval/eval.cpp +++ b/SeQuant/domain/eval/eval.cpp @@ -43,7 +43,7 @@ EvalExprBTAS::annot_t const& EvalExprBTAS::annot() const noexcept { } EvalExprBTAS::EvalExprBTAS(Tensor const& t) noexcept - : EvalExpr{t}, annot_{index_hash(t.const_braket()) | ranges::to} {} + : EvalExpr{t}, annot_{index_hash(t.const_indices()) | ranges::to} {} EvalExprBTAS::EvalExprBTAS(Constant const& c) noexcept : EvalExpr{c} {} @@ -55,7 +55,7 @@ EvalExprBTAS::EvalExprBTAS(EvalExprBTAS const& left, // : EvalExpr{left, right, op} { if (result_type() == ResultType::Tensor) { assert(!tot() && "Tensor of tensor not supported in BTAS"); - annot_ = index_hash(as_tensor().const_braket()) | ranges::to; + annot_ = index_hash(as_tensor().const_indices()) | ranges::to; } } diff --git a/SeQuant/domain/mbpt/mr.cpp b/SeQuant/domain/mbpt/mr.cpp index fae232b75..070cfaf4a 100644 --- a/SeQuant/domain/mbpt/mr.cpp +++ b/SeQuant/domain/mbpt/mr.cpp @@ -337,7 +337,7 @@ ExprPtr vac_av(ExprPtr expr, std::vector> nop_connections, auto tensor_ptr = std::dynamic_pointer_cast(factor); if (tensor_ptr) { - ranges::for_each(tensor_ptr->_braket(), + ranges::for_each(tensor_ptr->_indices(), [&](auto& idx) { op(idx, *tensor_ptr); }); } }); diff --git a/SeQuant/domain/mbpt/spin.cpp b/SeQuant/domain/mbpt/spin.cpp index 1b8462e74..3fc069c02 100644 --- a/SeQuant/domain/mbpt/spin.cpp +++ b/SeQuant/domain/mbpt/spin.cpp @@ -124,8 +124,7 @@ ExprPtr transform_expr(const ExprPtr& expr, auto transform_tensor = [&index_replacements](const Tensor& tensor) { auto result = std::make_shared(tensor); result->transform_indices(index_replacements); - ranges::for_each(result->const_braket(), - [](const Index& idx) { idx.reset_tag(); }); + reset_tags(*result); return result; }; @@ -800,9 +799,7 @@ ExprPtr S_maps(const ExprPtr& expr) { if (!has_tensor(expr, L"S")) return expr; auto reset_idx_tags = [](ExprPtr& expr) { - if (expr->is()) - ranges::for_each(expr->as().const_braket(), - [](const Index& idx) { idx.reset_tag(); }); + if (expr->is()) reset_tags(expr.as()); }; expr->visit(reset_idx_tags); @@ -869,9 +866,7 @@ ExprPtr closed_shell_spintrace( // Index tags are cleaned prior to calling the fast canonicalizer auto reset_idx_tags = [](ExprPtr& expr) { - if (expr->is()) - ranges::for_each(expr->as().const_braket(), - [](const Index& idx) { idx.reset_tag(); }); + if (expr->is()) reset_tags(expr.as()); }; // Cleanup index tags @@ -1369,9 +1364,7 @@ std::vector open_shell_spintrace( }; auto reset_idx_tags = [](ExprPtr& expr) { - if (expr->is()) - ranges::for_each(expr->as().const_braket(), - [](const Index& idx) { idx.reset_tag(); }); + if (expr->is()) reset_tags(expr.as()); }; // Internal and external index replacements are independent @@ -1561,9 +1554,7 @@ ExprPtr spintrace( }; auto reset_idx_tags = [](ExprPtr& expr) { - if (expr->is()) - ranges::for_each(expr->as().const_braket(), - [](const Index& idx) { idx.reset_tag(); }); + if (expr->is()) reset_tags(expr.as()); }; // Most important lambda of this function @@ -1742,8 +1733,7 @@ ExprPtr factorize_S(const ExprPtr& expression, const container::map& replacement_map) { auto result = std::make_shared(tensor); result->transform_indices(replacement_map); - ranges::for_each(result->const_braket(), - [](const Index& idx) { idx.reset_tag(); }); + reset_tags(*result); return result; }; From f0cd5bdb55b49f83f7f816a5437e35462cf7d3fb Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 5 Feb 2024 13:31:54 +0100 Subject: [PATCH 17/85] Fix wrong index label ordering --- SeQuant/core/index.hpp | 7 ++++++- tests/unit/test_index.cpp | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SeQuant/core/index.hpp b/SeQuant/core/index.hpp index 908494c9b..f698e6683 100644 --- a/SeQuant/core/index.hpp +++ b/SeQuant/core/index.hpp @@ -711,7 +711,12 @@ class Index : public Taggable { } if (i1.label() != i2.label()) { - // TODO: This won't yield the desired result for e.g. i_2 < i_13 + // Note: Can't simply use label1 < label2 as that won't yield expected + // results for e.g. i2 < i11 (which will yield false) + if (i1.label().size() != i2.label().size()) { + return i1.label().size() < i2.label().size(); + } + return i1.label() < i2.label(); } diff --git a/tests/unit/test_index.cpp b/tests/unit/test_index.cpp index 1ccb1071d..dca8f85f7 100644 --- a/tests/unit/test_index.cpp +++ b/tests/unit/test_index.cpp @@ -150,9 +150,15 @@ TEST_CASE("Index", "[elements][index]") { SECTION("ordering") { Index i1(L"i_1"); Index i2(L"i_2"); + Index i3(L"i_11"); REQUIRE(i1 < i2); REQUIRE(!(i2 < i1)); REQUIRE(!(i1 < i1)); + REQUIRE(i1 < i3); + REQUIRE(!(i3 < i1)); + REQUIRE(i2 < i3); + REQUIRE(!(i3 < i2)); + REQUIRE(!(i3 < i3)); Index a1(L"a_2"); REQUIRE(i1 < a1); REQUIRE(!(a1 < i1)); From 7dcf11b532872d5cf034707cd3c04fbbc5dceb0c Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 5 Feb 2024 16:20:15 +0100 Subject: [PATCH 18/85] Fix typo --- SeQuant/core/algorithm.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeQuant/core/algorithm.hpp b/SeQuant/core/algorithm.hpp index e8ff90c4c..58c89dac2 100644 --- a/SeQuant/core/algorithm.hpp +++ b/SeQuant/core/algorithm.hpp @@ -40,7 +40,7 @@ void bubble_sort(ForwardIter begin, Sentinel end, Compare comp) { const auto& val1 = *i; if (comp(val0, val1)) { // current assumption: whenever iter_wap from below does not fall back - // to std::iter_swap, we are handling zipped ranges where the the + // to std::iter_swap, we are handling zipped ranges where the // tuple sizes is two (even) -> thus using a non-std swap // implementation won't mess with the information of whether or not an // even amount of swaps has occurred. From 78fe054c4c10f1ff68ed65e1c92cd865f811ed99 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 5 Feb 2024 16:20:39 +0100 Subject: [PATCH 19/85] Account for aux indices in Tensor::static_less_than --- SeQuant/core/tensor.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index 7ea59cb94..be20c45fd 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -365,8 +365,6 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { return this->ket_rank() < that_cast.ket_rank(); } - // TODO: account for aux indices - // v1: compare hashes only // return Expr::static_less_than(that); // v2: compare fully @@ -376,9 +374,15 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { that_cast.bra().end()); } - return std::lexicographical_compare(this->ket().begin(), this->ket().end(), - that_cast.ket().begin(), - that_cast.ket().end()); + if (this->ket() != that_cast.ket()) { + return std::lexicographical_compare( + this->ket().begin(), this->ket().end(), that_cast.ket().begin(), + that_cast.ket().end()); + } + + return std::lexicographical_compare( + this->auxiliary().begin(), this->auxiliary().end(), + that_cast.auxiliary().begin(), that_cast.auxiliary().end()); } // these implement the AbstractTensor interface From 3f56d95f43f63b471682cda634875decc9dbc7dd Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 5 Feb 2024 16:21:20 +0100 Subject: [PATCH 20/85] Make TensorCanonicalizer expose two comparator objects --- SeQuant/core/tensor_canonicalizer.cpp | 25 ++++++++++++-------- SeQuant/core/tensor_canonicalizer.hpp | 34 +++++++++++++++++---------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/SeQuant/core/tensor_canonicalizer.cpp b/SeQuant/core/tensor_canonicalizer.cpp index 5037069ef..85da1652f 100644 --- a/SeQuant/core/tensor_canonicalizer.cpp +++ b/SeQuant/core/tensor_canonicalizer.cpp @@ -229,19 +229,26 @@ void TensorCanonicalizer::deregister_instance(std::wstring_view label) { } } -std::function - TensorCanonicalizer::index_comparer_ = TensorIndexComparer{}; +TensorCanonicalizer::index_comparer_t TensorCanonicalizer::index_comparer_ = TensorIndexComparer{}; -const std::function& -TensorCanonicalizer::index_comparer() { +TensorCanonicalizer::index_pair_comparer_t TensorCanonicalizer::index_pair_comparer_ = TensorIndexComparer{}; + +const TensorCanonicalizer::index_comparer_t& TensorCanonicalizer::index_comparer() { return index_comparer_; } -void TensorCanonicalizer::index_comparer( - std::function comparer) { +void TensorCanonicalizer::index_comparer(index_comparer_t comparer) { index_comparer_ = std::move(comparer); } +const TensorCanonicalizer::index_pair_comparer_t& TensorCanonicalizer::index_pair_comparer() { + return index_pair_comparer_; +} + +void TensorCanonicalizer::index_pair_comparer(index_pair_comparer_t comparer) { + index_pair_comparer_ = std::move(comparer); +} + ExprPtr NullTensorCanonicalizer::apply(AbstractTensor&) const { return {}; } void DefaultTensorCanonicalizer::tag_indices(AbstractTensor& t) const { @@ -257,9 +264,7 @@ void DefaultTensorCanonicalizer::tag_indices(AbstractTensor& t) const { ExprPtr DefaultTensorCanonicalizer::apply(AbstractTensor& t) const { tag_indices(t); - // TODO: Shove TensorIndexComparer into this->index_comparer_ (which makes it impossible for that to be a std::function) - //auto result = this->apply(t, this->index_comparer_); - auto result = this->apply(t, TensorIndexComparer{}); + auto result = this->apply(t, this->index_comparer_, this->index_pair_comparer_); reset_tags(t); @@ -274,7 +279,7 @@ ExprPtr TensorBlockCanonicalizer::apply(AbstractTensor& t) const { tag_indices(t); auto result = - DefaultTensorCanonicalizer::apply(t, TensorBlockIndexComparer{}); + DefaultTensorCanonicalizer::apply(t, TensorBlockIndexComparer{}, TensorBlockIndexComparer{}); reset_tags(t); diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp index 6015282a2..793928dd5 100644 --- a/SeQuant/core/tensor_canonicalizer.hpp +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -18,6 +18,10 @@ namespace sequant { /// of that class with TensorCanonicalizer::register_instance class TensorCanonicalizer { public: + using index_comparer_t = std::function; + using index_pair_t = std::pair; + using index_pair_comparer_t = std::function; + virtual ~TensorCanonicalizer(); /// @return ptr to the TensorCanonicalizer object, if any, that had been @@ -83,12 +87,16 @@ class TensorCanonicalizer { virtual ExprPtr apply(AbstractTensor&) const = 0; /// @return reference to the object used to compare Index objects - static const std::function& - index_comparer(); + static const index_comparer_t& index_comparer(); + + /// @param comparer the compare object to be used by this + static void index_comparer(index_comparer_t comparer); + + /// @return reference to the object used to compare Index objects + static const index_pair_comparer_t& index_pair_comparer(); /// @param comparer the compare object to be used by this - static void index_comparer( - std::function comparer); + static void index_pair_comparer( index_pair_comparer_t comparer); protected: inline auto bra_range(AbstractTensor& t) const { return t._bra_mutable(); } @@ -98,7 +106,9 @@ class TensorCanonicalizer { } /// the object used to compare indices - static std::function index_comparer_; + static index_comparer_t index_comparer_; + /// the object used to compare pairs of indices + static index_pair_comparer_t index_pair_comparer_; private: static std::pair< @@ -144,8 +154,8 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { /// Core of DefaultTensorCanonicalizer::apply, only does the canonicalization, /// i.e. no tagging/untagging - template - ExprPtr apply(AbstractTensor& t, const Compare& comp) const { + template + ExprPtr apply(AbstractTensor& t, const IndexComp &idxcmp, const IndexPairComp &paircmp) const { // std::wcout << "abstract tensor: " << to_latex(t) << "\n"; auto s = symmetry(t); auto is_antisymm = (s == Symmetry::antisymm); @@ -174,8 +184,8 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { // std::{stable_}sort does not necessarily use swap! so must implement // sort outselves .. thankfully ranks will be low so can stick with // bubble - bubble_sort(begin(_bra), end(_bra), comp); - bubble_sort(begin(_ket), end(_ket), comp); + bubble_sort(begin(_bra), end(_bra), idxcmp); + bubble_sort(begin(_ket), end(_ket), idxcmp); if (is_antisymm) even = IndexSwapper::thread_instance().even_num_of_swaps(); // std::wcout << " is " << (even ? "even" : "odd") << " and @@ -188,15 +198,15 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { auto _bra = bra_range(t); auto _ket = ket_range(t); auto _zip_braket = zip(take(_bra, _rank), take(_ket, _rank)); - bubble_sort(begin(_zip_braket), end(_zip_braket), comp); + bubble_sort(begin(_zip_braket), end(_zip_braket), paircmp); if (_bra_rank > _rank) { auto size_of_rest = _bra_rank - _rank; auto rest_of = counted(begin(_bra) + _rank, size_of_rest); - bubble_sort(begin(rest_of), end(rest_of), comp); + bubble_sort(begin(rest_of), end(rest_of), idxcmp); } else if (_ket_rank > _rank) { auto size_of_rest = _ket_rank - _rank; auto rest_of = counted(begin(_ket) + _rank, size_of_rest); - bubble_sort(begin(rest_of), end(rest_of), comp); + bubble_sort(begin(rest_of), end(rest_of), idxcmp); } } break; From 13421793059cba88223991664866c2ec62066b49 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 5 Feb 2024 16:21:47 +0100 Subject: [PATCH 21/85] Provide explicit to_wstring(Symmetry) function --- SeQuant/core/attr.hpp | 17 +++++++++++++++++ SeQuant/core/tensor_network.cpp | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/SeQuant/core/attr.hpp b/SeQuant/core/attr.hpp index f112c23a2..1362358ed 100644 --- a/SeQuant/core/attr.hpp +++ b/SeQuant/core/attr.hpp @@ -6,6 +6,7 @@ #define SEQUANT_ATTR_HPP #include +#include namespace sequant { @@ -49,6 +50,22 @@ inline std::wstring to_wolfram(const Symmetry& symmetry) { return result; } +inline std::wstring to_wstring(Symmetry sym) { + switch (sym) { + case Symmetry::symm: + return L"symmetric"; + case Symmetry::antisymm: + return L"antisymmetric"; + case Symmetry::nonsymm: + return L"nonsymmetric"; + case Symmetry::invalid: + return L"invalid"; + } + + assert(false); + std::abort(); +} + enum class BraKetPos { bra, ket, none }; inline std::wstring to_wolfram(BraKetPos a) { diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index a65e226ea..41de283cc 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -2,8 +2,6 @@ // Created by Eduard Valeyev on 2019-02-26. // -#include -#include #include #include #include @@ -16,6 +14,8 @@ #include #include #include +#include +#include #include #include From 45797273f0efd8d1524536dda5bf52c0027fbf69 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 6 Feb 2024 10:33:40 +0100 Subject: [PATCH 22/85] Get rid of duplication in testing of Sum equality Instead of hardcoding two expected LaTeX representations of a given expression, we now do an exhaustive search over all permutation of summands to see if one of them yields the desired permutation. This is acceptable as the order of summands has no practical relevance other than for matching a hard-coded order in a test case. The benefit of doing it this way is reduced maintenance cost in exchange for a runtime performance overhead when running the tests. However, the overhead is expected to be small and for test cases it shouldn't matter anyway. --- tests/unit/test_spin.cpp | 283 ++++++++++++++++++++++----------------- tests/unit/test_wick.cpp | 22 +-- tests/unit/utils.hpp | 27 ++++ 3 files changed, 198 insertions(+), 134 deletions(-) create mode 100644 tests/unit/utils.hpp diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index dfd8f5c50..d901ae414 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -18,6 +18,7 @@ #include "catch.hpp" #include "test_config.hpp" +#include "utils.hpp" #include #include @@ -278,9 +279,10 @@ TEST_CASE("Spin", "[spin]") { auto result = spintrace(expr); REQUIRE(result->is()); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - {{g^{{p_4}{p_3}}_{{p_1}{p_2}}}} + " - L"{{g^{{p_3}{p_4}}_{{p_1}{p_2}}}}\\bigr) }"); + REQUIRE(result->size() == 2); + REQUIRE_SUM_EQUAL(result, + L"{ \\bigl({{g^{{p_3}{p_4}}_{{p_1}{p_2}}}} - " + L"{{g^{{p_4}{p_3}}_{{p_1}{p_2}}}}\\bigr) }"); } SECTION("Product") { @@ -337,15 +339,19 @@ TEST_CASE("Spin", "[spin]") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl({{f^{{a_1}}_{{i_1}}}{t^{{i_1}}_{{a_1}}}} + " - L"{{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}}} - " - L"{{{\\frac{1}{2}}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}{i_1}}_{{" - L"a_1}{a_2}}}} - " - L"{{{\\frac{1}{2}}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}}_{{a_1}}}" - L"{t^{{i_1}}_{{a_2}}}} + " - L"{{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}}_{{a_1}}}{t^{{i_2}}_{{a_" - L"2}}}}\\bigr) }"); + REQUIRE(result->is()); + REQUIRE(result->size() == 5); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl( - " + L"{{{\\frac{1}{2}}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{" + L"a_2}}}} + " + L"{{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}}} " + L"+ {{f^{{a_1}}_{{i_1}}}{t^{{i_1}}_{{a_1}}}} - " + L"{{{\\frac{1}{2}}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}" + L"}_{{a_1}}}{t^{{i_1}}_{{a_2}}}} + " + L"{{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}" + L"}_{{a_1}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); } // Sum SECTION("Expand Antisymmetrizer"){// 0-body @@ -576,21 +582,21 @@ SECTION("Expand Symmetrizer") { REQUIRE(result->size() == 6); result->canonicalize(); rapid_simplify(result); - REQUIRE( - to_latex(result) == + REQUIRE_SUM_EQUAL( + result, L"{ " L"\\bigl({{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{" - L"i_3}}_{{a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_2}}_{{a_1}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_1}}}{t^{{i_2}}_{{" - L"a_4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_1}{i_5}}_{{a_2}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_1}}}{t^{{i_3}}_{{" - L"a_4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_5}{i_1}}_{{a_2}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_3}}}{t^{{i_2}}_{{" - L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_3}{i_4}}_{{a_1}{a_2}}}} + " + L"i_3}}_{{a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_2}{i_5}}_{{a_3}{a_1}}}} + " L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_2}}_{{" - L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_3}}_{{a_1}{a_2}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_2}}}{t^{{i_3}}_{{" - L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_2}{i_4}}_{{a_1}{a_3}}}}\\bigr) }"); + L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_3}{i_5}}_{{a_2}{a_1}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_1}}}{t^{{i_2}}_{{" + L"a_4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_1}{i_4}}_{{a_3}{a_2}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_1}}}{t^{{i_3}}_{{" + L"a_4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_1}{i_4}}_{{a_2}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{i_1}}_{{" + L"a_4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_2}{i_5}}_{{a_1}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_1}}_{{" + L"a_4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_3}{i_5}}_{{a_1}{a_2}}}}\\bigr) }"); } } @@ -627,7 +633,8 @@ SECTION("Symmetrize expression") { auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); REQUIRE(to_latex(result) == - L"{{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{i_1}{a_3}}_{{i_3}{i_4}}}{t^{{i_3}}_{{a_1}}}{t^{{i_4}}_{{a_2}}}{t^{{i_2}}_{{a_3}}}}"); + 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}}}}"); } { @@ -652,7 +659,8 @@ SECTION("Symmetrize expression") { REQUIRE(result->is() == false); REQUIRE( to_latex(result) == - L"{{{2}}{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{a_3}{a_4}}_{{i_3}{i_4}}}{t^{{i_4}}_{{a_2}}}{t^{{i_3}}_{{a_3}}}{t^{{i_1}{i_2}}_{{a_1}{a_4}}}}"); + L"{{{2}}{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{a_3}{a_4}}_{{i_3}{i_4}}}{t^{{" + L"i_4}}_{{a_2}}}{t^{{i_3}}_{{a_3}}}{t^{{i_1}{i_2}}_{{a_1}{a_4}}}}"); } } @@ -669,18 +677,20 @@ SECTION("Transform expression") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE( - to_latex(result) == - L"{ \\bigl( - {{g^{{a_2}{i_1}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}} + " - L"{{{2}}{g^{{i_1}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl({{{2}}{g^{{a_2}{i_1}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}} - " + L"{{g^{{i_1}{a_2}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); container::map idxmap = {{Index{L"i_1"}, Index{L"i_2"}}, {Index{L"i_2"}, Index{L"i_1"}}}; auto transformed_result = transform_expr(result, idxmap); - REQUIRE( - to_latex(transformed_result) == - L"{ \\bigl( - {{g^{{a_2}{i_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}} + " - L"{{{2}}{g^{{i_2}{a_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); + REQUIRE(transformed_result->is()); + REQUIRE(transformed_result->size() == 2); + REQUIRE_SUM_EQUAL( + transformed_result, + L"{ \\bigl({{{2}}{g^{{a_2}{i_2}}_{{i_1}{a_1}}}{t^{{i_1}}_{{a_2}}}} - " + L"{{g^{{i_2}{a_2}}_{{i_1}{a_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); } SECTION("Swap bra kets") { @@ -729,13 +739,14 @@ SECTION("Closed-shell spintrace CCD") { // Energy expression { { // standard - const auto input = ex(ExprPtrList{parse_expr( - L"1/4 g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_1,i_2}", Symmetry::antisymm)}); - auto result = closed_shell_CC_spintrace(input); - REQUIRE(result == parse_expr(L"2 g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_1,i_2} - " - L"g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_2,i_1}", - Symmetry::nonsymm)); - } + const auto input = ex(ExprPtrList{parse_expr( + L"1/4 g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_1,i_2}", Symmetry::antisymm)}); + auto result = closed_shell_CC_spintrace(input); + REQUIRE_SUM_EQUAL(result, to_latex(parse_expr( + L"- g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_2,i_1} + " + L"2 g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_1,i_2}", + 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)); @@ -794,20 +805,20 @@ SECTION("Closed-shell spintrace CCSD") { ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); simplify(result); - REQUIRE( - to_latex(result) == - L"{ \\bigl( - {{g^{{a_2}{i_1}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}} + " - L"{{{2}}{g^{{i_1}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl({{{2}}{g^{{a_2}{i_1}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}} - " + L"{{g^{{i_1}{a_2}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); container::map idxmap = {{Index{L"i_1"}, Index{L"i_2"}}, {Index{L"i_2"}, Index{L"i_1"}}}; auto transformed_result = transform_expr(result, idxmap); REQUIRE(transformed_result->is()); REQUIRE(transformed_result->size() == 2); - REQUIRE( - to_latex(transformed_result) == - L"{ \\bigl( - {{g^{{a_2}{i_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}} + " - L"{{{2}}{g^{{i_2}{a_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + transformed_result, + L"{ \\bigl({{{2}}{g^{{a_2}{i_2}}_{{i_1}{a_1}}}{t^{{i_1}}_{{a_2}}}} - " + L"{{g^{{i_2}{a_2}}_{{i_1}{a_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); } { @@ -855,12 +866,13 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}" - L"}} + " - L"{{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}}}" - L"\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl( - " + L"{{{2}}{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}" + L"}} + " + L"{{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}}}" + L"\\bigr) }"); } { @@ -878,11 +890,12 @@ SECTION("Closed-shell spintrace CCSD") { rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_1}{i_2}}_{{a_2}{a_3}}}} + " - L"{{{2}}{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}{i_1}}_{{a_2}{a_3}}" - L"}}\\bigr) }"); + REQUIRE_SUM_EQUAL(result, + L"{ " + L"\\bigl({{{2}}{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_1}{i_" + L"2}}_{{a_3}{a_2}}}} - " + L"{{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_1}{i_2}}_{{a_2}{a_" + L"3}}}}\\bigr) }"); } { @@ -898,10 +911,10 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE( - to_latex(result) == - L"{ \\bigl( - {{f^{{a_2}}_{{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{a_2}}}} + " - L"{{{2}}{f^{{a_2}}_{{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl({{{2}}{f^{{a_2}}_{{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}}} - " + L"{{f^{{a_2}}_{{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{a_2}}}}\\bigr) }"); } { @@ -917,10 +930,13 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ " - L"\\bigl({{{2}}{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}{t^{{i_1}}_{{a_3}}}} - " - L"{{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_1}}_{{a_2}}}{t^{{i_2}}_{{a_3}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ " + L"\\bigl({{{2}}{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}{t^{" + L"{i_1}}_{{a_3}}}} - " + L"{{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_1}}_{{a_2}}}{t^{{i_2}}_{{a_" + L"3}}}}\\bigr) }"); } { @@ -936,8 +952,13 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - {{{2}}{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}}_{{a_2}}}} + {{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl( - " + L"{{{2}}{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}}_" + L"{{a_2}}}} + " + L"{{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}}_{{a_" + L"2}}}}\\bigr) }"); } { @@ -953,10 +974,11 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{f^{{a_2}}_{{i_2}}}{t^{{i_2}}_{{a_1}}}{t^{{i_1}}_{{a_2}}}}" - L"\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl( - " + L"{{f^{{a_2}}_{{i_2}}}{t^{{i_2}}_{{a_1}}}{t^{{i_1}}_{{a_2}}}}" + L"\\bigr) }"); } { @@ -974,12 +996,12 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{" - L"i_2}{i_1}}_{{a_2}{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}{" - L"i_1}}_{{a_2}{a_3}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL(result, + L"{ " + L"\\bigl({{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}" + L"}{t^{{i_1}{i_3}}_{{a_3}{a_2}}}} - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{" + L"t^{{i_1}{i_2}}_{{a_3}{a_2}}}}\\bigr) }"); } { @@ -997,11 +1019,14 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl(" - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_2}}}{t^{{i_3}{i_2}}_{{a_1}{a_3}}}} - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}}}" - L"\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl(" + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_2}}}{t^{{i_3}{i_2}}_" + L"{{a_1}{a_3}}}} - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}{" + L"i_2}}_{{a_1}{a_2}}}}" + L"\\bigr) }"); } { @@ -1019,16 +1044,20 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{i_1}" - L"{i_3}}_{{a_1}{a_2}}}} + " - L"{{{4}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_1}" - L"{i_3}}_{{a_1}{a_3}}}} - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_3}" - L"{i_1}}_{{a_1}{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_2}}}{t^{{i_2}{i_1}" - L"}_{{a_1}{a_3}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL(result, + L"{ \\bigl(" + L"{{{4}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_3}}}{" + L"t^{{i_1}{i_2}}_{{a_1}{a_2}}}}" + L" + " + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{" + L"i_3}{i_1}}_{{a_1}{a_2}}}}" + L" - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{" + L"t^{{i_3}{i_1}}_{{a_1}{a_3}}}}" + L" - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_2}}}{" + L"t^{{i_1}{i_2}}_{{a_1}{a_3}}}}" + L"\\bigr) }"); } { @@ -1045,12 +1074,14 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}" - L"}_{{a_2}}}{t^{{i_1}}_{{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}}_{{" - L"a_2}}}{t^{{i_1}}_{{a_3}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL(result, + L"{ " + L"\\bigl( - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{" + L"t^{{i_2}}_{{a_2}}}{t^{{i_1}}_{{a_3}}}} + " + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{" + L"i_1}}_{{a_2}}}{t^{{i_2}}_{{a_3}}}}" + L"\\bigr) }"); } } // CCSD R1 @@ -1073,16 +1104,17 @@ SECTION("Closed-shell spintrace CCSDT terms") { input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}, {L"i_3", L"a_3"}}); simplify(result); REQUIRE(result->size() == 4); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" - L"}{t^{{i_2}{i_1}{i_4}}_{{a_1}{a_2}{a_3}}}} + " - L"{{{4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" - L"}{t^{{i_1}{i_2}{i_4}}_{{a_1}{a_2}{a_3}}}} + " - L"{{{2}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" - L"}{t^{{i_4}{i_1}{i_2}}_{{a_1}{a_2}{a_3}}}} - " - L"{{{4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" - L"}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ " + L"\\bigl({{{2}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{" + L"{i_3}}_{{i_4}}}{t^{{i_4}{i_1}{i_2}}_{{a_1}{a_2}{a_3}}}} - " + L"{{{4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_" + L"{{i_4}}}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}} + " + L"{{{4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" + L"}{t^{{i_1}{i_2}{i_4}}_{{a_1}{a_2}{a_3}}}} - " + L"{{{2}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" + L"}{t^{{i_2}{i_1}{i_4}}_{{a_1}{a_2}{a_3}}}}\\bigr) }"); } { // f * t3 @@ -1392,7 +1424,8 @@ SECTION("Open-shell spin-tracing") { auto ft3 = f * t3; ft3->canonicalize(); REQUIRE(to_latex(ft3) == - L"{{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↑_3}{i↓_2}}_{{a↑_3}{a↑_2}{a↓_2}}}}"); + L"{{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↑_3}{i↓_2}}_{{a↑_3}{a↑_2}{a↓_2}" + L"}}}"); } // g @@ -1470,18 +1503,20 @@ SECTION("Open-shell spin-tracing") { REQUIRE(to_latex(result[0]) == L"{{{\\frac{1}{12}}}{f^{{a↑_4}}_{{a↑_1}}}{\\bar{t}^{{i↑_1}{i↑_2}{" L"i↑_3}}_{{a↑_2}{a↑_3}{a↑_4}}}}"); - REQUIRE(to_latex(result[1]) == - L"{ \\bigl( - " - L"{{{\\frac{1}{12}}}{f^{{a↑_3}}_{{a↑_1}}}{t^{{i↑_1}{i↑_2}{i↓_3}}_" - L"{{a↑_2}{a↑_3}{a↓_3}}}} + " - L"{{{\\frac{1}{12}}}{f^{{a↑_3}}_{{a↑_1}}}{t^{{i↑_2}{i↑_1}{i↓_3}}_" - L"{{a↑_2}{a↑_3}{a↓_3}}}}\\bigr) }"); - REQUIRE(to_latex(result[2]) == - L"{ \\bigl( - " - L"{{{\\frac{1}{12}}}{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↓_3}{i↓_2}}_" - L"{{a↑_2}{a↓_2}{a↓_3}}}} + " - L"{{{\\frac{1}{12}}}{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↓_2}{i↓_3}}_{{" - L"a↑_2}{a↓_2}{a↓_3}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result[1], + L"{ \\bigl( - " + L"{{{\\frac{1}{12}}}{f^{{a↑_3}}_{{a↑_1}}}{t^{{i↑_1}{i↑_2}{i↓_3}}_" + L"{{a↑_2}{a↑_3}{a↓_3}}}} + " + L"{{{\\frac{1}{12}}}{f^{{a↑_3}}_{{a↑_1}}}{t^{{i↑_2}{i↑_1}{i↓_3}}_" + L"{{a↑_2}{a↑_3}{a↓_3}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result[2], + L"{ \\bigl( - " + L"{{{\\frac{1}{12}}}{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↓_3}{i↓_2}}_" + L"{{a↑_2}{a↓_2}{a↓_3}}}} + " + L"{{{\\frac{1}{12}}}{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↓_2}{i↓_3}}_{{" + L"a↑_2}{a↓_2}{a↓_3}}}}\\bigr) }"); REQUIRE(to_latex(result[3]) == L"{{{\\frac{1}{12}}}{f^{{a↓_4}}_{{a↓_1}}}{\\bar{t}^{{i↓_1}{i↓_2}{" L"i↓_3}}_{{a↓_2}{a↓_3}{a↓_4}}}}"); diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index 54b236376..7ba7ef52d 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -19,6 +19,7 @@ #include "catch.hpp" #include "test_config.hpp" +#include "utils.hpp" #include @@ -902,16 +903,17 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { rapid_simplify(wick_result_2); REQUIRE(wick_result_2->size() == 2); // now 2 terms - std::wcout << L"spinfree H2*T2 = " << to_latex(wick_result_2) - << std::endl; - REQUIRE(to_latex(wick_result_2) == - L"{ " - L"\\bigl({{{8}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}{i_2}}_{{" - L"a_1}{a_2}}}} - " - L"{{{4}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{a_" - L"2}}}}\\bigr) }"); - } - }); + std::wcout << L"spinfree H2*T2 = " << to_latex(wick_result_2) + << std::endl; + REQUIRE_SUM_EQUAL( + wick_result_2, + L"{ \\bigl( - " + L"{{{4}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{a_2}}" + L"}} + " + L"{{{8}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}" + L"}}\\bigr) }"); + } + }); // 2-body ^ 1-body ^ 1-body, with/without using topology SEQUANT_PROFILE_SINGLE("wick(H2*T1*T1)", { diff --git a/tests/unit/utils.hpp b/tests/unit/utils.hpp new file mode 100644 index 000000000..8b1aa4483 --- /dev/null +++ b/tests/unit/utils.hpp @@ -0,0 +1,27 @@ +#ifndef SEQUANT_TESTS_UTILS_HPP_ +#define SEQUANT_TESTS_UTILS_HPP_ + +#include + +#include +#include + +/// Checks all permutations of summands in order to match the LaTeX +/// representation to the given string. We do this as the order of summands is +/// dependent on the used hash function (which messes with tests) but ultimately +/// the order of summands does not matter whatsoever. Thus, it is sufficient to +/// test whether some permutation of summands yields the desired LaTeX +/// representation. +#define REQUIRE_SUM_EQUAL(sum, str) \ + REQUIRE(sum.is()); \ + if (to_latex(sum) != str) { \ + for (sequant::intmax_t i = 0; i < sequant::factorial(sum->size()); ++i) { \ + std::next_permutation(sum->begin(), sum->end()); \ + if (to_latex(sum) == str) { \ + break; \ + } \ + } \ + } \ + REQUIRE(to_latex(sum) == std::wstring(str)) + +#endif From c43964e1042ecb01ff770d66947a75d096c269a0 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 6 Feb 2024 12:26:12 +0100 Subject: [PATCH 23/85] Ensure failing eval node tests print something useful --- tests/unit/test_eval_node.cpp | 79 ++++++++++++++++------------------- tests/unit/utils.hpp | 5 +++ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/tests/unit/test_eval_node.cpp b/tests/unit/test_eval_node.cpp index e3a95145d..873b53de1 100644 --- a/tests/unit/test_eval_node.cpp +++ b/tests/unit/test_eval_node.cpp @@ -20,13 +20,9 @@ #include -namespace { +#include "utils.hpp" -// validates if x is constructible from tspec using parse_expr -auto validate_tensor = [](const auto& x, std::wstring_view tspec) -> bool { - return x.to_latex() == - sequant::parse_expr(tspec, sequant::Symmetry::antisymm)->to_latex(); -}; +namespace { auto eval_node(sequant::ExprPtr const& expr) { return sequant::eval_node(expr); @@ -77,24 +73,21 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { auto node1 = eval_node(p1); - REQUIRE(validate_tensor(node(node1, {}).as_tensor(), L"I_{a1,a2}^{i1,i2}")); + REQUIRE_TENSOR_EQUAL(node(node1, {}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); REQUIRE(node(node1, {R}).as_constant() == Constant{rational{1, 16}}); - REQUIRE( - validate_tensor(node(node1, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}")); + REQUIRE_TENSOR_EQUAL(node(node1, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); - REQUIRE( - validate_tensor(node(node1, {L, L}).as_tensor(), L"I_{a1,a2}^{a3,a4}")); + REQUIRE_TENSOR_EQUAL(node(node1, {L, L}).as_tensor(), L"I_{a1,a2}^{a3,a4}"); - REQUIRE( - validate_tensor(node(node1, {L, R}).as_tensor(), L"t_{a3,a4}^{i1,i2}")); + REQUIRE_TENSOR_EQUAL(node(node1, {L, R}).as_tensor(), L"t_{a3,a4}^{i1,i2}"); - REQUIRE(validate_tensor(node(node1, {L, L, L}).as_tensor(), - L"g_{i3,i4}^{a3,a4}")); + REQUIRE_TENSOR_EQUAL(node(node1, {L, L, L}).as_tensor(), + L"g_{i3,i4}^{a3,a4}"); - REQUIRE(validate_tensor(node(node1, {L, L, R}).as_tensor(), - L"t_{a1,a2}^{i3,i4}")); + REQUIRE_TENSOR_EQUAL(node(node1, {L, L, R}).as_tensor(), + L"t_{a1,a2}^{i3,i4}"); // 1/16 * A * (B * C) auto node2p = Product{p1->as().scalar(), {}}; @@ -104,24 +97,24 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { auto const node2 = eval_node(ex(node2p)); - REQUIRE(validate_tensor(node(node2, {}).as_tensor(), L"I_{a1,a2}^{i1,i2}")); + REQUIRE_TENSOR_EQUAL(node(node2, {}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); - REQUIRE( - validate_tensor(node(node2, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}")); + REQUIRE_TENSOR_EQUAL( + node(node2, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); REQUIRE(node(node2, {R}).as_constant() == Constant{rational{1, 16}}); - REQUIRE( - validate_tensor(node(node2, {L, L}).as_tensor(), L"g{i3,i4; a3,a4}")); + REQUIRE_TENSOR_EQUAL( + node(node2, {L, L}).as_tensor(), L"g{i3,i4; a3,a4}"); - REQUIRE(validate_tensor(node(node2, {L, R}).as_tensor(), - L"I{a1,a2,a3,a4;i3,i4,i1,i2}")); + REQUIRE_TENSOR_EQUAL(node(node2, {L, R}).as_tensor(), + L"I{a1,a2,a3,a4;i3,i4,i1,i2}"); - REQUIRE( - validate_tensor(node(node2, {L, R, L}).as_tensor(), L"t{a1,a2;i3,i4}")); + REQUIRE_TENSOR_EQUAL( + node(node2, {L, R, L}).as_tensor(), L"t{a1,a2;i3,i4}"); - REQUIRE( - validate_tensor(node(node2, {L, R, R}).as_tensor(), L"t{a3,a4;i1,i2}")); + REQUIRE_TENSOR_EQUAL( + node(node2, {L, R, R}).as_tensor(), L"t{a3,a4;i1,i2}"); } SECTION("sum") { @@ -133,20 +126,22 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { auto const node1 = eval_node(sum1); REQUIRE(node1->op_type() == EvalOp::Sum); REQUIRE(node1.left()->op_type() == EvalOp::Sum); - REQUIRE(validate_tensor(node1.left()->as_tensor(), L"I^{i1,i2}_{a1,a2}")); - REQUIRE(validate_tensor(node1.left().left()->as_tensor(), - L"X^{i1,i2}_{a1,a2}")); - REQUIRE(validate_tensor(node1.left().right()->as_tensor(), - L"Y^{i1,i2}_{a1,a2}")); + REQUIRE_TENSOR_EQUAL(node1.left()->as_tensor(), L"I^{i1,i2}_{a1,a2}"); + REQUIRE_TENSOR_EQUAL(node1.left().left()->as_tensor(), + L"X^{i1,i2}_{a1,a2}"); + REQUIRE_TENSOR_EQUAL(node1.left().right()->as_tensor(), + L"Y^{i1,i2}_{a1,a2}"); REQUIRE(node1.right()->op_type() == EvalOp::Prod); - REQUIRE( - (validate_tensor(node1.right()->as_tensor(), L"I_{a2,a1}^{i1,i2}") || - validate_tensor(node1.right()->as_tensor(), L"I_{a1,a2}^{i2,i1}"))); - REQUIRE(validate_tensor(node1.right().left()->as_tensor(), - L"g_{i3,a1}^{i1,i2}")); - REQUIRE( - validate_tensor(node1.right().right()->as_tensor(), L"t_{a2}^{i3}")); + 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().left()->as_tensor(), + L"g_{i3,a1}^{i1,i2}"); + REQUIRE_TENSOR_EQUAL( + node1.right().right()->as_tensor(), L"t_{a2}^{i3}"); } SECTION("variable") { @@ -178,8 +173,8 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { auto prod2 = parse_expr(L"a * t{i1;a1}"); auto node3 = eval_node(prod2); - REQUIRE(validate_tensor(node(node3, {}), L"I{i1;a1}")); - REQUIRE(validate_tensor(node(node3, {R}), L"t{i1;a1}")); + REQUIRE_TENSOR_EQUAL(node(node3, {}), L"I{i1;a1}"); + REQUIRE_TENSOR_EQUAL(node(node3, {R}), L"t{i1;a1}"); REQUIRE(node(node3, {L}).as_variable() == Variable{L"a"}); } diff --git a/tests/unit/utils.hpp b/tests/unit/utils.hpp index 8b1aa4483..27c2e4f38 100644 --- a/tests/unit/utils.hpp +++ b/tests/unit/utils.hpp @@ -24,4 +24,9 @@ } \ REQUIRE(to_latex(sum) == std::wstring(str)) +#define REQUIRE_TENSOR_EQUAL(tensor, spec) \ + REQUIRE(sequant::to_latex(tensor) == \ + sequant::to_latex( \ + sequant::parse_expr(spec, sequant::Symmetry::antisymm))); + #endif From 7c496489673efc7e5f8e4eefac52ab08905f3ca3 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 6 Feb 2024 14:50:40 +0100 Subject: [PATCH 24/85] Introduce new logging var --- SeQuant/core/logger.hpp | 1 + SeQuant/core/tensor_network.cpp | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SeQuant/core/logger.hpp b/SeQuant/core/logger.hpp index 3c68abf90..c2ba917ce 100644 --- a/SeQuant/core/logger.hpp +++ b/SeQuant/core/logger.hpp @@ -21,6 +21,7 @@ struct Logger : public Singleton { bool wick_stats = false; bool expand = false; bool canonicalize = false; + bool canonicalize_input_graph = false; bool canonicalize_dot = false; bool simplify = false; bool tensor_network = false; diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 41de283cc..be0e9ff7a 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -337,8 +337,7 @@ void TensorNetwork::canonicalize_graph(const named_indices_t &named_indices) { Graph graph = create_graph(&named_indices); // graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); - // TODO: Add logging var for this - if (false) { + if (Logger::get_instance().canonicalize_input_graph) { std::wcout << "Input graph for canonicalization:\n"; graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); } From 8181b2b3201d1419105861d370c5b8f7b3d2486a Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 6 Feb 2024 14:50:53 +0100 Subject: [PATCH 25/85] Make aux index symmetry treatment consistent --- SeQuant/core/tensor_canonicalizer.hpp | 5 ++--- SeQuant/core/tensor_network.cpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp index 793928dd5..f30c73774 100644 --- a/SeQuant/core/tensor_canonicalizer.hpp +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -214,9 +214,8 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { abort(); } - // For now we treat auxiliary indices as being unrelated to one another. - // This makes their order insignificant, allowing us to simply sort them. - auto _aux = auxiliary_range(t); + // TODO: Handle auxiliary index symmetries once they are introduced + // auto _aux = auxiliary_range(t); // ranges::sort(_aux, comp); ExprPtr result = diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index be0e9ff7a..340a3bdb3 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -757,7 +757,7 @@ TensorNetwork::Graph TensorNetwork::create_graph( std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); } - // TODO: handle aux indices permutation symmetries + // TODO: handle aux indices permutation symmetries once they are supported // for now, auxiliary indices are considered to always be asymmetric for (std::size_t i = 0; i < auxiliary_rank(tensor); ++i) { graph.vertex_labels.emplace_back(L"aux_" + std::to_wstring(i + 1)); From 2642ade08fd3a21b5e61de110910f75fdc3f9bf3 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 8 Feb 2024 14:23:22 +0100 Subject: [PATCH 26/85] Adapt formatting & minor tweaks --- CMakeLists.txt | 2 +- SeQuant/core/algorithm.hpp | 4 +- SeQuant/core/attr.hpp | 28 +++++++------- SeQuant/core/tensor.hpp | 18 ++++----- SeQuant/core/tensor_canonicalizer.cpp | 33 +++++++++------- SeQuant/core/tensor_canonicalizer.hpp | 14 ++++--- SeQuant/core/tensor_network.hpp | 17 +++++---- tests/unit/test_eval_node.cpp | 43 +++++++++------------ tests/unit/test_tensor_network.cpp | 55 +++++++++------------------ 9 files changed, 100 insertions(+), 114 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34fd661d7..acfedcc12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -419,7 +419,7 @@ if (BUILD_TESTING) tests/unit/test_math.cpp tests/unit/test_string.cpp tests/unit/test_latex.cpp - tests/unit/test_utilities.cpp + tests/unit/test_utilities.cpp ) if (TARGET tiledarray) diff --git a/SeQuant/core/algorithm.hpp b/SeQuant/core/algorithm.hpp index 58c89dac2..009ddd2c5 100644 --- a/SeQuant/core/algorithm.hpp +++ b/SeQuant/core/algorithm.hpp @@ -49,8 +49,8 @@ void bubble_sort(ForwardIter begin, Sentinel end, Compare comp) { swapped = true; } } else { - const auto &val0 = *inext; - const auto &val1 = *i; + const auto& val0 = *inext; + const auto& val1 = *i; static_assert(std::tuple_size_v> == 2, "need to generalize comparer to handle tuples"); diff --git a/SeQuant/core/attr.hpp b/SeQuant/core/attr.hpp index 1362358ed..409141a2b 100644 --- a/SeQuant/core/attr.hpp +++ b/SeQuant/core/attr.hpp @@ -5,8 +5,8 @@ #ifndef SEQUANT_ATTR_HPP #define SEQUANT_ATTR_HPP -#include #include +#include namespace sequant { @@ -51,19 +51,19 @@ inline std::wstring to_wolfram(const Symmetry& symmetry) { } inline std::wstring to_wstring(Symmetry sym) { - switch (sym) { - case Symmetry::symm: - return L"symmetric"; - case Symmetry::antisymm: - return L"antisymmetric"; - case Symmetry::nonsymm: - return L"nonsymmetric"; - case Symmetry::invalid: - return L"invalid"; - } - - assert(false); - std::abort(); + switch (sym) { + case Symmetry::symm: + return L"symmetric"; + case Symmetry::antisymm: + return L"antisymmetric"; + case Symmetry::nonsymm: + return L"nonsymmetric"; + case Symmetry::invalid: + return L"invalid"; + } + + assert(false); + std::abort(); } enum class BraKetPos { bra, ket, none }; diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index be20c45fd..cad634f79 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -229,17 +229,17 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { result += L"}"; if (!this->auxiliary_.empty()) { result += L"("; - const index_container_type &aux = auxiliary(); - for (std::size_t i = 0; i < auxiliary_rank(); ++i) { - result += sequant::to_latex(aux[i]); - - if (i + 1 < auxiliary_rank()) { - result += L","; - } - } + const index_container_type &aux = auxiliary(); + for (std::size_t i = 0; i < auxiliary_rank(); ++i) { + result += sequant::to_latex(aux[i]); + + if (i + 1 < auxiliary_rank()) { + result += L","; + } + } result += L")"; } - result += L"}"; + result += L"}"; return result; } diff --git a/SeQuant/core/tensor_canonicalizer.cpp b/SeQuant/core/tensor_canonicalizer.cpp index 85da1652f..4fb3d495c 100644 --- a/SeQuant/core/tensor_canonicalizer.cpp +++ b/SeQuant/core/tensor_canonicalizer.cpp @@ -115,14 +115,14 @@ struct TensorBlockIndexComparer { return 0; } - const int lhs_tag = lhs.tag().value(); - const int rhs_tag = rhs.tag().value(); + const int lhs_tag = lhs.tag().value(); + const int rhs_tag = rhs.tag().value(); - if (lhs_tag != rhs_tag) { - return lhs_tag < rhs_tag ? -1 : 1; - } + if (lhs_tag != rhs_tag) { + return lhs_tag < rhs_tag ? -1 : 1; + } - return 0; + return 0; } }; @@ -137,7 +137,7 @@ struct TensorIndexComparer { return res < 0; } - // Fall back to regular index compare to break the tie + // Fall back to regular index compare to break the tie if constexpr (is_tuple_like_v) { static_assert(std::tuple_size_v == 2, "TensorIndexComparer can only deal with tuple-like objects " @@ -229,11 +229,14 @@ void TensorCanonicalizer::deregister_instance(std::wstring_view label) { } } -TensorCanonicalizer::index_comparer_t TensorCanonicalizer::index_comparer_ = TensorIndexComparer{}; +TensorCanonicalizer::index_comparer_t TensorCanonicalizer::index_comparer_ = + TensorIndexComparer{}; -TensorCanonicalizer::index_pair_comparer_t TensorCanonicalizer::index_pair_comparer_ = TensorIndexComparer{}; +TensorCanonicalizer::index_pair_comparer_t + TensorCanonicalizer::index_pair_comparer_ = TensorIndexComparer{}; -const TensorCanonicalizer::index_comparer_t& TensorCanonicalizer::index_comparer() { +const TensorCanonicalizer::index_comparer_t& +TensorCanonicalizer::index_comparer() { return index_comparer_; } @@ -241,7 +244,8 @@ void TensorCanonicalizer::index_comparer(index_comparer_t comparer) { index_comparer_ = std::move(comparer); } -const TensorCanonicalizer::index_pair_comparer_t& TensorCanonicalizer::index_pair_comparer() { +const TensorCanonicalizer::index_pair_comparer_t& +TensorCanonicalizer::index_pair_comparer() { return index_pair_comparer_; } @@ -264,7 +268,8 @@ void DefaultTensorCanonicalizer::tag_indices(AbstractTensor& t) const { ExprPtr DefaultTensorCanonicalizer::apply(AbstractTensor& t) const { tag_indices(t); - auto result = this->apply(t, this->index_comparer_, this->index_pair_comparer_); + auto result = + this->apply(t, this->index_comparer_, this->index_pair_comparer_); reset_tags(t); @@ -278,8 +283,8 @@ using suitable_call_operator = ExprPtr TensorBlockCanonicalizer::apply(AbstractTensor& t) const { tag_indices(t); - auto result = - DefaultTensorCanonicalizer::apply(t, TensorBlockIndexComparer{}, TensorBlockIndexComparer{}); + auto result = DefaultTensorCanonicalizer::apply(t, TensorBlockIndexComparer{}, + TensorBlockIndexComparer{}); reset_tags(t); diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp index f30c73774..beddfe6e7 100644 --- a/SeQuant/core/tensor_canonicalizer.hpp +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -18,9 +18,10 @@ namespace sequant { /// of that class with TensorCanonicalizer::register_instance class TensorCanonicalizer { public: - using index_comparer_t = std::function; - using index_pair_t = std::pair; - using index_pair_comparer_t = std::function; + using index_comparer_t = std::function; + using index_pair_t = std::pair; + using index_pair_comparer_t = + std::function; virtual ~TensorCanonicalizer(); @@ -96,7 +97,7 @@ class TensorCanonicalizer { static const index_pair_comparer_t& index_pair_comparer(); /// @param comparer the compare object to be used by this - static void index_pair_comparer( index_pair_comparer_t comparer); + static void index_pair_comparer(index_pair_comparer_t comparer); protected: inline auto bra_range(AbstractTensor& t) const { return t._bra_mutable(); } @@ -155,7 +156,8 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { /// Core of DefaultTensorCanonicalizer::apply, only does the canonicalization, /// i.e. no tagging/untagging template - ExprPtr apply(AbstractTensor& t, const IndexComp &idxcmp, const IndexPairComp &paircmp) const { + ExprPtr apply(AbstractTensor& t, const IndexComp& idxcmp, + const IndexPairComp& paircmp) const { // std::wcout << "abstract tensor: " << to_latex(t) << "\n"; auto s = symmetry(t); auto is_antisymm = (s == Symmetry::antisymm); @@ -214,7 +216,7 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { abort(); } - // TODO: Handle auxiliary index symmetries once they are introduced + // TODO: Handle auxiliary index symmetries once they are introduced // auto _aux = auxiliary_range(t); // ranges::sort(_aux, comp); diff --git a/SeQuant/core/tensor_network.hpp b/SeQuant/core/tensor_network.hpp index 12f72f464..8c38b93f1 100644 --- a/SeQuant/core/tensor_network.hpp +++ b/SeQuant/core/tensor_network.hpp @@ -114,11 +114,11 @@ class TensorNetwork { return first < other.first; } - if(second < other.second) { - return second < other.second; - } + if (second < other.second) { + return second < other.second; + } - return index.space() < other.index.space(); + return index.space() < other.index.space(); } bool operator==(const Edge &other) const { @@ -264,15 +264,18 @@ class TensorNetwork { /// remains undefined. void canonicalize_graph(const named_indices_t &named_indices); - /// Canonicalizes every individual tensor for itself, taking into account only tensor blocks + /// Canonicalizes every individual tensor for itself, taking into account only + /// tensor blocks /// @returns The byproduct of the canonicalizations - ExprPtr canonicalize_individual_tensor_blocks(const named_indices_t &named_indices); + ExprPtr canonicalize_individual_tensor_blocks( + const named_indices_t &named_indices); /// Canonicalizes every individual tensor for itself /// @returns The byproduct of the canonicalizations ExprPtr canonicalize_individual_tensors(const named_indices_t &named_indices); - ExprPtr do_individual_canonicalization(const TensorCanonicalizer &canonicalizer); + ExprPtr do_individual_canonicalization( + const TensorCanonicalizer &canonicalizer); }; template diff --git a/tests/unit/test_eval_node.cpp b/tests/unit/test_eval_node.cpp index 873b53de1..79086222a 100644 --- a/tests/unit/test_eval_node.cpp +++ b/tests/unit/test_eval_node.cpp @@ -77,17 +77,17 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { REQUIRE(node(node1, {R}).as_constant() == Constant{rational{1, 16}}); - REQUIRE_TENSOR_EQUAL(node(node1, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); + REQUIRE_TENSOR_EQUAL(node(node1, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); - REQUIRE_TENSOR_EQUAL(node(node1, {L, L}).as_tensor(), L"I_{a1,a2}^{a3,a4}"); + REQUIRE_TENSOR_EQUAL(node(node1, {L, L}).as_tensor(), L"I_{a1,a2}^{a3,a4}"); - REQUIRE_TENSOR_EQUAL(node(node1, {L, R}).as_tensor(), L"t_{a3,a4}^{i1,i2}"); + REQUIRE_TENSOR_EQUAL(node(node1, {L, R}).as_tensor(), L"t_{a3,a4}^{i1,i2}"); REQUIRE_TENSOR_EQUAL(node(node1, {L, L, L}).as_tensor(), - L"g_{i3,i4}^{a3,a4}"); + L"g_{i3,i4}^{a3,a4}"); REQUIRE_TENSOR_EQUAL(node(node1, {L, L, R}).as_tensor(), - L"t_{a1,a2}^{i3,i4}"); + L"t_{a1,a2}^{i3,i4}"); // 1/16 * A * (B * C) auto node2p = Product{p1->as().scalar(), {}}; @@ -99,22 +99,18 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { REQUIRE_TENSOR_EQUAL(node(node2, {}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); - REQUIRE_TENSOR_EQUAL( - node(node2, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); + REQUIRE_TENSOR_EQUAL(node(node2, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); REQUIRE(node(node2, {R}).as_constant() == Constant{rational{1, 16}}); - REQUIRE_TENSOR_EQUAL( - node(node2, {L, L}).as_tensor(), L"g{i3,i4; a3,a4}"); + REQUIRE_TENSOR_EQUAL(node(node2, {L, L}).as_tensor(), L"g{i3,i4; a3,a4}"); REQUIRE_TENSOR_EQUAL(node(node2, {L, R}).as_tensor(), - L"I{a1,a2,a3,a4;i3,i4,i1,i2}"); + L"I{a1,a2,a3,a4;i3,i4,i1,i2}"); - REQUIRE_TENSOR_EQUAL( - node(node2, {L, R, L}).as_tensor(), L"t{a1,a2;i3,i4}"); + REQUIRE_TENSOR_EQUAL(node(node2, {L, R, L}).as_tensor(), L"t{a1,a2;i3,i4}"); - REQUIRE_TENSOR_EQUAL( - node(node2, {L, R, R}).as_tensor(), L"t{a3,a4;i1,i2}"); + REQUIRE_TENSOR_EQUAL(node(node2, {L, R, R}).as_tensor(), L"t{a3,a4;i1,i2}"); } SECTION("sum") { @@ -128,20 +124,19 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { REQUIRE(node1.left()->op_type() == EvalOp::Sum); REQUIRE_TENSOR_EQUAL(node1.left()->as_tensor(), L"I^{i1,i2}_{a1,a2}"); REQUIRE_TENSOR_EQUAL(node1.left().left()->as_tensor(), - L"X^{i1,i2}_{a1,a2}"); + L"X^{i1,i2}_{a1,a2}"); REQUIRE_TENSOR_EQUAL(node1.left().right()->as_tensor(), - L"Y^{i1,i2}_{a1,a2}"); + 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}"); - } + 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().left()->as_tensor(), - L"g_{i3,a1}^{i1,i2}"); - REQUIRE_TENSOR_EQUAL( - node1.right().right()->as_tensor(), L"t_{a2}^{i3}"); + L"g_{i3,a1}^{i1,i2}"); + REQUIRE_TENSOR_EQUAL(node1.right().right()->as_tensor(), L"t_{a2}^{i3}"); } SECTION("variable") { diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index c5c329901..58367de77 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -40,8 +40,6 @@ #include -// TODO: Add test cases with auxiliary indices - std::string to_utf8(const std::wstring& wstr) { using convert_type = std::codecvt_utf8; std::wstring_convert converter; @@ -86,28 +84,6 @@ TEST_CASE("TensorNetwork", "[elements]") { using namespace sequant::mbpt::sr; - /* - SECTION("dummy") { - using Edge = TensorNetwork::Edge; - using Vertex = TensorNetwork::Vertex; - using Origin = TensorNetwork::Origin; - - Vertex v1(Origin::Bra, 0, 0, Symmetry::antisymm); - Vertex v2(Origin::Ket, 0, 0, Symmetry::antisymm); - Vertex v3(Origin::Ket, 2, 0, Symmetry::antisymm); - Vertex v4(Origin::Ket, 1, 0, Symmetry::antisymm); - - Edge e1(v1, {}); - e1.connect_to(v3); - - Edge e2(v2, {}); - e2.connect_to(v3); - - std::wcout << std::boolalpha << (e1 < e2) << " reverse " << (e2 < e1) - << " with self " << (e1 < e1) << std::endl; std::abort(); - } - */ - SECTION("Edges") { using Vertex = TensorNetwork::Vertex; using Edge = TensorNetwork::Edge; @@ -384,26 +360,31 @@ TEST_CASE("TensorNetwork", "[elements]") { SECTION("miscellaneous") { const std::vector> inputs = { - {L"g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A", L"g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A"}, - {L"g{a_1,i_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A", L"-1 g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A"}, - - {L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N"}, - {L"g{a_1,i_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", L"g{i_1,a_1;i_2,i_3}:N * I{i_3,i_2;i_1,a_1}:N"}, - }; - + {L"g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A", + L"g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A"}, + {L"g{a_1,i_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A", + L"-1 g{i_1,a_1;i_2,i_3}:A * I{i_2,i_3;i_1,a_1}:A"}, + + {L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", + L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N"}, + {L"g{a_1,i_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", + L"g{i_1,a_1;i_2,i_3}:N * I{i_3,i_2;i_1,a_1}:N"}, + }; for (const auto& [input, expected] : inputs) { const auto input_tensors = parse_expr(input).as().factors(); TensorNetwork tn(input_tensors); - ExprPtr factor = tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), true); + ExprPtr factor = tn.canonicalize( + TensorCanonicalizer::cardinal_tensor_labels(), true); - ExprPtr prod = to_product(tn.tensors()); - if (factor) { - prod = ex(prod.as().scale(factor.as().value())); - } + ExprPtr prod = to_product(tn.tensors()); + if (factor) { + prod = ex( + prod.as().scale(factor.as().value())); + } - REQUIRE(deparse_expr(prod) == expected); + REQUIRE(deparse_expr(prod) == expected); } } From 897eeaa13b2cf339a533dff2ce601c9c46825802 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 8 Feb 2024 14:30:41 +0100 Subject: [PATCH 27/85] Improve benchmark integration in build setup --- .github/workflows/cmake.yml | 1 + CMakeLists.txt | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 2a1ee816f..de5bc4196 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -36,6 +36,7 @@ jobs: -DSEQUANT_USE_SYSTEM_BOOST_HASH=OFF -DCMAKE_CXX_STANDARD=20 -DCMAKE_CXX_EXTENSIONS=OFF + -DSEQUANT_BUILD_BENCHMARKS=ON steps: - uses: actions/checkout@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index acfedcc12..160d964b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,8 @@ include(AddCustomTargetSubproject) include(FeatureSummary) include(CMakePackageConfigHelpers) +option(SEQUANT_BUILD_BENCHMARKS "Enable building of benchmarks" OFF) + ########################## # Standard build variables ########################## @@ -571,7 +573,9 @@ else (BUILD_TESTING) add_custom_target_subproject(sequant check USES_TERMINAL COMMAND echo "WARNING: SeQuant testing disabled. To enable, give -DBUILD_TESTING=ON to cmake") endif (BUILD_TESTING) -add_subdirectory(benchmarks) +if (SEQUANT_BUILD_BENCHMARKS) + add_subdirectory(benchmarks) +endif() ####### Python ######## From 45eeeb9e7bdaa00f22b842814d0bb3f3f6974f8a Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 9 Feb 2024 10:25:13 +0100 Subject: [PATCH 28/85] Make TensorNetworks constructible from single tensors --- SeQuant/core/tensor_network.hpp | 48 ++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/SeQuant/core/tensor_network.hpp b/SeQuant/core/tensor_network.hpp index 8c38b93f1..1031d96c0 100644 --- a/SeQuant/core/tensor_network.hpp +++ b/SeQuant/core/tensor_network.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -153,20 +154,29 @@ class TensorNetwork { std::size_t vertex_to_tensor_idx(std::size_t vertex) const; }; - /// @throw std::logic_error if exprptr_range contains a non-tensor - /// @note uses RTTI - template - TensorNetwork(const ExprPtrRange &exprptr_range) { - for (const auto &ex : exprptr_range) { - ExprPtr clone = ex.clone(); - auto t = std::dynamic_pointer_cast(clone); - if (t) { - tensors_.emplace_back(std::move(t)); - } else { - throw std::logic_error( - "TensorNetwork::TensorNetwork: non-tensors in the given expression " - "range"); + TensorNetwork(const Expr &expr) { + if (expr.size() > 0) { + for (const ExprPtr &subexpr : expr) { + add_expr(*subexpr); } + } else { + add_expr(expr); + } + + init_edges(); + } + + TensorNetwork(const ExprPtr &expr) : TensorNetwork(*expr) {} + + template < + typename ExprPtrRange, + typename = std::enable_if_t && + !std::is_base_of_v>> + TensorNetwork(const ExprPtrRange &exprptr_range) { + static_assert( + std::is_base_of_v); + for (const ExprPtr ¤t : exprptr_range) { + add_expr(*current); } init_edges(); @@ -276,6 +286,18 @@ class TensorNetwork { ExprPtr do_individual_canonicalization( const TensorCanonicalizer &canonicalizer); + + void add_expr(const Expr &expr) { + ExprPtr clone = expr.clone(); + + auto tensor_ptr = std::dynamic_pointer_cast(clone); + if (!tensor_ptr) { + throw std::invalid_argument( + "TensorNetwork::TensorNetwork: tried to add non-tensor to network"); + } + + tensors_.push_back(std::move(tensor_ptr)); + } }; template From c81071ce1aa1945aef9116b7e1700c595cd35988 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 9 Feb 2024 10:57:28 +0100 Subject: [PATCH 29/85] Fix thousands seps in dot graph colors --- SeQuant/external/bliss/graph.hh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SeQuant/external/bliss/graph.hh b/SeQuant/external/bliss/graph.hh index c0db33dae..094b11c3b 100644 --- a/SeQuant/external/bliss/graph.hh +++ b/SeQuant/external/bliss/graph.hh @@ -35,6 +35,7 @@ class AbstractGraph; #include #include #include +#include #include "bignum.hh" #include "heap.hh" #include "kqueue.hh" @@ -693,6 +694,8 @@ class Graph : public AbstractGraph { auto int_to_rgb = [](unsigned int i) { std::basic_stringstream stream; + // Set locale of this stream to C to avoid any kind of thousands separator + stream.imbue(std::locale::classic()); stream << std::setfill(Char('0')) << std::setw(6) << std::hex << ((i << 8) >> 8); return stream.str(); From a316a41946a7b1234373a39df8262d3e6efecec6 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 9 Feb 2024 16:15:13 +0100 Subject: [PATCH 30/85] Use index label as vertex label (instead TeX) --- SeQuant/core/tensor_network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 340a3bdb3..8516e0ed3 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -771,7 +771,7 @@ TensorNetwork::Graph TensorNetwork::create_graph( // Now add all indices (edges) to the graph for (const Edge ¤t_edge : edges_) { const Index &index = current_edge.idx(); - graph.vertex_labels.push_back(index.to_latex()); + graph.vertex_labels.push_back(std::wstring(index.label())); graph.vertex_types.push_back(VertexType::Index); // Assign index color @@ -803,7 +803,7 @@ TensorNetwork::Graph TensorNetwork::create_graph( // Create a new vertex for this bundle of proto indices std::wstring spbundle_label = L"{"; for (const Index &proto : index.proto_indices()) { - spbundle_label += proto.to_latex(); + spbundle_label += proto.label(); } spbundle_label += L"}"; graph.vertex_labels.push_back(std::move(spbundle_label)); From 2cfba37caf45196201b85d5406e420e5cd5a4ad3 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 9 Feb 2024 16:15:53 +0100 Subject: [PATCH 31/85] Add executable to generate tensor network graphs --- CMakeLists.txt | 7 +- .../tensor_network_graphs.cpp | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 examples/tensor_network_graphs/tensor_network_graphs.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 160d964b9..4acea9a18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -543,8 +543,13 @@ if (BUILD_TESTING) target_link_libraries(${example${i}} SeQuant) endforeach () + set(example12 "tensor_network_graphs") + add_executable(${example12} EXCLUDE_FROM_ALL + examples/${example12}/${example12}.cpp) + target_link_libraries(${example12} SeQuant) + # add tests for running examples - set(lastexample 11) + set(lastexample 12) foreach (i RANGE ${lastexample}) if (TARGET ${example${i}}) add_dependencies(examples-sequant ${example${i}}) diff --git a/examples/tensor_network_graphs/tensor_network_graphs.cpp b/examples/tensor_network_graphs/tensor_network_graphs.cpp new file mode 100644 index 000000000..8da1d1ee3 --- /dev/null +++ b/examples/tensor_network_graphs/tensor_network_graphs.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + using namespace sequant; + +std::wstring from_utf8(std::string_view str) { + std::wstring_convert> converter; + return converter.from_bytes(std::string(str)); +} + +std::optional to_network(const ExprPtr &expr) { + if (expr.is()) { + return TensorNetwork({expr}); + } else if (expr.is()) { + for (const ExprPtr &factor : expr.as().factors()) { + if (!factor.is()) { + return {}; + } + } + + return TensorNetwork(expr.as().factors()); + } else { + return {}; + } +} + +int main(int argc, char **argv) { + + set_locale(); + mbpt::set_default_convention(); + + for (std::size_t i = 1; i < static_cast(argc); ++i) { + std::wstring current = from_utf8(argv[i]); + ExprPtr expr; + try { + expr = parse_expr(current); + } catch (const ParseError &e) { + std::wcout << "Failed to parse expression '" << current << "': " << e.what() << std::endl; + return 1; + } + assert(expr); + + std::optional network = to_network(expr); + if (!network.has_value()) { + std::wcout << "Failed to construct tensor network for input '" << current << "'" << std::endl; + return 2; + } + + TensorNetwork::Graph graph = network->create_graph(); + std::wcout << "Graph for '" << current << "'\n"; + graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); + } +} From 974f3bc299cd617a2a8a9838ed162fd9cf930221 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 12 Feb 2024 09:25:36 +0100 Subject: [PATCH 32/85] Add options to TN graph creator --- .../tensor_network_graphs.cpp | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/examples/tensor_network_graphs/tensor_network_graphs.cpp b/examples/tensor_network_graphs/tensor_network_graphs.cpp index 8da1d1ee3..38a480f62 100644 --- a/examples/tensor_network_graphs/tensor_network_graphs.cpp +++ b/examples/tensor_network_graphs/tensor_network_graphs.cpp @@ -35,13 +35,38 @@ std::optional to_network(const ExprPtr &expr) { } } +void print_help() { + std::wcout << "Helper to generate dot (GraphViz) representations of tensor network graphs.\n"; + std::wcout << "Usage:\n"; + std::wcout << " [options] [ [... [] ] ]\n"; + std::wcout << "Options:\n"; + std::wcout << " --help Shows this help message\n"; + std::wcout << " --no-named Treat all indices as unnamed (even if they are external)\n"; +} + int main(int argc, char **argv) { set_locale(); mbpt::set_default_convention(); + bool use_named_indices = true; + const TensorNetwork::named_indices_t empty_named_indices; + + if (argc <= 1) { + print_help(); + return 1; + } + for (std::size_t i = 1; i < static_cast(argc); ++i) { std::wstring current = from_utf8(argv[i]); + if (current == L"--help") { + print_help(); + return 0; + } else if (current == L"--no-named") { + use_named_indices = false; + continue; + } + ExprPtr expr; try { expr = parse_expr(current); @@ -57,7 +82,7 @@ int main(int argc, char **argv) { return 2; } - TensorNetwork::Graph graph = network->create_graph(); + TensorNetwork::Graph graph = network->create_graph(use_named_indices ? nullptr : &empty_named_indices); std::wcout << "Graph for '" << current << "'\n"; graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); } From 5403c14c8ec523994c5cc5f82db45139d597ea9d Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 12 Feb 2024 09:25:55 +0100 Subject: [PATCH 33/85] Format TN creator source code --- .../tensor_network_graphs.cpp | 123 +++++++++--------- 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/examples/tensor_network_graphs/tensor_network_graphs.cpp b/examples/tensor_network_graphs/tensor_network_graphs.cpp index 38a480f62..47b9a34df 100644 --- a/examples/tensor_network_graphs/tensor_network_graphs.cpp +++ b/examples/tensor_network_graphs/tensor_network_graphs.cpp @@ -1,89 +1,94 @@ -#include #include #include +#include #include #include #include -#include #include #include -#include #include +#include #include +#include - using namespace sequant; +using namespace sequant; std::wstring from_utf8(std::string_view str) { - std::wstring_convert> converter; - return converter.from_bytes(std::string(str)); + std::wstring_convert> converter; + return converter.from_bytes(std::string(str)); } std::optional to_network(const ExprPtr &expr) { - if (expr.is()) { - return TensorNetwork({expr}); - } else if (expr.is()) { - for (const ExprPtr &factor : expr.as().factors()) { - if (!factor.is()) { - return {}; - } - } + if (expr.is()) { + return TensorNetwork({expr}); + } else if (expr.is()) { + for (const ExprPtr &factor : expr.as().factors()) { + if (!factor.is()) { + return {}; + } + } - return TensorNetwork(expr.as().factors()); - } else { - return {}; - } + return TensorNetwork(expr.as().factors()); + } else { + return {}; + } } void print_help() { - std::wcout << "Helper to generate dot (GraphViz) representations of tensor network graphs.\n"; - std::wcout << "Usage:\n"; - std::wcout << " [options] [ [... [] ] ]\n"; - std::wcout << "Options:\n"; - std::wcout << " --help Shows this help message\n"; - std::wcout << " --no-named Treat all indices as unnamed (even if they are external)\n"; + std::wcout << "Helper to generate dot (GraphViz) representations of tensor " + "network graphs.\n"; + std::wcout << "Usage:\n"; + std::wcout + << " [options] [ [... [] ] ]\n"; + std::wcout << "Options:\n"; + std::wcout << " --help Shows this help message\n"; + std::wcout << " --no-named Treat all indices as unnamed (even if they are " + "external)\n"; } int main(int argc, char **argv) { + set_locale(); + mbpt::set_default_convention(); - set_locale(); - mbpt::set_default_convention(); - - bool use_named_indices = true; - const TensorNetwork::named_indices_t empty_named_indices; + bool use_named_indices = true; + const TensorNetwork::named_indices_t empty_named_indices; - if (argc <= 1) { - print_help(); - return 1; - } + if (argc <= 1) { + print_help(); + return 1; + } - for (std::size_t i = 1; i < static_cast(argc); ++i) { - std::wstring current = from_utf8(argv[i]); - if (current == L"--help") { - print_help(); - return 0; - } else if (current == L"--no-named") { - use_named_indices = false; - continue; - } + for (std::size_t i = 1; i < static_cast(argc); ++i) { + std::wstring current = from_utf8(argv[i]); + if (current == L"--help") { + print_help(); + return 0; + } else if (current == L"--no-named") { + use_named_indices = false; + continue; + } - ExprPtr expr; - try { - expr = parse_expr(current); - } catch (const ParseError &e) { - std::wcout << "Failed to parse expression '" << current << "': " << e.what() << std::endl; - return 1; - } - assert(expr); + ExprPtr expr; + try { + expr = parse_expr(current); + } catch (const ParseError &e) { + std::wcout << "Failed to parse expression '" << current + << "': " << e.what() << std::endl; + return 1; + } + assert(expr); - std::optional network = to_network(expr); - if (!network.has_value()) { - std::wcout << "Failed to construct tensor network for input '" << current << "'" << std::endl; - return 2; - } + std::optional network = to_network(expr); + if (!network.has_value()) { + std::wcout << "Failed to construct tensor network for input '" << current + << "'" << std::endl; + return 2; + } - TensorNetwork::Graph graph = network->create_graph(use_named_indices ? nullptr : &empty_named_indices); - std::wcout << "Graph for '" << current << "'\n"; - graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); - } + TensorNetwork::Graph graph = network->create_graph( + use_named_indices ? nullptr : &empty_named_indices); + std::wcout << "Graph for '" << current << "'\n"; + graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); + } } From 3850598ba0630308a3e4ed7be235d963b89cae46 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 4 Jul 2024 13:59:17 +0200 Subject: [PATCH 34/85] Make tensor_network_graphs propgram not fail tests --- examples/tensor_network_graphs/tensor_network_graphs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tensor_network_graphs/tensor_network_graphs.cpp b/examples/tensor_network_graphs/tensor_network_graphs.cpp index 4070a19ee..62e335d96 100644 --- a/examples/tensor_network_graphs/tensor_network_graphs.cpp +++ b/examples/tensor_network_graphs/tensor_network_graphs.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -59,7 +59,7 @@ int main(int argc, char **argv) { if (argc <= 1) { print_help(); - return 1; + return 0; } for (std::size_t i = 1; i < static_cast(argc); ++i) { From cb19a4fb51c779116e1cb352756cbf3803036a33 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 4 Jul 2024 15:05:04 +0200 Subject: [PATCH 35/85] Formatting --- tests/unit/test_tensor_network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 0c8e03709..4f290104a 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -482,8 +482,8 @@ TEST_CASE("TensorNetwork", "[elements]") { const auto [current_graph, current_graph_labels] = accessor.get_canonical_bliss_graph(tn); if (current_graph->cmp(*canonical_graph) != 0) { - std::wcout << "Canonical graph for " - << deparse(ex(copy)) << ":\n"; + std::wcout << "Canonical graph for " << deparse(ex(copy)) + << ":\n"; current_graph->write_dot(std::wcout, current_graph_labels); std::wcout << std::endl; } From dd88460f572cb43800bec544b44b1fa0e0e07f7d Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sun, 17 Nov 2024 17:45:46 -0500 Subject: [PATCH 36/85] tensor dox++ --- SeQuant/core/abstract_tensor.hpp | 68 +++++++++++++++++++++++++++----- SeQuant/core/tensor.hpp | 15 ++++--- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/SeQuant/core/abstract_tensor.hpp b/SeQuant/core/abstract_tensor.hpp index 99a7306f8..3505f1234 100644 --- a/SeQuant/core/abstract_tensor.hpp +++ b/SeQuant/core/abstract_tensor.hpp @@ -31,10 +31,47 @@ namespace sequant { class TensorCanonicalizer; -/// This interface class defines a Tensor concept. All Tensor objects must -/// fulfill the is_tensor trait (see below). To adapt an existing class +/// AbstractTensor is a [tensor](https://en.wikipedia.org/wiki/Tensor) over +/// general (i.e., not necessarily commutative) +/// rings. A tensor with \f$ k \geq 0 \f$ contravariant +/// (ket, [Dirac notation](https://en.wikipedia.org/wiki/Bra-ket_notation) ) and +/// \f$ b \geq 0 \f$ covariant (bra) modes +/// describes elements of a tensor product of \f$ b+k \f$ vector spaces. +/// Equivalently it represents a linear map between the tensor product +/// of \f$ k \f$ _primal_ vector spaces to the tensor product of \f$ b \f$ +/// _dual_ vector spaces. Tensor modes are 1-to-1 represented by unique +/// [indices](https://en.wikipedia.org/wiki/Abstract_index_notation), +/// represented by Index objects. +/// +/// It is also necessary to support modes that are "array-like" in that they +/// do not refer to a vector space or its dual; such modes can represent +/// ordinal indices (e.g. to treat a collection/sequence of tensors as +/// a single tensor) Thus each tensor has zero or more auxiliary (aux) modes. +/// The aux modes are invariant under the transformations of vector spaces and +/// do not contribute to the tensor symmetries. +/// +/// Tensors can have the following symmetries: +/// - Tensors can be symmetric or nonsymmetric with respect to the transposition +/// of corresponding (first, second, etc.) modes in bra/ket mode ranges. This +/// symmetry is used to treat particle as indistinguishable or distinguishable +/// in many-particle quantum mechanics context. +/// - Tensors can be symmetric, antisymmetric, and nonsymmetric +/// with respect to the transposition of modes within the bra or ket sets. +/// This symmetry is used to model the +/// distinguishable and indistinguishable (bosonic and fermionic) degrees +/// of freedom in many-body quantum mechanics context. More complicated +/// symmetries are not yet supported. +/// - Tensors can be symmetric, conjugate, or nonsymmetric with respect to +/// swap of bra with ket. This symmetry corresponds to time reversal in +/// physical simulation. +/// +/// Lastly, the supporting rings are not assumed to be scalars, hence tensor +/// product supporting the concept of tensor is not necessarily commutative. +/// +/// \note This interface class defines a Tensor _concept_. All Tensor objects +/// must fulfill the is_tensor trait (see below). To adapt an existing class /// intrusively derive it from AbstractTensor and implement all member -/// functions. This allows to implemement heterogeneous containers of objects +/// functions. This allows to implement heterogeneous containers of objects /// that meet the Tensor concept. class AbstractTensor { inline auto missing_instantiation_for(const char* fn_name) const { @@ -58,41 +95,54 @@ class AbstractTensor { ranges::any_view; - /// view of a contiguous range of Index objects + /// accessor bra (covariant) indices + /// @return view of a contiguous range of Index objects virtual const_any_view_randsz _bra() const { throw missing_instantiation_for("_bra"); } - /// view of a contiguous range of Index objects + /// accesses ket (contravariant) indices + /// @return view of a contiguous range of Index objects virtual const_any_view_randsz _ket() const { throw missing_instantiation_for("_ket"); } - /// view of a contiguous range of Index objects + /// accesses aux (invariant) indices + /// @return view of a contiguous range of Index objects virtual const_any_view_randsz _auxiliary() const { throw missing_instantiation_for("_auxiliary"); } + /// accesses bra and ket indices /// view of a not necessarily contiguous range of Index objects virtual const_any_view_rand _braket() const { throw missing_instantiation_for("_braket"); } - /// view of a not necessarily contiguous range of Index objects + /// accesses bra, ket, and aux indices + /// @return view of a not necessarily contiguous range of Index objects virtual const_any_view_rand _indices() const { throw missing_instantiation_for("_indices"); } + /// @return the number of bra indices virtual std::size_t _bra_rank() const { throw missing_instantiation_for("_bra_rank"); } + /// @return the number of ket indices virtual std::size_t _ket_rank() const { throw missing_instantiation_for("_ket_rank"); } + /// @return the number of aux indices virtual std::size_t _auxiliary_rank() const { throw missing_instantiation_for("_auxiliary_rank"); } + /// @return the permutational symmetry of the vector space indices of + /// the tensor virtual Symmetry _symmetry() const { throw missing_instantiation_for("_symmetry"); } + /// @return the symmetry of tensor under exchange of vectors space (bra) and + /// its dual (ket) virtual BraKetSymmetry _braket_symmetry() const { throw missing_instantiation_for("_braket_symmetry"); } + /// @return the symmetry of tensor under exchange of bra and ket virtual ParticleSymmetry _particle_symmetry() const { throw missing_instantiation_for("_particle_symmetry"); } @@ -222,8 +272,8 @@ static_assert(is_tensor_v, "The AbstractTensor class does not fulfill the requirements of " "the Tensor interface"); -/// @tparam IndexMap a {source Index -> target Index} map type; if it is not @c -/// container::map +/// @tparam IndexMap a {source Index -> target Index} map type; if it is not +/// @c container::map /// will need to make a copy. /// @param[in,out] t an AbstractTensor object whose indices will be transformed /// @param[in] index_map a const reference to an IndexMap object that specifies diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index 5fc502d2e..28f7416f8 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -33,7 +33,8 @@ namespace sequant { -/// @brief particle-symmetric Tensor, i.e. permuting +/// @brief a Tensor is an instance of AbstractTensor over a scalar field, i.e. +/// Tensors have commutative addition and product operations class Tensor : public Expr, public AbstractTensor, public Labeled { private: using index_container_type = container::svector; @@ -67,10 +68,10 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { } } - /// @return view of the bra+ket index ranges + /// @return concatenated view of the bra and ket index ranges auto braket() { return ranges::views::concat(bra_, ket_); } - /// @return view of all indices + /// @return concatenated view of bra, ket, and auxiliary index ranges auto indices() { return ranges::views::concat(bra_, ket_, auxiliary_); } /// asserts that @p label is not reserved @@ -168,12 +169,16 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// @return "core" label of the tensor std::wstring_view label() const override { return label_; } + /// @return the bra index range const auto &bra() const { return bra_; } + /// @return the ket index range const auto &ket() const { return ket_; } + /// @return the auxiliary index range const auto &auxiliary() const { return auxiliary_; } - /// @return joined view of the bra and ket index ranges + /// @return concatenated view of the bra and ket index ranges auto braket() const { return ranges::views::concat(bra_, ket_); } - /// @return joined view of all indices of this tensor (bra, ket and auxiliary) + /// @return concatenated view of all indices of this tensor (bra, ket and + /// auxiliary) auto indices() const { return ranges::views::concat(bra_, ket_, auxiliary_); } /// @return view of the bra+ket index ranges /// @note this is to work around broken lookup rules From 4f3cd68c0ce7103fbc7f78cda69fc082d9d98508 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sun, 17 Nov 2024 18:14:08 -0500 Subject: [PATCH 37/85] auxiliary -> aux --- SeQuant/core/abstract_tensor.hpp | 32 +++++----- SeQuant/core/eval_expr.cpp | 7 +-- SeQuant/core/export/itf.cpp | 8 +-- SeQuant/core/op.hpp | 8 +-- SeQuant/core/parse/deparse.cpp | 4 +- SeQuant/core/tensor.hpp | 78 +++++++++++++------------ SeQuant/core/tensor_canonicalizer.hpp | 8 +-- SeQuant/core/tensor_network.cpp | 8 +-- SeQuant/core/utility/indices.hpp | 10 ++-- SeQuant/domain/mbpt/antisymmetrizer.hpp | 2 +- SeQuant/domain/mbpt/spin.cpp | 27 +++++---- tests/unit/test_canonicalize.cpp | 2 +- tests/unit/test_parse.cpp | 16 ++--- tests/unit/test_tensor.cpp | 8 +-- 14 files changed, 106 insertions(+), 112 deletions(-) diff --git a/SeQuant/core/abstract_tensor.hpp b/SeQuant/core/abstract_tensor.hpp index 3505f1234..caa6cf21a 100644 --- a/SeQuant/core/abstract_tensor.hpp +++ b/SeQuant/core/abstract_tensor.hpp @@ -107,8 +107,8 @@ class AbstractTensor { } /// accesses aux (invariant) indices /// @return view of a contiguous range of Index objects - virtual const_any_view_randsz _auxiliary() const { - throw missing_instantiation_for("_auxiliary"); + virtual const_any_view_randsz _aux() const { + throw missing_instantiation_for("_aux"); } /// accesses bra and ket indices /// view of a not necessarily contiguous range of Index objects @@ -129,8 +129,8 @@ class AbstractTensor { throw missing_instantiation_for("_ket_rank"); } /// @return the number of aux indices - virtual std::size_t _auxiliary_rank() const { - throw missing_instantiation_for("_auxiliary_rank"); + virtual std::size_t _aux_rank() const { + throw missing_instantiation_for("_aux_rank"); } /// @return the permutational symmetry of the vector space indices of /// the tensor @@ -184,11 +184,11 @@ class AbstractTensor { virtual any_view_randsz _ket_mutable() { throw missing_instantiation_for("_ket_mutable"); } - /// @return mutable view of auxiliary indices + /// @return mutable view of aux indices /// @warning this is used for mutable access, flush memoized state before /// returning! - virtual any_view_randsz _auxiliary_mutable() { - throw missing_instantiation_for("_auxiliary_mutable"); + virtual any_view_randsz _aux_mutable() { + throw missing_instantiation_for("_aux_mutable"); } friend class TensorCanonicalizer; @@ -199,14 +199,12 @@ class AbstractTensor { /// @{ inline auto bra(const AbstractTensor& t) { return t._bra(); } inline auto ket(const AbstractTensor& t) { return t._ket(); } -inline auto auxiliary(const AbstractTensor& t) { return t._auxiliary(); } +inline auto aux(const AbstractTensor& t) { return t._aux(); } inline auto braket(const AbstractTensor& t) { return t._braket(); } inline auto indices(const AbstractTensor& t) { return t._indices(); } inline auto bra_rank(const AbstractTensor& t) { return t._bra_rank(); } inline auto ket_rank(const AbstractTensor& t) { return t._ket_rank(); } -inline auto auxiliary_rank(const AbstractTensor& t) { - return t._auxiliary_rank(); -} +inline auto aux_rank(const AbstractTensor& t) { return t._aux_rank(); } inline auto symmetry(const AbstractTensor& t) { return t._symmetry(); } inline auto braket_symmetry(const AbstractTensor& t) { return t._braket_symmetry(); @@ -222,12 +220,12 @@ inline auto to_latex(const AbstractTensor& t) { return t._to_latex(); } /// Type trait for checking whether a given class fulfills the Tensor interface /// requirements Object @c t of a type that meets the concept must satisfy the /// following: -/// - @c bra(t) , @c ket(t), @c auxiliary(t), @c braket(t) and +/// - @c bra(t) , @c ket(t), @c aux(t), @c braket(t) and /// @c indices(t) are valid expressions and evaluate to a range of Index /// objects; -/// - @c bra_rank(t), @c ket_rank(t) and @c auxiliary_rank(t) are valid -/// expression and return sizes of the @c bra(t), @c ket(t) and @c -/// auxiliary(t) ranges, respectively; +/// - @c bra_rank(t), @c ket_rank(t) and @c aux_rank(t) are valid +/// expression and return sizes of the @c bra(t), @c ket(t) and +/// @c aux(t) ranges, respectively; /// - @c symmetry(t) is a valid expression and evaluates to a Symmetry /// object that describes the symmetry of bra/ket of a /// _particle-symmetric_ @c t ; @@ -251,12 +249,12 @@ struct is_tensor : std::bool_constant< std::is_invocable_v && std::is_invocable_v && - std::is_invocable_v && + std::is_invocable_v && std::is_invocable_v && std::is_invocable_v && std::is_invocable_v && std::is_invocable_v && - std::is_invocable_v && + std::is_invocable_v && std::is_invocable_v && std::is_invocable_v && std::is_invocable_v && diff --git a/SeQuant/core/eval_expr.cpp b/SeQuant/core/eval_expr.cpp index 6cefbd4bf..dbb710455 100644 --- a/SeQuant/core/eval_expr.cpp +++ b/SeQuant/core/eval_expr.cpp @@ -339,7 +339,7 @@ ExprPtr make_sum(EvalExpr const& left, EvalExpr const& right) noexcept { auto ts = tensor_symmetry_sum(left, right); auto ps = particle_symmetry(ts); auto bks = get_default_context().braket_symmetry(); - return ex(L"I", t1.bra(), t1.ket(), t1.auxiliary(), ts, bks, ps); + return ex(L"I", t1.bra(), t1.ket(), t1.aux(), ts, bks, ps); } ExprPtr make_prod(EvalExpr const& left, EvalExpr const& right) noexcept { @@ -379,9 +379,8 @@ ExprPtr make_imed(EvalExpr const& left, EvalExpr const& right, assert(op == EvalOp::Prod && "scalar + tensor not supported"); auto const& t = right.expr()->as(); - return ex(Tensor{L"I", t.bra(), t.ket(), t.auxiliary(), - t.symmetry(), t.braket_symmetry(), - t.particle_symmetry()}); + return ex(Tensor{L"I", t.bra(), t.ket(), t.aux(), t.symmetry(), + t.braket_symmetry(), t.particle_symmetry()}); } else if (lres == ResultType::Tensor && rres == ResultType::Scalar) { // tensor (*) scalar diff --git a/SeQuant/core/export/itf.cpp b/SeQuant/core/export/itf.cpp index 6de7fff70..fafefd5dd 100644 --- a/SeQuant/core/export/itf.cpp +++ b/SeQuant/core/export/itf.cpp @@ -257,7 +257,7 @@ void one_electron_integral_remapper( auto braIndices = tensor.bra(); auto ketIndices = tensor.ket(); - assert(tensor.auxiliary().empty()); + assert(tensor.aux().empty()); IndexTypeComparer cmp; @@ -273,7 +273,7 @@ void one_electron_integral_remapper( } expr = ex(tensor.label(), std::move(braIndices), - std::move(ketIndices), tensor.auxiliary()); + std::move(ketIndices), tensor.aux()); } template @@ -317,7 +317,7 @@ void two_electron_integral_remapper( // Copy indices as we might have to mutate them auto braIndices = tensor.bra(); auto ketIndices = tensor.ket(); - assert(tensor.auxiliary().empty()); + assert(tensor.aux().empty()); IndexTypeComparer cmp; @@ -408,7 +408,7 @@ void two_electron_integral_remapper( } expr = ex(std::move(tensorLabel), std::move(braIndices), - std::move(ketIndices), tensor.auxiliary()); + std::move(ketIndices), tensor.aux()); } void integral_remapper(ExprPtr &expr, std::wstring_view oneElectronIntegralName, diff --git a/SeQuant/core/op.hpp b/SeQuant/core/op.hpp index 106dd3aa2..5b76089a9 100644 --- a/SeQuant/core/op.hpp +++ b/SeQuant/core/op.hpp @@ -829,7 +829,7 @@ class NormalOperator : public Operator, ranges::views::transform( [](auto &&op) -> const Index & { return op.index(); }); } - AbstractTensor::const_any_view_randsz _auxiliary() const override final { + AbstractTensor::const_any_view_randsz _aux() const override final { return {}; } AbstractTensor::const_any_view_rand _braket() const override final { @@ -842,7 +842,7 @@ class NormalOperator : public Operator, } std::size_t _bra_rank() const override final { return nannihilators(); } std::size_t _ket_rank() const override final { return ncreators(); } - std::size_t _auxiliary_rank() const override final { return 0; } + std::size_t _aux_rank() const override final { return 0; } Symmetry _symmetry() const override final { return (S == Statistics::FermiDirac ? (get_default_context(S).spbasis() == SPBasis::spinorbital @@ -890,9 +890,7 @@ class NormalOperator : public Operator, ranges::views::transform( [](auto &&op) -> Index & { return op.index(); }); } - AbstractTensor::any_view_randsz _auxiliary_mutable() override final { - return {}; - } + AbstractTensor::any_view_randsz _aux_mutable() override final { return {}; } }; static_assert( diff --git a/SeQuant/core/parse/deparse.cpp b/SeQuant/core/parse/deparse.cpp index 056b0f342..94ad99d99 100644 --- a/SeQuant/core/parse/deparse.cpp +++ b/SeQuant/core/parse/deparse.cpp @@ -136,11 +136,11 @@ std::wstring deparse(Tensor const& tensor, bool annot_sym) { if (tensor.ket_rank() > 0) { deparsed += L";" + details::deparse_indices(tensor.ket()); } - if (tensor.auxiliary_rank() > 0) { + if (tensor.aux_rank() > 0) { if (tensor.ket_rank() == 0) { deparsed += L";"; } - deparsed += L";" + details::deparse_indices(tensor.auxiliary()); + deparsed += L";" + details::deparse_indices(tensor.aux()); } deparsed += L"}"; diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index 28f7416f8..9f500a0ac 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -71,8 +71,8 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// @return concatenated view of the bra and ket index ranges auto braket() { return ranges::views::concat(bra_, ket_); } - /// @return concatenated view of bra, ket, and auxiliary index ranges - auto indices() { return ranges::views::concat(bra_, ket_, auxiliary_); } + /// @return concatenated view of bra, ket, and aux index ranges + auto indices() { return ranges::views::concat(bra_, ket_, aux_); } /// asserts that @p label is not reserved /// @note Tensor with reserved labels are constructed using friends of Tensor @@ -87,14 +87,14 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { template Tensor(std::wstring_view label, IndexRange1 &&bra_indices, - IndexRange2 &&ket_indices, IndexRange3 &&auxiliary_indices, - reserved_tag, Symmetry s = Symmetry::nonsymm, + IndexRange2 &&ket_indices, IndexRange3 &&aux_indices, reserved_tag, + Symmetry s = Symmetry::nonsymm, BraKetSymmetry bks = get_default_context().braket_symmetry(), ParticleSymmetry ps = ParticleSymmetry::symm) : label_(label), bra_(make_indices(bra_indices)), ket_(make_indices(ket_indices)), - auxiliary_(make_indices(auxiliary_indices)), + aux_(make_indices(aux_indices)), symmetry_(s), braket_symmetry_(bks), particle_symmetry_(ps) { @@ -112,7 +112,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// to indices) /// @param ket_indices list of ket indices (or objects that can be converted /// to indices) - /// @param auxiliary_indices list of auxiliary indices (or objects that can be + /// @param aux_indices list of aux indices (or objects that can be /// converted to indices) /// @param s the symmetry of bra or ket /// @param bks the symmetry with respect to bra-ket exchange @@ -123,14 +123,14 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { !meta::is_initializer_list_v> && !meta::is_initializer_list_v>>> Tensor(std::wstring_view label, IndexRange1 &&bra_indices, - IndexRange2 &&ket_indices, IndexRange3 &&auxiliary_indices, + IndexRange2 &&ket_indices, IndexRange3 &&aux_indices, Symmetry s = Symmetry::nonsymm, BraKetSymmetry bks = get_default_context().braket_symmetry(), ParticleSymmetry ps = ParticleSymmetry::symm) : Tensor(label, std::forward(bra_indices), std::forward(ket_indices), - std::forward(auxiliary_indices), reserved_tag{}, s, - bks, ps) { + std::forward(aux_indices), reserved_tag{}, s, bks, + ps) { assert_nonreserved_label(label_); } @@ -143,7 +143,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { /// to indices) /// @param ket_indices list of ket indices (or objects that can be converted /// to indices) - /// @param auxiliary_indices list of auxiliary indices (or objects that can be + /// @param aux_indices list of aux indices (or objects that can be /// converted to indices) /// @param s the symmetry of bra or ket /// @param bks the symmetry with respect to bra-ket exchange @@ -151,12 +151,11 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { template Tensor(std::wstring_view label, std::initializer_list bra_indices, std::initializer_list ket_indices, - std::initializer_list auxiliary_indices, - Symmetry s = Symmetry::nonsymm, + std::initializer_list aux_indices, Symmetry s = Symmetry::nonsymm, BraKetSymmetry bks = get_default_context().braket_symmetry(), ParticleSymmetry ps = ParticleSymmetry::symm) : Tensor(label, make_indices(bra_indices), make_indices(ket_indices), - make_indices(auxiliary_indices), reserved_tag{}, s, bks, ps) { + make_indices(aux_indices), reserved_tag{}, s, bks, ps) { assert_nonreserved_label(label_); } @@ -173,13 +172,13 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { const auto &bra() const { return bra_; } /// @return the ket index range const auto &ket() const { return ket_; } - /// @return the auxiliary index range - const auto &auxiliary() const { return auxiliary_; } + /// @return the aux index range + const auto &aux() const { return aux_; } /// @return concatenated view of the bra and ket index ranges auto braket() const { return ranges::views::concat(bra_, ket_); } /// @return concatenated view of all indices of this tensor (bra, ket and - /// auxiliary) - auto indices() const { return ranges::views::concat(bra_, ket_, auxiliary_); } + /// aux) + auto indices() const { return ranges::views::concat(bra_, ket_, aux_); } /// @return view of the bra+ket index ranges /// @note this is to work around broken lookup rules auto const_braket() const { return this->braket(); } @@ -207,8 +206,8 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { std::size_t bra_rank() const { return bra_.size(); } /// @return number of ket indices std::size_t ket_rank() const { return ket_.size(); } - /// @return number of auxiliary indices - auto auxiliary_rank() const { return auxiliary_.size(); } + /// @return number of aux indices + std::size_t aux_rank() const { return aux_.size(); } /// @return number of indices in bra/ket /// @throw std::logic_error if bra and ket ranks do not match std::size_t rank() const { @@ -234,13 +233,13 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { result += L"}_{"; for (const auto &i : this->bra()) result += sequant::to_latex(i); result += L"}"; - if (!this->auxiliary_.empty()) { + if (!this->aux_.empty()) { result += L"("; - const index_container_type &aux = auxiliary(); - for (std::size_t i = 0; i < auxiliary_rank(); ++i) { - result += sequant::to_latex(aux[i]); + const index_container_type &__aux = aux(); + for (std::size_t i = 0; i < aux_rank(); ++i) { + result += sequant::to_latex(__aux[i]); - if (i + 1 < auxiliary_rank()) { + if (i + 1 < aux_rank()) { result += L","; } } @@ -287,7 +286,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { std::wstring label_{}; index_container_type bra_{}; index_container_type ket_{}; - index_container_type auxiliary_{}; + index_container_type aux_{}; Symmetry symmetry_ = Symmetry::invalid; BraKetSymmetry braket_symmetry_ = BraKetSymmetry::invalid; ParticleSymmetry particle_symmetry_ = ParticleSymmetry::invalid; @@ -308,7 +307,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { auto val = hash::range(begin(bra()), end(bra())); bra_hash_value_ = val; hash::range(val, begin(ket()), end(ket())); - hash::range(val, begin(auxiliary()), end(auxiliary())); + hash::range(val, begin(aux()), end(aux())); hash::combine(val, label_); hash::combine(val, symmetry_); hash_value_ = val; @@ -324,7 +323,8 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { if (this->label() == that_cast.label() && this->symmetry() == that_cast.symmetry() && this->bra_rank() == that_cast.bra_rank() && - this->ket_rank() == that_cast.ket_rank()) { + this->ket_rank() == that_cast.ket_rank() && + this->aux_rank() == that_cast.aux_rank()) { // compare hash values first if (this->hash_value() == that.hash_value()) // hash values agree -> do full comparison @@ -351,6 +351,10 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { return this->ket_rank() < that_cast.ket_rank(); } + if (this->aux_rank() != that_cast.aux_rank()) { + return this->aux_rank() < that_cast.aux_rank(); + } + // v1: compare hashes only // return Expr::static_less_than(that); // v2: compare fully @@ -366,9 +370,9 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { that_cast.ket().end()); } - return std::lexicographical_compare( - this->auxiliary().begin(), this->auxiliary().end(), - that_cast.auxiliary().begin(), that_cast.auxiliary().end()); + return std::lexicographical_compare(this->aux().begin(), this->aux().end(), + that_cast.aux().begin(), + that_cast.aux().end()); } // these implement the AbstractTensor interface @@ -380,9 +384,9 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { return ranges::counted_view( ket_.empty() ? nullptr : &(ket_[0]), ket_.size()); } - AbstractTensor::const_any_view_randsz _auxiliary() const override final { + AbstractTensor::const_any_view_randsz _aux() const override final { return ranges::counted_view( - auxiliary_.empty() ? nullptr : &(auxiliary_[0]), auxiliary_.size()); + aux_.empty() ? nullptr : &(aux_[0]), aux_.size()); } AbstractTensor::const_any_view_rand _braket() const override final { return braket(); @@ -392,9 +396,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { } std::size_t _bra_rank() const override final { return bra_rank(); } std::size_t _ket_rank() const override final { return ket_rank(); } - std::size_t _auxiliary_rank() const override final { - return auxiliary_rank(); - } + std::size_t _aux_rank() const override final { return aux_rank(); } Symmetry _symmetry() const override final { return symmetry_; } BraKetSymmetry _braket_symmetry() const override final { return braket_symmetry_; @@ -430,10 +432,10 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { return ranges::counted_view(ket_.empty() ? nullptr : &(ket_[0]), ket_.size()); } - AbstractTensor::any_view_randsz _auxiliary_mutable() override final { + AbstractTensor::any_view_randsz _aux_mutable() override final { this->reset_hash_value(); - return ranges::counted_view( - auxiliary_.empty() ? nullptr : &(auxiliary_[0]), auxiliary_.size()); + return ranges::counted_view(aux_.empty() ? nullptr : &(aux_[0]), + aux_.size()); } }; // class Tensor diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp index beddfe6e7..818e03de8 100644 --- a/SeQuant/core/tensor_canonicalizer.hpp +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -102,9 +102,7 @@ class TensorCanonicalizer { protected: inline auto bra_range(AbstractTensor& t) const { return t._bra_mutable(); } inline auto ket_range(AbstractTensor& t) const { return t._ket_mutable(); } - inline auto auxiliary_range(AbstractTensor& t) const { - return t._auxiliary_mutable(); - } + inline auto aux_range(AbstractTensor& t) const { return t._aux_mutable(); } /// the object used to compare indices static index_comparer_t index_comparer_; @@ -163,7 +161,7 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { auto is_antisymm = (s == Symmetry::antisymm); const auto _bra_rank = bra_rank(t); const auto _ket_rank = ket_rank(t); - const auto _aux_rank = auxiliary_rank(t); + const auto _aux_rank = aux_rank(t); const auto _rank = std::min(_bra_rank, _ket_rank); // nothing to do for rank-1 tensors @@ -217,7 +215,7 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { } // TODO: Handle auxiliary index symmetries once they are introduced - // auto _aux = auxiliary_range(t); + // auto _aux = aux_range(t); // ranges::sort(_aux, comp); ExprPtr result = diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 4c5db4bc4..784bc51d8 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -65,8 +65,8 @@ struct TensorBlockCompare { if (ket_rank(lhs) != ket_rank(rhs)) { return ket_rank(lhs) < ket_rank(rhs); } - if (auxiliary_rank(lhs) != auxiliary_rank(rhs)) { - return auxiliary_rank(lhs) < auxiliary_rank(rhs); + if (aux_rank(lhs) != aux_rank(rhs)) { + return aux_rank(lhs) < aux_rank(rhs); } auto lhs_indices = indices(lhs); @@ -759,7 +759,7 @@ TensorNetwork::Graph TensorNetwork::create_graph( // TODO: handle aux indices permutation symmetries once they are supported // for now, auxiliary indices are considered to always be asymmetric - for (std::size_t i = 0; i < auxiliary_rank(tensor); ++i) { + for (std::size_t i = 0; i < aux_rank(tensor); ++i) { graph.vertex_labels.emplace_back(L"aux_" + std::to_wstring(i + 1)); graph.vertex_types.push_back(VertexType::TensorAux); graph.vertex_colors.push_back(2 * max_rank + i); @@ -942,7 +942,7 @@ void TensorNetwork::init_edges() { Vertex(Origin::Ket, tensor_idx, index_idx, tensor_symm)); } - auto aux_indices = auxiliary(tensor); + auto aux_indices = aux(tensor); for (std::size_t index_idx = 0; index_idx < aux_indices.size(); ++index_idx) { // Note: for the time being we don't have a way of expressing diff --git a/SeQuant/core/utility/indices.hpp b/SeQuant/core/utility/indices.hpp index 2b3e2c1ad..53abb8d0a 100644 --- a/SeQuant/core/utility/indices.hpp +++ b/SeQuant/core/utility/indices.hpp @@ -97,10 +97,10 @@ IndexGroups get_uncontracted_indices(const Tensor& t1, detail::not_in{t1.bra()}); // Auxiliary indices - std::copy_if(t1.auxiliary().begin(), t1.auxiliary().end(), - std::back_inserter(groups.aux), detail::not_in{t2.auxiliary()}); - std::copy_if(t2.auxiliary().begin(), t2.auxiliary().end(), - std::back_inserter(groups.aux), detail::not_in{t1.auxiliary()}); + std::copy_if(t1.aux().begin(), t1.aux().end(), std::back_inserter(groups.aux), + detail::not_in{t2.aux()}); + std::copy_if(t2.aux().begin(), t2.aux().end(), std::back_inserter(groups.aux), + detail::not_in{t1.aux()}); return groups; } @@ -132,7 +132,7 @@ IndexGroups get_unique_indices(const Tensor& tensor) { } } - for (const Index& current : tensor.auxiliary()) { + for (const Index& current : tensor.aux()) { if (encounteredIndices.find(current) == encounteredIndices.end()) { groups.aux.push_back(current); encounteredIndices.insert(current); diff --git a/SeQuant/domain/mbpt/antisymmetrizer.hpp b/SeQuant/domain/mbpt/antisymmetrizer.hpp index 34fd7156d..9c4e9077e 100644 --- a/SeQuant/domain/mbpt/antisymmetrizer.hpp +++ b/SeQuant/domain/mbpt/antisymmetrizer.hpp @@ -503,7 +503,7 @@ ExprPtr spin_sum(std::vector original_upper, } factor = ex(L"Γ", factor->as().bra(), factor->as().ket(), - factor->as().auxiliary()); + factor->as().aux()); } else if (factor->is()) { // prefactor = ex(-0.5) * // ex(factor->as().rank()) * prefactor; diff --git a/SeQuant/domain/mbpt/spin.cpp b/SeQuant/domain/mbpt/spin.cpp index c3fe59acc..b6e6ca620 100644 --- a/SeQuant/domain/mbpt/spin.cpp +++ b/SeQuant/domain/mbpt/spin.cpp @@ -126,7 +126,7 @@ ExprPtr swap_bra_ket(const ExprPtr& expr) { // Lambda for tensor auto tensor_swap = [](const Tensor& tensor) { auto result = Tensor(tensor.label(), tensor.ket(), tensor.bra(), - tensor.auxiliary(), tensor.symmetry(), + tensor.aux(), tensor.symmetry(), tensor.braket_symmetry(), tensor.particle_symmetry()); return ex(result); }; @@ -198,8 +198,8 @@ ExprPtr remove_spin(const ExprPtr& expr) { idx = make_spinfree(idx); } } - Tensor result(tensor.label(), bra, ket, tensor.auxiliary(), - tensor.symmetry(), tensor.braket_symmetry()); + Tensor result(tensor.label(), bra, ket, tensor.aux(), tensor.symmetry(), + tensor.braket_symmetry()); return std::make_shared(std::move(result)); }; @@ -287,9 +287,9 @@ ExprPtr expand_antisymm(const Tensor& tensor, bool skip_spinsymm) { assert(tensor.bra_rank() == tensor.ket_rank()); // Return non-symmetric tensor if rank is 1 if (tensor.bra_rank() == 1) { - Tensor new_tensor(tensor.label(), tensor.bra(), tensor.ket(), - tensor.auxiliary(), Symmetry::nonsymm, - tensor.braket_symmetry(), tensor.particle_symmetry()); + Tensor new_tensor(tensor.label(), tensor.bra(), tensor.ket(), tensor.aux(), + Symmetry::nonsymm, tensor.braket_symmetry(), + tensor.particle_symmetry()); return std::make_shared(new_tensor); } @@ -318,8 +318,8 @@ ExprPtr expand_antisymm(const Tensor& tensor, bool skip_spinsymm) { container::set ket_list(tensor.ket().begin(), tensor.ket().end()); auto expr_sum = std::make_shared(); do { - auto new_tensor = Tensor(tensor.label(), bra_list, ket_list, - tensor.auxiliary(), Symmetry::nonsymm); + auto new_tensor = Tensor(tensor.label(), bra_list, ket_list, tensor.aux(), + Symmetry::nonsymm); if (spin_symm_tensor(new_tensor)) { auto new_tensor_product = std::make_shared(); @@ -524,7 +524,7 @@ ExprPtr symmetrize_expr(const Product& product) { auto S = Tensor{}; if (A_is_nconserving) { - S = Tensor(L"S", A_tensor.bra(), A_tensor.ket(), A_tensor.auxiliary(), + S = Tensor(L"S", A_tensor.bra(), A_tensor.ket(), A_tensor.aux(), Symmetry::nonsymm); } else { // A is N-nonconserving auto n = std::min(A_tensor.bra_rank(), A_tensor.ket_rank()); @@ -532,8 +532,7 @@ ExprPtr symmetrize_expr(const Product& product) { A_tensor.bra().begin() + n); container::svector ket_list(A_tensor.ket().begin(), A_tensor.ket().begin() + n); - S = Tensor(L"S", bra_list, ket_list, A_tensor.auxiliary(), - Symmetry::nonsymm); + S = Tensor(L"S", bra_list, ket_list, A_tensor.aux(), Symmetry::nonsymm); } // Generate replacement maps from a list of Index type (could be a bra or a @@ -1081,7 +1080,7 @@ Tensor swap_spin(const Tensor& t) { return {t.label(), bra, ket, - t.auxiliary(), + t.aux(), t.symmetry(), t.braket_symmetry(), t.particle_symmetry()}; @@ -1121,7 +1120,7 @@ ExprPtr merge_tensors(const Tensor& O1, const Tensor& O2) { assert(O1.symmetry() == O2.symmetry()); auto bra = ranges::views::concat(O1.bra(), O2.bra()); auto ket = ranges::views::concat(O1.ket(), O2.ket()); - auto aux = ranges::views::concat(O1.auxiliary(), O2.auxiliary()); + auto aux = ranges::views::concat(O1.aux(), O2.aux()); return ex(Tensor(O1.label(), bra, ket, aux, O1.symmetry())); } @@ -1148,7 +1147,7 @@ std::vector open_shell_A_op(const Tensor& A) { ranges::for_each(spin_bra, [](const Index& i) { i.reset_tag(); }); ranges::for_each(spin_ket, [](const Index& i) { i.reset_tag(); }); result.at(i) = ex( - Tensor(L"A", spin_bra, spin_ket, A.auxiliary(), Symmetry::antisymm)); + Tensor(L"A", spin_bra, spin_ket, A.aux(), Symmetry::antisymm)); // std::wcout << to_latex(result.at(i)) << " "; } // std::wcout << "\n" << std::endl; diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 5567c7849..b344a7b8a 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -17,7 +17,7 @@ #include -// TODO: Add test cases with auxiliary indices +// TODO: Add test cases with aux indices TEST_CASE("Canonicalizer", "[algorithms]") { using namespace sequant; diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 8c55b1b88..27383aeaf 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -88,7 +88,7 @@ TEST_CASE("parse_expr", "[parse]") { REQUIRE(expr->is()); REQUIRE(expr->as().bra().empty()); REQUIRE(expr->as().ket().empty()); - REQUIRE(expr->as().auxiliary().empty()); + REQUIRE(expr->as().aux().empty()); REQUIRE(expr == parse_expr(L"t{;}")); REQUIRE(expr == parse_expr(L"t{;;}")); @@ -103,7 +103,7 @@ TEST_CASE("parse_expr", "[parse]") { REQUIRE(expr->as().bra().at(0).label() == L"i_1"); REQUIRE(expr->as().ket().size() == 1); REQUIRE(expr->as().ket().at(0) == L"a_1"); - REQUIRE(expr->as().auxiliary().empty()); + REQUIRE(expr->as().aux().empty()); REQUIRE(expr == parse_expr(L"t_{i1}^{a1}")); REQUIRE(expr == parse_expr(L"t^{a1}_{i1}")); @@ -118,7 +118,7 @@ TEST_CASE("parse_expr", "[parse]") { REQUIRE(expr->as().ket().size() == 2); REQUIRE(expr->as().ket().at(0).label() == L"a_1"); REQUIRE(expr->as().ket().at(1).label() == L"a_2"); - REQUIRE(expr->as().auxiliary().empty()); + REQUIRE(expr->as().aux().empty()); REQUIRE(expr == parse_expr(L"+t{i1, i2; a1, a2}")); REQUIRE(parse_expr(L"-t{i1;a1}")->is()); @@ -150,8 +150,8 @@ TEST_CASE("parse_expr", "[parse]") { REQUIRE(expr->is()); REQUIRE(expr->as().bra().empty()); REQUIRE(expr->as().ket().empty()); - REQUIRE(expr->as().auxiliary().size() == 1); - REQUIRE(expr->as().auxiliary()[0].label() == L"i_1"); + REQUIRE(expr->as().aux().size() == 1); + REQUIRE(expr->as().aux()[0].label() == L"i_1"); // All index groups at once expr = parse_expr(L"t{i1,i2;a1;x1,x2}"); @@ -161,9 +161,9 @@ TEST_CASE("parse_expr", "[parse]") { REQUIRE(expr->as().bra().at(1).label() == L"i_2"); REQUIRE(expr->as().ket().size() == 1); REQUIRE(expr->as().ket().at(0).label() == L"a_1"); - REQUIRE(expr->as().auxiliary().size() == 2); - REQUIRE(expr->as().auxiliary().at(0).label() == L"x_1"); - REQUIRE(expr->as().auxiliary().at(1).label() == L"x_2"); + REQUIRE(expr->as().aux().size() == 2); + REQUIRE(expr->as().aux().at(0).label() == L"x_1"); + REQUIRE(expr->as().aux().at(1).label() == L"x_2"); } SECTION("Tensor with symmetry annotation") { diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index a9f526b77..5a33174cf 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -32,7 +32,7 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(!t1); REQUIRE(t1.bra_rank() == 0); REQUIRE(t1.ket_rank() == 0); - REQUIRE(t1.auxiliary_rank() == 0); + REQUIRE(t1.aux_rank() == 0); REQUIRE(t1.rank() == 0); REQUIRE(t1.symmetry() == Symmetry::invalid); REQUIRE(t1.braket_symmetry() == BraKetSymmetry::invalid); @@ -44,7 +44,7 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(t2); REQUIRE(t2.bra_rank() == 1); REQUIRE(t2.ket_rank() == 1); - REQUIRE(t2.auxiliary_rank() == 0); + REQUIRE(t2.aux_rank() == 0); REQUIRE(t2.rank() == 1); REQUIRE(t2.const_indices().size() == 2); REQUIRE(t2.symmetry() == Symmetry::nonsymm); @@ -57,7 +57,7 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(t3); REQUIRE(t3.bra_rank() == 1); REQUIRE(t3.ket_rank() == 0); - REQUIRE(t3.auxiliary_rank() == 1); + REQUIRE(t3.aux_rank() == 1); REQUIRE_THROWS(t3.rank()); REQUIRE(t3.const_indices().size() == 2); REQUIRE(t3.symmetry() == Symmetry::nonsymm); @@ -76,7 +76,7 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(t4); REQUIRE(t4.bra_rank() == 2); REQUIRE(t4.ket_rank() == 2); - REQUIRE(t4.auxiliary_rank() == 1); + REQUIRE(t4.aux_rank() == 1); REQUIRE(t4.rank() == 2); REQUIRE(t4.const_indices().size() == 5); REQUIRE(t4.symmetry() == Symmetry::nonsymm); From 3dccf51117e8d51ba9752bbf00b0e1e52dc23394 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sun, 17 Nov 2024 23:42:17 -0500 Subject: [PATCH 38/85] TN::Edge: assert vertices are nonnull in vertex accessors --- SeQuant/core/tensor_network.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SeQuant/core/tensor_network.hpp b/SeQuant/core/tensor_network.hpp index 1031d96c0..b60073727 100644 --- a/SeQuant/core/tensor_network.hpp +++ b/SeQuant/core/tensor_network.hpp @@ -126,8 +126,14 @@ class TensorNetwork { return first == other.first && second == other.second; } - const Vertex &first_vertex() const { return first.value(); } - const Vertex &second_vertex() const { return second.value(); } + const Vertex &first_vertex() const { + assert(first.has_value()); + return first.value(); + } + const Vertex &second_vertex() const { + assert(second.has_value()); + return second.value(); + } /// @return the number of attached terminals (0, 1, or 2) std::size_t vertex_count() const { From c49da4a509b54b758807db81f9afabf238c9035a Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Mon, 18 Nov 2024 17:07:35 -0500 Subject: [PATCH 39/85] temporary revert of TensorNetwork to TensorNetworkV2 + restoration of old TensorNetwork for ease of troubleshooting --- CMakeLists.txt | 4 +- SeQuant/core/tensor_network.cpp | 1467 +++++++++++----------------- SeQuant/core/tensor_network.hpp | 306 +++--- SeQuant/core/tensor_network_v2.cpp | 1027 +++++++++++++++++++ SeQuant/core/tensor_network_v2.hpp | 329 +++++++ SeQuant/core/wick.impl.hpp | 9 +- SeQuant/core/wick_graph.cpp | 316 ------ SeQuant/core/wick_graph.hpp | 229 ----- tests/unit/test_tensor_network.cpp | 615 +++++++++++- 9 files changed, 2656 insertions(+), 1646 deletions(-) create mode 100644 SeQuant/core/tensor_network_v2.cpp create mode 100644 SeQuant/core/tensor_network_v2.hpp delete mode 100644 SeQuant/core/wick_graph.cpp delete mode 100644 SeQuant/core/wick_graph.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9db0af52a..01f0c0793 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,6 +242,8 @@ set(SeQuant_src SeQuant/core/tensor_canonicalizer.hpp SeQuant/core/tensor_network.cpp SeQuant/core/tensor_network.hpp + SeQuant/core/tensor_network_v2.cpp + SeQuant/core/tensor_network_v2.hpp SeQuant/core/timer.hpp SeQuant/core/utility/context.hpp SeQuant/core/utility/indices.hpp @@ -252,8 +254,6 @@ set(SeQuant_src SeQuant/core/utility/string.cpp SeQuant/core/wick.hpp SeQuant/core/wick.impl.hpp - SeQuant/core/wick_graph.cpp - SeQuant/core/wick_graph.hpp SeQuant/core/wolfram.hpp SeQuant/core/wstring.hpp SeQuant/domain/mbpt/antisymmetrizer.cpp diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 3afd4d4a3..f7529969a 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -14,17 +14,13 @@ #include #include #include -#include #include #include #include #include #include -#include -#include #include -#include #include #include @@ -36,476 +32,12 @@ namespace sequant { -struct FullLabelIndexLocator { - std::wstring_view label; - FullLabelIndexLocator(std::wstring_view label) : label(std::move(label)) {} - - bool operator()(const TensorNetwork::Edge &edge) const { - return edge.idx().full_label() == label; - } - - bool operator()(const Index &idx) const { return idx.full_label() == label; } -}; - -bool tensors_commute(const AbstractTensor &lhs, const AbstractTensor &rhs) { - // tensors commute if their colors are different or either one of them - // is a c-number - return !(color(lhs) == color(rhs) && !is_cnumber(lhs) && !is_cnumber(rhs)); -} - -struct TensorBlockCompare { - bool operator()(const AbstractTensor &lhs, const AbstractTensor &rhs) const { - if (label(lhs) != label(rhs)) { - return label(lhs) < label(rhs); - } - - if (bra_rank(lhs) != bra_rank(rhs)) { - return bra_rank(lhs) < bra_rank(rhs); - } - if (ket_rank(lhs) != ket_rank(rhs)) { - return ket_rank(lhs) < ket_rank(rhs); - } - if (aux_rank(lhs) != aux_rank(rhs)) { - return aux_rank(lhs) < aux_rank(rhs); - } - - auto lhs_indices = indices(lhs); - auto rhs_indices = indices(rhs); - - for (auto lhs_it = lhs_indices.begin(), rhs_it = rhs_indices.begin(); - lhs_it != lhs_indices.end() && rhs_it != rhs_indices.end(); - ++lhs_it, ++rhs_it) { - if (lhs_it->space() != rhs_it->space()) { - return lhs_it->space() < rhs_it->space(); - } - } - - // Tensors are identical - return false; - } -}; - -/// Compares tensors based on their label and orders them according to the order -/// of the given cardinal tensor labels. If two tensors can't be discriminated -/// via their label, they are compared based on regular -/// AbstractTensor::operator< or based on their tensor block (the spaces of -/// their indices) - depending on the configuration. If this doesn't -/// discriminate the tensors, they are considered equal -template -struct CanonicalTensorCompare { - const CardinalLabels &labels; - bool blocks_only; - - CanonicalTensorCompare(const CardinalLabels &labels, bool blocks_only) - : labels(labels), blocks_only(blocks_only) {} - - void set_blocks_only(bool blocks_only) { this->blocks_only = blocks_only; } - - bool operator()(const AbstractTensorPtr &lhs_ptr, - const AbstractTensorPtr &rhs_ptr) const { - assert(lhs_ptr); - assert(rhs_ptr); - const AbstractTensor &lhs = *lhs_ptr; - const AbstractTensor &rhs = *rhs_ptr; - - if (!tensors_commute(lhs, rhs)) { - return false; - } - - const auto get_label = [](const auto &t) { - if (label(t).back() == adjoint_label) { - // grab base label if adjoint label is present - return label(t).substr(0, label(t).size() - 1); - } - return label(t); - }; - - const auto lhs_it = std::find(labels.begin(), labels.end(), get_label(lhs)); - const auto rhs_it = std::find(labels.begin(), labels.end(), get_label(rhs)); - - if (lhs_it != rhs_it) { - // At least one of the tensors is a cardinal one - // -> Order by the occurrence in the cardinal label list - return std::distance(labels.begin(), lhs_it) < - std::distance(labels.begin(), rhs_it); - } - - // Either both are the same cardinal tensor or none is a cardinal tensor - if (blocks_only) { - TensorBlockCompare cmp; - return cmp(lhs, rhs); - } else { - return lhs < rhs; - } - } -}; - -TensorNetwork::Vertex::Vertex(Origin origin, std::size_t terminal_idx, - std::size_t index_slot, Symmetry terminal_symm) - : origin(origin), - terminal_idx(terminal_idx), - index_slot(index_slot), - terminal_symm(terminal_symm) {} - -TensorNetwork::Origin TensorNetwork::Vertex::getOrigin() const { - return origin; -} - -std::size_t TensorNetwork::Vertex::getTerminalIndex() const { - return terminal_idx; -} - -std::size_t TensorNetwork::Vertex::getIndexSlot() const { return index_slot; } - -Symmetry TensorNetwork::Vertex::getTerminalSymmetry() const { - return terminal_symm; -} - -bool TensorNetwork::Vertex::operator<(const Vertex &rhs) const { - if (terminal_idx != rhs.terminal_idx) { - return terminal_idx < rhs.terminal_idx; - } - - // Both vertices belong to same tensor -> they must have same symmetry - assert(terminal_symm == rhs.terminal_symm); - - if (origin != rhs.origin) { - return origin < rhs.origin; - } - - // We only take the index slot into account for non-symmetric tensors - if (terminal_symm == Symmetry::nonsymm) { - return index_slot < rhs.index_slot; - } else { - return false; - } -} - -bool TensorNetwork::Vertex::operator==(const Vertex &rhs) const { - // Slot position is only taken into account for non_symmetric tensors - const std::size_t lhs_slot = - (terminal_symm == Symmetry::nonsymm) * index_slot; - const std::size_t rhs_slot = - (rhs.terminal_symm == Symmetry::nonsymm) * rhs.index_slot; - - assert(terminal_idx != rhs.terminal_idx || - terminal_symm == rhs.terminal_symm); - - return terminal_idx == rhs.terminal_idx && lhs_slot == rhs_slot && - origin == rhs.origin; -} - -std::size_t TensorNetwork::Graph::vertex_to_index_idx( - std::size_t vertex) const { - assert(vertex_types.at(vertex) == VertexType::Index); - - std::size_t index_idx = 0; - for (std::size_t i = 0; i <= vertex; ++i) { - if (vertex_types[i] == VertexType::Index) { - ++index_idx; - } - } - - assert(index_idx > 0); - - return index_idx - 1; -} - -std::size_t TensorNetwork::Graph::vertex_to_tensor_idx( - std::size_t vertex) const { - assert(vertex_types.at(vertex) == VertexType::TensorCore); - - std::size_t tensor_idx = 0; - for (std::size_t i = 0; i <= vertex; ++i) { - if (vertex_types[i] == VertexType::TensorCore) { - ++tensor_idx; - } - } - - assert(tensor_idx > 0); - - return tensor_idx - 1; -} - -template -auto permute(const ArrayLike &vector, const Permutation &perm) { - using std::size; - auto sz = size(vector); - std::decay_t pvector(sz); - for (size_t i = 0; i != sz; ++i) pvector[perm[i]] = vector[i]; - return pvector; -} - -template -void apply_index_replacements(AbstractTensor &tensor, - const ReplacementMap &replacements) { -#ifndef NDEBUG - // assert that tensors' indices are not tagged since going to tag indices - assert(ranges::none_of( - indices(tensor), [](const Index &idx) { return idx.tag().has_value(); })); -#endif - - bool pass_mutated; - do { - pass_mutated = transform_indices(tensor, replacements); - } while (pass_mutated); // transform till stops changing - - reset_tags(tensor); -} - -template -void apply_index_replacements(ArrayLike &tensors, - const ReplacementMap &replacements) { - for (auto &tensor : tensors) { - apply_index_replacements(*tensor, replacements); - } -} - -template -void order_to_indices(Container &container) { - std::vector indices; - indices.resize(container.size()); - std::iota(indices.begin(), indices.end(), 0); - - std::sort(indices.begin(), indices.end(), - [&container](std::size_t lhs, std::size_t rhs) { - return container[lhs] < container[rhs]; - }); - // Overwrite container contents with indices - std::copy(indices.begin(), indices.end(), container.begin()); -} - -template -void sort_via_indices(Container &container, const Comparator &cmp) { - std::vector indices; - indices.resize(container.size()); - std::iota(indices.begin(), indices.end(), 0); - - if constexpr (stable) { - std::stable_sort(indices.begin(), indices.end(), cmp); - } else { - std::sort(indices.begin(), indices.end(), cmp); - } - - // Bring elements in container into the order given by indices - // (the association is container[k] = container[indices[k]]) - // -> implementation from https://stackoverflow.com/a/838789 - - for (std::size_t i = 0; i < container.size(); ++i) { - if (indices[i] == i) { - // This element is already where it is supposed to be - continue; - } - - // Find the offset of the index pointing to i - // -> since we are going to change the content of the vector at position i, - // we have to update the index-mapping referencing i to point to the new - // location of the element that used to be at position i - std::size_t k; - for (k = i + 1; k < container.size(); ++k) { - if (indices[k] == i) { - break; - } - } - std::swap(container[i], container[indices[i]]); - std::swap(indices[i], indices[k]); - } -} - -void TensorNetwork::canonicalize_graph(const named_indices_t &named_indices) { - if (Logger::instance().canonicalize) { - std::wcout << "TensorNetwork::canonicalize_graph: input tensors\n"; - size_t cnt = 0; - ranges::for_each(tensors_, [&](const auto &t) { - std::wcout << "tensor " << cnt++ << ": " << to_latex(*t) << std::endl; - }); - std::wcout << std::endl; - } - - if (!have_edges_) { - init_edges(); - } - - const auto is_anonymous_index = [named_indices](const Index &idx) { - return named_indices.find(idx) == named_indices.end(); - }; - - // index factory to generate anonymous indices - IndexFactory idxfac(is_anonymous_index, 1); - - // make the graph - Graph graph = create_graph(&named_indices); - // graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); - - if (Logger::instance().canonicalize_input_graph) { - std::wcout << "Input graph for canonicalization:\n"; - graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); - } - - // canonize the graph - bliss::Stats stats; - graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); - const unsigned int *canonize_perm = - graph.bliss_graph->canonical_form(stats, nullptr, nullptr); - - if (Logger::instance().canonicalize_dot) { - std::wcout << "Canonicalization permutation:\n"; - for (std::size_t i = 0; i < graph.vertex_labels.size(); ++i) { - std::wcout << i << " -> " << canonize_perm[i] << "\n"; - } - std::wcout << "Canonicalized graph:\n"; - bliss::Graph *cgraph = graph.bliss_graph->permute(canonize_perm); - cgraph->write_dot(std::wcout, {}, true); - auto cvlabels = permute(graph.vertex_labels, canonize_perm); - std::wcout << "with our labels:\n"; - cgraph->write_dot(std::wcout, cvlabels); - delete cgraph; - } - - container::map tensor_idx_to_vertex; - container::map> - tensor_idx_to_particle_order; - container::map index_idx_to_vertex; - std::size_t tensor_idx = 0; - std::size_t index_idx = 0; - - for (std::size_t vertex = 0; vertex < graph.vertex_types.size(); ++vertex) { - switch (graph.vertex_types[vertex]) { - case VertexType::Index: - index_idx_to_vertex[index_idx] = vertex; - index_idx++; - break; - case VertexType::TensorBraKet: { - assert(tensor_idx > 0); - const std::size_t base_tensor_idx = tensor_idx - 1; - assert(symmetry(*tensors_.at(base_tensor_idx)) == Symmetry::nonsymm); - tensor_idx_to_particle_order[base_tensor_idx].push_back( - canonize_perm[vertex]); - break; - } - case VertexType::TensorCore: - tensor_idx_to_vertex[tensor_idx] = vertex; - tensor_idx++; - break; - case VertexType::TensorBra: - case VertexType::TensorKet: - case VertexType::TensorAux: - case VertexType::SPBundle: - break; - } - } - - assert(index_idx_to_vertex.size() == edges_.size()); - assert(tensor_idx_to_vertex.size() == tensors_.size()); - assert(tensor_idx_to_particle_order.size() <= tensors_.size()); - - // order_to_indices(index_order); - for (auto ¤t : tensor_idx_to_particle_order) { - order_to_indices(current.second); - } - - container::map idxrepl; - // Sort edges so that their order corresponds to the order of indices in the - // canonical graph - // Use this ordering to relabel anonymous indices - const auto index_sorter = [&index_idx_to_vertex, &canonize_perm]( - std::size_t lhs_idx, std::size_t rhs_idx) { - const std::size_t lhs_vertex = index_idx_to_vertex.at(lhs_idx); - const std::size_t rhs_vertex = index_idx_to_vertex.at(rhs_idx); - - return canonize_perm[lhs_vertex] < canonize_perm[rhs_vertex]; - }; - - sort_via_indices(edges_, index_sorter); - - for (const Edge ¤t : edges_) { - const Index &idx = current.idx(); - - if (!is_anonymous_index(idx)) { - continue; - } - - idxrepl.insert(std::make_pair(idx, idxfac.make(idx))); - } - - apply_index_replacements(tensors_, idxrepl); - - // Perform particle-1,2-swaps as indicated by the graph canonization - for (std::size_t i = 0; i < tensors_.size(); ++i) { - AbstractTensor &tensor = *tensors_[i]; - const std::size_t num_particles = - std::min(bra_rank(tensor), ket_rank(tensor)); - - auto it = tensor_idx_to_particle_order.find(i); - if (it == tensor_idx_to_particle_order.end()) { - assert(num_particles == 0 || symmetry(*tensors_[i]) != Symmetry::nonsymm); - continue; - } - - const auto &particle_order = it->second; - auto bra_indices = tensor._bra(); - auto ket_indices = tensor._ket(); - - assert(num_particles == particle_order.size()); - - // Swap indices column-wise - idxrepl.clear(); - for (std::size_t col = 0; col < num_particles; ++col) { - if (particle_order[col] == col) { - continue; - } - - idxrepl.insert( - std::make_pair(bra_indices[col], bra_indices[particle_order[col]])); - idxrepl.insert( - std::make_pair(ket_indices[col], ket_indices[particle_order[col]])); - } - - if (!idxrepl.empty()) { - apply_index_replacements(tensor, idxrepl); - } - } - - // Bring tensors into canonical order (analogously to how we reordered - // indices), but ensure to respect commutativity! - const auto tensor_sorter = [this, &canonize_perm, &tensor_idx_to_vertex]( - std::size_t lhs_idx, std::size_t rhs_idx) { - const AbstractTensor &lhs = *tensors_[lhs_idx]; - const AbstractTensor &rhs = *tensors_[rhs_idx]; - - if (!tensors_commute(lhs, rhs)) { - return false; - } - - const std::size_t lhs_vertex = tensor_idx_to_vertex.at(lhs_idx); - const std::size_t rhs_vertex = tensor_idx_to_vertex.at(rhs_idx); - - // Commuting tensors are sorted based on their canonical order which is - // given by the order of the corresponding vertices in the canonical graph - // representation - return canonize_perm[lhs_vertex] < canonize_perm[rhs_vertex]; - }; - - sort_via_indices(tensors_, tensor_sorter); - - // The tensor reordering and index relabelling made the current set of edges - // invalid - edges_.clear(); - have_edges_ = false; - - if (Logger::instance().canonicalize) { - std::wcout << "TensorNetwork::canonicalize_graph: tensors after " - "canonicalization\n"; - size_t cnt = 0; - ranges::for_each(tensors_, [&](const auto &t) { - std::wcout << "tensor " << cnt++ << ": " << to_latex(*t) << std::endl; - }); - } -} - ExprPtr TensorNetwork::canonicalize( const container::vector &cardinal_tensor_labels, bool fast, const named_indices_t *named_indices_ptr) { + ExprPtr canon_biproduct = ex(1); + container::svector idx_terminals_sorted; // to avoid memory allocs + if (Logger::instance().canonicalize) { std::wcout << "TensorNetwork::canonicalize(" << (fast ? "fast" : "slow") << "): input tensors\n"; @@ -519,31 +51,53 @@ ExprPtr TensorNetwork::canonicalize( std::wcout << std::endl; } - if (!have_edges_) { - init_edges(); - } - - // initialize named_indices by default to all external indices - const auto &named_indices = - named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; - - if (!fast) { - // The graph-based canonization is required in call cases in which there are - // indistinguishable tensors present in the expression. Their order and - // indexing can only be determined via this rigorous canonization. - canonicalize_graph(named_indices); - } - - // Ensure each individual tensor is written in the way that its tensor - // block (== order of index spaces) is canonical - ExprPtr byproduct = canonicalize_individual_tensor_blocks(named_indices); - - CanonicalTensorCompare tensor_sorter( - cardinal_tensor_labels, true); - - std::stable_sort(tensors_.begin(), tensors_.end(), tensor_sorter); - - init_edges(); + // - resort tensors (this cannot be done in Product::canonicalize since that + // requires analysis of commutativity ... here we are dealing with tensors + // only and are free to reorder) + using std::begin; + using std::end; + bubble_sort( + begin(tensors_), end(tensors_), + [&cardinal_tensor_labels](const auto &first_ptr, const auto &second_ptr) { + const auto &first = *first_ptr; + const auto &second = *second_ptr; + // grab base label if adjoint label is present + auto base_label = [](const auto &t) { + if (label(t).back() == adjoint_label) { + return label(t).substr(0, label(t).size() - 1); + } else { + return label(t); + } + }; + // tensors commute if their colors are different or either one of them + // is a c-number + if ((color(first) != color(second)) || is_cnumber(first) || + is_cnumber(second)) { + const auto cardinal_tensor_labels_end = end(cardinal_tensor_labels); + const auto first_cardinal_it = + std::find(begin(cardinal_tensor_labels), + end(cardinal_tensor_labels), base_label(first)); + const auto second_cardinal_it = + std::find(begin(cardinal_tensor_labels), + end(cardinal_tensor_labels), base_label(second)); + const auto first_is_cardinal = + first_cardinal_it != cardinal_tensor_labels_end; + const auto second_is_cardinal = + second_cardinal_it != cardinal_tensor_labels_end; + if (first_is_cardinal && second_is_cardinal) { + if (first_cardinal_it == second_cardinal_it) + return first < second; + else + return first_cardinal_it < second_cardinal_it; + } else if (first_is_cardinal) + return true; + else if (second_is_cardinal) + return false; + else // neither is cardinal + return first < second; + } else + return false; + }); if (Logger::instance().canonicalize) { std::wcout << "TensorNetwork::canonicalize(" << (fast ? "fast" : "slow") @@ -554,6 +108,13 @@ ExprPtr TensorNetwork::canonicalize( }); } + if (edges_.empty()) init_edges(); + + // initialize named_indices by default to all external indices (these HAVE + // been computed in init_edges) + const auto &named_indices = + named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; + // helpers to filter named ("external" in traditional use case) / anonymous // ("internal" in traditional use case) auto is_named_index = [&](const Index &idx) { @@ -562,39 +123,235 @@ ExprPtr TensorNetwork::canonicalize( auto is_anonymous_index = [&](const Index &idx) { return named_indices.find(idx) == named_indices.end(); }; + auto namedness = [&](const Index &idx) { + return is_named_index(idx) ? 1 : 0; + }; - // Sort edges based on the order of the tensors they connect - std::stable_sort(edges_.begin(), edges_.end(), - [&is_named_index](const Edge &lhs, const Edge &rhs) { - // Sort first by index's character (named < anonymous), - // then by Edge (not by Index's full label) ... this - // automatically puts named indices first - const bool lhs_is_named = is_named_index(lhs.idx()); - const bool rhs_is_named = is_named_index(rhs.idx()); - - if (lhs_is_named == rhs_is_named) { - return lhs < rhs; - } else { - return lhs_is_named; - } - }); + // fast and slow canonizations produce index replacements for anonymous + // indices + idxrepl_.clear(); + auto &idxrepl = idxrepl_; // index factory to generate anonymous indices - // -> start reindexing anonymous indices from 1 - IndexFactory idxfac(is_anonymous_index, 1); - - container::map idxrepl; - - // Use the new order of edges as the canonical order of indices and relabel - // accordingly (but only anonymous indices, of course) - for (std::size_t i = named_indices.size(); i < edges_.size(); ++i) { - const Index &index = edges_[i].idx(); - assert(is_anonymous_index(index)); - Index replacement = idxfac.make(index); - idxrepl.emplace(std::make_pair(index, replacement)); - } + IndexFactory idxfac(is_anonymous_index, + 1); // start reindexing anonymous indices from 1 - // Done computing canonical index replacement list + if (!fast) { + // - canonize indices + // - canonize tensors using canonical list of indices + // Algorithm sketch: + // - to canonize indices make a graph whose vertices are the indices as + // well as the tensors and their terminals. + // - Indices with protoindices are connected to their protoindices, + // either directly or (if protoindices are symmetric) via a protoindex + // vertex. + // - Indices are colored by their space, which in general encodes also + // the space of the protoindices. + // - An anti/symmetric n-body tensor has 2 terminals, each connected to + // each other + to n index vertices. + // - A nonsymmetric n-body tensor has n terminals, each connected to 2 + // indices and 1 tensor vertex which is connected to all n terminal + // indices. + // - tensor vertices are colored by the label+rank+symmetry of the + // tensor; terminal vertices are colored by the color of its tensor, + // with the color of symm/antisymm terminals augmented by the + // terminal's type (bra/ket). + // - canonize the graph + + auto permute = [](const auto &vector, auto &&perm) { + using std::size; + auto sz = size(vector); + std::decay_t pvector(sz); + for (size_t i = 0; i != sz; ++i) pvector[perm[i]] = vector[i]; + return pvector; + }; + + // make the graph + auto [graph, vlabels, vcolors, vtypes] = make_bliss_graph(&named_indices); + // graph->write_dot(std::wcout, vlabels); + + // canonize the graph + bliss::Stats stats; + graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + const unsigned int *cl = graph->canonical_form(stats, nullptr, nullptr); + + if (Logger::instance().canonicalize_dot) { + bliss::Graph *cgraph = graph->permute(cl); + auto cvlabels = permute(vlabels, cl); + cgraph->write_dot(std::wcout, cvlabels); + delete cgraph; + } + + // make anonymous index replacement list + { + // for each color make a replacement list for bringing the indices to + // the canonical order + container::set colors; + container::multimap> + color2idx; // maps color to the ordinals of the corresponding + // indices in edges_ + their canonical ordinals + // collect colors and anonymous indices sorted by colors + size_t idx_cnt = 0; + for (auto &&ttpair : edges_) { + auto color = vcolors[idx_cnt]; + if (colors.find(color) == colors.end()) colors.insert(color); + if (is_anonymous_index(ttpair.idx())) { + color2idx.emplace(color, std::make_pair(idx_cnt, cl[idx_cnt])); + } + ++idx_cnt; + } + // for each color sort anonymous indices by canonical order + container::svector> + idx_can; // canonically-ordered list of {index ordinal in edges_, + // canonical ordinal} + for (auto &&color : colors) { + auto beg = color2idx.lower_bound(color); + auto end = color2idx.upper_bound(color); + const auto sz = end - beg; + if (sz > 1) { + idx_can.resize(sz); + size_t cnt = 0; + for (auto it = beg; it != end; ++it, ++cnt) { + idx_can[cnt] = it->second; + } + using std::begin; + using std::end; + std::sort(begin(idx_can), end(idx_can), + [](const std::pair &a, + const std::pair &b) { + return a.second < b.second; + }); + // make a replacement list by generating new indices in canonical + // order + for (auto &&p : idx_can) { + const auto &idx = (edges_.begin() + p.first)->idx(); + idxrepl.emplace(std::make_pair(idx, idxfac.make(idx))); + } + } else if (sz == 1) { // no need for resorting of colors with 1 index + // only, but still need to replace the index + const auto edge_it = edges_.begin() + beg->second.first; + const auto &idx = edge_it->idx(); + idxrepl.emplace(std::make_pair(idx, idxfac.make(idx))); + } + // sz == 0 is possible since some colors in colors refer to tensors + } + } // index repl + + // reorder *commuting* tensors_ to canonical order (defined by the core + // indices) + { + decltype(tensors_) tensors_canonized(tensors_.size(), nullptr); + + container::set colors; + container::multimap> + color2idx; // maps color to the ordinals of the corresponding + // tensors in tensors_ + their canonical ordinals given by cl + // collect colors and tensors sorted by colors + size_t vtx_cnt = 0; + size_t tensor_cnt = 0; + for (auto &&type : vtypes) { + if (type == VertexType::TensorCore) { // tensor core vertices were + // created in the order of their + // appearance in tensors_ + auto color = vcolors[vtx_cnt]; + if (colors.find(color) == colors.end()) colors.insert(color); + color2idx.emplace(color, std::make_pair(tensor_cnt, cl[vtx_cnt])); + ++tensor_cnt; + } + ++vtx_cnt; + } + // for each color sort tensors by canonical order + // this assumes that tensors of different colors always commute + // (reasonable) this only reorders tensors if they are c-numbers! + container::svector> + ord_can; // canonically-ordered list of {ordinal in tensors_, + // canonical ordinal} + container::svector + ord_orig; // originaly-ordered list of {canonical ordinal} + for (auto &&color : colors) { + auto beg = color2idx.lower_bound(color); + auto end = color2idx.upper_bound(color); + const auto sz = end - beg; + assert(sz > 0); + if (sz > 1) { + // all tensors of same color are c-numbers or all q-numbers ... + // inspect the first to determine the type + const bool cnumber = is_cnumber(*(tensors_.at(beg->second.first))); + + ord_can.resize(sz); + ord_orig.resize(sz); + + size_t cnt = 0; + for (auto it = beg; it != end; ++it, ++cnt) { + ord_can[cnt] = it->second; + ord_orig[cnt] = it->second.first; + // assert that all tensors of same color are all c-numbers or all + // q-numbers + assert(cnumber == is_cnumber(*(tensors_.at(ord_orig[cnt])))); + } + using std::begin; + using std::end; + if (cnumber) // only resort if these are cnumbers + std::sort(begin(ord_can), end(ord_can), + [](const std::pair &a, + const std::pair &b) { + return a.second < b.second; + }); + std::sort(begin(ord_orig), end(ord_orig)); + // write (potentially reordered) tensors to tensors_canonized + for (std::ptrdiff_t t = 0; t != sz; ++t) { + tensors_canonized.at(ord_orig[t]) = tensors_.at(ord_can[t].first); + } + + } else { // sz = 1 + auto tidx = beg->second.first; + tensors_canonized.at(tidx) = tensors_.at(tidx); + } + } // colors + + // commit the canonically-ordered list of tensors to tensors_ + using std::swap; + swap(tensors_canonized, tensors_); + + } // tensors canonizing + + } else { // fast approach uses heuristic canonization + // simpler approach that will work perfectly as long as tensors are + // distinguishable + + // - reindex anonymous indices using ordering of Edge as the + // canonical definition of the anonymous index list + { + // resort edges_ first by index's character (named named, 0 -> anonymous + const auto n2 = namedness(edge2.idx()); + if (n1 == n2) + return edge1 < edge2; + else + return n1 > n2; + }); + + // make index replacement list for anonymous indices only + const auto num_named_indices = named_indices.size(); + std::for_each( + begin(idx_terminals_sorted) + num_named_indices, + end(idx_terminals_sorted), + [&idxrepl, &idxfac, &is_anonymous_index](const auto &terminals) { + const auto &idx = terminals.idx(); + assert(is_anonymous_index( + idx)); // should only encounter anonymous indices here + idxrepl.emplace(std::make_pair(idx, idxfac.make(idx))); + }); + } + } // canonical index replacement list computed if (Logger::instance().canonicalize) { for (const auto &idxpair : idxrepl) { @@ -604,368 +361,356 @@ ExprPtr TensorNetwork::canonicalize( } } - apply_index_replacements(tensors_, idxrepl); - - byproduct *= canonicalize_individual_tensors(named_indices); +#ifndef NDEBUG + // assert that tensors' indices are not tagged since going to tag indices + { + for (const auto &tensor : tensors_) { + assert(ranges::none_of(braket(*tensor), [](const Index &idx) { + return idx.tag().has_value(); + })); + } + } +#endif + bool pass_mutated = false; + [[maybe_unused]] bool mutated = false; + do { + pass_mutated = false; + for (auto &tensor : tensors_) { + pass_mutated |= transform_indices(*tensor, idxrepl); + } + mutated |= pass_mutated; + } while (pass_mutated); // transform till stops changing - // We assume that re-indexing did not change the canonical order of tensors - assert(std::is_sorted(tensors_.begin(), tensors_.end(), tensor_sorter)); - // However, in order to produce the most aesthetically pleasing result, we now - // reorder tensors based on the regular AbstractTensor::operator<, which takes - // the explicit index labelling of tensors into account. - tensor_sorter.set_blocks_only(false); - std::stable_sort(tensors_.begin(), tensors_.end(), tensor_sorter); + // untag transformed indices (if any) + { + for (auto &tensor : tensors_) { + reset_tags(*tensor); + } + } - have_edges_ = false; + // - re-canonize tensors + { + // override the default canonicalizer + DefaultTensorCanonicalizer default_tensor_canonizer(named_indices); + for (auto &tensor : tensors_) { + auto nondefault_canonizer_ptr = + TensorCanonicalizer::nondefault_instance_ptr(tensor->_label()); + TensorCanonicalizer *tensor_canonizer = + nondefault_canonizer_ptr ? nondefault_canonizer_ptr.get() + : &default_tensor_canonizer; + auto bp = tensor_canonizer->apply(*tensor); + if (bp) *canon_biproduct *= *bp; + } + } + edges_.clear(); + ext_indices_.clear(); - assert(byproduct->is()); - return (byproduct->as().value() == 1) ? nullptr : byproduct; + assert(canon_biproduct->is()); + return (canon_biproduct->as().value() == 1) ? nullptr + : canon_biproduct; } -TensorNetwork::Graph TensorNetwork::create_graph( +std::tuple, std::vector, + std::vector, + std::vector> +TensorNetwork::make_bliss_graph( const named_indices_t *named_indices_ptr) const { - assert(have_edges_); + // must call init_edges() prior to calling this + if (edges_.empty()) { + init_edges(); + } - // initialize named_indices by default to all external indices - const named_indices_t &named_indices = + // initialize named_indices by default to all external indices (these HAVE + // been computed in init_edges) + const auto &named_indices = named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; - // Colors in the range [ 0, 3 * max_rank + named_indices.size() ) are - // reserved: Up to max_rank colors can be used for bra indices Up to - // max_rank colors can be used for ket indices Up to max_rank colors can be - // used for auxiliary indices Every named index is identified by a unique - // color - constexpr std::size_t named_idx_color_start = 3 * max_rank; - const std::size_t max_reserved_color = - named_idx_color_start + named_indices.size() - 1; - - // core, bra, ket, auxiliary and optionally (for non-symmetric tensors) a - // particle vertex - constexpr std::size_t num_tensor_components = 5; - // results - Graph graph; - // We know that at the very least all indices and all tensors will yield - // vertex representations - std::size_t vertex_count_estimate = - edges_.size() + num_tensor_components * tensors_.size(); - graph.vertex_labels.reserve(vertex_count_estimate); - graph.vertex_colors.reserve(vertex_count_estimate); - graph.vertex_types.reserve(vertex_count_estimate); - - using proto_bundle_t = - std::decay_t().proto_indices())>; - container::map proto_bundles; - - container::map tensor_vertices; - tensor_vertices.reserve(tensors_.size()); - - container::vector> edges; - edges.reserve(edges_.size() + tensors_.size()); - - // Add vertices for tensors - for (std::size_t tensor_idx = 0; tensor_idx < tensors_.size(); ++tensor_idx) { - assert(tensor_vertices.find(tensor_idx) == tensor_vertices.end()); - assert(tensors_.at(tensor_idx)); - const AbstractTensor &tensor = *tensors_.at(tensor_idx); - - // Tensor core - std::wstring_view tensor_label = label(tensor); - graph.vertex_labels.emplace_back(tensor_label); - graph.vertex_types.emplace_back(VertexType::TensorCore); - const std::size_t tensor_color = - hash::value(tensor_label) + max_reserved_color; - assert(tensor_color > max_reserved_color); - graph.vertex_colors.push_back(tensor_color); - - const std::size_t tensor_vertex = graph.vertex_labels.size() - 1; - tensor_vertices.insert(std::make_pair(tensor_idx, tensor_vertex)); - - // Create vertices to group indices - const Symmetry tensor_sym = symmetry(tensor); - if (tensor_sym == Symmetry::nonsymm) { - // Create separate vertices for every index - // Additionally, we need particle vertices to group indices that belong to - // the same particle (are in the same "column" in the usual tensor - // notation) - const std::size_t num_particle_vertices = - std::min(bra_rank(tensor), ket_rank(tensor)); - const bool is_part_symm = - particle_symmetry(tensor) == ParticleSymmetry::symm; - // TODO: How to handle BraKetSymmetry::conjugate? - const bool is_braket_symm = - braket_symmetry(tensor) == BraKetSymmetry::symm; - - for (std::size_t i = 0; i < num_particle_vertices; ++i) { - graph.vertex_labels.emplace_back(L"p_" + std::to_wstring(i + 1)); - graph.vertex_types.push_back(VertexType::TensorBraKet); - graph.vertex_colors.push_back(tensor_color); - edges.push_back( - std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); - } - - assert(bra_rank(tensor) <= max_rank); - for (std::size_t i = 0; i < bra_rank(tensor); ++i) { - const bool is_unpaired_idx = i >= num_particle_vertices; - const bool color_idx = is_unpaired_idx || !is_part_symm; - - graph.vertex_labels.emplace_back(L"bra_" + std::to_wstring(i + 1)); - graph.vertex_types.push_back(VertexType::TensorBra); - graph.vertex_colors.push_back(color_idx ? i : 0); - - const std::size_t connect_vertex = - tensor_vertex + (is_unpaired_idx ? 0 : (i + 1)); - edges.push_back( - std::make_pair(connect_vertex, graph.vertex_labels.size() - 1)); - } - - assert(ket_rank(tensor) <= max_rank); - for (std::size_t i = 0; i < ket_rank(tensor); ++i) { - const bool is_unpaired_idx = i >= num_particle_vertices; - const bool color_idx = is_unpaired_idx || !is_part_symm; - - graph.vertex_labels.emplace_back(L"ket_" + std::to_wstring(i + 1)); - graph.vertex_types.push_back(VertexType::TensorKet); - graph.vertex_colors.push_back((color_idx ? i : 0) + - (is_braket_symm ? 0 : max_rank)); + std::shared_ptr graph; + std::vector vertex_labels( + edges_.size()); // the size will be updated + std::vector vertex_color(edges_.size(), + 0); // the size will be updated + std::vector vertex_type( + edges_.size()); // the size will be updated + + // N.B. Colors [0, 2 max rank + named_indices.size()) are reserved: + // 0 - the bra vertex (for particle 0, if bra is nonsymm, or for the entire + // bra, if (anti)symm) 1 - the bra vertex for particle 1, if bra is nonsymm + // ... + // max_rank - the ket vertex (for particle 0, if particle-asymmetric, or for + // the entire ket, if particle-symmetric) max_rank+1 - the ket vertex for + // particle 1, if particle-asymmetric + // ... + // 2 max_rank - first named index + // 2 max_rank + 1 - second named index + // ... + // N.B. For braket-symmetric tensors the ket vertices use the same indices as + // the bra vertices + auto nonreserved_color = [&named_indices](size_t color) -> bool { + return color >= 2 * max_rank + named_indices.size(); + }; - const std::size_t connect_vertex = - tensor_vertex + (is_unpaired_idx ? 0 : (i + 1)); - edges.push_back( - std::make_pair(connect_vertex, graph.vertex_labels.size() - 1)); - } + // compute # of vertices + size_t nv = 0; + size_t index_cnt = 0; + size_t spbundle_cnt = 0; + // first count vertex indices ... the only complication are symmetric + // protoindex bundles this will keep track of unique symmetric protoindex + // bundles + using protoindex_bundle_t = + std::decay_t().proto_indices())>; + container::set symmetric_protoindex_bundles; + const size_t spbundle_vertex_offset = + edges_.size(); // where spbundle vertices will start + ranges::for_each(edges_, [&](const Edge &ttpair) { + const Index &idx = ttpair.idx(); + ++nv; // each index is a vertex + vertex_labels.at(index_cnt) = idx.to_latex(); + vertex_type.at(index_cnt) = VertexType::Index; + // assign color: named indices use reserved colors + const auto named_index_it = named_indices.find(idx); + if (named_index_it == + named_indices.end()) { // anonymous index? use Index::color + const auto idx_color = idx.color(); + assert(nonreserved_color(idx_color)); + vertex_color.at(index_cnt) = idx_color; } else { - // Shared set of bra/ket vertices for all indices - std::wstring suffix = tensor_sym == Symmetry::symm ? L"_s" : L"_a"; - - const std::size_t bra_color = 0; - graph.vertex_labels.push_back(L"bra" + suffix); - graph.vertex_types.push_back(VertexType::TensorBra); - graph.vertex_colors.push_back(bra_color); - edges.push_back( - std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); - - // TODO: figure out how to handle BraKetSymmetry::conjugate - const std::size_t ket_color = - braket_symmetry(tensor) == BraKetSymmetry::symm - ? bra_color - : bra_color + max_rank; - graph.vertex_labels.push_back(L"ket" + suffix); - graph.vertex_types.push_back(VertexType::TensorKet); - graph.vertex_colors.push_back(ket_color); - edges.push_back( - std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); - } - - // TODO: handle aux indices permutation symmetries once they are supported - // for now, auxiliary indices are considered to always be asymmetric - for (std::size_t i = 0; i < aux_rank(tensor); ++i) { - graph.vertex_labels.emplace_back(L"aux_" + std::to_wstring(i + 1)); - graph.vertex_types.push_back(VertexType::TensorAux); - graph.vertex_colors.push_back(2 * max_rank + i); - edges.push_back( - std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + const auto named_index_rank = named_index_it - named_indices.begin(); + vertex_color.at(index_cnt) = 2 * max_rank + named_index_rank; + } + // each symmetric proto index bundle will have a vertex ... + // for now only store the unique protoindex bundles in + // symmetric_protoindex_bundles, then commit their data to + // vertex_{labels,type,color} later + if (idx.has_proto_indices()) { + assert(idx.symmetric_proto_indices()); // only symmetric protoindices are + // supported right now + if (symmetric_protoindex_bundles.find(idx.proto_indices()) == + symmetric_protoindex_bundles + .end()) { // new bundle? make a vertex for it + auto graph = symmetric_protoindex_bundles.insert(idx.proto_indices()); + assert(graph.second); + } } - } - - // Now add all indices (edges) to the graph - for (const Edge ¤t_edge : edges_) { - const Index &index = current_edge.idx(); - graph.vertex_labels.push_back(std::wstring(index.label())); - graph.vertex_types.push_back(VertexType::Index); - - // Assign index color - std::size_t idx_color; - auto named_idx_iter = named_indices.find(index); - if (named_idx_iter == named_indices.end()) { - // This is an anonymous index - idx_color = index.color(); - assert(idx_color > max_reserved_color); - } else { - idx_color = static_cast( - std::distance(named_indices.begin(), named_idx_iter)); - idx_color += named_idx_color_start; + index_cnt++; + }); + // now commit protoindex bundle metadata + ranges::for_each(symmetric_protoindex_bundles, [&](const auto &bundle) { + ++nv; // each symmetric protoindex bundle is a vertex + std::wstring spbundle_label = L"{"; + for (auto &&pi : bundle) { + spbundle_label += pi.to_latex(); + } + spbundle_label += L"}"; + vertex_labels.push_back(spbundle_label); + vertex_type.push_back(VertexType::SPBundle); + const auto idx_proto_indices_color = Index::proto_indices_color(bundle); + assert(nonreserved_color(idx_proto_indices_color)); + vertex_color.push_back(idx_proto_indices_color); + spbundle_cnt++; + }); + // now account for vertex representation of tensors + size_t tensor_cnt = 0; + // this will map to tensor index to the first (core) vertex in its + // representation + container::svector tensor_vertex_offset(tensors_.size()); + ranges::for_each(tensors_, [&](const auto &t) { + tensor_vertex_offset.at(tensor_cnt) = nv; + // each tensor has a core vertex (to be colored by its label) + ++nv; + const auto tlabel = label(*t); + vertex_labels.emplace_back(tlabel); + vertex_type.emplace_back(VertexType::TensorCore); + const auto t_color = hash::value(tlabel); + static_assert(sizeof(t_color) == sizeof(unsigned long int)); + assert(nonreserved_color(t_color)); + vertex_color.push_back(t_color); + // symmetric/antisymmetric tensors are represented by 3 more vertices: + // - bra + // - ket + // - braket (connecting bra and ket to the core) + auto &tref = *t; + if (symmetry(tref) != Symmetry::nonsymm) { + nv += 3; + vertex_labels.push_back( + std::wstring(L"bra") + to_wstring(bra_rank(tref)) + + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); + vertex_type.push_back(VertexType::TensorBra); + vertex_color.push_back(0); + vertex_labels.push_back( + std::wstring(L"ket") + to_wstring(ket_rank(tref)) + + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); + vertex_type.push_back(VertexType::TensorKet); + vertex_color.push_back( + braket_symmetry(tref) == BraKetSymmetry::symm ? 0 : max_rank); + vertex_labels.push_back( + std::wstring(L"bk") + + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); + vertex_type.push_back(VertexType::TensorBraKet); + vertex_color.push_back(t_color); + } + // nonsymmetric tensors are represented by 3*rank more vertices (with rank = + // max(bra_rank(),ket_rank()) + else { + const auto rank = std::max(bra_rank(tref), ket_rank(tref)); + assert(rank <= max_rank); + for (size_t p = 0; p != rank; ++p) { + nv += 3; + auto pstr = to_wstring(p + 1); + vertex_labels.push_back(std::wstring(L"bra") + pstr); + vertex_type.push_back(VertexType::TensorBra); + const bool t_is_particle_symmetric = + particle_symmetry(tref) == ParticleSymmetry::nonsymm; + const auto bra_color = t_is_particle_symmetric ? p : 0; + vertex_color.push_back(bra_color); + vertex_labels.push_back(std::wstring(L"ket") + pstr); + vertex_type.push_back(VertexType::TensorKet); + vertex_color.push_back(braket_symmetry(tref) == BraKetSymmetry::symm + ? bra_color + : bra_color + max_rank); + vertex_labels.push_back(std::wstring(L"bk") + pstr); + vertex_type.push_back(VertexType::TensorBraKet); + vertex_color.push_back(t_color); + } } - graph.vertex_colors.push_back(idx_color); - - const std::size_t index_vertex = graph.vertex_labels.size() - 1; - - // Handle proto indices - if (index.has_proto_indices()) { - // For now we assume that all proto indices are symmetric - assert(index.symmetric_proto_indices()); - - std::size_t proto_vertex; - if (auto it = proto_bundles.find(index.proto_indices()); - it != proto_bundles.end()) { - proto_vertex = it->second; - } else { - // Create a new vertex for this bundle of proto indices - std::wstring spbundle_label = L"{"; - for (const Index &proto : index.proto_indices()) { - spbundle_label += proto.label(); - } - spbundle_label += L"}"; - graph.vertex_labels.push_back(std::move(spbundle_label)); - graph.vertex_types.push_back(VertexType::SPBundle); - const std::size_t bundle_color = - Index::proto_indices_color(index.proto_indices()) + - max_reserved_color; - assert(bundle_color); - graph.vertex_colors.push_back(bundle_color); - - proto_vertex = graph.vertex_labels.size() - 1; - proto_bundles.insert( - std::make_pair(index.proto_indices(), proto_vertex)); + ++tensor_cnt; + }); + + // allocate graph + graph = std::make_shared(nv); + + // add edges + // - each index's degree <= 2 + # of protoindex terminals + index_cnt = 0; + ranges::for_each(edges_, [&](const Edge &ttpair) { + for (int t = 0; t != 2; ++t) { + const auto terminal_index = t == 0 ? ttpair.first() : ttpair.second(); + const auto terminal_position = + t == 0 ? ttpair.first_position() : ttpair.second_position(); + if (terminal_index) { + const auto tidx = std::abs(terminal_index) - 1; + const auto ttpos = terminal_position; + const bool bra = terminal_index > 0; + const size_t braket_vertex_index = tensor_vertex_offset[tidx] + + /* core */ 1 + 3 * ttpos + + (bra ? 0 : 1); + graph->add_edge(index_cnt, braket_vertex_index); } - - edges.push_back(std::make_pair(index_vertex, proto_vertex)); } - - // Connect index to the tensor(s) it is connected to - for (std::size_t i = 0; i < current_edge.vertex_count(); ++i) { - assert(i <= 1); - const Vertex &vertex = - i == 0 ? current_edge.first_vertex() : current_edge.second_vertex(); - - assert(tensor_vertices.find(vertex.getTerminalIndex()) != - tensor_vertices.end()); - const std::size_t tensor_vertex = - tensor_vertices.find(vertex.getTerminalIndex())->second; - - // Store an edge connecting the index vertex to the corresponding tensor - // vertex - const bool tensor_is_nonsymm = - vertex.getTerminalSymmetry() == Symmetry::nonsymm; - const AbstractTensor &tensor = *tensors_[vertex.getTerminalIndex()]; - std::size_t offset; - if (tensor_is_nonsymm) { - // We have to find the correct vertex to connect this index to (for - // non-symmetric tensors each index has its dedicated "group" vertex) - - // Move off the tensor core's vertex - offset = 1; - // Move past the explicit particle vertices - offset += std::min(bra_rank(tensor), ket_rank(tensor)); - - if (vertex.getOrigin() > Origin::Bra) { - offset += bra_rank(tensor); - } - - offset += vertex.getIndexSlot(); + // if this index has symmetric protoindex bundles + if (ttpair.idx().has_proto_indices()) { + if (ttpair.idx().symmetric_proto_indices()) { + assert( + symmetric_protoindex_bundles.find(ttpair.idx().proto_indices()) != + symmetric_protoindex_bundles.end()); + const auto spbundle_idx = + symmetric_protoindex_bundles.find(ttpair.idx().proto_indices()) - + symmetric_protoindex_bundles.begin(); + graph->add_edge(index_cnt, spbundle_vertex_offset + spbundle_idx); } else { - static_assert(static_cast(Origin::Bra) == 1); - static_assert(static_cast(Origin::Ket) == 2); - static_assert(static_cast(Origin::Aux) == 3); - offset = static_cast(vertex.getOrigin()); - } - - if (vertex.getOrigin() > Origin::Ket) { - offset += ket_rank(tensor); + abort(); // nonsymmetric proto indices not supported yet } - - const std::size_t tensor_component_vertex = tensor_vertex + offset; - - assert(tensor_component_vertex < graph.vertex_labels.size()); - edges.push_back(std::make_pair(index_vertex, tensor_component_vertex)); } - } - - assert(graph.vertex_labels.size() == graph.vertex_colors.size()); - assert(graph.vertex_labels.size() == graph.vertex_types.size()); - - // Create the actual BLISS graph object - graph.bliss_graph = - std::make_unique(graph.vertex_labels.size()); - - for (const std::pair ¤t_edge : edges) { - graph.bliss_graph->add_edge(current_edge.first, current_edge.second); - } + ++index_cnt; + }); + // - link up proto indices, if any ... only symmetric protobundles are + // supported now + spbundle_cnt = spbundle_vertex_offset; + ranges::for_each(symmetric_protoindex_bundles, [&graph, this, &spbundle_cnt]( + const auto &bundle) { + for (auto &&proto_index : bundle) { + assert(edges_.find(proto_index.full_label()) != edges_.end()); + const auto proto_index_vertex = + edges_.find(proto_index.full_label()) - edges_.begin(); + graph->add_edge(spbundle_cnt, proto_index_vertex); + } + ++spbundle_cnt; + }); + // - link up tensors + tensor_cnt = 0; + ranges::for_each( + tensors_, [&graph, &tensor_cnt, &tensor_vertex_offset](const auto &t) { + const auto vertex_offset = tensor_vertex_offset.at(tensor_cnt); + // for each braket terminal linker + auto &tref = *t; + const size_t nbk = symmetry(tref) == Symmetry::nonsymm + ? std::max(bra_rank(tref), ket_rank(tref)) + : 1; + for (size_t bk = 1; bk <= nbk; ++bk) { + const int bk_vertex = vertex_offset + 3 * bk; + graph->add_edge(vertex_offset, bk_vertex); // core + graph->add_edge(bk_vertex - 2, bk_vertex); // bra + graph->add_edge(bk_vertex - 1, bk_vertex); // ket + } + ++tensor_cnt; + }); // compress vertex colors to 32 bits, as required by Bliss, by hashing - for (std::size_t vertex = 0; vertex < graph.vertex_colors.size(); ++vertex) { - auto color = graph.vertex_colors[vertex]; - static_assert(sizeof(color) == 8); - - color = (~color) + (color << 18); // color = (color << 18) - color - 1; - color = color ^ (color >> 31); - color = color * 21; // color = (color + (color << 2)) + (color << 4); - color = color ^ (color >> 11); - color = color + (color << 6); - color = color ^ (color >> 22); - - graph.bliss_graph->change_color(vertex, static_cast(color)); + size_t v_cnt = 0; + for (auto &&color : vertex_color) { + auto hash6432shift = [](size_t key) { + static_assert(sizeof(key) == 8); + key = (~key) + (key << 18); // key = (key << 18) - key - 1; + key = key ^ (key >> 31); + key = key * 21; // key = (key + (key << 2)) + (key << 4); + key = key ^ (key >> 11); + key = key + (key << 6); + key = key ^ (key >> 22); + return static_cast(key); + }; + graph->change_color(v_cnt, hash6432shift(color)); + ++v_cnt; } - return graph; + return {graph, vertex_labels, vertex_color, vertex_type}; } -void TensorNetwork::init_edges() { - edges_.clear(); - ext_indices_.clear(); +void TensorNetwork::init_edges() const { + if (have_edges_) return; - auto idx_insert = [this](const Index &idx, Vertex vertex) { + auto idx_insert = [this](const Index &idx, int tensor_idx, int pos) { if (Logger::instance().tensor_network) { std::wcout << "TensorNetwork::init_edges: idx=" << to_latex(idx) - << " attached to tensor " << vertex.getTerminalIndex() << " (" - << vertex.getOrigin() << ") at position " - << vertex.getIndexSlot() - << " (sym: " << to_wstring(vertex.getTerminalSymmetry()) << ")" + << " attached to tensor " << std::abs(tensor_idx) << "'s " + << ((tensor_idx > 0) ? "bra" : "ket") << " at position " << pos << std::endl; } - - auto it = std::find_if(edges_.begin(), edges_.end(), - FullLabelIndexLocator(idx.full_label())); - if (it == edges_.end()) { - edges_.emplace_back(std::move(vertex), idx); + decltype(edges_) &indices = this->edges_; + auto it = indices.find(idx.full_label()); + if (it == indices.end()) { + indices.emplace(Edge(tensor_idx, &idx, pos)); } else { - it->connect_to(std::move(vertex)); + const_cast(*it).connect_to(tensor_idx, pos); } }; - for (std::size_t tensor_idx = 0; tensor_idx < tensors_.size(); ++tensor_idx) { - assert(tensors_[tensor_idx]); - const AbstractTensor &tensor = *tensors_[tensor_idx]; - const Symmetry tensor_symm = symmetry(tensor); - - auto bra_indices = tensor._bra(); - for (std::size_t index_idx = 0; index_idx < bra_indices.size(); - ++index_idx) { - idx_insert(bra_indices[index_idx], - Vertex(Origin::Bra, tensor_idx, index_idx, tensor_symm)); - } - - auto ket_indices = tensor._ket(); - for (std::size_t index_idx = 0; index_idx < ket_indices.size(); - ++index_idx) { - idx_insert(ket_indices[index_idx], - Vertex(Origin::Ket, tensor_idx, index_idx, tensor_symm)); + int t_idx = 1; + for (auto &&t : tensors_) { + const auto t_is_nonsymm = symmetry(*t) == Symmetry::nonsymm; + size_t cnt = 0; + for (const Index &idx : t->_bra()) { + idx_insert(idx, t_idx, t_is_nonsymm ? cnt : 0); + ++cnt; } - - auto aux_indices = tensor._aux(); - for (std::size_t index_idx = 0; index_idx < aux_indices.size(); - ++index_idx) { - // Note: for the time being we don't have a way of expressing - // permutational symmetry of auxiliary indices so we just assume there is - // no such symmetry - idx_insert(aux_indices[index_idx], - Vertex(Origin::Aux, tensor_idx, index_idx, Symmetry::nonsymm)); + cnt = 0; + for (const Index &idx : t->_ket()) { + idx_insert(idx, -t_idx, t_is_nonsymm ? cnt : 0); + ++cnt; } + ++t_idx; } // extract external indices - for (const Edge ¤t : edges_) { - assert(current.vertex_count() > 0); - if (current.vertex_count() == 1) { - // External index (== Edge only connected to a single vertex in the - // network) + for (const auto &terminals : edges_) { + assert(terminals.size() != 0); + if (terminals.size() == 1) { // external? if (Logger::instance().tensor_network) { - std::wcout << "idx " << to_latex(current.idx()) << " is external" + std::wcout << "idx " << to_latex(terminals.idx()) << " is external" << std::endl; } - - bool inserted = ext_indices_.insert(current.idx()).second; - assert(inserted); + auto insertion_result = ext_indices_.emplace(terminals.idx()); + assert(insertion_result.second); } } @@ -976,36 +721,4 @@ container::svector> TensorNetwork::factorize() { abort(); // not yet implemented } -ExprPtr TensorNetwork::canonicalize_individual_tensor_blocks( - const named_indices_t &named_indices) { - return do_individual_canonicalization( - TensorBlockCanonicalizer(named_indices)); -} - -ExprPtr TensorNetwork::canonicalize_individual_tensors( - const named_indices_t &named_indices) { - return do_individual_canonicalization( - DefaultTensorCanonicalizer(named_indices)); -} - -ExprPtr TensorNetwork::do_individual_canonicalization( - const TensorCanonicalizer &canonicalizer) { - ExprPtr byproduct = ex(1); - - for (auto &tensor : tensors_) { - auto nondefault_canonizer_ptr = - TensorCanonicalizer::nondefault_instance_ptr(tensor->_label()); - const TensorCanonicalizer &tensor_canonizer = - nondefault_canonizer_ptr ? *nondefault_canonizer_ptr : canonicalizer; - - auto bp = canonicalizer.apply(*tensor); - - if (bp) { - byproduct *= bp; - } - } - - return byproduct; -} - } // namespace sequant diff --git a/SeQuant/core/tensor_network.hpp b/SeQuant/core/tensor_network.hpp index b60073727..620010f9b 100644 --- a/SeQuant/core/tensor_network.hpp +++ b/SeQuant/core/tensor_network.hpp @@ -9,17 +9,16 @@ #include #include #include +#include #include #include #include -#include #include #include #include #include #include -#include #include #include @@ -38,36 +37,8 @@ namespace sequant { /// graph), with Tensor objects represented by one or more vertices. class TensorNetwork { public: - friend class TensorNetworkAccessor; - constexpr static size_t max_rank = 256; - enum class Origin { - Bra = 1, - Ket, - Aux, - }; - - class Vertex { - public: - Vertex(Origin origin, std::size_t terminal_idx, std::size_t index_slot, - Symmetry terminal_symm); - - Origin getOrigin() const; - std::size_t getTerminalIndex() const; - std::size_t getIndexSlot() const; - Symmetry getTerminalSymmetry() const; - - bool operator<(const Vertex &rhs) const; - bool operator==(const Vertex &rhs) const; - - private: - Origin origin; - std::size_t terminal_idx; - std::size_t index_slot; - Symmetry terminal_symm; - }; - // clang-format off /// @brief Edge in a TensorNetwork = the Index annotating it + a pair of indices to identify which Tensor terminals it's connected to @@ -84,108 +55,102 @@ class TensorNetwork { class Edge { public: Edge() = default; - explicit Edge(Vertex vertex) : first(std::move(vertex)), second() {} - Edge(Vertex vertex, Index index) - : first(std::move(vertex)), second(), index(std::move(index)) {} - - Edge &connect_to(Vertex vertex) { - assert(!second.has_value()); - - if (!first.has_value()) { - // unconnected Edge - first = std::move(vertex); - } else { - second = std::move(vertex); - if (second < first) { - // Ensure first <= second - std::swap(first, second); - } + explicit Edge(int terminal_idx, int position = 0) + : first_(0), second_(terminal_idx), second_position_(position) {} + Edge(int terminal_idx, const Index *idxptr, int position = 0) + : first_(0), + second_(terminal_idx), + idxptr_(idxptr), + second_position_(position) {} + // Edge(const Edge&) = default; + // Edge(Edge&&) = default; + // Edge& operator=(const Edge&) = default; + // Edge& operator=(Edge&&) = default; + + Edge &connect_to(int terminal_idx, int position = 0) { + assert(first_ == 0 || second_ == 0); // not connected yet + assert(terminal_idx != 0); // valid idx + if (second_ == 0) { // unconnected Edge + second_ = terminal_idx; + second_position_ = position; + } else if (std::abs(second_) < + std::abs(terminal_idx)) { // connected to 2 Edges? ensure + // first_ < second_ + assert(first_ == 0); // there are slots left + first_ = second_; + first_position_ = second_position_; + second_ = terminal_idx; + second_position_ = position; + } else { // put into first slot + first_ = terminal_idx; + first_position_ = position; } return *this; } bool operator<(const Edge &other) const { - if (vertex_count() != other.vertex_count()) { - // Ensure external indices (edges that are only attached to a tensor on - // one side) always come before internal ones - return vertex_count() < other.vertex_count(); - } - - if (!(first == other.first)) { - return first < other.first; - } - - if (second < other.second) { - return second < other.second; + if (std::abs(first_) == std::abs(other.first_)) { + if (first_position_ == other.first_position_) { + if (std::abs(second_) == std::abs(other.second_)) { + return second_position_ < other.second_position_; + } else { + return std::abs(second_) < std::abs(other.second_); + } + } else { + return first_position_ < other.first_position_; + } + } else { + return std::abs(first_) < std::abs(other.first_); } - - return index.space() < other.index.space(); } bool operator==(const Edge &other) const { - return first == other.first && second == other.second; + return std::abs(first_) == std::abs(other.first_) && + std::abs(second_) == std::abs(other.second_) && + first_position_ == other.first_position_ && + second_position_ == other.second_position_; } - const Vertex &first_vertex() const { - assert(first.has_value()); - return first.value(); - } - const Vertex &second_vertex() const { - assert(second.has_value()); - return second.value(); - } + auto first() const { return first_; } + auto second() const { return second_; } + auto first_position() const { return first_position_; } + auto second_position() const { return second_position_; } /// @return the number of attached terminals (0, 1, or 2) - std::size_t vertex_count() const { - return second.has_value() ? 2 : (first.has_value() ? 1 : 0); - } + auto size() const { return (first_ != 0) ? 2 : ((second_ != 0) ? 1 : 0); } - const Index &idx() const { return index; } + const Index &idx() const { + assert(idxptr_ != nullptr); + return *idxptr_; + } private: - std::optional first; - std::optional second; - Index index; + // if only connected to 1 terminal, this is always 0 + // otherwise first_ <= second_ + int first_ = 0; + int second_ = 0; + const Index *idxptr_ = nullptr; + int first_position_ = 0; + int second_position_ = 0; }; - struct Graph { - std::unique_ptr bliss_graph; - std::vector vertex_labels; - std::vector vertex_colors; - std::vector vertex_types; - - Graph() = default; + using VertexType = sequant::VertexType; - std::size_t vertex_to_index_idx(std::size_t vertex) const; - std::size_t vertex_to_tensor_idx(std::size_t vertex) const; - }; - - TensorNetwork(const Expr &expr) { - if (expr.size() > 0) { - for (const ExprPtr &subexpr : expr) { - add_expr(*subexpr); + public: + /// @throw std::logic_error if exprptr_range contains a non-tensor + /// @note uses RTTI + template + TensorNetwork(ExprPtrRange &exprptr_range) { + for (auto &&ex : exprptr_range) { + auto t = std::dynamic_pointer_cast(ex); + if (t) { + tensors_.emplace_back(t); + } else { + throw std::logic_error( + "TensorNetwork::TensorNetwork: non-tensors in the given expression " + "range"); } - } else { - add_expr(expr); - } - - init_edges(); - } - - TensorNetwork(const ExprPtr &expr) : TensorNetwork(*expr) {} - - template < - typename ExprPtrRange, - typename = std::enable_if_t && - !std::is_base_of_v>> - TensorNetwork(const ExprPtrRange &exprptr_range) { - static_assert( - std::is_base_of_v); - for (const ExprPtr ¤t : exprptr_range) { - add_expr(*current); } - - init_edges(); } /// @return const reference to the sequence of tensors @@ -202,7 +167,7 @@ class TensorNetwork { /// @param named_indices specifies the indices that cannot be renamed, i.e. /// their labels are meaningful; default is nullptr, which results in external /// indices treated as named indices - /// @return byproduct of canonicalization (e.g. phase); if none, returns + /// @return biproduct of canonicalization (e.g. phase); if none, returns /// nullptr ExprPtr canonicalize( const container::vector &cardinal_tensor_labels = {}, @@ -219,29 +184,74 @@ class TensorNetwork { /// @c (((T3*T1)*T2)*T0) . container::svector> factorize(); + private: + // source tensors and indices + container::svector tensors_; + + struct FullLabelCompare { + using is_transparent = void; + bool operator()(const Edge &first, const Edge &second) const { + return first.idx().full_label() < second.idx().full_label(); + } + bool operator()(const Edge &first, const std::wstring_view &second) const { + return first.idx().full_label() < second; + } + bool operator()(const std::wstring_view &first, const Edge &second) const { + return first < second.idx().full_label(); + } + }; + // Index -> Edge, sorted by full label + mutable container::set edges_; + // set to true by init_edges(); + mutable bool have_edges_ = false; + // ext indices do not connect tensors + // sorted by *label* (not full label) of the corresponding value (Index) + // this ensures that proto indices are not considered and all internal indices + // have unique labels (not full labels) + mutable named_indices_t ext_indices_; + + // replacements of anonymous indices produced by the last call to + // canonicalize() + container::map idxrepl_; + + /// initializes edges_ and ext_indices_ + void init_edges() const; + + public: /// accessor for the Edge object sequence /// @return const reference to the sequence container of Edge objects, sorted /// by their Index's full label /// @sa Edge const auto &edges() const { - assert(have_edges_); + init_edges(); return edges_; } /// @brief Returns a range of external indices, i.e. those indices that do not /// connect tensors + + /// @note The external indices are sorted by *label* (not full label) of the + /// corresponding value (Index) const auto &ext_indices() const { - assert(have_edges_); + if (edges_.empty()) init_edges(); return ext_indices_; } + /// accessor for the list of anonymous index replacements performed by the + /// last call to canonicalize() + /// @return replacements of anonymous indices performed by the last call to + /// canonicalize() + const auto &idxrepl() const { return idxrepl_; }; + + public: /// @brief converts the network into a Bliss graph whose vertices are indices /// and tensor vertex representations /// @param[in] named_indices pointer to the set of named indices (ordinarily, /// this includes all external indices); /// default is nullptr, which means use all external indices for /// named indices - /// @return The created Graph object + /// @return {shared_ptr to Graph, vector of vertex labels, vector of vertex + /// colors, vector of vertex types} /// @note Rules for constructing the graph: /// - Indices with protoindices are connected to their protoindices, @@ -258,71 +268,11 @@ class TensorNetwork { /// tensor; terminal vertices are colored by the color of its tensor, /// with the color of symm/antisymm terminals augmented by the /// terminal's type (bra/ket). - Graph create_graph(const named_indices_t *named_indices = nullptr) const; - - private: - // source tensors and indices - container::svector tensors_; - - container::vector edges_; - bool have_edges_ = false; - // ext indices do not connect tensors - // sorted by *label* (not full label) of the corresponding value (Index) - // this ensures that proto indices are not considered and all internal indices - // have unique labels (not full labels) - named_indices_t ext_indices_; - - /// initializes edges_ and ext_indices_ - void init_edges(); - - /// Canonicalizes the network graph representation - /// Note: The explicit order of tensors and labelling of indices - /// remains undefined. - void canonicalize_graph(const named_indices_t &named_indices); - - /// Canonicalizes every individual tensor for itself, taking into account only - /// tensor blocks - /// @returns The byproduct of the canonicalizations - ExprPtr canonicalize_individual_tensor_blocks( - const named_indices_t &named_indices); - - /// Canonicalizes every individual tensor for itself - /// @returns The byproduct of the canonicalizations - ExprPtr canonicalize_individual_tensors(const named_indices_t &named_indices); - - ExprPtr do_individual_canonicalization( - const TensorCanonicalizer &canonicalizer); - - void add_expr(const Expr &expr) { - ExprPtr clone = expr.clone(); - - auto tensor_ptr = std::dynamic_pointer_cast(clone); - if (!tensor_ptr) { - throw std::invalid_argument( - "TensorNetwork::TensorNetwork: tried to add non-tensor to network"); - } - - tensors_.push_back(std::move(tensor_ptr)); - } + std::tuple, std::vector, + std::vector, std::vector> + make_bliss_graph(const named_indices_t *named_indices = nullptr) const; }; -template -std::basic_ostream &operator<<( - std::basic_ostream &stream, TensorNetwork::Origin origin) { - switch (origin) { - case TensorNetwork::Origin::Bra: - stream << "Bra"; - break; - case TensorNetwork::Origin::Ket: - stream << "Ket"; - break; - case TensorNetwork::Origin::Aux: - stream << "Aux"; - break; - } - return stream; -} - } // namespace sequant #endif // SEQUANT_TENSOR_NETWORK_H diff --git a/SeQuant/core/tensor_network_v2.cpp b/SeQuant/core/tensor_network_v2.cpp new file mode 100644 index 000000000..632a0b34b --- /dev/null +++ b/SeQuant/core/tensor_network_v2.cpp @@ -0,0 +1,1027 @@ +// +// Created by Eduard Valeyev on 2019-02-26. +// + +#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 { + +struct FullLabelIndexLocator { + std::wstring_view label; + FullLabelIndexLocator(std::wstring_view label) : label(std::move(label)) {} + + bool operator()(const TensorNetworkV2::Edge &edge) const { + return edge.idx().full_label() == label; + } + + bool operator()(const Index &idx) const { return idx.full_label() == label; } +}; + +bool tensors_commute(const AbstractTensor &lhs, const AbstractTensor &rhs) { + // tensors commute if their colors are different or either one of them + // is a c-number + return !(color(lhs) == color(rhs) && !is_cnumber(lhs) && !is_cnumber(rhs)); +} + +struct TensorBlockCompare { + bool operator()(const AbstractTensor &lhs, const AbstractTensor &rhs) const { + if (label(lhs) != label(rhs)) { + return label(lhs) < label(rhs); + } + + if (bra_rank(lhs) != bra_rank(rhs)) { + return bra_rank(lhs) < bra_rank(rhs); + } + if (ket_rank(lhs) != ket_rank(rhs)) { + return ket_rank(lhs) < ket_rank(rhs); + } + if (aux_rank(lhs) != aux_rank(rhs)) { + return aux_rank(lhs) < aux_rank(rhs); + } + + auto lhs_indices = indices(lhs); + auto rhs_indices = indices(rhs); + + for (auto lhs_it = lhs_indices.begin(), rhs_it = rhs_indices.begin(); + lhs_it != lhs_indices.end() && rhs_it != rhs_indices.end(); + ++lhs_it, ++rhs_it) { + if (lhs_it->space() != rhs_it->space()) { + return lhs_it->space() < rhs_it->space(); + } + } + + // Tensors are identical + return false; + } +}; + +/// Compares tensors based on their label and orders them according to the order +/// of the given cardinal tensor labels. If two tensors can't be discriminated +/// via their label, they are compared based on regular +/// AbstractTensor::operator< or based on their tensor block (the spaces of +/// their indices) - depending on the configuration. If this doesn't +/// discriminate the tensors, they are considered equal +template +struct CanonicalTensorCompare { + const CardinalLabels &labels; + bool blocks_only; + + CanonicalTensorCompare(const CardinalLabels &labels, bool blocks_only) + : labels(labels), blocks_only(blocks_only) {} + + void set_blocks_only(bool blocks_only) { this->blocks_only = blocks_only; } + + bool operator()(const AbstractTensorPtr &lhs_ptr, + const AbstractTensorPtr &rhs_ptr) const { + assert(lhs_ptr); + assert(rhs_ptr); + const AbstractTensor &lhs = *lhs_ptr; + const AbstractTensor &rhs = *rhs_ptr; + + if (!tensors_commute(lhs, rhs)) { + return false; + } + + const auto get_label = [](const auto &t) { + if (label(t).back() == adjoint_label) { + // grab base label if adjoint label is present + return label(t).substr(0, label(t).size() - 1); + } + return label(t); + }; + + const auto lhs_it = std::find(labels.begin(), labels.end(), get_label(lhs)); + const auto rhs_it = std::find(labels.begin(), labels.end(), get_label(rhs)); + + if (lhs_it != rhs_it) { + // At least one of the tensors is a cardinal one + // -> Order by the occurrence in the cardinal label list + return std::distance(labels.begin(), lhs_it) < + std::distance(labels.begin(), rhs_it); + } + + // Either both are the same cardinal tensor or none is a cardinal tensor + if (blocks_only) { + TensorBlockCompare cmp; + return cmp(lhs, rhs); + } else { + return lhs < rhs; + } + } +}; + +TensorNetworkV2::Vertex::Vertex(Origin origin, std::size_t terminal_idx, + std::size_t index_slot, Symmetry terminal_symm) + : origin(origin), + terminal_idx(terminal_idx), + index_slot(index_slot), + terminal_symm(terminal_symm) {} + +TensorNetworkV2::Origin TensorNetworkV2::Vertex::getOrigin() const { + return origin; +} + +std::size_t TensorNetworkV2::Vertex::getTerminalIndex() const { + return terminal_idx; +} + +std::size_t TensorNetworkV2::Vertex::getIndexSlot() const { return index_slot; } + +Symmetry TensorNetworkV2::Vertex::getTerminalSymmetry() const { + return terminal_symm; +} + +bool TensorNetworkV2::Vertex::operator<(const Vertex &rhs) const { + if (terminal_idx != rhs.terminal_idx) { + return terminal_idx < rhs.terminal_idx; + } + + // Both vertices belong to same tensor -> they must have same symmetry + assert(terminal_symm == rhs.terminal_symm); + + if (origin != rhs.origin) { + return origin < rhs.origin; + } + + // We only take the index slot into account for non-symmetric tensors + if (terminal_symm == Symmetry::nonsymm) { + return index_slot < rhs.index_slot; + } else { + return false; + } +} + +bool TensorNetworkV2::Vertex::operator==(const Vertex &rhs) const { + // Slot position is only taken into account for non_symmetric tensors + const std::size_t lhs_slot = + (terminal_symm == Symmetry::nonsymm) * index_slot; + const std::size_t rhs_slot = + (rhs.terminal_symm == Symmetry::nonsymm) * rhs.index_slot; + + assert(terminal_idx != rhs.terminal_idx || + terminal_symm == rhs.terminal_symm); + + return terminal_idx == rhs.terminal_idx && lhs_slot == rhs_slot && + origin == rhs.origin; +} + +std::size_t TensorNetworkV2::Graph::vertex_to_index_idx( + std::size_t vertex) const { + assert(vertex_types.at(vertex) == VertexType::Index); + + std::size_t index_idx = 0; + for (std::size_t i = 0; i <= vertex; ++i) { + if (vertex_types[i] == VertexType::Index) { + ++index_idx; + } + } + + assert(index_idx > 0); + + return index_idx - 1; +} + +std::size_t TensorNetworkV2::Graph::vertex_to_tensor_idx( + std::size_t vertex) const { + assert(vertex_types.at(vertex) == VertexType::TensorCore); + + std::size_t tensor_idx = 0; + for (std::size_t i = 0; i <= vertex; ++i) { + if (vertex_types[i] == VertexType::TensorCore) { + ++tensor_idx; + } + } + + assert(tensor_idx > 0); + + return tensor_idx - 1; +} + +template +auto permute(const ArrayLike &vector, const Permutation &perm) { + using std::size; + auto sz = size(vector); + std::decay_t pvector(sz); + for (size_t i = 0; i != sz; ++i) pvector[perm[i]] = vector[i]; + return pvector; +} + +template +void apply_index_replacements(AbstractTensor &tensor, + const ReplacementMap &replacements) { +#ifndef NDEBUG + // assert that tensors' indices are not tagged since going to tag indices + assert(ranges::none_of( + indices(tensor), [](const Index &idx) { return idx.tag().has_value(); })); +#endif + + bool pass_mutated; + do { + pass_mutated = transform_indices(tensor, replacements); + } while (pass_mutated); // transform till stops changing + + reset_tags(tensor); +} + +template +void apply_index_replacements(ArrayLike &tensors, + const ReplacementMap &replacements) { + for (auto &tensor : tensors) { + apply_index_replacements(*tensor, replacements); + } +} + +template +void order_to_indices(Container &container) { + std::vector indices; + indices.resize(container.size()); + std::iota(indices.begin(), indices.end(), 0); + + std::sort(indices.begin(), indices.end(), + [&container](std::size_t lhs, std::size_t rhs) { + return container[lhs] < container[rhs]; + }); + // Overwrite container contents with indices + std::copy(indices.begin(), indices.end(), container.begin()); +} + +template +void sort_via_indices(Container &container, const Comparator &cmp) { + std::vector indices; + indices.resize(container.size()); + std::iota(indices.begin(), indices.end(), 0); + + if constexpr (stable) { + std::stable_sort(indices.begin(), indices.end(), cmp); + } else { + std::sort(indices.begin(), indices.end(), cmp); + } + + // Bring elements in container into the order given by indices + // (the association is container[k] = container[indices[k]]) + // -> implementation from https://stackoverflow.com/a/838789 + + for (std::size_t i = 0; i < container.size(); ++i) { + if (indices[i] == i) { + // This element is already where it is supposed to be + continue; + } + + // Find the offset of the index pointing to i + // -> since we are going to change the content of the vector at position i, + // we have to update the index-mapping referencing i to point to the new + // location of the element that used to be at position i + std::size_t k; + for (k = i + 1; k < container.size(); ++k) { + if (indices[k] == i) { + break; + } + } + std::swap(container[i], container[indices[i]]); + std::swap(indices[i], indices[k]); + } +} + +void TensorNetworkV2::canonicalize_graph(const named_indices_t &named_indices) { + if (Logger::instance().canonicalize) { + std::wcout << "TensorNetworkV2::canonicalize_graph: input tensors\n"; + size_t cnt = 0; + ranges::for_each(tensors_, [&](const auto &t) { + std::wcout << "tensor " << cnt++ << ": " << to_latex(*t) << std::endl; + }); + std::wcout << std::endl; + } + + if (!have_edges_) { + init_edges(); + } + + const auto is_anonymous_index = [named_indices](const Index &idx) { + return named_indices.find(idx) == named_indices.end(); + }; + + // index factory to generate anonymous indices + IndexFactory idxfac(is_anonymous_index, 1); + + // make the graph + Graph graph = create_graph(&named_indices); + // graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); + + if (Logger::instance().canonicalize_input_graph) { + std::wcout << "Input graph for canonicalization:\n"; + graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); + } + + // canonize the graph + bliss::Stats stats; + graph.bliss_graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + const unsigned int *canonize_perm = + graph.bliss_graph->canonical_form(stats, nullptr, nullptr); + + if (Logger::instance().canonicalize_dot) { + std::wcout << "Canonicalization permutation:\n"; + for (std::size_t i = 0; i < graph.vertex_labels.size(); ++i) { + std::wcout << i << " -> " << canonize_perm[i] << "\n"; + } + std::wcout << "Canonicalized graph:\n"; + bliss::Graph *cgraph = graph.bliss_graph->permute(canonize_perm); + cgraph->write_dot(std::wcout, {}, true); + auto cvlabels = permute(graph.vertex_labels, canonize_perm); + std::wcout << "with our labels:\n"; + cgraph->write_dot(std::wcout, cvlabels); + delete cgraph; + } + + container::map tensor_idx_to_vertex; + container::map> + tensor_idx_to_particle_order; + container::map index_idx_to_vertex; + std::size_t tensor_idx = 0; + std::size_t index_idx = 0; + + for (std::size_t vertex = 0; vertex < graph.vertex_types.size(); ++vertex) { + switch (graph.vertex_types[vertex]) { + case VertexType::Index: + index_idx_to_vertex[index_idx] = vertex; + index_idx++; + break; + case VertexType::TensorBraKet: { + assert(tensor_idx > 0); + const std::size_t base_tensor_idx = tensor_idx - 1; + assert(symmetry(*tensors_.at(base_tensor_idx)) == Symmetry::nonsymm); + tensor_idx_to_particle_order[base_tensor_idx].push_back( + canonize_perm[vertex]); + break; + } + case VertexType::TensorCore: + tensor_idx_to_vertex[tensor_idx] = vertex; + tensor_idx++; + break; + case VertexType::TensorBra: + case VertexType::TensorKet: + case VertexType::TensorAux: + case VertexType::SPBundle: + break; + } + } + + assert(index_idx_to_vertex.size() == edges_.size()); + assert(tensor_idx_to_vertex.size() == tensors_.size()); + assert(tensor_idx_to_particle_order.size() <= tensors_.size()); + + // order_to_indices(index_order); + for (auto ¤t : tensor_idx_to_particle_order) { + order_to_indices(current.second); + } + + container::map idxrepl; + // Sort edges so that their order corresponds to the order of indices in the + // canonical graph + // Use this ordering to relabel anonymous indices + const auto index_sorter = [&index_idx_to_vertex, &canonize_perm]( + std::size_t lhs_idx, std::size_t rhs_idx) { + const std::size_t lhs_vertex = index_idx_to_vertex.at(lhs_idx); + const std::size_t rhs_vertex = index_idx_to_vertex.at(rhs_idx); + + return canonize_perm[lhs_vertex] < canonize_perm[rhs_vertex]; + }; + + sort_via_indices(edges_, index_sorter); + + for (const Edge ¤t : edges_) { + const Index &idx = current.idx(); + + if (!is_anonymous_index(idx)) { + continue; + } + + idxrepl.insert(std::make_pair(idx, idxfac.make(idx))); + } + + if (Logger::instance().canonicalize) { + for (const auto &idxpair : idxrepl) { + std::wcout << "TensorNetworkV2::canonicalize_graph: replacing " + << to_latex(idxpair.first) << " with " + << to_latex(idxpair.second) << std::endl; + } + } + + apply_index_replacements(tensors_, idxrepl); + + // Perform particle-1,2-swaps as indicated by the graph canonization + for (std::size_t i = 0; i < tensors_.size(); ++i) { + AbstractTensor &tensor = *tensors_[i]; + const std::size_t num_particles = + std::min(bra_rank(tensor), ket_rank(tensor)); + + auto it = tensor_idx_to_particle_order.find(i); + if (it == tensor_idx_to_particle_order.end()) { + assert(num_particles == 0 || symmetry(*tensors_[i]) != Symmetry::nonsymm); + continue; + } + + const auto &particle_order = it->second; + auto bra_indices = tensor._bra(); + auto ket_indices = tensor._ket(); + + assert(num_particles == particle_order.size()); + + // Swap indices column-wise + idxrepl.clear(); + for (std::size_t col = 0; col < num_particles; ++col) { + if (particle_order[col] == col) { + continue; + } + + idxrepl.insert( + std::make_pair(bra_indices[col], bra_indices[particle_order[col]])); + idxrepl.insert( + std::make_pair(ket_indices[col], ket_indices[particle_order[col]])); + } + + if (!idxrepl.empty()) { + if (Logger::instance().canonicalize) { + for (const auto &idxpair : idxrepl) { + std::wcout + << "TensorNetworkV2::canonicalize_graph: permuting particles in " + << to_latex(tensor) << " by replacing " << to_latex(idxpair.first) + << " with " << to_latex(idxpair.second) << std::endl; + } + } + apply_index_replacements(tensor, idxrepl); + } + } + + // Bring tensors into canonical order (analogously to how we reordered + // indices), but ensure to respect commutativity! + const auto tensor_sorter = [this, &canonize_perm, &tensor_idx_to_vertex]( + std::size_t lhs_idx, std::size_t rhs_idx) { + const AbstractTensor &lhs = *tensors_[lhs_idx]; + const AbstractTensor &rhs = *tensors_[rhs_idx]; + + if (!tensors_commute(lhs, rhs)) { + return false; + } + + const std::size_t lhs_vertex = tensor_idx_to_vertex.at(lhs_idx); + const std::size_t rhs_vertex = tensor_idx_to_vertex.at(rhs_idx); + + // Commuting tensors are sorted based on their canonical order which is + // given by the order of the corresponding vertices in the canonical graph + // representation + return canonize_perm[lhs_vertex] < canonize_perm[rhs_vertex]; + }; + + sort_via_indices(tensors_, tensor_sorter); + + // The tensor reordering and index relabelling made the current set of edges + // invalid + edges_.clear(); + have_edges_ = false; + + if (Logger::instance().canonicalize) { + std::wcout << "TensorNetworkV2::canonicalize_graph: tensors after " + "canonicalization\n"; + size_t cnt = 0; + ranges::for_each(tensors_, [&](const auto &t) { + std::wcout << "tensor " << cnt++ << ": " << to_latex(*t) << std::endl; + }); + } +} + +ExprPtr TensorNetworkV2::canonicalize( + const container::vector &cardinal_tensor_labels, bool fast, + const named_indices_t *named_indices_ptr) { + if (Logger::instance().canonicalize) { + std::wcout << "TensorNetworkV2::canonicalize(" << (fast ? "fast" : "slow") + << "): input tensors\n"; + size_t cnt = 0; + ranges::for_each(tensors_, [&](const auto &t) { + std::wcout << "tensor " << cnt++ << ": " << to_latex(*t) << std::endl; + }); + std::wcout << "cardinal_tensor_labels = "; + ranges::for_each(cardinal_tensor_labels, + [](auto &&i) { std::wcout << i << L" "; }); + std::wcout << std::endl; + } + + if (!have_edges_) { + init_edges(); + } + + // initialize named_indices by default to all external indices + const auto &named_indices = + named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; + + if (!fast) { + // The graph-based canonization is required in call cases in which there are + // indistinguishable tensors present in the expression. Their order and + // indexing can only be determined via this rigorous canonization. + canonicalize_graph(named_indices); + } + + // Ensure each individual tensor is written in the way that its tensor + // block (== order of index spaces) is canonical + ExprPtr byproduct = canonicalize_individual_tensor_blocks(named_indices); + + CanonicalTensorCompare tensor_sorter( + cardinal_tensor_labels, true); + + std::stable_sort(tensors_.begin(), tensors_.end(), tensor_sorter); + + init_edges(); + + if (Logger::instance().canonicalize) { + std::wcout << "TensorNetworkV2::canonicalize(" << (fast ? "fast" : "slow") + << "): tensors after initial sort\n"; + size_t cnt = 0; + ranges::for_each(tensors_, [&](const auto &t) { + std::wcout << "tensor " << cnt++ << ": " << to_latex(*t) << std::endl; + }); + } + + // helpers to filter named ("external" in traditional use case) / anonymous + // ("internal" in traditional use case) + auto is_named_index = [&](const Index &idx) { + return named_indices.find(idx) != named_indices.end(); + }; + auto is_anonymous_index = [&](const Index &idx) { + return named_indices.find(idx) == named_indices.end(); + }; + + // Sort edges based on the order of the tensors they connect + std::stable_sort(edges_.begin(), edges_.end(), + [&is_named_index](const Edge &lhs, const Edge &rhs) { + // Sort first by index's character (named < anonymous), + // then by Edge (not by Index's full label) ... this + // automatically puts named indices first + const bool lhs_is_named = is_named_index(lhs.idx()); + const bool rhs_is_named = is_named_index(rhs.idx()); + + if (lhs_is_named == rhs_is_named) { + return lhs < rhs; + } else { + return lhs_is_named; + } + }); + + // index factory to generate anonymous indices + // -> start reindexing anonymous indices from 1 + IndexFactory idxfac(is_anonymous_index, 1); + + container::map idxrepl; + + // Use the new order of edges as the canonical order of indices and relabel + // accordingly (but only anonymous indices, of course) + for (std::size_t i = named_indices.size(); i < edges_.size(); ++i) { + const Index &index = edges_[i].idx(); + assert(is_anonymous_index(index)); + Index replacement = idxfac.make(index); + idxrepl.emplace(std::make_pair(index, replacement)); + } + + // Done computing canonical index replacement list + + if (Logger::instance().canonicalize) { + for (const auto &idxpair : idxrepl) { + std::wcout << "TensorNetworkV2::canonicalize(" << (fast ? "fast" : "slow") + << "): replacing " << to_latex(idxpair.first) << " with " + << to_latex(idxpair.second) << std::endl; + } + } + + apply_index_replacements(tensors_, idxrepl); + + byproduct *= canonicalize_individual_tensors(named_indices); + + // We assume that re-indexing did not change the canonical order of tensors + assert(std::is_sorted(tensors_.begin(), tensors_.end(), tensor_sorter)); + // However, in order to produce the most aesthetically pleasing result, we now + // reorder tensors based on the regular AbstractTensor::operator<, which takes + // the explicit index labelling of tensors into account. + tensor_sorter.set_blocks_only(false); + std::stable_sort(tensors_.begin(), tensors_.end(), tensor_sorter); + + have_edges_ = false; + + assert(byproduct->is()); + return (byproduct->as().value() == 1) ? nullptr : byproduct; +} + +TensorNetworkV2::Graph TensorNetworkV2::create_graph( + const named_indices_t *named_indices_ptr) const { + assert(have_edges_); + + // initialize named_indices by default to all external indices + const named_indices_t &named_indices = + named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; + + // Colors in the range [ 0, 3 * max_rank + named_indices.size() ) are + // reserved: Up to max_rank colors can be used for bra indices Up to + // max_rank colors can be used for ket indices Up to max_rank colors can be + // used for auxiliary indices Every named index is identified by a unique + // color + constexpr std::size_t named_idx_color_start = 3 * max_rank; + const std::size_t max_reserved_color = + named_idx_color_start + named_indices.size() - 1; + + // core, bra, ket, auxiliary and optionally (for non-symmetric tensors) a + // particle vertex + constexpr std::size_t num_tensor_components = 5; + + // results + Graph graph; + // We know that at the very least all indices and all tensors will yield + // vertex representations + std::size_t vertex_count_estimate = + edges_.size() + num_tensor_components * tensors_.size(); + graph.vertex_labels.reserve(vertex_count_estimate); + graph.vertex_colors.reserve(vertex_count_estimate); + graph.vertex_types.reserve(vertex_count_estimate); + + using proto_bundle_t = + std::decay_t().proto_indices())>; + container::map proto_bundles; + + container::map tensor_vertices; + tensor_vertices.reserve(tensors_.size()); + + container::vector> edges; + edges.reserve(edges_.size() + tensors_.size()); + + // Add vertices for tensors + for (std::size_t tensor_idx = 0; tensor_idx < tensors_.size(); ++tensor_idx) { + assert(tensor_vertices.find(tensor_idx) == tensor_vertices.end()); + assert(tensors_.at(tensor_idx)); + const AbstractTensor &tensor = *tensors_.at(tensor_idx); + + // Tensor core + std::wstring_view tensor_label = label(tensor); + graph.vertex_labels.emplace_back(tensor_label); + graph.vertex_types.emplace_back(VertexType::TensorCore); + const std::size_t tensor_color = + hash::value(tensor_label) + max_reserved_color; + assert(tensor_color > max_reserved_color); + graph.vertex_colors.push_back(tensor_color); + + const std::size_t tensor_vertex = graph.vertex_labels.size() - 1; + tensor_vertices.insert(std::make_pair(tensor_idx, tensor_vertex)); + + // Create vertices to group indices + const Symmetry tensor_sym = symmetry(tensor); + if (tensor_sym == Symmetry::nonsymm) { + // Create separate vertices for every index + // Additionally, we need particle vertices to group indices that belong to + // the same particle (are in the same "column" in the usual tensor + // notation) + const std::size_t num_particle_vertices = + std::min(bra_rank(tensor), ket_rank(tensor)); + const bool is_part_symm = + particle_symmetry(tensor) == ParticleSymmetry::symm; + // TODO: How to handle BraKetSymmetry::conjugate? + const bool is_braket_symm = + braket_symmetry(tensor) == BraKetSymmetry::symm; + + for (std::size_t i = 0; i < num_particle_vertices; ++i) { + graph.vertex_labels.emplace_back(L"p_" + std::to_wstring(i + 1)); + graph.vertex_types.push_back(VertexType::TensorBraKet); + graph.vertex_colors.push_back(tensor_color); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + } + + assert(bra_rank(tensor) <= max_rank); + for (std::size_t i = 0; i < bra_rank(tensor); ++i) { + const bool is_unpaired_idx = i >= num_particle_vertices; + const bool color_idx = is_unpaired_idx || !is_part_symm; + + graph.vertex_labels.emplace_back(L"bra_" + std::to_wstring(i + 1)); + graph.vertex_types.push_back(VertexType::TensorBra); + graph.vertex_colors.push_back(color_idx ? i : 0); + + const std::size_t connect_vertex = + tensor_vertex + (is_unpaired_idx ? 0 : (i + 1)); + edges.push_back( + std::make_pair(connect_vertex, graph.vertex_labels.size() - 1)); + } + + assert(ket_rank(tensor) <= max_rank); + for (std::size_t i = 0; i < ket_rank(tensor); ++i) { + const bool is_unpaired_idx = i >= num_particle_vertices; + const bool color_idx = is_unpaired_idx || !is_part_symm; + + graph.vertex_labels.emplace_back(L"ket_" + std::to_wstring(i + 1)); + graph.vertex_types.push_back(VertexType::TensorKet); + graph.vertex_colors.push_back((color_idx ? i : 0) + + (is_braket_symm ? 0 : max_rank)); + + const std::size_t connect_vertex = + tensor_vertex + (is_unpaired_idx ? 0 : (i + 1)); + edges.push_back( + std::make_pair(connect_vertex, graph.vertex_labels.size() - 1)); + } + } else { + // Shared set of bra/ket vertices for all indices + std::wstring suffix = tensor_sym == Symmetry::symm ? L"_s" : L"_a"; + + const std::size_t bra_color = 0; + graph.vertex_labels.push_back(L"bra" + suffix); + graph.vertex_types.push_back(VertexType::TensorBra); + graph.vertex_colors.push_back(bra_color); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + + // TODO: figure out how to handle BraKetSymmetry::conjugate + const std::size_t ket_color = + braket_symmetry(tensor) == BraKetSymmetry::symm + ? bra_color + : bra_color + max_rank; + graph.vertex_labels.push_back(L"ket" + suffix); + graph.vertex_types.push_back(VertexType::TensorKet); + graph.vertex_colors.push_back(ket_color); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + } + + // TODO: handle aux indices permutation symmetries once they are supported + // for now, auxiliary indices are considered to always be asymmetric + for (std::size_t i = 0; i < aux_rank(tensor); ++i) { + graph.vertex_labels.emplace_back(L"aux_" + std::to_wstring(i + 1)); + graph.vertex_types.push_back(VertexType::TensorAux); + graph.vertex_colors.push_back(2 * max_rank + i); + edges.push_back( + std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); + } + } + + // Now add all indices (edges) to the graph + for (const Edge ¤t_edge : edges_) { + const Index &index = current_edge.idx(); + graph.vertex_labels.push_back(std::wstring(index.label())); + graph.vertex_types.push_back(VertexType::Index); + + // Assign index color + std::size_t idx_color; + auto named_idx_iter = named_indices.find(index); + if (named_idx_iter == named_indices.end()) { + // This is an anonymous index + idx_color = index.color(); + assert(idx_color > max_reserved_color); + } else { + idx_color = static_cast( + std::distance(named_indices.begin(), named_idx_iter)); + idx_color += named_idx_color_start; + } + graph.vertex_colors.push_back(idx_color); + + const std::size_t index_vertex = graph.vertex_labels.size() - 1; + + // Handle proto indices + if (index.has_proto_indices()) { + // For now we assume that all proto indices are symmetric + assert(index.symmetric_proto_indices()); + + std::size_t proto_vertex; + if (auto it = proto_bundles.find(index.proto_indices()); + it != proto_bundles.end()) { + proto_vertex = it->second; + } else { + // Create a new vertex for this bundle of proto indices + std::wstring spbundle_label = L"{"; + for (const Index &proto : index.proto_indices()) { + spbundle_label += proto.label(); + } + spbundle_label += L"}"; + graph.vertex_labels.push_back(std::move(spbundle_label)); + graph.vertex_types.push_back(VertexType::SPBundle); + const std::size_t bundle_color = + Index::proto_indices_color(index.proto_indices()) + + max_reserved_color; + assert(bundle_color); + graph.vertex_colors.push_back(bundle_color); + + proto_vertex = graph.vertex_labels.size() - 1; + proto_bundles.insert( + std::make_pair(index.proto_indices(), proto_vertex)); + } + + edges.push_back(std::make_pair(index_vertex, proto_vertex)); + } + + // Connect index to the tensor(s) it is connected to + for (std::size_t i = 0; i < current_edge.vertex_count(); ++i) { + assert(i <= 1); + const Vertex &vertex = + i == 0 ? current_edge.first_vertex() : current_edge.second_vertex(); + + assert(tensor_vertices.find(vertex.getTerminalIndex()) != + tensor_vertices.end()); + const std::size_t tensor_vertex = + tensor_vertices.find(vertex.getTerminalIndex())->second; + + // Store an edge connecting the index vertex to the corresponding tensor + // vertex + const bool tensor_is_nonsymm = + vertex.getTerminalSymmetry() == Symmetry::nonsymm; + const AbstractTensor &tensor = *tensors_[vertex.getTerminalIndex()]; + std::size_t offset; + if (tensor_is_nonsymm) { + // We have to find the correct vertex to connect this index to (for + // non-symmetric tensors each index has its dedicated "group" vertex) + + // Move off the tensor core's vertex + offset = 1; + // Move past the explicit particle vertices + offset += std::min(bra_rank(tensor), ket_rank(tensor)); + + if (vertex.getOrigin() > Origin::Bra) { + offset += bra_rank(tensor); + } + + offset += vertex.getIndexSlot(); + } else { + static_assert(static_cast(Origin::Bra) == 1); + static_assert(static_cast(Origin::Ket) == 2); + static_assert(static_cast(Origin::Aux) == 3); + offset = static_cast(vertex.getOrigin()); + } + + if (vertex.getOrigin() > Origin::Ket) { + offset += ket_rank(tensor); + } + + const std::size_t tensor_component_vertex = tensor_vertex + offset; + + assert(tensor_component_vertex < graph.vertex_labels.size()); + edges.push_back(std::make_pair(index_vertex, tensor_component_vertex)); + } + } + + assert(graph.vertex_labels.size() == graph.vertex_colors.size()); + assert(graph.vertex_labels.size() == graph.vertex_types.size()); + + // Create the actual BLISS graph object + graph.bliss_graph = + std::make_unique(graph.vertex_labels.size()); + + for (const std::pair ¤t_edge : edges) { + graph.bliss_graph->add_edge(current_edge.first, current_edge.second); + } + + // compress vertex colors to 32 bits, as required by Bliss, by hashing + for (std::size_t vertex = 0; vertex < graph.vertex_colors.size(); ++vertex) { + auto color = graph.vertex_colors[vertex]; + static_assert(sizeof(color) == 8); + + color = (~color) + (color << 18); // color = (color << 18) - color - 1; + color = color ^ (color >> 31); + color = color * 21; // color = (color + (color << 2)) + (color << 4); + color = color ^ (color >> 11); + color = color + (color << 6); + color = color ^ (color >> 22); + + graph.bliss_graph->change_color(vertex, static_cast(color)); + } + + return graph; +} + +void TensorNetworkV2::init_edges() { + edges_.clear(); + ext_indices_.clear(); + + auto idx_insert = [this](const Index &idx, Vertex vertex) { + if (Logger::instance().tensor_network) { + std::wcout << "TensorNetworkV2::init_edges: idx=" << to_latex(idx) + << " attached to tensor " << vertex.getTerminalIndex() << " (" + << vertex.getOrigin() << ") at position " + << vertex.getIndexSlot() + << " (sym: " << to_wstring(vertex.getTerminalSymmetry()) << ")" + << std::endl; + } + + auto it = std::find_if(edges_.begin(), edges_.end(), + FullLabelIndexLocator(idx.full_label())); + if (it == edges_.end()) { + edges_.emplace_back(std::move(vertex), idx); + } else { + it->connect_to(std::move(vertex)); + } + }; + + for (std::size_t tensor_idx = 0; tensor_idx < tensors_.size(); ++tensor_idx) { + assert(tensors_[tensor_idx]); + const AbstractTensor &tensor = *tensors_[tensor_idx]; + const Symmetry tensor_symm = symmetry(tensor); + + auto bra_indices = tensor._bra(); + for (std::size_t index_idx = 0; index_idx < bra_indices.size(); + ++index_idx) { + idx_insert(bra_indices[index_idx], + Vertex(Origin::Bra, tensor_idx, index_idx, tensor_symm)); + } + + auto ket_indices = tensor._ket(); + for (std::size_t index_idx = 0; index_idx < ket_indices.size(); + ++index_idx) { + idx_insert(ket_indices[index_idx], + Vertex(Origin::Ket, tensor_idx, index_idx, tensor_symm)); + } + + auto aux_indices = tensor._aux(); + for (std::size_t index_idx = 0; index_idx < aux_indices.size(); + ++index_idx) { + // Note: for the time being we don't have a way of expressing + // permutational symmetry of auxiliary indices so we just assume there is + // no such symmetry + idx_insert(aux_indices[index_idx], + Vertex(Origin::Aux, tensor_idx, index_idx, Symmetry::nonsymm)); + } + } + + // extract external indices + for (const Edge ¤t : edges_) { + assert(current.vertex_count() > 0); + if (current.vertex_count() == 1) { + // External index (== Edge only connected to a single vertex in the + // network) + if (Logger::instance().tensor_network) { + std::wcout << "idx " << to_latex(current.idx()) << " is external" + << std::endl; + } + + bool inserted = ext_indices_.insert(current.idx()).second; + assert(inserted); + } + } + + have_edges_ = true; +} + +container::svector> TensorNetworkV2::factorize() { + abort(); // not yet implemented +} + +ExprPtr TensorNetworkV2::canonicalize_individual_tensor_blocks( + const named_indices_t &named_indices) { + return do_individual_canonicalization( + TensorBlockCanonicalizer(named_indices)); +} + +ExprPtr TensorNetworkV2::canonicalize_individual_tensors( + const named_indices_t &named_indices) { + return do_individual_canonicalization( + DefaultTensorCanonicalizer(named_indices)); +} + +ExprPtr TensorNetworkV2::do_individual_canonicalization( + const TensorCanonicalizer &canonicalizer) { + ExprPtr byproduct = ex(1); + + for (auto &tensor : tensors_) { + auto nondefault_canonizer_ptr = + TensorCanonicalizer::nondefault_instance_ptr(tensor->_label()); + const TensorCanonicalizer &tensor_canonizer = + nondefault_canonizer_ptr ? *nondefault_canonizer_ptr : canonicalizer; + + auto bp = canonicalizer.apply(*tensor); + + if (bp) { + byproduct *= bp; + } + } + + return byproduct; +} + +} // namespace sequant diff --git a/SeQuant/core/tensor_network_v2.hpp b/SeQuant/core/tensor_network_v2.hpp new file mode 100644 index 000000000..5fea86a5b --- /dev/null +++ b/SeQuant/core/tensor_network_v2.hpp @@ -0,0 +1,329 @@ +// +// Created by Eduard Valeyev on 2019-02-02. +// + +#ifndef SEQUANT_TENSOR_NETWORK_V2_H +#define SEQUANT_TENSOR_NETWORK_V2_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// forward declarations +namespace bliss { +class Graph; +} + +namespace sequant { + +/// @brief A (non-directed) graph view of a sequence of AbstractTensor objects + +/// @note The main role of this is to canonize itself. Since Tensor objects can +/// be connected by multiple Index'es (thus edges are colored), what is +/// canonized is actually the graph of indices (roughly the dual of the tensor +/// graph), with Tensor objects represented by one or more vertices. +class TensorNetworkV2 { + public: + friend class TensorNetworkV2Accessor; + + constexpr static size_t max_rank = 256; + + enum class Origin { + Bra = 1, + Ket, + Aux, + }; + + class Vertex { + public: + Vertex(Origin origin, std::size_t terminal_idx, std::size_t index_slot, + Symmetry terminal_symm); + + Origin getOrigin() const; + std::size_t getTerminalIndex() const; + std::size_t getIndexSlot() const; + Symmetry getTerminalSymmetry() const; + + bool operator<(const Vertex &rhs) const; + bool operator==(const Vertex &rhs) const; + + private: + Origin origin; + std::size_t terminal_idx; + std::size_t index_slot; + Symmetry terminal_symm; + }; + + // clang-format off + /// @brief Edge in a TensorNetworkV2 = the Index annotating it + a pair of indices to identify which Tensor terminals it's connected to + + /// @note tensor terminals in a sequence of tensors are indexed as follows: + /// - >0 for bra terminals (i.e. "+7" indicated connection to a bra terminal + /// of 7th tensor object in the sequence) + /// - <0 for ket terminals + /// - 0 if free (not attached to any tensor objects) + /// - position records the terminal's location in the sequence of bra/ket + /// terminals (always 0 for symmetric/antisymmetric tensors) Terminal indices + /// are sorted by the tensor index (i.e. by the absolute value of the terminal + /// index), followed by position + // clang-format on + class Edge { + public: + Edge() = default; + explicit Edge(Vertex vertex) : first(std::move(vertex)), second() {} + Edge(Vertex vertex, Index index) + : first(std::move(vertex)), second(), index(std::move(index)) {} + + Edge &connect_to(Vertex vertex) { + assert(!second.has_value()); + + if (!first.has_value()) { + // unconnected Edge + first = std::move(vertex); + } else { + second = std::move(vertex); + if (second < first) { + // Ensure first <= second + std::swap(first, second); + } + } + return *this; + } + + bool operator<(const Edge &other) const { + if (vertex_count() != other.vertex_count()) { + // Ensure external indices (edges that are only attached to a tensor on + // one side) always come before internal ones + return vertex_count() < other.vertex_count(); + } + + if (!(first == other.first)) { + return first < other.first; + } + + if (second < other.second) { + return second < other.second; + } + + return index.space() < other.index.space(); + } + + bool operator==(const Edge &other) const { + return first == other.first && second == other.second; + } + + const Vertex &first_vertex() const { + assert(first.has_value()); + return first.value(); + } + const Vertex &second_vertex() const { + assert(second.has_value()); + return second.value(); + } + + /// @return the number of attached terminals (0, 1, or 2) + std::size_t vertex_count() const { + return second.has_value() ? 2 : (first.has_value() ? 1 : 0); + } + + const Index &idx() const { return index; } + + private: + std::optional first; + std::optional second; + Index index; + }; + + struct Graph { + std::unique_ptr bliss_graph; + std::vector vertex_labels; + std::vector vertex_colors; + std::vector vertex_types; + + Graph() = default; + + std::size_t vertex_to_index_idx(std::size_t vertex) const; + std::size_t vertex_to_tensor_idx(std::size_t vertex) const; + }; + + TensorNetworkV2(const Expr &expr) { + if (expr.size() > 0) { + for (const ExprPtr &subexpr : expr) { + add_expr(*subexpr); + } + } else { + add_expr(expr); + } + + init_edges(); + } + + TensorNetworkV2(const ExprPtr &expr) : TensorNetworkV2(*expr) {} + + template < + typename ExprPtrRange, + typename = std::enable_if_t && + !std::is_base_of_v>> + TensorNetworkV2(const ExprPtrRange &exprptr_range) { + static_assert( + std::is_base_of_v); + for (const ExprPtr ¤t : exprptr_range) { + add_expr(*current); + } + + init_edges(); + } + + /// @return const reference to the sequence of tensors + /// @note the order of tensors may be different from that provided as input + const auto &tensors() const { return tensors_; } + + using named_indices_t = container::set; + + /// @param cardinal_tensor_labels move all tensors with these labels to the + /// front before canonicalizing indices + /// @param fast if true (default), does fast canonicalization that is only + /// optimal if all tensors are distinct; set to false to perform complete + /// canonicalization + /// @param named_indices specifies the indices that cannot be renamed, i.e. + /// their labels are meaningful; default is nullptr, which results in external + /// indices treated as named indices + /// @return byproduct of canonicalization (e.g. phase); if none, returns + /// nullptr + ExprPtr canonicalize( + const container::vector &cardinal_tensor_labels = {}, + bool fast = true, const named_indices_t *named_indices = nullptr); + + /// Factorizes tensor network + /// @return sequence of binary products; each element encodes the tensors to + /// be + /// multiplied (values >0 refer to the tensors in tensors(), + /// values <0 refer to the elements of this sequence. E.g. sequences + /// @c {{0,1},{-1,2},{-2,3}} , @c {{0,2},{1,3},{-1,-2}} , @c + /// {{3,1},{2,-1},{0,-2}} encode the following respective + /// factorizations @c (((T0*T1)*T2)*T3) , @c ((T0*T2)*(T1*T3)) , and + /// @c (((T3*T1)*T2)*T0) . + container::svector> factorize(); + + /// accessor for the Edge object sequence + /// @return const reference to the sequence container of Edge objects, sorted + /// by their Index's full label + /// @sa Edge + const auto &edges() const { + assert(have_edges_); + return edges_; + } + + /// @brief Returns a range of external indices, i.e. those indices that do not + /// connect tensors + const auto &ext_indices() const { + assert(have_edges_); + return ext_indices_; + } + + /// @brief converts the network into a Bliss graph whose vertices are indices + /// and tensor vertex representations + /// @param[in] named_indices pointer to the set of named indices (ordinarily, + /// this includes all external indices); + /// default is nullptr, which means use all external indices for + /// named indices + /// @return The created Graph object + + /// @note Rules for constructing the graph: + /// - Indices with protoindices are connected to their protoindices, + /// either directly or (if protoindices are symmetric) via a protoindex + /// vertex. + /// - Indices are colored by their space, which in general encodes also + /// the space of the protoindices. + /// - An anti/symmetric n-body tensor has 2 terminals, each connected to + /// each other + to n index vertices. + /// - A nonsymmetric n-body tensor has n terminals, each connected to 2 + /// indices and 1 tensor vertex which is connected to all n terminal + /// indices. + /// - tensor vertices are colored by the label+rank+symmetry of the + /// tensor; terminal vertices are colored by the color of its tensor, + /// with the color of symm/antisymm terminals augmented by the + /// terminal's type (bra/ket). + Graph create_graph(const named_indices_t *named_indices = nullptr) const; + + private: + // source tensors and indices + container::svector tensors_; + + container::vector edges_; + bool have_edges_ = false; + // ext indices do not connect tensors + // sorted by *label* (not full label) of the corresponding value (Index) + // this ensures that proto indices are not considered and all internal indices + // have unique labels (not full labels) + named_indices_t ext_indices_; + + /// initializes edges_ and ext_indices_ + void init_edges(); + + /// Canonicalizes the network graph representation + /// Note: The explicit order of tensors and labelling of indices + /// remains undefined. + void canonicalize_graph(const named_indices_t &named_indices); + + /// Canonicalizes every individual tensor for itself, taking into account only + /// tensor blocks + /// @returns The byproduct of the canonicalizations + ExprPtr canonicalize_individual_tensor_blocks( + const named_indices_t &named_indices); + + /// Canonicalizes every individual tensor for itself + /// @returns The byproduct of the canonicalizations + ExprPtr canonicalize_individual_tensors(const named_indices_t &named_indices); + + ExprPtr do_individual_canonicalization( + const TensorCanonicalizer &canonicalizer); + + void add_expr(const Expr &expr) { + ExprPtr clone = expr.clone(); + + auto tensor_ptr = std::dynamic_pointer_cast(clone); + if (!tensor_ptr) { + throw std::invalid_argument( + "TensorNetworkV2::TensorNetworkV2: tried to add non-tensor to " + "network"); + } + + tensors_.push_back(std::move(tensor_ptr)); + } +}; + +template +std::basic_ostream &operator<<( + std::basic_ostream &stream, TensorNetworkV2::Origin origin) { + switch (origin) { + case TensorNetworkV2::Origin::Bra: + stream << "Bra"; + break; + case TensorNetworkV2::Origin::Ket: + stream << "Ket"; + break; + case TensorNetworkV2::Origin::Aux: + stream << "Aux"; + break; + } + return stream; +} + +} // namespace sequant + +#endif // SEQUANT_TENSOR_NETWORK_H diff --git a/SeQuant/core/wick.impl.hpp b/SeQuant/core/wick.impl.hpp index fb7cc4f53..59e0a8fd0 100644 --- a/SeQuant/core/wick.impl.hpp +++ b/SeQuant/core/wick.impl.hpp @@ -10,7 +10,6 @@ #include #include #include -#include #ifdef SEQUANT_HAS_EXECUTION_HEADER #include @@ -600,12 +599,12 @@ ExprPtr WickTheorem::compute(const bool count_only, << to_latex(expr_input_) << std::endl; // construct graph representation of the tensor product - WickGraph network(expr_input_->as().factors()); - auto [graph, vlabels, vcolors, vtypes] = network.make_bliss_graph(); + TensorNetwork tn(expr_input_->as().factors()); + auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(); const auto n = vlabels.size(); assert(vtypes.size() == n); - const auto &tn_edges = network.edges(); - const auto &tn_tensors = network.tensors(); + const auto &tn_edges = tn.edges(); + const auto &tn_tensors = tn.tensors(); if (Logger::instance().wick_topology) { std::basic_ostringstream oss; diff --git a/SeQuant/core/wick_graph.cpp b/SeQuant/core/wick_graph.cpp deleted file mode 100644 index c77b6b8c5..000000000 --- a/SeQuant/core/wick_graph.cpp +++ /dev/null @@ -1,316 +0,0 @@ -// -// Created by Eduard Valeyev on 2019-02-26. -// - -#include "wick_graph.hpp" -#include "bliss.hpp" -#include "logger.hpp" - -namespace sequant { - -std::tuple, std::vector, - std::vector, std::vector> -WickGraph::make_bliss_graph(const named_indices_t *named_indices_ptr) const { - // must call init_edges() prior to calling this - if (edges_.empty()) { - init_edges(); - } - - // initialize named_indices by default to all external indices (these HAVE - // been computed in init_edges) - const auto &named_indices = - named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; - - // results - std::shared_ptr graph; - std::vector vertex_labels( - edges_.size()); // the size will be updated - std::vector vertex_color(edges_.size(), - 0); // the size will be updated - std::vector vertex_type( - edges_.size()); // the size will be updated - - // N.B. Colors [0, 2 max rank + named_indices.size()) are reserved: - // 0 - the bra vertex (for particle 0, if bra is nonsymm, or for the entire - // bra, if (anti)symm) 1 - the bra vertex for particle 1, if bra is nonsymm - // ... - // max_rank - the ket vertex (for particle 0, if particle-asymmetric, or for - // the entire ket, if particle-symmetric) max_rank+1 - the ket vertex for - // particle 1, if particle-asymmetric - // ... - // 2 max_rank - first named index - // 2 max_rank + 1 - second named index - // ... - // N.B. For braket-symmetric tensors the ket vertices use the same indices as - // the bra vertices - auto nonreserved_color = [&named_indices](size_t color) -> bool { - return color >= 2 * max_rank + named_indices.size(); - }; - - // compute # of vertices - size_t nv = 0; - size_t index_cnt = 0; - size_t spbundle_cnt = 0; - // first count vertex indices ... the only complication are symmetric - // protoindex bundles this will keep track of unique symmetric protoindex - // bundles - using protoindex_bundle_t = - std::decay_t().proto_indices())>; - container::set symmetric_protoindex_bundles; - const size_t spbundle_vertex_offset = - edges_.size(); // where spbundle vertices will start - ranges::for_each(edges_, [&](const Edge &ttpair) { - const Index &idx = ttpair.idx(); - ++nv; // each index is a vertex - vertex_labels.at(index_cnt) = idx.to_latex(); - vertex_type.at(index_cnt) = VertexType::Index; - // assign color: named indices use reserved colors - const auto named_index_it = named_indices.find(idx); - if (named_index_it == - named_indices.end()) { // anonymous index? use Index::color - const auto idx_color = idx.color(); - assert(nonreserved_color(idx_color)); - vertex_color.at(index_cnt) = idx_color; - } else { - const auto named_index_rank = named_index_it - named_indices.begin(); - vertex_color.at(index_cnt) = 2 * max_rank + named_index_rank; - } - // each symmetric proto index bundle will have a vertex ... - // for now only store the unique protoindex bundles in - // symmetric_protoindex_bundles, then commit their data to - // vertex_{labels,type,color} later - if (idx.has_proto_indices()) { - assert(idx.symmetric_proto_indices()); // only symmetric protoindices are - // supported right now - if (symmetric_protoindex_bundles.find(idx.proto_indices()) == - symmetric_protoindex_bundles - .end()) { // new bundle? make a vertex for it - auto graph = symmetric_protoindex_bundles.insert(idx.proto_indices()); - assert(graph.second); - } - } - index_cnt++; - }); - // now commit protoindex bundle metadata - ranges::for_each(symmetric_protoindex_bundles, [&](const auto &bundle) { - ++nv; // each symmetric protoindex bundle is a vertex - std::wstring spbundle_label = L"{"; - for (auto &&pi : bundle) { - spbundle_label += pi.to_latex(); - } - spbundle_label += L"}"; - vertex_labels.push_back(spbundle_label); - vertex_type.push_back(VertexType::SPBundle); - const auto idx_proto_indices_color = Index::proto_indices_color(bundle); - assert(nonreserved_color(idx_proto_indices_color)); - vertex_color.push_back(idx_proto_indices_color); - spbundle_cnt++; - }); - // now account for vertex representation of tensors - size_t tensor_cnt = 0; - // this will map to tensor index to the first (core) vertex in its - // representation - container::svector tensor_vertex_offset(tensors_.size()); - ranges::for_each(tensors_, [&](const auto &t) { - tensor_vertex_offset.at(tensor_cnt) = nv; - // each tensor has a core vertex (to be colored by its label) - ++nv; - const auto tlabel = label(*t); - vertex_labels.emplace_back(tlabel); - vertex_type.emplace_back(VertexType::TensorCore); - const auto t_color = hash::value(tlabel); - static_assert(sizeof(t_color) == sizeof(unsigned long int)); - assert(nonreserved_color(t_color)); - vertex_color.push_back(t_color); - // symmetric/antisymmetric tensors are represented by 3 more vertices: - // - bra - // - ket - // - braket (connecting bra and ket to the core) - auto &tref = *t; - if (symmetry(tref) != Symmetry::nonsymm) { - nv += 3; - vertex_labels.push_back( - std::wstring(L"bra") + to_wstring(bra_rank(tref)) + - ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); - vertex_type.push_back(VertexType::TensorBra); - vertex_color.push_back(0); - vertex_labels.push_back( - std::wstring(L"ket") + to_wstring(ket_rank(tref)) + - ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); - vertex_type.push_back(VertexType::TensorKet); - vertex_color.push_back( - braket_symmetry(tref) == BraKetSymmetry::symm ? 0 : max_rank); - vertex_labels.push_back( - std::wstring(L"bk") + - ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); - vertex_type.push_back(VertexType::TensorBraKet); - vertex_color.push_back(t_color); - } - // nonsymmetric tensors are represented by 3*rank more vertices (with rank = - // max(bra_rank(),ket_rank()) - else { - const auto rank = std::max(bra_rank(tref), ket_rank(tref)); - assert(rank <= max_rank); - for (size_t p = 0; p != rank; ++p) { - nv += 3; - auto pstr = to_wstring(p + 1); - vertex_labels.push_back(std::wstring(L"bra") + pstr); - vertex_type.push_back(VertexType::TensorBra); - const bool t_is_particle_symmetric = - particle_symmetry(tref) == ParticleSymmetry::nonsymm; - const auto bra_color = t_is_particle_symmetric ? p : 0; - vertex_color.push_back(bra_color); - vertex_labels.push_back(std::wstring(L"ket") + pstr); - vertex_type.push_back(VertexType::TensorKet); - vertex_color.push_back(braket_symmetry(tref) == BraKetSymmetry::symm - ? bra_color - : bra_color + max_rank); - vertex_labels.push_back(std::wstring(L"bk") + pstr); - vertex_type.push_back(VertexType::TensorBraKet); - vertex_color.push_back(t_color); - } - } - ++tensor_cnt; - }); - - // allocate graph - graph = std::make_shared(nv); - - // add edges - // - each index's degree <= 2 + # of protoindex terminals - index_cnt = 0; - ranges::for_each(edges_, [&](const Edge &ttpair) { - for (int t = 0; t != 2; ++t) { - const auto terminal_index = t == 0 ? ttpair.first() : ttpair.second(); - const auto terminal_position = - t == 0 ? ttpair.first_position() : ttpair.second_position(); - if (terminal_index) { - const auto tidx = std::abs(terminal_index) - 1; - const auto ttpos = terminal_position; - const bool bra = terminal_index > 0; - const size_t braket_vertex_index = tensor_vertex_offset[tidx] + - /* core */ 1 + 3 * ttpos + - (bra ? 0 : 1); - graph->add_edge(index_cnt, braket_vertex_index); - } - } - // if this index has symmetric protoindex bundles - if (ttpair.idx().has_proto_indices()) { - if (ttpair.idx().symmetric_proto_indices()) { - assert( - symmetric_protoindex_bundles.find(ttpair.idx().proto_indices()) != - symmetric_protoindex_bundles.end()); - const auto spbundle_idx = - symmetric_protoindex_bundles.find(ttpair.idx().proto_indices()) - - symmetric_protoindex_bundles.begin(); - graph->add_edge(index_cnt, spbundle_vertex_offset + spbundle_idx); - } else { - abort(); // nonsymmetric proto indices not supported yet - } - } - ++index_cnt; - }); - // - link up proto indices, if any ... only symmetric protobundles are - // supported now - spbundle_cnt = spbundle_vertex_offset; - ranges::for_each(symmetric_protoindex_bundles, [&graph, this, &spbundle_cnt]( - const auto &bundle) { - for (auto &&proto_index : bundle) { - assert(edges_.find(proto_index.full_label()) != edges_.end()); - const auto proto_index_vertex = - edges_.find(proto_index.full_label()) - edges_.begin(); - graph->add_edge(spbundle_cnt, proto_index_vertex); - } - ++spbundle_cnt; - }); - // - link up tensors - tensor_cnt = 0; - ranges::for_each( - tensors_, [&graph, &tensor_cnt, &tensor_vertex_offset](const auto &t) { - const auto vertex_offset = tensor_vertex_offset.at(tensor_cnt); - // for each braket terminal linker - auto &tref = *t; - const size_t nbk = symmetry(tref) == Symmetry::nonsymm - ? std::max(bra_rank(tref), ket_rank(tref)) - : 1; - for (size_t bk = 1; bk <= nbk; ++bk) { - const int bk_vertex = vertex_offset + 3 * bk; - graph->add_edge(vertex_offset, bk_vertex); // core - graph->add_edge(bk_vertex - 2, bk_vertex); // bra - graph->add_edge(bk_vertex - 1, bk_vertex); // ket - } - ++tensor_cnt; - }); - - // compress vertex colors to 32 bits, as required by Bliss, by hashing - size_t v_cnt = 0; - for (auto &&color : vertex_color) { - auto hash6432shift = [](size_t key) { - static_assert(sizeof(key) == 8); - key = (~key) + (key << 18); // key = (key << 18) - key - 1; - key = key ^ (key >> 31); - key = key * 21; // key = (key + (key << 2)) + (key << 4); - key = key ^ (key >> 11); - key = key + (key << 6); - key = key ^ (key >> 22); - return static_cast(key); - }; - graph->change_color(v_cnt, hash6432shift(color)); - ++v_cnt; - } - - return {graph, vertex_labels, vertex_color, vertex_type}; -} - -void WickGraph::init_edges() const { - if (have_edges_) return; - - auto idx_insert = [this](const Index &idx, int tensor_idx, int pos) { - if (Logger::instance().tensor_network) { - std::wcout << "WickGraph::init_edges: idx=" << to_latex(idx) - << " attached to tensor " << std::abs(tensor_idx) << "'s " - << ((tensor_idx > 0) ? "bra" : "ket") << " at position " << pos - << std::endl; - } - decltype(edges_) &indices = this->edges_; - auto it = indices.find(idx.full_label()); - if (it == indices.end()) { - indices.emplace(Edge(tensor_idx, &idx, pos)); - } else { - const_cast(*it).connect_to(tensor_idx, pos); - } - }; - - int t_idx = 1; - for (auto &&t : tensors_) { - const auto t_is_nonsymm = symmetry(*t) == Symmetry::nonsymm; - size_t cnt = 0; - for (const Index &idx : t->_bra()) { - idx_insert(idx, t_idx, t_is_nonsymm ? cnt : 0); - ++cnt; - } - cnt = 0; - for (const Index &idx : t->_ket()) { - idx_insert(idx, -t_idx, t_is_nonsymm ? cnt : 0); - ++cnt; - } - ++t_idx; - } - - // extract external indices - for (const auto &terminals : edges_) { - assert(terminals.size() != 0); - if (terminals.size() == 1) { // external? - if (Logger::instance().tensor_network) { - std::wcout << "idx " << to_latex(terminals.idx()) << " is external" - << std::endl; - } - auto insertion_result = ext_indices_.emplace(terminals.idx()); - assert(insertion_result.second); - } - } - - have_edges_ = true; -} - -} // namespace sequant diff --git a/SeQuant/core/wick_graph.hpp b/SeQuant/core/wick_graph.hpp deleted file mode 100644 index 9d45ed3ff..000000000 --- a/SeQuant/core/wick_graph.hpp +++ /dev/null @@ -1,229 +0,0 @@ -#ifndef SEQUANT_WICK_GRAPH_H -#define SEQUANT_WICK_GRAPH_H - -#include "abstract_tensor.hpp" -#include "container.hpp" -#include "tensor.hpp" -#include "vertex_type.hpp" - -// forward declarations -namespace bliss { -class Graph; -} - -namespace sequant { - -/// @brief A (non-directed) graph view of a sequence of AbstractTensor objects - -/// @note The main role of this is to canonize itself. Since Tensor objects can -/// be connected by multiple Index'es (thus edges are colored), what is -/// canonized is actually the graph of indices (roughly the dual of the tensor -/// graph), with Tensor objects represented by one or more vertices. -class WickGraph { - public: - constexpr static size_t max_rank = 256; - - // clang-format off - /// @brief Edge in a TensorNetwork = the Index annotating it + a pair of indices to identify which Tensor terminals it's connected to - - /// @note tensor terminals in a sequence of tensors are indexed as follows: - /// - >0 for bra terminals (i.e. "+7" indicated connection to a bra terminal - /// of 7th tensor object in the sequence) - /// - <0 for ket terminals - /// - 0 if free (not attached to any tensor objects) - /// - position records the terminal's location in the sequence of bra/ket - /// terminals (always 0 for symmetric/antisymmetric tensors) Terminal indices - /// are sorted by the tensor index (i.e. by the absolute value of the terminal - /// index), followed by position - // clang-format on - class Edge { - public: - Edge() = default; - explicit Edge(int terminal_idx, int position = 0) - : first_(0), second_(terminal_idx), second_position_(position) {} - Edge(int terminal_idx, const Index *idxptr, int position = 0) - : first_(0), - second_(terminal_idx), - idxptr_(idxptr), - second_position_(position) {} - // Edge(const Edge&) = default; - // Edge(Edge&&) = default; - // Edge& operator=(const Edge&) = default; - // Edge& operator=(Edge&&) = default; - - Edge &connect_to(int terminal_idx, int position = 0) { - assert(first_ == 0 || second_ == 0); // not connected yet - assert(terminal_idx != 0); // valid idx - if (second_ == 0) { // unconnected Edge - second_ = terminal_idx; - second_position_ = position; - } else if (std::abs(second_) < - std::abs(terminal_idx)) { // connected to 2 Edges? ensure - // first_ < second_ - assert(first_ == 0); // there are slots left - first_ = second_; - first_position_ = second_position_; - second_ = terminal_idx; - second_position_ = position; - } else { // put into first slot - first_ = terminal_idx; - first_position_ = position; - } - return *this; - } - - bool operator<(const Edge &other) const { - if (std::abs(first_) == std::abs(other.first_)) { - if (first_position_ == other.first_position_) { - if (std::abs(second_) == std::abs(other.second_)) { - return second_position_ < other.second_position_; - } else { - return std::abs(second_) < std::abs(other.second_); - } - } else { - return first_position_ < other.first_position_; - } - } else { - return std::abs(first_) < std::abs(other.first_); - } - } - - bool operator==(const Edge &other) const { - return std::abs(first_) == std::abs(other.first_) && - std::abs(second_) == std::abs(other.second_) && - first_position_ == other.first_position_ && - second_position_ == other.second_position_; - } - - auto first() const { return first_; } - auto second() const { return second_; } - auto first_position() const { return first_position_; } - auto second_position() const { return second_position_; } - - /// @return the number of attached terminals (0, 1, or 2) - auto size() const { return (first_ != 0) ? 2 : ((second_ != 0) ? 1 : 0); } - - const Index &idx() const { - assert(idxptr_ != nullptr); - return *idxptr_; - } - - private: - // if only connected to 1 terminal, this is always 0 - // otherwise first_ <= second_ - int first_ = 0; - int second_ = 0; - const Index *idxptr_ = nullptr; - int first_position_ = 0; - int second_position_ = 0; - }; - - public: - /// @throw std::logic_error if exprptr_range contains a non-tensor - /// @note uses RTTI - template - WickGraph(ExprPtrRange &exprptr_range) { - for (auto &&ex : exprptr_range) { - auto t = std::dynamic_pointer_cast(ex); - if (t) { - tensors_.emplace_back(t); - } else { - throw std::logic_error( - "TensorNetwork::TensorNetwork: non-tensors in the given expression " - "range"); - } - } - } - - /// @return const reference to the sequence of tensors - /// @note the order of tensors may be different from that provided as input - const auto &tensors() const { return tensors_; } - - using named_indices_t = container::set; - - private: - // source tensors and indices - container::svector tensors_; - - struct FullLabelCompare { - using is_transparent = void; - bool operator()(const Edge &first, const Edge &second) const { - return first.idx().full_label() < second.idx().full_label(); - } - bool operator()(const Edge &first, const std::wstring_view &second) const { - return first.idx().full_label() < second; - } - bool operator()(const std::wstring_view &first, const Edge &second) const { - return first < second.idx().full_label(); - } - }; - // Index -> Edge, sorted by full label - mutable container::set edges_; - // set to true by init_edges(); - mutable bool have_edges_ = false; - // ext indices do not connect tensors - // sorted by *label* (not full label) of the corresponding value (Index) - // this ensures that proto indices are not considered and all internal indices - // have unique labels (not full labels) - mutable named_indices_t ext_indices_; - - // replacements of anonymous indices produced by the last call to - // canonicalize() - container::map idxrepl_; - - /// initializes edges_ and ext_indices_ - void init_edges() const; - - public: - /// accessor for the Edge object sequence - /// @return const reference to the sequence container of Edge objects, sorted - /// by their Index's full label - /// @sa Edge - const auto &edges() const { - init_edges(); - return edges_; - } - - /// @brief Returns a range of external indices, i.e. those indices that do not - /// connect tensors - - /// @note The external indices are sorted by *label* (not full label) of the - /// corresponding value (Index) - const auto &ext_indices() const { - if (edges_.empty()) init_edges(); - return ext_indices_; - } - - public: - /// @brief converts the network into a Bliss graph whose vertices are indices - /// and tensor vertex representations - /// @param[in] named_indices pointer to the set of named indices (ordinarily, - /// this includes all external indices); - /// default is nullptr, which means use all external indices for - /// named indices - /// @return {shared_ptr to Graph, vector of vertex labels, vector of vertex - /// colors, vector of vertex types} - - /// @note Rules for constructing the graph: - /// - Indices with protoindices are connected to their protoindices, - /// either directly or (if protoindices are symmetric) via a protoindex - /// vertex. - /// - Indices are colored by their space, which in general encodes also - /// the space of the protoindices. - /// - An anti/symmetric n-body tensor has 2 terminals, each connected to - /// each other + to n index vertices. - /// - A nonsymmetric n-body tensor has n terminals, each connected to 2 - /// indices and 1 tensor vertex which is connected to all n terminal - /// indices. - /// - tensor vertices are colored by the label+rank+symmetry of the - /// tensor; terminal vertices are colored by the color of its tensor, - /// with the color of symm/antisymm terminals augmented by the - /// terminal's type (bra/ket). - std::tuple, std::vector, - std::vector, std::vector> - make_bliss_graph(const named_indices_t *named_indices = nullptr) const; -}; - -} // namespace sequant - -#endif // SEQUANT_WICK_GRAPH_H diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index eed0ea3de..17d7dcb97 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -17,8 +17,8 @@ #include #include #include +#include #include -#include #include #include @@ -46,6 +46,543 @@ #include #include +TEST_CASE("TensorNetwork", "[elements]") { + using namespace sequant; + using namespace sequant::mbpt; + using sequant::Context; + namespace t = sequant::mbpt::tensor; + namespace o = sequant::mbpt::op; + + SECTION("constructors") { + { // with Tensors + auto t1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}); + auto t2 = ex(L"t", bra{L"i_1"}, ket{L"i_2"}); + auto t1_x_t2 = t1 * t2; + REQUIRE_NOTHROW(TensorNetwork(*t1_x_t2)); + + auto t1_x_t2_p_t2 = t1 * (t2 + t2); // can only use a flat tensor product + REQUIRE_THROWS_AS(TensorNetwork(*t1_x_t2_p_t2), std::logic_error); + } + + { // with NormalOperators + constexpr const auto V = Vacuum::SingleProduct; + auto t1 = ex(cre({L"i_1"}), ann({L"i_2"}), V); + auto t2 = ex(cre({L"i_2"}), ann({L"i_1"}), V); + auto t1_x_t2 = t1 * t2; + REQUIRE_NOTHROW(TensorNetwork(*t1_x_t2)); + } + + { // with Tensors and NormalOperators + auto tmp = t::A(nₚ(-2)) * t::H_(2) * t::T_(2) * t::T_(2); + REQUIRE_NOTHROW(TensorNetwork(tmp->as().factors())); + } + + } // SECTION("constructors") + + SECTION("accessors") { + { + constexpr const auto V = Vacuum::SingleProduct; + auto t1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}); + auto t2 = ex(cre({L"i_1"}), ann({L"i_3"}), V); + auto t1_x_t2 = t1 * t2; + REQUIRE_NOTHROW(TensorNetwork(*t1_x_t2)); + TensorNetwork tn(*t1_x_t2); + + // edges + auto edges = tn.edges(); + REQUIRE(edges.size() == 3); + + // ext indices + auto ext_indices = tn.ext_indices(); + REQUIRE(ext_indices.size() == 2); + + // tensors + auto tensors = tn.tensors(); + REQUIRE(size(tensors) == 2); + REQUIRE(std::dynamic_pointer_cast(tensors[0])); + REQUIRE(std::dynamic_pointer_cast(tensors[1])); + REQUIRE(*std::dynamic_pointer_cast(tensors[0]) == *t1); + REQUIRE(*std::dynamic_pointer_cast(tensors[1]) == *t2); + + // index replacements performed by canonicalize() ... since canonicalize() + // not invoked this is empty + auto idxrepl = tn.idxrepl(); + REQUIRE(idxrepl.size() == 0); + } + } // SECTION("accessors") + + SECTION("canonicalizer") { + { + { // with no external indices, hence no named indices whatsoever + Index::reset_tmp_index(); + constexpr const auto V = Vacuum::SingleProduct; + auto t1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}); + auto t2 = ex(cre({L"i_1"}), ann({L"i_2"}), V); + auto t1_x_t2 = t1 * t2; + TensorNetwork tn(*t1_x_t2); + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); + + REQUIRE(size(tn.tensors()) == 2); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[0])); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[1])); + // std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) << + // std::endl; std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) << + // std::endl; + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == + L"{F^{{i_1}}_{{i_2}}}"); + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == + L"{\\tilde{a}^{{i_2}}_{{i_1}}}"); + REQUIRE(tn.idxrepl().size() == 2); + } + + { + Index::reset_tmp_index(); + constexpr const auto V = Vacuum::SingleProduct; + auto t1 = ex(L"F", bra{L"i_2"}, ket{L"i_17"}); + auto t2 = ex(cre({L"i_2"}), ann({L"i_3"}), V); + auto t1_x_t2 = t1 * t2; + + // with all external named indices + { + TensorNetwork tn(*t1_x_t2); + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); + + REQUIRE(size(tn.tensors()) == 2); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[0])); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[1])); + // std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) << + // std::endl; std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) << + // std::endl; + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == + L"{\\tilde{a}^{{i_1}}_{{i_3}}}"); + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == + L"{F^{{i_{17}}}_{{i_1}}}"); + } + + // with explicit named indices + { + Index::reset_tmp_index(); + TensorNetwork tn(*t1_x_t2); + + using named_indices_t = TensorNetwork::named_indices_t; + named_indices_t indices{Index{L"i_17"}}; + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false, + &indices); + + REQUIRE(size(tn.tensors()) == 2); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[0])); + REQUIRE(std::dynamic_pointer_cast(tn.tensors()[1])); + // std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) + // << std::endl; std::wcout << + // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) + // << std::endl; + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == + L"{\\tilde{a}^{{i_2}}_{{i_1}}}"); + REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == + L"{F^{{i_{17}}}_{{i_2}}}"); + } + } + } + } // SECTION("canonicalizer") + + SECTION("bliss graph") { + auto ctx_resetter = set_scoped_default_context( + Context(sequant::mbpt::make_legacy_spaces(), Vacuum::SingleProduct)); + Index::reset_tmp_index(); + // to generate expressions in specified (i.e., platform-independent) manner + // can't use operator expression (due to unspecified order of evaluation of + // function arguments), must use initializer list + auto tmp = ex>( + {t::A(nₚ(-2)), t::H_(2), t::T_(2), t::T_(2), t::T_(2)}); + // canonicalize to avoid dependence on the implementation details of + // mbpt::sr::make_op + canonicalize(tmp); + // std::wcout << "A2*H2*T2*T2*T2 = " << to_latex(tmp) << std::endl; + TensorNetwork tn(tmp->as().factors()); + + // make graph + REQUIRE_NOTHROW(tn.make_bliss_graph()); + auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(); + + // create dot + std::basic_ostringstream oss; + REQUIRE_NOTHROW(graph->write_dot(oss, vlabels)); + // std::wcout << "oss.str() = " << std::endl << oss.str() << std::endl; + REQUIRE(oss.str() == + L"graph g {\n" + "v0 [label=\"{a_1}\"; color=\"#64f,acf\"];\n" + "v0 -- v29\n" + "v0 -- v58\n" + "v1 [label=\"{a_2}\"; color=\"#64f,acf\"];\n" + "v1 -- v29\n" + "v1 -- v58\n" + "v2 [label=\"{a_3}\"; color=\"#64f,acf\"];\n" + "v2 -- v33\n" + "v2 -- v54\n" + "v3 [label=\"{a_4}\"; color=\"#64f,acf\"];\n" + "v3 -- v33\n" + "v3 -- v54\n" + "v4 [label=\"{a_5}\"; color=\"#64f,acf\"];\n" + "v4 -- v37\n" + "v4 -- v50\n" + "v5 [label=\"{a_6}\"; color=\"#64f,acf\"];\n" + "v5 -- v37\n" + "v5 -- v50\n" + "v6 [label=\"{a_7}\"; color=\"#64f,acf\"];\n" + "v6 -- v22\n" + "v6 -- v41\n" + "v7 [label=\"{a_8}\"; color=\"#64f,acf\"];\n" + "v7 -- v22\n" + "v7 -- v41\n" + "v8 [label=\"{i_1}\"; color=\"#9c2,a20\"];\n" + "v8 -- v30\n" + "v8 -- v57\n" + "v9 [label=\"{i_2}\"; color=\"#9c2,a20\"];\n" + "v9 -- v30\n" + "v9 -- v57\n" + "v10 [label=\"{i_3}\"; color=\"#9c2,a20\"];\n" + "v10 -- v34\n" + "v10 -- v53\n" + "v11 [label=\"{i_4}\"; color=\"#9c2,a20\"];\n" + "v11 -- v34\n" + "v11 -- v53\n" + "v12 [label=\"{i_5}\"; color=\"#9c2,a20\"];\n" + "v12 -- v38\n" + "v12 -- v49\n" + "v13 [label=\"{i_6}\"; color=\"#9c2,a20\"];\n" + "v13 -- v38\n" + "v13 -- v49\n" + "v14 [label=\"{i_7}\"; color=\"#9c2,a20\"];\n" + "v14 -- v21\n" + "v14 -- v42\n" + "v15 [label=\"{i_8}\"; color=\"#9c2,a20\"];\n" + "v15 -- v21\n" + "v15 -- v42\n" + "v16 [label=\"{\\kappa_1}\"; color=\"#712,6de\"];\n" + "v16 -- v25\n" + "v16 -- v46\n" + "v17 [label=\"{\\kappa_2}\"; color=\"#712,6de\"];\n" + "v17 -- v25\n" + "v17 -- v46\n" + "v18 [label=\"{\\kappa_3}\"; color=\"#712,6de\"];\n" + "v18 -- v26\n" + "v18 -- v45\n" + "v19 [label=\"{\\kappa_4}\"; color=\"#712,6de\"];\n" + "v19 -- v26\n" + "v19 -- v45\n" + "v20 [label=\"A\"; color=\"#518,020\"];\n" + "v20 -- v23\n" + "v21 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v21 -- v23\n" + "v22 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v22 -- v23\n" + "v23 [label=\"bka\"; color=\"#518,020\"];\n" + "v24 [label=\"g\"; color=\"#2e0,351\"];\n" + "v24 -- v27\n" + "v25 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v25 -- v27\n" + "v26 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v26 -- v27\n" + "v27 [label=\"bka\"; color=\"#2e0,351\"];\n" + "v28 [label=\"t\"; color=\"#43,e44\"];\n" + "v28 -- v31\n" + "v29 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v29 -- v31\n" + "v30 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v30 -- v31\n" + "v31 [label=\"bka\"; color=\"#43,e44\"];\n" + "v32 [label=\"t\"; color=\"#43,e44\"];\n" + "v32 -- v35\n" + "v33 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v33 -- v35\n" + "v34 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v34 -- v35\n" + "v35 [label=\"bka\"; color=\"#43,e44\"];\n" + "v36 [label=\"t\"; color=\"#43,e44\"];\n" + "v36 -- v39\n" + "v37 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v37 -- v39\n" + "v38 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v38 -- v39\n" + "v39 [label=\"bka\"; color=\"#43,e44\"];\n" + "v40 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v40 -- v43\n" + "v41 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v41 -- v43\n" + "v42 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v42 -- v43\n" + "v43 [label=\"bka\"; color=\"#cbf,be5\"];\n" + "v44 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v44 -- v47\n" + "v45 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v45 -- v47\n" + "v46 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v46 -- v47\n" + "v47 [label=\"bka\"; color=\"#cbf,be5\"];\n" + "v48 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v48 -- v51\n" + "v49 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v49 -- v51\n" + "v50 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v50 -- v51\n" + "v51 [label=\"bka\"; color=\"#cbf,be5\"];\n" + "v52 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v52 -- v55\n" + "v53 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v53 -- v55\n" + "v54 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v54 -- v55\n" + "v55 [label=\"bka\"; color=\"#cbf,be5\"];\n" + "v56 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v56 -- v59\n" + "v57 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v57 -- v59\n" + "v58 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v58 -- v59\n" + "v59 [label=\"bka\"; color=\"#cbf,be5\"];\n" + "}\n"); + + // compute automorphism group + { + bliss::Stats stats; + graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + + std::vector> aut_generators; + auto save_aut = [&aut_generators](const unsigned int n, + const unsigned int* aut) { + aut_generators.emplace_back(aut, aut + n); + }; + graph->find_automorphisms(stats, &bliss::aut_hook, + &save_aut); + std::basic_ostringstream oss; + bliss::print_auts(aut_generators, oss, decltype(vlabels){}); + REQUIRE(oss.str() == + L"(14,15)\n" + "(6,7)\n" + "(18,19)\n" + "(16,17)\n" + "(8,9)\n" + "(0,1)\n" + "(10,11)\n" + "(12,13)\n" + "(2,3)\n" + "(4,5)\n" + "(2,4)(3,5)(10,12)(11,13)(32,36)(33,37)(34,38)(35,39)(48,52)(49," + "53)(50,54)(51,55)\n" + "(0,2)(1,3)(8,10)(9,11)(28,32)(29,33)(30,34)(31,35)(52,56)(53,57)" + "(54,58)(55,59)\n"); + + // change to 1 to user vertex labels rather than indices + if (0) { + std::basic_ostringstream oss2; + bliss::print_auts(aut_generators, oss2, vlabels); + std::wcout << oss2.str() << std::endl; + } + } + + } // SECTION("bliss graph") + + SECTION("misc1") { + if (false) { + Index::reset_tmp_index(); + // TN1 from manuscript + auto g = ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"a_4"}, + Symmetry::antisymm); + auto ta = ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_1", L"i_2"}, + Symmetry::antisymm); + auto tb = ex(L"t", bra{L"a_2", L"a_4"}, ket{L"i_3", L"i_4"}, + Symmetry::antisymm); + + auto tmp = g * ta * tb; + // std::wcout << "TN1 = " << to_latex(tmp) << std::endl; + TensorNetwork tn(tmp->as().factors()); + + // make graph + // N.B. treat all indices as dummy so that the automorphism ignores the + using named_indices_t = TensorNetwork::named_indices_t; + named_indices_t indices{}; + REQUIRE_NOTHROW(tn.make_bliss_graph(&indices)); + auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(&indices); + + // create dot + { + std::basic_ostringstream oss; + REQUIRE_NOTHROW(graph->write_dot(oss, vlabels)); + // std::wcout << "oss.str() = " << std::endl << oss.str() << std::endl; + } + + bliss::Stats stats; + graph->set_splitting_heuristic(bliss::Graph::shs_fsm); + + std::vector> aut_generators; + auto save_aut = [&aut_generators](const unsigned int n, + const unsigned int* aut) { + aut_generators.emplace_back(aut, aut + n); + }; + graph->find_automorphisms(stats, &bliss::aut_hook, + &save_aut); + CHECK(aut_generators.size() == + 2); // there are 2 generators, i1<->i2, i3<->i4 + + std::basic_ostringstream oss; + bliss::print_auts(aut_generators, oss, vlabels); + CHECK(oss.str() == L"({i_3},{i_4})\n({i_1},{i_2})\n"); + // std::wcout << oss.str() << std::endl; + } + + // profile canonicalization for synthetic tests in + // DOI 10.1016/j.cpc.2018.02.014 + if (false) { + for (auto testcase : {0, 1, 2, 3}) { + // - testcase=0,2 are "equivalent" and correspond to the "frustrated" + // case in Section 5.3 of DOI 10.1016/j.cpc.2018.02.014 + // - testcase=1 corresponds to the "frustrated" case in Section 5.4 of + // DOI 10.1016/j.cpc.2018.02.014 + // - testcase=3 corresponds to the "No symmetry dummy" + // case in Section 5.1 of DOI 10.1016/j.cpc.2018.02.014 + if (testcase == 0) + std::wcout + << "canonicalizing network with 1 totally-symmetric tensor with " + "N indices and 1 asymmetric tensor with N indices\n"; + else if (testcase == 3) + std::wcout << "canonicalizing network with 1 asymmetric tensor with " + "N indices and 1 asymmetric tensor with N indices\n"; + else + std::wcout << "canonicalizing network with n equivalent asymmetric " + "tensors with N/n indices each and 1 asymmetric tensor " + "with N indices\n"; + + std::wcout << "N,n,min_time,geommean_time,max_time\n"; + + for (auto N : + {1, 2, 4, 8, 16, 32, 64, 128, 256}) { // total number of indices + + int n; + switch (testcase) { + case 0: + n = 1; + break; + case 1: + n = N / 2; + break; + case 2: + n = N; + break; + case 3: + n = 1; + break; + default: + abort(); + } + if (n == 0 || n > N) continue; + + auto ctx_resetter = set_scoped_default_context( + (static_cast(N) > Index::min_tmp_index()) + ? Context(get_default_context()) + .set_first_dummy_index_ordinal(N + 1) + : get_default_context()); + + // make list of indices + std::vector indices; + for (auto i = 0; i != N; ++i) { + std::wostringstream oss; + oss << "i_" << i; + indices.emplace_back(oss.str()); + } + std::random_device rd; + + // randomly sample connectivity between bra and ket tensors + const auto S = 10; // how many samples to take + + auto product_time = + 1.; // product of all times, need to get geometric mean + auto min_time = + std::numeric_limits::max(); // total time for all samples + auto max_time = + std::numeric_limits::min(); // total time for all samples + for (auto s = 0; s != S; ++s) { + // make tensors of independently (and randomly) permuted + // contravariant and covariant indices + auto contravariant_indices = indices; + auto covariant_indices = indices; + + std::shuffle(contravariant_indices.begin(), + contravariant_indices.end(), std::mt19937{rd()}); + std::shuffle(covariant_indices.begin(), covariant_indices.end(), + std::mt19937{rd()}); + + auto utensors = + covariant_indices | ranges::views::chunk(N / n) | + ranges::views::transform([&](const auto& idxs) { + return ex( + L"u", bra(idxs), ket{}, + (testcase == 3 + ? Symmetry::nonsymm + : ((n == 1) ? Symmetry::symm : Symmetry::nonsymm))); + }) | + ranges::to_vector; + CHECK(utensors.size() == static_cast(n)); + auto dtensors = + contravariant_indices | ranges::views::chunk(N) | + ranges::views::transform([&](const auto& idxs) { + return ex(L"d", bra{}, ket(idxs), Symmetry::nonsymm); + }) | + ranges::to_vector; + CHECK(dtensors.size() == 1); + + ExprPtr expr; + for (auto g = 0; g != n; ++g) { + if (g == 0) + expr = utensors[0] * dtensors[0]; + else + expr = expr * utensors[g]; + } + + TensorNetwork tn(expr->as().factors()); + + // produce misc data for publication + if (false && s == 0) { + std::wcout << "N=" << N << " n=" << n << " expr:\n" + << expr->to_latex() << std::endl; + + // make graph + REQUIRE_NOTHROW(tn.make_bliss_graph()); + auto [graph, vlabels, vcolors, vtypes] = tn.make_bliss_graph(); + + // create dot + std::basic_ostringstream oss; + REQUIRE_NOTHROW(graph->write_dot(oss, vlabels)); + std::wcout << "bliss graph:" << std::endl + << oss.str() << std::endl; + } + + sequant::TimerPool<> timer; + timer.start(); + tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), + false); + timer.stop(); + const auto elapsed_seconds = timer.read(); + product_time *= elapsed_seconds; + min_time = std::min(min_time, elapsed_seconds); + max_time = std::max(max_time, elapsed_seconds); + } + + const auto geommean_time = std::pow(product_time, 1. / S); + std::wcout << N << "," << n << "," << min_time << "," << geommean_time + << "," << max_time << "\n"; + } + } + } + + } // SECTION("misc1") + +} // TEST_CASE("TensorNetwork") + std::string to_utf8(const std::wstring& wstr) { using convert_type = std::codecvt_utf8; std::wstring_convert converter; @@ -72,10 +609,10 @@ sequant::ExprPtr to_product(const Container& container) { } namespace sequant { -class TensorNetworkAccessor { +class TensorNetworkV2Accessor { public: auto get_canonical_bliss_graph( - sequant::TensorNetwork tn, + sequant::TensorNetworkV2 tn, const sequant::TensorNetwork::named_indices_t* named_indices = nullptr) { tn.canonicalize_graph(named_indices ? *named_indices : tn.ext_indices_); tn.init_edges(); @@ -85,7 +622,7 @@ class TensorNetworkAccessor { }; } // namespace sequant -TEST_CASE("TensorNetwork", "[elements]") { +TEST_CASE("TensorNetworkV2", "[elements]") { using namespace sequant; using namespace sequant::mbpt; using sequant::Context; @@ -97,9 +634,9 @@ TEST_CASE("TensorNetwork", "[elements]") { BraKetSymmetry::conjugate, SPBasis::spinorbital)); SECTION("Edges") { - using Vertex = TensorNetwork::Vertex; - using Edge = TensorNetwork::Edge; - using Origin = TensorNetwork::Origin; + using Vertex = TensorNetworkV2::Vertex; + using Edge = TensorNetworkV2::Edge; + using Origin = TensorNetworkV2::Origin; Vertex v1(Origin::Bra, 0, 1, Symmetry::antisymm); Vertex v2(Origin::Bra, 0, 0, Symmetry::antisymm); @@ -158,10 +695,10 @@ TEST_CASE("TensorNetwork", "[elements]") { auto t1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}); auto t2 = ex(L"t", bra{L"i_1"}, ket{L"i_2"}); auto t1_x_t2 = t1 * t2; - REQUIRE_NOTHROW(TensorNetwork(*t1_x_t2)); + REQUIRE_NOTHROW(TensorNetworkV2(*t1_x_t2)); auto t1_x_t2_p_t2 = t1 * (t2 + t2); // can only use a flat tensor product - REQUIRE_THROWS_AS(TensorNetwork(*t1_x_t2_p_t2), std::logic_error); + REQUIRE_THROWS_AS(TensorNetworkV2(*t1_x_t2_p_t2), std::logic_error); } { // with NormalOperators @@ -169,12 +706,12 @@ TEST_CASE("TensorNetwork", "[elements]") { auto t1 = ex(cre({L"i_1"}), ann({L"i_2"}), V); auto t2 = ex(cre({L"i_2"}), ann({L"i_1"}), V); auto t1_x_t2 = t1 * t2; - REQUIRE_NOTHROW(TensorNetwork(*t1_x_t2)); + REQUIRE_NOTHROW(TensorNetworkV2(*t1_x_t2)); } { // with Tensors and NormalOperators auto tmp = t::A(nₚ(-2)) * t::H_(2) * t::T_(2) * t::T_(2); - REQUIRE_NOTHROW(TensorNetwork(tmp->as().factors())); + REQUIRE_NOTHROW(TensorNetworkV2(tmp->as().factors())); } } // SECTION("constructors") @@ -185,8 +722,8 @@ TEST_CASE("TensorNetwork", "[elements]") { auto t1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}); auto t2 = ex(cre({L"i_1"}), ann({L"i_3"}), V); auto t1_x_t2 = t1 * t2; - REQUIRE_NOTHROW(TensorNetwork(*t1_x_t2)); - TensorNetwork tn(*t1_x_t2); + REQUIRE_NOTHROW(TensorNetworkV2(*t1_x_t2)); + TensorNetworkV2 tn(*t1_x_t2); // edges auto edges = tn.edges(); @@ -213,7 +750,7 @@ TEST_CASE("TensorNetwork", "[elements]") { auto t1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}); auto t2 = ex(cre({L"i_1"}), ann({L"i_2"}), V); auto t1_x_t2 = t1 * t2; - TensorNetwork tn(*t1_x_t2); + TensorNetworkV2 tn(*t1_x_t2); tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); REQUIRE(size(tn.tensors()) == 2); @@ -239,7 +776,7 @@ TEST_CASE("TensorNetwork", "[elements]") { // with all external named indices SECTION("implicit") { - TensorNetwork tn(*t1_x_t2); + TensorNetworkV2 tn(*t1_x_t2); tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); REQUIRE(size(tn.tensors()) == 2); @@ -259,9 +796,9 @@ TEST_CASE("TensorNetwork", "[elements]") { // with explicit named indices SECTION("explicit") { Index::reset_tmp_index(); - TensorNetwork tn(*t1_x_t2); + TensorNetworkV2 tn(*t1_x_t2); - using named_indices_t = TensorNetwork::named_indices_t; + using named_indices_t = TensorNetworkV2::named_indices_t; named_indices_t indices{Index{L"i_17"}}; tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false, &indices); @@ -289,7 +826,7 @@ TEST_CASE("TensorNetwork", "[elements]") { for (int variant : {1, 2}) { for (bool fast : {true, false}) { - TensorNetwork tn( + TensorNetworkV2 tn( std::vector{variant == 1 ? input1 : input2}); tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), fast); REQUIRE(tn.tensors().size() == 1); @@ -308,7 +845,7 @@ TEST_CASE("TensorNetwork", "[elements]") { L"A{i_1,i_2;i_3,i_4}:A * I1{i_1,i_2;;x_1}:N * I2{;i_3,i_4;x_1}:N"; for (bool fast : {true, false}) { - TensorNetwork tn(input); + TensorNetworkV2 tn(input); tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), fast); const auto result = ex(to_tensors(tn.tensors())); REQUIRE(deparse(result) == expected); @@ -329,27 +866,27 @@ TEST_CASE("TensorNetwork", "[elements]") { const auto first = parse_expr(pair.first).as().factors(); const auto second = parse_expr(pair.second).as().factors(); - TensorNetworkAccessor accessor; + TensorNetworkV2Accessor accessor; auto [first_graph, first_labels] = - accessor.get_canonical_bliss_graph(TensorNetwork(first)); + accessor.get_canonical_bliss_graph(TensorNetworkV2(first)); auto [second_graph, second_labels] = - accessor.get_canonical_bliss_graph(TensorNetwork(second)); + accessor.get_canonical_bliss_graph(TensorNetworkV2(second)); if (first_graph->cmp(*second_graph) != 0) { std::wstringstream stream; stream << "First graph:\n"; first_graph->write_dot(stream, first_labels, true); stream << "Second graph:\n"; second_graph->write_dot(stream, second_labels, true); - stream << "Wick graph:\n"; + stream << "TN graph:\n"; auto [wick_graph, labels, d1, d2] = - WickGraph(first).make_bliss_graph(); + TensorNetwork(first).make_bliss_graph(); wick_graph->write_dot(stream, labels, true); FAIL(to_utf8(stream.str())); } - TensorNetwork tn1(first); - TensorNetwork tn2(second); + TensorNetworkV2 tn1(first); + TensorNetworkV2 tn2(second); tn1.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); tn2.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); @@ -381,7 +918,7 @@ TEST_CASE("TensorNetwork", "[elements]") { for (const auto& [input, expected] : inputs) { const auto input_tensors = parse_expr(input).as().factors(); - TensorNetwork tn(input_tensors); + TensorNetworkV2 tn(input_tensors); ExprPtr factor = tn.canonicalize( TensorCanonicalizer::cardinal_tensor_labels(), true); @@ -408,9 +945,9 @@ TEST_CASE("TensorNetwork", "[elements]") { const auto expected = expectedExpr.factors(); - TensorNetworkAccessor accessor; + TensorNetworkV2Accessor accessor; const auto [canonical_graph, canonical_graph_labels] = - accessor.get_canonical_bliss_graph(TensorNetwork(expected)); + accessor.get_canonical_bliss_graph(TensorNetworkV2(expected)); std::wcout << "Canonical graph:\n"; canonical_graph->write_dot(std::wcout, canonical_graph_labels); @@ -472,7 +1009,7 @@ TEST_CASE("TensorNetwork", "[elements]") { reset_tags(expr.as()); } - TensorNetwork tn(copy); + TensorNetworkV2 tn(copy); // At the heart of our canonicalization lies the fact that we can // always create the uniquely defined canonical graph for a given @@ -499,7 +1036,7 @@ TEST_CASE("TensorNetwork", "[elements]") { // The canonical graph must not change due to the other // canonicalization steps we perform - REQUIRE(accessor.get_canonical_bliss_graph(TensorNetwork(actual)) + REQUIRE(accessor.get_canonical_bliss_graph(TensorNetworkV2(actual)) .first->cmp(*canonical_graph) == 0); REQUIRE(actual.size() == expected.size()); @@ -542,11 +1079,11 @@ TEST_CASE("TensorNetwork", "[elements]") { auto factors1 = parse_expr(current).as().factors(); auto factors2 = parse_expr(current).as().factors(); - TensorNetwork reference_tn(factors1); + TensorNetworkV2 reference_tn(factors1); reference_tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); - TensorNetwork check_tn(factors2); + TensorNetworkV2 check_tn(factors2); check_tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false); @@ -578,14 +1115,14 @@ TEST_CASE("TensorNetwork", "[elements]") { auto tmp = g * ta * tb; // std::wcout << "TN1 = " << to_latex(tmp) << std::endl; - TensorNetwork tn(tmp->as().factors()); + TensorNetworkV2 tn(tmp->as().factors()); // make graph // N.B. treat all indices as dummy so that the automorphism ignores the - using named_indices_t = TensorNetwork::named_indices_t; + using named_indices_t = TensorNetworkV2::named_indices_t; named_indices_t indices{}; REQUIRE_NOTHROW(tn.create_graph(&indices)); - TensorNetwork::Graph graph = tn.create_graph(&indices); + TensorNetworkV2::Graph graph = tn.create_graph(&indices); // create dot { @@ -722,7 +1259,7 @@ TEST_CASE("TensorNetwork", "[elements]") { expr = expr * utensors[g]; } - TensorNetwork tn(expr->as().factors()); + TensorNetworkV2 tn(expr->as().factors()); // produce misc data for publication if (false && s == 0) { @@ -731,7 +1268,7 @@ TEST_CASE("TensorNetwork", "[elements]") { // make graph REQUIRE_NOTHROW(tn.create_graph()); - TensorNetwork::Graph graph = tn.create_graph(); + TensorNetworkV2::Graph graph = tn.create_graph(); // create dot std::basic_ostringstream oss; @@ -761,4 +1298,4 @@ TEST_CASE("TensorNetwork", "[elements]") { } // SECTION("misc1") -} // TEST_CASE("TensorNetwork") +} // TEST_CASE("TensorNetworkV2") From 7e9ca6ed769c100727f8d3d63ffb59a751cf09cb Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 00:15:30 -0500 Subject: [PATCH 40/85] typo --- SeQuant/core/algorithm.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SeQuant/core/algorithm.hpp b/SeQuant/core/algorithm.hpp index 6581aeafd..ff4143e40 100644 --- a/SeQuant/core/algorithm.hpp +++ b/SeQuant/core/algorithm.hpp @@ -39,8 +39,8 @@ void bubble_sort(ForwardIter begin, Sentinel end, Compare comp) { const auto& val0 = *inext; const auto& val1 = *i; if (comp(val0, val1)) { - // current assumption: whenever iter_wap from below does not fall back - // to std::iter_swap, we are handling zipped ranges where the + // current assumption: whenever iter_swap from below does not fall + // back to std::iter_swap, we are handling zipped ranges where the // tuple sizes is two (even) -> thus using a non-std swap // implementation won't mess with the information of whether or not an // even amount of swaps has occurred. From 5b2fe769e420c163bfd02d16719752bbfe7403d5 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 00:16:25 -0500 Subject: [PATCH 41/85] DefaultTensorCanonicalizer::apply: early exit if 1-body 0-aux operator (not 1-aux) --- SeQuant/core/tensor_canonicalizer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeQuant/core/tensor_canonicalizer.hpp b/SeQuant/core/tensor_canonicalizer.hpp index 818e03de8..a8214476b 100644 --- a/SeQuant/core/tensor_canonicalizer.hpp +++ b/SeQuant/core/tensor_canonicalizer.hpp @@ -165,7 +165,7 @@ class DefaultTensorCanonicalizer : public TensorCanonicalizer { const auto _rank = std::min(_bra_rank, _ket_rank); // nothing to do for rank-1 tensors - if (_bra_rank == 1 && _ket_rank == 1 && _aux_rank == 1) return nullptr; + if (_bra_rank == 1 && _ket_rank == 1 && _aux_rank == 0) return nullptr; using ranges::begin; using ranges::end; From ba832da3054fc181fee88d7c45f627a5ea9f1ae7 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 00:17:20 -0500 Subject: [PATCH 42/85] TensorBlockIndexComparer uses Index::operator< which is the right way to compare Indices --- SeQuant/core/tensor_canonicalizer.cpp | 66 ++------------------------- 1 file changed, 3 insertions(+), 63 deletions(-) diff --git a/SeQuant/core/tensor_canonicalizer.cpp b/SeQuant/core/tensor_canonicalizer.cpp index c3e4e99e8..de7961e06 100644 --- a/SeQuant/core/tensor_canonicalizer.cpp +++ b/SeQuant/core/tensor_canonicalizer.cpp @@ -49,81 +49,21 @@ struct TensorBlockIndexComparer { static_assert(std::is_same_v, Index>, "TensorBlockIndexComparer can only work with indices"); - // First compare only index spaces of equivalent pairs - int res = compare_spaces(lhs_first, rhs_first); + int res = lhs_first < rhs_first ? -1 : (rhs_first < lhs_first ? 1 : 0); if (res != 0) { return res; } - res = compare_spaces(lhs_second, rhs_second); - if (res != 0) { - return res; - } - - // Then consider tags of equivalent pairs - res = compare_tags(lhs_first, rhs_first); - if (res != 0) { - return res; - } - - res = compare_tags(lhs_second, rhs_second); + res = lhs_second < rhs_second ? -1 : (rhs_second < lhs_second ? 1 : 0); return res; } else { static_assert(std::is_same_v, Index>, "TensorBlockIndexComparer can only work with indices"); - int res = compare_spaces(lhs, rhs); - if (res != 0) { - return res; - } - - res = compare_tags(lhs, rhs); + int res = lhs < rhs ? -1 : (rhs < lhs ? 1 : 0); return res; } } - - int compare_spaces(const Index& lhs, const Index& rhs) const { - if (lhs.space() != rhs.space()) { - return lhs.space() < rhs.space() ? -1 : 1; - } - - if (lhs.has_proto_indices() != rhs.has_proto_indices()) { - return lhs.has_proto_indices() ? -1 : 1; - } - - if (lhs.proto_indices().size() != rhs.proto_indices().size()) { - return lhs.proto_indices().size() < rhs.proto_indices().size() ? -1 : 1; - } - - for (std::size_t i = 0; i < lhs.proto_indices().size(); ++i) { - const auto& lhs_proto = lhs.proto_indices()[i]; - const auto& rhs_proto = rhs.proto_indices()[i]; - - int res = compare_spaces(lhs_proto, rhs_proto); - if (res != 0) { - return res; - } - } - - // Index spaces are equal - return 0; - } - - int compare_tags(const Index& lhs, const Index& rhs) const { - if (!lhs.tag().has_value() || !rhs.tag().has_value()) { - // We only compare tags if both indices have a tag - return 0; - } - - const int lhs_tag = lhs.tag().value(); - const int rhs_tag = rhs.tag().value(); - - if (lhs_tag != rhs_tag) { - return lhs_tag < rhs_tag ? -1 : 1; - } - - return 0; - } }; struct TensorIndexComparer { From 8bfe40e0e3f0469f6552405bc2e98618a2aa35b5 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 00:18:17 -0500 Subject: [PATCH 43/85] test_tensor_network.cpp: bliss graph ref output updated --- tests/unit/test_tensor_network.cpp | 120 ++++++++++++++--------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 17d7dcb97..8aa06884c 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -215,136 +215,136 @@ TEST_CASE("TensorNetwork", "[elements]") { // std::wcout << "oss.str() = " << std::endl << oss.str() << std::endl; REQUIRE(oss.str() == L"graph g {\n" - "v0 [label=\"{a_1}\"; color=\"#64f,acf\"];\n" + "v0 [label=\"{a_1}\"; color=\"#64facf\"];\n" "v0 -- v29\n" "v0 -- v58\n" - "v1 [label=\"{a_2}\"; color=\"#64f,acf\"];\n" + "v1 [label=\"{a_2}\"; color=\"#64facf\"];\n" "v1 -- v29\n" "v1 -- v58\n" - "v2 [label=\"{a_3}\"; color=\"#64f,acf\"];\n" + "v2 [label=\"{a_3}\"; color=\"#64facf\"];\n" "v2 -- v33\n" "v2 -- v54\n" - "v3 [label=\"{a_4}\"; color=\"#64f,acf\"];\n" + "v3 [label=\"{a_4}\"; color=\"#64facf\"];\n" "v3 -- v33\n" "v3 -- v54\n" - "v4 [label=\"{a_5}\"; color=\"#64f,acf\"];\n" + "v4 [label=\"{a_5}\"; color=\"#64facf\"];\n" "v4 -- v37\n" "v4 -- v50\n" - "v5 [label=\"{a_6}\"; color=\"#64f,acf\"];\n" + "v5 [label=\"{a_6}\"; color=\"#64facf\"];\n" "v5 -- v37\n" "v5 -- v50\n" - "v6 [label=\"{a_7}\"; color=\"#64f,acf\"];\n" + "v6 [label=\"{a_7}\"; color=\"#64facf\"];\n" "v6 -- v22\n" "v6 -- v41\n" - "v7 [label=\"{a_8}\"; color=\"#64f,acf\"];\n" + "v7 [label=\"{a_8}\"; color=\"#64facf\"];\n" "v7 -- v22\n" "v7 -- v41\n" - "v8 [label=\"{i_1}\"; color=\"#9c2,a20\"];\n" + "v8 [label=\"{i_1}\"; color=\"#9c2a20\"];\n" "v8 -- v30\n" "v8 -- v57\n" - "v9 [label=\"{i_2}\"; color=\"#9c2,a20\"];\n" + "v9 [label=\"{i_2}\"; color=\"#9c2a20\"];\n" "v9 -- v30\n" "v9 -- v57\n" - "v10 [label=\"{i_3}\"; color=\"#9c2,a20\"];\n" + "v10 [label=\"{i_3}\"; color=\"#9c2a20\"];\n" "v10 -- v34\n" "v10 -- v53\n" - "v11 [label=\"{i_4}\"; color=\"#9c2,a20\"];\n" + "v11 [label=\"{i_4}\"; color=\"#9c2a20\"];\n" "v11 -- v34\n" "v11 -- v53\n" - "v12 [label=\"{i_5}\"; color=\"#9c2,a20\"];\n" + "v12 [label=\"{i_5}\"; color=\"#9c2a20\"];\n" "v12 -- v38\n" "v12 -- v49\n" - "v13 [label=\"{i_6}\"; color=\"#9c2,a20\"];\n" + "v13 [label=\"{i_6}\"; color=\"#9c2a20\"];\n" "v13 -- v38\n" "v13 -- v49\n" - "v14 [label=\"{i_7}\"; color=\"#9c2,a20\"];\n" + "v14 [label=\"{i_7}\"; color=\"#9c2a20\"];\n" "v14 -- v21\n" "v14 -- v42\n" - "v15 [label=\"{i_8}\"; color=\"#9c2,a20\"];\n" + "v15 [label=\"{i_8}\"; color=\"#9c2a20\"];\n" "v15 -- v21\n" "v15 -- v42\n" - "v16 [label=\"{\\kappa_1}\"; color=\"#712,6de\"];\n" + "v16 [label=\"{\\kappa_1}\"; color=\"#7126de\"];\n" "v16 -- v25\n" "v16 -- v46\n" - "v17 [label=\"{\\kappa_2}\"; color=\"#712,6de\"];\n" + "v17 [label=\"{\\kappa_2}\"; color=\"#7126de\"];\n" "v17 -- v25\n" "v17 -- v46\n" - "v18 [label=\"{\\kappa_3}\"; color=\"#712,6de\"];\n" + "v18 [label=\"{\\kappa_3}\"; color=\"#7126de\"];\n" "v18 -- v26\n" "v18 -- v45\n" - "v19 [label=\"{\\kappa_4}\"; color=\"#712,6de\"];\n" + "v19 [label=\"{\\kappa_4}\"; color=\"#7126de\"];\n" "v19 -- v26\n" "v19 -- v45\n" - "v20 [label=\"A\"; color=\"#518,020\"];\n" + "v20 [label=\"A\"; color=\"#518020\"];\n" "v20 -- v23\n" - "v21 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v21 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v21 -- v23\n" - "v22 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v22 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v22 -- v23\n" - "v23 [label=\"bka\"; color=\"#518,020\"];\n" - "v24 [label=\"g\"; color=\"#2e0,351\"];\n" + "v23 [label=\"bka\"; color=\"#518020\"];\n" + "v24 [label=\"g\"; color=\"#2e0351\"];\n" "v24 -- v27\n" - "v25 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v25 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v25 -- v27\n" - "v26 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v26 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v26 -- v27\n" - "v27 [label=\"bka\"; color=\"#2e0,351\"];\n" - "v28 [label=\"t\"; color=\"#43,e44\"];\n" + "v27 [label=\"bka\"; color=\"#2e0351\"];\n" + "v28 [label=\"t\"; color=\"#043e44\"];\n" "v28 -- v31\n" - "v29 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v29 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v29 -- v31\n" - "v30 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v30 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v30 -- v31\n" - "v31 [label=\"bka\"; color=\"#43,e44\"];\n" - "v32 [label=\"t\"; color=\"#43,e44\"];\n" + "v31 [label=\"bka\"; color=\"#043e44\"];\n" + "v32 [label=\"t\"; color=\"#043e44\"];\n" "v32 -- v35\n" - "v33 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v33 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v33 -- v35\n" - "v34 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v34 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v34 -- v35\n" - "v35 [label=\"bka\"; color=\"#43,e44\"];\n" - "v36 [label=\"t\"; color=\"#43,e44\"];\n" + "v35 [label=\"bka\"; color=\"#043e44\"];\n" + "v36 [label=\"t\"; color=\"#043e44\"];\n" "v36 -- v39\n" - "v37 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v37 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v37 -- v39\n" - "v38 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v38 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v38 -- v39\n" - "v39 [label=\"bka\"; color=\"#43,e44\"];\n" - "v40 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v39 [label=\"bka\"; color=\"#043e44\"];\n" + "v40 [label=\"ã\"; color=\"#cbfbe5\"];\n" "v40 -- v43\n" - "v41 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v41 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v41 -- v43\n" - "v42 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v42 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v42 -- v43\n" - "v43 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "v44 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v43 [label=\"bka\"; color=\"#cbfbe5\"];\n" + "v44 [label=\"ã\"; color=\"#cbfbe5\"];\n" "v44 -- v47\n" - "v45 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v45 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v45 -- v47\n" - "v46 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v46 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v46 -- v47\n" - "v47 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "v48 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v47 [label=\"bka\"; color=\"#cbfbe5\"];\n" + "v48 [label=\"ã\"; color=\"#cbfbe5\"];\n" "v48 -- v51\n" - "v49 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v49 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v49 -- v51\n" - "v50 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v50 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v50 -- v51\n" - "v51 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "v52 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v51 [label=\"bka\"; color=\"#cbfbe5\"];\n" + "v52 [label=\"ã\"; color=\"#cbfbe5\"];\n" "v52 -- v55\n" - "v53 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v53 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v53 -- v55\n" - "v54 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v54 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v54 -- v55\n" - "v55 [label=\"bka\"; color=\"#cbf,be5\"];\n" - "v56 [label=\"ã\"; color=\"#cbf,be5\"];\n" + "v55 [label=\"bka\"; color=\"#cbfbe5\"];\n" + "v56 [label=\"ã\"; color=\"#cbfbe5\"];\n" "v56 -- v59\n" - "v57 [label=\"bra2a\"; color=\"#eaa,2ab\"];\n" + "v57 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" "v57 -- v59\n" - "v58 [label=\"ket2a\"; color=\"#5a8,fd3\"];\n" + "v58 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" "v58 -- v59\n" - "v59 [label=\"bka\"; color=\"#cbf,be5\"];\n" + "v59 [label=\"bka\"; color=\"#cbfbe5\"];\n" "}\n"); // compute automorphism group From 4a9543851105e2bae0939495986351fe74930d70 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 00:27:25 -0500 Subject: [PATCH 44/85] amend tests for ba832da3054fc181fee88d7c45f627a5ea9f1ae7 --- tests/unit/test_canonicalize.cpp | 41 +++---- tests/unit/test_spin.cpp | 170 ++++++++++++++--------------- tests/unit/test_tensor_network.cpp | 2 +- 3 files changed, 102 insertions(+), 111 deletions(-) diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 6330e7742..87d5d1190 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -65,8 +65,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == - L"{{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{" - L"a_3}}}{t^{{a_2}}_{{i_3}}}{t^{{a_1}{a_3}}_{{i_1}{i_2}}}}"); + L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" + L"a_2}}}{t^{{a_3}}_{{i_3}}}{t^{{a_1}{a_2}}_{{i_1}{i_2}}}}"); } { auto input = @@ -78,8 +78,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == - L"{{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{" - L"a_3}}}{t^{{a_3}}_{{i_2}}}{t^{{a_1}{a_2}}_{{i_1}{i_3}}}}"); + L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" + L"a_2}}}{t^{{a_2}}_{{i_2}}}{t^{{a_1}{a_3}}_{{i_1}{i_3}}}}"); } { // Product containing Variables auto q2 = ex(L"q2"); @@ -95,8 +95,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { Symmetry::nonsymm); canonicalize(input); REQUIRE(to_latex(input) == - L"{{p}{q1}{{q2}^*}{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{" - L"a_3}}}{t^{{a_3}}_{{i_2}}}{t^{{a_1}{a_2}}_{{i_1}{i_3}}}}"); + L"{{p}{q1}{{q2}^*}{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" + L"a_2}}}{t^{{a_2}}_{{i_2}}}{t^{{a_1}{a_3}}_{{i_1}{i_3}}}}"); } { // Product containing adjoint of a Tensor auto f2 = ex(L"f", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, @@ -119,8 +119,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"w") * ex(rational{1, 2}); canonicalize(input2); REQUIRE(to_latex(input2) == - L"{{{\\frac{1}{2}}}{w}{S^{{i_1}{i_2}}_{{a_1}{a_2}}}{f^{{i_3}}_{{" - L"a_3}}}{f⁺^{{i_1}{i_3}}_{{a_1}{a_2}}}{t^{{a_3}}_{{i_2}}}}"); + L"{{{\\frac{1}{2}}}{w}{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" + L"a_2}}}{f⁺^{{i_1}{i_3}}_{{a_1}{a_3}}}{t^{{a_2}}_{{i_2}}}}"); } } @@ -141,8 +141,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(to_latex(input) == L"{ " - L"\\bigl({{g^{{p_3}{p_4}}_{{p_1}{p_2}}}{t^{{p_1}}_{{p_3}}}{t^{{p_" - L"2}}_{{p_4}}}}\\bigr) }"); + L"\\bigl({{g^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}{t^{{" + L"p_3}}_{{p_4}}}}\\bigr) }"); } // CASE 2: Symmetric tensors @@ -161,8 +161,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(to_latex(input) == L"{ " - L"\\bigl({{g^{{p_3}{p_4}}_{{p_1}{p_2}}}{t^{{p_1}}_{{p_3}}}{t^{{p_" - L"2}}_{{p_4}}}}\\bigr) }"); + L"\\bigl({{g^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}{t^{{p_" + L"3}}_{{p_4}}}}\\bigr) }"); } // Case 3: Anti-symmetric tensors @@ -181,8 +181,9 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(to_latex(input) == L"{ " - L"\\bigl({{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}{t^{{p_1}}_{{p_3}}}" - L"{t^{{p_2}}_{{p_4}}}}\\bigr) }"); + L"\\bigl({{\\bar{g}^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}" + L"{t^{{p_" + L"3}}_{{p_4}}}}\\bigr) }"); } // Case 4: permuted indices @@ -228,8 +229,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { REQUIRE(input->size() == 1); REQUIRE(to_latex(input) == L"{ " - L"\\bigl({{g^{{i_1}{a_3}}_{{i_3}{i_4}}}{t^{{i_4}}_{{a_2}}}{t^{{i_" - L"3}{i_2}}_{{a_1}{a_3}}}}\\bigr) }"); + L"\\bigl({{g^{{a_3}{i_1}}_{{i_3}{i_4}}}{t^{{i_3}}_{{a_2}}}{t^{{i_" + L"4}{i_2}}_{{a_1}{a_3}}}}\\bigr) }"); } { // Case 5: CCSDT R3: S3 * F * T3 @@ -273,11 +274,11 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(term1); canonicalize(term2); REQUIRE(to_latex(term1) == - L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_" - L"4}}}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}}"); + L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_3}{i_4}}}{f^{{i_4}}_{{i_" + L"2}}}{t^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}}"); REQUIRE(to_latex(term2) == - L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_" - L"4}}}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}}"); + L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_3}{i_4}}}{f^{{i_4}}_{{i_" + L"2}}}{t^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}}"); auto sum_of_terms = term1 + term2; simplify(sum_of_terms); REQUIRE(to_latex(sum_of_terms) == diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 638635762..8da3b9e72 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -557,21 +557,21 @@ SECTION("Expand Symmetrizer") { REQUIRE(result->size() == 6); result->canonicalize(); rapid_simplify(result); - REQUIRE_SUM_EQUAL( - result, + REQUIRE( + to_latex(result) == L"{ " L"\\bigl({{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{" - L"i_3}}_{{a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_2}{i_5}}_{{a_3}{a_1}}}} + " + L"i_3}}_{{a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_2}}_{{a_1}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_1}}}{t^{{i_2}}_{{" + L"a_4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_1}{i_5}}_{{a_2}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_1}}}{t^{{i_3}}_{{" + L"a_4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_5}{i_1}}_{{a_2}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_3}}}{t^{{i_2}}_{{" + L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_3}{i_4}}_{{a_1}{a_2}}}} + " L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_2}}_{{" - L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_3}{i_5}}_{{a_2}{a_1}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_1}}}{t^{{i_2}}_{{" - L"a_4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_1}{i_4}}_{{a_3}{a_2}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_1}}}{t^{{i_3}}_{{" - L"a_4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_1}{i_4}}_{{a_2}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{i_1}}_{{" - L"a_4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_2}{i_5}}_{{a_1}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_1}}_{{" - L"a_4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_3}{i_5}}_{{a_1}{a_2}}}}\\bigr) }"); + L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_3}}_{{a_1}{a_2}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_2}}}{t^{{i_3}}_{{" + L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_2}{i_4}}_{{a_1}{a_3}}}}\\bigr) }"); } } @@ -609,8 +609,8 @@ SECTION("Symmetrize expression") { ex(L"t", bra{L"a_3"}, ket{L"i_1"}); 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}}}}"); + L"{{S^{{a_2}{a_1}}_{{i_3}{i_4}}}{g^{{i_4}{a_3}}_{{i_1}{i_2}}}{t^{" + L"{i_1}}_{{a_1}}}{t^{{i_2}}_{{a_2}}}{t^{{i_3}}_{{a_3}}}}"); } { @@ -633,8 +633,8 @@ SECTION("Symmetrize expression") { REQUIRE(result->is() == false); REQUIRE( to_latex(result) == - L"{{{2}}{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{a_3}{a_4}}_{{i_3}{i_4}}}{t^{{" - L"i_4}}_{{a_2}}}{t^{{i_3}}_{{a_3}}}{t^{{i_1}{i_2}}_{{a_1}{a_4}}}}"); + L"{{{2}}{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{a_3}{a_4}}_{{i_3}{i_4}}}{t^" + L"{{i_3}}_{{a_4}}}{t^{{i_4}}_{{a_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_3}}}}"); } } @@ -652,8 +652,8 @@ SECTION("Transform expression") { canonicalize(result); REQUIRE_SUM_EQUAL( result, - L"{ \\bigl({{{2}}{g^{{a_2}{i_1}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}} - " - L"{{g^{{i_1}{a_2}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); + L"{ \\bigl( - {{g^{{a_2}{i_1}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}} + " + L"{{{2}}{g^{{i_1}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); container::map idxmap = {{Index{L"i_1"}, Index{L"i_2"}}, {Index{L"i_2"}, Index{L"i_1"}}}; @@ -662,8 +662,8 @@ SECTION("Transform expression") { REQUIRE(transformed_result->size() == 2); REQUIRE_SUM_EQUAL( transformed_result, - L"{ \\bigl({{{2}}{g^{{a_2}{i_2}}_{{i_1}{a_1}}}{t^{{i_1}}_{{a_2}}}} - " - L"{{g^{{i_2}{a_2}}_{{i_1}{a_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); + L"{ \\bigl( - {{g^{{a_2}{i_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}} + " + L"{{{2}}{g^{{i_2}{a_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); } SECTION("Swap bra kets") { @@ -832,9 +832,9 @@ SECTION("Closed-shell spintrace CCSD") { REQUIRE_SUM_EQUAL( result, L"{ \\bigl( - " - L"{{{2}}{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}" + L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}" L"}} + " - L"{{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}}}" + L"{{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}}}" L"\\bigr) }"); } @@ -852,12 +852,12 @@ SECTION("Closed-shell spintrace CCSD") { rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL(result, - L"{ " - L"\\bigl({{{2}}{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_1}{i_" - L"2}}_{{a_3}{a_2}}}} - " - L"{{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_1}{i_2}}_{{a_2}{a_" - L"3}}}}\\bigr) }"); + REQUIRE_SUM_EQUAL( + result, + L"{ \\bigl( - " + L"{{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_1}{i_2}}_{{a_2}{a_3}}}} + " + L"{{{2}}{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}{i_1}}_{{a_2}{a_3}}" + L"}}\\bigr) }"); } { @@ -872,10 +872,10 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL( - result, - L"{ \\bigl({{{2}}{f^{{a_2}}_{{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}}} - " - L"{{f^{{a_2}}_{{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{a_2}}}}\\bigr) }"); + REQUIRE( + to_latex(result) == + L"{ \\bigl( - {{f^{{a_2}}_{{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{a_2}}}} + " + L"{{{2}}{f^{{a_2}}_{{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}}}\\bigr) }"); } { @@ -890,13 +890,12 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL( - result, - L"{ " - L"\\bigl({{{2}}{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_2}}_{{a_2}}}{t^{" - L"{i_1}}_{{a_3}}}} - " - L"{{g^{{a_2}{a_3}}_{{i_2}{a_1}}}{t^{{i_1}}_{{a_2}}}{t^{{i_2}}_{{a_" - L"3}}}}\\bigr) }"); + REQUIRE(to_latex(result) == + L"{ " + L"\\bigl({{{2}}{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_1}}_{{a_3}}}{t^{" + L"{i_2}}_{{a_2}}}} - " + L"{{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_1}}_{{a_2}}}{t^{{i_2}}_{{a_" + L"3}}}}\\bigr) }"); } { @@ -911,13 +910,12 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL( - result, - L"{ \\bigl( - " - L"{{{2}}{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}}_" - L"{{a_2}}}} + " - L"{{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}}_{{a_" - L"2}}}}\\bigr) }"); + REQUIRE(to_latex(result) == + L"{ " + L"\\bigl({{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_" + L"3}}_{{a_2}}}} - " + L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}" + L"}_{{a_2}}}}\\bigr) }"); } { @@ -932,11 +930,10 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL( - result, - L"{ \\bigl( - " - L"{{f^{{a_2}}_{{i_2}}}{t^{{i_2}}_{{a_1}}}{t^{{i_1}}_{{a_2}}}}" - L"\\bigr) }"); + REQUIRE(to_latex(result) == + L"{ \\bigl( - " + L"{{f^{{a_2}}_{{i_2}}}{t^{{i_2}}_{{a_1}}}{t^{{i_1}}_{{a_2}}}}" + L"\\bigr) }"); } { @@ -953,12 +950,12 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL(result, - L"{ " - L"\\bigl({{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}" - L"}{t^{{i_1}{i_3}}_{{a_3}{a_2}}}} - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{" - L"t^{{i_1}{i_2}}_{{a_3}{a_2}}}}\\bigr) }"); + REQUIRE(to_latex(result) == + L"{ \\bigl( - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{" + L"i_2}{i_1}}_{{a_2}{a_3}}}} + " + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}{" + L"i_1}}_{{a_2}{a_3}}}}\\bigr) }"); } { @@ -975,14 +972,12 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL( - result, - L"{ \\bigl(" - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_2}}}{t^{{i_3}{i_2}}_" - L"{{a_1}{a_3}}}} - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}{" - L"i_2}}_{{a_1}{a_2}}}}" - L"\\bigr) }"); + REQUIRE(to_latex(result) == + L"{ \\bigl( - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}" + L"{i_2}}_{{a_1}{a_2}}}} + " + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_2}{i_3}" + L"}_{{a_1}{a_2}}}}\\bigr) }"); } { @@ -999,20 +994,16 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL(result, - L"{ \\bigl(" - L"{{{4}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_3}}}{" - L"t^{{i_1}{i_2}}_{{a_1}{a_2}}}}" - L" + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{" - L"i_3}{i_1}}_{{a_1}{a_2}}}}" - L" - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{" - L"t^{{i_3}{i_1}}_{{a_1}{a_3}}}}" - L" - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_2}}}{" - L"t^{{i_1}{i_2}}_{{a_1}{a_3}}}}" - L"\\bigr) }"); + REQUIRE(to_latex(result) == + L"{ \\bigl( - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{i_1}" + L"{i_3}}_{{a_1}{a_2}}}} + " + L"{{{4}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_1}" + L"{i_3}}_{{a_1}{a_3}}}} - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_3}" + L"{i_1}}_{{a_1}{a_3}}}} + " + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_2}}}{t^{{i_2}{i_1}" + L"}_{{a_1}{a_3}}}}\\bigr) }"); } { @@ -1028,14 +1019,12 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL(result, - L"{ " - L"\\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{" - L"t^{{i_2}}_{{a_2}}}{t^{{i_1}}_{{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{" - L"i_1}}_{{a_2}}}{t^{{i_2}}_{{a_3}}}}" - L"\\bigr) }"); + REQUIRE(to_latex(result) == + L"{ \\bigl( - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}" + L"}_{{a_2}}}{t^{{i_1}}_{{a_3}}}} + " + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}}_{{" + L"a_2}}}{t^{{i_1}}_{{a_3}}}}\\bigr) }"); } } // CCSD R1 @@ -1390,13 +1379,14 @@ SECTION("Open-shell spin-tracing") { REQUIRE(result.size() == 3); 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}}}}")); + toUtf8( + L"{{{-\\frac{1}{2}}}{\\bar{g}^{{i↑_1}{i↑_2}}_{{a↑_1}{i↑_3}}}{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}}}}"); REQUIRE(to_latex(result[2]) == - L"{{{\\frac{1}{2}}}{\\bar{g}^{{i↓_1}{i↓_2}}_{{i↓_3}{a↓_1}}}{t^{{" + L"{{{-\\frac{1}{2}}}{\\bar{g}^{{i↓_1}{i↓_2}}_{{a↓_1}{i↓_3}}}{t^{{" L"i↓_3}}_{{a↓_2}}}}"); } diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 8aa06884c..aaa8ed360 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -912,7 +912,7 @@ TEST_CASE("TensorNetworkV2", "[elements]") { {L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N"}, {L"g{a_1,i_1;i_2,i_3}:N * I{i_2,i_3;i_1,a_1}:N", - L"g{i_1,a_1;i_2,i_3}:N * I{i_3,i_2;i_1,a_1}:N"}, + L"g{i_1,a_1;i_2,i_3}:N * I{i_2,i_3;a_1,i_1}:N"}, }; for (const auto& [input, expected] : inputs) { From 2e8c90907816c3ec6ee377bd068da77a240ca08a Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 00:42:27 -0500 Subject: [PATCH 45/85] initial support for aux indices in old TensorNetwork --- SeQuant/core/tensor_network.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index f7529969a..3e464b4ee 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -434,7 +434,7 @@ TensorNetwork::make_bliss_graph( std::vector vertex_type( edges_.size()); // the size will be updated - // N.B. Colors [0, 2 max rank + named_indices.size()) are reserved: + // N.B. Colors [0, 3 max rank + named_indices.size()) are reserved: // 0 - the bra vertex (for particle 0, if bra is nonsymm, or for the entire // bra, if (anti)symm) 1 - the bra vertex for particle 1, if bra is nonsymm // ... @@ -442,13 +442,15 @@ TensorNetwork::make_bliss_graph( // the entire ket, if particle-symmetric) max_rank+1 - the ket vertex for // particle 1, if particle-asymmetric // ... - // 2 max_rank - first named index - // 2 max_rank + 1 - second named index + // 2 max_rank - the aux index + // ... + // 3 max_rank - first named index + // 3 max_rank + 1 - second named index // ... // N.B. For braket-symmetric tensors the ket vertices use the same indices as // the bra vertices auto nonreserved_color = [&named_indices](size_t color) -> bool { - return color >= 2 * max_rank + named_indices.size(); + return color >= 3 * max_rank + named_indices.size(); }; // compute # of vertices @@ -477,7 +479,7 @@ TensorNetwork::make_bliss_graph( vertex_color.at(index_cnt) = idx_color; } else { const auto named_index_rank = named_index_it - named_indices.begin(); - vertex_color.at(index_cnt) = 2 * max_rank + named_index_rank; + vertex_color.at(index_cnt) = 3 * max_rank + named_index_rank; } // each symmetric proto index bundle will have a vertex ... // for now only store the unique protoindex bundles in @@ -574,6 +576,17 @@ TensorNetwork::make_bliss_graph( vertex_color.push_back(t_color); } } + // aux indices currently do not support any symmetry + assert(aux_rank(tref) <= max_rank); + for (size_t p = 0; p != aux_rank(tref); ++p) { + nv += 1; + auto pstr = to_wstring(p + 1); + vertex_labels.push_back(std::wstring(L"aux") + pstr); + vertex_type.push_back(VertexType::TensorAux); + const auto color = 2 * max_rank + p; + vertex_color.push_back(color); + } + ++tensor_cnt; }); From 1f80f517e30c5869a8ba2ac4eee0423d3f8bb699 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 00:55:50 -0500 Subject: [PATCH 46/85] amend test_spin.cpp for changes in canonicalization due to 2e8c90907816c3ec6ee377bd068da77a240ca08a --- tests/unit/test_spin.cpp | 91 ++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 8da3b9e72..f71be5ac4 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -560,18 +560,18 @@ SECTION("Expand Symmetrizer") { REQUIRE( to_latex(result) == L"{ " - L"\\bigl({{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{" - L"i_3}}_{{a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_2}}_{{a_1}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_1}}}{t^{{i_2}}_{{" - L"a_4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_1}{i_5}}_{{a_2}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_1}}}{t^{{i_3}}_{{" - L"a_4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_5}{i_1}}_{{a_2}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_3}}}{t^{{i_2}}_{{" - L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_3}{i_4}}_{{a_1}{a_2}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_2}}_{{" - L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_3}}_{{a_1}{a_2}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_2}}}{t^{{i_3}}_{{" - L"a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_2}{i_4}}_{{a_1}{a_3}}}}\\bigr) }"); + L"\\bigl({{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{i_" + L"3}}_{{a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_2}}_{{a_1}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_1}}}{t^{{i_2}}_{{a_" + L"4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_4}{i_1}}_{{a_2}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_1}}_{{a_" + L"4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_3}{i_5}}_{{a_1}{a_2}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_1}}}{t^{{i_3}}_{{a_" + L"4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_1}{i_4}}_{{a_2}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{i_1}}_{{a_" + L"4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_2}{i_5}}_{{a_1}{a_3}}}} + " + L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_2}}_{{a_" + L"4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_3}}_{{a_1}{a_2}}}}\\bigr) }"); } } @@ -829,13 +829,12 @@ SECTION("Closed-shell spintrace CCSD") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL( - result, - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_1}{a_2}}" - L"}} + " - L"{{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_2}{i_3}}_{{a_1}{a_2}}}}" - L"\\bigr) }"); + REQUIRE_SUM_EQUAL(result, + L"{ " + L"\\bigl({{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{" + L"a_1}{a_2}}}} - " + L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_" + L"1}{a_2}}}}\\bigr) }"); } { @@ -892,8 +891,8 @@ SECTION("Closed-shell spintrace CCSD") { canonicalize(result); REQUIRE(to_latex(result) == L"{ " - L"\\bigl({{{2}}{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_1}}_{{a_3}}}{t^{" - L"{i_2}}_{{a_2}}}} - " + L"\\bigl({{{2}}{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}{t^{" + L"{i_1}}_{{a_3}}}} - " L"{{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_1}}_{{a_2}}}{t^{{i_2}}_{{a_" L"3}}}}\\bigr) }"); } @@ -912,10 +911,10 @@ SECTION("Closed-shell spintrace CCSD") { canonicalize(result); REQUIRE(to_latex(result) == L"{ " - L"\\bigl({{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_" - L"3}}_{{a_2}}}} - " - L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}" - L"}_{{a_2}}}}\\bigr) }"); + L"\\bigl({{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}" + L"}_{{a_2}}}} - " + L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}}_" + L"{{a_2}}}}\\bigr) }"); } { @@ -951,10 +950,10 @@ SECTION("Closed-shell spintrace CCSD") { rapid_simplify(result); canonicalize(result); REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{" - L"i_2}{i_1}}_{{a_2}{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}{" + L"{ " + L"\\bigl({{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_1}" + L"{i_2}}_{{a_2}{a_3}}}} - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}{" L"i_1}}_{{a_2}{a_3}}}}\\bigr) }"); } @@ -973,11 +972,11 @@ SECTION("Closed-shell spintrace CCSD") { rapid_simplify(result); canonicalize(result); REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}" - L"{i_2}}_{{a_1}{a_2}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_2}{i_3}" - L"}_{{a_1}{a_2}}}}\\bigr) }"); + L"{ " + L"\\bigl({{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_2}}}{t^{{i_3}" + L"{i_2}}_{{a_1}{a_3}}}} - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}{" + L"i_2}}_{{a_1}{a_2}}}}\\bigr) }"); } { @@ -996,14 +995,14 @@ SECTION("Closed-shell spintrace CCSD") { canonicalize(result); REQUIRE(to_latex(result) == L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{i_1}" - L"{i_3}}_{{a_1}{a_2}}}} + " - L"{{{4}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_1}" - L"{i_3}}_{{a_1}{a_3}}}} - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_3}" - L"{i_1}}_{{a_1}{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_2}}}{t^{{i_2}{i_1}" - L"}_{{a_1}{a_3}}}}\\bigr) }"); + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{i_1}{" + L"i_3}}_{{a_1}{a_2}}}} + " + L"{{{4}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_1}{" + L"i_3}}_{{a_1}{a_3}}}} + " + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{i_3}{i_1}}_" + L"{{a_1}{a_2}}}} - " + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_3}{" + L"i_1}}_{{a_1}{a_3}}}}\\bigr) }"); } { @@ -1021,10 +1020,10 @@ SECTION("Closed-shell spintrace CCSD") { canonicalize(result); REQUIRE(to_latex(result) == L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}" - L"}_{{a_2}}}{t^{{i_1}}_{{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_1}}}{t^{{i_3}}_{{" - L"a_2}}}{t^{{i_1}}_{{a_3}}}}\\bigr) }"); + L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}}_" + L"{{a_2}}}{t^{{i_1}}_{{a_3}}}} + " + L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_1}}_{{a_" + L"2}}}{t^{{i_2}}_{{a_3}}}}\\bigr) }"); } } // CCSD R1 From 5e13a2c30abdd3c4e04b81130084e2dfcb1db951 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 01:10:30 -0500 Subject: [PATCH 47/85] tensor_network_graphs can use V1 and V2 TensorNetworks --- .../tensor_network_graphs.cpp | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/examples/tensor_network_graphs/tensor_network_graphs.cpp b/examples/tensor_network_graphs/tensor_network_graphs.cpp index 62e335d96..3a6fd7062 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 @@ -36,6 +37,22 @@ std::optional to_network(const ExprPtr &expr) { } } +std::optional to_network_v2(const ExprPtr &expr) { + if (expr.is()) { + return TensorNetworkV2({expr}); + } else if (expr.is()) { + for (const ExprPtr &factor : expr.as().factors()) { + if (!factor.is()) { + return {}; + } + } + + return TensorNetworkV2(expr.as().factors()); + } else { + return {}; + } +} + void print_help() { std::wcout << "Helper to generate dot (GraphViz) representations of tensor " "network graphs.\n"; @@ -44,6 +61,7 @@ void print_help() { << " [options] [ [... [] ] ]\n"; std::wcout << "Options:\n"; std::wcout << " --help Shows this help message\n"; + std::wcout << " --v2 Use TensorNetworkV2\n"; std::wcout << " --no-named Treat all indices as unnamed (even if they are " "external)\n"; } @@ -55,6 +73,7 @@ int main(int argc, char **argv) { BraKetSymmetry::conjugate, SPBasis::spinorbital)); bool use_named_indices = true; + bool use_tnv2 = false; const TensorNetwork::named_indices_t empty_named_indices; if (argc <= 1) { @@ -70,6 +89,9 @@ int main(int argc, char **argv) { } else if (current == L"--no-named") { use_named_indices = false; continue; + } else if (current == L"--v2") { + use_tnv2 = true; + continue; } ExprPtr expr; @@ -82,16 +104,30 @@ int main(int argc, char **argv) { } assert(expr); - std::optional network = to_network(expr); - if (!network.has_value()) { - std::wcout << "Failed to construct tensor network for input '" << current - << "'" << std::endl; - return 2; - } + if (!use_tnv2) { + std::optional network = to_network(expr); + if (!network.has_value()) { + std::wcout << "Failed to construct tensor network for input '" + << current << "'" << std::endl; + return 2; + } + + auto [graph, vlabels, vcolors, vtypes] = network->make_bliss_graph( + use_named_indices ? nullptr : &empty_named_indices); + std::wcout << "Graph for '" << current << "'\n"; + graph->write_dot(std::wcout, vlabels); + } else { + std::optional network = to_network_v2(expr); + if (!network.has_value()) { + std::wcout << "Failed to construct tensor network for input '" + << current << "'" << std::endl; + return 2; + } - TensorNetwork::Graph graph = network->create_graph( - use_named_indices ? nullptr : &empty_named_indices); - std::wcout << "Graph for '" << current << "'\n"; - graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); + TensorNetworkV2::Graph graph = network->create_graph( + use_named_indices ? nullptr : &empty_named_indices); + std::wcout << "Graph for '" << current << "'\n"; + graph.bliss_graph->write_dot(std::wcout, graph.vertex_labels); + } } } From 9b8acfec5bebeb62b728ab142d80ad1abb4b2cce Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 07:59:00 -0500 Subject: [PATCH 48/85] biproduct -> byproduct --- SeQuant/core/expr.hpp | 8 ++++---- SeQuant/core/expr_algorithm.hpp | 12 ++++++------ SeQuant/core/tensor_network.cpp | 10 +++++----- SeQuant/core/tensor_network.hpp | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/SeQuant/core/expr.hpp b/SeQuant/core/expr.hpp index f66a9004f..a6bb3babe 100644 --- a/SeQuant/core/expr.hpp +++ b/SeQuant/core/expr.hpp @@ -266,18 +266,18 @@ class Expr : public std::enable_shared_from_this, std::const_pointer_cast(this->shared_from_this())); } - /// Canonicalizes @c this and returns the biproduct of canonicalization (e.g. + /// Canonicalizes @c this and returns the byproduct of canonicalization (e.g. /// phase) - /// @return the biproduct of canonicalization, or @c nullptr if no biproduct + /// @return the byproduct of canonicalization, or @c nullptr if no byproduct /// generated virtual ExprPtr canonicalize() { return {}; // by default do nothing and return nullptr } /// Performs approximate, but fast, canonicalization of @c this and returns - /// the biproduct of canonicalization (e.g. phase) The default is to use + /// the byproduct of canonicalization (e.g. phase) The default is to use /// canonicalize(), unless overridden in the derived class. - /// @return the biproduct of canonicalization, or @c nullptr if no biproduct + /// @return the byproduct of canonicalization, or @c nullptr if no byproduct /// generated virtual ExprPtr rapid_canonicalize() { return this->canonicalize(); } diff --git a/SeQuant/core/expr_algorithm.hpp b/SeQuant/core/expr_algorithm.hpp index faccf6fd4..0a3baeef9 100644 --- a/SeQuant/core/expr_algorithm.hpp +++ b/SeQuant/core/expr_algorithm.hpp @@ -15,9 +15,9 @@ namespace sequant { /// _replaced_ (i.e. `&expr` may be mutated by call) /// @return \p expr to facilitate chaining inline ExprPtr& canonicalize(ExprPtr& expr) { - const auto biproduct = expr->canonicalize(); - if (biproduct && biproduct->is()) { - expr = biproduct * expr; + const auto byproduct = expr->canonicalize(); + if (byproduct && byproduct->is()) { + expr = byproduct * expr; } return expr; } @@ -27,9 +27,9 @@ inline ExprPtr& canonicalize(ExprPtr& expr) { /// @param[in] expr_rv rvalue-ref-to-expression to be canonicalized /// @return canonicalized form of \p expr_rv inline ExprPtr canonicalize(ExprPtr&& expr_rv) { - const auto biproduct = expr_rv->canonicalize(); - if (biproduct && biproduct->is()) { - expr_rv = biproduct * expr_rv; + const auto byproduct = expr_rv->canonicalize(); + if (byproduct && byproduct->is()) { + expr_rv = byproduct * expr_rv; } return std::move(expr_rv); } diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 3e464b4ee..fa3751e3d 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -35,7 +35,7 @@ namespace sequant { ExprPtr TensorNetwork::canonicalize( const container::vector &cardinal_tensor_labels, bool fast, const named_indices_t *named_indices_ptr) { - ExprPtr canon_biproduct = ex(1); + ExprPtr canon_byproduct = ex(1); container::svector idx_terminals_sorted; // to avoid memory allocs if (Logger::instance().canonicalize) { @@ -399,15 +399,15 @@ ExprPtr TensorNetwork::canonicalize( nondefault_canonizer_ptr ? nondefault_canonizer_ptr.get() : &default_tensor_canonizer; auto bp = tensor_canonizer->apply(*tensor); - if (bp) *canon_biproduct *= *bp; + if (bp) *canon_byproduct *= *bp; } } edges_.clear(); ext_indices_.clear(); - assert(canon_biproduct->is()); - return (canon_biproduct->as().value() == 1) ? nullptr - : canon_biproduct; + assert(canon_byproduct->is()); + return (canon_byproduct->as().value() == 1) ? nullptr + : canon_byproduct; } std::tuple, std::vector, diff --git a/SeQuant/core/tensor_network.hpp b/SeQuant/core/tensor_network.hpp index 620010f9b..cdc55afd5 100644 --- a/SeQuant/core/tensor_network.hpp +++ b/SeQuant/core/tensor_network.hpp @@ -167,7 +167,7 @@ class TensorNetwork { /// @param named_indices specifies the indices that cannot be renamed, i.e. /// their labels are meaningful; default is nullptr, which results in external /// indices treated as named indices - /// @return biproduct of canonicalization (e.g. phase); if none, returns + /// @return byproduct of canonicalization (e.g. phase); if none, returns /// nullptr ExprPtr canonicalize( const container::vector &cardinal_tensor_labels = {}, From 7a4a2ad6a986827d61f07c586230c9c4fbd2213a Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 08:01:25 -0500 Subject: [PATCH 49/85] to_latex(Tensor): auxiliary indices typeset in square brackets, by analogy with array indexing in programming languages --- SeQuant/core/tensor.hpp | 4 ++-- tests/unit/test_tensor.cpp | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/SeQuant/core/tensor.hpp b/SeQuant/core/tensor.hpp index 90f6a124c..f64dd837f 100644 --- a/SeQuant/core/tensor.hpp +++ b/SeQuant/core/tensor.hpp @@ -314,7 +314,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { for (const auto &i : this->bra()) result += sequant::to_latex(i); result += L"}"; if (!this->aux_.empty()) { - result += L"("; + result += L"["; const index_container_type &__aux = this->aux(); for (std::size_t i = 0; i < aux_rank(); ++i) { result += sequant::to_latex(__aux[i]); @@ -323,7 +323,7 @@ class Tensor : public Expr, public AbstractTensor, public Labeled { result += L","; } } - result += L")"; + result += L"]"; } result += L"}"; return result; diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index 745cd99d8..ac6521bf9 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -140,7 +140,10 @@ TEST_CASE("Tensor", "[elements]") { REQUIRE(to_latex(t1) == L"{F^{{i_2}}_{{i_1}}}"); auto t2 = Tensor(L"F", bra{L"i_1"}, ket{L"i_2"}, aux{L"i_3"}); - REQUIRE(to_latex(t2) == L"{F^{{i_2}}_{{i_1}}({i_3})}"); + REQUIRE(to_latex(t2) == L"{F^{{i_2}}_{{i_1}}[{i_3}]}"); + + auto t3 = Tensor(L"F", bra{L"i_1"}, ket{L"i_2"}, aux{L"i_3", L"i_4"}); + REQUIRE(to_latex(t3) == L"{F^{{i_2}}_{{i_1}}[{i_3},{i_4}]}"); auto h1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}) * ex(cre({L"i_1"}), ann({L"i_2"})); From 805bc3357ac6380cfb4f58598b0a8fb4b12ebe97 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 08:02:40 -0500 Subject: [PATCH 50/85] [unit] test_canonicalize.cpp: validate canonicalization of Tensors with aux indices --- tests/unit/test_canonicalize.cpp | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 87d5d1190..2ddc85c0e 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -52,6 +52,44 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(op); REQUIRE(to_latex(op) == L"{g^{{p_3}{p_4}}_{{p_1}{p_2}}}"); } + { + auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, + Symmetry::symm); + canonicalize(op); + REQUIRE(to_latex(op) == L"{g^{{p_3}{p_4}}_{{p_1}{p_2}}}"); + } + { + auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, + Symmetry::antisymm); + canonicalize(op); + REQUIRE(to_latex(op) == L"{{{-1}}{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}}"); + } + + // aux indices + { + auto op = ex(L"B", bra{L"p_1"}, ket{L"p_2"}, aux{L"p_3"}, + Symmetry::nonsymm); + canonicalize(op); + REQUIRE(to_latex(op) == L"{B^{{p_2}}_{{p_1}}[{p_3}]}"); + } + { + auto op = ex(L"B", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, + aux{L"p_5"}, Symmetry::nonsymm); + canonicalize(op); + REQUIRE(to_latex(op) == L"{B^{{p_4}{p_3}}_{{p_1}{p_2}}[{p_5}]}"); + } + { + auto op = ex(L"B", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, + aux{L"p_5"}, Symmetry::symm); + canonicalize(op); + REQUIRE(to_latex(op) == L"{B^{{p_3}{p_4}}_{{p_1}{p_2}}[{p_5}]}"); + } + { + auto op = ex(L"B", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, + aux{L"p_5"}, Symmetry::antisymm); + canonicalize(op); + REQUIRE(to_latex(op) == L"{{{-1}}{B^{{p_3}{p_4}}_{{p_1}{p_2}}[{p_5}]}}"); + } } SECTION("Products") { From 78b00a0ba1cc14c54a6efd4cd792928d8f088d94 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 08:16:45 -0500 Subject: [PATCH 51/85] to_latex(Product) replace -1 prefactor by a minus sign --- SeQuant/core/expr.hpp | 7 ++++++- tests/unit/test_canonicalize.cpp | 4 ++-- tests/unit/test_wick.cpp | 3 +-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/SeQuant/core/expr.hpp b/SeQuant/core/expr.hpp index a6bb3babe..e0bccba1a 100644 --- a/SeQuant/core/expr.hpp +++ b/SeQuant/core/expr.hpp @@ -1141,7 +1141,12 @@ class Product : public Expr { if (!scalar().is_zero()) { const auto scal = negate ? -scalar() : scalar(); if (!scal.is_identity()) { - result += sequant::to_latex(scal); + // replace -1 prefactor by - + if (!(negate ? scalar() : -scalar()).is_identity()) { + result += sequant::to_latex(scal); + } else { + result += L"{-}"; + } } for (const auto &i : factors()) { if (i->is()) diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 2ddc85c0e..2ab4bcede 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -62,7 +62,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, Symmetry::antisymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{{{-1}}{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}}"); + REQUIRE(to_latex(op) == L"{{-}{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}}"); } // aux indices @@ -88,7 +88,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto op = ex(L"B", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, aux{L"p_5"}, Symmetry::antisymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{{{-1}}{B^{{p_3}{p_4}}_{{p_1}{p_2}}[{p_5}]}}"); + REQUIRE(to_latex(op) == L"{{-}{B^{{p_3}{p_4}}_{{p_1}{p_2}}[{p_5}]}}"); } } diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index ded5b196f..99fec8cd2 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -647,8 +647,7 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { ExprPtr result; REQUIRE_NOTHROW(result = wick.compute()); // std::wcout << "result = " << to_latex(result) << std::endl; - REQUIRE(to_latex(result) == - L"{{{-1}}{\\bar{g}^{{i_1}{a_2}}_{{a_3}{a_4}}}}"); + REQUIRE(to_latex(result) == L"{{-}{\\bar{g}^{{i_1}{a_2}}_{{a_3}{a_4}}}}"); } // odd number of ops -> full contraction is 0 From f18471ca4a48a43743ec2f4bc0a75e69922b2885 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 19 Nov 2024 08:33:01 -0500 Subject: [PATCH 52/85] [unit] test TensorNetwork with aux indices --- tests/unit/test_canonicalize.cpp | 43 ++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 2ab4bcede..1558bb4e6 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -17,8 +17,6 @@ #include -// TODO: Add test cases with aux indices - TEST_CASE("Canonicalizer", "[algorithms]") { using namespace sequant; @@ -161,6 +159,19 @@ TEST_CASE("Canonicalizer", "[algorithms]") { L"a_2}}}{f⁺^{{i_1}{i_3}}_{{a_1}{a_3}}}{t^{{a_2}}_{{i_2}}}}"); } } + { + auto input = ex(rational{1, 2}) * + ex(L"B", bra{L"p_2"}, ket{L"p_4"}, aux{L"p_5"}, + Symmetry::nonsymm) * + ex(L"B", bra{L"p_1"}, ket{L"p_3"}, aux{L"p_5"}, + Symmetry::nonsymm) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, Symmetry::nonsymm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, Symmetry::nonsymm); + canonicalize(input); + REQUIRE(to_latex(input) == + L"{{{\\frac{1}{2}}}{t^{{p_3}}_{{p_1}}}{t^{{p_4}}_{{p_2}}}{B^{{p_1}}" + L"_{{p_3}}[{p_5}]}{B^{{p_2}}_{{p_4}}[{p_5}]}}"); + } SECTION("sum of products") { { @@ -345,5 +356,33 @@ TEST_CASE("Canonicalizer", "[algorithms]") { L"}_{{i_4}}}{t^{{i_4}{i_1}{i_2}}_{{a_1}{a_2}{a_3}}}}\\bigr) }"); } } + + // Case 6: Case 4 w/ aux indices + { + auto input = + ex(rational{4, 3}) * + ex(L"B", bra{L"i_3"}, ket{L"a_3"}, aux{L"p_5"}, + Symmetry::nonsymm) * + ex(L"B", bra{L"i_4"}, ket{L"i_1"}, aux{L"p_5"}, + Symmetry::nonsymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, Symmetry::nonsymm) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_4", L"i_2"}, + Symmetry::nonsymm) - + ex(rational{1, 3}) * + ex(L"B", bra{L"i_3"}, ket{L"i_1"}, aux{L"p_5"}, + Symmetry::nonsymm) * + ex(L"B", bra{L"i_4"}, ket{L"a_3"}, aux{L"p_5"}, + Symmetry::nonsymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, Symmetry::nonsymm) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_3", L"i_2"}, + Symmetry::nonsymm); + + canonicalize(input); + REQUIRE(input->size() == 1); + REQUIRE(to_latex(input) == + L"{ " + L"\\bigl({{t^{{i_3}}_{{a_2}}}{t^{{i_4}{i_2}}_{{a_1}{a_3}}}{B^{{a_" + L"3}}_{{i_3}}[{p_5}]}{B^{{i_1}}_{{i_4}}[{p_5}]}}\\bigr) }"); + } } } From 747216ea994cf7156b30acbb8c9a1a7565b31062 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 17 Dec 2024 16:09:27 +0100 Subject: [PATCH 53/85] Make output of Index::full_label() a bit more readable --- SeQuant/core/index.hpp | 10 ++++++---- tests/unit/test_spin.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/SeQuant/core/index.hpp b/SeQuant/core/index.hpp index 1321a6cc7..625660ffe 100644 --- a/SeQuant/core/index.hpp +++ b/SeQuant/core/index.hpp @@ -389,10 +389,12 @@ class Index : public Taggable { std::wstring_view full_label() const { if (!has_proto_indices()) return label(); if (full_label_) return *full_label_; - std::wstring result = label_; - ranges::for_each(proto_indices_, [&result](const Index &idx) { - result += idx.full_label(); - }); + std::wstring result = label_ + L"<"; + result += + ranges::views::transform(proto_indices_, + [](const Index &idx) { return idx.label(); }) | + ranges::views::join(L", ") | ranges::to(); + result += L">"; full_label_ = result; return *full_label_; } diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index f71be5ac4..1a4ef78c3 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -140,19 +140,19 @@ TEST_CASE("Spin", "[spin]") { // proto REQUIRE_NOTHROW(make_spinalpha(p_i)); REQUIRE(make_spinalpha(p_i).label() == L"p↑"); - REQUIRE(make_spinalpha(p_i).full_label() == L"p↑i↑"); + REQUIRE(make_spinalpha(p_i).full_label() == L"p↑"); REQUIRE(make_spinalpha(p_i).to_latex() == L"{p↑^{{i↑}}}"); REQUIRE_NOTHROW(make_spinalpha(p1_i)); REQUIRE(make_spinalpha(p1_i).label() == L"p↑_1"); - REQUIRE(make_spinalpha(p1_i).full_label() == L"p↑_1i↑"); + REQUIRE(make_spinalpha(p1_i).full_label() == L"p↑_1"); REQUIRE(make_spinalpha(p1_i).to_latex() == L"{p↑_1^{{i↑}}}"); REQUIRE_NOTHROW(make_spinalpha(p_i1)); REQUIRE(make_spinalpha(p_i1).label() == L"p↑"); - REQUIRE(make_spinalpha(p_i1).full_label() == L"p↑i↑_1"); + REQUIRE(make_spinalpha(p_i1).full_label() == L"p↑"); REQUIRE(make_spinalpha(p_i1).to_latex() == L"{p↑^{{i↑_1}}}"); REQUIRE_NOTHROW(make_spinalpha(p1_i1)); REQUIRE(make_spinalpha(p1_i1).label() == L"p↑_1"); - REQUIRE(make_spinalpha(p1_i1).full_label() == L"p↑_1i↑_1"); + REQUIRE(make_spinalpha(p1_i1).full_label() == L"p↑_1"); REQUIRE(make_spinalpha(p1_i1).to_latex() == L"{p↑_1^{{i↑_1}}}"); } From 73a54e68794a9503a83cf9386d2e0e9d13301a06 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 17 Dec 2024 16:10:01 +0100 Subject: [PATCH 54/85] Use full label as vertex label --- SeQuant/core/tensor_network_v2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeQuant/core/tensor_network_v2.cpp b/SeQuant/core/tensor_network_v2.cpp index 632a0b34b..91d0ddbb7 100644 --- a/SeQuant/core/tensor_network_v2.cpp +++ b/SeQuant/core/tensor_network_v2.cpp @@ -787,7 +787,7 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( // Now add all indices (edges) to the graph for (const Edge ¤t_edge : edges_) { const Index &index = current_edge.idx(); - graph.vertex_labels.push_back(std::wstring(index.label())); + graph.vertex_labels.push_back(std::wstring(index.full_label())); graph.vertex_types.push_back(VertexType::Index); // Assign index color From f6c92539be01467e76d7e7c0a12c82c68076f6bf Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 17 Dec 2024 16:10:30 +0100 Subject: [PATCH 55/85] Connect protoindex bundle vertices to the vertices of underlying indices --- SeQuant/core/tensor_network_v2.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/SeQuant/core/tensor_network_v2.cpp b/SeQuant/core/tensor_network_v2.cpp index 91d0ddbb7..02f572039 100644 --- a/SeQuant/core/tensor_network_v2.cpp +++ b/SeQuant/core/tensor_network_v2.cpp @@ -785,6 +785,8 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( } // Now add all indices (edges) to the graph + container::map index_vertices; + for (const Edge ¤t_edge : edges_) { const Index &index = current_edge.idx(); graph.vertex_labels.push_back(std::wstring(index.full_label())); @@ -806,6 +808,8 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( const std::size_t index_vertex = graph.vertex_labels.size() - 1; + index_vertices[index] = index_vertex; + // Handle proto indices if (index.has_proto_indices()) { // For now we assume that all proto indices are symmetric @@ -887,6 +891,22 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( } } + // Add edges between proto index bundle vertices and all vertices of the + // indices contained in that bundle i.e. if the bundle is {i_1,i_2}, the + // bundle would be connected with vertices for i_1 and i_2 + for (const auto &[bundle, vertex] : proto_bundles) { + for (const Index &idx : bundle) { + auto it = index_vertices.find(idx); + + assert(it != index_vertices.end()); + if (it == index_vertices.end()) { + std::abort(); + } + + edges.push_back(std::make_pair(it->second, vertex)); + } + } + assert(graph.vertex_labels.size() == graph.vertex_colors.size()); assert(graph.vertex_labels.size() == graph.vertex_types.size()); From 299d0cbcc1e142d850c2af6c44f021aff63fed2a Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 17 Dec 2024 16:54:14 +0100 Subject: [PATCH 56/85] Allow whitespace within proto-index list --- SeQuant/core/parse/parse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeQuant/core/parse/parse.cpp b/SeQuant/core/parse/parse.cpp index 77f46633a..7b6f5bed9 100644 --- a/SeQuant/core/parse/parse.cpp +++ b/SeQuant/core/parse/parse.cpp @@ -97,7 +97,7 @@ auto index_label_def = x3::lexeme[ ]; auto index_def = x3::lexeme[ - index_label >> -('<' >> index_label % ',' >> ">") + index_label >> -x3::skip['<' >> index_label % ',' >> ">"] ]; const std::vector noIndices; From c1994d4d61a30ae2cbcea835f71810bc276639c7 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 17 Dec 2024 17:00:51 +0100 Subject: [PATCH 57/85] Make protobundle vertex labels a little more readable --- SeQuant/core/tensor_network_v2.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SeQuant/core/tensor_network_v2.cpp b/SeQuant/core/tensor_network_v2.cpp index 02f572039..6171f819a 100644 --- a/SeQuant/core/tensor_network_v2.cpp +++ b/SeQuant/core/tensor_network_v2.cpp @@ -821,11 +821,14 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( proto_vertex = it->second; } else { // Create a new vertex for this bundle of proto indices - std::wstring spbundle_label = L"{"; - for (const Index &proto : index.proto_indices()) { - spbundle_label += proto.label(); - } - spbundle_label += L"}"; + std::wstring spbundle_label = + L"{" + + (ranges::views::transform( + index.proto_indices(), + [](const Index &idx) { return idx.label(); }) | + ranges::views::join(L", ") | ranges::to()) + + L"}"; + graph.vertex_labels.push_back(std::move(spbundle_label)); graph.vertex_types.push_back(VertexType::SPBundle); const std::size_t bundle_color = From e9acf0c6cf17aa2e35556e6d589be72923399d92 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 18 Dec 2024 09:51:21 +0100 Subject: [PATCH 58/85] Decouple label annotations in eval exprs from full label representation --- SeQuant/core/eval_expr.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/SeQuant/core/eval_expr.cpp b/SeQuant/core/eval_expr.cpp index 306cd7e03..c84f6c7ce 100644 --- a/SeQuant/core/eval_expr.cpp +++ b/SeQuant/core/eval_expr.cpp @@ -61,6 +61,16 @@ NestedTensorIndices::NestedTensorIndices(const sequant::Tensor& tnsr) { append_unique(outer, ix); } +std::string to_label_annotation(const Index& idx) { + using namespace ranges::views; + using ranges::to; + + return sequant::to_string(idx.label()) + + (idx.proto_indices() | transform(&Index::label) | + transform([](auto&& str) { return sequant::to_string(str); }) | + ranges::views::join | to); +} + std::string EvalExpr::braket_annot() const noexcept { if (!is_tensor()) return {}; @@ -71,12 +81,9 @@ std::string EvalExpr::braket_annot() const noexcept { auto annot = [](auto&& ixs) -> std::string { using namespace ranges::views; - auto full_labels = ixs // - | transform(&Index::full_label) // - | transform([](auto&& fl) { // - return sequant::to_string(fl); - }); - return full_labels // + auto annotations = ixs | transform(to_label_annotation); + + return annotations // | intersperse(std::string{","}) // | join // | ranges::to; From d9cedfd9ce127490284130b3af673eac13f1a6f0 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 18 Dec 2024 17:41:04 +0100 Subject: [PATCH 59/85] Renamed VertexType::BraKet to VertexType::Particle as that fits future use --- SeQuant/core/tensor_network.cpp | 4 ++-- SeQuant/core/tensor_network_v2.cpp | 4 ++-- SeQuant/core/vertex_type.hpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index fa3751e3d..9f94d50ff 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -549,7 +549,7 @@ TensorNetwork::make_bliss_graph( vertex_labels.push_back( std::wstring(L"bk") + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); - vertex_type.push_back(VertexType::TensorBraKet); + vertex_type.push_back(VertexType::Particle); vertex_color.push_back(t_color); } // nonsymmetric tensors are represented by 3*rank more vertices (with rank = @@ -572,7 +572,7 @@ TensorNetwork::make_bliss_graph( ? bra_color : bra_color + max_rank); vertex_labels.push_back(std::wstring(L"bk") + pstr); - vertex_type.push_back(VertexType::TensorBraKet); + vertex_type.push_back(VertexType::Particle); vertex_color.push_back(t_color); } } diff --git a/SeQuant/core/tensor_network_v2.cpp b/SeQuant/core/tensor_network_v2.cpp index 6171f819a..0d6caa2f9 100644 --- a/SeQuant/core/tensor_network_v2.cpp +++ b/SeQuant/core/tensor_network_v2.cpp @@ -375,7 +375,7 @@ void TensorNetworkV2::canonicalize_graph(const named_indices_t &named_indices) { index_idx_to_vertex[index_idx] = vertex; index_idx++; break; - case VertexType::TensorBraKet: { + case VertexType::Particle: { assert(tensor_idx > 0); const std::size_t base_tensor_idx = tensor_idx - 1; assert(symmetry(*tensors_.at(base_tensor_idx)) == Symmetry::nonsymm); @@ -714,7 +714,7 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( for (std::size_t i = 0; i < num_particle_vertices; ++i) { graph.vertex_labels.emplace_back(L"p_" + std::to_wstring(i + 1)); - graph.vertex_types.push_back(VertexType::TensorBraKet); + graph.vertex_types.push_back(VertexType::Particle); graph.vertex_colors.push_back(tensor_color); edges.push_back( std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); diff --git a/SeQuant/core/vertex_type.hpp b/SeQuant/core/vertex_type.hpp index cdd182555..3a82d80d0 100644 --- a/SeQuant/core/vertex_type.hpp +++ b/SeQuant/core/vertex_type.hpp @@ -10,7 +10,7 @@ enum class VertexType { TensorKet, TensorAux, TensorCore, - TensorBraKet, + Particle, }; } From 0f51f6af10df7b96c15d301b520c438fc9181e96 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 18 Dec 2024 17:42:13 +0100 Subject: [PATCH 60/85] Create dedicated VertexPainter that ensures no color clashes can occur --- SeQuant/core/tensor_network_v2.cpp | 314 ++++++++++++++++++++++------- SeQuant/core/tensor_network_v2.hpp | 23 ++- tests/unit/test_tensor_network.cpp | 4 +- 3 files changed, 259 insertions(+), 82 deletions(-) diff --git a/SeQuant/core/tensor_network_v2.cpp b/SeQuant/core/tensor_network_v2.cpp index 0d6caa2f9..bdd4a4808 100644 --- a/SeQuant/core/tensor_network_v2.cpp +++ b/SeQuant/core/tensor_network_v2.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -312,7 +313,7 @@ void sort_via_indices(Container &container, const Comparator &cmp) { } } -void TensorNetworkV2::canonicalize_graph(const named_indices_t &named_indices) { +void TensorNetworkV2::canonicalize_graph(const NamedIndexSet &named_indices) { if (Logger::instance().canonicalize) { std::wcout << "TensorNetworkV2::canonicalize_graph: input tensors\n"; size_t cnt = 0; @@ -521,7 +522,7 @@ void TensorNetworkV2::canonicalize_graph(const named_indices_t &named_indices) { ExprPtr TensorNetworkV2::canonicalize( const container::vector &cardinal_tensor_labels, bool fast, - const named_indices_t *named_indices_ptr) { + const NamedIndexSet *named_indices_ptr) { if (Logger::instance().canonicalize) { std::wcout << "TensorNetworkV2::canonicalize(" << (fast ? "fast" : "slow") << "): input tensors\n"; @@ -638,22 +639,224 @@ ExprPtr TensorNetworkV2::canonicalize( return (byproduct->as().value() == 1) ? nullptr : byproduct; } +using ProtoBundle = + std::decay_t().proto_indices())>; + +struct BraGroup { + explicit BraGroup(std::size_t id) : id(id) {} + + std::size_t id; +}; +struct KetGroup { + explicit KetGroup(std::size_t id) : id(id) {} + + std::size_t id; +}; +struct AuxGroup { + explicit AuxGroup(std::size_t id) : id(id) {} + + std::size_t id; +}; +struct ParticleGroup { + explicit ParticleGroup(std::size_t id) : id(id) {} + + std::size_t id; +}; + +class VertexPainter { + public: + using Color = TensorNetworkV2::Graph::VertexColor; + using VertexData = + std::variant; + using ColorMap = container::map; + + VertexPainter(const TensorNetworkV2::NamedIndexSet &named_indices) + : used_colors_(), named_indices_(named_indices) {} + + const ColorMap &used_colors() const { return used_colors_; } + + Color operator()(const AbstractTensor &tensor) { + Color color = to_color(hash::value(label(tensor))); + + return ensure_uniqueness(color, tensor); + } + + Color operator()(const BraGroup &group) { + Color color = to_color(group.id + 0xff); + + return ensure_uniqueness(color, group); + } + + Color operator()(const KetGroup &group) { + Color color = to_color(group.id + 0xff00); + + return ensure_uniqueness(color, group); + } + + Color operator()(const AuxGroup &group) { + Color color = to_color(group.id + 3 * 0xff0000); + + return ensure_uniqueness(color, group); + } + + Color operator()(const ParticleGroup &group) { + Color color = to_color(group.id); + + return ensure_uniqueness(color, group); + } + + Color operator()(const Index &idx) { + auto it = named_indices_.find(idx); + + // TODO: shift + std::size_t pre_color; + if (it == named_indices_.end()) { + // anonymous index + pre_color = idx.color(); + } else { + pre_color = static_cast( + std::distance(named_indices_.begin(), it)); + } + + return ensure_uniqueness(to_color(pre_color), idx); + } + + Color operator()(const ProtoBundle &bundle) { + Color color = to_color(Index::proto_indices_color(bundle)); + + return ensure_uniqueness(color, bundle); + } + + private: + ColorMap used_colors_; + const TensorNetworkV2::NamedIndexSet &named_indices_; + + Color to_color(std::size_t color) const { + // Due to the way we compute the input color, different colors might only + // differ by a value of 1. This is fine for the algorithmic purpose (after + // all, colors need only be different - by how much is irrelevant), but + // sometimes we'll want to use those colors as actual colors to show to a + // human being. In those cases, having larger differences makes it easier to + // recognize different colors. Therefore, we hash-combined with an + // arbitrarily chosen salt with the goal that this will uniformly spread out + // all input values and therefore increase color differences. + constexpr std::size_t salt = 0x43d2c59cb15b73f0; + hash::combine(color, salt); + + if constexpr (sizeof(Color) >= sizeof(std::size_t)) { + return color; + } + + // Need to somehow fit the color into a lower precision integer. In the + // general case, this is necessarily a lossy conversion. We make the + // assumption that the input color is + // - a hash, or + // - computed from some object ID + // In the first case, we assume that the used hash function has a uniform + // distribution or if there is a bias, the bias is towards lower numbers. + // This allows us to simply reuse the lower x bits of the hash as a new hash + // (where x == CHAR_BIT * sizeof(VertexColor)). In the second case we assume + // that such values never exceed the possible value range of VertexColor so + // that again, we can simply take the lower x bits of color and in this case + // even retain the numeric value representing the color. Handily, this is + // exactly what happens when we perform a conversion into a narrower type. + // We only have to make sure that the underlying types are unsigned as + // otherwise the behavior is undefined. + static_assert(sizeof(Color) < sizeof(std::size_t)); + static_assert(std::is_unsigned_v, + "Narrowing conversion are undefined for signed integers"); + static_assert(std::is_unsigned_v, + "Narrowing conversion are undefined for signed integers"); + return static_cast(color); + } + + template + Color ensure_uniqueness(Color color, const T &val) { + auto it = used_colors_.find(color); + while (it != used_colors_.end() && !may_have_same_color(it->second, val)) { + // Color collision: val was computed to have the same color + // as another object, but these objects do not compare equal (for + // the purpose of color assigning). + // -> Need to modify color until conflict is resolved. + color++; + it = used_colors_.find(color); + } + + if (it == used_colors_.end()) { + // We have not yet seen this color before -> add it to cache + if constexpr (std::is_same_v || + std::is_same_v) { + used_colors_[color] = &val; + } else { + used_colors_[color] = val; + } + } + + return color; + } + + bool may_have_same_color(const VertexData &data, + const AbstractTensor &tensor) { + return std::holds_alternative(data) && + label(*std::get(data)) == label(tensor); + } + + bool may_have_same_color(const VertexData &data, const BraGroup &group) { + return std::holds_alternative(data) && + std::get(data).id == group.id; + } + + bool may_have_same_color(const VertexData &data, const KetGroup &group) { + return std::holds_alternative(data) && + std::get(data).id == group.id; + } + + bool may_have_same_color(const VertexData &data, const AuxGroup &group) { + return std::holds_alternative(data) && + std::get(data).id == group.id; + } + + bool may_have_same_color(const VertexData &data, const ParticleGroup &group) { + return std::holds_alternative(data) && + std::get(data).id == group.id; + } + + bool may_have_same_color(const VertexData &data, const Index &idx) { + if (!std::holds_alternative(data)) { + return false; + } + + const Index &lhs = std::get(data); + + auto it1 = named_indices_.find(lhs); + auto it2 = named_indices_.find(idx); + + if (it1 != it2) { + // Either one index is named and the other is not or both are named, but + // are different indices + return false; + } + + return lhs.color() == idx.color(); + } + + bool may_have_same_color(const VertexData &data, const ProtoBundle &bundle) { + return std::holds_alternative(data) && + Index::proto_indices_color(*std::get(data)) == + Index::proto_indices_color(bundle); + } +}; + TensorNetworkV2::Graph TensorNetworkV2::create_graph( - const named_indices_t *named_indices_ptr) const { + const NamedIndexSet *named_indices_ptr) const { assert(have_edges_); // initialize named_indices by default to all external indices - const named_indices_t &named_indices = + const NamedIndexSet &named_indices = named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; - // Colors in the range [ 0, 3 * max_rank + named_indices.size() ) are - // reserved: Up to max_rank colors can be used for bra indices Up to - // max_rank colors can be used for ket indices Up to max_rank colors can be - // used for auxiliary indices Every named index is identified by a unique - // color - constexpr std::size_t named_idx_color_start = 3 * max_rank; - const std::size_t max_reserved_color = - named_idx_color_start + named_indices.size() - 1; + VertexPainter colorizer(named_indices); // core, bra, ket, auxiliary and optionally (for non-symmetric tensors) a // particle vertex @@ -669,9 +872,7 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( graph.vertex_colors.reserve(vertex_count_estimate); graph.vertex_types.reserve(vertex_count_estimate); - using proto_bundle_t = - std::decay_t().proto_indices())>; - container::map proto_bundles; + container::map proto_bundles; container::map tensor_vertices; tensor_vertices.reserve(tensors_.size()); @@ -686,13 +887,9 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( const AbstractTensor &tensor = *tensors_.at(tensor_idx); // Tensor core - std::wstring_view tensor_label = label(tensor); - graph.vertex_labels.emplace_back(tensor_label); + graph.vertex_labels.emplace_back(label(tensor)); graph.vertex_types.emplace_back(VertexType::TensorCore); - const std::size_t tensor_color = - hash::value(tensor_label) + max_reserved_color; - assert(tensor_color > max_reserved_color); - graph.vertex_colors.push_back(tensor_color); + graph.vertex_colors.push_back(colorizer(tensor)); const std::size_t tensor_vertex = graph.vertex_labels.size() - 1; tensor_vertices.insert(std::make_pair(tensor_idx, tensor_vertex)); @@ -715,19 +912,19 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( for (std::size_t i = 0; i < num_particle_vertices; ++i) { graph.vertex_labels.emplace_back(L"p_" + std::to_wstring(i + 1)); graph.vertex_types.push_back(VertexType::Particle); - graph.vertex_colors.push_back(tensor_color); + // Particles are indistinguishable -> always use same ID + graph.vertex_colors.push_back(colorizer(ParticleGroup{0})); edges.push_back( std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); } - assert(bra_rank(tensor) <= max_rank); for (std::size_t i = 0; i < bra_rank(tensor); ++i) { const bool is_unpaired_idx = i >= num_particle_vertices; const bool color_idx = is_unpaired_idx || !is_part_symm; graph.vertex_labels.emplace_back(L"bra_" + std::to_wstring(i + 1)); graph.vertex_types.push_back(VertexType::TensorBra); - graph.vertex_colors.push_back(color_idx ? i : 0); + graph.vertex_colors.push_back(colorizer(BraGroup{color_idx ? i : 0})); const std::size_t connect_vertex = tensor_vertex + (is_unpaired_idx ? 0 : (i + 1)); @@ -735,15 +932,19 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( std::make_pair(connect_vertex, graph.vertex_labels.size() - 1)); } - assert(ket_rank(tensor) <= max_rank); for (std::size_t i = 0; i < ket_rank(tensor); ++i) { const bool is_unpaired_idx = i >= num_particle_vertices; const bool color_idx = is_unpaired_idx || !is_part_symm; graph.vertex_labels.emplace_back(L"ket_" + std::to_wstring(i + 1)); graph.vertex_types.push_back(VertexType::TensorKet); - graph.vertex_colors.push_back((color_idx ? i : 0) + - (is_braket_symm ? 0 : max_rank)); + if (is_braket_symm) { + // Use BraGroup for kets as well as they are supposed to be + // indistinguishable + graph.vertex_colors.push_back(colorizer(BraGroup{color_idx ? i : 0})); + } else { + graph.vertex_colors.push_back(colorizer(KetGroup{color_idx ? i : 0})); + } const std::size_t connect_vertex = tensor_vertex + (is_unpaired_idx ? 0 : (i + 1)); @@ -754,21 +955,21 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( // Shared set of bra/ket vertices for all indices std::wstring suffix = tensor_sym == Symmetry::symm ? L"_s" : L"_a"; - const std::size_t bra_color = 0; graph.vertex_labels.push_back(L"bra" + suffix); graph.vertex_types.push_back(VertexType::TensorBra); - graph.vertex_colors.push_back(bra_color); + graph.vertex_colors.push_back(colorizer(BraGroup{0})); edges.push_back( std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); - // TODO: figure out how to handle BraKetSymmetry::conjugate - const std::size_t ket_color = - braket_symmetry(tensor) == BraKetSymmetry::symm - ? bra_color - : bra_color + max_rank; graph.vertex_labels.push_back(L"ket" + suffix); graph.vertex_types.push_back(VertexType::TensorKet); - graph.vertex_colors.push_back(ket_color); + // TODO: figure out how to handle BraKetSymmetry::conjugate + if (braket_symmetry(tensor) == BraKetSymmetry::symm) { + // Use BraGroup for kets as well as they should be indistinguishable + graph.vertex_colors.push_back(colorizer(BraGroup{0})); + } else { + graph.vertex_colors.push_back(colorizer(KetGroup{0})); + } edges.push_back( std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); } @@ -778,7 +979,7 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( for (std::size_t i = 0; i < aux_rank(tensor); ++i) { graph.vertex_labels.emplace_back(L"aux_" + std::to_wstring(i + 1)); graph.vertex_types.push_back(VertexType::TensorAux); - graph.vertex_colors.push_back(2 * max_rank + i); + graph.vertex_colors.push_back(colorizer(AuxGroup{i})); edges.push_back( std::make_pair(tensor_vertex, graph.vertex_labels.size() - 1)); } @@ -791,20 +992,7 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( const Index &index = current_edge.idx(); graph.vertex_labels.push_back(std::wstring(index.full_label())); graph.vertex_types.push_back(VertexType::Index); - - // Assign index color - std::size_t idx_color; - auto named_idx_iter = named_indices.find(index); - if (named_idx_iter == named_indices.end()) { - // This is an anonymous index - idx_color = index.color(); - assert(idx_color > max_reserved_color); - } else { - idx_color = static_cast( - std::distance(named_indices.begin(), named_idx_iter)); - idx_color += named_idx_color_start; - } - graph.vertex_colors.push_back(idx_color); + graph.vertex_colors.push_back(colorizer(index)); const std::size_t index_vertex = graph.vertex_labels.size() - 1; @@ -831,11 +1019,7 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( graph.vertex_labels.push_back(std::move(spbundle_label)); graph.vertex_types.push_back(VertexType::SPBundle); - const std::size_t bundle_color = - Index::proto_indices_color(index.proto_indices()) + - max_reserved_color; - assert(bundle_color); - graph.vertex_colors.push_back(bundle_color); + graph.vertex_colors.push_back(colorizer(index.proto_indices())); proto_vertex = graph.vertex_labels.size() - 1; proto_bundles.insert( @@ -921,19 +1105,9 @@ TensorNetworkV2::Graph TensorNetworkV2::create_graph( graph.bliss_graph->add_edge(current_edge.first, current_edge.second); } - // compress vertex colors to 32 bits, as required by Bliss, by hashing - for (std::size_t vertex = 0; vertex < graph.vertex_colors.size(); ++vertex) { - auto color = graph.vertex_colors[vertex]; - static_assert(sizeof(color) == 8); - - color = (~color) + (color << 18); // color = (color << 18) - color - 1; - color = color ^ (color >> 31); - color = color * 21; // color = (color + (color << 2)) + (color << 4); - color = color ^ (color >> 11); - color = color + (color << 6); - color = color ^ (color >> 22); - - graph.bliss_graph->change_color(vertex, static_cast(color)); + for (const auto [vertex, color] : + ranges::views::enumerate(graph.vertex_colors)) { + graph.bliss_graph->change_color(vertex, color); } return graph; @@ -1016,13 +1190,13 @@ container::svector> TensorNetworkV2::factorize() { } ExprPtr TensorNetworkV2::canonicalize_individual_tensor_blocks( - const named_indices_t &named_indices) { + const NamedIndexSet &named_indices) { return do_individual_canonicalization( TensorBlockCanonicalizer(named_indices)); } ExprPtr TensorNetworkV2::canonicalize_individual_tensors( - const named_indices_t &named_indices) { + const NamedIndexSet &named_indices) { return do_individual_canonicalization( DefaultTensorCanonicalizer(named_indices)); } diff --git a/SeQuant/core/tensor_network_v2.hpp b/SeQuant/core/tensor_network_v2.hpp index 5fea86a5b..64f5e0c21 100644 --- a/SeQuant/core/tensor_network_v2.hpp +++ b/SeQuant/core/tensor_network_v2.hpp @@ -40,8 +40,6 @@ class TensorNetworkV2 { public: friend class TensorNetworkV2Accessor; - constexpr static size_t max_rank = 256; - enum class Origin { Bra = 1, Ket, @@ -149,9 +147,14 @@ class TensorNetworkV2 { }; struct Graph { + /// The type used to encode the color of a vertex. The restriction of this + /// being as 32-bit integer comes from how BLISS is trying to convert these + /// into RGB values. + using VertexColor = std::uint32_t; + std::unique_ptr bliss_graph; std::vector vertex_labels; - std::vector vertex_colors; + std::vector vertex_colors; std::vector vertex_types; Graph() = default; @@ -192,7 +195,7 @@ class TensorNetworkV2 { /// @note the order of tensors may be different from that provided as input const auto &tensors() const { return tensors_; } - using named_indices_t = container::set; + using NamedIndexSet = container::set; /// @param cardinal_tensor_labels move all tensors with these labels to the /// front before canonicalizing indices @@ -206,7 +209,7 @@ class TensorNetworkV2 { /// nullptr ExprPtr canonicalize( const container::vector &cardinal_tensor_labels = {}, - bool fast = true, const named_indices_t *named_indices = nullptr); + bool fast = true, const NamedIndexSet *named_indices = nullptr); /// Factorizes tensor network /// @return sequence of binary products; each element encodes the tensors to @@ -258,7 +261,7 @@ class TensorNetworkV2 { /// tensor; terminal vertices are colored by the color of its tensor, /// with the color of symm/antisymm terminals augmented by the /// terminal's type (bra/ket). - Graph create_graph(const named_indices_t *named_indices = nullptr) const; + Graph create_graph(const NamedIndexSet *named_indices = nullptr) const; private: // source tensors and indices @@ -270,7 +273,7 @@ class TensorNetworkV2 { // sorted by *label* (not full label) of the corresponding value (Index) // this ensures that proto indices are not considered and all internal indices // have unique labels (not full labels) - named_indices_t ext_indices_; + NamedIndexSet ext_indices_; /// initializes edges_ and ext_indices_ void init_edges(); @@ -278,17 +281,17 @@ class TensorNetworkV2 { /// Canonicalizes the network graph representation /// Note: The explicit order of tensors and labelling of indices /// remains undefined. - void canonicalize_graph(const named_indices_t &named_indices); + void canonicalize_graph(const NamedIndexSet &named_indices); /// Canonicalizes every individual tensor for itself, taking into account only /// tensor blocks /// @returns The byproduct of the canonicalizations ExprPtr canonicalize_individual_tensor_blocks( - const named_indices_t &named_indices); + const NamedIndexSet &named_indices); /// Canonicalizes every individual tensor for itself /// @returns The byproduct of the canonicalizations - ExprPtr canonicalize_individual_tensors(const named_indices_t &named_indices); + ExprPtr canonicalize_individual_tensors(const NamedIndexSet &named_indices); ExprPtr do_individual_canonicalization( const TensorCanonicalizer &canonicalizer); diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index aaa8ed360..a2bb74903 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -798,7 +798,7 @@ TEST_CASE("TensorNetworkV2", "[elements]") { Index::reset_tmp_index(); TensorNetworkV2 tn(*t1_x_t2); - using named_indices_t = TensorNetworkV2::named_indices_t; + using named_indices_t = TensorNetworkV2::NamedIndexSet; named_indices_t indices{Index{L"i_17"}}; tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), false, &indices); @@ -1119,7 +1119,7 @@ TEST_CASE("TensorNetworkV2", "[elements]") { // make graph // N.B. treat all indices as dummy so that the automorphism ignores the - using named_indices_t = TensorNetworkV2::named_indices_t; + using named_indices_t = TensorNetworkV2::NamedIndexSet; named_indices_t indices{}; REQUIRE_NOTHROW(tn.create_graph(&indices)); TensorNetworkV2::Graph graph = tn.create_graph(&indices); From ae76ae336f4081ed804a4d36d17e2d6602e67a91 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 18 Dec 2024 17:52:34 +0100 Subject: [PATCH 61/85] Make color in dot graphs more prominent (easier to differentiate colors) --- SeQuant/external/bliss/graph.hh | 3 +- tests/unit/test_tensor_network.cpp | 180 +++++++++++++++++++---------- 2 files changed, 122 insertions(+), 61 deletions(-) diff --git a/SeQuant/external/bliss/graph.hh b/SeQuant/external/bliss/graph.hh index 094b11c3b..ec5d987fb 100644 --- a/SeQuant/external/bliss/graph.hh +++ b/SeQuant/external/bliss/graph.hh @@ -714,7 +714,8 @@ class Graph : public AbstractGraph { } else os << vnum; if (rgb_colors) { - os << "\"; color=\"#" << int_to_rgb(v.color) << "\"];\n"; + auto color = int_to_rgb(v.color); + os << "\"; style=filled; color=\"#" << color << "\"; fillcolor=\"#" << color << "80\"; penwidth=2];\n"; } else { os << ":" << v.color << "\"];\n"; } diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index a2bb74903..a8c7cb117 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -215,136 +215,196 @@ TEST_CASE("TensorNetwork", "[elements]") { // std::wcout << "oss.str() = " << std::endl << oss.str() << std::endl; REQUIRE(oss.str() == L"graph g {\n" - "v0 [label=\"{a_1}\"; color=\"#64facf\"];\n" + "v0 [label=\"{a_1}\"; style=filled; color=\"#64facf\"; " + "fillcolor=\"#64facf80\"; penwidth=2];\n" "v0 -- v29\n" "v0 -- v58\n" - "v1 [label=\"{a_2}\"; color=\"#64facf\"];\n" + "v1 [label=\"{a_2}\"; style=filled; color=\"#64facf\"; " + "fillcolor=\"#64facf80\"; penwidth=2];\n" "v1 -- v29\n" "v1 -- v58\n" - "v2 [label=\"{a_3}\"; color=\"#64facf\"];\n" + "v2 [label=\"{a_3}\"; style=filled; color=\"#64facf\"; " + "fillcolor=\"#64facf80\"; penwidth=2];\n" "v2 -- v33\n" "v2 -- v54\n" - "v3 [label=\"{a_4}\"; color=\"#64facf\"];\n" + "v3 [label=\"{a_4}\"; style=filled; color=\"#64facf\"; " + "fillcolor=\"#64facf80\"; penwidth=2];\n" "v3 -- v33\n" "v3 -- v54\n" - "v4 [label=\"{a_5}\"; color=\"#64facf\"];\n" + "v4 [label=\"{a_5}\"; style=filled; color=\"#64facf\"; " + "fillcolor=\"#64facf80\"; penwidth=2];\n" "v4 -- v37\n" "v4 -- v50\n" - "v5 [label=\"{a_6}\"; color=\"#64facf\"];\n" + "v5 [label=\"{a_6}\"; style=filled; color=\"#64facf\"; " + "fillcolor=\"#64facf80\"; penwidth=2];\n" "v5 -- v37\n" "v5 -- v50\n" - "v6 [label=\"{a_7}\"; color=\"#64facf\"];\n" + "v6 [label=\"{a_7}\"; style=filled; color=\"#64facf\"; " + "fillcolor=\"#64facf80\"; penwidth=2];\n" "v6 -- v22\n" "v6 -- v41\n" - "v7 [label=\"{a_8}\"; color=\"#64facf\"];\n" + "v7 [label=\"{a_8}\"; style=filled; color=\"#64facf\"; " + "fillcolor=\"#64facf80\"; penwidth=2];\n" "v7 -- v22\n" "v7 -- v41\n" - "v8 [label=\"{i_1}\"; color=\"#9c2a20\"];\n" + "v8 [label=\"{i_1}\"; style=filled; color=\"#9c2a20\"; " + "fillcolor=\"#9c2a2080\"; penwidth=2];\n" "v8 -- v30\n" "v8 -- v57\n" - "v9 [label=\"{i_2}\"; color=\"#9c2a20\"];\n" + "v9 [label=\"{i_2}\"; style=filled; color=\"#9c2a20\"; " + "fillcolor=\"#9c2a2080\"; penwidth=2];\n" "v9 -- v30\n" "v9 -- v57\n" - "v10 [label=\"{i_3}\"; color=\"#9c2a20\"];\n" + "v10 [label=\"{i_3}\"; style=filled; color=\"#9c2a20\"; " + "fillcolor=\"#9c2a2080\"; penwidth=2];\n" "v10 -- v34\n" "v10 -- v53\n" - "v11 [label=\"{i_4}\"; color=\"#9c2a20\"];\n" + "v11 [label=\"{i_4}\"; style=filled; color=\"#9c2a20\"; " + "fillcolor=\"#9c2a2080\"; penwidth=2];\n" "v11 -- v34\n" "v11 -- v53\n" - "v12 [label=\"{i_5}\"; color=\"#9c2a20\"];\n" + "v12 [label=\"{i_5}\"; style=filled; color=\"#9c2a20\"; " + "fillcolor=\"#9c2a2080\"; penwidth=2];\n" "v12 -- v38\n" "v12 -- v49\n" - "v13 [label=\"{i_6}\"; color=\"#9c2a20\"];\n" + "v13 [label=\"{i_6}\"; style=filled; color=\"#9c2a20\"; " + "fillcolor=\"#9c2a2080\"; penwidth=2];\n" "v13 -- v38\n" "v13 -- v49\n" - "v14 [label=\"{i_7}\"; color=\"#9c2a20\"];\n" + "v14 [label=\"{i_7}\"; style=filled; color=\"#9c2a20\"; " + "fillcolor=\"#9c2a2080\"; penwidth=2];\n" "v14 -- v21\n" "v14 -- v42\n" - "v15 [label=\"{i_8}\"; color=\"#9c2a20\"];\n" + "v15 [label=\"{i_8}\"; style=filled; color=\"#9c2a20\"; " + "fillcolor=\"#9c2a2080\"; penwidth=2];\n" "v15 -- v21\n" "v15 -- v42\n" - "v16 [label=\"{\\kappa_1}\"; color=\"#7126de\"];\n" + "v16 [label=\"{\\kappa_1}\"; style=filled; color=\"#7126de\"; " + "fillcolor=\"#7126de80\"; penwidth=2];\n" "v16 -- v25\n" "v16 -- v46\n" - "v17 [label=\"{\\kappa_2}\"; color=\"#7126de\"];\n" + "v17 [label=\"{\\kappa_2}\"; style=filled; color=\"#7126de\"; " + "fillcolor=\"#7126de80\"; penwidth=2];\n" "v17 -- v25\n" "v17 -- v46\n" - "v18 [label=\"{\\kappa_3}\"; color=\"#7126de\"];\n" + "v18 [label=\"{\\kappa_3}\"; style=filled; color=\"#7126de\"; " + "fillcolor=\"#7126de80\"; penwidth=2];\n" "v18 -- v26\n" "v18 -- v45\n" - "v19 [label=\"{\\kappa_4}\"; color=\"#7126de\"];\n" + "v19 [label=\"{\\kappa_4}\"; style=filled; color=\"#7126de\"; " + "fillcolor=\"#7126de80\"; penwidth=2];\n" "v19 -- v26\n" "v19 -- v45\n" - "v20 [label=\"A\"; color=\"#518020\"];\n" + "v20 [label=\"A\"; style=filled; color=\"#518020\"; " + "fillcolor=\"#51802080\"; penwidth=2];\n" "v20 -- v23\n" - "v21 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v21 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v21 -- v23\n" - "v22 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v22 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v22 -- v23\n" - "v23 [label=\"bka\"; color=\"#518020\"];\n" - "v24 [label=\"g\"; color=\"#2e0351\"];\n" + "v23 [label=\"bka\"; style=filled; color=\"#518020\"; " + "fillcolor=\"#51802080\"; penwidth=2];\n" + "v24 [label=\"g\"; style=filled; color=\"#2e0351\"; " + "fillcolor=\"#2e035180\"; penwidth=2];\n" "v24 -- v27\n" - "v25 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v25 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v25 -- v27\n" - "v26 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v26 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v26 -- v27\n" - "v27 [label=\"bka\"; color=\"#2e0351\"];\n" - "v28 [label=\"t\"; color=\"#043e44\"];\n" + "v27 [label=\"bka\"; style=filled; color=\"#2e0351\"; " + "fillcolor=\"#2e035180\"; penwidth=2];\n" + "v28 [label=\"t\"; style=filled; color=\"#043e44\"; " + "fillcolor=\"#043e4480\"; penwidth=2];\n" "v28 -- v31\n" - "v29 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v29 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v29 -- v31\n" - "v30 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v30 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v30 -- v31\n" - "v31 [label=\"bka\"; color=\"#043e44\"];\n" - "v32 [label=\"t\"; color=\"#043e44\"];\n" + "v31 [label=\"bka\"; style=filled; color=\"#043e44\"; " + "fillcolor=\"#043e4480\"; penwidth=2];\n" + "v32 [label=\"t\"; style=filled; color=\"#043e44\"; " + "fillcolor=\"#043e4480\"; penwidth=2];\n" "v32 -- v35\n" - "v33 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v33 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v33 -- v35\n" - "v34 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v34 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v34 -- v35\n" - "v35 [label=\"bka\"; color=\"#043e44\"];\n" - "v36 [label=\"t\"; color=\"#043e44\"];\n" + "v35 [label=\"bka\"; style=filled; color=\"#043e44\"; " + "fillcolor=\"#043e4480\"; penwidth=2];\n" + "v36 [label=\"t\"; style=filled; color=\"#043e44\"; " + "fillcolor=\"#043e4480\"; penwidth=2];\n" "v36 -- v39\n" - "v37 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v37 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v37 -- v39\n" - "v38 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v38 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v38 -- v39\n" - "v39 [label=\"bka\"; color=\"#043e44\"];\n" - "v40 [label=\"ã\"; color=\"#cbfbe5\"];\n" + "v39 [label=\"bka\"; style=filled; color=\"#043e44\"; " + "fillcolor=\"#043e4480\"; penwidth=2];\n" + "v40 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" "v40 -- v43\n" - "v41 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v41 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v41 -- v43\n" - "v42 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v42 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v42 -- v43\n" - "v43 [label=\"bka\"; color=\"#cbfbe5\"];\n" - "v44 [label=\"ã\"; color=\"#cbfbe5\"];\n" + "v43 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" + "v44 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" "v44 -- v47\n" - "v45 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v45 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v45 -- v47\n" - "v46 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v46 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v46 -- v47\n" - "v47 [label=\"bka\"; color=\"#cbfbe5\"];\n" - "v48 [label=\"ã\"; color=\"#cbfbe5\"];\n" + "v47 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" + "v48 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" "v48 -- v51\n" - "v49 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v49 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v49 -- v51\n" - "v50 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v50 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v50 -- v51\n" - "v51 [label=\"bka\"; color=\"#cbfbe5\"];\n" - "v52 [label=\"ã\"; color=\"#cbfbe5\"];\n" + "v51 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" + "v52 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" "v52 -- v55\n" - "v53 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v53 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v53 -- v55\n" - "v54 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v54 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v54 -- v55\n" - "v55 [label=\"bka\"; color=\"#cbfbe5\"];\n" - "v56 [label=\"ã\"; color=\"#cbfbe5\"];\n" + "v55 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" + "v56 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" "v56 -- v59\n" - "v57 [label=\"bra2a\"; color=\"#eaa2ab\"];\n" + "v57 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " + "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" "v57 -- v59\n" - "v58 [label=\"ket2a\"; color=\"#5a8fd3\"];\n" + "v58 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " + "fillcolor=\"#5a8fd380\"; penwidth=2];\n" "v58 -- v59\n" - "v59 [label=\"bka\"; color=\"#cbfbe5\"];\n" + "v59 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " + "fillcolor=\"#cbfbe580\"; penwidth=2];\n" "}\n"); // compute automorphism group From e76ebb30d1db92148a0b7ec86b9a112fda99a0ad Mon Sep 17 00:00:00 2001 From: Bimal Gaudel Date: Thu, 9 Jan 2025 12:18:16 -0500 Subject: [PATCH 62/85] Functions for numeric comparison on `Index` suffix. --- SeQuant/core/index.hpp | 15 +++++++++++++++ SeQuant/core/utility/indices.hpp | 13 +++++++++++++ 2 files changed, 28 insertions(+) diff --git a/SeQuant/core/index.hpp b/SeQuant/core/index.hpp index 625660ffe..e3ddbbab0 100644 --- a/SeQuant/core/index.hpp +++ b/SeQuant/core/index.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -366,6 +367,20 @@ class Index : public Taggable { return make_split_label(this->label()); } + /// + /// \return The numeric suffix if present in the label. + /// + std::optional suffix() const { + auto &&[_, s_] = split_label(); + auto &&s = sequant::to_string(s_); + + int value{}; + if (std::from_chars(s.data(), s.data() + s.size(), value).ec == std::errc{}) + return value; + else + return std::nullopt; + } + /// @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, diff --git a/SeQuant/core/utility/indices.hpp b/SeQuant/core/utility/indices.hpp index 53abb8d0a..63a578674 100644 --- a/SeQuant/core/utility/indices.hpp +++ b/SeQuant/core/utility/indices.hpp @@ -225,6 +225,19 @@ IndexGroups get_unique_indices(const ExprPtr& expr) { } } +/// +/// Does the numeric comparison of the index suffixes using less-than operator. +/// +/// \param idx1 +/// \param idx2 +/// \return True if the numeric suffix of \c idx1 is less than that of \c idx2. +/// +inline bool suffix_compare(Index const& idx1, Index const& idx2) { + auto&& s1 = idx1.suffix(); + auto&& s2 = idx2.suffix(); + return (s1 && s2) && s1.value() < s2.value(); +} + } // namespace sequant #endif // SEQUANT_CORE_UTILITY_INDICES_HPP From 8bc20460e6a33110e908d77f3a67d2d1e55b233d Mon Sep 17 00:00:00 2001 From: Bimal Gaudel Date: Thu, 9 Jan 2025 12:20:03 -0500 Subject: [PATCH 63/85] Functions for CSV-transformation and density-fitting of 4-center 'g'. --- SeQuant/core/optimize.hpp | 27 ++++++ SeQuant/core/optimize/optimize.cpp | 134 +++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/SeQuant/core/optimize.hpp b/SeQuant/core/optimize.hpp index 06bc49cce..8bbbc68fb 100644 --- a/SeQuant/core/optimize.hpp +++ b/SeQuant/core/optimize.hpp @@ -375,6 +375,33 @@ ExprPtr optimize(ExprPtr const& expr, IdxToSize const& idx2size) { /// \return Optimized expression for lower evaluation cost. ExprPtr optimize(ExprPtr const& expr); +/// +/// Converts the 4-center 'g' tensors into a product of two rank-3 tensors. +/// +/// \param expr The expression to be density-fit. +/// \param aux_label The label of the introduced auxilliary index. eg. 'x', 'p'. +/// \return The density-fit expression if 'g' of rank-4 present, otherwise the +/// input expression itself will be returned. +/// +ExprPtr density_fit(ExprPtr const& expr, std::wstring const& aux_label); + +/// +/// Converts the tensors in CSV basis into a product of full-basis +/// tensors times the CSV-transformation tensors. +/// +/// \param expr The expression to be CSV-transformed. +/// \param coeff_tensor_label The label of the CSV-tranformation tensors that +/// will be introduced. +/// \param csv_tensors The label of the CSV-basis tensors that will be +/// written as the transformed product. Eg. 'f', 'g'. +/// \return The CSV-transformed expression if CSV-tensors with labels present +/// in @c csv_tensors appear in @c expr. Otherwise returns the input +/// expression itself. +ExprPtr csv_transform(ExprPtr const& expr, + std::wstring const& coeff_tensor_label = L"C", + container::svector const& csv_tensors = { + L"f", L"g"}); + } // namespace sequant #endif // SEQUANT_OPTIMIZE_OPTIMIZE_HPP diff --git a/SeQuant/core/optimize/optimize.cpp b/SeQuant/core/optimize/optimize.cpp index 86a7df5e2..f90ecca2f 100644 --- a/SeQuant/core/optimize/optimize.cpp +++ b/SeQuant/core/optimize/optimize.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -163,4 +164,137 @@ ExprPtr optimize(ExprPtr const& expr) { expr, [](Index const& ix) { return ix.space().approximate_size(); }); } +ExprPtr density_fit_impl(Tensor const& tnsr, Index const& aux_idx) { + assert(tnsr.bra_rank() == 2 // + && tnsr.ket_rank() == 2 // + && tnsr.aux_rank() == 0); + + auto t1 = ex(L"g", bra({ranges::front(tnsr.bra())}), + ket({ranges::front(tnsr.ket())}), aux({aux_idx})); + + auto t2 = ex(L"g", bra({ranges::back(tnsr.bra())}), + ket({ranges::back(tnsr.ket())}), aux({aux_idx})); + + return ex(1, ExprPtrList{t1, t2}); +} + +ExprPtr density_fit(ExprPtr const& expr, std::wstring const& aux_label) { + using ranges::views::transform; + if (expr->is()) + return ex(*expr | transform([&aux_label](auto&& x) { + return density_fit(x, aux_label); + })); + + else if (expr->is()) { + auto const& g = expr->as(); + if (g.label() == L"g" // + && g.bra_rank() == 2 // + && g.ket_rank() == 2 // + && ranges::none_of(g.indices(), &Index::has_proto_indices)) + return density_fit_impl(expr->as(), Index(aux_label + L"_1")); + else + return expr; + } else if (expr->is()) { + auto const& prod = expr->as(); + + Product result; + result.scale(prod.scalar()); + size_t aux_ix = 0; + for (auto&& f : prod.factors()) + if (f.is() && f.as().label() == L"g") { + auto const& g = f->as(); + auto g_df = density_fit_impl( + g, Index(aux_label + L"_" + std::to_wstring(++aux_ix))); + result.append(1, std::move(g_df), Product::Flatten::Yes); + } else { + result.append(1, f, Product::Flatten::No); + } + return ex(std::move(result)); + } else + return expr; +} + +ExprPtr csv_transform_impl(Tensor const& tnsr, + std::wstring_view coeff_tensor_label) { + using ranges::views::transform; + + if (ranges::none_of(tnsr.const_braket(), &Index::has_proto_indices)) + return nullptr; + + //// + auto drop_protos = [](auto&& ixs) { + return ixs | transform(&Index::drop_proto_indices); + }; + //// + + if (tnsr.label() == overlap_label()) { + assert(tnsr.bra_rank() == 1 // + && tnsr.ket_rank() == 1 // + && tnsr.aux_rank() == 0); + + auto&& bra_idx = tnsr.bra().at(0); + auto&& ket_idx = tnsr.ket().at(0); + + auto dummy_idx = suffix_compare(bra_idx, ket_idx) // + ? bra_idx.drop_proto_indices() // + : ket_idx.drop_proto_indices(); + + return ex( + 1, + ExprPtrList{ex(coeff_tensor_label, // + bra({bra_idx}), ket({dummy_idx})), // + ex(coeff_tensor_label, // + bra({dummy_idx}), ket({ket_idx}))}); + } + + Product result; + result.append(1, ex(tnsr.label(), bra(drop_protos(tnsr.bra())), + ket(drop_protos(tnsr.ket())), tnsr.aux())); + + for (auto&& idx : tnsr.bra()) + if (idx.has_proto_indices()) + result.append(1, ex(coeff_tensor_label, bra({idx}), + ket({idx.drop_proto_indices()}), aux({}))); + for (auto&& idx : tnsr.ket()) + if (idx.has_proto_indices()) + result.append( + 1, ex(coeff_tensor_label, bra({idx.drop_proto_indices()}), + ket({idx}), aux({}))); + + return ex(std::move(result)); +} + +ExprPtr csv_transform(ExprPtr const& expr, + std::wstring const& coeff_tensor_label, + container::svector const& csv_tensors) { + using ranges::views::transform; + if (expr->is()) + return ex(*expr // + | transform([&coeff_tensor_label, // + &csv_tensors](auto&& x) { + return csv_transform(x, coeff_tensor_label, csv_tensors); + })); + else if (expr->is()) { + auto const& tnsr = expr->as(); + if (!ranges::contains(csv_tensors, tnsr.label())) return expr; + return csv_transform_impl(tnsr, coeff_tensor_label); + } else if (expr->is()) { + auto const& prod = expr->as(); + + Product result; + result.scale(prod.scalar()); + + for (auto&& f : prod.factors()) { + auto trans = csv_transform(f, coeff_tensor_label, csv_tensors); + result.append(1, trans ? trans : f, + (f->is() || f->is()) ? Product::Flatten::No + : Product::Flatten::Yes); + } + + return ex(std::move(result)); + + } else + return expr; +} + } // namespace sequant From b818110190ca394ab88061c7e9b455feef2c28d0 Mon Sep 17 00:00:00 2001 From: Bimal Gaudel Date: Fri, 10 Jan 2025 09:57:36 -0500 Subject: [PATCH 64/85] Add option to optimize function to control whether to reorder summands for better cache utilization. --- SeQuant/core/optimize.hpp | 12 +++++++++--- SeQuant/core/optimize/optimize.cpp | 5 +++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/SeQuant/core/optimize.hpp b/SeQuant/core/optimize.hpp index 8bbbc68fb..563fe6472 100644 --- a/SeQuant/core/optimize.hpp +++ b/SeQuant/core/optimize.hpp @@ -345,11 +345,14 @@ Sum reorder(Sum const& sum); /// /// \param expr Expression to be optimized. /// \param idxsz An invocable object that maps an Index object to size. +/// \param reorder_sum If true, the summands are reordered so that terms with +/// common sub-expressions appear closer to each other. /// \return Optimized expression for lower evaluation cost. template >> -ExprPtr optimize(ExprPtr const& expr, IdxToSize const& idx2size) { +ExprPtr optimize(ExprPtr const& expr, IdxToSize const& idx2size, + bool reorder_sum) { using ranges::views::transform; if (expr->is()) return expr->clone(); @@ -360,7 +363,7 @@ ExprPtr optimize(ExprPtr const& expr, IdxToSize const& idx2size) { return optimize(s, idx2size); }) | ranges::to_vector; auto sum = Sum{smands.begin(), smands.end()}; - return ex(opt::reorder(sum)); + return reorder_sum ? ex(opt::reorder(sum)) : ex(std::move(sum)); } else throw std::runtime_error{"Optimization attempted on unsupported Expr type"}; } @@ -372,8 +375,11 @@ ExprPtr optimize(ExprPtr const& expr, IdxToSize const& idx2size) { /// index extent. /// /// \param expr Expression to be optimized. +/// \param reorder_sum If true, the summands are reordered so that terms with +/// common sub-expressions appear closer to each other. +/// True by default. /// \return Optimized expression for lower evaluation cost. -ExprPtr optimize(ExprPtr const& expr); +ExprPtr optimize(ExprPtr const& expr, bool reorder_sum = true); /// /// Converts the 4-center 'g' tensors into a product of two rank-3 tensors. diff --git a/SeQuant/core/optimize/optimize.cpp b/SeQuant/core/optimize/optimize.cpp index f90ecca2f..b53236b62 100644 --- a/SeQuant/core/optimize/optimize.cpp +++ b/SeQuant/core/optimize/optimize.cpp @@ -159,9 +159,10 @@ Sum reorder(Sum const& sum) { } // namespace opt -ExprPtr optimize(ExprPtr const& expr) { +ExprPtr optimize(ExprPtr const& expr, bool reorder_sum) { return opt::optimize( - expr, [](Index const& ix) { return ix.space().approximate_size(); }); + expr, [](Index const& ix) { return ix.space().approximate_size(); }, + reorder_sum); } ExprPtr density_fit_impl(Tensor const& tnsr, Index const& aux_idx) { From a62e0f8716da3f6dfb7ba956454f4db93521e381 Mon Sep 17 00:00:00 2001 From: Bimal Gaudel Date: Mon, 13 Jan 2025 09:16:30 -0500 Subject: [PATCH 65/85] Add function to obtain the tensor-of-tensor indices as outer and inner groups. --- SeQuant/core/utility/indices.hpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/SeQuant/core/utility/indices.hpp b/SeQuant/core/utility/indices.hpp index 63a578674..5d6900594 100644 --- a/SeQuant/core/utility/indices.hpp +++ b/SeQuant/core/utility/indices.hpp @@ -62,6 +62,13 @@ struct IndexGroups { } }; +/// A composite type for holding tensor-of-tensor indices +template > +struct TensorOfTensorIndices { + Container outer; + Container inner; +}; + template > IndexGroups get_unique_indices(const ExprPtr& expr); @@ -225,6 +232,29 @@ IndexGroups get_unique_indices(const ExprPtr& expr) { } } +template > +TensorOfTensorIndices tot_indices(AbstractTensor const& tnsr) { + using ranges::views::concat; + using ranges::views::filter; + using ranges::views::join; + using ranges::views::transform; + + auto indep_idxs = filter(indices(tnsr), // + ranges::not_fn(&Index::has_proto_indices)); + + auto dep_idxs = filter(indices(tnsr), &Index::has_proto_indices); + + TensorOfTensorIndices result; + + for (auto&& idx : dep_idxs) result.inner.emplace_back(idx); + + for (auto&& idx : + concat(indep_idxs, join(dep_idxs | transform(&Index::proto_indices)))) + if (!ranges::contains(result.outer, idx)) result.outer.emplace_back(idx); + + return result; +} + /// /// Does the numeric comparison of the index suffixes using less-than operator. /// From a5fba28ed6b16896a5de7aa8e3a2fea98f477aa1 Mon Sep 17 00:00:00 2001 From: Bimal Gaudel Date: Mon, 13 Jan 2025 09:18:13 -0500 Subject: [PATCH 66/85] Consider proto-indices and aux indices in single-term optimization. --- SeQuant/core/optimize.hpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/SeQuant/core/optimize.hpp b/SeQuant/core/optimize.hpp index 563fe6472..1733a1bf3 100644 --- a/SeQuant/core/optimize.hpp +++ b/SeQuant/core/optimize.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #if __cplusplus >= 202002L #include @@ -190,21 +191,24 @@ template , bool> = true> eval_seq_t single_term_opt(TensorNetwork const& network, IdxToSz const& idxsz) { + using ranges::views::concat; + using IndexContainer = container::svector; // number of terms auto const nt = network.tensors().size(); if (nt == 1) return eval_seq_t{0}; if (nt == 2) return eval_seq_t{0, 1, -1}; - auto nth_tensor_indices = container::svector>{}; + auto nth_tensor_indices = container::svector{}; nth_tensor_indices.reserve(nt); for (std::size_t i = 0; i < nt; ++i) { auto const& tnsr = *network.tensors().at(i); - auto bk = container::svector{}; - bk.reserve(bra_rank(tnsr) + ket_rank(tnsr)); - for (auto&& idx : indices(tnsr)) bk.push_back(idx); - ranges::sort(bk, Index::LabelCompare{}); - nth_tensor_indices.emplace_back(std::move(bk)); + auto oixs = tot_indices(tnsr); + auto ixs = concat(oixs.outer, oixs.inner) // + | ranges::to; + + ranges::sort(ixs, Index::LabelCompare{}); + nth_tensor_indices.emplace_back(std::move(ixs)); } container::svector results((1 << nt), OptRes{{}, 0, {}}); @@ -256,10 +260,9 @@ eval_seq_t single_term_opt(TensorNetwork const& network, IdxToSz const& idxsz) { auto const& first = results[curr_parts.first].sequence; auto const& second = results[curr_parts.second].sequence; - curr_result.sequence = - (first[0] < second[0] ? ranges::views::concat(first, second) - : ranges::views::concat(second, first)) | - ranges::to; + curr_result.sequence = (first[0] < second[0] ? concat(first, second) + : concat(second, first)) | + ranges::to; curr_result.sequence.push_back(-1); } } From befd214350c5d62e7c71fb3edaadedb1620a8c5e Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 13 Jan 2025 16:16:18 +0100 Subject: [PATCH 67/85] Add definition for missing parse overload --- SeQuant/core/parse/deparse.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/SeQuant/core/parse/deparse.cpp b/SeQuant/core/parse/deparse.cpp index 25a6d0c4d..c95690ffc 100644 --- a/SeQuant/core/parse/deparse.cpp +++ b/SeQuant/core/parse/deparse.cpp @@ -96,18 +96,23 @@ std::wstring deparse_scalar(const Constant::scalar_type& scalar) { } // namespace details std::wstring deparse(const ExprPtr& expr, bool annot_sym) { - using namespace details; if (!expr) return {}; - if (expr->is()) - return deparse(expr->as(), annot_sym); - else if (expr->is()) - return deparse(expr->as(), annot_sym); - else if (expr->is()) - return deparse(expr->as(), annot_sym); - else if (expr->is()) - return deparse(expr->as()); - else if (expr->is()) - return deparse(expr->as()); + + return deparse(*expr, annot_sym); +} + +std::wstring deparse(const Expr& expr, bool annot_sym) { + using namespace details; + if (expr.is()) + return deparse(expr.as(), annot_sym); + else if (expr.is()) + return deparse(expr.as(), annot_sym); + else if (expr.is()) + return deparse(expr.as(), annot_sym); + else if (expr.is()) + return deparse(expr.as()); + else if (expr.is()) + return deparse(expr.as()); else throw std::runtime_error("Unsupported expr type for deparse!"); } From bffc95a2c1172ceed0bf9419bb34cfb4cde4d684 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 14 Jan 2025 12:44:59 +0100 Subject: [PATCH 68/85] Add Catch2 support for SeQuant objects --- tests/unit/catch2_sequant.hpp | 111 +++++++++++++++++++++++++++++ tests/unit/test_asy_cost.cpp | 2 + tests/unit/test_binary_node.cpp | 2 + tests/unit/test_bliss.cpp | 2 + tests/unit/test_cache_manager.cpp | 2 + tests/unit/test_canonicalize.cpp | 2 + tests/unit/test_eval_btas.cpp | 2 + tests/unit/test_eval_expr.cpp | 2 + tests/unit/test_eval_node.cpp | 2 + tests/unit/test_eval_ta.cpp | 2 + tests/unit/test_export.cpp | 18 +---- tests/unit/test_expr.cpp | 2 + tests/unit/test_fusion.cpp | 2 + tests/unit/test_index.cpp | 2 + tests/unit/test_iterator.cpp | 2 + tests/unit/test_latex.cpp | 2 + tests/unit/test_main.cpp | 2 + tests/unit/test_math.cpp | 2 + tests/unit/test_mbpt.cpp | 2 + tests/unit/test_mbpt_cc.cpp | 1 + tests/unit/test_meta.cpp | 2 + tests/unit/test_op.cpp | 2 + tests/unit/test_optimize.cpp | 2 + tests/unit/test_parse.cpp | 2 + tests/unit/test_runtime.cpp | 2 + tests/unit/test_space.cpp | 2 + tests/unit/test_spin.cpp | 1 + tests/unit/test_string.cpp | 2 + tests/unit/test_tensor.cpp | 2 + tests/unit/test_tensor_network.cpp | 2 + tests/unit/test_utilities.cpp | 19 +---- tests/unit/test_wick.cpp | 1 + 32 files changed, 171 insertions(+), 32 deletions(-) create mode 100644 tests/unit/catch2_sequant.hpp diff --git a/tests/unit/catch2_sequant.hpp b/tests/unit/catch2_sequant.hpp new file mode 100644 index 000000000..c18f83c86 --- /dev/null +++ b/tests/unit/catch2_sequant.hpp @@ -0,0 +1,111 @@ +#ifndef SEQUANT_TESTS_CATCH2_SEQUANT_H +#define SEQUANT_TESTS_CATCH2_SEQUANT_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Catch { + +// Make sure Catch uses proper string representation for SeQuant types + +template <> +struct StringMaker { + static std::string convert(const sequant::Expr &expr) { + try { + return sequant::to_string(sequant::deparse(expr, true)); + } catch (const std::exception &) { + // deparse doesn't support all kinds of expressions -> fall back to LaTeX + // representation + return sequant::to_string(sequant::to_latex(expr)); + } + } +}; +template <> +struct StringMaker { + static std::string convert(const sequant::ExprPtr &expr) { + return StringMaker::convert(*expr); + } +}; + +template <> +struct StringMaker { + static std::string convert(const sequant::Index &idx) { + return sequant::to_string(idx.full_label()); + } +}; + +} // namespace Catch + + +/// Matches that the tested expression is equivalent to the given one. Two expressions are +/// considered equivalent if they both have the same canonical form (i.e. they are +/// the same expression after canonicalization) +class EquivalentToMatcher : public Catch::Matchers::MatcherGenericBase { + public: + /// Constructs the matcher with the expected expression. The constructor + /// accepts either an actual expression object (as Expr & or ExprPtr) or + /// a (w)string-like object which will then be parsed to yield the actual + /// expression object. + template + EquivalentToMatcher(T &&expression) { + if constexpr (std::is_convertible_v) { + m_expr = sequant::parse_expr( + sequant::to_wstring(std::string(std::forward(expression))), + sequant::Symmetry::nonsymm); + } else if constexpr (std::is_convertible_v) { + m_expr = sequant::parse_expr(std::wstring(std::forward(expression)), + sequant::Symmetry::nonsymm); + } else if constexpr (std::is_convertible_v) { + // Clone in order to not have to worry about later modification + m_expr = expression.clone(); + } else { + static_assert(std::is_convertible_v, + "Invalid type for expression"); + + // Clone in order to not have to worry about later modification + m_expr = expression->clone(); + } + + assert(m_expr); + + // Bring expression into canonical form + m_expr->canonicalize(); + } + + bool match(const sequant::Expr &expr) const { + // Never modify the expression that we are trying to check in order to avoid + // side-effects + sequant::ExprPtr clone = expr.clone(); + + clone->canonicalize(); + + return *clone == *m_expr; + } + + std::string describe() const override { + return "Equivalent to: " + Catch::Detail::stringify(m_expr); + } + + private: + sequant::ExprPtr m_expr; +}; + +template +EquivalentToMatcher EquivalentTo(T &&expression) { + return EquivalentToMatcher(std::forward(expression)); +} + +#endif diff --git a/tests/unit/test_asy_cost.cpp b/tests/unit/test_asy_cost.cpp index 338a870d9..167c81f3d 100644 --- a/tests/unit/test_asy_cost.cpp +++ b/tests/unit/test_asy_cost.cpp @@ -3,6 +3,8 @@ #include #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_binary_node.cpp b/tests/unit/test_binary_node.cpp index 4a1b53fa2..27e04e149 100644 --- a/tests/unit/test_binary_node.cpp +++ b/tests/unit/test_binary_node.cpp @@ -2,6 +2,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_bliss.cpp b/tests/unit/test_bliss.cpp index eba806aea..2dadef649 100644 --- a/tests/unit/test_bliss.cpp +++ b/tests/unit/test_bliss.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include diff --git a/tests/unit/test_cache_manager.cpp b/tests/unit/test_cache_manager.cpp index a0595058b..744cc06ea 100644 --- a/tests/unit/test_cache_manager.cpp +++ b/tests/unit/test_cache_manager.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + namespace sequant { struct TestCacheManager {}; diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 1558bb4e6..4b821862e 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -1,5 +1,7 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_eval_btas.cpp b/tests/unit/test_eval_btas.cpp index cb365c48c..9282551c2 100644 --- a/tests/unit/test_eval_btas.cpp +++ b/tests/unit/test_eval_btas.cpp @@ -1,6 +1,8 @@ #include #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_eval_expr.cpp b/tests/unit/test_eval_expr.cpp index 553f4356f..5a8b6b85c 100644 --- a/tests/unit/test_eval_expr.cpp +++ b/tests/unit/test_eval_expr.cpp @@ -1,5 +1,7 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_eval_node.cpp b/tests/unit/test_eval_node.cpp index 621ab9402..752f28866 100644 --- a/tests/unit/test_eval_node.cpp +++ b/tests/unit/test_eval_node.cpp @@ -1,5 +1,7 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_eval_ta.cpp b/tests/unit/test_eval_ta.cpp index 699296554..9e7368ad1 100644 --- a/tests/unit/test_eval_ta.cpp +++ b/tests/unit/test_eval_ta.cpp @@ -1,6 +1,8 @@ #include #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_export.cpp b/tests/unit/test_export.cpp index 3a66add1b..727796511 100644 --- a/tests/unit/test_export.cpp +++ b/tests/unit/test_export.cpp @@ -1,5 +1,7 @@ #include +#include "catch2_sequant.hpp" + #include #include @@ -7,22 +9,6 @@ #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, false)); - } -}; - -} // namespace Catch - std::vector> twoElectronIntegralSymmetries() { // Symmetries of spin-summed (skeleton) two-electron integrals return { diff --git a/tests/unit/test_expr.cpp b/tests/unit/test_expr.cpp index cee9e4db0..817e9e217 100644 --- a/tests/unit/test_expr.cpp +++ b/tests/unit/test_expr.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_fusion.cpp b/tests/unit/test_fusion.cpp index 4b582c4c6..f3bca6f90 100644 --- a/tests/unit/test_fusion.cpp +++ b/tests/unit/test_fusion.cpp @@ -1,5 +1,7 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_index.cpp b/tests/unit/test_index.cpp index 23715a413..1d9f5fbec 100644 --- a/tests/unit/test_index.cpp +++ b/tests/unit/test_index.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_iterator.cpp b/tests/unit/test_iterator.cpp index 3ea1d0a65..a0093d2f6 100644 --- a/tests/unit/test_iterator.cpp +++ b/tests/unit/test_iterator.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_latex.cpp b/tests/unit/test_latex.cpp index a0bd275b5..b9c49ca2b 100644 --- a/tests/unit/test_latex.cpp +++ b/tests/unit/test_latex.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include diff --git a/tests/unit/test_main.cpp b/tests/unit/test_main.cpp index fc7b3d92a..688d69ef6 100644 --- a/tests/unit/test_main.cpp +++ b/tests/unit/test_main.cpp @@ -6,6 +6,8 @@ #include #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_math.cpp b/tests/unit/test_math.cpp index 789e4b3ce..1216a52d2 100644 --- a/tests/unit/test_math.cpp +++ b/tests/unit/test_math.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_mbpt.cpp b/tests/unit/test_mbpt.cpp index d27a85f88..eb237d7e7 100644 --- a/tests/unit/test_mbpt.cpp +++ b/tests/unit/test_mbpt.cpp @@ -18,7 +18,9 @@ #include #include +#include #include "test_config.hpp" +#include "catch2_sequant.hpp" #include #include diff --git a/tests/unit/test_mbpt_cc.cpp b/tests/unit/test_mbpt_cc.cpp index b46bccbef..002325172 100644 --- a/tests/unit/test_mbpt_cc.cpp +++ b/tests/unit/test_mbpt_cc.cpp @@ -8,6 +8,7 @@ #include #include "test_config.hpp" +#include "catch2_sequant.hpp" TEST_CASE("SR-TCC", "[mbpt/cc]") { using namespace sequant::mbpt; diff --git a/tests/unit/test_meta.cpp b/tests/unit/test_meta.cpp index d36892c7f..bfcaf966c 100644 --- a/tests/unit/test_meta.cpp +++ b/tests/unit/test_meta.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include diff --git a/tests/unit/test_op.cpp b/tests/unit/test_op.cpp index 21a696831..4f5e84f41 100644 --- a/tests/unit/test_op.cpp +++ b/tests/unit/test_op.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_optimize.cpp b/tests/unit/test_optimize.cpp index dcd93db7d..3bae4b5de 100644 --- a/tests/unit/test_optimize.cpp +++ b/tests/unit/test_optimize.cpp @@ -1,5 +1,7 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 27383aeaf..d08e91a46 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -1,6 +1,8 @@ #include #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_runtime.cpp b/tests/unit/test_runtime.cpp index beb05fb13..5b249ff05 100644 --- a/tests/unit/test_runtime.cpp +++ b/tests/unit/test_runtime.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_space.cpp b/tests/unit/test_space.cpp index b6cf69f0e..8c171240e 100644 --- a/tests/unit/test_space.cpp +++ b/tests/unit/test_space.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 1a4ef78c3..55a1d0f63 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -11,6 +11,7 @@ #include #include "test_config.hpp" +#include "catch2_sequant.hpp" #include #include diff --git a/tests/unit/test_string.cpp b/tests/unit/test_string.cpp index c7f586152..841699519 100644 --- a/tests/unit/test_string.cpp +++ b/tests/unit/test_string.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index ac6521bf9..7fd4a8fc0 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index a8c7cb117..9f4fbb11a 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -4,6 +4,8 @@ #include +#include "catch2_sequant.hpp" + #include #include #include diff --git a/tests/unit/test_utilities.cpp b/tests/unit/test_utilities.cpp index e8985b515..26480e2bc 100644 --- a/tests/unit/test_utilities.cpp +++ b/tests/unit/test_utilities.cpp @@ -1,12 +1,15 @@ #include #include +#include "catch2_sequant.hpp" + #include #include #include #include #include #include +#include #include #include @@ -17,22 +20,6 @@ #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(); } diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index 99fec8cd2..c30091597 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -21,6 +21,7 @@ #include #include "test_config.hpp" #include "utils.hpp" +#include "catch2_sequant.hpp" #include From ff16b685bd7f388310260b72e77e6d57847e41e1 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 14 Jan 2025 13:11:41 +0100 Subject: [PATCH 69/85] Fix formatting --- tests/unit/catch2_sequant.hpp | 15 +++++++-------- tests/unit/test_mbpt.cpp | 2 +- tests/unit/test_mbpt_cc.cpp | 2 +- tests/unit/test_spin.cpp | 2 +- tests/unit/test_utilities.cpp | 2 +- tests/unit/test_wick.cpp | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/unit/catch2_sequant.hpp b/tests/unit/catch2_sequant.hpp index c18f83c86..812ad5b7f 100644 --- a/tests/unit/catch2_sequant.hpp +++ b/tests/unit/catch2_sequant.hpp @@ -49,16 +49,15 @@ struct StringMaker { } // namespace Catch - -/// Matches that the tested expression is equivalent to the given one. Two expressions are -/// considered equivalent if they both have the same canonical form (i.e. they are -/// the same expression after canonicalization) +/// Matches that the tested expression is equivalent to the given one. Two +/// expressions are considered equivalent if they both have the same canonical +/// form (i.e. they are the same expression after canonicalization) class EquivalentToMatcher : public Catch::Matchers::MatcherGenericBase { public: - /// Constructs the matcher with the expected expression. The constructor - /// accepts either an actual expression object (as Expr & or ExprPtr) or - /// a (w)string-like object which will then be parsed to yield the actual - /// expression object. + /// Constructs the matcher with the expected expression. The constructor + /// accepts either an actual expression object (as Expr & or ExprPtr) or + /// a (w)string-like object which will then be parsed to yield the actual + /// expression object. template EquivalentToMatcher(T &&expression) { if constexpr (std::is_convertible_v) { diff --git a/tests/unit/test_mbpt.cpp b/tests/unit/test_mbpt.cpp index ef23f38f7..d18dcf995 100644 --- a/tests/unit/test_mbpt.cpp +++ b/tests/unit/test_mbpt.cpp @@ -19,8 +19,8 @@ #include #include -#include "test_config.hpp" #include "catch2_sequant.hpp" +#include "test_config.hpp" #include #include diff --git a/tests/unit/test_mbpt_cc.cpp b/tests/unit/test_mbpt_cc.cpp index 002325172..d82a86c71 100644 --- a/tests/unit/test_mbpt_cc.cpp +++ b/tests/unit/test_mbpt_cc.cpp @@ -7,8 +7,8 @@ #include #include -#include "test_config.hpp" #include "catch2_sequant.hpp" +#include "test_config.hpp" TEST_CASE("SR-TCC", "[mbpt/cc]") { using namespace sequant::mbpt; diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 55a1d0f63..7e4b15991 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -10,8 +10,8 @@ // the old spin attribute representation is used to avoid changing the tests #include -#include "test_config.hpp" #include "catch2_sequant.hpp" +#include "test_config.hpp" #include #include diff --git a/tests/unit/test_utilities.cpp b/tests/unit/test_utilities.cpp index 26480e2bc..54be913e5 100644 --- a/tests/unit/test_utilities.cpp +++ b/tests/unit/test_utilities.cpp @@ -8,8 +8,8 @@ #include #include #include -#include #include +#include #include #include diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index c30091597..9297d5d9a 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -19,9 +19,9 @@ #include #include +#include "catch2_sequant.hpp" #include "test_config.hpp" #include "utils.hpp" -#include "catch2_sequant.hpp" #include From 8512cc70687fbf6786a6db963ffa4d3acf095311 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 15 Jan 2025 15:50:16 +0100 Subject: [PATCH 70/85] Make ExprPtr hash as pointed-to Expr --- CMakeLists.txt | 1 + SeQuant/core/hash.cpp | 8 ++++++++ SeQuant/core/hash.hpp | 4 ++++ tests/unit/test_expr.cpp | 2 ++ 4 files changed, 15 insertions(+) create mode 100644 SeQuant/core/hash.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 01f0c0793..e3c2fa4fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -209,6 +209,7 @@ set(SeQuant_src SeQuant/core/expr.hpp SeQuant/core/expr_algorithm.hpp SeQuant/core/expr_operator.hpp + SeQuant/core/hash.cpp SeQuant/core/hash.hpp SeQuant/core/hugenholtz.hpp SeQuant/core/index.cpp diff --git a/SeQuant/core/hash.cpp b/SeQuant/core/hash.cpp new file mode 100644 index 000000000..2b5f84836 --- /dev/null +++ b/SeQuant/core/hash.cpp @@ -0,0 +1,8 @@ +#include +#include + +namespace sequant { + +std::size_t hash_value(const ExprPtr &expr) { return hash_value(*expr); } + +} // namespace sequant diff --git a/SeQuant/core/hash.hpp b/SeQuant/core/hash.hpp index 7c2b48d60..7dd413b7a 100644 --- a/SeQuant/core/hash.hpp +++ b/SeQuant/core/hash.hpp @@ -22,6 +22,8 @@ namespace sequant_boost = boost; namespace sequant { +class ExprPtr; + namespace hash { /// the hashing versions known to SeQuant (N.B. hashing changed in Boost 1.81) @@ -89,6 +91,8 @@ auto hash_value(const T& obj) { return sequant_boost::hash_value(obj); } +std::size_t hash_value(const ExprPtr& expr); + // clang-format off // rationale: // boost::hash_combine is busted ... it dispatches to one of 3 implementations (all line numbers refer to boost 1.72.0): diff --git a/tests/unit/test_expr.cpp b/tests/unit/test_expr.cpp index 817e9e217..b858ce737 100644 --- a/tests/unit/test_expr.cpp +++ b/tests/unit/test_expr.cpp @@ -643,6 +643,8 @@ TEST_CASE("Expr", "[elements]") { REQUIRE_NOTHROW(hash_value(ex5_init)); REQUIRE(hash_value(ex5_init) != hash_value(ex(1))); + REQUIRE(hash_value(ex(1)) == hash_value(ex(1))); + auto hasher = [](const std::shared_ptr &) -> unsigned int { return 0; }; From 9739183442d35966b02abc7caadfdebf75c31a59 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 15 Jan 2025 17:18:04 +0100 Subject: [PATCH 71/85] Improvements for Catch2 SeQuant support --- tests/unit/catch2_sequant.hpp | 40 ++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/tests/unit/catch2_sequant.hpp b/tests/unit/catch2_sequant.hpp index 812ad5b7f..60140c689 100644 --- a/tests/unit/catch2_sequant.hpp +++ b/tests/unit/catch2_sequant.hpp @@ -23,14 +23,30 @@ namespace Catch { template <> struct StringMaker { - static std::string convert(const sequant::Expr &expr) { + static std::string convert(const sequant::Expr &expr, + bool include_canonical = true) { + std::string str; try { - return sequant::to_string(sequant::deparse(expr, true)); + str = sequant::to_string(sequant::deparse(expr, true)); } catch (const std::exception &) { // deparse doesn't support all kinds of expressions -> fall back to LaTeX // representation - return sequant::to_string(sequant::to_latex(expr)); + str = sequant::to_string(sequant::to_latex(expr)); } + + if (include_canonical) { + sequant::ExprPtr clone = expr.clone(); + canonicalize(clone); + simplify(clone); + std::string canon_str = + StringMaker::convert(*clone, false); + + if (canon_str != str) { + str += " (canonicalized: " + canon_str + ")"; + } + } + + return str; } }; template <> @@ -39,6 +55,12 @@ struct StringMaker { return StringMaker::convert(*expr); } }; +template <> +struct StringMaker { + static std::string convert(const sequant::Tensor &tensor) { + return StringMaker::convert(tensor); + } +}; template <> struct StringMaker { @@ -49,6 +71,8 @@ struct StringMaker { } // namespace Catch +namespace { + /// Matches that the tested expression is equivalent to the given one. Two /// expressions are considered equivalent if they both have the same canonical /// form (i.e. they are the same expression after canonicalization) @@ -81,15 +105,19 @@ class EquivalentToMatcher : public Catch::Matchers::MatcherGenericBase { assert(m_expr); // Bring expression into canonical form - m_expr->canonicalize(); + simplify(m_expr); + canonicalize(m_expr); } + bool match(const sequant::ExprPtr &expr) const { return match(*expr); } + bool match(const sequant::Expr &expr) const { // Never modify the expression that we are trying to check in order to avoid // side-effects sequant::ExprPtr clone = expr.clone(); - clone->canonicalize(); + canonicalize(clone); + simplify(clone); return *clone == *m_expr; } @@ -102,6 +130,8 @@ class EquivalentToMatcher : public Catch::Matchers::MatcherGenericBase { sequant::ExprPtr m_expr; }; +} // namespace + template EquivalentToMatcher EquivalentTo(T &&expression) { return EquivalentToMatcher(std::forward(expression)); From 45ba1d826f9db9fd5703b9792b99034800132226 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 15 Jan 2025 17:19:32 +0100 Subject: [PATCH 72/85] Fix EvalNode tests --- tests/unit/test_eval_node.cpp | 62 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/tests/unit/test_eval_node.cpp b/tests/unit/test_eval_node.cpp index 752f28866..af8cb2898 100644 --- a/tests/unit/test_eval_node.cpp +++ b/tests/unit/test_eval_node.cpp @@ -23,8 +23,6 @@ #include -#include "utils.hpp" - namespace { auto eval_node(sequant::ExprPtr const& expr) { @@ -76,21 +74,24 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { auto node1 = eval_node(p1); - REQUIRE_TENSOR_EQUAL(node(node1, {}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); + REQUIRE_THAT(node(node1, {}).as_tensor(), EquivalentTo("I{a1,a2;i1,i2}:A")); REQUIRE(node(node1, {R}).as_constant() == Constant{rational{1, 16}}); - REQUIRE_TENSOR_EQUAL(node(node1, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); + REQUIRE_THAT(node(node1, {L}).as_tensor(), + EquivalentTo("I{a1,a2;i1,i2}:A")); - REQUIRE_TENSOR_EQUAL(node(node1, {L, L}).as_tensor(), L"I_{a1,a2}^{a3,a4}"); + REQUIRE_THAT(node(node1, {L, L}).as_tensor(), + EquivalentTo("I{a1,a2;a3,a4}:A")); - REQUIRE_TENSOR_EQUAL(node(node1, {L, R}).as_tensor(), L"t_{a3,a4}^{i1,i2}"); + REQUIRE_THAT(node(node1, {L, R}).as_tensor(), + EquivalentTo("t{a3,a4;i1,i2}:A")); - REQUIRE_TENSOR_EQUAL(node(node1, {L, L, L}).as_tensor(), - L"g_{i3,i4}^{a3,a4}"); + REQUIRE_THAT(node(node1, {L, L, L}).as_tensor(), + EquivalentTo("g{i3,i4;a3,a4}:A")); - REQUIRE_TENSOR_EQUAL(node(node1, {L, L, R}).as_tensor(), - L"t_{a1,a2}^{i3,i4}"); + REQUIRE_THAT(node(node1, {L, L, R}).as_tensor(), + EquivalentTo("t{a1,a2;i3,i4}:A")); // 1/16 * A * (B * C) auto node2p = Product{p1->as().scalar(), {}}; @@ -100,20 +101,24 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { auto const node2 = eval_node(ex(node2p)); - REQUIRE_TENSOR_EQUAL(node(node2, {}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); + REQUIRE_THAT(node(node2, {}).as_tensor(), EquivalentTo("I{a1,a2;i1,i2}:N")); - REQUIRE_TENSOR_EQUAL(node(node2, {L}).as_tensor(), L"I_{a1,a2}^{i1,i2}"); + REQUIRE_THAT(node(node2, {L}).as_tensor(), + EquivalentTo("I{a1,a2;i1,i2}:N")); REQUIRE(node(node2, {R}).as_constant() == Constant{rational{1, 16}}); - REQUIRE_TENSOR_EQUAL(node(node2, {L, L}).as_tensor(), L"g{i3,i4; a3,a4}"); + REQUIRE_THAT(node(node2, {L, L}).as_tensor(), + EquivalentTo("g{i3,i4; a3,a4}:A")); - REQUIRE_TENSOR_EQUAL(node(node2, {L, R}).as_tensor(), - L"I{a1,a2,a3,a4;i3,i4,i1,i2}"); + REQUIRE_THAT(node(node2, {L, R}).as_tensor(), + EquivalentTo("I{a1,a2,a3,a4;i3,i4,i1,i2}:A")); - REQUIRE_TENSOR_EQUAL(node(node2, {L, R, L}).as_tensor(), L"t{a1,a2;i3,i4}"); + REQUIRE_THAT(node(node2, {L, R, L}).as_tensor(), + EquivalentTo("t{a1,a2;i3,i4}:A")); - REQUIRE_TENSOR_EQUAL(node(node2, {L, R, R}).as_tensor(), L"t{a3,a4;i1,i2}"); + REQUIRE_THAT(node(node2, {L, R, R}).as_tensor(), + EquivalentTo("t{a3,a4;i1,i2}:A")); } SECTION("sum") { @@ -125,17 +130,18 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { auto const node1 = eval_node(sum1); REQUIRE(node1->op_type() == EvalOp::Sum); REQUIRE(node1.left()->op_type() == EvalOp::Sum); - REQUIRE_TENSOR_EQUAL(node1.left()->as_tensor(), L"I^{i1,i2}_{a1,a2}"); - REQUIRE_TENSOR_EQUAL(node1.left().left()->as_tensor(), - L"X^{i1,i2}_{a1,a2}"); - REQUIRE_TENSOR_EQUAL(node1.left().right()->as_tensor(), - L"Y^{i1,i2}_{a1,a2}"); + REQUIRE_THAT(node1.left()->as_tensor(), EquivalentTo("I{a1,a2;i1,i2}:A")); + REQUIRE_THAT(node1.left().left()->as_tensor(), + EquivalentTo("X{a1,a2;i1,i2}:A")); + REQUIRE_THAT(node1.left().right()->as_tensor(), + EquivalentTo("Y{a1,a2;i1,i2}:A")); REQUIRE(node1.right()->op_type() == EvalOp::Prod); - 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}"); + REQUIRE_THAT(node1.right()->as_tensor(), EquivalentTo("I{a2,a1;i1,i2}:N")); + REQUIRE_THAT(node1.right().left()->as_tensor(), + EquivalentTo("g{i3,a1;i1,i2}:A")); + REQUIRE_THAT(node1.right().right()->as_tensor(), + EquivalentTo("t{a2;i3}:A")); } SECTION("variable") { @@ -167,8 +173,8 @@ TEST_CASE("TEST EVAL_NODE", "[EvalNode]") { auto prod2 = parse_expr(L"a * t{i1;a1}"); auto node3 = eval_node(prod2); - REQUIRE_TENSOR_EQUAL(node(node3, {}), L"I{i1;a1}"); - REQUIRE_TENSOR_EQUAL(node(node3, {R}), L"t{i1;a1}"); + REQUIRE_THAT(node(node3, {}).as_tensor(), EquivalentTo("I{i1;a1}")); + REQUIRE_THAT(node(node3, {R}).as_tensor(), EquivalentTo("t{i1;a1}")); REQUIRE(node(node3, {L}).as_variable() == Variable{L"a"}); } From 60d96096d592c972d201a7ffdbda0600f84a4d2f Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 15 Jan 2025 17:20:43 +0100 Subject: [PATCH 73/85] Remove now redundant test utils header --- tests/unit/test_spin.cpp | 248 ++++++++++++--------------------------- tests/unit/test_wick.cpp | 11 +- tests/unit/utils.hpp | 32 ----- 3 files changed, 79 insertions(+), 212 deletions(-) delete mode 100644 tests/unit/utils.hpp diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 7e4b15991..18b7d55d4 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -30,7 +30,6 @@ #include #include "test_config.hpp" -#include "utils.hpp" #include #include @@ -272,9 +271,7 @@ TEST_CASE("Spin", "[spin]") { REQUIRE(result->is()); canonicalize(result); REQUIRE(result->size() == 2); - REQUIRE_SUM_EQUAL(result, - L"{ \\bigl({{g^{{p_3}{p_4}}_{{p_1}{p_2}}}} - " - L"{{g^{{p_4}{p_3}}_{{p_1}{p_2}}}}\\bigr) }"); + REQUIRE_THAT(result, EquivalentTo("- g{p1,p2;p4,p3} + g{p1,p2;p3,p4}")); } SECTION("Product") { @@ -328,17 +325,11 @@ TEST_CASE("Spin", "[spin]") { canonicalize(result); REQUIRE(result->is()); REQUIRE(result->size() == 5); - REQUIRE_SUM_EQUAL( + REQUIRE_THAT( result, - L"{ \\bigl( - " - L"{{{\\frac{1}{2}}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{" - L"a_2}}}} + " - L"{{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}}} " - L"+ {{f^{{a_1}}_{{i_1}}}{t^{{i_1}}_{{a_1}}}} - " - L"{{{\\frac{1}{2}}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}" - L"}_{{a_1}}}{t^{{i_1}}_{{a_2}}}} + " - L"{{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}" - L"}_{{a_1}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); + EquivalentTo("-1/2 g{i1,i2;a1,a2} t{a1,a2;i2,i1} + g{i1,i2;a1,a2} " + "t{a1,a2;i1,i2} + f{i1;a1} t{a1;i1} - 1/2 g{i1,i2;a1,a2} " + "t{a1;i2} t{a2;i1} + g{i1,i2;a1,a2} t{a1;i1} t{a2;i2}")); } // Sum SECTION("Expand Antisymmetrizer"){// 0-body @@ -558,21 +549,15 @@ SECTION("Expand Symmetrizer") { REQUIRE(result->size() == 6); result->canonicalize(); rapid_simplify(result); - REQUIRE( - to_latex(result) == - L"{ " - L"\\bigl({{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{i_" - L"3}}_{{a_4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_2}}_{{a_1}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_1}}}{t^{{i_2}}_{{a_" - L"4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_4}{i_1}}_{{a_2}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_1}}_{{a_" - L"4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_3}{i_5}}_{{a_1}{a_2}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_5}}_{{a_1}}}{t^{{i_3}}_{{a_" - L"4}}}{t^{{i_2}}_{{a_5}}}{t^{{i_1}{i_4}}_{{a_2}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_2}}}{t^{{i_1}}_{{a_" - L"4}}}{t^{{i_3}}_{{a_5}}}{t^{{i_2}{i_5}}_{{a_1}{a_3}}}} + " - L"{{{4}}{g^{{a_4}{a_5}}_{{i_4}{i_5}}}{t^{{i_4}}_{{a_3}}}{t^{{i_2}}_{{a_" - L"4}}}{t^{{i_1}}_{{a_5}}}{t^{{i_5}{i_3}}_{{a_1}{a_2}}}}\\bigr) }"); + REQUIRE_THAT( + result, + EquivalentTo( + "4 g{i4,i5;a4,a5} t{a2;i4} t{a4;i3} t{a5;i1} t{a1,a3;i5,i2} + " + "4 g{i4,i5;a4,a5} t{a1;i5} t{a4;i2} t{a5;i3} t{a2,a3;i4,i1} + " + "4 g{i4,i5;a4,a5} t{a3;i4} t{a4;i1} t{a5;i2} t{a1,a2;i3,i5} + " + "4 g{i4,i5;a4,a5} t{a1;i5} t{a4;i3} t{a5;i2} t{a2,a3;i1,i4} + " + "4 g{i4,i5;a4,a5} t{a2;i4} t{a4;i1} t{a5;i3} t{a1,a3;i2,i5} + " + "4 g{i4,i5;a4,a5} t{a3;i4} t{a4;i2} t{a5;i1} t{a1,a2;i5,i3}")); } } @@ -591,9 +576,8 @@ SECTION("Symmetrize expression") { ex(L"t", bra{L"a_3"}, ket{L"i_1"}); auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); - REQUIRE(to_latex(result) == - L"{{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{i_2}{a_3}}_{{a_1}{a_2}}}{t^{" - L"{i_1}}_{{a_3}}}}"); + REQUIRE_THAT(result, + EquivalentTo("S{i1,i2;a1,a2} g{a1,a2;i2,a3}:S t{a3;i1}")); } { @@ -609,9 +593,10 @@ SECTION("Symmetrize expression") { ex(L"t", bra{L"a_1"}, ket{L"i_4"}) * ex(L"t", bra{L"a_3"}, ket{L"i_1"}); auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}); - REQUIRE(to_latex(result) == - L"{{S^{{a_2}{a_1}}_{{i_3}{i_4}}}{g^{{i_4}{a_3}}_{{i_1}{i_2}}}{t^{" - L"{i_1}}_{{a_1}}}{t^{{i_2}}_{{a_2}}}{t^{{i_3}}_{{a_3}}}}"); + REQUIRE_THAT( + result, + EquivalentTo( + "S{i3,i4;a2,a1} g{i1,i2;i4,a3}:S t{a1;i1} t{a2;i2} t{a3;i3}")); } { @@ -632,10 +617,8 @@ SECTION("Symmetrize expression") { auto result = factorize_S(input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); REQUIRE(result->is() == false); - REQUIRE( - to_latex(result) == - L"{{{2}}{S^{{a_1}{a_2}}_{{i_1}{i_2}}}{g^{{a_3}{a_4}}_{{i_3}{i_4}}}{t^" - L"{{i_3}}_{{a_4}}}{t^{{i_4}}_{{a_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_3}}}}"); + REQUIRE_THAT(result, EquivalentTo("2 S{i1,i2;a1,a2} g{i3,i4;a3,a4}:S " + "t{a4;i3} t{a2;i4} t{a1,a3;i1,i2}")); } } @@ -651,20 +634,18 @@ SECTION("Transform expression") { expand(result); rapid_simplify(result); canonicalize(result); - REQUIRE_SUM_EQUAL( + REQUIRE_THAT( result, - L"{ \\bigl( - {{g^{{a_2}{i_1}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}} + " - L"{{{2}}{g^{{i_1}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}}\\bigr) }"); + EquivalentTo("- g{a1,i2;a2,i1} t{a2;i2} + 2 g{a1,i2;i1,a2} t{a2;i2}")); container::map idxmap = {{Index{L"i_1"}, Index{L"i_2"}}, {Index{L"i_2"}, Index{L"i_1"}}}; auto transformed_result = transform_expr(result, idxmap); REQUIRE(transformed_result->is()); REQUIRE(transformed_result->size() == 2); - REQUIRE_SUM_EQUAL( + REQUIRE_THAT( transformed_result, - L"{ \\bigl( - {{g^{{a_2}{i_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}} + " - L"{{{2}}{g^{{i_2}{a_2}}_{{a_1}{i_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); + EquivalentTo("- g{a1,i1;a2,i2} t{a2;i1} + 2 g{a1,i1;i2,a2} t{a2;i1}")); } SECTION("Swap bra kets") { @@ -713,11 +694,9 @@ SECTION("Closed-shell spintrace CCD") { const auto input = ex(ExprPtrList{parse_expr( L"1/4 g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_1,i_2}", Symmetry::antisymm)}); auto result = closed_shell_CC_spintrace(input); - REQUIRE_SUM_EQUAL( - result, - to_latex(parse_expr(L"- g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_2,i_1} + " - L"2 g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_1,i_2}", - Symmetry::nonsymm))); + REQUIRE_THAT(result, + EquivalentTo(L"- g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_2,i_1} + " + L"2 g{i_1,i_2;a_1,a_2} t{a_1,a_2;i_1,i_2}")); } { // CSV (aka PNO) Index i1(L"i_1", {L"i", 0b01}); @@ -797,10 +776,7 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - {{f^{{i_1}}_{{i_2}}}{t^{{i_2}}_{{a_1}}}}\\bigr) }"); + REQUIRE_THAT(result, EquivalentTo("- f{i2;i1} t{a1;i2}")); } { @@ -811,10 +787,7 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl({{f^{{a_2}}_{{a_1}}}{t^{{i_1}}_{{a_2}}}}\\bigr) }"); + REQUIRE_THAT(result, EquivalentTo("f{a1;a2} t{a2;i1}")); } { @@ -828,14 +801,10 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE_SUM_EQUAL(result, - L"{ " - L"\\bigl({{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{" - L"a_1}{a_2}}}} - " - L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}{i_2}}_{{a_" - L"1}{a_2}}}}\\bigr) }"); + REQUIRE_THAT( + result, + EquivalentTo( + "g{i2,i3;i1,a2} t{a1,a2;i3,i2} - 2 g{i2,i3;a2,i1} t{a1,a2;i3,i2}")); } { @@ -849,15 +818,9 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE_SUM_EQUAL( - result, - L"{ \\bigl( - " - L"{{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_1}{i_2}}_{{a_2}{a_3}}}} + " - L"{{{2}}{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}{i_1}}_{{a_2}{a_3}}" - L"}}\\bigr) }"); + REQUIRE_THAT(result, EquivalentTo("- g{a1,i2;a3,a2} t{a2,a3;i1,i2} + 2 " + "g{a1,i2;a3,a2} t{a2,a3;i2,i1}")); } { @@ -870,12 +833,9 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE( - to_latex(result) == - L"{ \\bigl( - {{f^{{a_2}}_{{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{a_2}}}} + " - L"{{{2}}{f^{{a_2}}_{{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}}}\\bigr) }"); + REQUIRE_THAT( + result, + EquivalentTo("-f{i2;a2} t{a1,a2;i2,i1} + 2 f{i2;a2} t{a1,a2;i1,i2}")); } { @@ -888,14 +848,8 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ " - L"\\bigl({{{2}}{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_2}}_{{a_2}}}{t^{" - L"{i_1}}_{{a_3}}}} - " - L"{{g^{{a_3}{a_2}}_{{a_1}{i_2}}}{t^{{i_1}}_{{a_2}}}{t^{{i_2}}_{{a_" - L"3}}}}\\bigr) }"); + REQUIRE_THAT(result, EquivalentTo("2 g{a1,i2;a3,a2} t{a2;i2} t{a3;i1} - " + "g{a1,i2;a3,a2} t{a2;i1} t{a3;i2}")); } { @@ -908,14 +862,8 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ " - L"\\bigl({{g^{{i_1}{a_2}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}" - L"}_{{a_2}}}} - " - L"{{{2}}{g^{{a_2}{i_1}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}}_" - L"{{a_2}}}}\\bigr) }"); + REQUIRE_THAT(result, EquivalentTo("g{i2,i3;i1,a2} t{a1;i3} t{a2;i2} - 2 " + "g{i2,i3;a2,i1} t{a1;i3} t{a2;i2}")); } { @@ -928,12 +876,7 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{f^{{a_2}}_{{i_2}}}{t^{{i_2}}_{{a_1}}}{t^{{i_1}}_{{a_2}}}}" - L"\\bigr) }"); + REQUIRE_THAT(result, EquivalentTo("-f{i2;a2} t{a1;i2} t{a2;i1}")); } { @@ -948,14 +891,9 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ " - L"\\bigl({{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_1}" - L"{i_2}}_{{a_2}{a_3}}}} - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}{" - L"i_1}}_{{a_2}{a_3}}}}\\bigr) }"); + REQUIRE_THAT(result, + EquivalentTo("g{i2,i3;a2,a3} t{a1;i3} t{a2,a3;i1,i2} - 2 " + "g{i2,i3;a2,a3} t{a1;i3} t{a2,a3;i2,i1}")); } { @@ -970,14 +908,9 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ " - L"\\bigl({{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_2}}}{t^{{i_3}" - L"{i_2}}_{{a_1}{a_3}}}} - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_1}}_{{a_3}}}{t^{{i_3}{" - L"i_2}}_{{a_1}{a_2}}}}\\bigr) }"); + REQUIRE_THAT(result, + EquivalentTo("g{i2,i3;a2,a3} t{a2;i1} t{a1,a3;i3,i2} - 2 " + "g{i2,i3;a2,a3} t{a3;i1} t{a1,a2;i3,i2}")); } { @@ -992,18 +925,11 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{i_1}{" - L"i_3}}_{{a_1}{a_2}}}} + " - L"{{{4}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_1}{" - L"i_3}}_{{a_1}{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_3}}}{t^{{i_3}{i_1}}_" - L"{{a_1}{a_2}}}} - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_2}}_{{a_2}}}{t^{{i_3}{" - L"i_1}}_{{a_1}{a_3}}}}\\bigr) }"); + REQUIRE_THAT(result, + EquivalentTo("-2 g{i2,i3;a2,a3} t{a3;i2} t{a1,a2;i1,i3} + 4 " + "g{i2,i3;a2,a3} t{a2;i2} t{a1,a3;i1,i3} + " + "g{i2,i3;a2,a3} t{a3;i2} t{a1,a2;i3,i1} - 2 " + "g{i2,i3;a2,a3} t{a2;i2} t{a1,a3;i3,i1}")); } { @@ -1017,14 +943,9 @@ SECTION("Closed-shell spintrace CCSD") { auto result = ex(rational{1, 2}) * spintrace(input, {{L"i_1", L"a_1"}}); expand(result); - rapid_simplify(result); - canonicalize(result); - REQUIRE(to_latex(result) == - L"{ \\bigl( - " - L"{{{2}}{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_2}}_" - L"{{a_2}}}{t^{{i_1}}_{{a_3}}}} + " - L"{{g^{{a_2}{a_3}}_{{i_2}{i_3}}}{t^{{i_3}}_{{a_1}}}{t^{{i_1}}_{{a_" - L"2}}}{t^{{i_2}}_{{a_3}}}}\\bigr) }"); + REQUIRE_THAT(result, + EquivalentTo("-2 g{i2,i3;a2,a3} t{a1;i3} t{a2;i2} t{a3;i1} + " + "g{i2,i3;a2,a3} t{a1;i3} t{a2;i1} t{a3;i2}")); } } // CCSD R1 @@ -1044,17 +965,12 @@ SECTION("Closed-shell spintrace CCSDT terms") { input, {{L"i_1", L"a_1"}, {L"i_2", L"a_2"}, {L"i_3", L"a_3"}}); simplify(result); REQUIRE(result->size() == 4); - REQUIRE_SUM_EQUAL( + REQUIRE_THAT( result, - L"{ " - L"\\bigl({{{2}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{" - L"{i_3}}_{{i_4}}}{t^{{i_4}{i_1}{i_2}}_{{a_1}{a_2}{a_3}}}} - " - L"{{{4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_" - L"{{i_4}}}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}} + " - L"{{{4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" - L"}{t^{{i_1}{i_2}{i_4}}_{{a_1}{a_2}{a_3}}}} - " - L"{{{2}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_4}}" - L"}{t^{{i_2}{i_1}{i_4}}_{{a_1}{a_2}{a_3}}}}\\bigr) }"); + EquivalentTo("2 S{i1,i2,i3;a1,a2,a3} f{i4;i3} t{a1,a2,a3;i4,i1,i2} - 4 " + "S{i1,i2,i3;a1,a2,a3} f{i4;i3} t{a1,a2,a3;i1,i4,i2} + 4 " + "S{i1,i2,i3;a1,a2,a3} f{i4;i3} t{a1,a2,a3;i1,i2,i4} - 2 " + "S{i1,i2,i3;a1,a2,a3} f{i4;i3} t{a1,a2,a3;i2,i1,i4}")); } { // f * t3 @@ -1402,20 +1318,12 @@ SECTION("Open-shell spin-tracing") { REQUIRE(to_latex(result[0]) == L"{{{\\frac{1}{12}}}{f^{{a↑_4}}_{{a↑_1}}}{\\bar{t}^{{i↑_1}{i↑_2}{" L"i↑_3}}_{{a↑_2}{a↑_3}{a↑_4}}}}"); - REQUIRE_SUM_EQUAL( - result[1], - L"{ \\bigl( - " - L"{{{\\frac{1}{12}}}{f^{{a↑_3}}_{{a↑_1}}}{t^{{i↑_1}{i↑_2}{i↓_3}}_" - L"{{a↑_2}{a↑_3}{a↓_3}}}} + " - L"{{{\\frac{1}{12}}}{f^{{a↑_3}}_{{a↑_1}}}{t^{{i↑_2}{i↑_1}{i↓_3}}_" - L"{{a↑_2}{a↑_3}{a↓_3}}}}\\bigr) }"); - REQUIRE_SUM_EQUAL( - result[2], - L"{ \\bigl( - " - L"{{{\\frac{1}{12}}}{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↓_3}{i↓_2}}_" - L"{{a↑_2}{a↓_2}{a↓_3}}}} + " - L"{{{\\frac{1}{12}}}{f^{{a↑_2}}_{{a↑_1}}}{t^{{i↑_1}{i↓_2}{i↓_3}}_{{" - L"a↑_2}{a↓_2}{a↓_3}}}}\\bigr) }"); + REQUIRE_THAT(result[1], + EquivalentTo("-1/12 f{a↑1;a↑3} t{a↑2,a↑3,a↓3;i↑_1,i↑2,i↓3} + " + "1/12 f{a↑1;a↑3} t{a↑2,a↑3,a↓3;i↑2,i↑1,i↓3}")); + REQUIRE_THAT(result[2], + EquivalentTo("-1/12 f{a↑1;a↑2} t{a↑2,a↓2,a↓3;i↑1,i↓3,i↓2} + " + "1/12 f{a↑1;a↑2} t{a↑2,a↓2,a↓3;i↑1,i↓2,i↓3}")); REQUIRE(to_latex(result[3]) == L"{{{\\frac{1}{12}}}{f^{{a↓_4}}_{{a↓_1}}}{\\bar{t}^{{i↓_1}{i↓_2}{" L"i↓_3}}_{{a↓_2}{a↓_3}{a↓_4}}}}"); @@ -1436,11 +1344,9 @@ SECTION("Open-shell spin-tracing") { ex(g) * ex(t3); auto result = expand_A_op(input); result->visit(reset_idx_tags); - canonicalize(result); - rapid_simplify(result); - REQUIRE(to_latex(result) == - L"{{{\\frac{1}{3}}}{\\bar{g}^{{i↑_1}{i↑_2}}_{{i↑_3}{i↑_4}}}{t^{{" - L"i↑_3}{i↑_4}{i↓_3}}_{{a↑_1}{a↑_2}{a↓_3}}}}"); + REQUIRE_THAT( + result, + EquivalentTo("1/3 g{i↑3,i↑4;i↑1,i↑2}:A t{a↑1,a↑2,a↓3;i↑3,i↑4,i↓3}:N")); g = Tensor(L"g", bra{i4A, i5A}, ket{i1A, i2A}, Symmetry::antisymm); t3 = @@ -1450,11 +1356,9 @@ SECTION("Open-shell spin-tracing") { ex(t3); result = expand_A_op(input); result->visit(reset_idx_tags); - canonicalize(result); - rapid_simplify(result); - REQUIRE(to_latex(result) == - L"{{{\\frac{1}{3}}}{\\bar{g}^{{i↑_1}{i↑_2}}_{{i↑_3}{i↑_4}}}{t^{{" - L"i↑_3}{i↑_4}{i↓_3}}_{{a↑_1}{a↑_2}{a↓_3}}}}"); + REQUIRE_THAT( + result, + EquivalentTo("1/3 g{i↑3,i↑4;i↑1,i↑2}:A t{a↑_1,a↑2,a↓3;i↑3,i↑4,i↓3}:N")); } // CCSDT R3 10 aaa, bbb diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index 9297d5d9a..8564bcef6 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -21,7 +21,6 @@ #include #include "catch2_sequant.hpp" #include "test_config.hpp" -#include "utils.hpp" #include @@ -913,13 +912,9 @@ TEST_CASE("WickTheorem", "[algorithms][wick]") { std::wcout << L"spinfree H2*T2 = " << to_latex(wick_result_2) << std::endl; - REQUIRE_SUM_EQUAL( - wick_result_2, - L"{ \\bigl( - " - L"{{{4}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_2}{i_1}}_{{a_1}{a_2}}" - L"}} + " - L"{{{8}}{g^{{a_1}{a_2}}_{{i_1}{i_2}}}{t^{{i_1}{i_2}}_{{a_1}{a_2}}" - L"}}\\bigr) }"); + REQUIRE_THAT(wick_result_2, + EquivalentTo("-4 * g{i1,i2;a1,a2} t{a1,a2;i2,i1} + 8 " + "g{i1,i2;a1,a2} t{a1,a2;i1,i2}")); } }); diff --git a/tests/unit/utils.hpp b/tests/unit/utils.hpp deleted file mode 100644 index e0e13312f..000000000 --- a/tests/unit/utils.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef SEQUANT_TESTS_UTILS_HPP_ -#define SEQUANT_TESTS_UTILS_HPP_ - -#include - -#include -#include - -/// Checks all permutations of summands in order to match the LaTeX -/// representation to the given string. We do this as the order of summands is -/// dependent on the used hash function (which messes with tests) but ultimately -/// the order of summands does not matter whatsoever. Thus, it is sufficient to -/// test whether some permutation of summands yields the desired LaTeX -/// representation. -#define REQUIRE_SUM_EQUAL(sum, str) \ - REQUIRE(sum.is()); \ - if (to_latex(sum) != str) { \ - for (sequant::intmax_t i = 0; i < sequant::factorial(sum->size()); ++i) { \ - std::next_permutation(sum->begin(), sum->end()); \ - if (to_latex(sum) == str) { \ - break; \ - } \ - } \ - } \ - REQUIRE(to_latex(sum) == std::wstring(str)) - -#define REQUIRE_TENSOR_EQUAL(tensor, spec) \ - REQUIRE(sequant::to_latex(tensor) == \ - sequant::to_latex( \ - sequant::parse_expr(spec, sequant::Symmetry::antisymm))); - -#endif From de0d23019a973f364918c420c36f2c956ba3ff1f Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 15 Jan 2025 17:21:07 +0100 Subject: [PATCH 74/85] Add catch2_sequant.hpp to CMakeLists.txt --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3c2fa4fd..ee759aca1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -414,6 +414,7 @@ if (BUILD_TESTING) include(FindOrFetchCatch2) set(utests_src + tests/unit/catch2_sequant.hpp tests/unit/test_space.cpp tests/unit/test_index.cpp tests/unit/test_op.cpp From f87ebc7541cbe17aac958c2e15a5f52a625ea95e Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Wed, 15 Jan 2025 17:24:05 +0100 Subject: [PATCH 75/85] Use new antisymm. tensor LaTeX repr in test --- tests/unit/test_canonicalize.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 4b821862e..1093560f5 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -88,7 +88,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto op = ex(L"B", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, aux{L"p_5"}, Symmetry::antisymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{{-}{B^{{p_3}{p_4}}_{{p_1}{p_2}}[{p_5}]}}"); + REQUIRE(to_latex(op) == + L"{{-}{\\bar{B}^{{p_3}{p_4}}_{{p_1}{p_2}}[{p_5}]}}"); } } From 77944ed729accc553f729c2c6ea17dfb856195b7 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 16 Jan 2025 10:11:12 +0100 Subject: [PATCH 76/85] Properly parse conjugate-complex variables --- SeQuant/core/parse/ast_conversions.hpp | 8 +++++--- SeQuant/core/parse/deparse.cpp | 2 +- tests/unit/test_parse.cpp | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/SeQuant/core/parse/ast_conversions.hpp b/SeQuant/core/parse/ast_conversions.hpp index df41e6608..6e1025d7e 100644 --- a/SeQuant/core/parse/ast_conversions.hpp +++ b/SeQuant/core/parse/ast_conversions.hpp @@ -194,11 +194,13 @@ ExprPtr ast_to_expr(const parse::ast::NullaryValue &value, } ExprPtr operator()(const parse::ast::Variable &variable) const { + ExprPtr var = ex(variable.name); + if (variable.conjugated) { - return ex(variable.name + L"^*"); - } else { - return ex(variable.name); + var->as().conjugate(); } + + return var; } ExprPtr operator()(const parse::ast::Number &number) const { diff --git a/SeQuant/core/parse/deparse.cpp b/SeQuant/core/parse/deparse.cpp index c95690ffc..023f755c8 100644 --- a/SeQuant/core/parse/deparse.cpp +++ b/SeQuant/core/parse/deparse.cpp @@ -162,7 +162,7 @@ std::wstring deparse(const Constant& constant) { } std::wstring deparse(const Variable& variable) { - return std::wstring(variable.label()); + return std::wstring(variable.label()) + (variable.conjugated() ? L"^*" : L""); } std::wstring deparse(Product const& prod, bool annot_sym) { diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index d08e91a46..207ca6abf 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -204,9 +204,8 @@ TEST_CASE("parse_expr", "[parse]") { REQUIRE(parse_expr(L"α^*")->is()); REQUIRE(parse_expr(L"β^*")->is()); REQUIRE(parse_expr(L"b^*")->is()); - // Currently the conjugated "property" really just is part of the - // variable's name - REQUIRE(parse_expr(L"b^*")->as().label() == L"b^*"); + REQUIRE(parse_expr(L"b^*")->as().conjugated()); + REQUIRE(parse_expr(L"b^*")->as().label() == L"b"); } SECTION("Product") { @@ -369,7 +368,8 @@ TEST_CASE("deparse", "[parse]") { L"a + b - 4 specialVariable", L"variable + A{a_1;i_1}:N * B{i_1;a_1}:A", L"1/2 (a + b) * c", - L"T1{}:N + T2{;;x_1}:N * T3{;;x_1}:N + T4{a_1;;x_2}:S * T5{;a_1;x_2}:S"}; + L"T1{}:N + T2{;;x_1}:N * T3{;;x_1}:N + T4{a_1;;x_2}:S * T5{;a_1;x_2}:S", + L"q1 * q2^* * q3"}; for (const std::wstring& current : expressions) { ExprPtr expression = parse_expr(current); From e68607c503fc33e5fb442261c1bdffd9550ae3ac Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 16 Jan 2025 10:52:44 +0100 Subject: [PATCH 77/85] Implement SimplifiesToMatcher --- tests/unit/catch2_sequant.hpp | 100 ++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 35 deletions(-) diff --git a/tests/unit/catch2_sequant.hpp b/tests/unit/catch2_sequant.hpp index 60140c689..55f22e589 100644 --- a/tests/unit/catch2_sequant.hpp +++ b/tests/unit/catch2_sequant.hpp @@ -73,40 +73,39 @@ struct StringMaker { namespace { -/// Matches that the tested expression is equivalent to the given one. Two -/// expressions are considered equivalent if they both have the same canonical -/// form (i.e. they are the same expression after canonicalization) -class EquivalentToMatcher : public Catch::Matchers::MatcherGenericBase { +/// Converts the given expression-like object into an actual ExprPtr. +/// It accepts either an actual expression object (as Expr & or ExprPtr) or +/// a (w)string-like object which will then be parsed to yield the actual +/// expression object. +template +sequant::ExprPtr to_expression(T &&expression) { + if constexpr (std::is_convertible_v) { + return sequant::parse_expr( + sequant::to_wstring(std::string(std::forward(expression))), + sequant::Symmetry::nonsymm); + } else if constexpr (std::is_convertible_v) { + return sequant::parse_expr(std::wstring(std::forward(expression)), + sequant::Symmetry::nonsymm); + } else if constexpr (std::is_convertible_v) { + // Clone in order to not have to worry about later modification + return expression.clone(); + } else { + static_assert(std::is_convertible_v, + "Invalid type for expression"); + + // Clone in order to not have to worry about later modification + return expression->clone(); + } +} + +template +class ExpressionMatcher : public Catch::Matchers::MatcherGenericBase { public: - /// Constructs the matcher with the expected expression. The constructor - /// accepts either an actual expression object (as Expr & or ExprPtr) or - /// a (w)string-like object which will then be parsed to yield the actual - /// expression object. template - EquivalentToMatcher(T &&expression) { - if constexpr (std::is_convertible_v) { - m_expr = sequant::parse_expr( - sequant::to_wstring(std::string(std::forward(expression))), - sequant::Symmetry::nonsymm); - } else if constexpr (std::is_convertible_v) { - m_expr = sequant::parse_expr(std::wstring(std::forward(expression)), - sequant::Symmetry::nonsymm); - } else if constexpr (std::is_convertible_v) { - // Clone in order to not have to worry about later modification - m_expr = expression.clone(); - } else { - static_assert(std::is_convertible_v, - "Invalid type for expression"); - - // Clone in order to not have to worry about later modification - m_expr = expression->clone(); - } - + ExpressionMatcher(T &&expression) + : m_expr(to_expression(std::forward(expression))) { assert(m_expr); - - // Bring expression into canonical form - simplify(m_expr); - canonicalize(m_expr); + Subclass::pre_comparison(m_expr); } bool match(const sequant::ExprPtr &expr) const { return match(*expr); } @@ -116,20 +115,46 @@ class EquivalentToMatcher : public Catch::Matchers::MatcherGenericBase { // side-effects sequant::ExprPtr clone = expr.clone(); - canonicalize(clone); - simplify(clone); + Subclass::pre_comparison(clone); return *clone == *m_expr; } std::string describe() const override { - return "Equivalent to: " + Catch::Detail::stringify(m_expr); + return Subclass::comparison_requirement() + ": " + + Catch::Detail::stringify(m_expr); } - private: + protected: sequant::ExprPtr m_expr; }; +/// Matches that the tested expression is equivalent to the given one. Two +/// expressions are considered equivalent if they both have the same canonical +/// form (i.e. they are the same expression after canonicalization) +struct EquivalentToMatcher : ExpressionMatcher { + using ExpressionMatcher::ExpressionMatcher; + + static void pre_comparison(sequant::ExprPtr &expr) { + sequant::canonicalize(expr); + sequant::simplify(expr); + } + + static std::string comparison_requirement() { return "Equivalent to"; } +}; + +/// Matches that the tested expression simplifies (**without** +/// re-canonicalization!) to the same form as the given one +struct SimplifiesToMatcher : ExpressionMatcher { + using ExpressionMatcher::ExpressionMatcher; + + static void pre_comparison(sequant::ExprPtr &expr) { + sequant::rapid_simplify(expr); + } + + static std::string comparison_requirement() { return "Simplifies to"; } +}; + } // namespace template @@ -137,4 +162,9 @@ EquivalentToMatcher EquivalentTo(T &&expression) { return EquivalentToMatcher(std::forward(expression)); } +template +SimplifiesToMatcher SimplifiesTo(T &&expression) { + return SimplifiesToMatcher(std::forward(expression)); +} + #endif From d66f6a08eff52b0e99e0776007b39e8b60364cd8 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 16 Jan 2025 10:53:11 +0100 Subject: [PATCH 78/85] Use SimplifiesTo matcher in canonicalization tests --- tests/unit/test_canonicalize.cpp | 129 ++++++++++++++----------------- 1 file changed, 58 insertions(+), 71 deletions(-) diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 1093560f5..a1e909fe4 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -32,37 +32,37 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, Symmetry::nonsymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{g^{{p_3}{p_4}}_{{p_1}{p_2}}}"); + REQUIRE_THAT(op, SimplifiesTo("g{p1,p2;p3,p4}")); } { auto op = ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_3", L"p_4"}, Symmetry::nonsymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{g^{{p_4}{p_3}}_{{p_1}{p_2}}}"); + REQUIRE_THAT(op, SimplifiesTo("g{p1,p2;p4,p3}")); } { auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, Symmetry::nonsymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{g^{{p_4}{p_3}}_{{p_1}{p_2}}}"); + REQUIRE_THAT(op, SimplifiesTo("g{p1,p2;p4,p3}")); } { auto op = ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, Symmetry::nonsymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{g^{{p_3}{p_4}}_{{p_1}{p_2}}}"); + REQUIRE_THAT(op, SimplifiesTo("g{p1,p2;p3,p4}")); } { auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, Symmetry::symm); canonicalize(op); - REQUIRE(to_latex(op) == L"{g^{{p_3}{p_4}}_{{p_1}{p_2}}}"); + REQUIRE_THAT(op, SimplifiesTo("g{p1,p2;p3,p4}:S")); } { auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, Symmetry::antisymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{{-}{\\bar{g}^{{p_3}{p_4}}_{{p_1}{p_2}}}}"); + REQUIRE_THAT(op, SimplifiesTo("-g{p1,p2;p3,p4}:A")); } // aux indices @@ -70,26 +70,25 @@ TEST_CASE("Canonicalizer", "[algorithms]") { auto op = ex(L"B", bra{L"p_1"}, ket{L"p_2"}, aux{L"p_3"}, Symmetry::nonsymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{B^{{p_2}}_{{p_1}}[{p_3}]}"); + REQUIRE_THAT(op, SimplifiesTo("B{p1;p2;p3}")); } { auto op = ex(L"B", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, aux{L"p_5"}, Symmetry::nonsymm); canonicalize(op); - REQUIRE(to_latex(op) == L"{B^{{p_4}{p_3}}_{{p_1}{p_2}}[{p_5}]}"); + REQUIRE_THAT(op, SimplifiesTo("B{p1,p2;p4,p3;p5}")); } { auto op = ex(L"B", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, aux{L"p_5"}, Symmetry::symm); canonicalize(op); - REQUIRE(to_latex(op) == L"{B^{{p_3}{p_4}}_{{p_1}{p_2}}[{p_5}]}"); + REQUIRE_THAT(op, SimplifiesTo("B{p1,p2;p3,p4;p5}:S")); } { auto op = ex(L"B", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, aux{L"p_5"}, Symmetry::antisymm); canonicalize(op); - REQUIRE(to_latex(op) == - L"{{-}{\\bar{B}^{{p_3}{p_4}}_{{p_1}{p_2}}[{p_5}]}}"); + REQUIRE_THAT(op, SimplifiesTo("-B{p1,p2;p3,p4;p5}:A")); } } @@ -103,9 +102,9 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"i_1", L"i_2"}, ket{L"a_5", L"a_2"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" - L"a_2}}}{t^{{a_3}}_{{i_3}}}{t^{{a_1}{a_2}}_{{i_1}{i_2}}}}"); + REQUIRE_THAT( + input, + SimplifiesTo("S{a1,a3;i1,i2} f{a2;i3} t{i3;a3} t{i1,i2;a1,a2}")); } { auto input = @@ -116,9 +115,9 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" - L"a_2}}}{t^{{a_2}}_{{i_2}}}{t^{{a_1}{a_3}}_{{i_1}{i_3}}}}"); + REQUIRE_THAT( + input, + SimplifiesTo("S{a1,a3;i1,i2} f{a2;i3} t{i_2;a_2} t{i1,i3;a1,a3}")); } { // Product containing Variables auto q2 = ex(L"q2"); @@ -133,9 +132,10 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{{p}{q1}{{q2}^*}{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" - L"a_2}}}{t^{{a_2}}_{{i_2}}}{t^{{a_1}{a_3}}_{{i_1}{i_3}}}}"); + REQUIRE_THAT( + input, + SimplifiesTo( + "p q1 q2^* S{a1,a3;i1,i2} f{a2;i3} t {i2;a2} t{i1,i3;a1,a3}")); } { // Product containing adjoint of a Tensor auto f2 = ex(L"f", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, @@ -147,9 +147,9 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"f", bra{L"a_5"}, ket{L"i_5"}, Symmetry::nonsymm) * ex(L"t", bra{L"i_1"}, ket{L"a_5"}, Symmetry::nonsymm) * f2; canonicalize(input1); - REQUIRE(to_latex(input1) == - L"{{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{a_2}}}{f⁺^{{i_1}{i_" - L"3}}_{{a_1}{a_3}}}{t^{{a_2}}_{{i_2}}}}"); + REQUIRE_THAT( + input1, + SimplifiesTo("S{a1,a3;i1,i2} f{a2;i3} f⁺{a1,a3;i1,i3} t{i2;a2}")); auto input2 = ex(L"S", bra{L"a_1", L"a_2"}, ket{L"i_1", L"i_2"}, Symmetry::nonsymm) * @@ -157,9 +157,10 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"i_1"}, ket{L"a_5"}, Symmetry::nonsymm) * f2 * ex(L"w") * ex(rational{1, 2}); canonicalize(input2); - REQUIRE(to_latex(input2) == - L"{{{\\frac{1}{2}}}{w}{S^{{i_1}{i_2}}_{{a_1}{a_3}}}{f^{{i_3}}_{{" - L"a_2}}}{f⁺^{{i_1}{i_3}}_{{a_1}{a_3}}}{t^{{a_2}}_{{i_2}}}}"); + REQUIRE_THAT( + input2, + SimplifiesTo( + "1/2 w S{a1,a3;i1,i2} f{a2;i3} f⁺{a1,a3;i1,i3} t{i2;a2}")); } } { @@ -171,9 +172,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"p_4"}, ket{L"p_2"}, Symmetry::nonsymm) * ex(L"t", bra{L"p_3"}, ket{L"p_1"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{{{\\frac{1}{2}}}{t^{{p_3}}_{{p_1}}}{t^{{p_4}}_{{p_2}}}{B^{{p_1}}" - L"_{{p_3}}[{p_5}]}{B^{{p_2}}_{{p_4}}[{p_5}]}}"); + REQUIRE_THAT(input, + SimplifiesTo("1/2 t{p1;p3} t{p2;p4} B{p3;p1;p5} B{p4;p2;p5}")); } SECTION("sum of products") { @@ -191,10 +191,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"p_3"}, ket{L"p_1"}, Symmetry::nonsymm) * ex(L"t", bra{L"p_4"}, ket{L"p_2"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{ " - L"\\bigl({{g^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}{t^{{" - L"p_3}}_{{p_4}}}}\\bigr) }"); + REQUIRE_THAT(input, SimplifiesTo("g{p2,p3;p1,p4} t{p1;p2} t{p4;p3}")); } // CASE 2: Symmetric tensors @@ -211,10 +208,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"p_3"}, ket{L"p_1"}, Symmetry::nonsymm) * ex(L"t", bra{L"p_4"}, ket{L"p_2"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{ " - L"\\bigl({{g^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}{t^{{p_" - L"3}}_{{p_4}}}}\\bigr) }"); + REQUIRE_THAT(input, SimplifiesTo("g{p2,p3;p1,p4}:S t{p1;p2} t{p4;p3}")); } // Case 3: Anti-symmetric tensors @@ -231,11 +225,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"p_3"}, ket{L"p_1"}, Symmetry::nonsymm) * ex(L"t", bra{L"p_4"}, ket{L"p_2"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{ " - L"\\bigl({{\\bar{g}^{{p_1}{p_4}}_{{p_2}{p_3}}}{t^{{p_2}}_{{p_1}}}" - L"{t^{{p_" - L"3}}_{{p_4}}}}\\bigr) }"); + REQUIRE_THAT(input, SimplifiesTo("g{p2,p3;p1,p4}:A t{p1;p2} t{p4;p3}")); } // Case 4: permuted indices @@ -255,10 +245,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { Symmetry::antisymm); canonicalize(input); REQUIRE(input->size() == 1); - REQUIRE(to_latex(input) == - L"{ " - "\\bigl({{\\bar{g}^{{i_1}{a_3}}_{{i_3}{i_4}}}{t^{{i_3}}_{{a_2}}}{" - "\\bar{t}^{{i_2}{i_4}}_{{a_1}{a_3}}}}\\bigr) }"); + REQUIRE_THAT(input, + SimplifiesTo("g{i3,i4;i1,a3}:A t{a2;i3} t{a1,a3;i2,i4}:A")); } // Case 4: permuted indices from CCSD R2 biorthogonal configuration @@ -279,10 +267,8 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(input->size() == 1); - REQUIRE(to_latex(input) == - L"{ " - L"\\bigl({{g^{{a_3}{i_1}}_{{i_3}{i_4}}}{t^{{i_3}}_{{a_2}}}{t^{{i_" - L"4}{i_2}}_{{a_1}{a_3}}}}\\bigr) }"); + REQUIRE_THAT(input, + SimplifiesTo("g{i3,i4;a3,i1} t{a2;i3} t{a1,a3;i4,i2}")); } { // Case 5: CCSDT R3: S3 * F * T3 @@ -302,10 +288,10 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, ket{L"i_2", L"i_4", L"i_3"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{ \\bigl( - " - L"{{{8}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_" - L"4}}}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}}\\bigr) }"); + REQUIRE_THAT( + input, + SimplifiesTo( + "-8 S{i1,i2,i3;a1,a2,a3} f{i4;i3} t{a1,a2,a3;i1,i4,i2}")); } { @@ -325,17 +311,20 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ket{L"i_2", L"i_4", L"i_3"}, Symmetry::nonsymm); canonicalize(term1); canonicalize(term2); - REQUIRE(to_latex(term1) == - L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_3}{i_4}}}{f^{{i_4}}_{{i_" - L"2}}}{t^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}}"); - REQUIRE(to_latex(term2) == - L"{{{-4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_3}{i_4}}}{f^{{i_4}}_{{i_" - L"2}}}{t^{{i_1}{i_2}{i_3}}_{{a_1}{a_2}{a_3}}}}"); + REQUIRE_THAT( + term1, + SimplifiesTo( + "-4 S{i1,i3,i4;a1,a2,a3} f{i2;i4} t{a1,a2,a3;i1,i2,i3}")); + REQUIRE_THAT( + term2, + SimplifiesTo( + "-4 S{i1,i3,i4;a1,a2,a3} f{i2;i4} t{a1,a2,a3;i1,i2,i3}")); auto sum_of_terms = term1 + term2; simplify(sum_of_terms); - REQUIRE(to_latex(sum_of_terms) == - L"{{{-8}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}}_{{i_" - L"4}}}{t^{{i_1}{i_4}{i_2}}_{{a_1}{a_2}{a_3}}}}"); + REQUIRE_THAT( + sum_of_terms, + SimplifiesTo( + "-8 S{i1,i2,i3;a1,a2,a3} f{i4;i3} t{a1,a2,a3;i1,i4,i2}")); } { // Terms 2 and 4 from spin-traced result @@ -353,10 +342,9 @@ TEST_CASE("Canonicalizer", "[algorithms]") { ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, ket{L"i_2", L"i_3", L"i_4"}, Symmetry::nonsymm); canonicalize(input); - REQUIRE(to_latex(input) == - L"{ " - L"\\bigl({{{4}}{S^{{a_1}{a_2}{a_3}}_{{i_1}{i_2}{i_3}}}{f^{{i_3}" - L"}_{{i_4}}}{t^{{i_4}{i_1}{i_2}}_{{a_1}{a_2}{a_3}}}}\\bigr) }"); + REQUIRE_THAT( + input, SimplifiesTo( + "4 S{i1,i2,i3;a1,a2,a3} f{i4;i3} t{a1,a2,a3;i4,i1,i2}")); } } @@ -382,10 +370,9 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(input->size() == 1); - REQUIRE(to_latex(input) == - L"{ " - L"\\bigl({{t^{{i_3}}_{{a_2}}}{t^{{i_4}{i_2}}_{{a_1}{a_3}}}{B^{{a_" - L"3}}_{{i_3}}[{p_5}]}{B^{{i_1}}_{{i_4}}[{p_5}]}}\\bigr) }"); + REQUIRE_THAT( + input, + SimplifiesTo("t{a2;i3} t{a1,a3;i4,i2} B{i3;a3;p5} B{i4;i1;p5}")); } } } From 3c596c2e0cab1cc26f75e8f4a26a57ba0a567778 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 16 Jan 2025 11:01:19 +0100 Subject: [PATCH 79/85] Make use of to_string utility function --- tests/unit/test_tensor_network.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 9f4fbb11a..23f810d2e 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -645,12 +646,6 @@ TEST_CASE("TensorNetwork", "[elements]") { } // TEST_CASE("TensorNetwork") -std::string to_utf8(const std::wstring& wstr) { - using convert_type = std::codecvt_utf8; - std::wstring_convert converter; - return converter.to_bytes(wstr); -} - template std::vector to_tensors(const Container& cont) { std::vector tensors; @@ -944,7 +939,7 @@ TEST_CASE("TensorNetworkV2", "[elements]") { TensorNetwork(first).make_bliss_graph(); wick_graph->write_dot(stream, labels, true); - FAIL(to_utf8(stream.str())); + FAIL(to_string(stream.str())); } TensorNetworkV2 tn1(first); @@ -1117,7 +1112,7 @@ TEST_CASE("TensorNetworkV2", "[elements]") { } sstream << "\nInput was " << deparse(ex(factors)) << "\n"; - FAIL(to_utf8(sstream.str())); + FAIL(to_string(sstream.str())); } } while (std::next_permutation(indices.begin() + 4, indices.end())); } while (std::next_permutation(indices.begin(), indices.begin() + 4)); From 0931c3dd2f05e4acd510abc7df3453d8bd594934 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 16 Jan 2025 13:06:20 +0100 Subject: [PATCH 80/85] Extract VertexPainter implementation --- CMakeLists.txt | 2 + SeQuant/core/tensor_network_v2.cpp | 210 +---------------------------- SeQuant/core/vertex_painter.cpp | 159 ++++++++++++++++++++++ SeQuant/core/vertex_painter.hpp | 106 +++++++++++++++ 4 files changed, 268 insertions(+), 209 deletions(-) create mode 100644 SeQuant/core/vertex_painter.cpp create mode 100644 SeQuant/core/vertex_painter.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ee759aca1..5ab939237 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,6 +253,8 @@ set(SeQuant_src SeQuant/core/utility/singleton.hpp SeQuant/core/utility/string.hpp SeQuant/core/utility/string.cpp + SeQuant/core/vertex_painter.cpp + SeQuant/core/vertex_painter.hpp SeQuant/core/wick.hpp SeQuant/core/wick.impl.hpp SeQuant/core/wolfram.hpp diff --git a/SeQuant/core/tensor_network_v2.cpp b/SeQuant/core/tensor_network_v2.cpp index bdd4a4808..047f78cf0 100644 --- a/SeQuant/core/tensor_network_v2.cpp +++ b/SeQuant/core/tensor_network_v2.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -639,215 +640,6 @@ ExprPtr TensorNetworkV2::canonicalize( return (byproduct->as().value() == 1) ? nullptr : byproduct; } -using ProtoBundle = - std::decay_t().proto_indices())>; - -struct BraGroup { - explicit BraGroup(std::size_t id) : id(id) {} - - std::size_t id; -}; -struct KetGroup { - explicit KetGroup(std::size_t id) : id(id) {} - - std::size_t id; -}; -struct AuxGroup { - explicit AuxGroup(std::size_t id) : id(id) {} - - std::size_t id; -}; -struct ParticleGroup { - explicit ParticleGroup(std::size_t id) : id(id) {} - - std::size_t id; -}; - -class VertexPainter { - public: - using Color = TensorNetworkV2::Graph::VertexColor; - using VertexData = - std::variant; - using ColorMap = container::map; - - VertexPainter(const TensorNetworkV2::NamedIndexSet &named_indices) - : used_colors_(), named_indices_(named_indices) {} - - const ColorMap &used_colors() const { return used_colors_; } - - Color operator()(const AbstractTensor &tensor) { - Color color = to_color(hash::value(label(tensor))); - - return ensure_uniqueness(color, tensor); - } - - Color operator()(const BraGroup &group) { - Color color = to_color(group.id + 0xff); - - return ensure_uniqueness(color, group); - } - - Color operator()(const KetGroup &group) { - Color color = to_color(group.id + 0xff00); - - return ensure_uniqueness(color, group); - } - - Color operator()(const AuxGroup &group) { - Color color = to_color(group.id + 3 * 0xff0000); - - return ensure_uniqueness(color, group); - } - - Color operator()(const ParticleGroup &group) { - Color color = to_color(group.id); - - return ensure_uniqueness(color, group); - } - - Color operator()(const Index &idx) { - auto it = named_indices_.find(idx); - - // TODO: shift - std::size_t pre_color; - if (it == named_indices_.end()) { - // anonymous index - pre_color = idx.color(); - } else { - pre_color = static_cast( - std::distance(named_indices_.begin(), it)); - } - - return ensure_uniqueness(to_color(pre_color), idx); - } - - Color operator()(const ProtoBundle &bundle) { - Color color = to_color(Index::proto_indices_color(bundle)); - - return ensure_uniqueness(color, bundle); - } - - private: - ColorMap used_colors_; - const TensorNetworkV2::NamedIndexSet &named_indices_; - - Color to_color(std::size_t color) const { - // Due to the way we compute the input color, different colors might only - // differ by a value of 1. This is fine for the algorithmic purpose (after - // all, colors need only be different - by how much is irrelevant), but - // sometimes we'll want to use those colors as actual colors to show to a - // human being. In those cases, having larger differences makes it easier to - // recognize different colors. Therefore, we hash-combined with an - // arbitrarily chosen salt with the goal that this will uniformly spread out - // all input values and therefore increase color differences. - constexpr std::size_t salt = 0x43d2c59cb15b73f0; - hash::combine(color, salt); - - if constexpr (sizeof(Color) >= sizeof(std::size_t)) { - return color; - } - - // Need to somehow fit the color into a lower precision integer. In the - // general case, this is necessarily a lossy conversion. We make the - // assumption that the input color is - // - a hash, or - // - computed from some object ID - // In the first case, we assume that the used hash function has a uniform - // distribution or if there is a bias, the bias is towards lower numbers. - // This allows us to simply reuse the lower x bits of the hash as a new hash - // (where x == CHAR_BIT * sizeof(VertexColor)). In the second case we assume - // that such values never exceed the possible value range of VertexColor so - // that again, we can simply take the lower x bits of color and in this case - // even retain the numeric value representing the color. Handily, this is - // exactly what happens when we perform a conversion into a narrower type. - // We only have to make sure that the underlying types are unsigned as - // otherwise the behavior is undefined. - static_assert(sizeof(Color) < sizeof(std::size_t)); - static_assert(std::is_unsigned_v, - "Narrowing conversion are undefined for signed integers"); - static_assert(std::is_unsigned_v, - "Narrowing conversion are undefined for signed integers"); - return static_cast(color); - } - - template - Color ensure_uniqueness(Color color, const T &val) { - auto it = used_colors_.find(color); - while (it != used_colors_.end() && !may_have_same_color(it->second, val)) { - // Color collision: val was computed to have the same color - // as another object, but these objects do not compare equal (for - // the purpose of color assigning). - // -> Need to modify color until conflict is resolved. - color++; - it = used_colors_.find(color); - } - - if (it == used_colors_.end()) { - // We have not yet seen this color before -> add it to cache - if constexpr (std::is_same_v || - std::is_same_v) { - used_colors_[color] = &val; - } else { - used_colors_[color] = val; - } - } - - return color; - } - - bool may_have_same_color(const VertexData &data, - const AbstractTensor &tensor) { - return std::holds_alternative(data) && - label(*std::get(data)) == label(tensor); - } - - bool may_have_same_color(const VertexData &data, const BraGroup &group) { - return std::holds_alternative(data) && - std::get(data).id == group.id; - } - - bool may_have_same_color(const VertexData &data, const KetGroup &group) { - return std::holds_alternative(data) && - std::get(data).id == group.id; - } - - bool may_have_same_color(const VertexData &data, const AuxGroup &group) { - return std::holds_alternative(data) && - std::get(data).id == group.id; - } - - bool may_have_same_color(const VertexData &data, const ParticleGroup &group) { - return std::holds_alternative(data) && - std::get(data).id == group.id; - } - - bool may_have_same_color(const VertexData &data, const Index &idx) { - if (!std::holds_alternative(data)) { - return false; - } - - const Index &lhs = std::get(data); - - auto it1 = named_indices_.find(lhs); - auto it2 = named_indices_.find(idx); - - if (it1 != it2) { - // Either one index is named and the other is not or both are named, but - // are different indices - return false; - } - - return lhs.color() == idx.color(); - } - - bool may_have_same_color(const VertexData &data, const ProtoBundle &bundle) { - return std::holds_alternative(data) && - Index::proto_indices_color(*std::get(data)) == - Index::proto_indices_color(bundle); - } -}; - TensorNetworkV2::Graph TensorNetworkV2::create_graph( const NamedIndexSet *named_indices_ptr) const { assert(have_edges_); diff --git a/SeQuant/core/vertex_painter.cpp b/SeQuant/core/vertex_painter.cpp new file mode 100644 index 000000000..06b23cce2 --- /dev/null +++ b/SeQuant/core/vertex_painter.cpp @@ -0,0 +1,159 @@ +#include +#include + +namespace sequant { + +VertexPainter::VertexPainter( + const TensorNetworkV2::NamedIndexSet &named_indices) + : used_colors_(), named_indices_(named_indices) {} + +VertexPainter::Color VertexPainter::operator()(const AbstractTensor &tensor) { + Color color = to_color(hash::value(label(tensor))); + + return ensure_uniqueness(color, tensor); +} + +VertexPainter::Color VertexPainter::operator()(const BraGroup &group) { + Color color = to_color(group.id + 0xff); + + return ensure_uniqueness(color, group); +} + +VertexPainter::Color VertexPainter::operator()(const KetGroup &group) { + Color color = to_color(group.id + 0xff00); + + return ensure_uniqueness(color, group); +} + +VertexPainter::Color VertexPainter::operator()(const AuxGroup &group) { + Color color = to_color(group.id + 3 * 0xff0000); + + return ensure_uniqueness(color, group); +} + +VertexPainter::Color VertexPainter::operator()(const ParticleGroup &group) { + Color color = to_color(group.id); + + return ensure_uniqueness(color, group); +} + +VertexPainter::Color VertexPainter::operator()(const Index &idx) { + auto it = named_indices_.find(idx); + + std::size_t pre_color; + if (it == named_indices_.end()) { + // anonymous index + pre_color = idx.color(); + } else { + pre_color = static_cast( + std::distance(named_indices_.begin(), it)); + } + // shift + pre_color += 0xaa; + + return ensure_uniqueness(to_color(pre_color), idx); +} + +VertexPainter::Color VertexPainter::operator()(const ProtoBundle &bundle) { + Color color = to_color(Index::proto_indices_color(bundle)); + + return ensure_uniqueness(color, bundle); +} + +VertexPainter::Color VertexPainter::to_color(std::size_t color) const { + // Due to the way we compute the input color, different colors might only + // differ by a value of 1. This is fine for the algorithmic purpose (after + // all, colors need only be different - by how much is irrelevant), but + // sometimes we'll want to use those colors as actual colors to show to a + // human being. In those cases, having larger differences makes it easier to + // recognize different colors. Therefore, we hash-combine with an + // arbitrarily chosen salt with the goal that this will uniformly spread out + // all input values and therefore increase color differences. + constexpr std::size_t salt = 0x43d2c59cb15b73f0; + hash::combine(color, salt); + + if constexpr (sizeof(Color) >= sizeof(std::size_t)) { + return color; + } + + // Need to somehow fit the color into a lower precision integer. In the + // general case, this is necessarily a lossy conversion. We make the + // assumption that the input color is + // - a hash, or + // - computed from some object ID + // In the first case, we assume that the used hash function has a uniform + // distribution or if there is a bias, the bias is towards lower numbers. + // This allows us to simply reuse the lower x bits of the hash as a new hash + // (where x == CHAR_BIT * sizeof(VertexColor)). In the second case we assume + // that such values never exceed the possible value range of VertexColor so + // that again, we can simply take the lower x bits of color and in this case + // even retain the numeric value representing the color. Handily, this is + // exactly what happens when we perform a conversion into a narrower type. + // We only have to make sure that the underlying types are unsigned as + // otherwise the behavior is undefined. + static_assert(sizeof(Color) < sizeof(std::size_t)); + static_assert(std::is_unsigned_v, + "Narrowing conversion are undefined for signed integers"); + static_assert(std::is_unsigned_v, + "Narrowing conversion are undefined for signed integers"); + return static_cast(color); +} + +bool VertexPainter::may_have_same_color(const VertexData &data, + const AbstractTensor &tensor) { + return std::holds_alternative(data) && + label(*std::get(data)) == label(tensor); +} + +bool VertexPainter::may_have_same_color(const VertexData &data, + const BraGroup &group) { + return std::holds_alternative(data) && + std::get(data).id == group.id; +} + +bool VertexPainter::may_have_same_color(const VertexData &data, + const KetGroup &group) { + return std::holds_alternative(data) && + std::get(data).id == group.id; +} + +bool VertexPainter::may_have_same_color(const VertexData &data, + const AuxGroup &group) { + return std::holds_alternative(data) && + std::get(data).id == group.id; +} + +bool VertexPainter::may_have_same_color(const VertexData &data, + const ParticleGroup &group) { + return std::holds_alternative(data) && + std::get(data).id == group.id; +} + +bool VertexPainter::may_have_same_color(const VertexData &data, + const Index &idx) { + if (!std::holds_alternative(data)) { + return false; + } + + const Index &lhs = std::get(data); + + auto it1 = named_indices_.find(lhs); + auto it2 = named_indices_.find(idx); + + if (it1 != it2) { + // Either one index is named and the other is not or both are named, but + // are different indices + return false; + } + + return lhs.color() == idx.color(); +} + +bool VertexPainter::may_have_same_color(const VertexData &data, + const ProtoBundle &bundle) { + return std::holds_alternative(data) && + Index::proto_indices_color(*std::get(data)) == + Index::proto_indices_color(bundle); +} + +} // namespace sequant diff --git a/SeQuant/core/vertex_painter.hpp b/SeQuant/core/vertex_painter.hpp new file mode 100644 index 000000000..896285707 --- /dev/null +++ b/SeQuant/core/vertex_painter.hpp @@ -0,0 +1,106 @@ +#ifndef SEQUANT_VERTEX_PAINTER_H +#define SEQUANT_VERTEX_PAINTER_H + +#include +#include +#include +#include + +#include +#include + +namespace sequant { + +using ProtoBundle = + std::decay_t().proto_indices())>; + +struct BraGroup { + explicit BraGroup(std::size_t id) : id(id) {} + + std::size_t id; +}; +struct KetGroup { + explicit KetGroup(std::size_t id) : id(id) {} + + std::size_t id; +}; +struct AuxGroup { + explicit AuxGroup(std::size_t id) : id(id) {} + + std::size_t id; +}; +struct ParticleGroup { + explicit ParticleGroup(std::size_t id) : id(id) {} + + std::size_t id; +}; + +/// Can be used to assign unique colors to a set of objects. The class +/// automatically ensures that there are no accidental color duplications for +/// objects that actually should have different colors (i.e. this is more than a +/// hash function). It is intended to be used to determine the vertex colors in +/// a colored graph representing a tensor network. +class VertexPainter { + public: + using Color = TensorNetworkV2::Graph::VertexColor; + using VertexData = + std::variant; + using ColorMap = container::map; + + VertexPainter(const TensorNetworkV2::NamedIndexSet &named_indices); + + const ColorMap &used_colors() const; + + Color operator()(const AbstractTensor &tensor); + Color operator()(const BraGroup &group); + Color operator()(const KetGroup &group); + Color operator()(const AuxGroup &group); + Color operator()(const ParticleGroup &group); + Color operator()(const Index &idx); + Color operator()(const ProtoBundle &bundle); + + private: + ColorMap used_colors_; + const TensorNetworkV2::NamedIndexSet &named_indices_; + + Color to_color(std::size_t color) const; + + template + Color ensure_uniqueness(Color color, const T &val) { + auto it = used_colors_.find(color); + while (it != used_colors_.end() && !may_have_same_color(it->second, val)) { + // Color collision: val was computed to have the same color + // as another object, but these objects do not compare equal (for + // the purpose of color assigning). + // -> Need to modify color until conflict is resolved. + color++; + it = used_colors_.find(color); + } + + if (it == used_colors_.end()) { + // We have not yet seen this color before -> add it to cache + if constexpr (std::is_same_v || + std::is_same_v) { + used_colors_[color] = &val; + } else { + used_colors_[color] = val; + } + } + + return color; + } + + bool may_have_same_color(const VertexData &data, + const AbstractTensor &tensor); + bool may_have_same_color(const VertexData &data, const BraGroup &group); + bool may_have_same_color(const VertexData &data, const KetGroup &group); + bool may_have_same_color(const VertexData &data, const AuxGroup &group); + bool may_have_same_color(const VertexData &data, const ParticleGroup &group); + bool may_have_same_color(const VertexData &data, const Index &idx); + bool may_have_same_color(const VertexData &data, const ProtoBundle &bundle); +}; + +} // namespace sequant + +#endif From 2dfdc85a3cb846ec7bb7e9d32e6d0e080814ec1d Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 16 Jan 2025 14:27:06 +0100 Subject: [PATCH 81/85] Make old graph repr use colors of new one --- SeQuant/core/tensor_network.cpp | 100 +++---- tests/unit/test_canonicalize.cpp | 15 +- tests/unit/test_tensor_network.cpp | 425 +++++++++++++++-------------- 3 files changed, 258 insertions(+), 282 deletions(-) diff --git a/SeQuant/core/tensor_network.cpp b/SeQuant/core/tensor_network.cpp index 9f94d50ff..d30cf6543 100644 --- a/SeQuant/core/tensor_network.cpp +++ b/SeQuant/core/tensor_network.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -425,6 +426,8 @@ TensorNetwork::make_bliss_graph( const auto &named_indices = named_indices_ptr == nullptr ? this->ext_indices() : *named_indices_ptr; + VertexPainter colorizer(named_indices); + // results std::shared_ptr graph; std::vector vertex_labels( @@ -434,25 +437,6 @@ TensorNetwork::make_bliss_graph( std::vector vertex_type( edges_.size()); // the size will be updated - // N.B. Colors [0, 3 max rank + named_indices.size()) are reserved: - // 0 - the bra vertex (for particle 0, if bra is nonsymm, or for the entire - // bra, if (anti)symm) 1 - the bra vertex for particle 1, if bra is nonsymm - // ... - // max_rank - the ket vertex (for particle 0, if particle-asymmetric, or for - // the entire ket, if particle-symmetric) max_rank+1 - the ket vertex for - // particle 1, if particle-asymmetric - // ... - // 2 max_rank - the aux index - // ... - // 3 max_rank - first named index - // 3 max_rank + 1 - second named index - // ... - // N.B. For braket-symmetric tensors the ket vertices use the same indices as - // the bra vertices - auto nonreserved_color = [&named_indices](size_t color) -> bool { - return color >= 3 * max_rank + named_indices.size(); - }; - // compute # of vertices size_t nv = 0; size_t index_cnt = 0; @@ -470,17 +454,8 @@ TensorNetwork::make_bliss_graph( ++nv; // each index is a vertex vertex_labels.at(index_cnt) = idx.to_latex(); vertex_type.at(index_cnt) = VertexType::Index; - // assign color: named indices use reserved colors - const auto named_index_it = named_indices.find(idx); - if (named_index_it == - named_indices.end()) { // anonymous index? use Index::color - const auto idx_color = idx.color(); - assert(nonreserved_color(idx_color)); - vertex_color.at(index_cnt) = idx_color; - } else { - const auto named_index_rank = named_index_it - named_indices.begin(); - vertex_color.at(index_cnt) = 3 * max_rank + named_index_rank; - } + vertex_color.at(index_cnt) = colorizer(idx); + // each symmetric proto index bundle will have a vertex ... // for now only store the unique protoindex bundles in // symmetric_protoindex_bundles, then commit their data to @@ -507,11 +482,10 @@ TensorNetwork::make_bliss_graph( spbundle_label += L"}"; vertex_labels.push_back(spbundle_label); vertex_type.push_back(VertexType::SPBundle); - const auto idx_proto_indices_color = Index::proto_indices_color(bundle); - assert(nonreserved_color(idx_proto_indices_color)); - vertex_color.push_back(idx_proto_indices_color); + vertex_color.push_back(colorizer(bundle)); spbundle_cnt++; }); + // now account for vertex representation of tensors size_t tensor_cnt = 0; // this will map to tensor index to the first (core) vertex in its @@ -524,10 +498,8 @@ TensorNetwork::make_bliss_graph( const auto tlabel = label(*t); vertex_labels.emplace_back(tlabel); vertex_type.emplace_back(VertexType::TensorCore); - const auto t_color = hash::value(tlabel); - static_assert(sizeof(t_color) == sizeof(unsigned long int)); - assert(nonreserved_color(t_color)); - vertex_color.push_back(t_color); + vertex_color.push_back(colorizer(*t)); + // symmetric/antisymmetric tensors are represented by 3 more vertices: // - bra // - ket @@ -539,18 +511,24 @@ TensorNetwork::make_bliss_graph( std::wstring(L"bra") + to_wstring(bra_rank(tref)) + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); vertex_type.push_back(VertexType::TensorBra); - vertex_color.push_back(0); + vertex_color.push_back(colorizer(BraGroup{0})); vertex_labels.push_back( std::wstring(L"ket") + to_wstring(ket_rank(tref)) + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); vertex_type.push_back(VertexType::TensorKet); - vertex_color.push_back( - braket_symmetry(tref) == BraKetSymmetry::symm ? 0 : max_rank); + if (braket_symmetry(tref) == BraKetSymmetry::symm) { + // Use BraGroup for kets as well as they are supposed to be + // indistinguishable + vertex_color.push_back(colorizer(BraGroup{0})); + } else { + vertex_color.push_back(colorizer(KetGroup{0})); + } vertex_labels.push_back( std::wstring(L"bk") + ((symmetry(tref) == Symmetry::antisymm) ? L"a" : L"s")); vertex_type.push_back(VertexType::Particle); - vertex_color.push_back(t_color); + // Color bk node in same color as tensor core + vertex_color.push_back(colorizer(tref)); } // nonsymmetric tensors are represented by 3*rank more vertices (with rank = // max(bra_rank(),ket_rank()) @@ -562,18 +540,24 @@ TensorNetwork::make_bliss_graph( auto pstr = to_wstring(p + 1); vertex_labels.push_back(std::wstring(L"bra") + pstr); vertex_type.push_back(VertexType::TensorBra); - const bool t_is_particle_symmetric = + const bool distinguishable_particles = particle_symmetry(tref) == ParticleSymmetry::nonsymm; - const auto bra_color = t_is_particle_symmetric ? p : 0; - vertex_color.push_back(bra_color); + vertex_color.push_back( + colorizer(BraGroup{distinguishable_particles ? p : 0})); vertex_labels.push_back(std::wstring(L"ket") + pstr); vertex_type.push_back(VertexType::TensorKet); - vertex_color.push_back(braket_symmetry(tref) == BraKetSymmetry::symm - ? bra_color - : bra_color + max_rank); + if (braket_symmetry(tref) == BraKetSymmetry::symm) { + // Use BraGroup for kets as well as they are supposed to be + // indistinguishable + vertex_color.push_back( + colorizer(BraGroup{distinguishable_particles ? p : 0})); + } else { + vertex_color.push_back( + colorizer(KetGroup{distinguishable_particles ? p : 0})); + } vertex_labels.push_back(std::wstring(L"bk") + pstr); vertex_type.push_back(VertexType::Particle); - vertex_color.push_back(t_color); + vertex_color.push_back(colorizer(tref)); } } // aux indices currently do not support any symmetry @@ -583,8 +567,7 @@ TensorNetwork::make_bliss_graph( auto pstr = to_wstring(p + 1); vertex_labels.push_back(std::wstring(L"aux") + pstr); vertex_type.push_back(VertexType::TensorAux); - const auto color = 2 * max_rank + p; - vertex_color.push_back(color); + vertex_color.push_back(colorizer(AuxGroup{p})); } ++tensor_cnt; @@ -659,21 +642,8 @@ TensorNetwork::make_bliss_graph( ++tensor_cnt; }); - // compress vertex colors to 32 bits, as required by Bliss, by hashing - size_t v_cnt = 0; - for (auto &&color : vertex_color) { - auto hash6432shift = [](size_t key) { - static_assert(sizeof(key) == 8); - key = (~key) + (key << 18); // key = (key << 18) - key - 1; - key = key ^ (key >> 31); - key = key * 21; // key = (key + (key << 2)) + (key << 4); - key = key ^ (key >> 11); - key = key + (key << 6); - key = key ^ (key >> 22); - return static_cast(key); - }; - graph->change_color(v_cnt, hash6432shift(color)); - ++v_cnt; + for (const auto [vertex, color] : ranges::views::enumerate(vertex_color)) { + graph->change_color(vertex, color); } return {graph, vertex_labels, vertex_color, vertex_type}; diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index a1e909fe4..77284c749 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -104,7 +104,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE_THAT( input, - SimplifiesTo("S{a1,a3;i1,i2} f{a2;i3} t{i3;a3} t{i1,i2;a1,a2}")); + SimplifiesTo("S{a1,a2;i1,i2} f{a3;i3} t{i3;a2} t{i1,i2;a1,a3}")); } { auto input = @@ -149,7 +149,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input1); REQUIRE_THAT( input1, - SimplifiesTo("S{a1,a3;i1,i2} f{a2;i3} f⁺{a1,a3;i1,i3} t{i2;a2}")); + SimplifiesTo("S{a1,a2;i1,i3} f{a3;i2} f⁺{a1,a2;i1,i2} t{i3;a3}")); auto input2 = ex(L"S", bra{L"a_1", L"a_2"}, ket{L"i_1", L"i_2"}, Symmetry::nonsymm) * @@ -160,7 +160,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { REQUIRE_THAT( input2, SimplifiesTo( - "1/2 w S{a1,a3;i1,i2} f{a2;i3} f⁺{a1,a3;i1,i3} t{i2;a2}")); + "1/2 w S{a1,a2;i1,i3} f{a3;i2} f⁺{a1,a2;i1,i2} t{i3;a3}")); } } { @@ -190,8 +190,9 @@ TEST_CASE("Canonicalizer", "[algorithms]") { Symmetry::nonsymm) * ex(L"t", bra{L"p_3"}, ket{L"p_1"}, Symmetry::nonsymm) * ex(L"t", bra{L"p_4"}, ket{L"p_2"}, Symmetry::nonsymm); + simplify(input); canonicalize(input); - REQUIRE_THAT(input, SimplifiesTo("g{p2,p3;p1,p4} t{p1;p2} t{p4;p3}")); + REQUIRE_THAT(input, SimplifiesTo("g{p3,p4;p1,p2} t{p1;p3} t{p2;p4}")); } // CASE 2: Symmetric tensors @@ -268,7 +269,7 @@ TEST_CASE("Canonicalizer", "[algorithms]") { canonicalize(input); REQUIRE(input->size() == 1); REQUIRE_THAT(input, - SimplifiesTo("g{i3,i4;a3,i1} t{a2;i3} t{a1,a3;i4,i2}")); + SimplifiesTo("g{i3,i4;i1,a3} t{a2;i4} t{a1,a3;i3,i2}")); } { // Case 5: CCSDT R3: S3 * F * T3 @@ -369,10 +370,10 @@ TEST_CASE("Canonicalizer", "[algorithms]") { Symmetry::nonsymm); canonicalize(input); - REQUIRE(input->size() == 1); + simplify(input); REQUIRE_THAT( input, - SimplifiesTo("t{a2;i3} t{a1,a3;i4,i2} B{i3;a3;p5} B{i4;i1;p5}")); + SimplifiesTo("t{a2;i4} t{a1,a3;i3,i2} B{i3;i1;p5} B{i4;a3;p5}")); } } } diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 23f810d2e..f73f6d1bf 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -134,9 +134,9 @@ TEST_CASE("TensorNetwork", "[elements]") { // to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) << // std::endl; REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[0])) == - L"{F^{{i_1}}_{{i_2}}}"); + L"{F^{{i_2}}_{{i_1}}}"); REQUIRE(to_latex(std::dynamic_pointer_cast(tn.tensors()[1])) == - L"{\\tilde{a}^{{i_2}}_{{i_1}}}"); + L"{\\tilde{a}^{{i_1}}_{{i_2}}}"); REQUIRE(tn.idxrepl().size() == 2); } @@ -216,199 +216,202 @@ TEST_CASE("TensorNetwork", "[elements]") { std::basic_ostringstream oss; REQUIRE_NOTHROW(graph->write_dot(oss, vlabels)); // std::wcout << "oss.str() = " << std::endl << oss.str() << std::endl; - REQUIRE(oss.str() == - L"graph g {\n" - "v0 [label=\"{a_1}\"; style=filled; color=\"#64facf\"; " - "fillcolor=\"#64facf80\"; penwidth=2];\n" - "v0 -- v29\n" - "v0 -- v58\n" - "v1 [label=\"{a_2}\"; style=filled; color=\"#64facf\"; " - "fillcolor=\"#64facf80\"; penwidth=2];\n" - "v1 -- v29\n" - "v1 -- v58\n" - "v2 [label=\"{a_3}\"; style=filled; color=\"#64facf\"; " - "fillcolor=\"#64facf80\"; penwidth=2];\n" - "v2 -- v33\n" - "v2 -- v54\n" - "v3 [label=\"{a_4}\"; style=filled; color=\"#64facf\"; " - "fillcolor=\"#64facf80\"; penwidth=2];\n" - "v3 -- v33\n" - "v3 -- v54\n" - "v4 [label=\"{a_5}\"; style=filled; color=\"#64facf\"; " - "fillcolor=\"#64facf80\"; penwidth=2];\n" - "v4 -- v37\n" - "v4 -- v50\n" - "v5 [label=\"{a_6}\"; style=filled; color=\"#64facf\"; " - "fillcolor=\"#64facf80\"; penwidth=2];\n" - "v5 -- v37\n" - "v5 -- v50\n" - "v6 [label=\"{a_7}\"; style=filled; color=\"#64facf\"; " - "fillcolor=\"#64facf80\"; penwidth=2];\n" - "v6 -- v22\n" - "v6 -- v41\n" - "v7 [label=\"{a_8}\"; style=filled; color=\"#64facf\"; " - "fillcolor=\"#64facf80\"; penwidth=2];\n" - "v7 -- v22\n" - "v7 -- v41\n" - "v8 [label=\"{i_1}\"; style=filled; color=\"#9c2a20\"; " - "fillcolor=\"#9c2a2080\"; penwidth=2];\n" - "v8 -- v30\n" - "v8 -- v57\n" - "v9 [label=\"{i_2}\"; style=filled; color=\"#9c2a20\"; " - "fillcolor=\"#9c2a2080\"; penwidth=2];\n" - "v9 -- v30\n" - "v9 -- v57\n" - "v10 [label=\"{i_3}\"; style=filled; color=\"#9c2a20\"; " - "fillcolor=\"#9c2a2080\"; penwidth=2];\n" - "v10 -- v34\n" - "v10 -- v53\n" - "v11 [label=\"{i_4}\"; style=filled; color=\"#9c2a20\"; " - "fillcolor=\"#9c2a2080\"; penwidth=2];\n" - "v11 -- v34\n" - "v11 -- v53\n" - "v12 [label=\"{i_5}\"; style=filled; color=\"#9c2a20\"; " - "fillcolor=\"#9c2a2080\"; penwidth=2];\n" - "v12 -- v38\n" - "v12 -- v49\n" - "v13 [label=\"{i_6}\"; style=filled; color=\"#9c2a20\"; " - "fillcolor=\"#9c2a2080\"; penwidth=2];\n" - "v13 -- v38\n" - "v13 -- v49\n" - "v14 [label=\"{i_7}\"; style=filled; color=\"#9c2a20\"; " - "fillcolor=\"#9c2a2080\"; penwidth=2];\n" - "v14 -- v21\n" - "v14 -- v42\n" - "v15 [label=\"{i_8}\"; style=filled; color=\"#9c2a20\"; " - "fillcolor=\"#9c2a2080\"; penwidth=2];\n" - "v15 -- v21\n" - "v15 -- v42\n" - "v16 [label=\"{\\kappa_1}\"; style=filled; color=\"#7126de\"; " - "fillcolor=\"#7126de80\"; penwidth=2];\n" - "v16 -- v25\n" - "v16 -- v46\n" - "v17 [label=\"{\\kappa_2}\"; style=filled; color=\"#7126de\"; " - "fillcolor=\"#7126de80\"; penwidth=2];\n" - "v17 -- v25\n" - "v17 -- v46\n" - "v18 [label=\"{\\kappa_3}\"; style=filled; color=\"#7126de\"; " - "fillcolor=\"#7126de80\"; penwidth=2];\n" - "v18 -- v26\n" - "v18 -- v45\n" - "v19 [label=\"{\\kappa_4}\"; style=filled; color=\"#7126de\"; " - "fillcolor=\"#7126de80\"; penwidth=2];\n" - "v19 -- v26\n" - "v19 -- v45\n" - "v20 [label=\"A\"; style=filled; color=\"#518020\"; " - "fillcolor=\"#51802080\"; penwidth=2];\n" - "v20 -- v23\n" - "v21 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v21 -- v23\n" - "v22 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v22 -- v23\n" - "v23 [label=\"bka\"; style=filled; color=\"#518020\"; " - "fillcolor=\"#51802080\"; penwidth=2];\n" - "v24 [label=\"g\"; style=filled; color=\"#2e0351\"; " - "fillcolor=\"#2e035180\"; penwidth=2];\n" - "v24 -- v27\n" - "v25 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v25 -- v27\n" - "v26 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v26 -- v27\n" - "v27 [label=\"bka\"; style=filled; color=\"#2e0351\"; " - "fillcolor=\"#2e035180\"; penwidth=2];\n" - "v28 [label=\"t\"; style=filled; color=\"#043e44\"; " - "fillcolor=\"#043e4480\"; penwidth=2];\n" - "v28 -- v31\n" - "v29 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v29 -- v31\n" - "v30 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v30 -- v31\n" - "v31 [label=\"bka\"; style=filled; color=\"#043e44\"; " - "fillcolor=\"#043e4480\"; penwidth=2];\n" - "v32 [label=\"t\"; style=filled; color=\"#043e44\"; " - "fillcolor=\"#043e4480\"; penwidth=2];\n" - "v32 -- v35\n" - "v33 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v33 -- v35\n" - "v34 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v34 -- v35\n" - "v35 [label=\"bka\"; style=filled; color=\"#043e44\"; " - "fillcolor=\"#043e4480\"; penwidth=2];\n" - "v36 [label=\"t\"; style=filled; color=\"#043e44\"; " - "fillcolor=\"#043e4480\"; penwidth=2];\n" - "v36 -- v39\n" - "v37 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v37 -- v39\n" - "v38 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v38 -- v39\n" - "v39 [label=\"bka\"; style=filled; color=\"#043e44\"; " - "fillcolor=\"#043e4480\"; penwidth=2];\n" - "v40 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v40 -- v43\n" - "v41 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v41 -- v43\n" - "v42 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v42 -- v43\n" - "v43 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v44 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v44 -- v47\n" - "v45 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v45 -- v47\n" - "v46 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v46 -- v47\n" - "v47 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v48 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v48 -- v51\n" - "v49 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v49 -- v51\n" - "v50 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v50 -- v51\n" - "v51 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v52 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v52 -- v55\n" - "v53 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v53 -- v55\n" - "v54 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v54 -- v55\n" - "v55 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v56 [label=\"ã\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "v56 -- v59\n" - "v57 [label=\"bra2a\"; style=filled; color=\"#eaa2ab\"; " - "fillcolor=\"#eaa2ab80\"; penwidth=2];\n" - "v57 -- v59\n" - "v58 [label=\"ket2a\"; style=filled; color=\"#5a8fd3\"; " - "fillcolor=\"#5a8fd380\"; penwidth=2];\n" - "v58 -- v59\n" - "v59 [label=\"bka\"; style=filled; color=\"#cbfbe5\"; " - "fillcolor=\"#cbfbe580\"; penwidth=2];\n" - "}\n"); + const std::wstring actual = oss.str(); + const std::wstring expected = + L"graph g {\n" + L"v0 [label=\"{a_1}\"; style=filled; color=\"#2603c0\"; " + L"fillcolor=\"#2603c080\"; penwidth=2];\n" + L"v0 -- v29\n" + L"v0 -- v58\n" + L"v1 [label=\"{a_2}\"; style=filled; color=\"#2603c0\"; " + L"fillcolor=\"#2603c080\"; penwidth=2];\n" + L"v1 -- v29\n" + L"v1 -- v58\n" + L"v2 [label=\"{a_3}\"; style=filled; color=\"#2603c0\"; " + L"fillcolor=\"#2603c080\"; penwidth=2];\n" + L"v2 -- v33\n" + L"v2 -- v54\n" + L"v3 [label=\"{a_4}\"; style=filled; color=\"#2603c0\"; " + L"fillcolor=\"#2603c080\"; penwidth=2];\n" + L"v3 -- v33\n" + L"v3 -- v54\n" + L"v4 [label=\"{a_5}\"; style=filled; color=\"#2603c0\"; " + L"fillcolor=\"#2603c080\"; penwidth=2];\n" + L"v4 -- v37\n" + L"v4 -- v50\n" + L"v5 [label=\"{a_6}\"; style=filled; color=\"#2603c0\"; " + L"fillcolor=\"#2603c080\"; penwidth=2];\n" + L"v5 -- v37\n" + L"v5 -- v50\n" + L"v6 [label=\"{a_7}\"; style=filled; color=\"#2603c0\"; " + L"fillcolor=\"#2603c080\"; penwidth=2];\n" + L"v6 -- v22\n" + L"v6 -- v41\n" + L"v7 [label=\"{a_8}\"; style=filled; color=\"#2603c0\"; " + L"fillcolor=\"#2603c080\"; penwidth=2];\n" + L"v7 -- v22\n" + L"v7 -- v41\n" + L"v8 [label=\"{i_1}\"; style=filled; color=\"#103109\"; " + L"fillcolor=\"#10310980\"; penwidth=2];\n" + L"v8 -- v30\n" + L"v8 -- v57\n" + L"v9 [label=\"{i_2}\"; style=filled; color=\"#103109\"; " + L"fillcolor=\"#10310980\"; penwidth=2];\n" + L"v9 -- v30\n" + L"v9 -- v57\n" + L"v10 [label=\"{i_3}\"; style=filled; color=\"#103109\"; " + L"fillcolor=\"#10310980\"; penwidth=2];\n" + L"v10 -- v34\n" + L"v10 -- v53\n" + L"v11 [label=\"{i_4}\"; style=filled; color=\"#103109\"; " + L"fillcolor=\"#10310980\"; penwidth=2];\n" + L"v11 -- v34\n" + L"v11 -- v53\n" + L"v12 [label=\"{i_5}\"; style=filled; color=\"#103109\"; " + L"fillcolor=\"#10310980\"; penwidth=2];\n" + L"v12 -- v38\n" + L"v12 -- v49\n" + L"v13 [label=\"{i_6}\"; style=filled; color=\"#103109\"; " + L"fillcolor=\"#10310980\"; penwidth=2];\n" + L"v13 -- v38\n" + L"v13 -- v49\n" + L"v14 [label=\"{i_7}\"; style=filled; color=\"#103109\"; " + L"fillcolor=\"#10310980\"; penwidth=2];\n" + L"v14 -- v21\n" + L"v14 -- v42\n" + L"v15 [label=\"{i_8}\"; style=filled; color=\"#103109\"; " + L"fillcolor=\"#10310980\"; penwidth=2];\n" + L"v15 -- v21\n" + L"v15 -- v42\n" + L"v16 [label=\"{\\kappa_1}\"; style=filled; color=\"#0d4103\"; " + L"fillcolor=\"#0d410380\"; penwidth=2];\n" + L"v16 -- v25\n" + L"v16 -- v46\n" + L"v17 [label=\"{\\kappa_2}\"; style=filled; color=\"#0d4103\"; " + L"fillcolor=\"#0d410380\"; penwidth=2];\n" + L"v17 -- v25\n" + L"v17 -- v46\n" + L"v18 [label=\"{\\kappa_3}\"; style=filled; color=\"#0d4103\"; " + L"fillcolor=\"#0d410380\"; penwidth=2];\n" + L"v18 -- v26\n" + L"v18 -- v45\n" + L"v19 [label=\"{\\kappa_4}\"; style=filled; color=\"#0d4103\"; " + L"fillcolor=\"#0d410380\"; penwidth=2];\n" + L"v19 -- v26\n" + L"v19 -- v45\n" + L"v20 [label=\"A\"; style=filled; color=\"#bd2ec1\"; " + L"fillcolor=\"#bd2ec180\"; penwidth=2];\n" + L"v20 -- v23\n" + L"v21 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v21 -- v23\n" + L"v22 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v22 -- v23\n" + L"v23 [label=\"bka\"; style=filled; color=\"#bd2ec1\"; " + L"fillcolor=\"#bd2ec180\"; penwidth=2];\n" + L"v24 [label=\"g\"; style=filled; color=\"#120912\"; " + L"fillcolor=\"#12091280\"; penwidth=2];\n" + L"v24 -- v27\n" + L"v25 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v25 -- v27\n" + L"v26 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v26 -- v27\n" + L"v27 [label=\"bka\"; style=filled; color=\"#120912\"; " + L"fillcolor=\"#12091280\"; penwidth=2];\n" + L"v28 [label=\"t\"; style=filled; color=\"#4b7e1b\"; " + L"fillcolor=\"#4b7e1b80\"; penwidth=2];\n" + L"v28 -- v31\n" + L"v29 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v29 -- v31\n" + L"v30 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v30 -- v31\n" + L"v31 [label=\"bka\"; style=filled; color=\"#4b7e1b\"; " + L"fillcolor=\"#4b7e1b80\"; penwidth=2];\n" + L"v32 [label=\"t\"; style=filled; color=\"#4b7e1b\"; " + L"fillcolor=\"#4b7e1b80\"; penwidth=2];\n" + L"v32 -- v35\n" + L"v33 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v33 -- v35\n" + L"v34 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v34 -- v35\n" + L"v35 [label=\"bka\"; style=filled; color=\"#4b7e1b\"; " + L"fillcolor=\"#4b7e1b80\"; penwidth=2];\n" + L"v36 [label=\"t\"; style=filled; color=\"#4b7e1b\"; " + L"fillcolor=\"#4b7e1b80\"; penwidth=2];\n" + L"v36 -- v39\n" + L"v37 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v37 -- v39\n" + L"v38 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v38 -- v39\n" + L"v39 [label=\"bka\"; style=filled; color=\"#4b7e1b\"; " + L"fillcolor=\"#4b7e1b80\"; penwidth=2];\n" + L"v40 [label=\"ã\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v40 -- v43\n" + L"v41 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v41 -- v43\n" + L"v42 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v42 -- v43\n" + L"v43 [label=\"bka\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v44 [label=\"ã\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v44 -- v47\n" + L"v45 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v45 -- v47\n" + L"v46 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v46 -- v47\n" + L"v47 [label=\"bka\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v48 [label=\"ã\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v48 -- v51\n" + L"v49 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v49 -- v51\n" + L"v50 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v50 -- v51\n" + L"v51 [label=\"bka\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v52 [label=\"ã\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v52 -- v55\n" + L"v53 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v53 -- v55\n" + L"v54 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v54 -- v55\n" + L"v55 [label=\"bka\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v56 [label=\"ã\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"v56 -- v59\n" + L"v57 [label=\"bra2a\"; style=filled; color=\"#6ecb7d\"; " + L"fillcolor=\"#6ecb7d80\"; penwidth=2];\n" + L"v57 -- v59\n" + L"v58 [label=\"ket2a\"; style=filled; color=\"#cfd472\"; " + L"fillcolor=\"#cfd47280\"; penwidth=2];\n" + L"v58 -- v59\n" + L"v59 [label=\"bka\"; style=filled; color=\"#e024b7\"; " + L"fillcolor=\"#e024b780\"; penwidth=2];\n" + L"}\n"; + + REQUIRE(actual == expected); // compute automorphism group { @@ -424,21 +427,23 @@ TEST_CASE("TensorNetwork", "[elements]") { &save_aut); std::basic_ostringstream oss; bliss::print_auts(aut_generators, oss, decltype(vlabels){}); - REQUIRE(oss.str() == - L"(14,15)\n" - "(6,7)\n" - "(18,19)\n" - "(16,17)\n" - "(8,9)\n" - "(0,1)\n" - "(10,11)\n" - "(12,13)\n" - "(2,3)\n" - "(4,5)\n" - "(2,4)(3,5)(10,12)(11,13)(32,36)(33,37)(34,38)(35,39)(48,52)(49," - "53)(50,54)(51,55)\n" - "(0,2)(1,3)(8,10)(9,11)(28,32)(29,33)(30,34)(31,35)(52,56)(53,57)" - "(54,58)(55,59)\n"); + const std::wstring actual = oss.str(); + const std::wstring expected = + L"(18,19)\n" + L"(16,17)\n" + L"(14,15)\n" + L"(6,7)\n" + L"(12,13)\n" + L"(4,5)\n" + L"(10,11)\n" + L"(8,9)\n" + L"(2,3)\n" + L"(0,1)\n" + L"(0,2)(1,3)(8,10)(9,11)(28,32)(29,33)(30,34)(31,35)(52,56)(53,57)(" + L"54,58)(55,59)\n" + L"(2,4)(3,5)(10,12)(11,13)(32,36)(33,37)(34,38)(35,39)(48,52)(49,53)(" + L"50,54)(51,55)\n"; + REQUIRE(actual == expected); // change to 1 to user vertex labels rather than indices if (0) { From 094aae05cf332db11724b199289c2fbc794f8a14 Mon Sep 17 00:00:00 2001 From: Bimal Gaudel Date: Thu, 16 Jan 2025 09:39:46 -0500 Subject: [PATCH 82/85] Ops count for evaluations involving expressions with non-empty proto-indices fixed. --- SeQuant/core/optimize.hpp | 68 +++++++++++++++----------------- SeQuant/core/utility/indices.hpp | 23 ++++++----- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/SeQuant/core/optimize.hpp b/SeQuant/core/optimize.hpp index 1733a1bf3..7491610cf 100644 --- a/SeQuant/core/optimize.hpp +++ b/SeQuant/core/optimize.hpp @@ -52,6 +52,23 @@ class Tensor; namespace opt { +/// +/// \param idxsz An invocable that returns size_t for Index argument. +/// \param idxs Index objects. +/// \return flops count +/// +template , + bool> = true> +double ops_count(IdxToSz const& idxsz, Idxs const& idxs) { + auto oixs = tot_indices(idxs); + double ops = 1.0; + for (auto&& idx : ranges::views::concat(oixs.outer, oixs.inner)) + ops *= std::invoke(idxsz, idx); + // ops == 1.0 implies zero flops. + return ops == 1.0 ? 0 : ops; +} + namespace { /// @@ -85,26 +102,6 @@ void biparts(I n, F const& func) { } } -/// -/// \tparam IdxToSz map-like {IndexSpace : size_t} -/// \param idxsz see @c IdxToSz -/// \param commons Index objects -/// \param diffs Index objects -/// \return flops count -/// @note @c commons and @c diffs have unique indices individually as well as -/// combined -template , - bool> = true> -double ops_count(IdxToSz const& idxsz, container::svector const& commons, - container::svector const& diffs) { - double ops = 1.0; - for (auto&& idx : ranges::views::concat(commons, diffs)) - ops *= std::invoke(idxsz, idx); - // ops == 1.0 implies both commons and diffs empty - return ops == 1.0 ? 0 : ops; -} - /// /// any element in the vector belongs to the integral range [-1,N) /// where N is the length of the [Expr] (ie. the iterable of expressions) @@ -139,20 +136,20 @@ struct OptRes { /// @note I1 and I2 containers are assumed to be sorted by using /// Index::LabelCompare{}; /// -template +template > container::svector common_indices(I1 const& idxs1, I2 const& idxs2) { using std::back_inserter; using std::begin; using std::end; using std::set_intersection; - assert(std::is_sorted(begin(idxs1), end(idxs1), Index::LabelCompare{})); - assert(std::is_sorted(begin(idxs2), end(idxs2), Index::LabelCompare{})); + assert(std::is_sorted(begin(idxs1), end(idxs1), Comp{})); + assert(std::is_sorted(begin(idxs2), end(idxs2), Comp{})); container::svector result; set_intersection(begin(idxs1), end(idxs1), begin(idxs2), end(idxs2), - back_inserter(result), Index::LabelCompare{}); + back_inserter(result), Comp{}); return result; } @@ -163,20 +160,20 @@ container::svector common_indices(I1 const& idxs1, I2 const& idxs2) { /// @note I1 and I2 containers are assumed to be sorted by using /// Index::LabelCompare{}; /// -template +template > container::svector diff_indices(I1 const& idxs1, I2 const& idxs2) { using std::back_inserter; using std::begin; using std::end; using std::set_symmetric_difference; - assert(std::is_sorted(begin(idxs1), end(idxs1), Index::LabelCompare{})); - assert(std::is_sorted(begin(idxs2), end(idxs2), Index::LabelCompare{})); + assert(std::is_sorted(begin(idxs1), end(idxs1), Comp{})); + assert(std::is_sorted(begin(idxs2), end(idxs2), Comp{})); container::svector result; set_symmetric_difference(begin(idxs1), end(idxs1), begin(idxs2), end(idxs2), - back_inserter(result), Index::LabelCompare{}); + back_inserter(result), Comp{}); return result; } @@ -203,12 +200,11 @@ eval_seq_t single_term_opt(TensorNetwork const& network, IdxToSz const& idxsz) { for (std::size_t i = 0; i < nt; ++i) { auto const& tnsr = *network.tensors().at(i); - auto oixs = tot_indices(tnsr); - auto ixs = concat(oixs.outer, oixs.inner) // - | ranges::to; + nth_tensor_indices.emplace_back(); + auto& ixs = nth_tensor_indices.back(); + for (auto&& j : indices(tnsr)) ixs.emplace_back(j); - ranges::sort(ixs, Index::LabelCompare{}); - nth_tensor_indices.emplace_back(std::move(ixs)); + ranges::sort(ixs, std::less{}); } container::svector results((1 << nt), OptRes{{}, 0, {}}); @@ -234,9 +230,9 @@ eval_seq_t single_term_opt(TensorNetwork const& network, IdxToSz const& idxsz) { auto commons = common_indices(results[lpart].indices, results[rpart].indices); auto diffs = diff_indices(results[lpart].indices, results[rpart].indices); - auto new_cost = ops_count(idxsz, // - commons, diffs) // - + results[lpart].flops // + auto new_cost = ops_count(idxsz, // + concat(commons, diffs)) // + + results[lpart].flops // + results[rpart].flops; if (new_cost <= curr_cost) { curr_cost = new_cost; diff --git a/SeQuant/core/utility/indices.hpp b/SeQuant/core/utility/indices.hpp index 5d6900594..55977e825 100644 --- a/SeQuant/core/utility/indices.hpp +++ b/SeQuant/core/utility/indices.hpp @@ -232,25 +232,28 @@ IndexGroups get_unique_indices(const ExprPtr& expr) { } } -template > -TensorOfTensorIndices tot_indices(AbstractTensor const& tnsr) { +template , typename Rng> +TensorOfTensorIndices tot_indices(Rng const& idxs) { + using ranges::not_fn; using ranges::views::concat; using ranges::views::filter; using ranges::views::join; using ranges::views::transform; - auto indep_idxs = filter(indices(tnsr), // - ranges::not_fn(&Index::has_proto_indices)); - - auto dep_idxs = filter(indices(tnsr), &Index::has_proto_indices); + // Container indep_idxs; TensorOfTensorIndices result; + auto& outer = result.outer; + + for (auto&& i : idxs | transform(&Index::proto_indices) | join) + if (!ranges::contains(outer, i)) outer.emplace_back(i); - for (auto&& idx : dep_idxs) result.inner.emplace_back(idx); + for (auto&& i : idxs | filter(not_fn(&Index::has_proto_indices))) + if (!ranges::contains(outer, i)) outer.emplace_back(i); - for (auto&& idx : - concat(indep_idxs, join(dep_idxs | transform(&Index::proto_indices)))) - if (!ranges::contains(result.outer, idx)) result.outer.emplace_back(idx); + auto& inner = result.inner; + for (auto&& i : idxs | filter(&Index::has_proto_indices)) + inner.emplace_back(i); return result; } From a274e52577a77702c71cc7cb94972e542fc17a1f Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 16 Jan 2025 19:03:33 +0100 Subject: [PATCH 83/85] More use of SimplifiesTo matcher --- SeQuant/core/parse/deparse.cpp | 38 +++++++++++++++++++++++++++--- tests/unit/test_parse.cpp | 9 +++---- tests/unit/test_tensor_network.cpp | 4 ++-- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/SeQuant/core/parse/deparse.cpp b/SeQuant/core/parse/deparse.cpp index 023f755c8..24538a8b3 100644 --- a/SeQuant/core/parse/deparse.cpp +++ b/SeQuant/core/parse/deparse.cpp @@ -37,8 +37,8 @@ std::wstring deparse_indices(const Range& indices) { return deparsed; } -std::wstring deparse_sym(Symmetry sym) { - switch (sym) { +std::wstring deparse_symm(Symmetry symm) { + switch (symm) { case Symmetry::symm: return L"S"; case Symmetry::antisymm: @@ -53,6 +53,36 @@ std::wstring deparse_sym(Symmetry sym) { return L"INVALIDANDUNREACHABLE"; } +std::wstring deparse_symm(BraKetSymmetry symm) { + switch (symm) { + case BraKetSymmetry::conjugate: + return L"C"; + case BraKetSymmetry::symm: + return L"S"; + case BraKetSymmetry::nonsymm: + return L"N"; + case BraKetSymmetry::invalid: + return L"INVALID"; + } + + assert(false); + return L"INVALIDANDUNREACHABLE"; +} + +std::wstring deparse_symm(ParticleSymmetry symm) { + switch (symm) { + case ParticleSymmetry::symm: + return L"S"; + case ParticleSymmetry::nonsymm: + return L"N"; + case ParticleSymmetry::invalid: + return L"INVALID"; + } + + assert(false); + return L"INVALIDANDUNREACHABLE"; +} + std::wstring deparse_scalar(const Constant::scalar_type& scalar) { const auto& real = scalar.real(); const auto& realNumerator = boost::multiprecision::numerator(real); @@ -151,7 +181,9 @@ std::wstring deparse(Tensor const& tensor, bool annot_sym) { deparsed += L"}"; if (annot_sym) { - deparsed += L":" + details::deparse_sym(tensor.symmetry()); + deparsed += L":" + details::deparse_symm(tensor.symmetry()); + deparsed += L"-" + details::deparse_symm(tensor.braket_symmetry()); + deparsed += L"-" + details::deparse_symm(tensor.particle_symmetry()); } return deparsed; diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 207ca6abf..574d9f1fb 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -361,14 +361,15 @@ TEST_CASE("deparse", "[parse]") { using namespace sequant; std::vector expressions = { - L"t{a_1,a_2;a_3,a_4}:N", + L"t{a_1,a_2;a_3,a_4}:N-C-S", L"42", L"1/2", - L"-1/4 t{a_1,i_1;a_2,i_2}:S", + L"-1/4 t{a_1,i_1;a_2,i_2}:S-N-N", L"a + b - 4 specialVariable", - L"variable + A{a_1;i_1}:N * B{i_1;a_1}:A", + L"variable + A{a_1;i_1}:N-N-S * B{i_1;a_1}:A-C-S", L"1/2 (a + b) * c", - L"T1{}:N + T2{;;x_1}:N * T3{;;x_1}:N + T4{a_1;;x_2}:S * T5{;a_1;x_2}:S", + L"T1{}:N-N-N + T2{;;x_1}:N-N-N * T3{;;x_1}:N-N-N + T4{a_1;;x_2}:S-C-S * " + L"T5{;a_1;x_2}:S-S-S", L"q1 * q2^* * q3"}; for (const std::wstring& current : expressions) { diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index f73f6d1bf..3e5ab6960 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -910,7 +910,7 @@ TEST_CASE("TensorNetworkV2", "[elements]") { TensorNetworkV2 tn(input); tn.canonicalize(TensorCanonicalizer::cardinal_tensor_labels(), fast); const auto result = ex(to_tensors(tn.tensors())); - REQUIRE(deparse(result) == expected); + REQUIRE_THAT(result, SimplifiesTo(expected)); } } @@ -990,7 +990,7 @@ TEST_CASE("TensorNetworkV2", "[elements]") { prod.as().scale(factor.as().value())); } - REQUIRE(deparse(prod) == expected); + REQUIRE_THAT(prod, SimplifiesTo(expected)); } } From 91f5a5c024995d1272883b1db78fe14fd55bee4e Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Thu, 16 Jan 2025 18:50:03 +0100 Subject: [PATCH 84/85] Parse: support full symmetry specification --- SeQuant/core/parse.hpp | 24 +++++- SeQuant/core/parse/ast.hpp | 17 +++- SeQuant/core/parse/ast_conversions.hpp | 115 ++++++++++++++++++++----- SeQuant/core/parse/parse.cpp | 20 +++-- tests/unit/test_parse.cpp | 20 +++-- 5 files changed, 160 insertions(+), 36 deletions(-) diff --git a/SeQuant/core/parse.hpp b/SeQuant/core/parse.hpp index df9dc9f79..8804abfb7 100644 --- a/SeQuant/core/parse.hpp +++ b/SeQuant/core/parse.hpp @@ -44,7 +44,25 @@ struct ParseError : std::runtime_error { /// '1.0/2.0 * t{i1;a1} * f{i1; a1}' same as above /// 't{i1,i2; a1, a2}' a tensor having indices with proto indices. /// a1 is an index with i1 and i2 as proto-indices. -/// \param tensor_sym The symmetry of all atomic tensors in the +/// Every tensor may optionally be annoted with index symmetry specifications. The general syntax is +/// [: [- [-]]] +/// (no whitespace is allowed at this place). Examples are +/// 't{i1;i2}:A', 't{i1;i2}:A-S', 't{i1;i2}:N-C-S' +/// Possible values for are +/// - 'A' for antisymmetry (sequant::Symmetry::antisymm) +/// - 'S' for symmetric (sequant::Symmetry::symm) +/// - 'N' for non-symmetric (sequant::Symmetry::nonsymm) +/// Possible values for are +/// - 'C' for antisymmetry (sequant::BraKetSymmetry::conjugate) +/// - 'S' for symmetric (sequant::BraKetSymmetry::symm) +/// - 'N' for non-symmetric (sequant::BraKetSymmetry::nonsymm) +/// Possible values for are +/// - 'S' for symmetric (sequant::ParticleSymmetry::symm) +/// - 'N' for non-symmetric (sequant::ParticleSymmetry::nonsymm) +/// \param perm_symm Default index permutation symmetry to be used if tensors don't specify a permutation +/// symmetry explicitly. +/// \param braket_symm Default BraKet symmetry to be used if tensors don't specify a BraKet symmetry explicitly. +/// \param particle_symm Default particle symmetry to be used if tensors don't specify a particle symmetry explicitly. /// @c raw expression. Explicit tensor symmetry can /// be annotated in the expression itself. In that case, the /// annotated symmetry will be used. @@ -54,7 +72,9 @@ struct ParseError : std::runtime_error { /// \return SeQuant expression. // clang-format on ExprPtr parse_expr(std::wstring_view raw, - Symmetry tensor_sym = Symmetry::nonsymm); + Symmetry perm_symm = Symmetry::nonsymm, + BraKetSymmetry braket_symm = BraKetSymmetry::nonsymm, + ParticleSymmetry particle_symm = ParticleSymmetry::symm); /// /// Get a parsable string from an expression. diff --git a/SeQuant/core/parse/ast.hpp b/SeQuant/core/parse/ast.hpp index 2bf749278..521289652 100644 --- a/SeQuant/core/parse/ast.hpp +++ b/SeQuant/core/parse/ast.hpp @@ -7,6 +7,7 @@ #define BOOST_SPIRIT_X3_UNICODE #include +#include #include #include #include @@ -66,17 +67,23 @@ struct IndexGroups : boost::spirit::x3::position_tagged { reverse_bra_ket(reverse_bra_ket) {} }; +struct SymmetrySpec : boost::spirit::x3::position_tagged { + static constexpr char unspecified = '\0'; + char perm_symm = unspecified; + char braket_symm = unspecified; + char particle_symm = unspecified; +}; + struct Tensor : boost::spirit::x3::position_tagged { - static constexpr char unspecified_symmetry = '\0'; std::wstring name; IndexGroups indices; - char symmetry; + boost::optional symmetry; Tensor(std::wstring name = {}, IndexGroups indices = {}, - char symmetry = unspecified_symmetry) + boost::optional symmetry = {}) : name(std::move(name)), indices(std::move(indices)), - symmetry(symmetry) {} + symmetry(std::move(symmetry)) {} }; struct Product; @@ -125,6 +132,8 @@ BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Number, numerator, denominator); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Variable, name, conjugated); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::IndexGroups, bra, ket, auxiliaries, reverse_bra_ket); +BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::SymmetrySpec, perm_symm, + braket_symm, particle_symm); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Tensor, name, indices, symmetry); BOOST_FUSION_ADAPT_STRUCT(sequant::parse::ast::Product, factors); diff --git a/SeQuant/core/parse/ast_conversions.hpp b/SeQuant/core/parse/ast_conversions.hpp index 6e1025d7e..f47580d7b 100644 --- a/SeQuant/core/parse/ast_conversions.hpp +++ b/SeQuant/core/parse/ast_conversions.hpp @@ -22,6 +22,9 @@ namespace sequant::parse::transform { +using DefaultSymmetries = + std::tuple; + template std::tuple get_pos(const AST &ast, const PositionCache &cache, @@ -114,9 +117,9 @@ make_indices(const parse::ast::IndexGroups &groups, } template -Symmetry to_symmetry(char c, std::size_t offset, const Iterator &begin, - Symmetry default_symmetry) { - if (c == parse::ast::Tensor::unspecified_symmetry) { +Symmetry to_perm_symmetry(char c, std::size_t offset, const Iterator &begin, + Symmetry default_symmetry) { + if (c == parse::ast::SymmetrySpec::unspecified) { return default_symmetry; } @@ -136,6 +139,52 @@ Symmetry to_symmetry(char c, std::size_t offset, const Iterator &begin, std::string("Invalid symmetry specifier '") + c + "'"); } +template +BraKetSymmetry to_braket_symmetry(char c, std::size_t offset, + const Iterator &begin, + BraKetSymmetry default_symmetry) { + if (c == parse::ast::SymmetrySpec::unspecified) { + return default_symmetry; + } + + switch (c) { + case 'C': + case 'c': + return BraKetSymmetry::conjugate; + case 'S': + case 's': + return BraKetSymmetry::symm; + case 'N': + case 'n': + return BraKetSymmetry::nonsymm; + } + + throw ParseError( + offset, 1, std::string("Invalid BraKet symmetry specifier '") + c + "'"); +} + +template +ParticleSymmetry to_particle_symmetry(char c, std::size_t offset, + const Iterator &begin, + ParticleSymmetry default_symmetry) { + if (c == parse::ast::SymmetrySpec::unspecified) { + return default_symmetry; + } + + switch (c) { + case 'S': + case 's': + return ParticleSymmetry::symm; + case 'N': + case 'n': + return ParticleSymmetry::nonsymm; + } + + throw ParseError( + offset, 1, + std::string("Invalid particle symmetry specifier '") + c + "'"); +} + template Constant to_constant(const parse::ast::Number &number, const PositionCache &position_cache, @@ -152,45 +201,71 @@ Constant to_constant(const parse::ast::Number &number, } } +template +std::tuple to_symmetries( + const boost::optional &symm_spec, + const DefaultSymmetries &default_symms, const PositionCache &cache, + const Iterator &begin) { + if (!symm_spec.has_value()) { + return {std::get<0>(default_symms), std::get<1>(default_symms), + std::get<2>(default_symms)}; + } + + const ast::SymmetrySpec &spec = symm_spec.get(); + + auto [offset, length] = get_pos(spec, cache, begin); + + // Note: symmetry specifications are a separator (colon or dash) followed by + // an uppercase letter each (no whitespace allowed in-between) + Symmetry perm_symm = to_perm_symmetry(spec.perm_symm, offset + 1, begin, + std::get<0>(default_symms)); + BraKetSymmetry braket_symm = to_braket_symmetry( + spec.braket_symm, offset + 3, begin, std::get<1>(default_symms)); + ParticleSymmetry particle_symm = to_particle_symmetry( + spec.particle_symm, offset + 5, begin, std::get<2>(default_symms)); + + return {perm_symm, braket_symm, particle_symm}; +} + template ExprPtr ast_to_expr(const parse::ast::Product &product, const PositionCache &position_cache, const Iterator &begin, - Symmetry default_symmetry); + const DefaultSymmetries &default_symms); template ExprPtr ast_to_expr(const parse::ast::Sum &sum, const PositionCache &position_cache, const Iterator &begin, - Symmetry default_symmetry); + const DefaultSymmetries &default_symms); template ExprPtr ast_to_expr(const parse::ast::NullaryValue &value, const PositionCache &position_cache, const Iterator &begin, - Symmetry default_symmetry) { + const DefaultSymmetries &default_symms) { struct Transformer { std::reference_wrapper position_cache; std::reference_wrapper begin; - std::reference_wrapper default_symmetry; + std::reference_wrapper default_symms; ExprPtr operator()(const parse::ast::Product &product) const { return ast_to_expr(product, position_cache.get(), - begin.get(), default_symmetry); + begin.get(), default_symms.get()); } ExprPtr operator()(const parse::ast::Sum &sum) const { return ast_to_expr(sum, position_cache.get(), begin.get(), - default_symmetry); + default_symms.get()); } ExprPtr operator()(const parse::ast::Tensor &tensor) const { auto [braIndices, ketIndices, auxiliaries] = make_indices(tensor.indices, position_cache.get(), begin.get()); - auto [offset, length] = - get_pos(tensor, position_cache.get(), begin.get()); + auto [perm_symm, braket_symm, particle_symm] = + to_symmetries(tensor.symmetry, default_symms.get(), + position_cache.get(), begin.get()); return ex(tensor.name, bra(std::move(braIndices)), ket(std::move(ketIndices)), aux(std::move(auxiliaries)), - to_symmetry(tensor.symmetry, offset + length - 1, - begin.get(), default_symmetry)); + perm_symm, braket_symm, particle_symm); } ExprPtr operator()(const parse::ast::Variable &variable) const { @@ -211,7 +286,7 @@ ExprPtr ast_to_expr(const parse::ast::NullaryValue &value, return boost::apply_visitor( Transformer{std::ref(position_cache), std::ref(begin), - std::ref(default_symmetry)}, + std::ref(default_symms)}, value); } @@ -223,7 +298,7 @@ bool holds_alternative(const boost::variant &v) noexcept { template ExprPtr ast_to_expr(const parse::ast::Product &product, const PositionCache &position_cache, const Iterator &begin, - Symmetry default_symmetry) { + const DefaultSymmetries &default_symms) { if (product.factors.empty()) { // This shouldn't happen assert(false); @@ -233,7 +308,7 @@ ExprPtr ast_to_expr(const parse::ast::Product &product, if (product.factors.size() == 1) { return ast_to_expr(product.factors.front(), position_cache, begin, - default_symmetry); + default_symms); } std::vector factors; @@ -247,7 +322,7 @@ ExprPtr ast_to_expr(const parse::ast::Product &product, position_cache, begin); } else { factors.push_back( - ast_to_expr(value, position_cache, begin, default_symmetry)); + ast_to_expr(value, position_cache, begin, default_symms)); } } @@ -267,13 +342,13 @@ ExprPtr ast_to_expr(const parse::ast::Product &product, template ExprPtr ast_to_expr(const parse::ast::Sum &sum, const PositionCache &position_cache, const Iterator &begin, - Symmetry default_symmetry) { + const DefaultSymmetries &default_symms) { if (sum.summands.empty()) { return {}; } if (sum.summands.size() == 1) { return ast_to_expr(sum.summands.front(), position_cache, begin, - default_symmetry); + default_symms); } std::vector summands; @@ -281,7 +356,7 @@ ExprPtr ast_to_expr(const parse::ast::Sum &sum, std::transform( sum.summands.begin(), sum.summands.end(), std::back_inserter(summands), [&](const parse::ast::Product &product) { - return ast_to_expr(product, position_cache, begin, default_symmetry); + return ast_to_expr(product, position_cache, begin, default_symms); }); return ex(std::move(summands)); diff --git a/SeQuant/core/parse/parse.cpp b/SeQuant/core/parse/parse.cpp index 7b6f5bed9..0a4c4e23e 100644 --- a/SeQuant/core/parse/parse.cpp +++ b/SeQuant/core/parse/parse.cpp @@ -47,6 +47,7 @@ struct ExprRule; struct IndexLabelRule; struct IndexRule; struct IndexGroupRule; +struct SymmetrySpecRule; // Types x3::rule number{"Number"}; @@ -63,6 +64,7 @@ x3::rule name{"Name"}; x3::rule index_label{"IndexLabel"}; x3::rule index{"Index"}; x3::rule index_groups{"IndexGroups"}; +x3::rule symmetry_spec{"SymmetrySpec"}; auto to_char_type = [](auto c) { return static_cast(c); @@ -105,8 +107,12 @@ auto index_groups_def = L"_{" > -(index % ',') > L"}^{" > -(index % ',') > L" | L"^{" > -(index % ',') > L"}_{" > -(index % ',') > L"}" >> x3::attr(noIndices) >> x3::attr(true) | '{' > -(index % ',') > -( ';' > -(index % ',')) > -(';' > -(index % ',')) > '}' >> x3::attr(false); +auto symmetry_spec_def= x3::lexeme[ + ':' >> x3::upper >> -('-' >> x3::upper) >> -('-' >> x3::upper) + ]; + auto tensor_def = x3::lexeme[ - name >> x3::skip[index_groups] >> -(':' >> x3::upper) + name >> x3::skip[index_groups] >> -(symmetry_spec) ]; auto nullary = number | tensor | variable; @@ -125,7 +131,7 @@ auto expr_def = -sum > x3::eoi; // clang-format on BOOST_SPIRIT_DEFINE(name, number, variable, index_label, index, index_groups, - tensor, product, sum, expr); + tensor, product, sum, expr, symmetry_spec); struct position_cache_tag; struct error_handler_tag; @@ -163,6 +169,7 @@ struct ExprRule : helpers::annotate_position, helpers::error_handler {}; struct IndexLabelRule : helpers::annotate_position, helpers::error_handler {}; struct IndexRule : helpers::annotate_position, helpers::error_handler {}; struct IndexGroupRule : helpers::annotate_position, helpers::error_handler {}; +struct SymmetrySpecRule : helpers::annotate_position, helpers::error_handler {}; } // namespace parse @@ -180,7 +187,8 @@ struct ErrorHandler { } }; -ExprPtr parse_expr(std::wstring_view input, Symmetry default_symmetry) { +ExprPtr parse_expr(std::wstring_view input, Symmetry perm_symm, + BraKetSymmetry braket_symm, ParticleSymmetry particle_symm) { using iterator_type = decltype(input)::iterator; x3::position_cache> positions(input.begin(), input.end()); @@ -217,8 +225,10 @@ ExprPtr parse_expr(std::wstring_view input, Symmetry default_symmetry) { throw; } - return parse::transform::ast_to_expr(ast, positions, input.begin(), - default_symmetry); + return parse::transform::ast_to_expr( + ast, positions, input.begin(), + parse::transform::DefaultSymmetries{perm_symm, braket_symm, + particle_symm}); } } // namespace sequant diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 574d9f1fb..12e884c53 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -170,11 +170,21 @@ TEST_CASE("parse_expr", "[parse]") { SECTION("Tensor with symmetry annotation") { auto expr1 = parse_expr(L"t{a1;i1}:A"); - auto expr2 = parse_expr(L"t{a1;i1}:S"); - auto expr3 = parse_expr(L"t{a1;i1}:N"); - REQUIRE(expr1->as().symmetry() == sequant::Symmetry::antisymm); - REQUIRE(expr2->as().symmetry() == sequant::Symmetry::symm); - REQUIRE(expr3->as().symmetry() == sequant::Symmetry::nonsymm); + auto expr2 = parse_expr(L"t{a1;i1}:S-C"); + auto expr3 = parse_expr(L"t{a1;i1}:N-S-N"); + + const Tensor& t1 = expr1->as(); + const Tensor& t2 = expr2->as(); + const Tensor& t3 = expr3->as(); + + REQUIRE(t1.symmetry() == Symmetry::antisymm); + + REQUIRE(t2.symmetry() == Symmetry::symm); + REQUIRE(t2.braket_symmetry() == BraKetSymmetry::conjugate); + + REQUIRE(t3.symmetry() == Symmetry::nonsymm); + REQUIRE(t3.braket_symmetry() == BraKetSymmetry::symm); + REQUIRE(t3.particle_symmetry() == ParticleSymmetry::nonsymm); } SECTION("Constant") { From 12a1df2a61539fab429caa58c425947f0fbf6d4d Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 17 Jan 2025 13:50:57 +0100 Subject: [PATCH 85/85] Avoid forbidden symmetry combination in tests --- tests/unit/test_parse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 12e884c53..d9a91140f 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -374,7 +374,7 @@ TEST_CASE("deparse", "[parse]") { L"t{a_1,a_2;a_3,a_4}:N-C-S", L"42", L"1/2", - L"-1/4 t{a_1,i_1;a_2,i_2}:S-N-N", + L"-1/4 t{a_1,i_1;a_2,i_2}:S-N-S", L"a + b - 4 specialVariable", L"variable + A{a_1;i_1}:N-N-S * B{i_1;a_1}:A-C-S", L"1/2 (a + b) * c",