From 7cbc7992e44018b589f2cedb3029ad9f18867d08 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 15:46:30 +0200 Subject: [PATCH 01/22] Make tensors non-symmetric by default As long as the calling site doesn't explicitly specify a symmetry or hermiticity, the tensor will be entirely non-symmetric. This conforms to what a typical user will expect. In case we do have a symmetry, we don't silently overwrite it, even if we know it doesn't make sense. Instead, throw a proper error informing the user that they can't do this. This way, they will always know what is happening. --- SeQuant/core/expressions/tensor.hpp | 45 ++++++----------------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index 07b3b7d5c2..0876736f70 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -268,24 +269,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { ket_(make_indices(ket_indices)), aux_(make_indices(aux_indices)), symmetry_(s), - // Derive bra<->ket exchange symmetry from the indices' fields with a - // default Hermitian abstract trait, unless the caller explicitly - // requested a particular BraKetSymmetry (e.g. Nonsymm for amplitudes). - // base_field(bra_, ket_) is well-defined here: declaration order of the - // private members guarantees bra_/ket_ are constructed before - // braket_symmetry_. - // If the caller didn't specify a BraKetSymmetry: when at least one - // of bra/ket is nonempty, derive from base_field(bra_, ket_) + - // Hermitian (matches the field-agnostic Hermiticity-taking ctor). - // When both bra and ket are empty, the bra↔ket exchange has no - // physical meaning — fall back to the literal Conjugate default - // (historical Context::braket_symmetry() value); deriving Symm there - // would break the spintrace bookkeeping for vacuum-aux tensors. - braket_symmetry_(bks_opt.value_or( - (bra_.empty() && ket_.empty()) - ? BraKetSymmetry::Conjugate - : to_braket_symmetry(Hermiticity::Hermitian, - sequant::base_field(bra_, ket_)))), + braket_symmetry_(bks_opt.value_or(BraKetSymmetry::Nonsymm)), hermiticity_(to_hermiticity(braket_symmetry_)), column_symmetry_(ps), bra_net_rank_(ranges::count_if( @@ -302,9 +286,9 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { // canonicalize_slots() may act on it. if ((label_ == reserved::antisymm_label() || label_ == reserved::symm_label()) && - braket_symmetry_ == BraKetSymmetry::Symm) { - braket_symmetry_ = BraKetSymmetry::Conjugate; - hermiticity_ = to_hermiticity(BraKetSymmetry::Conjugate); + braket_symmetry_ != BraKetSymmetry::Nonsymm) { + throw Exception( + "(Anti)symmetrization operators must not have braket symmetry"); } validate_indices(); validate_symmetries(); @@ -323,18 +307,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { ket_(std::move(ket_indices)), aux_(std::move(aux_indices)), symmetry_(s), - // If the caller didn't specify a BraKetSymmetry: when at least one - // of bra/ket is nonempty, derive from base_field(bra_, ket_) + - // Hermitian (matches the field-agnostic Hermiticity-taking ctor). - // When both bra and ket are empty, the bra↔ket exchange has no - // physical meaning — fall back to the literal Conjugate default - // (historical Context::braket_symmetry() value); deriving Symm there - // would break the spintrace bookkeeping for vacuum-aux tensors. - braket_symmetry_(bks_opt.value_or( - (bra_.empty() && ket_.empty()) - ? BraKetSymmetry::Conjugate - : to_braket_symmetry(Hermiticity::Hermitian, - sequant::base_field(bra_, ket_)))), + braket_symmetry_(bks_opt.value_or(BraKetSymmetry::Nonsymm)), hermiticity_(to_hermiticity(braket_symmetry_)), column_symmetry_(ps), bra_net_rank_(ranges::count_if( @@ -351,9 +324,9 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { // canonicalize_slots() may act on it. if ((label_ == reserved::antisymm_label() || label_ == reserved::symm_label()) && - braket_symmetry_ == BraKetSymmetry::Symm) { - braket_symmetry_ = BraKetSymmetry::Conjugate; - hermiticity_ = to_hermiticity(BraKetSymmetry::Conjugate); + braket_symmetry_ != BraKetSymmetry::Nonsymm) { + throw Exception( + "(Anti)symmetrization operators must not have braket symmetry"); } validate_indices(); validate_symmetries(); From 140e41d226af9c4ac2fd0f59161036953343f7b2 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 16:42:17 +0200 Subject: [PATCH 02/22] Default to no symmetries when deserializing --- SeQuant/core/io/serialization/v1/deserialize.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/SeQuant/core/io/serialization/v1/deserialize.cpp b/SeQuant/core/io/serialization/v1/deserialize.cpp index f8e8ff7d2f..d9c07a7beb 100644 --- a/SeQuant/core/io/serialization/v1/deserialize.cpp +++ b/SeQuant/core/io/serialization/v1/deserialize.cpp @@ -246,15 +246,8 @@ AST parse(const StartRule &start, std::wstring_view input, transform::DefaultSymmetries to_default_symms( const DeserializationOptions &options) { - // Deserializer's BraKet fallback for legacy/short serialized forms that - // omit the braket spec. Matches the historical Context::braket_symmetry() - // default of Conjugate, preserving snapshot-test stability for existing - // serialized fixtures. Callers who want a specific fallback (e.g. - // Hermiticity::Hermitian to make deserialized expectations match the - // programmatic ex(label, bra, ket) default, or Nonsymm for - // amplitudes in CSE tests) set options.def_braket_symm explicitly. - transform::DefaultSymmetries symms{ - Symmetry::Nonsymm, BraKetSymmetry::Conjugate, ColumnSymmetry::Symm}; + transform::DefaultSymmetries symms{Symmetry::Nonsymm, BraKetSymmetry::Nonsymm, + ColumnSymmetry::Nonsymm}; if (options.def_perm_symm.has_value()) { std::get<0>(symms) = options.def_perm_symm.value(); From 83eed70689fc4bad571f15bcab5c2baad874fefb Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 16:42:41 +0200 Subject: [PATCH 03/22] Special handling of symm op symmetries in deserialization --- .../core/io/serialization/v1/ast_conversions.hpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/SeQuant/core/io/serialization/v1/ast_conversions.hpp b/SeQuant/core/io/serialization/v1/ast_conversions.hpp index 22e761c470..11a6e1f7e8 100644 --- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp +++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp @@ -283,14 +283,6 @@ struct Transformer { auto [braIndices, ketIndices, auxiliaries] = make_indices(tensor.indices, position_cache.get(), begin.get()); - // braket_symm is a std::variant: a concrete - // BraKetSymmetry (a 'C'/'S'/'N' spec, or the default fallback) is forwarded - // verbatim, while a Hermiticity (an 'H'/'A' spec, or a Hermiticity-valued - // default) defers to the Tensor ctor, which resolves it against - // base_field(bra, ket) — matching the programmatic ex(label, bra, - // ket) default. The std::visit below dispatches on which alternative is - // held to the matching Tensor ctor overload (BraKetSymmetry- vs - // Hermiticity-taking). auto [perm_symm, braket_symm, column_symm] = to_symmetries(tensor.symmetry, default_symms.get(), position_cache.get(), begin.get()); @@ -331,6 +323,13 @@ struct Transformer { ann(std::move(braIndices)), vac); } + // Set required symmetries for symmetrization operators + if (tensor.name == reserved::antisymm_label()) { + perm_symm = Symmetry::Antisymm; + } else if (tensor.name == reserved::symm_label()) { + column_symm = ColumnSymmetry::Symm; + } + // Dispatch to correct Tensor constructor (taking either BraKetSymmetry or // Hermiticity) return std::visit( From 8dba518a9d6879e5c1cac580b3c2c43331dec1af Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 16:43:09 +0200 Subject: [PATCH 04/22] Infer column symmetry instead of erroring --- SeQuant/core/expressions/tensor.hpp | 58 ++++++++++++----------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index 0876736f70..bc74cb7dcd 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -276,22 +276,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { bra_, [](const Index &idx) { return static_cast(idx); })), ket_net_rank_(ranges::count_if( ket_, [](const Index &idx) { return static_cast(idx); })) { - // The (anti)symmetrizer is a permutation-bookkeeping operator whose - // bra<->ket orientation defines/extracts the external indices and must be - // preserved; it must never be bra<->ket-symmetric (Symm), or - // canonicalization would swap its bra and ket and corrupt external-index - // extraction. Over a real field a Hermitian operator becomes Symm, so - // demote Symm to Conjugate here (Conjugate is treated as no-swap by the - // canonicalizer, matching the complex-field behavior) before - // canonicalize_slots() may act on it. - if ((label_ == reserved::antisymm_label() || - label_ == reserved::symm_label()) && - braket_symmetry_ != BraKetSymmetry::Nonsymm) { - throw Exception( - "(Anti)symmetrization operators must not have braket symmetry"); - } validate_indices(); - validate_symmetries(); + check_symmetries(); canonicalize_slots(); } @@ -314,22 +300,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { bra_, [](const Index &idx) { return static_cast(idx); })), ket_net_rank_(ranges::count_if( ket_, [](const Index &idx) { return static_cast(idx); })) { - // The (anti)symmetrizer is a permutation-bookkeeping operator whose - // bra<->ket orientation defines/extracts the external indices and must be - // preserved; it must never be bra<->ket-symmetric (Symm), or - // canonicalization would swap its bra and ket and corrupt external-index - // extraction. Over a real field a Hermitian operator becomes Symm, so - // demote Symm to Conjugate here (Conjugate is treated as no-swap by the - // canonicalizer, matching the complex-field behavior) before - // canonicalize_slots() may act on it. - if ((label_ == reserved::antisymm_label() || - label_ == reserved::symm_label()) && - braket_symmetry_ != BraKetSymmetry::Nonsymm) { - throw Exception( - "(Anti)symmetrization operators must not have braket symmetry"); - } validate_indices(); - validate_symmetries(); + check_symmetries(); canonicalize_slots(); } @@ -771,11 +743,27 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { std::size_t bra_net_rank_; std::size_t ket_net_rank_; - void validate_symmetries() { - // (anti)symmetric bra or ket makes sense only for particle-symmetric - // tensors - if (symmetry_ == Symmetry::Symm || symmetry_ == Symmetry::Antisymm) - SEQUANT_ASSERT(column_symmetry_ == ColumnSymmetry::Symm); + void check_symmetries() { + // The (anti)symmetrizer is a permutation-bookkeeping operator whose + // bra<->ket orientation defines/extracts the external indices and must be + // preserved; it must never be bra<->ket-symmetric (Symm), or + // canonicalization would swap its bra and ket and corrupt external-index + // extraction. Over a real field a Hermitian operator becomes Symm, so + // demote Symm to Conjugate here (Conjugate is treated as no-swap by the + // canonicalizer, matching the complex-field behavior) before + // canonicalize_slots() may act on it. + if ((label_ == reserved::antisymm_label() || + label_ == reserved::symm_label()) && + braket_symmetry_ != BraKetSymmetry::Nonsymm) { + throw Exception( + "(Anti)symmetrization operators must not have braket symmetry"); + } + + if (symmetry_ == Symmetry::Symm || symmetry_ == Symmetry::Antisymm) { + // (Anti)symmetry in bra and ket indices automatically implies column + // symmetry + column_symmetry_ = ColumnSymmetry::Symm; + } } hash_type memoizing_hash() const override { From 18c15411c6bc1d9594d51b6f6bb6a3b293c0ee79 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 16:43:33 +0200 Subject: [PATCH 05/22] Don't mark symm ops as hermitian --- SeQuant/domain/mbpt/context.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SeQuant/domain/mbpt/context.cpp b/SeQuant/domain/mbpt/context.cpp index 0ef837bdb8..25655ef28b 100644 --- a/SeQuant/domain/mbpt/context.cpp +++ b/SeQuant/domain/mbpt/context.cpp @@ -160,8 +160,11 @@ OpClass to_op_class(const std::wstring& op) { } Hermiticity op_hermiticity(const std::wstring& op) { - // reserved labels are OpClass::gen, hence Hermitian by default - if (ranges::contains(reserved::labels(), op)) { + if (op == reserved::antisymm_label() || op == reserved::symm_label()) { + // Symmetrization operators are non-hermitian + return Hermiticity::NonHermitian; + } else if (ranges::contains(reserved::labels(), op)) { + // reserved labels are OpClass::gen, hence Hermitian by default return default_hermiticity(OpClass::gen); } else { return get_default_mbpt_context().op_registry()->hermiticity(op); From 83e0139267e176eef425e9c60da226a879ec546b Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 16:43:59 +0200 Subject: [PATCH 06/22] Adapt parse tests to new symm handling --- tests/unit/test_parse.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 78d1c2d11a..f4256ad14f 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -533,9 +533,8 @@ TEST_CASE("serialization", "[serialization]") { REQUIRE(result.ket()[0].full_label() == L"e_1"); REQUIRE(result.ket()[1].full_label() == L"e_2"); REQUIRE(result.symmetry() == Symmetry::Antisymm); - // serialized form `R{...}:A` omits braket_symmetry; deserializer's - // legacy fallback is Conjugate (see v1/deserialize.cpp to_default_symms) - REQUIRE(result.braket_symmetry() == BraKetSymmetry::Conjugate); + REQUIRE(result.braket_symmetry() == BraKetSymmetry::Nonsymm); + // This column symmetry is implicit with Symmetry::Antisymm REQUIRE(result.column_symmetry() == ColumnSymmetry::Symm); REQUIRE(result.expression().is()); From 109aae35dbd138c42a629843f0202588d140c762 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 16:44:17 +0200 Subject: [PATCH 07/22] Fix invalid symm op symmetry spec --- tests/unit/test_spin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 24091c6a53..3660245d1e 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -1180,7 +1180,7 @@ SECTION("Closed-shell spintrace CCSDT terms") { REQUIRE_THAT( result, EquivalentTo( - L"3 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * " + L"3 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * " "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_3,i_1,i_2}:N-C-S")); } @@ -1195,7 +1195,7 @@ SECTION("Closed-shell spintrace CCSDT terms") { REQUIRE_THAT( result, EquivalentTo( - L"-6/5 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * " + L"-6/5 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * " "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_1,i_2,i_3}:N-C-S + " "3 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * g{a_1,a_2;a_4,a_5}:N-C-S *" " t{a_3,a_4,a_5;i_3,i_1,i_2}:N-C-S -" From 4586be968e46bd793d6da001e83c4a5aae6371e8 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 16:44:52 +0200 Subject: [PATCH 08/22] Parse biorth exprs as column symmetric --- tests/unit/test_biorthogonalization.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_biorthogonalization.cpp b/tests/unit/test_biorthogonalization.cpp index e62ef56ac1..8d5320d5a9 100644 --- a/tests/unit/test_biorthogonalization.cpp +++ b/tests/unit/test_biorthogonalization.cpp @@ -39,7 +39,8 @@ TEST_CASE("biorthogonalization", "[Biorthogonalization]") { for (std::size_t i = 0; i < inputs.size(); ++i) { CAPTURE(i); - ExprPtr input_expr = deserialize(inputs.at(i)); + ExprPtr input_expr = + deserialize(inputs.at(i), {.def_col_symm = ColumnSymmetry::Symm}); auto externals = external_indices(input_expr); @@ -80,7 +81,8 @@ TEST_CASE("biorthogonalization", "[Biorthogonalization]") { container::svector expressions; container::svector expected; for (std::size_t k = 0; k < inputs.at(i).size(); ++k) { - ResultExpr parsed = deserialize(inputs.at(i).at(k)); + ResultExpr parsed = deserialize( + inputs.at(i).at(k), {.def_col_symm = ColumnSymmetry::Symm}); expressions.push_back(parsed); expected.push_back( @@ -108,7 +110,8 @@ TEST_CASE("biorthogonalization", "[Biorthogonalization]") { container::svector expressions; for (const std::wstring &str : current_inputs) { - expressions.push_back(deserialize(str)); + expressions.push_back(deserialize( + str, {.def_col_symm = ColumnSymmetry::Symm})); } REQUIRE_THROWS_WITH( From 7e867111e257d7b34c08eaca4519ba05aac822df Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Mon, 15 Jun 2026 16:45:26 +0200 Subject: [PATCH 09/22] Fix invalid symm op symmetry spec --- tests/unit/test_tensor_network.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index 21aaa3c157..f4be9861eb 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -1831,12 +1831,11 @@ TEST_CASE("tensor_network_v3", "[elements][valgrind_skip]") { } SECTION("special") { - auto factors = - deserialize( - L"Ŝ{i_1;a_1}:N-C-S g{i_2,a_1;a_2,i_1}:N-C-S " - L"t{a_2;i_2}:N-C-S") - ->as() - .factors(); + auto factors = deserialize( + L"Ŝ{i_1;a_1} g{i_2,a_1;a_2,i_1}:N-C-S " + L"t{a_2;i_2}:N-C-S") + ->as() + .factors(); TN tn(factors); From 4e20c36c5a992457b3993e2bd37ae672e4c06d09 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Mon, 15 Jun 2026 20:40:28 -0700 Subject: [PATCH 10/22] core: source default tensor symmetries from the Context Builds on #553 ("non-symmetric by default") by making the default tensor symmetries configurable through sequant::Context rather than hardcoding them. - Context gains default `symmetry`, `hermiticity`, and `column_symmetry` (Defaults + Options + accessors + set()). Library defaults are the safest (Nonsymm / NonHermitian / Nonsymm). BraKet symmetry is never a Context knob: it is a derived property, computed per-tensor from the default Hermiticity and base_field, consistent with the removal of Context::braket_symmetry. - Tensor ctors resolve unspecified perm/column/hermiticity against the active Context; the Context is consulted only when something is unspecified, so fully-explicit construction (the hot path) stays lock-free. - The deserializer's default symmetries are sourced from the active Context. - Context::operator== now also compares the symmetry defaults, so scoped/default context switches that differ only in these actually take effect. - mbpt applications (unit-test harness + srcc/stcc integration) opt into the particle-symmetric convention via column = Symm. - Update unit/integration expectations to the resulting (physically correct) symmetries; add tests for the Context-driven defaults and for rejecting braket symmetry on (anti)symmetrization operators. --- SeQuant/core/context.cpp | 29 ++- SeQuant/core/context.hpp | 38 ++++ SeQuant/core/expressions/tensor.hpp | 189 ++++++++++++------ .../io/serialization/v1/ast_conversions.hpp | 8 +- .../core/io/serialization/v1/deserialize.cpp | 10 +- tests/integration/srcc.cpp | 8 +- tests/integration/stcc.cpp | 4 +- tests/unit/data/sf_r2_direct_real_inc.hpp | 2 +- tests/unit/test_biorthogonalization.cpp | 21 +- tests/unit/test_canonicalize.cpp | 8 +- tests/unit/test_main.cpp | 7 +- tests/unit/test_mbpt.cpp | 14 +- tests/unit/test_parse.cpp | 10 + tests/unit/test_spin.cpp | 156 +++++++-------- tests/unit/test_tensor.cpp | 38 +++- tests/unit/test_tensor_network.cpp | 15 +- tests/unit/test_wick.cpp | 28 +-- 17 files changed, 389 insertions(+), 196 deletions(-) diff --git a/SeQuant/core/context.cpp b/SeQuant/core/context.cpp index c7f63c69cf..cba9b80121 100644 --- a/SeQuant/core/context.cpp +++ b/SeQuant/core/context.cpp @@ -31,6 +31,9 @@ bool operator==(const Context& ctx1, const Context& ctx2) { ctx1.canonicalization_options() == ctx2.canonicalization_options() && ctx1.braket_typesetting() == ctx2.braket_typesetting() && ctx1.braket_slot_typesetting() == ctx2.braket_slot_typesetting() && + ctx1.symmetry() == ctx2.symmetry() && + ctx1.hermiticity() == ctx2.hermiticity() && + ctx1.column_symmetry() == ctx2.column_symmetry() && *ctx1.index_space_registry() == *ctx2.index_space_registry(); } @@ -132,7 +135,10 @@ Context::Context(Options options) first_dummy_index_ordinal_(options.first_dummy_index_ordinal), canonicalization_options_(options.canonicalization_options), braket_typesetting_(options.braket_typesetting), - braket_slot_typesetting_(options.braket_slot_typesetting) {} + braket_slot_typesetting_(options.braket_slot_typesetting), + symmetry_(options.symmetry), + hermiticity_(options.hermiticity), + column_symmetry_(options.column_symmetry) {} Context Context::clone() const { Context ctx(*this); @@ -176,6 +182,12 @@ BraKetSlotTypesetting Context::braket_slot_typesetting() const { return braket_slot_typesetting_; } +Symmetry Context::symmetry() const { return symmetry_; } + +Hermiticity Context::hermiticity() const { return hermiticity_; } + +ColumnSymmetry Context::column_symmetry() const { return column_symmetry_; } + Context& Context::set(Vacuum vacuum) { vacuum_ = vacuum; return *this; @@ -229,6 +241,21 @@ Context& Context::set(BraKetSlotTypesetting bkst) { return *this; } +Context& Context::set(Symmetry symmetry) { + symmetry_ = symmetry; + return *this; +} + +Context& Context::set(Hermiticity hermiticity) { + hermiticity_ = hermiticity; + return *this; +} + +Context& Context::set(ColumnSymmetry column_symmetry) { + column_symmetry_ = column_symmetry; + return *this; +} + IndexSpace get_particle_space(const IndexSpace::QuantumNumbers& qn) { return get_default_context().index_space_registry()->particle_space(qn); } diff --git a/SeQuant/core/context.hpp b/SeQuant/core/context.hpp index 4c3ee9641d..a9adb210dd 100644 --- a/SeQuant/core/context.hpp +++ b/SeQuant/core/context.hpp @@ -58,6 +58,15 @@ class Context { constexpr static auto braket_typesetting = BraKetTypesetting::ContraSub; constexpr static auto braket_slot_typesetting = BraKetSlotTypesetting::TensorPackage; + // default symmetries for newly-constructed tensors are the *safest* + // (most general) possible; applications can fine-tune them via Context for + // ergonomics (e.g. mbpt assumes particle-symmetric tensors). Note that + // there is no braket-symmetry default: braket symmetry is a *derived* + // property of a tensor (from its #Hermiticity and #base_field), so + // #hermiticity is the knob instead (cf. removal of Context::braket_symmetry). + constexpr static auto symmetry = Symmetry::Nonsymm; + constexpr static auto hermiticity = Hermiticity::NonHermitian; + constexpr static auto column_symmetry = ColumnSymmetry::Nonsymm; }; /// helper for the named-parameter constructor of Context @@ -86,6 +95,14 @@ class Context { /// the BraKetSlotTypesetting object BraKetSlotTypesetting braket_slot_typesetting = Defaults::braket_slot_typesetting; + /// the default bra/ket permutational Symmetry for new tensors + Symmetry symmetry = Defaults::symmetry; + /// the default Hermiticity for new tensors; the braket symmetry of a new + /// tensor is *derived* from this and the tensor's #base_field + Hermiticity hermiticity = Defaults::hermiticity; + /// the default ColumnSymmetry (particle-permutation symmetry) for new + /// tensors + ColumnSymmetry column_symmetry = Defaults::column_symmetry; }; static Options make_default_options() { return {}; } @@ -155,6 +172,14 @@ class Context { /// \return BraKetSlotTypesetting of this context; see BraKetSlotTypesetting /// for the meaning of the possible values BraKetSlotTypesetting braket_slot_typesetting() const; + /// \return the default bra/ket permutational Symmetry for new tensors + Symmetry symmetry() const; + /// \return the default Hermiticity for new tensors; the braket symmetry of a + /// new tensor is *derived* from this and the tensor's #base_field + Hermiticity hermiticity() const; + /// \return the default ColumnSymmetry (particle-permutation symmetry) for new + /// tensors + ColumnSymmetry column_symmetry() const; /// Sets the Vacuum for this context, convenient for chaining /// \param vacuum Vacuum @@ -195,6 +220,16 @@ class Context { /// \param braket_slot_typeset BraKetSlotTypesetting /// \return ref to `*this`, for chaining Context& set(BraKetSlotTypesetting braket_slot_typeset); + /// Sets the default bra/ket permutational Symmetry for new tensors + /// \return ref to `*this`, for chaining + Context& set(Symmetry symmetry); + /// Sets the default Hermiticity for new tensors (the braket symmetry of a new + /// tensor is derived from this and the tensor's base field) + /// \return ref to `*this`, for chaining + Context& set(Hermiticity hermiticity); + /// Sets the default ColumnSymmetry for new tensors + /// \return ref to `*this`, for chaining + Context& set(ColumnSymmetry column_symmetry); private: std::shared_ptr idx_space_reg_ = nullptr; @@ -207,6 +242,9 @@ class Context { BraKetTypesetting braket_typesetting_ = Defaults::braket_typesetting; BraKetSlotTypesetting braket_slot_typesetting_ = Defaults::braket_slot_typesetting; + Symmetry symmetry_ = Defaults::symmetry; + Hermiticity hermiticity_ = Defaults::hermiticity; + ColumnSymmetry column_symmetry_ = Defaults::column_symmetry; }; /// Context object equality comparison diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index bc74cb7dcd..dc140b5a2b 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -255,23 +255,73 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { } } + /// the four symmetry attributes of a Tensor, fully resolved (no defaulting + /// left to do) + struct resolved_symmetries { + Symmetry symmetry; + BraKetSymmetry braket_symmetry; + Hermiticity hermiticity; + ColumnSymmetry column_symmetry; + }; + + /// Resolves the (possibly unspecified) symmetry attributes of a tensor. + /// - #BraKetSymmetry is a *derived* property: if not given explicitly it is + /// computed from the (explicit or default) #Hermiticity and the tensor's + /// @p base_fld via to_braket_symmetry(). + /// - #Symmetry, #Hermiticity and #ColumnSymmetry fall back to the active + /// default sequant::Context when not specified. + /// @note The default Context is consulted *only* when some attribute is left + /// unspecified, so fully-explicit construction (the hot path, e.g. + /// canonicalization copying a source tensor's attributes) never pays + /// the get_default_context() synchronization cost. + static resolved_symmetries resolve_symmetries( + std::optional s, std::optional bks_opt, + std::optional ps, std::optional herm_opt, + Field base_fld) { + Symmetry s_resolved; + Hermiticity h_resolved; + ColumnSymmetry ps_resolved; + if (!s.has_value() || !ps.has_value() || + (!bks_opt.has_value() && !herm_opt.has_value())) { + const Context &ctx = get_default_context(); + s_resolved = s.value_or(ctx.symmetry()); + ps_resolved = ps.value_or(ctx.column_symmetry()); + h_resolved = herm_opt.value_or(ctx.hermiticity()); + } else { + s_resolved = *s; + ps_resolved = *ps; + // not used below (bks_opt is set), value is irrelevant + h_resolved = herm_opt.value_or(Hermiticity::NonHermitian); + } + const BraKetSymmetry bks_resolved = + bks_opt.has_value() ? *bks_opt + : to_braket_symmetry(h_resolved, base_fld); + // preserve the exact #Hermiticity trait (incl. AntiHermitian, which the + // BraKetSymmetry round-trip cannot represent) when it was given; otherwise + // back-fill it from the explicit-or-derived braket symmetry + const Hermiticity hermiticity_resolved = + herm_opt.has_value() + ? *herm_opt + : (bks_opt.has_value() ? to_hermiticity(*bks_opt) : h_resolved); + return {s_resolved, bks_resolved, hermiticity_resolved, ps_resolved}; + } + + // fully-resolved terminal ctor (range form) template Tensor(S &&label, const bra &bra_indices, const ket &ket_indices, const aux &aux_indices, reserved_tag, - Symmetry s = Symmetry::Nonsymm, - std::optional bks_opt = std::nullopt, - ColumnSymmetry ps = ColumnSymmetry::Symm) + resolved_symmetries rsym) : label_(toUtf16(std::forward(label))), bra_(make_indices(bra_indices)), ket_(make_indices(ket_indices)), aux_(make_indices(aux_indices)), - symmetry_(s), - braket_symmetry_(bks_opt.value_or(BraKetSymmetry::Nonsymm)), - hermiticity_(to_hermiticity(braket_symmetry_)), - column_symmetry_(ps), + symmetry_(rsym.symmetry), + braket_symmetry_(rsym.braket_symmetry), + hermiticity_(rsym.hermiticity), + column_symmetry_(rsym.column_symmetry), bra_net_rank_(ranges::count_if( bra_, [](const Index &idx) { return static_cast(idx); })), ket_net_rank_(ranges::count_if( @@ -281,21 +331,20 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { canonicalize_slots(); } + // fully-resolved terminal ctor (move form) template Tensor(S &&label, bra &&bra_indices, ket &&ket_indices, aux &&aux_indices, reserved_tag, - Symmetry s = Symmetry::Nonsymm, - std::optional bks_opt = std::nullopt, - ColumnSymmetry ps = ColumnSymmetry::Symm) + resolved_symmetries rsym) : label_(toUtf16(std::forward(label))), bra_(std::move(bra_indices)), ket_(std::move(ket_indices)), aux_(std::move(aux_indices)), - symmetry_(s), - braket_symmetry_(bks_opt.value_or(BraKetSymmetry::Nonsymm)), - hermiticity_(to_hermiticity(braket_symmetry_)), - column_symmetry_(ps), + symmetry_(rsym.symmetry), + braket_symmetry_(rsym.braket_symmetry), + hermiticity_(rsym.hermiticity), + column_symmetry_(rsym.column_symmetry), bra_net_rank_(ranges::count_if( bra_, [](const Index &idx) { return static_cast(idx); })), ket_net_rank_(ranges::count_if( @@ -305,6 +354,44 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { canonicalize_slots(); } + // defaulting reserved-tag ctor (range form): resolves unspecified symmetries + // against the default Context, then delegates to the resolved terminal + template + Tensor(S &&label, const bra &bra_indices, + const ket &ket_indices, + const aux &aux_indices, reserved_tag, + std::optional s = std::nullopt, + std::optional bks_opt = std::nullopt, + std::optional ps = std::nullopt, + std::optional herm_opt = std::nullopt) + : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, + reserved_tag{}, + resolve_symmetries( + s, bks_opt, ps, herm_opt, + sequant::base_field(make_indices(bra_indices), + make_indices(ket_indices)))) {} + + // defaulting reserved-tag ctor (move form) + template + Tensor(S &&label, bra &&bra_indices, + ket &&ket_indices, + aux &&aux_indices, reserved_tag, + std::optional s = std::nullopt, + std::optional bks_opt = std::nullopt, + std::optional ps = std::nullopt, + std::optional herm_opt = std::nullopt) + // base_field(bra_indices, ket_indices) is read during argument + // evaluation, before the delegated ctor moves the indices out -- safe + // regardless of argument evaluation order + : Tensor( + std::forward(label), std::move(bra_indices), + std::move(ket_indices), std::move(aux_indices), reserved_tag{}, + resolve_symmetries(s, bks_opt, ps, herm_opt, + sequant::base_field(bra_indices, ket_indices))) { + } + public: /// constructs an uninitialized Tensor /// @sa Tensor::operator bool() @@ -334,9 +421,10 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { template Tensor(S &&label, const bra &bra_indices, - const ket &ket_indices, Symmetry s = Symmetry::Nonsymm, + const ket &ket_indices, + std::optional s = std::nullopt, std::optional bks_opt = std::nullopt, - ColumnSymmetry ps = ColumnSymmetry::Symm) + std::optional ps = std::nullopt) : Tensor(std::forward(label), bra_indices, ket_indices, sequant::aux{}, reserved_tag{}, s, bks_opt, ps) { assert_nonreserved_label(label_); @@ -357,9 +445,10 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { range_of_castables_to_index IndexRange3> Tensor(S &&label, const bra &bra_indices, const ket &ket_indices, - const aux &aux_indices, Symmetry s = Symmetry::Nonsymm, + const aux &aux_indices, + std::optional s = std::nullopt, std::optional bks_opt = std::nullopt, - ColumnSymmetry ps = ColumnSymmetry::Symm) + std::optional ps = std::nullopt) : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, reserved_tag{}, s, bks_opt, ps) { assert_nonreserved_label(label_); @@ -376,9 +465,9 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { template Tensor(S &&label, bra &&bra_indices, ket &&ket_indices, - Symmetry s = Symmetry::Nonsymm, + std::optional s = std::nullopt, std::optional bks_opt = std::nullopt, - ColumnSymmetry ps = ColumnSymmetry::Symm) + std::optional ps = std::nullopt) : Tensor(std::forward(label), std::move(bra_indices), std::move(ket_indices), sequant::aux{}, reserved_tag{}, s, bks_opt, ps) { @@ -399,9 +488,9 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { Tensor(S &&label, bra &&bra_indices, ket &&ket_indices, aux &&aux_indices, - Symmetry s = Symmetry::Nonsymm, + std::optional s = std::nullopt, std::optional bks_opt = std::nullopt, - ColumnSymmetry ps = ColumnSymmetry::Symm) + std::optional ps = std::nullopt) : Tensor(std::forward(label), std::move(bra_indices), std::move(ket_indices), std::move(aux_indices), reserved_tag{}, s, bks_opt, ps) { @@ -427,25 +516,15 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { range_of_castables_to_index IndexRange2> Tensor(S &&label, const bra &bra_indices, const ket &ket_indices, Symmetry s, Hermiticity h, - ColumnSymmetry ps = ColumnSymmetry::Symm) - // The base_field must be resolved into a BraKetSymmetry *here*, in the - // delegation, because the delegated-to ctor's body runs - // canonicalize_slots() (which keys off braket_symmetry_) -- it cannot be - // fixed up afterwards. Hence make_indices is unavoidably evaluated here - // too (the delegated ctor materializes bra_/ket_ from the same ranges - // again); the duplication is the cost of safe delegation, not an - // oversight. - : Tensor(std::forward(label), bra_indices, ket_indices, s, - to_braket_symmetry( - h, sequant::base_field(make_indices(bra_indices), - make_indices(ket_indices))), - ps) { - // Overwrite after delegation to preserve the exact trait (incl. - // AntiHermitian, which the BraKetSymmetry round-trip cannot represent). - // Stays consistent with the reserved-(anti)symmetrizer Symm->Conjugate - // demotion in the delegated ctor: that demotion only adjusts - // braket_symmetry_ (leaving hermiticity Hermitian), which h also implies. - hermiticity_ = h; + std::optional ps = std::nullopt) + // pass the Hermiticity through to the reserved-tag ctor, which derives + // braket_symmetry_ from it and base_field() before canonicalize_slots() + // runs (the latter keys off braket_symmetry_), and preserves the exact + // trait (incl. AntiHermitian) in hermiticity_. + : Tensor(std::forward(label), bra_indices, ket_indices, sequant::aux{}, + reserved_tag{}, std::optional(s), std::nullopt, ps, + std::optional(h)) { + assert_nonreserved_label(label_); } /// @param label the tensor label @@ -461,25 +540,15 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { Tensor(S &&label, const bra &bra_indices, const ket &ket_indices, const aux &aux_indices, Symmetry s, Hermiticity h, - ColumnSymmetry ps = ColumnSymmetry::Symm) - // The base_field must be resolved into a BraKetSymmetry *here*, in the - // delegation, because the delegated-to ctor's body runs - // canonicalize_slots() (which keys off braket_symmetry_) -- it cannot be - // fixed up afterwards. Hence make_indices is unavoidably evaluated here - // too (the delegated ctor materializes bra_/ket_ from the same ranges - // again); the duplication is the cost of safe delegation, not an - // oversight. - : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, s, - to_braket_symmetry( - h, sequant::base_field(make_indices(bra_indices), - make_indices(ket_indices))), - ps) { - // Overwrite after delegation to preserve the exact trait (incl. - // AntiHermitian, which the BraKetSymmetry round-trip cannot represent). - // Stays consistent with the reserved-(anti)symmetrizer Symm->Conjugate - // demotion in the delegated ctor: that demotion only adjusts - // braket_symmetry_ (leaving hermiticity Hermitian), which h also implies. - hermiticity_ = h; + std::optional ps = std::nullopt) + // pass the Hermiticity through to the reserved-tag ctor, which derives + // braket_symmetry_ from it and base_field() before canonicalize_slots() + // runs (the latter keys off braket_symmetry_), and preserves the exact + // trait (incl. AntiHermitian) in hermiticity_. + : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, + reserved_tag{}, std::optional(s), std::nullopt, ps, + std::optional(h)) { + assert_nonreserved_label(label_); } /// @} diff --git a/SeQuant/core/io/serialization/v1/ast_conversions.hpp b/SeQuant/core/io/serialization/v1/ast_conversions.hpp index 11a6e1f7e8..97ef675b0e 100644 --- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp +++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp @@ -323,7 +323,13 @@ struct Transformer { ann(std::move(braIndices)), vac); } - // Set required symmetries for symmetrization operators + // Force the defining symmetry of each reserved (anti)symmetrization + // operator (overriding the parsed/default one). The two differ by design: + // the antisymmetrizer  antisymmetrizes within bra and within ket (a + // permutational #Symmetry), whereas the symmetrizer Ŝ symmetrizes the + // {bra,ket} particle columns (a #ColumnSymmetry). The remaining symmetry + // attributes are resolved/validated by the Tensor ctor (which also requires + // these operators to be braket-Nonsymm). if (tensor.name == reserved::antisymm_label()) { perm_symm = Symmetry::Antisymm; } else if (tensor.name == reserved::symm_label()) { diff --git a/SeQuant/core/io/serialization/v1/deserialize.cpp b/SeQuant/core/io/serialization/v1/deserialize.cpp index d9c07a7beb..5bcea928ed 100644 --- a/SeQuant/core/io/serialization/v1/deserialize.cpp +++ b/SeQuant/core/io/serialization/v1/deserialize.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -246,8 +247,13 @@ AST parse(const StartRule &start, std::wstring_view input, transform::DefaultSymmetries to_default_symms( const DeserializationOptions &options) { - transform::DefaultSymmetries symms{Symmetry::Nonsymm, BraKetSymmetry::Nonsymm, - ColumnSymmetry::Nonsymm}; + // unspecified symmetries default to the active Context's tensor-symmetry + // defaults; braket symmetry is seeded as the Context's Hermiticity, which is + // resolved against each tensor's base_field downstream (matching the + // programmatic Tensor ctor). Per-call DeserializationOptions still override. + const Context &ctx = get_default_context(); + transform::DefaultSymmetries symms{ctx.symmetry(), ctx.hermiticity(), + ctx.column_symmetry()}; if (options.def_perm_symm.has_value()) { std::get<0>(symms) = options.def_perm_symm.value(); diff --git a/tests/integration/srcc.cpp b/tests/integration/srcc.cpp index e1e70e83df..a0f17f0607 100644 --- a/tests/integration/srcc.cpp +++ b/tests/integration/srcc.cpp @@ -103,7 +103,9 @@ class compute_cceqvec { : AssertStrictBraKetSymmetry::Yes; auto context_resetter = sequant::set_scoped_default_context( sequant::Context({.index_space_registry_shared_ptr = so_reg, - .vacuum = Vacuum::SingleProduct}) + .vacuum = Vacuum::SingleProduct, + // mbpt works with particle-symmetric tensors + .column_symmetry = ColumnSymmetry::Symm}) .set(so_strict)); std::vector eqvec_so; switch (type) { @@ -284,7 +286,9 @@ int main(int argc, char* argv[]) { sequant::set_default_context( sequant::Context({.index_space_registry_shared_ptr = sr_reg, .vacuum = Vacuum::SingleProduct, - .spbasis = spbasis}) + .spbasis = spbasis, + // mbpt works with particle-symmetric tensors + .column_symmetry = ColumnSymmetry::Symm}) .set(strict)); TensorCanonicalizer::register_instance( std::make_shared()); diff --git a/tests/integration/stcc.cpp b/tests/integration/stcc.cpp index aa6dade25b..a477a1bf64 100644 --- a/tests/integration/stcc.cpp +++ b/tests/integration/stcc.cpp @@ -38,7 +38,9 @@ int main(int argc, char* argv[]) { .vacuum = Vacuum::SingleProduct, .canonicalization_options = CanonicalizeOptions::default_options().copy_and_set( - CanonicalizationMethod::Complete)}); + CanonicalizationMethod::Complete), + // mbpt works with particle-symmetric tensors + .column_symmetry = ColumnSymmetry::Symm}); TensorCanonicalizer::register_instance( std::make_shared()); mbpt::set_default_mbpt_context( diff --git a/tests/unit/data/sf_r2_direct_real_inc.hpp b/tests/unit/data/sf_r2_direct_real_inc.hpp index 1241421ac9..c36866e12a 100644 --- a/tests/unit/data/sf_r2_direct_real_inc.hpp +++ b/tests/unit/data/sf_r2_direct_real_inc.hpp @@ -13,7 +13,7 @@ namespace sequant::tests::data { inline const std::wstring& sf_r2_direct_real() { static const std::wstring s = LR"FIXTURE( -8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_3;i_3}:N-N-S * t{a_1,a_2;i_1,i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_3,i_4}:N-N-S * t{a_3,a_4;i_1,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3,a_4;i_2,i_1}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_4,i_2}:N-N-S * t{a_3,a_4;i_3,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,i_1}:N-S-S * t{a_2,a_3;i_3,i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_2,i_4}:N-N-S * t{a_2,a_4;i_3,i_1}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_2;i_3}:N-N-S * t{a_3;i_1}:N-N-S * t{a_4;i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;a_1,a_2}:N-S-S * t{a_3;i_1}:N-N-S * t{a_4;i_2}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;i_1,a_3}:N-S-S * t{a_2,a_3;i_2,i_3}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * f{i_2;i_3}:N-S-S * t{a_1,a_2;i_1,i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_1,a_3;i_3,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,i_1}:N-S-S * t{a_2,a_3;i_2,i_3}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S * t{a_2,a_4;i_2,i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S * t{a_3;i_1}:N-N-S * t{a_4;i_2}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_2}:N-N-S * t{a_4;i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_3;i_1}:N-N-S * t{a_2,a_4;i_3,i_2}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_4,i_3}:N-N-S * t{a_2,a_4;i_2,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * f{i_1;i_3}:N-S-S * t{a_1,a_2;i_2,i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_4;i_2}:N-N-S * t{a_2,a_3;i_3,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_4;i_2}:N-N-S * t{a_1,a_3;i_3,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,i_2}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_2}:N-N-S * t{a_4;i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_2,i_3}:N-N-S * t{a_3,a_4;i_4,i_1}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,i_2;i_3,i_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_2;i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_3,i_1}:N-N-S * t{a_2,a_3;i_2,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_2;i_3,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * f{a_2;a_3}:N-S-S * t{a_1,a_3;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;a_1,a_2}:N-S-S * t{a_3;i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * f{a_1;a_3}:N-S-S * t{a_2,a_3;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2,a_3;i_1,i_4}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;a_2,i_3}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_3,i_2}:N-N-S * t{a_3,a_4;i_4,i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;i_2,a_3}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S * t{a_3;i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_2,a_3;i_3,i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_3;i_3}:N-N-S * t{a_1,a_2;i_4,i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_2,i_4}:N-N-S * t{a_2,a_3;i_3,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_3;i_3}:N-N-S * t{a_1,a_4;i_2,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * f{a_3;i_3}:N-S-S * t{a_2;i_3}:N-N-S * t{a_1,a_3;i_2,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_4,i_2}:N-N-S * t{a_2,a_4;i_3,i_1}:N-N-S + 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_1,i_3}:N-N-S * t{a_2,a_4;i_2,i_4}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_2;i_3}:N-N-S * t{a_1,a_3;i_1,i_4}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_3,i_1}:N-N-S * t{a_2,a_4;i_2,i_4}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;i_2,a_3}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_3;i_2}:N-N-S * t{a_2,a_4;i_3,i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2,a_3;i_4,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,i_2;a_1,a_2}:N-S-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * f{a_3;i_3}:N-S-S * t{a_2;i_3}:N-N-S * t{a_1,a_3;i_1,i_2}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,i_1;i_3,a_1}:N-S-S * t{a_2;i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;i_2,a_3}:N-S-S * t{a_2,a_3;i_1,i_3}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;i_1,i_2}:N-S-S * t{a_1,a_2;i_4,i_3}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_3;i_3}:N-N-S * t{a_4;i_2}:N-N-S * t{a_1,a_2;i_1,i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;a_1,a_2}:N-S-S * t{a_3,a_4;i_1,i_2}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_4,i_2}:N-N-S * t{a_2,a_3;i_3,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,i_2}:N-S-S * t{a_2,a_3;i_3,i_1}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_2;i_3}:N-N-S * t{a_3,a_4;i_1,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_3;i_1}:N-N-S * t{a_4;i_2}:N-N-S * t{a_1,a_2;i_3,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_3,i_4}:N-N-S * t{a_2,a_4;i_2,i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_2;i_4,i_3}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_3;i_1}:N-N-S * t{a_4;i_3}:N-N-S * t{a_1,a_2;i_2,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_3;i_4}:N-N-S * t{a_1,a_2;i_2,i_3}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;a_1,i_3}:N-S-S * t{a_2,a_3;i_1,i_3}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3,a_4;i_2,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,a_4}:N-S-S * t{a_4;i_2}:N-N-S * t{a_2,a_3;i_1,i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;i_1,i_2}:N-S-S * t{a_1,a_2;i_3,i_4}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_1}:N-N-S * t{a_1,a_4;i_4,i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_3,i_4}:N-N-S * t{a_2,a_4;i_1,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_4;i_3,i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_4}:N-N-S * t{a_2,a_4;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;i_1,a_3}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S * t{a_3;i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,i_2;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_3,i_1}:N-N-S * t{a_2,a_4;i_4,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_3;i_1}:N-N-S * t{a_1,a_4;i_3,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_2;i_2,a_3}:N-S-S * t{a_1,a_3;i_3,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_4;i_3}:N-N-S * t{a_1,a_3;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_4;i_3}:N-N-S * t{a_1,a_2;i_1,i_4}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,i_2}:N-S-S * t{a_2,a_3;i_1,i_3}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_3;i_3}:N-N-S * t{a_1,a_4;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_4;i_1}:N-N-S * t{a_2,a_3;i_2,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;a_1,i_3}:N-S-S * t{a_2,a_3;i_2,i_3}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,i_2;a_2,a_1}:N-S-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_4;i_1,i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_3;i_3}:N-N-S * t{a_4;i_1}:N-N-S * t{a_1,a_2;i_2,i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,i_1;i_3,a_2}:N-S-S * t{a_1;i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_3;i_3}:N-N-S * t{a_2,a_4;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_2,i_4}:N-N-S * t{a_3,a_4;i_3,i_1}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_4;i_1}:N-N-S * t{a_1,a_2;i_3,i_4}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_4;i_2}:N-N-S * t{a_2,a_3;i_1,i_4}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_3,i_1}:N-N-S * t{a_2,a_3;i_4,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,a_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_2,a_4;i_1,i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_2;i_3}:N-N-S * t{a_1,a_3;i_4,i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_2}:N-N-S * t{a_2,a_4;i_1,i_4}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_2,i_4}:N-N-S * t{a_2,a_3;i_1,i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S * t{a_3,a_4;i_1,i_2}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_1,i_3}:N-N-S * t{a_2,a_3;i_2,i_4}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;i_2,a_3}:N-S-S * t{a_2,a_3;i_3,i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_4,i_3}:N-N-S * t{a_2,a_4;i_1,i_2}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;a_1,a_2}:N-S-S * t{a_3,a_4;i_2,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * f{a_3;i_3}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_2;i_1,i_3}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_4,i_3}:N-N-S * t{a_3,a_4;i_1,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,a_2}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * f{i_3;a_3}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_2;i_3,i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_4}:N-N-S * t{a_1,a_4;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S * t{a_2,a_4;i_4,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_1,a_3;i_1,i_3}:N-N-S + 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_3;i_3}:N-N-S * t{a_1,a_4;i_1,i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_1,a_3;i_2,i_3}:N-N-S + 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_4;i_2}:N-N-S * t{a_1,a_3;i_1,i_3}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;a_2,a_1}:N-S-S * t{a_3;i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_4;i_3}:N-N-S * t{a_2,a_3;i_1,i_2}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_3;i_4}:N-N-S * t{a_1,a_2;i_1,i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_2,i_4}:N-N-S * t{a_2,a_4;i_1,i_3}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{a_3,a_4;a_1,a_2}:N-S-S * t{a_3;i_2}:N-N-S * t{a_4;i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_2;a_3,i_2}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S +8 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_3;i_3}:N-N-S * t{a_1,a_2;i_1,i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_3,i_4}:N-N-S * t{a_3,a_4;i_1,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3,a_4;i_2,i_1}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_4,i_2}:N-N-S * t{a_3,a_4;i_3,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,i_1}:N-S-S * t{a_2,a_3;i_3,i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_2,i_4}:N-N-S * t{a_2,a_4;i_3,i_1}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_2;i_3}:N-N-S * t{a_3;i_1}:N-N-S * t{a_4;i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;a_1,a_2}:N-S-S * t{a_3;i_1}:N-N-S * t{a_4;i_2}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;i_1,a_3}:N-S-S * t{a_2,a_3;i_2,i_3}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * f{i_2;i_3}:N-S-S * t{a_1,a_2;i_1,i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_1,a_3;i_3,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,i_1}:N-S-S * t{a_2,a_3;i_2,i_3}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S * t{a_2,a_4;i_2,i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S * t{a_3;i_1}:N-N-S * t{a_4;i_2}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_2}:N-N-S * t{a_4;i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_3;i_1}:N-N-S * t{a_2,a_4;i_3,i_2}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_4,i_3}:N-N-S * t{a_2,a_4;i_2,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * f{i_1;i_3}:N-S-S * t{a_1,a_2;i_2,i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_4;i_2}:N-N-S * t{a_2,a_3;i_3,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_4;i_2}:N-N-S * t{a_1,a_3;i_3,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,i_2}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_2}:N-N-S * t{a_4;i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_2,i_3}:N-N-S * t{a_3,a_4;i_4,i_1}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,i_2;i_3,i_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_2;i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_3,i_1}:N-N-S * t{a_2,a_3;i_2,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_2;i_3,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * f{a_2;a_3}:N-S-S * t{a_1,a_3;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;a_1,a_2}:N-S-S * t{a_3;i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * f{a_1;a_3}:N-S-S * t{a_2,a_3;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2,a_3;i_1,i_4}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;a_2,i_3}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_3,i_2}:N-N-S * t{a_3,a_4;i_4,i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;i_2,a_3}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S * t{a_3;i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_2,a_3;i_3,i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_3;i_3}:N-N-S * t{a_1,a_2;i_4,i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_2,i_4}:N-N-S * t{a_2,a_3;i_3,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_3;i_3}:N-N-S * t{a_1,a_4;i_2,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * f{a_3;i_3}:N-S-S * t{a_2;i_3}:N-N-S * t{a_1,a_3;i_2,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_4,i_2}:N-N-S * t{a_2,a_4;i_3,i_1}:N-N-S + 16 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_1,i_3}:N-N-S * t{a_2,a_4;i_2,i_4}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_2;i_3}:N-N-S * t{a_1,a_3;i_1,i_4}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_3,i_1}:N-N-S * t{a_2,a_4;i_2,i_4}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;i_2,a_3}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_3;i_2}:N-N-S * t{a_2,a_4;i_3,i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2,a_3;i_4,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,i_2;a_1,a_2}:N-S-S - 8 Ŝ{i_1,i_2;a_1,a_2} * f{a_3;i_3}:N-S-S * t{a_2;i_3}:N-N-S * t{a_1,a_3;i_1,i_2}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,i_1;i_3,a_1}:N-S-S * t{a_2;i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;i_2,a_3}:N-S-S * t{a_2,a_3;i_1,i_3}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;i_1,i_2}:N-S-S * t{a_1,a_2;i_4,i_3}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_3;i_3}:N-N-S * t{a_4;i_2}:N-N-S * t{a_1,a_2;i_1,i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;a_1,a_2}:N-S-S * t{a_3,a_4;i_1,i_2}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_4,i_2}:N-N-S * t{a_2,a_3;i_3,i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,i_2}:N-S-S * t{a_2,a_3;i_3,i_1}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_2;i_3}:N-N-S * t{a_3,a_4;i_1,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_3;i_1}:N-N-S * t{a_4;i_2}:N-N-S * t{a_1,a_2;i_3,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_3,i_4}:N-N-S * t{a_2,a_4;i_2,i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_2;i_4,i_3}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_3;i_1}:N-N-S * t{a_4;i_3}:N-N-S * t{a_1,a_2;i_2,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_3;i_4}:N-N-S * t{a_1,a_2;i_2,i_3}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;a_1,i_3}:N-S-S * t{a_2,a_3;i_1,i_3}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3,a_4;i_2,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,a_4}:N-S-S * t{a_4;i_2}:N-N-S * t{a_2,a_3;i_1,i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;i_1,i_2}:N-S-S * t{a_1,a_2;i_3,i_4}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_1}:N-N-S * t{a_1,a_4;i_4,i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_3,i_4}:N-N-S * t{a_2,a_4;i_1,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_4;i_3,i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_4}:N-N-S * t{a_2,a_4;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;i_1,a_3}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S * t{a_3;i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,i_2;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_3,i_1}:N-N-S * t{a_2,a_4;i_4,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_3;i_1}:N-N-S * t{a_1,a_4;i_3,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_2;i_2,a_3}:N-S-S * t{a_1,a_3;i_3,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_4;i_3}:N-N-S * t{a_1,a_3;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_4;i_3}:N-N-S * t{a_1,a_2;i_1,i_4}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,i_2}:N-S-S * t{a_2,a_3;i_1,i_3}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_3;i_3}:N-N-S * t{a_1,a_4;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_4;i_1}:N-N-S * t{a_2,a_3;i_2,i_4}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;a_1,i_3}:N-S-S * t{a_2,a_3;i_2,i_3}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,i_2;a_2,a_1}:N-S-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_4;i_1,i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_3;i_3}:N-N-S * t{a_4;i_1}:N-N-S * t{a_1,a_2;i_2,i_4}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,i_1;i_3,a_2}:N-S-S * t{a_1;i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_4}:N-N-S * t{a_3;i_3}:N-N-S * t{a_2,a_4;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_2,i_4}:N-N-S * t{a_3,a_4;i_3,i_1}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_4;i_1}:N-N-S * t{a_1,a_2;i_3,i_4}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,i_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_4;i_2}:N-N-S * t{a_2,a_3;i_1,i_4}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_3,i_1}:N-N-S * t{a_2,a_3;i_4,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,a_4}:N-S-S * t{a_3;i_2}:N-N-S * t{a_2,a_4;i_1,i_3}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_2;i_3}:N-N-S * t{a_1,a_3;i_4,i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_2}:N-N-S * t{a_2,a_4;i_1,i_4}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_2,i_4}:N-N-S * t{a_2,a_3;i_1,i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_2;i_4}:N-N-S * t{a_3,a_4;i_1,i_2}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_4;i_1,i_3}:N-N-S * t{a_2,a_3;i_2,i_4}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;i_2,a_3}:N-S-S * t{a_2,a_3;i_3,i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_4,i_3}:N-N-S * t{a_2,a_4;i_1,i_2}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;a_1,a_2}:N-S-S * t{a_3,a_4;i_2,i_1}:N-N-S - 8 Ŝ{i_1,i_2;a_1,a_2} * f{a_3;i_3}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_2;i_1,i_3}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_2;i_4,i_3}:N-N-S * t{a_3,a_4;i_1,i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,a_2}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * f{i_3;a_3}:N-S-S * t{a_3;i_2}:N-N-S * t{a_1,a_2;i_3,i_1}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_2;i_3}:N-N-S * t{a_3;i_4}:N-N-S * t{a_1,a_4;i_1,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S * t{a_2,a_4;i_4,i_2}:N-N-S + 8 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_1,a_3;i_1,i_3}:N-N-S + 16 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_3;i_3}:N-N-S * t{a_1,a_4;i_1,i_2}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;i_3,i_4}:N-S-S * t{a_2;i_4}:N-N-S * t{a_1,a_3;i_2,i_3}:N-N-S + 16 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_2}:N-S-S * t{a_4;i_2}:N-N-S * t{a_1,a_3;i_1,i_3}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;a_2,a_1}:N-S-S * t{a_3;i_2}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;i_3,a_1}:N-S-S * t{a_4;i_3}:N-N-S * t{a_2,a_3;i_1,i_2}:N-N-S - 16 Ŝ{i_1,i_2;a_1,a_2} * g{i_2,a_3;i_3,i_4}:N-S-S * t{a_3;i_4}:N-N-S * t{a_1,a_2;i_1,i_3}:N-N-S + 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,i_4;a_3,a_4}:N-S-S * t{a_1,a_3;i_2,i_4}:N-N-S * t{a_2,a_4;i_1,i_3}:N-N-S - 2 Ŝ{i_1,i_2;a_1,a_2} * g{a_3,a_4;a_1,a_2}:N-S-S * t{a_3;i_2}:N-N-S * t{a_4;i_1}:N-N-S - 4 Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_2;a_3,i_2}:N-S-S * t{a_1;i_3}:N-N-S * t{a_3;i_1}:N-N-S )FIXTURE"; return s; } diff --git a/tests/unit/test_biorthogonalization.cpp b/tests/unit/test_biorthogonalization.cpp index 8d5320d5a9..0cd04f12c8 100644 --- a/tests/unit/test_biorthogonalization.cpp +++ b/tests/unit/test_biorthogonalization.cpp @@ -27,20 +27,19 @@ TEST_CASE("biorthogonalization", "[Biorthogonalization]") { L"1/2 t{a1;i1}", L"1/6 (2 g{a1,a2;i1,i2} + g{a2,a1;i1,i2})", // cmp. Wang & Knizia (2018), DOI: arXiv:1805.00565 L"1/120 (" - "-7 t{i_1,i_2,i_3;a_2,a_3,a_1}:N-C-S " - "- 7 t{i_1,i_2,i_3;a_3,a_1,a_2}:N-C-S " - "- t{i_1,i_2,i_3;a_2,a_1,a_3}:N-C-S " - "- t{i_1,i_2,i_3;a_3,a_2,a_1}:N-C-S " - "- t{i_1,i_2,i_3;a_1,a_3,a_2}:N-C-S " - "+ 17t{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S)"}; + "-7 t{i_1,i_2,i_3;a_2,a_3,a_1}:N-N-S " + "- 7 t{i_1,i_2,i_3;a_3,a_1,a_2}:N-N-S " + "- t{i_1,i_2,i_3;a_2,a_1,a_3}:N-N-S " + "- t{i_1,i_2,i_3;a_3,a_2,a_1}:N-N-S " + "- t{i_1,i_2,i_3;a_1,a_3,a_2}:N-N-S " + "+ 17t{i_1,i_2,i_3;a_1,a_2,a_3}:N-N-S)"}; REQUIRE(inputs.size() == expected_outputs.size()); for (std::size_t i = 0; i < inputs.size(); ++i) { CAPTURE(i); - ExprPtr input_expr = - deserialize(inputs.at(i), {.def_col_symm = ColumnSymmetry::Symm}); + ExprPtr input_expr = deserialize(inputs.at(i)); auto externals = external_indices(input_expr); @@ -81,8 +80,7 @@ TEST_CASE("biorthogonalization", "[Biorthogonalization]") { container::svector expressions; container::svector expected; for (std::size_t k = 0; k < inputs.at(i).size(); ++k) { - ResultExpr parsed = deserialize( - inputs.at(i).at(k), {.def_col_symm = ColumnSymmetry::Symm}); + ResultExpr parsed = deserialize(inputs.at(i).at(k)); expressions.push_back(parsed); expected.push_back( @@ -110,8 +108,7 @@ TEST_CASE("biorthogonalization", "[Biorthogonalization]") { container::svector expressions; for (const std::wstring &str : current_inputs) { - expressions.push_back(deserialize( - str, {.def_col_symm = ColumnSymmetry::Symm})); + expressions.push_back(deserialize(str)); } REQUIRE_THROWS_WITH( diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 5b32c3e2ba..3bbb004d4a 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -268,9 +268,9 @@ TEST_CASE("canonicalization", "[algorithms]") { .spbasis = SPBasis::Spinfree}) .set(AssertStrictBraKetSymmetry::No)); auto input = deserialize( - L"8 * Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,i_1}:N-S-S " + L"8 * Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,i_1}:N-S-S " L"* t{a_2,a_3;i_2,i_3}:N-N-S " - L"+ 8 * Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;a_1,i_3}:N-S-S " + L"+ 8 * Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;a_1,i_3}:N-S-S " L"* t{a_2,a_3;i_2,i_3}:N-N-S"); simplify(input); const std::size_t n = @@ -291,10 +291,10 @@ TEST_CASE("canonicalization", "[algorithms]") { ctx_min.set(AssertStrictBraKetSymmetry::No); auto resetter = set_scoped_default_context(ctx_min); auto exA = deserialize( - L"8 * Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_3,a_1;a_3,i_1}:N-S-S " + L"8 * Ŝ{i_1,i_2;a_1,a_2} * g{i_3,a_1;a_3,i_1}:N-S-S " L"* t{a_2,a_3;i_2,i_3}:N-N-S"); auto exB = deserialize( - L"8 * Ŝ{i_1,i_2;a_1,a_2}:N-C-S * g{i_1,a_3;a_1,i_3}:N-S-S " + L"8 * Ŝ{i_1,i_2;a_1,a_2} * g{i_1,a_3;a_1,i_3}:N-S-S " L"* t{a_2,a_3;i_2,i_3}:N-N-S"); REQUIRE(exA); REQUIRE(exB); diff --git a/tests/unit/test_main.cpp b/tests/unit/test_main.cpp index 53d43a326e..be040855ce 100644 --- a/tests/unit/test_main.cpp +++ b/tests/unit/test_main.cpp @@ -48,7 +48,12 @@ int main(int argc, char* argv[]) { .braket_typesetting = BraKetTypesetting::ContraSub, // to_latex() reference outputs predominantly assume the original // (naive) convention - .braket_slot_typesetting = BraKetSlotTypesetting::Naive}); + .braket_slot_typesetting = BraKetSlotTypesetting::Naive, + // mbpt works with particle-symmetric tensors, so default new/parsed + // tensors to particle (column) symmetry; bra-ket and permutational + // symmetries stay at the safe Nonsymm/NonHermitian library defaults and + // are specified explicitly where needed (e.g. Hermitian integrals) + .column_symmetry = ColumnSymmetry::Symm}); TensorCanonicalizer::set_cardinal_tensor_labels( sequant::mbpt::cardinal_tensor_labels()); // uncomment to enable verbose output ... diff --git a/tests/unit/test_mbpt.cpp b/tests/unit/test_mbpt.cpp index 0e3e8f4e10..37c1e9ef84 100644 --- a/tests/unit/test_mbpt.cpp +++ b/tests/unit/test_mbpt.cpp @@ -1039,8 +1039,8 @@ SECTION("SRSF") { auto scalar = expr->as().scalar(); REQUIRE(scalar == 1); REQUIRE_THAT(expr, - EquivalentTo("Ŝ{a_3,a_4;i_3,i_4}:N-C-S * " - "f{a_1,a_2;i_1,i_2}:N-C-S * ã{i_3,i_4;a_3,a_4}")); + EquivalentTo("Ŝ{a_3,a_4;i_3,i_4} * " + "f{a_1,a_2;i_1,i_2}:N-N-S * ã{i_3,i_4;a_3,a_4}")); } } // SECTION("SRSF") @@ -1135,9 +1135,9 @@ SECTION("rules") { const std::vector expected = { L"t{a1,a2;i1,i2} t{a3;i3}", L"t{a1,a2;i1,i2} g{a3;i3}", - L"t{a1,a2;i1,i2} B{i1;a1;x_1} B{i2;a2;x_1}", - L"t{a1,a2;i1,i2} (B{i1;a1;x_1} B{i2;a2;x_1} " - "- B{i2;a1;x_1} B{i1;a2;x_1})", + L"t{a1,a2;i1,i2} B{i1;a1;x_1}:N-C-S B{i2;a2;x_1}:N-C-S", + L"t{a1,a2;i1,i2} (B{i1;a1;x_1}:N-C-S B{i2;a2;x_1}:N-C-S " + "- B{i2;a1;x_1}:N-C-S B{i1;a2;x_1}:N-C-S)", }; REQUIRE(inputs.size() == expected.size()); @@ -1363,7 +1363,7 @@ SECTION("avoided-connections") { // only one term with no A-{f,g} connection REQUIRE(res2.is()); const std::wstring expected2 = - L"-1 Â{i_1;a_1}:A-C-S t{a_1,a_2;i_2,i_1}:A-C-S f{i_2;a_2}:A-C-S"; + L"-1 Â{i_1;a_1} t{a_1,a_2;i_2,i_1}:A-C-S f{i_2;a_2}:A-C-S"; REQUIRE_THAT(sequant::simplify(res2), EquivalentTo(expected2)); // same test as above but from Operator level and labels for connectivity @@ -1383,7 +1383,7 @@ SECTION("avoided-connections") { REQUIRE(res4_full.size() == 4); REQUIRE(res4.is()); // only single term survives const std::wstring expected4 = - L"Â{i_1;a_2}:A-C-S Â{a_1;i_2}:A-C-S g{i_3,i_2;a_3,a_1}:A-C-S " + L"Â{i_1;a_2} Â{a_1;i_2} g{i_3,i_2;a_3,a_1}:A-C-S " L"t{a_3,a_2;i_3,i_1}:A-C-S"; REQUIRE_THAT(simplify(res4), EquivalentTo(expected4)); } diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index f4256ad14f..8072e6c6a4 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -500,6 +500,16 @@ TEST_CASE("serialization", "[serialization]") { io::serialization::SerializationError, serializationErrorMatches(9, 1, "Invalid symmetry specifier")); } + + SECTION("(anti)symmetrization operators reject braket symmetry") { + // a reserved (anti)symmetrizer must be braket-Nonsymm; an explicit + // non-Nonsymm braket spec is rejected by the Tensor ctor + REQUIRE_THROWS(deserialize(L"Ŝ{i1,i2;a1,a2}:N-C-S")); + REQUIRE_THROWS(deserialize(L"Â{i1,i2;a1,a2}:A-C-S")); + // the default (braket-Nonsymm) form is accepted + REQUIRE_NOTHROW(deserialize(L"Ŝ{i1,i2;a1,a2}")); + REQUIRE_NOTHROW(deserialize(L"Â{i1,i2;a1,a2}")); + } } } diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 3660245d1e..4402c82c2a 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -293,11 +293,11 @@ TEST_CASE("spin", "[spin]") { REQUIRE_THAT( result, EquivalentTo( - "1/4 g{p↑_1,p↑_2;p↑_3,p↑_4}:N-C-S - 1/4 " - "g{p↑_2,p↑_1;p↑_3,p↑_4}:N-C-S + 1/4 g{p↑_1,p↓_2;p↑_3,p↓_4}:N-C-S " - "+ 1/4 g{p↑_2,p↓_1;p↑_4,p↓_3}:N-C-S + 1/4 " - "g{p↓_1,p↓_2;p↓_3,p↓_4}:N-C-S - 1/4 " - "g{p↓_2,p↓_1;p↓_3,p↓_4}:N-C-S")); + "1/4 g{p↑_1,p↑_2;p↑_3,p↑_4}:N-N-S - 1/4 " + "g{p↑_2,p↑_1;p↑_3,p↑_4}:N-N-S + 1/4 g{p↑_1,p↓_2;p↑_3,p↓_4}:N-N-S " + "+ 1/4 g{p↑_2,p↓_1;p↑_4,p↓_3}:N-N-S + 1/4 " + "g{p↓_1,p↓_2;p↓_3,p↓_4}:N-N-S - 1/4 " + "g{p↓_2,p↓_1;p↓_3,p↓_4}:N-N-S")); } } @@ -589,23 +589,23 @@ SECTION("partial expansion + S_maps = full expansion") { Symmetry::Antisymm); auto result = symmetrize_expr(input); REQUIRE_THAT( - result, SimplifiesTo( - "1/2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * t{a_1,a_2;i_1,i_2}:A-C-S " - "- 1/2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * t{a_1,a_2;i_2,i_1}:A-C-S")); + result, + SimplifiesTo("1/2 Ŝ{i_1,i_2;a_1,a_2} * t{a_1,a_2;i_1,i_2}:A-N-S " + "- 1/2 Ŝ{i_1,i_2;a_1,a_2} * t{a_1,a_2;i_2,i_1}:A-N-S")); result = S_maps(result); REQUIRE_THAT( result, SimplifiesTo( - "1/4 t{a_1,a_2;i_1,i_2}:A-C-S + 1/4 t{a_2,a_1;i_2,i_1}:A-C-S " - "- 1/4 t{a_1,a_2;i_2,i_1}:A-C-S - 1/4 t{a_2,a_1;i_1,i_2}:A-C-S")); + "1/4 t{a_1,a_2;i_1,i_2}:A-N-S + 1/4 t{a_2,a_1;i_2,i_1}:A-N-S " + "- 1/4 t{a_1,a_2;i_2,i_1}:A-N-S - 1/4 t{a_2,a_1;i_1,i_2}:A-N-S")); result = expand_A_op(input); REQUIRE_THAT( result, SimplifiesTo( - "1/4 t{a_1,a_2;i_1,i_2}:A-C-S - 1/4 t{a_1,a_2;i_2,i_1}:A-C-S " - "- 1/4 t{a_2,a_1;i_1,i_2}:A-C-S + 1/4 t{a_2,a_1;i_2,i_1}:A-C-S")); + "1/4 t{a_1,a_2;i_1,i_2}:A-N-S - 1/4 t{a_1,a_2;i_2,i_1}:A-N-S " + "- 1/4 t{a_2,a_1;i_1,i_2}:A-N-S + 1/4 t{a_2,a_1;i_2,i_1}:A-N-S")); } SECTION("partial spintracing + S_maps = full spintracing") { @@ -617,20 +617,20 @@ SECTION("partial spintracing + S_maps = full spintracing") { input, IdxGroupList{{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}); REQUIRE_THAT( result, - EquivalentTo("-2 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * t{a_1,a_2;i_2,i_1}:N-C-S " - "+ 4 Ŝ{i_1,i_2;a_1,a_2}:N-C-S * t{a_1,a_2;i_1,i_2}:N-C-S")); + EquivalentTo("-2 Ŝ{i_1,i_2;a_1,a_2} * t{a_1,a_2;i_2,i_1}:N-N-S " + "+ 4 Ŝ{i_1,i_2;a_1,a_2} * t{a_1,a_2;i_1,i_2}:N-N-S")); result = S_maps(result); REQUIRE_THAT( result, EquivalentTo( - "-1 t{a_1,a_2;i_2,i_1}:N-C-S - 1 t{a_2,a_1;i_1,i_2}:N-C-S " - "+ 2 t{a_1,a_2;i_1,i_2}:N-C-S + 2 t{a_2,a_1;i_2,i_1}:N-C-S")); + "-1 t{a_1,a_2;i_2,i_1}:N-N-S - 1 t{a_2,a_1;i_1,i_2}:N-N-S " + "+ 2 t{a_1,a_2;i_1,i_2}:N-N-S + 2 t{a_2,a_1;i_2,i_1}:N-N-S")); result = closed_shell_spintrace( input, IdxGroupList{{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}, true); REQUIRE_THAT( result, EquivalentTo( - "-1 t{a_1,a_2;i_2,i_1}:N-C-S - 1 t{a_2,a_1;i_1,i_2}:N-C-S " - "+ 2 t{a_1,a_2;i_1,i_2}:N-C-S + 2 t{a_2,a_1;i_2,i_1}:N-C-S")); + "-1 t{a_1,a_2;i_2,i_1}:N-N-S - 1 t{a_2,a_1;i_1,i_2}:N-N-S " + "+ 2 t{a_1,a_2;i_1,i_2}:N-N-S + 2 t{a_2,a_1;i_2,i_1}:N-N-S")); } SECTION("Symmetrize expression") { @@ -801,9 +801,9 @@ SECTION("Closed-shell spintrace CCD") { closed_shell_CC_spintrace_v1(pno_ccd_energy_so_as_sum); REQUIRE_THAT(pno_ccd_energy_sf, EquivalentTo("2 g{a1,a2;i1,i2}:N-C " - "t{i1,i2;a1,a2}:N-C - " + "t{i1,i2;a1,a2}:N-N - " "g{a1,a2;i1,i2}:N-C " - "t{i1,i2;a2,a1}:N-C")); + "t{i1,i2;a2,a1}:N-N")); } { // CSV (aka PNO) for more compact equations const auto pno_ccd_energy_so = deserialize( @@ -817,9 +817,9 @@ SECTION("Closed-shell spintrace CCD") { closed_shell_CC_spintrace_v2(pno_ccd_energy_so_as_sum); REQUIRE_THAT(pno_ccd_energy_sf, EquivalentTo("2 g{a1,a2;i1,i2}:N-C " - "t{i1,i2;a1,a2}:N-C - " + "t{i1,i2;a1,a2}:N-N - " "g{a1,a2;i1,i2}:N-C " - "t{i1,i2;a2,a1}:N-C")); + "t{i1,i2;a2,a1}:N-N")); } } } @@ -1083,14 +1083,14 @@ SECTION("Closed-shell spintrace CCSDT terms") { REQUIRE_THAT( result, - EquivalentTo("24 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * f{i_4;i_3}:N-C-S * " - "t{a_1,a_2,a_3;i_1,i_2,i_4}:N-C-S - 12" - " Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * f{i_4;i_3}:N-C-S * " - "t{a_1,a_2,a_3;i_2,i_1,i_4}:N-C-S + 12" - " Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * f{i_4;i_1}:N-C-S * " - "t{a_1,a_2,a_3;i_2,i_3,i_4}:N-C-S - 24" - " Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * f{i_4;i_2}:N-C-S * " - "t{a_1,a_2,a_3;i_1,i_3,i_4}:N-C-S ")); + EquivalentTo("24 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * f{i_4;i_3}:N-N-S * " + "t{a_1,a_2,a_3;i_1,i_2,i_4}:N-N-S - 12" + " Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * f{i_4;i_3}:N-N-S * " + "t{a_1,a_2,a_3;i_2,i_1,i_4}:N-N-S + 12" + " Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * f{i_4;i_1}:N-N-S * " + "t{a_1,a_2,a_3;i_2,i_3,i_4}:N-N-S - 24" + " Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * f{i_4;i_2}:N-N-S * " + "t{a_1,a_2,a_3;i_1,i_3,i_4}:N-N-S ")); } SECTION( @@ -1116,25 +1116,25 @@ SECTION("Closed-shell spintrace CCSDT terms") { REQUIRE_THAT( result_1, EquivalentTo( - " 8 g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_3,i_1,i_2}:N-C-S + " + " 8 g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_3,i_1,i_2}:N-N-S + " "2" - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_2,i_3,i_1}:N-C-S - 4" - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_3,i_1,i_2}:N-C-S - 4" - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_1,i_3,i_2}:N-C-S - 4 " - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_2,i_1,i_3}:N-C-S - 4 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_2,i_1,i_3}:N-C-S + 2 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_3,i_1,i_2}:N-C-S - 4" - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_3,i_2,i_1}:N-C-S + 2 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_2,i_3,i_1}:N-C-S - 4 " - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_1,i_3,i_2}:N-C-S - 4 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_1,i_2,i_3}:N-C-S + 8 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_1,i_2,i_3}:N-C-S + 8 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_2,i_1,i_3}:N-C-S + 2 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_3,i_2,i_1}:N-C-S - 4 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_2,i_3,i_1}:N-C-S - 4 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_3,i_2,i_1}:N-C-S + 2 " - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_1,i_2,i_3}:N-C-S + 2" - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_1,i_3,i_2}:N-C-S")); + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_2,i_3,i_1}:N-N-S - 4" + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_3,i_1,i_2}:N-N-S - 4" + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_1,i_3,i_2}:N-N-S - 4 " + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_2,i_1,i_3}:N-N-S - 4 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_2,i_1,i_3}:N-N-S + 2 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_3,i_1,i_2}:N-N-S - 4" + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_3,i_2,i_1}:N-N-S + 2 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_2,i_3,i_1}:N-N-S - 4 " + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_1,i_3,i_2}:N-N-S - 4 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_1,i_2,i_3}:N-N-S + 8 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_1,i_2,i_3}:N-N-S + 8 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_2,i_1,i_3}:N-N-S + 2 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_3,i_2,i_1}:N-N-S - 4 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_2,i_3,i_1}:N-N-S - 4 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_3,i_2,i_1}:N-N-S + 2 " + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_1,i_2,i_3}:N-N-S + 2" + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_1,i_3,i_2}:N-N-S")); // the new efficient method, does spintracing with partial expansion, then // expanding by S_map (this method is used in @@ -1150,24 +1150,24 @@ SECTION("Closed-shell spintrace CCSDT terms") { REQUIRE_THAT( result_2, EquivalentTo( - "8 g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_3,i_1,i_2}:N-C-S + 2" - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_2,i_3,i_1}:N-C-S - 4 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_3,i_1,i_2}:N-C-S - 4 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_1,i_3,i_2}:N-C-S - 4 " - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_2,i_1,i_3}:N-C-S - 4" - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_2,i_1,i_3}:N-C-S+ 2 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_3,i_1,i_2}:N-C-S - 4 " - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_3,i_2,i_1}:N-C-S + 2 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_2,i_3,i_1}:N-C-S - 4" - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_1,i_3,i_2}:N-C-S - 4" - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_1,i_2,i_3}:N-C-S + 8 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_1,i_2,i_3}:N-C-S + 8 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_2,i_1,i_3}:N-C-S + 2 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_3,i_2,i_1}:N-C-S - 4 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_2,i_3,i_1}:N-C-S - 4 " - "g{a_2,a_3;a_4,a_5}:N-C-S * t{a_1,a_4,a_5;i_3,i_2,i_1}:N-C-S + 2 " - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_1,i_2,i_3}:N-C-S + 2 " - "g{a_1,a_3;a_4,a_5}:N-C-S * t{a_2,a_4,a_5;i_1,i_3,i_2}:N-C-S")); + "8 g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_3,i_1,i_2}:N-N-S + 2" + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_2,i_3,i_1}:N-N-S - 4 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_3,i_1,i_2}:N-N-S - 4 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_1,i_3,i_2}:N-N-S - 4 " + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_2,i_1,i_3}:N-N-S - 4" + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_2,i_1,i_3}:N-N-S+ 2 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_3,i_1,i_2}:N-N-S - 4 " + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_3,i_2,i_1}:N-N-S + 2 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_2,i_3,i_1}:N-N-S - 4" + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_1,i_3,i_2}:N-N-S - 4" + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_1,i_2,i_3}:N-N-S + 8 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_1,i_2,i_3}:N-N-S + 8 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_2,i_1,i_3}:N-N-S + 2 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_3,i_2,i_1}:N-N-S - 4 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_2,i_3,i_1}:N-N-S - 4 " + "g{a_2,a_3;a_4,a_5}:N-N-S * t{a_1,a_4,a_5;i_3,i_2,i_1}:N-N-S + 2 " + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_1,i_2,i_3}:N-N-S + 2 " + "g{a_1,a_3;a_4,a_5}:N-N-S * t{a_2,a_4,a_5;i_1,i_3,i_2}:N-N-S")); } SECTION("the most expensive terms in CCSDT in v2") { // results in 1 term @@ -1181,7 +1181,7 @@ SECTION("Closed-shell spintrace CCSDT terms") { result, EquivalentTo( L"3 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * " - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_3,i_1,i_2}:N-C-S")); + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_3,i_1,i_2}:N-N-S")); } SECTION("most expensive CCSDT term in v1") { // results in 4 terms @@ -1196,13 +1196,13 @@ SECTION("Closed-shell spintrace CCSDT terms") { result, EquivalentTo( L"-6/5 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * " - "g{a_1,a_2;a_4,a_5}:N-C-S * t{a_3,a_4,a_5;i_1,i_2,i_3}:N-C-S + " - "3 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * g{a_1,a_2;a_4,a_5}:N-C-S *" - " t{a_3,a_4,a_5;i_3,i_1,i_2}:N-C-S -" - " 3/5 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * g{a_1,a_2;a_4,a_5}:N-C-S *" - " t{a_3,a_4,a_5;i_3,i_2,i_1}:N-C-S -" - " 6/5 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3}:N-C-S * g{a_1,a_2;a_4,a_5}:N-C-S *" - " t{a_3,a_4,a_5;i_2,i_1,i_3}:N-C-S")); + "g{a_1,a_2;a_4,a_5}:N-N-S * t{a_3,a_4,a_5;i_1,i_2,i_3}:N-N-S + " + "3 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * g{a_1,a_2;a_4,a_5}:N-N-S *" + " t{a_3,a_4,a_5;i_3,i_1,i_2}:N-N-S -" + " 3/5 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * g{a_1,a_2;a_4,a_5}:N-N-S *" + " t{a_3,a_4,a_5;i_3,i_2,i_1}:N-N-S -" + " 6/5 Ŝ{i_1,i_2,i_3;a_1,a_2,a_3} * g{a_1,a_2;a_4,a_5}:N-N-S *" + " t{a_3,a_4,a_5;i_2,i_1,i_3}:N-N-S")); } SECTION("f * t3") { @@ -1573,8 +1573,8 @@ SECTION("Open-shell spin-tracing") { auto result = expand_A_op(input); result->visit(reset_idx_tags); REQUIRE_THAT(result, - EquivalentTo("-1 g{i↑_3,i↑_4;i↑_1,i↑_2}:A-C-S * " - "t{a↑_1,a↑_2,a↓_3;i↑_4,i↑_3,i↓_3}:N-C-S")); + EquivalentTo("-1 g{i↑_3,i↑_4;i↑_1,i↑_2}:A-N-S * " + "t{a↑_1,a↑_2,a↓_3;i↑_4,i↑_3,i↓_3}:N-N-S")); g = Tensor(L"g", bra{i4A, i5A}, ket{i1A, i2A}, Symmetry::Antisymm); t3 = @@ -1584,8 +1584,8 @@ SECTION("Open-shell spin-tracing") { result = expand_A_op(input); result->visit(reset_idx_tags); REQUIRE_THAT(result, - EquivalentTo("-1 g{i↑_3,i↑_4;i↑_1,i↑_2}:A-C-S * " - "t{a↑_1,a↑_2,a↓_3;i↑_4,i↑_3,i↓_3}:N-C-S")); + EquivalentTo("-1 g{i↑_3,i↑_4;i↑_1,i↑_2}:A-N-S * " + "t{a↑_1,a↑_2,a↓_3;i↑_4,i↑_3,i↓_3}:N-N-S")); } // CCSDT R3 10 aaa, bbb diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index aa9ecb6198..3167245fc8 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -59,7 +59,10 @@ TEST_CASE("tensor", "[elements]") { REQUIRE(ranges::distance(t2.const_indices().begin(), t2.const_indices().end()) == 2); REQUIRE(t2.symmetry() == Symmetry::Nonsymm); - REQUIRE(t2.braket_symmetry() == BraKetSymmetry::Conjugate); + // bare ctor: braket symmetry is derived from the default Context's + // Hermiticity (NonHermitian here) -> Nonsymm; column symmetry follows the + // Context default (Symm in the test context) + REQUIRE(t2.braket_symmetry() == BraKetSymmetry::Nonsymm); REQUIRE(t2.column_symmetry() == ColumnSymmetry::Symm); REQUIRE(t2.label() == L"F"); @@ -82,7 +85,7 @@ TEST_CASE("tensor", "[elements]") { REQUIRE(ranges::distance(t3.const_indices().begin(), t3.const_indices().end()) == 2); REQUIRE(t3.symmetry() == Symmetry::Nonsymm); - REQUIRE(t3.braket_symmetry() == BraKetSymmetry::Conjugate); + REQUIRE(t3.braket_symmetry() == BraKetSymmetry::Nonsymm); REQUIRE(t3.column_symmetry() == ColumnSymmetry::Symm); REQUIRE(t3.label() == L"N"); @@ -432,7 +435,9 @@ TEST_CASE("tensor", "[elements]") { SECTION("adjoint") { auto f1 = Tensor(L"F", bra{L"i_1", L"i_2"}, ket{L"i_3", L"i_4"}); REQUIRE_NOTHROW(f1.adjoint()); - REQUIRE(to_latex(f1) == L"{F^{{i_1}{i_2}}_{{i_3}{i_4}}}"); + // F is now non-Hermitian by default (braket Nonsymm), so its adjoint is + // marked with the conjugation superscript + REQUIRE(to_latex(f1) == L"{F⁺^{{i_1}{i_2}}_{{i_3}{i_4}}}"); auto t1 = Tensor(L"t", bra{L"a_1"}, ket{L"i_1"}, Symmetry::Nonsymm, BraKetSymmetry::Nonsymm); @@ -445,7 +450,7 @@ TEST_CASE("tensor", "[elements]") { ex(cre{L"i_1"}, ann{L"i_2"}); h1 = adjoint(h1); REQUIRE(to_latex(h1) == - L"{{\\tilde{a}^{{i_2}}_{{i_1}}}{F^{{i_1}}_{{i_2}}}}"); + L"{{\\tilde{a}^{{i_2}}_{{i_1}}}{F⁺^{{i_1}}_{{i_2}}}}"); h1 = adjoint(h1); REQUIRE(to_latex(h1) == L"{{F^{{i_2}}_{{i_1}}}{\\tilde{a}^{{i_1}}_{{i_2}}}}"); @@ -564,4 +569,29 @@ TEST_CASE("tensor_hermiticity", "[elements]") { // complex spaces -> Conjugate -> the two orientations remain distinct REQUIRE_FALSE(make_diff(Field::Complex) == ex(0)); } + + SECTION("default symmetries are taken from the Context") { + // unspecified symmetries are resolved against the active default Context; + // braket symmetry is *derived* from the Context's default Hermiticity and + // the tensor's base field (there is no braket-symmetry Context knob) + auto ctx = get_default_context(); + ctx.set(Symmetry::Nonsymm) + .set(Hermiticity::Hermitian) + .set(ColumnSymmetry::Nonsymm); + auto resetter = set_scoped_default_context(ctx); + + auto g = Tensor(L"g", bra{L"i_1"}, ket{L"i_2"}); + REQUIRE(g.symmetry() == Symmetry::Nonsymm); + REQUIRE(g.hermiticity() == Hermiticity::Hermitian); + REQUIRE(g.column_symmetry() == ColumnSymmetry::Nonsymm); + REQUIRE(g.braket_symmetry() == + to_braket_symmetry(Hermiticity::Hermitian, g.base_field())); + + // explicitly-specified symmetries override the Context defaults + auto t = Tensor(L"t", bra{L"a_1"}, ket{L"i_1"}, Symmetry::Nonsymm, + BraKetSymmetry::Nonsymm, ColumnSymmetry::Symm); + REQUIRE(t.braket_symmetry() == BraKetSymmetry::Nonsymm); + REQUIRE(t.hermiticity() == Hermiticity::NonHermitian); + REQUIRE(t.column_symmetry() == ColumnSymmetry::Symm); + } } diff --git a/tests/unit/test_tensor_network.cpp b/tests/unit/test_tensor_network.cpp index f4be9861eb..97fb9aafe2 100644 --- a/tests/unit/test_tensor_network.cpp +++ b/tests/unit/test_tensor_network.cpp @@ -283,10 +283,10 @@ TEMPLATE_TEST_CASE("tensor_network_shared", "[elements]", TensorNetworkV1, std::vector>> tests{ {L"G{;;a1,a2,a3,a4} T{;;i3,i2,a3,a4}", - v3 ? idxvec_t{L"i_3", L"i_2", L"a_2", L"a_1"} + v3 ? idxvec_t{L"i_2", L"i_3", L"a_2", L"a_1"} : idxvec_t{L"i_2", L"i_3", L"a_1", L"a_2"}}, {L"G{;;a1,a2,a3,a4} T{;;i2,i3,a3,a4}", - v3 ? idxvec_t{L"i_2", L"i_3", L"a_2", L"a_1"} + v3 ? idxvec_t{L"i_3", L"i_2", L"a_2", L"a_1"} : idxvec_t{L"i_3", L"i_2", L"a_1", L"a_2"}}, }; @@ -1182,12 +1182,11 @@ TEST_CASE("tensor_network_v2", "[elements][valgrind_skip]") { } SECTION("special") { - auto factors = - deserialize( - L"Ŝ{i_1;a_1}:N-C-S g{i_2,a_1;a_2,i_1}:N-C-S " - L"t{a_2;i_2}:N-C-S") - ->as() - .factors(); + auto factors = deserialize( + L"Ŝ{i_1;a_1} g{i_2,a_1;a_2,i_1}:N-C-S " + L"t{a_2;i_2}:N-C-S") + ->as() + .factors(); TensorNetworkV2 tn(factors); diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index ac71bca4cd..b5218175b4 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -1305,23 +1305,23 @@ TEST_CASE("wick", "[algorithms][wick][valgrind_skip]") { // sequant::wprintf(to_latex_align(Ld_H2N_L), L" = \n", // to_latex_align(result2, 0, 2), L"\n"); REQUIRE(result2.as().size() == 5); - // Sum-term ordering shifted vs master after the removal of - // Context::braket_symmetry: the canonical sort key for the two - // -1/2 ã·v̄·w terms swapped. The expression is mathematically - // unchanged. + // Sum-term ordering depends on the tensors' (Context-derived) braket + // and column symmetries; the canonical sort key for these ã·v̄·w terms + // shifted vs master as those defaults changed. The expression is + // mathematically unchanged. REQUIRE( result2.to_latex() == L"{ \\bigl( - " - L"{{{\\frac{1}{4}}}{\\tilde{a}^{{p_3}{p_4}{p_5}}_{{p_1}{p_2}{p_5}" - L"}}{\\bar{v}^{{p_1}{p_2}}_{{p_3}{p_4}}}{w^{}_{}[{p_5}]}} - " - L"{{{\\frac{1}{2}}}{\\tilde{a}^{{p_2}{p_3}}_{{e_1}{p_1}}}{\\bar{" - L"v}^{{e_1}{p_1}}_{{p_2}{p_3}}}{w^{}_{}[{e_1}]}} + " - L"{{\\tilde{a}^{{p_2}}_{{p_1}}}{\\bar{v}^{{e_1}{p_1}}_{{e_1}{p_2}" - L"}}{w^{}_{}[{e_1}]}} - " - L"{{{\\frac{1}{2}}}{\\tilde{a}^{{e_1}{p_3}}_{{p_1}{p_2}}}{\\bar{" - L"v}^{{p_1}{p_2}}_{{e_1}{p_3}}}{w^{}_{}[{e_1}]}} + " - L"{{{\\frac{1}{4}}}{\\tilde{a}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{" - L"v}^{{p_1}{p_2}}_{{p_3}{p_4}}}{w^{}_{}[{e_1}]}}\\bigr) }"); + L"{{{\\frac{1}{2}}}{\\tilde{a}^{{e_1}{p_3}}_{{p_1}{p_2}}}{\\bar{v}^" + L"{{p_1}{p_2}}_{{e_1}{p_3}}}{w^{}_{}[{e_1}]}} - " + L"{{{\\frac{1}{4}}}{\\tilde{a}^{{p_3}{p_4}{p_5}}_{{p_1}{p_2}{p_5}}}" + L"{\\bar{v}^{{p_1}{p_2}}_{{p_3}{p_4}}}{w^{}_{}[{p_5}]}} + " + L"{{\\tilde{a}^{{p_2}}_{{p_1}}}{\\bar{v}^{{e_1}{p_1}}_{{e_1}{p_2}}}" + L"{w^{}_{}[{e_1}]}} + " + L"{{{\\frac{1}{4}}}{\\tilde{a}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{v}^" + L"{{p_1}{p_2}}_{{p_3}{p_4}}}{w^{}_{}[{e_1}]}} - " + L"{{{\\frac{1}{2}}}{\\tilde{a}^{{p_2}{p_3}}_{{e_1}{p_1}}}{\\bar{v}^" + L"{{e_1}{p_1}}_{{p_2}{p_3}}}{w^{}_{}[{e_1}]}}\\bigr) }"); } // simplified example with "diagonal" operator from the paper, inspired by From 7fdb7946a9565c4f73a092b6da3918667986ccea Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Mon, 15 Jun 2026 21:18:40 -0700 Subject: [PATCH 11/22] deserialize: keep reserved (anti)symmetrizers braket-Nonsymm regardless of Context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Copilot review on #557: - A bare "Ŝ{...}"/"Â{...}" previously inherited the Context's default Hermiticity, so under a Hermitian-default Context it would derive a non-Nonsymm braket and fail to construct. Force braket = Nonsymm for these reserved operators when no braket spec is given (an explicit spec is left for the Tensor ctor to reject). Covered by a new test under a Hermitian Context. - Fix a misleading comment in Tensor::resolve_symmetries() about when the resolved Hermiticity is used. --- SeQuant/core/expressions/tensor.hpp | 6 +++++- .../core/io/serialization/v1/ast_conversions.hpp | 16 +++++++++++++--- tests/unit/test_parse.cpp | 10 ++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index dc140b5a2b..50b3016608 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -290,7 +290,11 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { } else { s_resolved = *s; ps_resolved = *ps; - // not used below (bks_opt is set), value is irrelevant + // h_resolved is consulted below only to derive the braket symmetry, and + // only when no BraKetSymmetry was given. To reach this branch with no + // BraKetSymmetry, a Hermiticity must have been given, so herm_opt is set + // in exactly the case h_resolved is used; the value_or fallback is + // unused. h_resolved = herm_opt.value_or(Hermiticity::NonHermitian); } const BraKetSymmetry bks_resolved = diff --git a/SeQuant/core/io/serialization/v1/ast_conversions.hpp b/SeQuant/core/io/serialization/v1/ast_conversions.hpp index 97ef675b0e..f177e85dd1 100644 --- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp +++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp @@ -327,14 +327,24 @@ struct Transformer { // operator (overriding the parsed/default one). The two differ by design: // the antisymmetrizer  antisymmetrizes within bra and within ket (a // permutational #Symmetry), whereas the symmetrizer Ŝ symmetrizes the - // {bra,ket} particle columns (a #ColumnSymmetry). The remaining symmetry - // attributes are resolved/validated by the Tensor ctor (which also requires - // these operators to be braket-Nonsymm). + // {bra,ket} particle columns (a #ColumnSymmetry). + const bool is_reserved_symmetrizer = + tensor.name == reserved::antisymm_label() || + tensor.name == reserved::symm_label(); if (tensor.name == reserved::antisymm_label()) { perm_symm = Symmetry::Antisymm; } else if (tensor.name == reserved::symm_label()) { column_symm = ColumnSymmetry::Symm; } + // (Anti)symmetrization operators are always braket-Nonsymm. When no braket + // symmetry is spelled out, force Nonsymm rather than letting them inherit + // the Context's default Hermiticity (which could derive a non-Nonsymm + // braket and make a plain "Ŝ{...}"/"Â{...}" fail to construct). An explicit + // braket spec is left untouched so the Tensor ctor still rejects it. + if (is_reserved_symmetrizer && + (!tensor.symmetry.has_value() || + tensor.symmetry.value().braket_symm == ast::SymmetrySpec::unspecified)) + braket_symm = BraKetSymmetry::Nonsymm; // Dispatch to correct Tensor constructor (taking either BraKetSymmetry or // Hermiticity) diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 8072e6c6a4..e830d7203d 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -509,6 +509,16 @@ TEST_CASE("serialization", "[serialization]") { // the default (braket-Nonsymm) form is accepted REQUIRE_NOTHROW(deserialize(L"Ŝ{i1,i2;a1,a2}")); REQUIRE_NOTHROW(deserialize(L"Â{i1,i2;a1,a2}")); + // ... and stays accepted even when the Context defaults to Hermitian: + // reserved (anti)symmetrizers must not inherit the Context braket + // default (which would otherwise derive a non-Nonsymm braket and throw) + { + auto ctx = get_default_context(); + ctx.set(Hermiticity::Hermitian); + auto resetter = set_scoped_default_context(ctx); + REQUIRE_NOTHROW(deserialize(L"Ŝ{i1,i2;a1,a2}")); + REQUIRE_NOTHROW(deserialize(L"Â{i1,i2;a1,a2}")); + } } } } From a7f9dea3df27126a8d73901e75087aacf433ed9a Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Wed, 8 Jul 2026 16:19:59 -0400 Subject: [PATCH 12/22] tensor: make programmatic symmetry defaults Context-free (Option B) Programmatic Tensor construction now resolves unspecified symmetry attributes against fixed library defaults instead of the active Context; only deserialization honors the Context-level symmetry/hermiticity/ column-symmetry defaults. This makes a ctor call's meaning independent of ambient global state. The fixed defaults are the safest fully non-symmetric / non-Hermitian choice (Symmetry::Nonsymm, Hermiticity::NonHermitian, ColumnSymmetry::Nonsymm), encoded once in the new public Tensor::Defaults struct and consumed by resolve_symmetries(). Ctor @param docs now state which value each unspecified optional resolves to. Adds a named-parameter idiom (TensorSymmetries, C++20 designated initializers) so a TU can define a symmetry pack once and reuse it, sidestepping positional-arg strictness. context.hpp docs retargeted to describe the deserializer-only role of the Context symmetry knobs. MBPT/physics sites, whose particles are indistinguishable (hence tensors are particle/column-symmetric), now pass ColumnSymmetry::Symm explicitly: op.cpp, spin.cpp, antisymmetrizer.cpp, biorthogonalization.cpp, and the DF/THC factor builders (rules/df.cpp, rules/thc.cpp). Unit tests updated to the new defaults: particle-symmetric constructions carry an explicit column-Symm pack (per-TU TensorSymmetries ps), matching the parsed references that still follow the Context. Full unit suite green (6563 assertions, 49 cases). --- SeQuant/core/context.hpp | 44 ++-- SeQuant/core/expressions/tensor.hpp | 233 ++++++++++++++---- SeQuant/domain/mbpt/antisymmetrizer.cpp | 18 +- SeQuant/domain/mbpt/biorthogonalization.cpp | 5 +- SeQuant/domain/mbpt/op.cpp | 28 ++- SeQuant/domain/mbpt/rules/df.cpp | 12 +- SeQuant/domain/mbpt/rules/thc.cpp | 22 +- SeQuant/domain/mbpt/spin.cpp | 19 +- tests/unit/test_canonicalize.cpp | 251 +++++++++----------- tests/unit/test_eval_expr.cpp | 9 +- tests/unit/test_parse.cpp | 24 ++ tests/unit/test_spin.cpp | 163 +++++++------ tests/unit/test_tensor.cpp | 92 +++---- tests/unit/test_wick.cpp | 51 ++-- 14 files changed, 598 insertions(+), 373 deletions(-) diff --git a/SeQuant/core/context.hpp b/SeQuant/core/context.hpp index a9adb210dd..dc71891394 100644 --- a/SeQuant/core/context.hpp +++ b/SeQuant/core/context.hpp @@ -58,12 +58,15 @@ class Context { constexpr static auto braket_typesetting = BraKetTypesetting::ContraSub; constexpr static auto braket_slot_typesetting = BraKetSlotTypesetting::TensorPackage; - // default symmetries for newly-constructed tensors are the *safest* - // (most general) possible; applications can fine-tune them via Context for - // ergonomics (e.g. mbpt assumes particle-symmetric tensors). Note that - // there is no braket-symmetry default: braket symmetry is a *derived* - // property of a tensor (from its #Hermiticity and #base_field), so - // #hermiticity is the knob instead (cf. removal of Context::braket_symmetry). + // default symmetries used when *deserializing* a tensor whose symmetry is + // under-specified in the input; the library defaults are the *safest* (most + // general) possible, and applications can fine-tune them via Context for + // ergonomics (e.g. mbpt assumes particle-symmetric tensors). These do NOT + // affect the programmatic Tensor ctors, whose defaults are fixed and + // independent of the ambient Context. Note that there is no braket-symmetry + // default: braket symmetry is a *derived* property of a tensor (from its + // #Hermiticity and #base_field), so #hermiticity is the knob instead (cf. + // removal of Context::braket_symmetry). constexpr static auto symmetry = Symmetry::Nonsymm; constexpr static auto hermiticity = Hermiticity::NonHermitian; constexpr static auto column_symmetry = ColumnSymmetry::Nonsymm; @@ -95,13 +98,13 @@ class Context { /// the BraKetSlotTypesetting object BraKetSlotTypesetting braket_slot_typesetting = Defaults::braket_slot_typesetting; - /// the default bra/ket permutational Symmetry for new tensors + /// the default bra/ket permutational Symmetry for deserialized tensors Symmetry symmetry = Defaults::symmetry; - /// the default Hermiticity for new tensors; the braket symmetry of a new - /// tensor is *derived* from this and the tensor's #base_field + /// the default Hermiticity for deserialized tensors; the braket symmetry + /// of a deserialized tensor is *derived* from this and its #base_field Hermiticity hermiticity = Defaults::hermiticity; - /// the default ColumnSymmetry (particle-permutation symmetry) for new - /// tensors + /// the default ColumnSymmetry (particle-permutation symmetry) for + /// deserialized tensors ColumnSymmetry column_symmetry = Defaults::column_symmetry; }; static Options make_default_options() { return {}; } @@ -172,13 +175,14 @@ class Context { /// \return BraKetSlotTypesetting of this context; see BraKetSlotTypesetting /// for the meaning of the possible values BraKetSlotTypesetting braket_slot_typesetting() const; - /// \return the default bra/ket permutational Symmetry for new tensors + /// \return the default bra/ket permutational Symmetry for deserialized tensors Symmetry symmetry() const; - /// \return the default Hermiticity for new tensors; the braket symmetry of a - /// new tensor is *derived* from this and the tensor's #base_field + /// \return the default Hermiticity for deserialized tensors; the braket + /// symmetry of a deserialized tensor is *derived* from this and its + /// #base_field Hermiticity hermiticity() const; - /// \return the default ColumnSymmetry (particle-permutation symmetry) for new - /// tensors + /// \return the default ColumnSymmetry (particle-permutation symmetry) for + /// deserialized tensors ColumnSymmetry column_symmetry() const; /// Sets the Vacuum for this context, convenient for chaining @@ -220,14 +224,14 @@ class Context { /// \param braket_slot_typeset BraKetSlotTypesetting /// \return ref to `*this`, for chaining Context& set(BraKetSlotTypesetting braket_slot_typeset); - /// Sets the default bra/ket permutational Symmetry for new tensors + /// Sets the default bra/ket permutational Symmetry for deserialized tensors /// \return ref to `*this`, for chaining Context& set(Symmetry symmetry); - /// Sets the default Hermiticity for new tensors (the braket symmetry of a new - /// tensor is derived from this and the tensor's base field) + /// Sets the default Hermiticity for deserialized tensors (the braket symmetry + /// of a deserialized tensor is derived from this and its base field) /// \return ref to `*this`, for chaining Context& set(Hermiticity hermiticity); - /// Sets the default ColumnSymmetry for new tensors + /// Sets the default ColumnSymmetry for deserialized tensors /// \return ref to `*this`, for chaining Context& set(ColumnSymmetry column_symmetry); diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index 50b3016608..b3ef5f430f 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -54,9 +54,59 @@ DEFINE_STRONG_TYPE_FOR_RANGE_AND_RANGESIZE(ket); // strong type wrapper for objects associated with aux DEFINE_STRONG_TYPE_FOR_RANGE_AND_RANGESIZE(aux); +/// @brief named-parameter pack of a Tensor's symmetry attributes +/// +/// Passed to the Tensor constructors as a single argument (the named-parameter +/// idiom via designated initializers) instead of the positional +/// `Symmetry`/`BraKetSymmetry`/`ColumnSymmetry` arguments, sidestepping their +/// order strictness. Each field is optional: a field left unset falls back to +/// the *fixed* library default when the Tensor is constructed (see +/// Tensor::resolve_symmetries), exactly as an omitted positional argument +/// would. This makes it easy to define one symmetry pack for a translation unit +/// (e.g. `constexpr TensorSymmetries particle_symmetric{.column = +/// ColumnSymmetry::Symm};`) and reuse it at every construction site. +/// +/// @note #braket and #hermiticity are two ways to specify the same underlying +/// trait: if #braket is set it is used directly (and, unless #hermiticity +/// is also set, the Hermiticity is back-filled from it); otherwise the +/// braket symmetry is *derived* from #hermiticity and the tensor's base +/// field. Prefer #hermiticity for field-agnostic physical facts. +struct TensorSymmetries { + /// bra/ket permutational symmetry + std::optional perm = std::nullopt; + /// bra<->ket exchange symmetry (an alternative to #hermiticity; see note) + std::optional braket = std::nullopt; + /// abstract (field-agnostic) adjoint symmetry + std::optional hermiticity = std::nullopt; + /// particle (column) permutation symmetry + std::optional column = std::nullopt; +}; + /// @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 MutatableLabeled { + public: + /// @brief the fixed library defaults used to resolve the symmetry attributes + /// a Tensor constructor leaves unspecified + /// + /// Every programmatic constructor resolves an omitted `std::optional` + /// symmetry argument (or an unset TensorSymmetries field) against these; see + /// resolve_symmetries(). They are *independent of the active + /// sequant::Context* -- unlike deserialization, whose corresponding defaults + /// come from Context. There is deliberately no BraKetSymmetry default: the + /// braket symmetry is never a free default but is *derived* from #hermiticity + /// and the tensor's #base_field via to_braket_symmetry() (so the default + /// Hermiticity #NonHermitian yields a #Nonsymm braket over any field). + struct Defaults { + /// default bra/ket permutational Symmetry + static constexpr Symmetry symmetry = Symmetry::Nonsymm; + /// default (field-agnostic) Hermiticity; the observable BraKetSymmetry is + /// derived from this and the tensor's base field + static constexpr Hermiticity hermiticity = Hermiticity::NonHermitian; + /// default particle-exchange ColumnSymmetry + static constexpr ColumnSymmetry column_symmetry = ColumnSymmetry::Nonsymm; + }; + private: using index_container_type = container::svector; static auto make_indices(IndexList indices) { return indices; } @@ -268,35 +318,23 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// - #BraKetSymmetry is a *derived* property: if not given explicitly it is /// computed from the (explicit or default) #Hermiticity and the tensor's /// @p base_fld via to_braket_symmetry(). - /// - #Symmetry, #Hermiticity and #ColumnSymmetry fall back to the active - /// default sequant::Context when not specified. - /// @note The default Context is consulted *only* when some attribute is left - /// unspecified, so fully-explicit construction (the hot path, e.g. - /// canonicalization copying a source tensor's attributes) never pays - /// the get_default_context() synchronization cost. + /// - #Symmetry, #Hermiticity and #ColumnSymmetry fall back to the *fixed* + /// library defaults in Tensor::Defaults -- the safest, fully non-symmetric + /// / non-Hermitian choice -- when not specified. + /// @note Programmatic Tensor construction never consults the default + /// sequant::Context: the meaning of a ctor call is independent of + /// ambient global state (so it is predictable and lock-free). Only the + /// deserializer honors the Context-level symmetry defaults, since it is + /// the boundary that turns under-specified external input into tensors; + /// domains that need a different programmatic default (e.g. mbpt, whose + /// tensors are particle/column-symmetric) pass it explicitly. static resolved_symmetries resolve_symmetries( std::optional s, std::optional bks_opt, std::optional ps, std::optional herm_opt, Field base_fld) { - Symmetry s_resolved; - Hermiticity h_resolved; - ColumnSymmetry ps_resolved; - if (!s.has_value() || !ps.has_value() || - (!bks_opt.has_value() && !herm_opt.has_value())) { - const Context &ctx = get_default_context(); - s_resolved = s.value_or(ctx.symmetry()); - ps_resolved = ps.value_or(ctx.column_symmetry()); - h_resolved = herm_opt.value_or(ctx.hermiticity()); - } else { - s_resolved = *s; - ps_resolved = *ps; - // h_resolved is consulted below only to derive the braket symmetry, and - // only when no BraKetSymmetry was given. To reach this branch with no - // BraKetSymmetry, a Hermiticity must have been given, so herm_opt is set - // in exactly the case h_resolved is used; the value_or fallback is - // unused. - h_resolved = herm_opt.value_or(Hermiticity::NonHermitian); - } + const Symmetry s_resolved = s.value_or(Symmetry::Nonsymm); + const ColumnSymmetry ps_resolved = ps.value_or(ColumnSymmetry::Nonsymm); + const Hermiticity h_resolved = herm_opt.value_or(Hermiticity::NonHermitian); const BraKetSymmetry bks_resolved = bks_opt.has_value() ? *bks_opt : to_braket_symmetry(h_resolved, base_fld); @@ -359,7 +397,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { } // defaulting reserved-tag ctor (range form): resolves unspecified symmetries - // against the default Context, then delegates to the resolved terminal + // against fixed library defaults, then delegates to the resolved terminal template @@ -419,9 +457,14 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// to indices) /// @param ket_indices list of ket 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 - /// @param ps the symmetry under exchange of particles + /// @param s bra/ket permutational symmetry; if unset (`std::nullopt`), + /// defaults to Tensor::Defaults::symmetry (#Symmetry::Nonsymm) + /// @param bks_opt symmetry under bra-ket exchange; if unset (`std::nullopt`) + /// it is *derived* from the default Tensor::Defaults::hermiticity + /// (#Hermiticity::NonHermitian), i.e. #BraKetSymmetry::Nonsymm + /// @param ps particle- (column-) exchange symmetry; if unset + /// (`std::nullopt`), defaults to Tensor::Defaults::column_symmetry + /// (#ColumnSymmetry::Nonsymm) template Tensor(S &&label, const bra &bra_indices, @@ -441,9 +484,14 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// to indices) /// @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 - /// @param ps the symmetry under exchange of particles + /// @param s bra/ket permutational symmetry; if unset (`std::nullopt`), + /// defaults to Tensor::Defaults::symmetry (#Symmetry::Nonsymm) + /// @param bks_opt symmetry under bra-ket exchange; if unset (`std::nullopt`) + /// it is *derived* from the default Tensor::Defaults::hermiticity + /// (#Hermiticity::NonHermitian), i.e. #BraKetSymmetry::Nonsymm + /// @param ps particle- (column-) exchange symmetry; if unset + /// (`std::nullopt`), defaults to Tensor::Defaults::column_symmetry + /// (#ColumnSymmetry::Nonsymm) template @@ -463,9 +511,14 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// to indices) /// @param ket_indices list of ket 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 - /// @param ps the symmetry under exchange of particles + /// @param s bra/ket permutational symmetry; if unset (`std::nullopt`), + /// defaults to Tensor::Defaults::symmetry (#Symmetry::Nonsymm) + /// @param bks_opt symmetry under bra-ket exchange; if unset (`std::nullopt`) + /// it is *derived* from the default Tensor::Defaults::hermiticity + /// (#Hermiticity::NonHermitian), i.e. #BraKetSymmetry::Nonsymm + /// @param ps particle- (column-) exchange symmetry; if unset + /// (`std::nullopt`), defaults to Tensor::Defaults::column_symmetry + /// (#ColumnSymmetry::Nonsymm) template Tensor(S &&label, bra &&bra_indices, ket &&ket_indices, @@ -485,9 +538,14 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// to indices) /// @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 - /// @param ps the symmetry under exchange of particles + /// @param s bra/ket permutational symmetry; if unset (`std::nullopt`), + /// defaults to Tensor::Defaults::symmetry (#Symmetry::Nonsymm) + /// @param bks_opt symmetry under bra-ket exchange; if unset (`std::nullopt`) + /// it is *derived* from the default Tensor::Defaults::hermiticity + /// (#Hermiticity::NonHermitian), i.e. #BraKetSymmetry::Nonsymm + /// @param ps particle- (column-) exchange symmetry; if unset + /// (`std::nullopt`), defaults to Tensor::Defaults::column_symmetry + /// (#ColumnSymmetry::Nonsymm) template Tensor(S &&label, bra &&bra_indices, ket &&ket_indices, @@ -513,9 +571,12 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// @param label the tensor label /// @param bra_indices list of bra indices /// @param ket_indices list of ket indices - /// @param s the symmetry of bra or ket - /// @param h the abstract symmetry under (Hermitian) adjoint - /// @param ps the symmetry under exchange of particles + /// @param s bra/ket permutational symmetry + /// @param h the abstract symmetry under (Hermitian) adjoint; the observable + /// #BraKetSymmetry is derived from this and the tensor's #base_field + /// @param ps particle- (column-) exchange symmetry; if unset + /// (`std::nullopt`), defaults to Tensor::Defaults::column_symmetry + /// (#ColumnSymmetry::Nonsymm) template Tensor(S &&label, const bra &bra_indices, @@ -535,9 +596,12 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// @param bra_indices list of bra indices /// @param ket_indices list of ket indices /// @param aux_indices list of aux indices - /// @param s the symmetry of bra or ket - /// @param h the abstract symmetry under (Hermitian) adjoint - /// @param ps the symmetry under exchange of particles + /// @param s bra/ket permutational symmetry + /// @param h the abstract symmetry under (Hermitian) adjoint; the observable + /// #BraKetSymmetry is derived from this and the tensor's #base_field + /// @param ps particle- (column-) exchange symmetry; if unset + /// (`std::nullopt`), defaults to Tensor::Defaults::column_symmetry + /// (#ColumnSymmetry::Nonsymm) template @@ -557,6 +621,74 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// @} + /// @name named-parameter symmetry constructors + /// Specify the tensor's symmetries as a single TensorSymmetries pack (the + /// named-parameter idiom) rather than positional arguments; unset fields fall + /// back to the fixed library defaults. Convenient for reusing one symmetry + /// pack (e.g. a per-TU default) across many construction sites. + /// @{ + + /// @param label the tensor label + /// @param bra_indices list of bra indices + /// @param ket_indices list of ket indices + /// @param syms the (partially specified) symmetry pack + template + Tensor(S &&label, const bra &bra_indices, + const ket &ket_indices, TensorSymmetries syms) + : Tensor(std::forward(label), bra_indices, ket_indices, sequant::aux{}, + reserved_tag{}, syms.perm, syms.braket, syms.column, + syms.hermiticity) { + assert_nonreserved_label(label_); + } + + /// @param label the tensor label + /// @param bra_indices list of bra indices + /// @param ket_indices list of ket indices + /// @param aux_indices list of aux indices + /// @param syms the (partially specified) symmetry pack + template + Tensor(S &&label, const bra &bra_indices, + const ket &ket_indices, + const aux &aux_indices, TensorSymmetries syms) + : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, + reserved_tag{}, syms.perm, syms.braket, syms.column, + syms.hermiticity) { + assert_nonreserved_label(label_); + } + + /// @param label the tensor label + /// @param bra_indices list of bra indices + /// @param ket_indices list of ket indices + /// @param syms the (partially specified) symmetry pack + template + Tensor(S &&label, bra &&bra_indices, + ket &&ket_indices, TensorSymmetries syms) + : Tensor(std::forward(label), std::move(bra_indices), + std::move(ket_indices), sequant::aux{}, reserved_tag{}, + syms.perm, syms.braket, syms.column, syms.hermiticity) { + assert_nonreserved_label(label_); + } + + /// @param label the tensor label + /// @param bra_indices list of bra indices + /// @param ket_indices list of ket indices + /// @param aux_indices list of aux indices + /// @param syms the (partially specified) symmetry pack + template + Tensor(S &&label, bra &&bra_indices, + ket &&ket_indices, + aux &&aux_indices, TensorSymmetries syms) + : Tensor(std::forward(label), std::move(bra_indices), + std::move(ket_indices), std::move(aux_indices), reserved_tag{}, + syms.perm, syms.braket, syms.column, syms.hermiticity) { + assert_nonreserved_label(label_); + } + + /// @} + /// @return true if the Tensor is initialized explicit operator bool() const { return !label_.empty(); } @@ -1014,13 +1146,22 @@ static_assert(is_tensor, using TensorPtr = std::shared_ptr; inline ExprPtr make_overlap(const Index &bra_index, const Index &ket_index) { + // metric/overlap tensors are particle (column) symmetric by convention, and + // must compare equal to the same overlap parsed from text (which the + // deserializer builds Symm under an mbpt Context); the programmatic column + // default is Nonsymm, so request Symm explicitly. return ex(Tensor(reserved::overlap_label(), bra{bra_index}, - ket{ket_index}, aux{}, Tensor::reserved_tag{})); + ket{ket_index}, aux{}, Tensor::reserved_tag{}, + Symmetry::Nonsymm, std::nullopt, + ColumnSymmetry::Symm)); } inline ExprPtr make_kronecker(const Index &bra_index, const Index &ket_index) { + // see make_overlap: a Kronecker delta is likewise particle (column) symmetric return ex(Tensor(reserved::kronecker_label(), bra{bra_index}, - ket{ket_index}, aux{}, Tensor::reserved_tag{})); + ket{ket_index}, aux{}, Tensor::reserved_tag{}, + Symmetry::Nonsymm, std::nullopt, + ColumnSymmetry::Symm)); } } // namespace sequant diff --git a/SeQuant/domain/mbpt/antisymmetrizer.cpp b/SeQuant/domain/mbpt/antisymmetrizer.cpp index 130df98a1d..cb22278ddf 100644 --- a/SeQuant/domain/mbpt/antisymmetrizer.cpp +++ b/SeQuant/domain/mbpt/antisymmetrizer.cpp @@ -85,8 +85,11 @@ antisymm_element::antisymm_element(ExprPtr ex_) { new_kets.push_back(unique_kets_list[j].second[index_label_pos]); index_label_pos++; } - auto new_tensor = ex(label, bra(std::move(new_bras)), - ket(std::move(new_kets))); + // mbpt tensors are particle (column) symmetric; the programmatic + // Tensor default is column-Nonsymm, so request Symm explicitly + auto new_tensor = ex( + label, bra(std::move(new_bras)), ket(std::move(new_kets)), + Symmetry::Nonsymm, std::nullopt, ColumnSymmetry::Symm); new_product = new_tensor * new_product; new_product->canonicalize(); } @@ -344,9 +347,11 @@ ExprPtr max_similarity(const std::vector& original_upper, } } if (new_pairs > og_pairs) { - factor = ex(-1) * ex(factor->as().label(), - bra(std::move(current_lower)), - ket(std::move(current_upper))); + factor = ex(-1) * + ex(factor->as().label(), + bra(std::move(current_lower)), + ket(std::move(current_upper)), Symmetry::Nonsymm, + std::nullopt, ColumnSymmetry::Symm); } } else if (factor->is()) { std::vector current_upper; @@ -422,7 +427,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(), Symmetry::Nonsymm, + std::nullopt, ColumnSymmetry::Symm); } else if (factor->is()) { // prefactor = ex(-0.5) * // ex(factor->as().rank()) * prefactor; diff --git a/SeQuant/domain/mbpt/biorthogonalization.cpp b/SeQuant/domain/mbpt/biorthogonalization.cpp index e68850e74c..0cae180a89 100644 --- a/SeQuant/domain/mbpt/biorthogonalization.cpp +++ b/SeQuant/domain/mbpt/biorthogonalization.cpp @@ -651,8 +651,11 @@ ExprPtr biorthogonal_transform_pre_nnsproject_impl( auto bixs = ext_idxs | transform([](auto&& vec) { return get_bra_idx(vec); }); auto kixs = ext_idxs | transform([](auto&& vec) { return get_ket_idx(vec); }); + // the reserved symmetrizer Ŝ is column (particle) symmetric; request Symm + // explicitly since the programmatic Tensor default is column-Nonsymm ExprPtr S_tensor = - ex(Tensor{reserved::symm_label(), bra(kixs), ket(bixs)}); + ex(Tensor{reserved::symm_label(), bra(kixs), ket(bixs), + Symmetry::Nonsymm, std::nullopt, ColumnSymmetry::Symm}); if (factor_out_nns_projector) { if (ext_idxs.size() > 1) { diff --git a/SeQuant/domain/mbpt/op.cpp b/SeQuant/domain/mbpt/op.cpp index c69c20ece5..e416a2e280 100644 --- a/SeQuant/domain/mbpt/op.cpp +++ b/SeQuant/domain/mbpt/op.cpp @@ -609,9 +609,13 @@ ExprPtr OpMaker::operator()(std::optional dep, [this, opsymm_opt, full_label, op_herm]( const auto& creidxs, const auto& annidxs, const auto& batchidxs, Symmetry opsymm) { + // mbpt operators act on indistinguishable particles, so they are + // particle (column) symmetric; the programmatic Tensor default is + // Nonsymm, so request Symm explicitly (harmless/redundant when opsymm + // is (Anti)symm, which already implies column symmetry). return ex(full_label, bra(creidxs), ket(annidxs), aux(batchidxs), opsymm_opt ? *opsymm_opt : opsymm, - op_herm); + op_herm, ColumnSymmetry::Symm); }, dep ? *dep : UseDepIdx::None, normalization); } @@ -620,8 +624,12 @@ ExprPtr OpMaker::operator()(std::optional dep, cre_spaces_, ann_spaces_, [this, opsymm_opt, full_label, op_herm]( const auto& creidxs, const auto& annidxs, Symmetry opsymm) { + // mbpt operators act on indistinguishable particles, so they are + // particle (column) symmetric; request Symm explicitly (the + // programmatic Tensor default is Nonsymm). return ex(full_label, bra(creidxs), ket(annidxs), - opsymm_opt ? *opsymm_opt : opsymm, op_herm); + opsymm_opt ? *opsymm_opt : opsymm, op_herm, + ColumnSymmetry::Symm); }, dep ? *dep : UseDepIdx::None, normalization); } @@ -703,11 +711,15 @@ ExprPtr F(bool use_tensor, const IndexSpace& reference_occupied) { auto ketidxs_K = ketidxs; using std::begin; ketidxs_K.emplace(begin(ketidxs_K), m2); + // g is a 2-particle integral over indistinguishable particles -> + // particle (column) symmetric; the Antisymm branch above gets + // this implicitly, but Nonsymm perm does not, so request it. return (ex(L"g", bra(std::move(braidx_J)), - ket(std::move(ketidxs_J)), Symmetry::Nonsymm) - + ket(std::move(ketidxs_J)), Symmetry::Nonsymm, + std::nullopt, ColumnSymmetry::Symm) - ex(L"g", bra(std::move(braidx_K)), - ket(std::move(ketidxs_K)), - Symmetry::Nonsymm)) * + ket(std::move(ketidxs_K)), Symmetry::Nonsymm, + std::nullopt, ColumnSymmetry::Symm)) * ex(kronecker_label(), bra{m2}, ket{m1}, Symmetry::Nonsymm); } @@ -1344,9 +1356,13 @@ ExprPtr expectation_value_impl(ExprPtr expr, OpConnections connect, braidxs.size() == ketidxs.size()); // need to handle particle # violating case? const auto rank = braidxs.size(); + // an RDM over indistinguishable particles is particle (column) + // symmetric; the Antisymm (spinor) branch implies it, the Nonsymm + // (spin-free) branch needs it requested explicitly. return ex( rdm_label, bra(std::move(braidxs)), ket(std::move(ketidxs)), - rank > 1 && spinor ? Symmetry::Antisymm : Symmetry::Nonsymm); + rank > 1 && spinor ? Symmetry::Antisymm : Symmetry::Nonsymm, + std::nullopt, ColumnSymmetry::Symm); }; if (exptr.template is()) { diff --git a/SeQuant/domain/mbpt/rules/df.cpp b/SeQuant/domain/mbpt/rules/df.cpp index e32f99dab4..b7186aaa56 100644 --- a/SeQuant/domain/mbpt/rules/df.cpp +++ b/SeQuant/domain/mbpt/rules/df.cpp @@ -31,20 +31,24 @@ ExprPtr density_fit_impl(Tensor const& tnsr, Index const& aux_idx, // when the Tensor is built. auto t1 = ex(factor_label, bra({ranges::front(tnsr.bra())}), ket({ranges::front(tnsr.ket())}), aux({aux_idx}), - Symmetry::Nonsymm, Hermiticity::Hermitian); + Symmetry::Nonsymm, Hermiticity::Hermitian, + ColumnSymmetry::Symm); auto t2 = ex(factor_label, bra({ranges::back(tnsr.bra())}), ket({ranges::back(tnsr.ket())}), aux({aux_idx}), - Symmetry::Nonsymm, Hermiticity::Hermitian); + Symmetry::Nonsymm, Hermiticity::Hermitian, + ColumnSymmetry::Symm); if (tnsr.symmetry() == Symmetry::Antisymm) { auto t3 = ex(factor_label, bra({ranges::back(tnsr.bra())}), ket({ranges::front(tnsr.ket())}), aux({aux_idx}), - Symmetry::Nonsymm, Hermiticity::Hermitian); + Symmetry::Nonsymm, Hermiticity::Hermitian, + ColumnSymmetry::Symm); auto t4 = ex(factor_label, bra({ranges::front(tnsr.bra())}), ket({ranges::back(tnsr.ket())}), aux({aux_idx}), - Symmetry::Nonsymm, Hermiticity::Hermitian); + Symmetry::Nonsymm, Hermiticity::Hermitian, + ColumnSymmetry::Symm); return t1 * t2 - t3 * t4; } diff --git a/SeQuant/domain/mbpt/rules/thc.cpp b/SeQuant/domain/mbpt/rules/thc.cpp index 7fcfbf5978..e2b669b7b8 100644 --- a/SeQuant/domain/mbpt/rules/thc.cpp +++ b/SeQuant/domain/mbpt/rules/thc.cpp @@ -15,6 +15,14 @@ namespace sequant::mbpt { +// THC/DF factor and central tensors live in the particle-indistinguishable +// MBPT domain, so they are particle- (column-) symmetric; programmatic Tensor +// construction is Context-independent (see Tensor::Defaults), so spell that +// default out here and feed it at every factor construction site. +namespace { +constexpr TensorSymmetries ps{.column = ColumnSymmetry::Symm}; +} // namespace + ExprPtr tensor_hypercontract_impl(Tensor const& tnsr, Index const& aux_idx_1, Index const& aux_idx_2, std::wstring_view factor_label, @@ -24,20 +32,20 @@ ExprPtr tensor_hypercontract_impl(Tensor const& tnsr, Index const& aux_idx_1, && tnsr.aux_rank() == 0); auto t1 = ex(factor_label, bra({ranges::front(tnsr.bra())}), ket(), - aux({aux_idx_1})); + aux({aux_idx_1}), ps); auto t2 = ex(factor_label, bra(), ket({ranges::front(tnsr.ket())}), - aux({aux_idx_1})); + aux({aux_idx_1}), ps); auto t3 = ex(factor_label, bra({ranges::back(tnsr.bra())}), ket(), - aux({aux_idx_2})); + aux({aux_idx_2}), ps); auto t4 = ex(factor_label, bra(), ket({ranges::back(tnsr.ket())}), - aux({aux_idx_2})); - auto z = ex(aux_label, bra(), ket(), aux({aux_idx_1, aux_idx_2})); + aux({aux_idx_2}), ps); + auto z = ex(aux_label, bra(), ket(), aux({aux_idx_1, aux_idx_2}), ps); if (tnsr.symmetry() == Symmetry::Antisymm) { auto t1a = ex(factor_label, bra({ranges::back(tnsr.bra())}), ket(), - aux({aux_idx_1})); + aux({aux_idx_1}), ps); auto t3a = ex(factor_label, bra({ranges::front(tnsr.bra())}), ket(), - aux({aux_idx_2})); + aux({aux_idx_2}), ps); return (t1 * t2 * z * t3 * t4) - (t1a * t2 * z * t3a * t4); } diff --git a/SeQuant/domain/mbpt/spin.cpp b/SeQuant/domain/mbpt/spin.cpp index a182e40374..197b2885d7 100644 --- a/SeQuant/domain/mbpt/spin.cpp +++ b/SeQuant/domain/mbpt/spin.cpp @@ -324,8 +324,8 @@ ExprPtr remove_spin(const ExprPtr& expr) { } } return ex(tensor.label(), bra(std::move(b)), ket(std::move(k)), - tensor.aux(), tensor.symmetry(), - tensor.braket_symmetry()); + tensor.aux(), tensor.symmetry(), tensor.braket_symmetry(), + tensor.column_symmetry()); }; auto remove_spin_from_product = @@ -632,8 +632,13 @@ ExprPtr symmetrize_expr(const ProductPtr& product) { auto S = Tensor{}; if (A_is_nconserving) { + // the symmetrizer Ŝ symmetrizes the {bra,ket} particle columns: it is + // column-symmetric (with Nonsymm bra/ket permutational symmetry). The + // programmatic Tensor default is column-Nonsymm, so request Symm explicitly + // (matching the deserializer, which forces Symm for the reserved Ŝ). S = Tensor(reserved::symm_label(), A_tensor.bra(), A_tensor.ket(), - A_tensor.aux(), Symmetry::Nonsymm); + A_tensor.aux(), Symmetry::Nonsymm, std::nullopt, + ColumnSymmetry::Symm); } 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(), @@ -641,7 +646,8 @@ ExprPtr symmetrize_expr(const ProductPtr& product) { container::svector ket_list(A_tensor.ket().begin(), A_tensor.ket().begin() + n); S = Tensor(reserved::symm_label(), bra(std::move(bra_list)), - ket(std::move(ket_list)), A_tensor.aux(), Symmetry::Nonsymm); + ket(std::move(ket_list)), A_tensor.aux(), Symmetry::Nonsymm, + std::nullopt, ColumnSymmetry::Symm); } const auto nf = rational{1, factorial(S.ket_rank())}; @@ -1231,7 +1237,10 @@ ExprPtr merge_tensors(const Tensor& O1, const Tensor& O2) { auto b = ranges::views::concat(O1.bra(), O2.bra()); auto k = ranges::views::concat(O1.ket(), O2.ket()); auto a = ranges::views::concat(O1.aux(), O2.aux()); - return ex(Tensor(O1.label(), bra(b), ket(k), aux(a), O1.symmetry())); + // preserve all of O1's symmetry attributes (incl. column symmetry, which the + // programmatic Tensor default would otherwise reset to Nonsymm) + return ex(Tensor(O1.label(), bra(b), ket(k), aux(a), O1.symmetry(), + O1.braket_symmetry(), O1.column_symmetry())); } std::vector open_shell_A_op(const Tensor& A) { diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 3bbb004d4a..7d9ebfc366 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -28,6 +28,15 @@ #include #include +namespace { +/// this TU canonicalizes MBPT tensors, whose particles are indistinguishable, +/// hence they are particle- (column-) symmetric; programmatic Tensor +/// construction is Context-independent (see Tensor::Defaults), so spell that +/// default out once here and feed it at every construction site that must match +/// parsed references +constexpr sequant::TensorSymmetries ps{.column = sequant::ColumnSymmetry::Symm}; +} // namespace + TEST_CASE("canonicalization", "[algorithms]") { using namespace sequant; @@ -41,26 +50,22 @@ TEST_CASE("canonicalization", "[algorithms]") { SECTION("Tensors") { { - auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, - Symmetry::Nonsymm); + auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, ps); canonicalize(op); 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); + auto op = ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_3", L"p_4"}, ps); canonicalize(op); 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); + auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, ps); canonicalize(op); 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); + auto op = ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, ps); canonicalize(op); REQUIRE_THAT(op, SimplifiesTo("g{p1,p2;p3,p4}")); } @@ -79,14 +84,13 @@ TEST_CASE("canonicalization", "[algorithms]") { // aux indices { - auto op = ex(L"B", bra{L"p_1"}, ket{L"p_2"}, aux{L"p_3"}, - Symmetry::Nonsymm); + auto op = ex(L"B", bra{L"p_1"}, ket{L"p_2"}, aux{L"p_3"}, ps); canonicalize(op); 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); + aux{L"p_5"}, ps); canonicalize(op); REQUIRE_THAT(op, SimplifiesTo("B{p1,p2;p4,p3;p5}")); } @@ -113,11 +117,10 @@ TEST_CASE("canonicalization", "[algorithms]") { { auto input = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, Symmetry::Nonsymm) * - ex(L"f", bra{L"a_5"}, ket{L"i_5"}, Symmetry::Nonsymm) * - ex(L"t", bra{L"i_5"}, ket{L"a_1"}, Symmetry::Nonsymm) * - ex(L"t", bra{L"i_1", L"i_2"}, ket{L"a_5", L"a_2"}, - Symmetry::Nonsymm); + ket{L"i_1", L"i_2"}, ps) * + ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * + ex(L"t", bra{L"i_5"}, ket{L"a_1"}, ps) * + ex(L"t", bra{L"i_1", L"i_2"}, ket{L"a_5", L"a_2"}, ps); canonicalize(input); REQUIRE_THAT( input, @@ -126,11 +129,10 @@ TEST_CASE("canonicalization", "[algorithms]") { { auto input = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, Symmetry::Nonsymm) * - 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) * - ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, - Symmetry::Nonsymm); + ket{L"i_1", L"i_2"}, ps) * + ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * + ex(L"t", bra{L"i_1"}, ket{L"a_5"}, ps) * + ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, ps); canonicalize(input); REQUIRE_THAT( input, @@ -176,13 +178,11 @@ TEST_CASE("canonicalization", "[algorithms]") { q2->adjoint(); auto input = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, Symmetry::Nonsymm) * - q2 * ex(L"f", bra{L"a_5"}, ket{L"i_5"}, Symmetry::Nonsymm) * - ex(L"p") * - ex(L"t", bra{L"i_1"}, ket{L"a_5"}, Symmetry::Nonsymm) * + ket{L"i_1", L"i_2"}, ps) * + q2 * ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * + ex(L"p") * ex(L"t", bra{L"i_1"}, ket{L"a_5"}, ps) * ex(L"q1") * - ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, - Symmetry::Nonsymm); + ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, ps); canonicalize(input); REQUIRE_THAT(input, SimplifiesTo("p q1 q2^* Ŝ{a_1,a_2;i_1,i_2} f{a_3;i_3} " @@ -190,23 +190,22 @@ TEST_CASE("canonicalization", "[algorithms]") { } { // Product containing adjoint of a Tensor auto f2 = ex(L"f", bra{L"a_1", L"a_2"}, ket{L"i_5", L"i_2"}, - Symmetry::Nonsymm, BraKetSymmetry::Nonsymm); + Symmetry::Nonsymm, BraKetSymmetry::Nonsymm, + ColumnSymmetry::Symm); f2->adjoint(); - auto input1 = - ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, Symmetry::Nonsymm) * - 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; + auto input1 = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, + ket{L"i_1", L"i_2"}, ps) * + ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * + ex(L"t", bra{L"i_1"}, ket{L"a_5"}, ps) * f2; canonicalize(input1); REQUIRE_THAT(input1, SimplifiesTo("Ŝ{a_1,a_2;i_1,i_2} f{a_3;i_3} " "f⁺{i_1,i_3;a_1,a_2}:N-N-S t{i_2;a_3}")); - auto input2 = - ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, Symmetry::Nonsymm) * - 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 * - ex(L"w") * ex(rational{1, 2}); + auto input2 = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, + ket{L"i_1", L"i_2"}, ps) * + ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * + ex(L"t", bra{L"i_1"}, ket{L"a_5"}, ps) * f2 * + ex(L"w") * ex(rational{1, 2}); canonicalize(input2); REQUIRE_THAT(input2, SimplifiesTo("1/2 w Ŝ{a_1,a_2;i_1,i_2} f{a_3;i_3} " @@ -214,14 +213,11 @@ TEST_CASE("canonicalization", "[algorithms]") { } // with aux indices { - 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); + auto input = ex(rational{1, 2}) * + ex(L"B", bra{L"p_2"}, ket{L"p_4"}, aux{L"p_5"}, ps) * + ex(L"B", bra{L"p_1"}, ket{L"p_3"}, aux{L"p_5"}, ps) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps); canonicalize(input); // because bra and ket are in same space dummy renaming flips the bra and // ket even though the tensors are not bra-ket symmetric @@ -238,9 +234,11 @@ TEST_CASE("canonicalization", "[algorithms]") { // whether bra/ket swap should occur for each tensor auto input = ex(rational{1, 2}) * ex(L"B", bra{L"p_2"}, ket{L"p_1"}, aux{L"p_5"}, - Symmetry::Nonsymm, BraKetSymmetry::Symm) * + Symmetry::Nonsymm, BraKetSymmetry::Symm, + ColumnSymmetry::Symm) * ex(L"B", bra{L"p_1"}, ket{L"p_2"}, aux{L"p_5"}, - Symmetry::Nonsymm, BraKetSymmetry::Symm); + Symmetry::Nonsymm, BraKetSymmetry::Symm, + ColumnSymmetry::Symm); REQUIRE_THAT(input, EquivalentTo("1/2 B{p1;p2;p5}:N-S B{p1;p2;p5}:N-S")); } // SF R2 ±pair extracted from the real-field CCSD doubles. Under @@ -475,15 +473,13 @@ TEST_CASE("canonicalization", "[algorithms]") { // CASE 1: Non-symmetric tensors auto input = ex(rational{1, 2}) * - ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, - 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) + + ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, ps) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps) + ex(rational{1, 2}) * - ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, - 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); + ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, ps) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps); simplify(input); canonicalize(input); REQUIRE_THAT(input, @@ -492,53 +488,50 @@ TEST_CASE("canonicalization", "[algorithms]") { // CASE 2: Symmetric tensors { - auto input = - ex(rational{1, 2}) * - ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, - Symmetry::Symm) * - 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) + - ex(rational{1, 2}) * - ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, - Symmetry::Symm) * - 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); + auto input = ex(rational{1, 2}) * + ex(L"g", bra{L"p_1", L"p_2"}, + ket{L"p_3", L"p_4"}, Symmetry::Symm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps) + + ex(rational{1, 2}) * + ex(L"g", bra{L"p_2", L"p_1"}, + ket{L"p_4", L"p_3"}, Symmetry::Symm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps); canonicalize(input); REQUIRE_THAT(input, EquivalentTo("g{p2,p3;p1,p4}:S t{p1;p2} t{p4;p3}")); } // Case 3: Anti-symmetric tensors { - auto input = - ex(rational{1, 2}) * - ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, - Symmetry::Antisymm) * - 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) + - ex(rational{1, 2}) * - ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, - Symmetry::Antisymm) * - 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); + auto input = ex(rational{1, 2}) * + ex(L"g", bra{L"p_1", L"p_2"}, + ket{L"p_3", L"p_4"}, Symmetry::Antisymm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps) + + ex(rational{1, 2}) * + ex(L"g", bra{L"p_2", L"p_1"}, + ket{L"p_4", L"p_3"}, Symmetry::Antisymm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps); canonicalize(input); REQUIRE_THAT(input, EquivalentTo("g{p2,p3;p1,p4}:A t{p1;p2} t{p4;p3}")); } // Case 4: permuted indices { - auto input = - ex(rational{4, 3}) * - ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"i_1"}, - Symmetry::Antisymm) * - 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::Antisymm) - - ex(rational{1, 3}) * - ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, - Symmetry::Antisymm) * - 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::Antisymm); + auto input = ex(rational{4, 3}) * + ex(L"g", bra{L"i_3", L"i_4"}, + ket{L"a_3", L"i_1"}, Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, ps) * + ex(L"t", bra{L"a_1", L"a_3"}, + ket{L"i_4", L"i_2"}, Symmetry::Antisymm) - + ex(rational{1, 3}) * + ex(L"g", bra{L"i_3", L"i_4"}, + ket{L"i_1", L"a_3"}, Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * + ex(L"t", bra{L"a_1", L"a_3"}, + ket{L"i_3", L"i_2"}, Symmetry::Antisymm); canonicalize(input); REQUIRE(input->size() == 1); REQUIRE_THAT(input, @@ -549,17 +542,13 @@ TEST_CASE("canonicalization", "[algorithms]") { { auto input = ex(rational{4, 3}) * - ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"i_1"}, - 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(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"i_1"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, ps) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_4", L"i_2"}, ps) - ex(rational{1, 3}) * - ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, - 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); + ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_3", L"i_2"}, ps); canonicalize(input); REQUIRE(input->size() == 1); @@ -573,16 +562,16 @@ TEST_CASE("canonicalization", "[algorithms]") { auto input = ex(-4) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Nonsymm) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}) * + ket{L"a_1", L"a_2", L"a_3"}, ps) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_3", L"i_2", L"i_4"}, Symmetry::Nonsymm) + + ket{L"i_3", L"i_2", L"i_4"}, ps) + ex(-4) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Nonsymm) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}) * + ket{L"a_1", L"a_2", L"a_3"}, ps) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * 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); + ket{L"i_2", L"i_4", L"i_3"}, ps); canonicalize(input); REQUIRE_THAT( input, @@ -594,17 +583,17 @@ TEST_CASE("canonicalization", "[algorithms]") { auto term1 = ex(-4) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Nonsymm) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}) * + ket{L"a_1", L"a_2", L"a_3"}, ps) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_3", L"i_2", L"i_4"}, Symmetry::Nonsymm); + ket{L"i_3", L"i_2", L"i_4"}, ps); auto term2 = ex(-4) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Nonsymm) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}) * + ket{L"a_1", L"a_2", L"a_3"}, ps) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * 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); + ket{L"i_2", L"i_4", L"i_3"}, ps); canonicalize(term1); canonicalize(term2); REQUIRE_THAT(term1, @@ -625,16 +614,16 @@ TEST_CASE("canonicalization", "[algorithms]") { auto input = ex(2) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Nonsymm) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}) * + ket{L"a_1", L"a_2", L"a_3"}, ps) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_3", L"i_4", L"i_2"}, Symmetry::Nonsymm) + + ket{L"i_3", L"i_4", L"i_2"}, ps) + ex(2) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Nonsymm) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}) * + ket{L"a_1", L"a_2", L"a_3"}, ps) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * 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); + ket{L"i_2", L"i_3", L"i_4"}, ps); canonicalize(input); REQUIRE_THAT( input, EquivalentTo( @@ -646,21 +635,15 @@ TEST_CASE("canonicalization", "[algorithms]") { { 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(L"B", bra{L"i_3"}, ket{L"a_3"}, aux{L"p_5"}, ps) * + ex(L"B", bra{L"i_4"}, ket{L"i_1"}, aux{L"p_5"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, ps) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_4", L"i_2"}, ps) - 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); + ex(L"B", bra{L"i_3"}, ket{L"i_1"}, aux{L"p_5"}, ps) * + ex(L"B", bra{L"i_4"}, ket{L"a_3"}, aux{L"p_5"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_3", L"i_2"}, ps); canonicalize(input); simplify(input); diff --git a/tests/unit/test_eval_expr.cpp b/tests/unit/test_eval_expr.cpp index 4d8339404f..d0002e8677 100644 --- a/tests/unit/test_eval_expr.cpp +++ b/tests/unit/test_eval_expr.cpp @@ -192,9 +192,12 @@ TEST_CASE("eval_expr", "[EvalExpr]") { res = deserialize(L"Amplitude{i1;a1} = t{a1;i1}"); root_expr = binarize(res)->expr(); REQUIRE(root_expr.is()); - REQUIRE(root_expr.as() == Tensor(L"Amplitude", - bra(IndexList{L"i_1"}), - ket(IndexList{L"a_1"}))); + // the deserialized ResultExpr's Amplitude picks up the Context's column + // symmetry (Symm), so the programmatic reference must request it too -- + // programmatic ctors are Context-independent (see Tensor::Defaults) + REQUIRE(root_expr.as() == + Tensor(L"Amplitude", bra(IndexList{L"i_1"}), ket(IndexList{L"a_1"}), + TensorSymmetries{.column = ColumnSymmetry::Symm})); } SECTION("Adjoint op") { diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index e830d7203d..e0f3cec742 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -89,6 +89,30 @@ TEST_CASE("serialization", "[serialization]") { ctx.set(mbpt::make_sr_spaces()); auto ctx_resetter = set_scoped_default_context(ctx); + SECTION("default symmetries follow the Context") { + // the deserializer sources unspecified tensor symmetries from the active + // Context (unlike the programmatic Tensor ctor, which uses fixed + // defaults -- see test_tensor "programmatic ctor defaults are + // Context-independent"). Flip the Context column-symmetry default and + // confirm parsed tensors follow it. + auto ctx_symm = get_default_context(); + ctx_symm.set(ColumnSymmetry::Symm); + { + auto resetter = set_scoped_default_context(ctx_symm); + REQUIRE(deserialize(L"t{i1,i2;a1,a2}") + ->as() + .column_symmetry() == ColumnSymmetry::Symm); + } + auto ctx_nonsymm = get_default_context(); + ctx_nonsymm.set(ColumnSymmetry::Nonsymm); + { + auto resetter = set_scoped_default_context(ctx_nonsymm); + REQUIRE(deserialize(L"t{i1,i2;a1,a2}") + ->as() + .column_symmetry() == ColumnSymmetry::Nonsymm); + } + } + SECTION("Scalar tensor") { auto expr = deserialize(L"t{}"); REQUIRE(expr->is()); diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index 4402c82c2a..b1d70f6079 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -33,6 +33,14 @@ #include #include +namespace { +/// this TU exercises MBPT tensors, whose particles are indistinguishable, hence +/// they are particle- (column-) symmetric; programmatic Tensor construction is +/// Context-independent (see Tensor::Defaults), so spell that default out once +/// here and feed it at every construction site that must match parsed refs +constexpr sequant::TensorSymmetries ps{.column = sequant::ColumnSymmetry::Symm}; +} // namespace + TEST_CASE("spin", "[spin]") { using namespace sequant; using namespace sequant::mbpt; @@ -61,8 +69,8 @@ TEST_CASE("spin", "[spin]") { Index i1(L"i_1"); Index a1(L"a_1", {i1}); - const auto expr = - ex(L"t", bra{i1}, ket{a1}) * ex(L"F", bra{a1}, ket{i1}); + const auto expr = ex(L"t", bra{i1}, ket{a1}, ps) * + ex(L"F", bra{a1}, ket{i1}, ps); REQUIRE_NOTHROW(spintrace(expr)); { // assume spin-free spaces auto expr_st = spintrace(expr); @@ -195,7 +203,7 @@ TEST_CASE("spin", "[spin]") { auto p3 = Index(L"p↑_3"); auto p4 = Index(L"p↓_4"); - auto input = ex(L"t", bra{p1, p2}, ket{p3, p4}); + auto input = ex(L"t", bra{p1, p2}, ket{p3, p4}, ps); REQUIRE(can_expand(input->as()) == true); REQUIRE(ms_conserving_columns(input->as()) == true); @@ -206,7 +214,7 @@ TEST_CASE("spin", "[spin]") { for (auto& i : result->as().const_braket_indices()) REQUIRE(i.space().base_key() == L"p"); - input = ex(L"t", bra{p1, p3}, ket{p2, p4}); + input = ex(L"t", bra{p1, p3}, ket{p2, p4}, ps); REQUIRE_THAT(swap_spin(input), EquivalentTo("t{p↓1,p↓3;p↑2,p↑4}")); REQUIRE(can_expand(input->as()) == false); REQUIRE(ms_conserving_columns(input->as()) == false); @@ -214,7 +222,7 @@ TEST_CASE("spin", "[spin]") { SECTION("Tensor: expand_antisymm") { // 1-body - auto input = ex(L"t", bra{L"a_1"}, ket{L"i_1"}); + auto input = ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps); auto result = expand_antisymm(input->as()); REQUIRE(input->as() == result->as()); REQUIRE(!result->is()); @@ -302,8 +310,8 @@ TEST_CASE("spin", "[spin]") { } SECTION("Product") { - const auto expr = ex(L"f", bra{L"i_1"}, ket{L"a_1"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_1"}); + const auto expr = ex(L"f", bra{L"i_1"}, ket{L"a_1"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps); auto result = spintrace(expr); canonicalize(result); REQUIRE_THAT(result, EquivalentTo("2 f{i1;a1} t{a1;i1}")); @@ -315,8 +323,8 @@ TEST_CASE("spin", "[spin]") { const auto expr = ex(rational{1, 2}) * ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_1"}, ket{L"i_1"}) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}); + ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps); auto result = spintrace(expr); canonicalize(result); REQUIRE_THAT(result, @@ -349,13 +357,13 @@ TEST_CASE("spin", "[spin]") { SECTION("Sum") { // f * t1 + 1/2 * g * t1 * t1 + 1/4 * g * t2 - const auto ex1 = ex(L"f", bra{L"i_1"}, ket{L"a_1"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_1"}); + const auto ex1 = ex(L"f", bra{L"i_1"}, ket{L"a_1"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps); const auto ex2 = ex(rational{1, 2}) * ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_1"}, ket{L"i_1"}) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}); + ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps); const auto ex3 = ex(rational{1, 4}) * ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, Symmetry::Antisymm) * @@ -421,8 +429,8 @@ TEST_CASE("spin", "[spin]") { Symmetry::Antisymm) * ex(L"g", bra{L"a_1", L"a_2"}, ket{L"a_3", L"a_4"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}) * - ex(L"t", bra{L"a_4"}, ket{L"i_2"}); + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps) * + ex(L"t", bra{L"a_4"}, ket{L"i_2"}, ps); result = expand_A_op(input); REQUIRE(result->is()); REQUIRE(result->size() == 4); @@ -437,10 +445,10 @@ TEST_CASE("spin", "[spin]") { Symmetry::Antisymm) * ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"a_4"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}) * - ex(L"t", bra{L"a_4"}, ket{L"i_2"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_3"}) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}); + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps) * + ex(L"t", bra{L"a_4"}, ket{L"i_2"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_3"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps); result = expand_A_op(input); REQUIRE_THAT( result, @@ -559,12 +567,11 @@ SECTION("Expand Symmetrizer") { const auto input = ex(symm_label(), bra{L"i_1", L"i_2", L"i_3"}, ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Nonsymm) * - ex(L"g", bra{L"i_4", L"i_5"}, ket{L"a_4", L"a_5"}, - Symmetry::Nonsymm) * - ex(L"t", bra{L"a_3"}, ket{L"i_4"}) * - ex(L"t", bra{L"a_5"}, ket{L"i_1"}) * - ex(L"t", bra{L"a_4"}, ket{L"i_2"}) * - ex(L"t", bra{L"a_1", L"a_2"}, ket{L"i_5", L"i_3"}); + ex(L"g", bra{L"i_4", L"i_5"}, ket{L"a_4", L"a_5"}, ps) * + ex(L"t", bra{L"a_3"}, ket{L"i_4"}, ps) * + ex(L"t", bra{L"a_5"}, ket{L"i_1"}, ps) * + ex(L"t", bra{L"a_4"}, ket{L"i_2"}, ps) * + ex(L"t", bra{L"a_1", L"a_2"}, ket{L"i_5", L"i_3"}, ps); auto result = S_maps(input); REQUIRE(result->is()); REQUIRE(result->size() == 6); @@ -637,10 +644,10 @@ SECTION("Symmetrize expression") { { // g * t1 + g * t1 auto input = ex(L"g", bra{L"a_1", L"a_2"}, ket{L"i_1", L"a_3"}, Symmetry::Symm) * - ex(L"t", bra{L"a_3"}, ket{L"i_2"}) + + ex(L"t", bra{L"a_3"}, ket{L"i_2"}, ps) + ex(L"g", bra{L"a_2", L"a_1"}, ket{L"i_2", L"a_3"}, Symmetry::Symm) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}); + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps); auto const ext_idxs = external_indices(input); auto bixs = ext_idxs | ranges::views::transform( @@ -648,7 +655,7 @@ SECTION("Symmetrize expression") { auto kixs = ext_idxs | ranges::views::transform( [](auto&& vec) { return get_ket_idx(vec); }); auto result = ex(Tensor{symm_label(), bra(std::move(kixs)), - ket(std::move(bixs))}) * + ket(std::move(bixs)), ps}) * input; REQUIRE_THAT(result, EquivalentTo("2 Ŝ{i1,i2;a1,a2} g{a1,a2;i2,a3}:S t{a3;i1}")); @@ -664,7 +671,7 @@ SECTION("Symmetrize expression") { auto kixs = ext_idxs | ranges::views::transform( [](auto&& vec) { return get_ket_idx(vec); }); auto result = ex(Tensor{symm_label(), bra(std::move(kixs)), - ket(std::move(bixs))}) * + ket(std::move(bixs)), ps}) * input; REQUIRE_THAT(result, EquivalentTo("Ŝ{i1,i2;a1,a2} * g{a1,a2;i1,i2}:S")); } @@ -672,14 +679,14 @@ SECTION("Symmetrize expression") { { // g * t1 * t1 * t1 + g * t1 * t1 * t1 auto input = ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, Symmetry::Symm) * - ex(L"t", bra{L"a_1"}, ket{L"i_3"}) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}) * - ex(L"t", bra{L"a_3"}, ket{L"i_2"}) + + ex(L"t", bra{L"a_1"}, ket{L"i_3"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * + ex(L"t", bra{L"a_3"}, ket{L"i_2"}, ps) + ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_2", L"a_3"}, Symmetry::Symm) * - ex(L"t", bra{L"a_2"}, ket{L"i_3"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_4"}) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}); + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_4"}, ps) * + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps); auto const ext_idxs = external_indices(input); auto bixs = ext_idxs | ranges::views::transform( @@ -688,7 +695,7 @@ SECTION("Symmetrize expression") { [](auto&& vec) { return get_ket_idx(vec); }); auto result = ex(Tensor{reserved::symm_label(), bra(std::move(kixs)), - ket(std::move(bixs))}) * + ket(std::move(bixs)), ps}) * input; REQUIRE_THAT( result, @@ -701,15 +708,15 @@ SECTION("Symmetrize expression") { ex(2) * ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"a_4"}, Symmetry::Symm) * - ex(L"t", bra{L"a_3"}, ket{L"i_3"}) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}) * - ex(L"t", bra{L"a_1", L"a_4"}, ket{L"i_1", L"i_2"}) + + ex(L"t", bra{L"a_3"}, ket{L"i_3"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * + ex(L"t", bra{L"a_1", L"a_4"}, ket{L"i_1", L"i_2"}, ps) + ex(2) * ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"a_4"}, Symmetry::Symm) * - ex(L"t", bra{L"a_3"}, ket{L"i_3"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_4"}) * - ex(L"t", bra{L"a_2", L"a_4"}, ket{L"i_2", L"i_1"}); + ex(L"t", bra{L"a_3"}, ket{L"i_3"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_4"}, ps) * + ex(L"t", bra{L"a_2", L"a_4"}, ket{L"i_2", L"i_1"}, ps); auto const ext_idxs = external_indices(input); auto bixs = ext_idxs | ranges::views::transform( @@ -718,7 +725,7 @@ SECTION("Symmetrize expression") { [](auto&& vec) { return get_ket_idx(vec); }); auto result = ex(Tensor{reserved::symm_label(), bra(std::move(kixs)), - ket(std::move(bixs))}) * + ket(std::move(bixs)), ps}) * input; simplify(result); REQUIRE(result->is() == false); @@ -738,27 +745,26 @@ SECTION("Swap bra kets") { // Tensor { - auto input = ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, - Symmetry::Nonsymm); + auto input = ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, ps); auto result = swap_bra_ket(input); REQUIRE_THAT(result, EquivalentTo("g{a1,a2;i1,i2}")); } // Product { - auto input = ex(L"g", bra{L"a_5", L"a_6"}, ket{L"i_5", L"i_6"}, - Symmetry::Nonsymm) * - ex(L"t", bra{L"i_2"}, ket{L"a_6"}); + auto input = + ex(L"g", bra{L"a_5", L"a_6"}, ket{L"i_5", L"i_6"}, ps) * + ex(L"t", bra{L"i_2"}, ket{L"a_6"}, ps); auto result = swap_bra_ket(input); REQUIRE_THAT(result, EquivalentTo("g{i5,i6;a5,a6} t{a6;i2}")); } // Sum { - auto input = ex(L"f", bra{L"i_1"}, ket{L"i_5"}) + - ex(L"g", bra{L"a_5", L"a_6"}, ket{L"i_5", L"i_6"}, - Symmetry::Nonsymm) * - ex(L"t", bra{L"i_2"}, ket{L"a_6"}); + auto input = + ex(L"f", bra{L"i_1"}, ket{L"i_5"}, ps) + + ex(L"g", bra{L"a_5", L"a_6"}, ket{L"i_5", L"i_6"}, ps) * + ex(L"t", bra{L"i_2"}, ket{L"a_6"}, ps); auto result = swap_bra_ket(input); // TODO: This should be EquivalentTo but canonicalization currently doesn't // permit expressions that break co/contra variance of index contractions. @@ -829,7 +835,7 @@ SECTION("Closed-shell spintrace CCSD") { { // A * f const auto input = ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"f", bra{L"a_1"}, ket{L"i_1"}); + ex(L"f", bra{L"a_1"}, ket{L"i_1"}, ps); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -842,8 +848,8 @@ SECTION("Closed-shell spintrace CCSD") { // - A * f * t1 const auto input = ex(-1) * ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"f", bra{L"i_2"}, ket{L"i_1"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_2"}); + ex(L"f", bra{L"i_2"}, ket{L"i_1"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_2"}, ps); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -853,8 +859,8 @@ SECTION("Closed-shell spintrace CCSD") { { // A * f * t1 const auto input = ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"f", bra{L"a_1"}, ket{L"a_2"}) * - ex(L"t", bra{L"a_2"}, ket{L"i_1"}); + ex(L"f", bra{L"a_1"}, ket{L"a_2"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_1"}, ps); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -914,8 +920,8 @@ SECTION("Closed-shell spintrace CCSD") { const auto input = ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * ex(L"g", bra{L"i_2", L"a_1"}, ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}); + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps) * + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -928,8 +934,8 @@ SECTION("Closed-shell spintrace CCSD") { const auto input = ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * ex(L"g", bra{L"i_2", L"i_3"}, ket{L"i_1", L"a_2"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_3"}); + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_3"}, ps); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -941,9 +947,9 @@ SECTION("Closed-shell spintrace CCSD") { // A * f * t1 * t1 const auto input = ex(-1) * ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"f", bra{L"i_2"}, ket{L"a_2"}) * - ex(L"t", bra{L"a_2"}, ket{L"i_1"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_2"}); + ex(L"f", bra{L"i_2"}, ket{L"a_2"}, ps) * + ex(L"t", bra{L"a_2"}, ket{L"i_1"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_2"}, ps); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -956,7 +962,7 @@ SECTION("Closed-shell spintrace CCSD") { ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_1"}, ket{L"i_2"}) * + ex(L"t", bra{L"a_1"}, ket{L"i_2"}, ps) * ex(L"t", bra{L"a_2", L"a_3"}, ket{L"i_1", L"i_3"}, Symmetry::Antisymm); auto result = ex(rational{1, 2}) * @@ -973,7 +979,7 @@ SECTION("Closed-shell spintrace CCSD") { ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_1"}) * + ex(L"t", bra{L"a_2"}, ket{L"i_1"}, ps) * ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_2", L"i_3"}, Symmetry::Antisymm); auto result = ex(rational{1, 2}) * @@ -990,7 +996,7 @@ SECTION("Closed-shell spintrace CCSD") { ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps) * ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_1", L"i_3"}, Symmetry::Antisymm); auto result = ex(rational{1, 2}) * @@ -1008,9 +1014,9 @@ SECTION("Closed-shell spintrace CCSD") { auto input = ex(-1) * ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}) * - ex(L"t", bra{L"a_1"}, ket{L"i_3"}); + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps) * + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps) * + ex(L"t", bra{L"a_1"}, ket{L"i_3"}, ps); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -1072,7 +1078,7 @@ SECTION("Closed-shell spintrace CCSDT terms") { ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Antisymm) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, ket{L"i_2", L"i_3", L"i_4"}, Symmetry::Antisymm) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}); + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps); auto result = expand_A_op(input); REQUIRE(result->size() == 36); @@ -1478,8 +1484,9 @@ SECTION("Open-shell spin-tracing") { // Tensor canonicalize { - auto t3 = ex(Tensor(L"t", bra{a3A, a2B, a2A}, ket{i1A, i2B, i3A})); - auto f = ex(Tensor(L"f", bra{a1A}, ket{a2A})); + auto t3 = + ex(Tensor(L"t", bra{a3A, a2B, a2A}, ket{i1A, i2B, i3A}, ps)); + auto f = ex(Tensor(L"f", bra{a1A}, ket{a2A}, ps)); auto ft3 = f * t3; ft3->canonicalize(); REQUIRE_THAT(ft3, EquivalentTo("f{a↑1;a↑2} t{a↑3,a↑2,a↓2;i↑1,i↑3,i↓2}")); @@ -1501,7 +1508,7 @@ SECTION("Open-shell spin-tracing") { // f_oo * t2 { auto input = ex(rational{1, 2}) * - ex(L"f", bra{L"i_3"}, ket{L"i_1"}) * + ex(L"f", bra{L"i_3"}, ket{L"i_1"}, ps) * ex(L"t", bra{L"a_1", L"a_2"}, ket{L"i_2", L"i_3"}, Symmetry::Antisymm); @@ -1539,7 +1546,7 @@ SECTION("Open-shell spin-tracing") { // f * t3 { auto input = ex(rational{1, 12}) * - ex(L"f", bra{L"a_1"}, ket{L"a_4"}) * + ex(L"f", bra{L"a_1"}, ket{L"a_4"}, ps) * ex(L"t", bra{L"a_2", L"a_3", L"a_4"}, ket{L"i_1", L"i_2", L"i_3"}, Symmetry::Antisymm); auto result = open_shell_spintrace( @@ -1566,8 +1573,7 @@ SECTION("Open-shell spin-tracing") { Symmetry::Antisymm); auto g = Tensor(L"g", bra{i3A, i4A}, ket{i1A, i2A}, Symmetry::Antisymm); - auto t3 = - Tensor(L"t", bra{a1A, a2A, a3B}, ket{i3A, i4A, i3B}, Symmetry::Nonsymm); + auto t3 = Tensor(L"t", bra{a1A, a2A, a3B}, ket{i3A, i4A, i3B}, ps); auto input = ex(A2_aab) * ex(g) * ex(t3); auto result = expand_A_op(input); @@ -1577,8 +1583,7 @@ SECTION("Open-shell spin-tracing") { "t{a↑_1,a↑_2,a↓_3;i↑_4,i↑_3,i↓_3}:N-N-S")); g = Tensor(L"g", bra{i4A, i5A}, ket{i1A, i2A}, Symmetry::Antisymm); - t3 = - Tensor(L"t", bra{a1A, a2A, a3B}, ket{i4A, i5A, i3B}, Symmetry::Nonsymm); + t3 = Tensor(L"t", bra{a1A, a2A, a3B}, ket{i4A, i5A, i3B}, ps); input = ex(A2_aab) * ex(g) * ex(t3); result = expand_A_op(input); diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index 3167245fc8..ba605a7136 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -25,6 +25,13 @@ #include #include +namespace { +/// the MBPT-style particle-symmetric default (column = Symm); used where these +/// tests exercise particle-symmetric behavior (programmatic Tensor ctors are +/// Context-independent -- see Tensor::Defaults -- so it is spelled out here) +constexpr sequant::TensorSymmetries ps{.column = sequant::ColumnSymmetry::Symm}; +} // namespace + TEST_CASE("tensor", "[elements]") { using namespace sequant; @@ -59,11 +66,12 @@ TEST_CASE("tensor", "[elements]") { REQUIRE(ranges::distance(t2.const_indices().begin(), t2.const_indices().end()) == 2); REQUIRE(t2.symmetry() == Symmetry::Nonsymm); - // bare ctor: braket symmetry is derived from the default Context's - // Hermiticity (NonHermitian here) -> Nonsymm; column symmetry follows the - // Context default (Symm in the test context) + // programmatic ctor defaults are fixed and Context-independent: braket + // symmetry derives from the default NonHermitian -> Nonsymm, and column + // symmetry defaults to the safe Nonsymm (the Context's Symm default applies + // only to deserialized tensors) REQUIRE(t2.braket_symmetry() == BraKetSymmetry::Nonsymm); - REQUIRE(t2.column_symmetry() == ColumnSymmetry::Symm); + REQUIRE(t2.column_symmetry() == ColumnSymmetry::Nonsymm); REQUIRE(t2.label() == L"F"); // bra/kets of different rank @@ -86,7 +94,7 @@ TEST_CASE("tensor", "[elements]") { t3.const_indices().end()) == 2); REQUIRE(t3.symmetry() == Symmetry::Nonsymm); REQUIRE(t3.braket_symmetry() == BraKetSymmetry::Nonsymm); - REQUIRE(t3.column_symmetry() == ColumnSymmetry::Symm); + REQUIRE(t3.column_symmetry() == ColumnSymmetry::Nonsymm); REQUIRE(t3.label() == L"N"); REQUIRE_NOTHROW(Tensor(L"g", bra{Index{L"i_1"}, Index{L"i_2"}}, @@ -113,14 +121,12 @@ TEST_CASE("tensor", "[elements]") { SECTION("null indices") { // null indices ok in asymmetric bra or ket REQUIRE_NOTHROW(Tensor(L"N", bra{L"i_2", L"", L"i_3"}, - ket{L"", L"i_1", L""}, aux{}, Symmetry::Nonsymm)); + ket{L"", L"i_1", L""}, aux{}, ps)); REQUIRE_NOTHROW(Tensor(L"N", bra{L"", L"i_1", L""}, - ket{L"i_2", L"", L"i_3"}, aux{}, - Symmetry::Nonsymm)); + ket{L"i_2", L"", L"i_3"}, aux{}, ps)); - REQUIRE_NOTHROW( - Tensor(L"N", bra{L""}, ket{L"i_1"}, aux{}, Symmetry::Nonsymm)); - Tensor t(L"N", bra{L""}, ket{L"i_1"}, aux{}, Symmetry::Nonsymm); + REQUIRE_NOTHROW(Tensor(L"N", bra{L""}, ket{L"i_1"}, aux{}, ps)); + Tensor t(L"N", bra{L""}, ket{L"i_1"}, aux{}, ps); REQUIRE(t.bra_rank() == 0); REQUIRE(t.ket_rank() == 1); REQUIRE(t.bra_net_rank() == 0); @@ -130,29 +136,25 @@ TEST_CASE("tensor", "[elements]") { // in fact slots of asymmetric particle-symmetric tensors are kept in // canonical order - REQUIRE(Tensor(L"N", bra{L""}, ket{L"i_1"}, aux{}, Symmetry::Nonsymm) == - Tensor(L"N", bra{}, ket{L"i_1"}, aux{}, Symmetry::Nonsymm)); + REQUIRE(Tensor(L"N", bra{L""}, ket{L"i_1"}, aux{}, ps) == + Tensor(L"N", bra{}, ket{L"i_1"}, aux{}, ps)); REQUIRE(Tensor(L"N", bra{L"i_2", L"", L"i_3"}, ket{L"", L"i_1", L""}, - aux{}, Symmetry::Nonsymm) == - Tensor(L"N", bra{L"i_2", L"i_3"}, ket{L"", L"", L"i_1"}, aux{}, - Symmetry::Nonsymm)); + aux{}, ps) == Tensor(L"N", bra{L"i_2", L"i_3"}, + ket{L"", L"", L"i_1"}, aux{}, ps)); REQUIRE(Tensor(L"N", bra{L"", L"i_1", L""}, ket{L"i_2", L"", L"i_3"}, - aux{}, Symmetry::Nonsymm) == - Tensor(L"N", bra{L"i_1", L"", L""}, ket{L"", L"i_2", L"i_3"}, - aux{}, Symmetry::Nonsymm)); - REQUIRE( - Tensor(L"N", bra{L"", L"i_1", L"", L"i_4"}, - ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, Symmetry::Nonsymm) == - Tensor(L"N", bra{L"i_4", L"i_1", L"", L""}, - ket{L"i_5", L"", L"i_2", L"i_3"}, aux{}, Symmetry::Nonsymm)); + aux{}, ps) == Tensor(L"N", bra{L"i_1", L"", L""}, + ket{L"", L"i_2", L"i_3"}, aux{}, ps)); + REQUIRE(Tensor(L"N", bra{L"", L"i_1", L"", L"i_4"}, + ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, ps) == + Tensor(L"N", bra{L"i_4", L"i_1", L"", L""}, + ket{L"i_5", L"", L"i_2", L"i_3"}, aux{}, ps)); // in fact unnecessary null indices are dropped in canonicalization - REQUIRE( - Tensor(L"N", bra{L"", L"i_1", L"", L"i_4"}, - ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, Symmetry::Nonsymm) == - Tensor(L"N", bra{L"i_4", L"i_1"}, ket{L"i_5", L"", L"i_2", L"i_3"}, - aux{}, Symmetry::Nonsymm)); + REQUIRE(Tensor(L"N", bra{L"", L"i_1", L"", L"i_4"}, + ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, ps) == + Tensor(L"N", bra{L"i_4", L"i_1"}, + ket{L"i_5", L"", L"i_2", L"i_3"}, aux{}, ps)); Tensor t5(L"N", bra{L"", L"i_1", L"", L"i_4"}, - ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, Symmetry::Nonsymm); + ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, ps); REQUIRE(t5.bra_rank() == 2); REQUIRE(t5.bra_net_rank() == 2); REQUIRE(t5.bra()[0] == L"i_4"); @@ -347,7 +349,7 @@ TEST_CASE("tensor", "[elements]") { auto t2 = Tensor(L"F", bra{L"i_1"}, ket{L"i_2"}, aux{L"i_3"}); auto t3 = Tensor(L"F", bra{L"i_1"}, ket{L"i_2"}, aux{L"i_3", L"i_4"}); auto t4 = Tensor(L"F", bra{Index(L"i_1", {L"i_5", L"i_6"}), Index{}}, - ket{L"", L"i_2"}, aux{L"i_3", L"i_4"}, Symmetry::Nonsymm); + ket{L"", L"i_2"}, aux{L"i_3", L"i_4"}, ps); auto h1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}) * ex(cre({L"i_1"}), ann({L"i_2"})); @@ -556,9 +558,11 @@ TEST_CASE("tensor_hermiticity", "[elements]") { }; auto make_diff = [&idx](Field f) { auto a = ex(L"g", bra{idx(L"i_1", f)}, ket{idx(L"i_2", f)}, - Symmetry::Nonsymm, Hermiticity::Hermitian); + Symmetry::Nonsymm, Hermiticity::Hermitian, + ColumnSymmetry::Symm); auto b = ex(L"g", bra{idx(L"i_2", f)}, ket{idx(L"i_1", f)}, - Symmetry::Nonsymm, Hermiticity::Hermitian); + Symmetry::Nonsymm, Hermiticity::Hermitian, + ColumnSymmetry::Symm); ExprPtr diff = a - b; simplify(diff); return diff; @@ -570,24 +574,26 @@ TEST_CASE("tensor_hermiticity", "[elements]") { REQUIRE_FALSE(make_diff(Field::Complex) == ex(0)); } - SECTION("default symmetries are taken from the Context") { - // unspecified symmetries are resolved against the active default Context; - // braket symmetry is *derived* from the Context's default Hermiticity and - // the tensor's base field (there is no braket-symmetry Context knob) + SECTION("programmatic ctor defaults are Context-independent") { + // The Context's default symmetries govern *deserialization* only (see + // test_parse). A programmatic Tensor ctor always resolves unspecified + // attributes against fixed library defaults -- fully non-symmetric / + // non-Hermitian -- regardless of the active Context. Set the Context + // defaults to non-default values and confirm they are ignored here. auto ctx = get_default_context(); - ctx.set(Symmetry::Nonsymm) + ctx.set(Symmetry::Antisymm) .set(Hermiticity::Hermitian) - .set(ColumnSymmetry::Nonsymm); + .set(ColumnSymmetry::Symm); auto resetter = set_scoped_default_context(ctx); - auto g = Tensor(L"g", bra{L"i_1"}, ket{L"i_2"}); + auto g = Tensor(L"g", bra{L"i_1", L"i_2"}, ket{L"i_3", L"i_4"}); REQUIRE(g.symmetry() == Symmetry::Nonsymm); - REQUIRE(g.hermiticity() == Hermiticity::Hermitian); + REQUIRE(g.hermiticity() == Hermiticity::NonHermitian); REQUIRE(g.column_symmetry() == ColumnSymmetry::Nonsymm); REQUIRE(g.braket_symmetry() == - to_braket_symmetry(Hermiticity::Hermitian, g.base_field())); + to_braket_symmetry(Hermiticity::NonHermitian, g.base_field())); - // explicitly-specified symmetries override the Context defaults + // explicitly-specified symmetries override the fixed defaults auto t = Tensor(L"t", bra{L"a_1"}, ket{L"i_1"}, Symmetry::Nonsymm, BraKetSymmetry::Nonsymm, ColumnSymmetry::Symm); REQUIRE(t.braket_symmetry() == BraKetSymmetry::Nonsymm); diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index b5218175b4..f92d7bcac1 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -34,6 +34,16 @@ #include namespace sequant { + +namespace { +// mbpt convention: tensors describe indistinguishable particles and so are +// particle (column) symmetric. The fixed (Context-independent) Tensor ctor +// defaults column symmetry to Nonsymm, so pass this named-parameter pack to opt +// the TU's tensors into particle symmetry (unset fields keep the library +// defaults). Defining it once here keeps the per-site cost to a single `, ps`. +constexpr TensorSymmetries ps{.column = ColumnSymmetry::Symm}; +} // namespace + struct WickAccessor {}; template <> @@ -1014,10 +1024,8 @@ TEST_CASE("wick", "[algorithms][wick][valgrind_skip]") { // multiply tensor factors and expand auto wick_result_2 = - ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, - Symmetry::Nonsymm) * - ex(L"t", bra{L"a_4", L"a_5"}, ket{L"i_4", L"i_5"}, - Symmetry::Nonsymm) * + ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, ps) * + ex(L"t", bra{L"a_4", L"a_5"}, ket{L"i_4", L"i_5"}, ps) * wick_result; expand(wick_result_2); REQUIRE(wick_result_2->size() == 4); // still 4 terms @@ -1305,21 +1313,23 @@ TEST_CASE("wick", "[algorithms][wick][valgrind_skip]") { // sequant::wprintf(to_latex_align(Ld_H2N_L), L" = \n", // to_latex_align(result2, 0, 2), L"\n"); REQUIRE(result2.as().size() == 5); - // Sum-term ordering depends on the tensors' (Context-derived) braket - // and column symmetries; the canonical sort key for these ã·v̄·w terms - // shifted vs master as those defaults changed. The expression is - // mathematically unchanged. + // Sum-term ordering depends on the tensors' braket and column + // symmetries; the canonical sort key for these ã·v̄·w terms reflects the + // fixed (Context-independent) Tensor ctor defaults -- notably w, which + // carries no bra/ket and so is column-Nonsymm by default -- and shifted + // vs the previous (Context-Symm) ordering. The expression is + // mathematically unchanged (same five terms, reordered). REQUIRE( result2.to_latex() == L"{ \\bigl( - " - L"{{{\\frac{1}{2}}}{\\tilde{a}^{{e_1}{p_3}}_{{p_1}{p_2}}}{\\bar{v}^" - L"{{p_1}{p_2}}_{{e_1}{p_3}}}{w^{}_{}[{e_1}]}} - " L"{{{\\frac{1}{4}}}{\\tilde{a}^{{p_3}{p_4}{p_5}}_{{p_1}{p_2}{p_5}}}" - L"{\\bar{v}^{{p_1}{p_2}}_{{p_3}{p_4}}}{w^{}_{}[{p_5}]}} + " - L"{{\\tilde{a}^{{p_2}}_{{p_1}}}{\\bar{v}^{{e_1}{p_1}}_{{e_1}{p_2}}}" - L"{w^{}_{}[{e_1}]}} + " + L"{\\bar{v}^{{p_1}{p_2}}_{{p_3}{p_4}}}{w^{}_{}[{p_5}]}} - " + L"{{{\\frac{1}{2}}}{\\tilde{a}^{{e_1}{p_3}}_{{p_1}{p_2}}}{\\bar{v}^" + L"{{p_1}{p_2}}_{{e_1}{p_3}}}{w^{}_{}[{e_1}]}} + " L"{{{\\frac{1}{4}}}{\\tilde{a}^{{p_3}{p_4}}_{{p_1}{p_2}}}{\\bar{v}^" - L"{{p_1}{p_2}}_{{p_3}{p_4}}}{w^{}_{}[{e_1}]}} - " + L"{{p_1}{p_2}}_{{p_3}{p_4}}}{w^{}_{}[{e_1}]}} + " + L"{{\\tilde{a}^{{p_2}}_{{p_1}}}{\\bar{v}^{{e_1}{p_1}}_{{e_1}{p_2}}}" + L"{w^{}_{}[{e_1}]}} - " L"{{{\\frac{1}{2}}}{\\tilde{a}^{{p_2}{p_3}}_{{e_1}{p_1}}}{\\bar{v}^" L"{{e_1}{p_1}}_{{p_2}{p_3}}}{w^{}_{}[{e_1}]}}\\bigr) }"); } @@ -1340,12 +1350,15 @@ TEST_CASE("wick", "[algorithms][wick][valgrind_skip]") { REQUIRE(result.as().size() == 5); // clang-format off + // h is aux-only (no bra/ket particle columns) -> column-Nonsymm, like + // the programmatic h in `input`; pin it so the parsed reference matches + // the fixed-default programmatic ctor (overlaps/deltas stay Symm) auto expected = deserialize( - "- h{;;p_3} ã{p_1,p_3;p_2,p_3}" - "+ h{;;p_3} δ{p_1;a_1} δ{a_2;p_2} ã{p_3;p_3} s{a_1;a_2} " - "- h{;;a_1} δ{a_2;p_2} ã{p_1;a_1} s{a_1;a_2} " - "- h{;;a_2} δ{p_1;a_1} s{a_1;a_2} ã{a_2;p_2} " - "+ h{;;a_3} δ{p_1;a_1} δ{a_2;p_2} s{a_1;a_3} s{a_3;a_2} "); + "- h{;;p_3}:N-N-N ã{p_1,p_3;p_2,p_3}" + "+ h{;;p_3}:N-N-N δ{p_1;a_1} δ{a_2;p_2} ã{p_3;p_3} s{a_1;a_2} " + "- h{;;a_1}:N-N-N δ{a_2;p_2} ã{p_1;a_1} s{a_1;a_2} " + "- h{;;a_2}:N-N-N δ{p_1;a_1} s{a_1;a_2} ã{a_2;p_2} " + "+ h{;;a_3}:N-N-N δ{p_1;a_1} δ{a_2;p_2} s{a_1;a_3} s{a_3;a_2} "); // clang-format on simplify(expected); From e3bd206be65257219f2f97474866ba7841803137 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Wed, 8 Jul 2026 17:57:30 -0400 Subject: [PATCH 13/22] tests: define the particle-symmetric `ps` pack once, shared (fix unity build) Each MBPT test TU defined its own file-local `constexpr TensorSymmetries ps` (some in the global anonymous namespace, test_wick's inside `namespace sequant`). In CMAKE_UNITY_BUILD=ON builds (used by the Debug/Valgrind CI jobs) several test .cpp are concatenated into one TU, so these definitions collided -- `reference to 'ps' is ambiguous` between the global-anonymous and the sequant-namespace copies. Define it once as `inline constexpr sequant::ps` in the shared catch2_sequant.hpp (guarded, so a unity TU sees a single definition) and drop the per-file copies. Verified with a local CMAKE_UNITY_BUILD=ON Debug build: all 6563 assertions pass. --- tests/unit/catch2_sequant.hpp | 12 ++++++++++++ tests/unit/test_canonicalize.cpp | 10 ++-------- tests/unit/test_spin.cpp | 9 ++------- tests/unit/test_tensor.cpp | 8 ++------ tests/unit/test_wick.cpp | 10 ++-------- 5 files changed, 20 insertions(+), 29 deletions(-) diff --git a/tests/unit/catch2_sequant.hpp b/tests/unit/catch2_sequant.hpp index 3dc0185ef1..b71475b08b 100644 --- a/tests/unit/catch2_sequant.hpp +++ b/tests/unit/catch2_sequant.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,17 @@ #include #include +namespace sequant { +/// Shared particle-symmetric default symmetry pack (column = Symm) for the MBPT +/// test TUs. Programmatic Tensor construction is Context-independent (see +/// Tensor::Defaults), so tests spell out MBPT particle symmetry explicitly and +/// feed this at the construction sites that must match parsed references. +/// Defined `inline` in this shared header (rather than once per TU) so that +/// unity/jumbo test builds see a single definition instead of colliding +/// anonymous-namespace copies. +inline constexpr TensorSymmetries ps{.column = ColumnSymmetry::Symm}; +} // namespace sequant + namespace Catch { // Make sure Catch uses proper string representation for SeQuant types diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 7d9ebfc366..7ec2c57eab 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -28,14 +28,8 @@ #include #include -namespace { -/// this TU canonicalizes MBPT tensors, whose particles are indistinguishable, -/// hence they are particle- (column-) symmetric; programmatic Tensor -/// construction is Context-independent (see Tensor::Defaults), so spell that -/// default out once here and feed it at every construction site that must match -/// parsed references -constexpr sequant::TensorSymmetries ps{.column = sequant::ColumnSymmetry::Symm}; -} // namespace +// the particle-symmetric default symmetry pack `ps` (column = Symm) is defined +// in catch2_sequant.hpp and shared across the MBPT test TUs TEST_CASE("canonicalization", "[algorithms]") { using namespace sequant; diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index b1d70f6079..dbd7ab745b 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -33,13 +33,8 @@ #include #include -namespace { -/// this TU exercises MBPT tensors, whose particles are indistinguishable, hence -/// they are particle- (column-) symmetric; programmatic Tensor construction is -/// Context-independent (see Tensor::Defaults), so spell that default out once -/// here and feed it at every construction site that must match parsed refs -constexpr sequant::TensorSymmetries ps{.column = sequant::ColumnSymmetry::Symm}; -} // namespace +// the particle-symmetric default symmetry pack `ps` (column = Symm) is defined +// in catch2_sequant.hpp and shared across the MBPT test TUs TEST_CASE("spin", "[spin]") { using namespace sequant; diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index ba605a7136..b20440889e 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -25,12 +25,8 @@ #include #include -namespace { -/// the MBPT-style particle-symmetric default (column = Symm); used where these -/// tests exercise particle-symmetric behavior (programmatic Tensor ctors are -/// Context-independent -- see Tensor::Defaults -- so it is spelled out here) -constexpr sequant::TensorSymmetries ps{.column = sequant::ColumnSymmetry::Symm}; -} // namespace +// the particle-symmetric default symmetry pack `ps` (column = Symm) is defined +// in catch2_sequant.hpp and shared across the MBPT test TUs TEST_CASE("tensor", "[elements]") { using namespace sequant; diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index f92d7bcac1..91b6c45394 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -35,14 +35,8 @@ namespace sequant { -namespace { -// mbpt convention: tensors describe indistinguishable particles and so are -// particle (column) symmetric. The fixed (Context-independent) Tensor ctor -// defaults column symmetry to Nonsymm, so pass this named-parameter pack to opt -// the TU's tensors into particle symmetry (unset fields keep the library -// defaults). Defining it once here keeps the per-site cost to a single `, ps`. -constexpr TensorSymmetries ps{.column = ColumnSymmetry::Symm}; -} // namespace +// the particle-symmetric default symmetry pack `ps` (column = Symm) is defined +// in catch2_sequant.hpp and shared across the MBPT test TUs struct WickAccessor {}; From 81935b1d6e1a566ad4ad97da38c3dbbaea6837ec Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Wed, 8 Jul 2026 18:01:47 -0400 Subject: [PATCH 14/22] tensor: source resolve_symmetries fallbacks from Tensor::Defaults resolve_symmetries() documented that unspecified attributes fall back to Tensor::Defaults but still hardcoded the literals (Symmetry::Nonsymm, ColumnSymmetry::Nonsymm, Hermiticity::NonHermitian), so the two could silently diverge. Use the Defaults constants directly, and clarify the Hermiticity/BraKetSymmetry back-fill comments (addresses Copilot review). --- SeQuant/core/expressions/tensor.hpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index b3ef5f430f..be97c346cc 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -332,15 +332,19 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { std::optional s, std::optional bks_opt, std::optional ps, std::optional herm_opt, Field base_fld) { - const Symmetry s_resolved = s.value_or(Symmetry::Nonsymm); - const ColumnSymmetry ps_resolved = ps.value_or(ColumnSymmetry::Nonsymm); - const Hermiticity h_resolved = herm_opt.value_or(Hermiticity::NonHermitian); + // unspecified attributes fall back to the single source of truth + const Symmetry s_resolved = s.value_or(Defaults::symmetry); + const ColumnSymmetry ps_resolved = ps.value_or(Defaults::column_symmetry); + const Hermiticity h_resolved = herm_opt.value_or(Defaults::hermiticity); + // braket symmetry is derived from the explicit-or-default Hermiticity and + // the base field unless it was given explicitly const BraKetSymmetry bks_resolved = bks_opt.has_value() ? *bks_opt : to_braket_symmetry(h_resolved, base_fld); - // preserve the exact #Hermiticity trait (incl. AntiHermitian, which the - // BraKetSymmetry round-trip cannot represent) when it was given; otherwise - // back-fill it from the explicit-or-derived braket symmetry + // report the exact #Hermiticity trait (incl. AntiHermitian, which the + // BraKetSymmetry round-trip cannot represent) when Hermiticity was given; + // otherwise, if only BraKetSymmetry was given, back-fill Hermiticity from + // it; otherwise use the (default) Hermiticity that already fed bks_resolved const Hermiticity hermiticity_resolved = herm_opt.has_value() ? *herm_opt From 47869fbcff536535d94e5a0790cd7cf90bc78d76 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Wed, 8 Jul 2026 18:58:13 -0400 Subject: [PATCH 15/22] tensor: force reserved (anti)symmetrizers to be particle (column) symmetric An (anti)symmetrization operator acts on indistinguishable particles, so it is inherently particle- (column-) symmetric, and the library already builds its symmetrizers that way. But nothing enforced it: a symmetrizer built programmatically without an explicit column symmetry got the (Option B) Context-independent default of Nonsymm. When such a Nonsymm symmetrizer met an otherwise-identical Symm one (e.g. the srcc integration test builds the 1-body S manually while the library's is Symm), the two failed to cancel, breaking the spin-free-vs-spin-traced equality check. Enforce column = Symm for reserved (anti)symmetrizer labels in check_symmetries(), alongside the existing fixed-braket-symmetry rule, so a symmetrizer is identical however it is built. Fixes the srcc/*_csv_sf integration test; full unit suite and all srcc/stcc/eomcc/antisymmetrizer integration variants pass locally. --- SeQuant/core/expressions/tensor.hpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index be97c346cc..da288e32bd 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -961,11 +961,18 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { // demote Symm to Conjugate here (Conjugate is treated as no-swap by the // canonicalizer, matching the complex-field behavior) before // canonicalize_slots() may act on it. - if ((label_ == reserved::antisymm_label() || - label_ == reserved::symm_label()) && - braket_symmetry_ != BraKetSymmetry::Nonsymm) { - throw Exception( - "(Anti)symmetrization operators must not have braket symmetry"); + if (label_ == reserved::antisymm_label() || + label_ == reserved::symm_label()) { + if (braket_symmetry_ != BraKetSymmetry::Nonsymm) + throw Exception( + "(Anti)symmetrization operators must not have braket symmetry"); + // (Anti)symmetrization operators act on indistinguishable particles, so + // they are always particle- (column-) symmetric. Enforce it here -- like + // the fixed braket symmetry above -- so that a symmetrizer is identical + // however it was built (programmatically, where the column-symmetry + // default is the Context-independent Nonsymm, or by deserialization); a + // mismatch would silently prevent otherwise-equal terms from cancelling. + column_symmetry_ = ColumnSymmetry::Symm; } if (symmetry_ == Symmetry::Symm || symmetry_ == Symmetry::Antisymm) { From 5bad4cd083ad5e70cd5d2a9cb62bd9e6b12fa787 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 4 Aug 2026 12:59:39 -0400 Subject: [PATCH 16/22] tensor: TensorSymmetries pack in resolve_symmetries + (anti)symmetrizer factories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses two review comments (Krzmbrzl): 1. `resolve_symmetries()` took four parallel `std::optional` symmetry arguments that are exactly the fields of the existing TensorSymmetries named-parameter pack -- take the pack instead. The private reserved-tag defaulting ctors follow suit, which also lets the TensorSymmetries public ctors forward the pack whole instead of unpacking it field-by-field. 2. Reserved (anti)symmetrizers silently had their column symmetry forced to Symm; be consistent with the braket-symmetry handling and *throw* when a contradicting column symmetry was spelled out, while still supplying the correct one when it was merely left unspecified (so the Context-independent Nonsymm column default cannot produce a symmetrizer that differs from a deserialized one). Add make_symmetrizer()/make_antisymmetrizer() (cf. make_overlap) plus the `symmetrizer_symmetries`/`antisymmetrizer_symmetries` packs they are built from, and use them at the mbpt construction sites. Two supporting fixes fall out of (2): - check_symmetries() now applies the "(anti)symmetric bra/ket implies column symmetry" promotion *first*, so  -- column-symmetric by implication of its Antisymm perm symmetry alone -- is never reported as contradicting. - the deserializer forces column = Symm for  as well as Ŝ; it always passes a fully specified column symmetry, so under a column-Nonsymm Context it would otherwise hand the ctor a contradiction. Also drops a stale comment in check_symmetries() describing a Symm -> Conjugate braket demotion that the code has not done since the braket symmetry became an error. --- SeQuant/core/expressions/tensor.hpp | 225 ++++++++++++------ .../io/serialization/v1/ast_conversions.hpp | 7 +- SeQuant/domain/mbpt/biorthogonalization.cpp | 6 +- SeQuant/domain/mbpt/spin.cpp | 14 +- tests/unit/test_parse.cpp | 16 ++ tests/unit/test_tensor.cpp | 114 +++++++-- 6 files changed, 278 insertions(+), 104 deletions(-) diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index da288e32bd..7a00d88fe0 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -312,6 +312,11 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { BraKetSymmetry braket_symmetry; Hermiticity hermiticity; ColumnSymmetry column_symmetry; + /// whether #column_symmetry was spelled out by the caller (rather than + /// defaulted); check_symmetries() rejects an explicit column symmetry that + /// contradicts a reserved label's defining symmetry, but silently supplies + /// the correct one when it was merely left unspecified + bool column_symmetry_specified; }; /// Resolves the (possibly unspecified) symmetry attributes of a tensor. @@ -321,6 +326,9 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// - #Symmetry, #Hermiticity and #ColumnSymmetry fall back to the *fixed* /// library defaults in Tensor::Defaults -- the safest, fully non-symmetric /// / non-Hermitian choice -- when not specified. + /// @param syms the (partially specified) symmetry pack + /// @param base_fld the tensor's #base_field, used to derive the + /// #BraKetSymmetry from the #Hermiticity /// @note Programmatic Tensor construction never consults the default /// sequant::Context: the meaning of a ctor call is independent of /// ambient global state (so it is predictable and lock-free). Only the @@ -328,28 +336,30 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// the boundary that turns under-specified external input into tensors; /// domains that need a different programmatic default (e.g. mbpt, whose /// tensors are particle/column-symmetric) pass it explicitly. - static resolved_symmetries resolve_symmetries( - std::optional s, std::optional bks_opt, - std::optional ps, std::optional herm_opt, - Field base_fld) { + static resolved_symmetries resolve_symmetries(const TensorSymmetries &syms, + Field base_fld) { // unspecified attributes fall back to the single source of truth - const Symmetry s_resolved = s.value_or(Defaults::symmetry); - const ColumnSymmetry ps_resolved = ps.value_or(Defaults::column_symmetry); - const Hermiticity h_resolved = herm_opt.value_or(Defaults::hermiticity); + const Symmetry s_resolved = syms.perm.value_or(Defaults::symmetry); + const ColumnSymmetry ps_resolved = + syms.column.value_or(Defaults::column_symmetry); + const Hermiticity h_resolved = + syms.hermiticity.value_or(Defaults::hermiticity); // braket symmetry is derived from the explicit-or-default Hermiticity and // the base field unless it was given explicitly const BraKetSymmetry bks_resolved = - bks_opt.has_value() ? *bks_opt - : to_braket_symmetry(h_resolved, base_fld); + syms.braket.has_value() ? *syms.braket + : to_braket_symmetry(h_resolved, base_fld); // report the exact #Hermiticity trait (incl. AntiHermitian, which the // BraKetSymmetry round-trip cannot represent) when Hermiticity was given; // otherwise, if only BraKetSymmetry was given, back-fill Hermiticity from // it; otherwise use the (default) Hermiticity that already fed bks_resolved const Hermiticity hermiticity_resolved = - herm_opt.has_value() - ? *herm_opt - : (bks_opt.has_value() ? to_hermiticity(*bks_opt) : h_resolved); - return {s_resolved, bks_resolved, hermiticity_resolved, ps_resolved}; + syms.hermiticity.has_value() + ? *syms.hermiticity + : (syms.braket.has_value() ? to_hermiticity(*syms.braket) + : h_resolved); + return {s_resolved, bks_resolved, hermiticity_resolved, ps_resolved, + syms.column.has_value()}; } // fully-resolved terminal ctor (range form) @@ -373,7 +383,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { ket_net_rank_(ranges::count_if( ket_, [](const Index &idx) { return static_cast(idx); })) { validate_indices(); - check_symmetries(); + check_symmetries(rsym.column_symmetry_specified); canonicalize_slots(); } @@ -396,7 +406,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { ket_net_rank_(ranges::count_if( ket_, [](const Index &idx) { return static_cast(idx); })) { validate_indices(); - check_symmetries(); + check_symmetries(rsym.column_symmetry_specified); canonicalize_slots(); } @@ -408,35 +418,26 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { Tensor(S &&label, const bra &bra_indices, const ket &ket_indices, const aux &aux_indices, reserved_tag, - std::optional s = std::nullopt, - std::optional bks_opt = std::nullopt, - std::optional ps = std::nullopt, - std::optional herm_opt = std::nullopt) + const TensorSymmetries &syms = {}) : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, reserved_tag{}, resolve_symmetries( - s, bks_opt, ps, herm_opt, - sequant::base_field(make_indices(bra_indices), - make_indices(ket_indices)))) {} + syms, sequant::base_field(make_indices(bra_indices), + make_indices(ket_indices)))) {} // defaulting reserved-tag ctor (move form) template Tensor(S &&label, bra &&bra_indices, ket &&ket_indices, aux &&aux_indices, reserved_tag, - std::optional s = std::nullopt, - std::optional bks_opt = std::nullopt, - std::optional ps = std::nullopt, - std::optional herm_opt = std::nullopt) + const TensorSymmetries &syms = {}) // base_field(bra_indices, ket_indices) is read during argument // evaluation, before the delegated ctor moves the indices out -- safe // regardless of argument evaluation order - : Tensor( - std::forward(label), std::move(bra_indices), - std::move(ket_indices), std::move(aux_indices), reserved_tag{}, - resolve_symmetries(s, bks_opt, ps, herm_opt, - sequant::base_field(bra_indices, ket_indices))) { - } + : Tensor(std::forward(label), std::move(bra_indices), + std::move(ket_indices), std::move(aux_indices), reserved_tag{}, + resolve_symmetries( + syms, sequant::base_field(bra_indices, ket_indices))) {} public: /// constructs an uninitialized Tensor @@ -477,7 +478,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { std::optional bks_opt = std::nullopt, std::optional ps = std::nullopt) : Tensor(std::forward(label), bra_indices, ket_indices, sequant::aux{}, - reserved_tag{}, s, bks_opt, ps) { + reserved_tag{}, + TensorSymmetries{.perm = s, .braket = bks_opt, .column = ps}) { assert_nonreserved_label(label_); } @@ -506,7 +508,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { std::optional bks_opt = std::nullopt, std::optional ps = std::nullopt) : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, - reserved_tag{}, s, bks_opt, ps) { + reserved_tag{}, + TensorSymmetries{.perm = s, .braket = bks_opt, .column = ps}) { assert_nonreserved_label(label_); } @@ -530,8 +533,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { std::optional bks_opt = std::nullopt, std::optional ps = std::nullopt) : Tensor(std::forward(label), std::move(bra_indices), - std::move(ket_indices), sequant::aux{}, reserved_tag{}, s, - bks_opt, ps) { + std::move(ket_indices), sequant::aux{}, reserved_tag{}, + TensorSymmetries{.perm = s, .braket = bks_opt, .column = ps}) { assert_nonreserved_label(label_); } @@ -559,7 +562,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { std::optional ps = std::nullopt) : Tensor(std::forward(label), std::move(bra_indices), std::move(ket_indices), std::move(aux_indices), reserved_tag{}, - s, bks_opt, ps) { + TensorSymmetries{.perm = s, .braket = bks_opt, .column = ps}) { assert_nonreserved_label(label_); } @@ -591,8 +594,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { // runs (the latter keys off braket_symmetry_), and preserves the exact // trait (incl. AntiHermitian) in hermiticity_. : Tensor(std::forward(label), bra_indices, ket_indices, sequant::aux{}, - reserved_tag{}, std::optional(s), std::nullopt, ps, - std::optional(h)) { + reserved_tag{}, + TensorSymmetries{.perm = s, .hermiticity = h, .column = ps}) { assert_nonreserved_label(label_); } @@ -618,8 +621,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { // runs (the latter keys off braket_symmetry_), and preserves the exact // trait (incl. AntiHermitian) in hermiticity_. : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, - reserved_tag{}, std::optional(s), std::nullopt, ps, - std::optional(h)) { + reserved_tag{}, + TensorSymmetries{.perm = s, .hermiticity = h, .column = ps}) { assert_nonreserved_label(label_); } @@ -641,8 +644,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { Tensor(S &&label, const bra &bra_indices, const ket &ket_indices, TensorSymmetries syms) : Tensor(std::forward(label), bra_indices, ket_indices, sequant::aux{}, - reserved_tag{}, syms.perm, syms.braket, syms.column, - syms.hermiticity) { + reserved_tag{}, syms) { assert_nonreserved_label(label_); } @@ -658,8 +660,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { const ket &ket_indices, const aux &aux_indices, TensorSymmetries syms) : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, - reserved_tag{}, syms.perm, syms.braket, syms.column, - syms.hermiticity) { + reserved_tag{}, syms) { assert_nonreserved_label(label_); } @@ -671,8 +672,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { Tensor(S &&label, bra &&bra_indices, ket &&ket_indices, TensorSymmetries syms) : Tensor(std::forward(label), std::move(bra_indices), - std::move(ket_indices), sequant::aux{}, reserved_tag{}, - syms.perm, syms.braket, syms.column, syms.hermiticity) { + std::move(ket_indices), sequant::aux{}, reserved_tag{}, syms) { assert_nonreserved_label(label_); } @@ -687,7 +687,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { aux &&aux_indices, TensorSymmetries syms) : Tensor(std::forward(label), std::move(bra_indices), std::move(ket_indices), std::move(aux_indices), reserved_tag{}, - syms.perm, syms.braket, syms.column, syms.hermiticity) { + syms) { assert_nonreserved_label(label_); } @@ -952,32 +952,43 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { std::size_t bra_net_rank_; std::size_t ket_net_rank_; - void check_symmetries() { + /// @param column_symmetry_specified whether the column symmetry was spelled + /// out by the caller rather than defaulted; a *specified* value that + /// contradicts a reserved label's defining symmetry is an error, an + /// unspecified one is simply replaced by the correct value + /// @sa make_symmetrizer(), make_antisymmetrizer() + void check_symmetries(bool column_symmetry_specified) { + if (symmetry_ == Symmetry::Symm || symmetry_ == Symmetry::Antisymm) { + // (Anti)symmetry in bra and ket indices automatically implies column + // symmetry. N.B. this promotion runs first so that the reserved-label + // check below sees the implied column symmetry (the antisymmetrizer  is + // Antisymm, hence column-symmetric by implication alone). + column_symmetry_ = ColumnSymmetry::Symm; + } + // The (anti)symmetrizer is a permutation-bookkeeping operator whose // bra<->ket orientation defines/extracts the external indices and must be // preserved; it must never be bra<->ket-symmetric (Symm), or // canonicalization would swap its bra and ket and corrupt external-index - // extraction. Over a real field a Hermitian operator becomes Symm, so - // demote Symm to Conjugate here (Conjugate is treated as no-swap by the - // canonicalizer, matching the complex-field behavior) before - // canonicalize_slots() may act on it. + // extraction. if (label_ == reserved::antisymm_label() || label_ == reserved::symm_label()) { if (braket_symmetry_ != BraKetSymmetry::Nonsymm) throw Exception( "(Anti)symmetrization operators must not have braket symmetry"); // (Anti)symmetrization operators act on indistinguishable particles, so - // they are always particle- (column-) symmetric. Enforce it here -- like - // the fixed braket symmetry above -- so that a symmetrizer is identical - // however it was built (programmatically, where the column-symmetry - // default is the Context-independent Nonsymm, or by deserialization); a - // mismatch would silently prevent otherwise-equal terms from cancelling. - column_symmetry_ = ColumnSymmetry::Symm; - } - - if (symmetry_ == Symmetry::Symm || symmetry_ == Symmetry::Antisymm) { - // (Anti)symmetry in bra and ket indices automatically implies column - // symmetry + // they are always particle- (column-) symmetric; this is a defining + // property, not a free parameter, so reject a contradicting explicit + // request (like the braket symmetry above) and supply the correct value + // when none was given. The latter makes a (anti)symmetrizer identical + // however it was built -- programmatically, where the column-symmetry + // default is the Context-independent Nonsymm, or by deserialization -- + // since a mismatch would silently prevent otherwise-equal terms from + // cancelling. + if (column_symmetry_specified && column_symmetry_ != ColumnSymmetry::Symm) + throw Exception( + "(Anti)symmetrization operators must be column (particle) " + "symmetric"); column_symmetry_ = ColumnSymmetry::Symm; } } @@ -1163,18 +1174,96 @@ inline ExprPtr make_overlap(const Index &bra_index, const Index &ket_index) { // default is Nonsymm, so request Symm explicitly. return ex(Tensor(reserved::overlap_label(), bra{bra_index}, ket{ket_index}, aux{}, Tensor::reserved_tag{}, - Symmetry::Nonsymm, std::nullopt, - ColumnSymmetry::Symm)); + TensorSymmetries{.perm = Symmetry::Nonsymm, + .column = ColumnSymmetry::Symm})); } inline ExprPtr make_kronecker(const Index &bra_index, const Index &ket_index) { // see make_overlap: a Kronecker delta is likewise particle (column) symmetric return ex(Tensor(reserved::kronecker_label(), bra{bra_index}, ket{ket_index}, aux{}, Tensor::reserved_tag{}, - Symmetry::Nonsymm, std::nullopt, - ColumnSymmetry::Symm)); + TensorSymmetries{.perm = Symmetry::Nonsymm, + .column = ColumnSymmetry::Symm})); } +/// @name (anti)symmetrization operator factories +/// The reserved (anti)symmetrization operators Ŝ/ have *defining* symmetries +/// that are not free parameters: both are braket-Nonsymm (their bra<->ket +/// orientation defines which indices are external) and column- (particle-) +/// symmetric (they act on indistinguishable particles), and  is additionally +/// Antisymm in bra and in ket. These factories spell those out, so that a +/// (anti)symmetrizer built programmatically is identical to one obtained by +/// deserialization; the Tensor ctors reject any contradicting symmetry. +/// @{ + +/// the defining symmetries of the reserved antisymmetrization operator  +inline constexpr TensorSymmetries antisymmetrizer_symmetries{ + .perm = Symmetry::Antisymm, + .braket = BraKetSymmetry::Nonsymm, + .column = ColumnSymmetry::Symm}; + +/// the defining symmetries of the reserved symmetrization operator Ŝ +inline constexpr TensorSymmetries symmetrizer_symmetries{ + .perm = Symmetry::Nonsymm, + .braket = BraKetSymmetry::Nonsymm, + .column = ColumnSymmetry::Symm}; + +/// @brief makes an antisymmetrization operator  +/// @param bra_indices the bra ("upper") external indices +/// @param ket_indices the ket ("lower") external indices +/// @return an ExprPtr to the  Tensor +template +ExprPtr make_antisymmetrizer(const bra &bra_indices, + const ket &ket_indices) { + return ex(Tensor(reserved::antisymm_label(), bra_indices, ket_indices, + antisymmetrizer_symmetries)); +} + +/// @brief makes an antisymmetrization operator  +/// @param bra_indices the bra ("upper") external indices +/// @param ket_indices the ket ("lower") external indices +/// @param aux_indices the aux indices +/// @return an ExprPtr to the  Tensor +template +ExprPtr make_antisymmetrizer(const bra &bra_indices, + const ket &ket_indices, + const aux &aux_indices) { + return ex(Tensor(reserved::antisymm_label(), bra_indices, ket_indices, + aux_indices, antisymmetrizer_symmetries)); +} + +/// @brief makes a symmetrization operator Ŝ +/// @param bra_indices the bra ("upper") external indices +/// @param ket_indices the ket ("lower") external indices +/// @return an ExprPtr to the Ŝ Tensor +template +ExprPtr make_symmetrizer(const bra &bra_indices, + const ket &ket_indices) { + return ex(Tensor(reserved::symm_label(), bra_indices, ket_indices, + symmetrizer_symmetries)); +} + +/// @brief makes a symmetrization operator Ŝ +/// @param bra_indices the bra ("upper") external indices +/// @param ket_indices the ket ("lower") external indices +/// @param aux_indices the aux indices +/// @return an ExprPtr to the Ŝ Tensor +template +ExprPtr make_symmetrizer(const bra &bra_indices, + const ket &ket_indices, + const aux &aux_indices) { + return ex(Tensor(reserved::symm_label(), bra_indices, ket_indices, + aux_indices, symmetrizer_symmetries)); +} + +/// @} + } // namespace sequant #endif // SEQUANT_EXPRESSIONS_TENSOR_HPP diff --git a/SeQuant/core/io/serialization/v1/ast_conversions.hpp b/SeQuant/core/io/serialization/v1/ast_conversions.hpp index f177e85dd1..e27fc10628 100644 --- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp +++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp @@ -333,7 +333,12 @@ struct Transformer { tensor.name == reserved::symm_label(); if (tensor.name == reserved::antisymm_label()) { perm_symm = Symmetry::Antisymm; - } else if (tensor.name == reserved::symm_label()) { + } + if (is_reserved_symmetrizer) { + // both act on indistinguishable particles, hence are column-symmetric + // (for  this also follows from its Antisymm perm symmetry). Force it + // rather than passing the Context's column default through, which the + // Tensor ctor would reject as a contradicting *explicit* request. column_symm = ColumnSymmetry::Symm; } // (Anti)symmetrization operators are always braket-Nonsymm. When no braket diff --git a/SeQuant/domain/mbpt/biorthogonalization.cpp b/SeQuant/domain/mbpt/biorthogonalization.cpp index 0cae180a89..0c4b9174c8 100644 --- a/SeQuant/domain/mbpt/biorthogonalization.cpp +++ b/SeQuant/domain/mbpt/biorthogonalization.cpp @@ -651,11 +651,7 @@ ExprPtr biorthogonal_transform_pre_nnsproject_impl( auto bixs = ext_idxs | transform([](auto&& vec) { return get_bra_idx(vec); }); auto kixs = ext_idxs | transform([](auto&& vec) { return get_ket_idx(vec); }); - // the reserved symmetrizer Ŝ is column (particle) symmetric; request Symm - // explicitly since the programmatic Tensor default is column-Nonsymm - ExprPtr S_tensor = - ex(Tensor{reserved::symm_label(), bra(kixs), ket(bixs), - Symmetry::Nonsymm, std::nullopt, ColumnSymmetry::Symm}); + ExprPtr S_tensor = make_symmetrizer(bra(kixs), ket(bixs)); if (factor_out_nns_projector) { if (ext_idxs.size() > 1) { diff --git a/SeQuant/domain/mbpt/spin.cpp b/SeQuant/domain/mbpt/spin.cpp index 197b2885d7..81a4519f76 100644 --- a/SeQuant/domain/mbpt/spin.cpp +++ b/SeQuant/domain/mbpt/spin.cpp @@ -631,14 +631,12 @@ ExprPtr symmetrize_expr(const ProductPtr& product) { SEQUANT_ASSERT(A_tensor.rank() > 1); auto S = Tensor{}; + // the symmetrizer Ŝ symmetrizes the {bra,ket} particle columns: it is + // column-symmetric with Nonsymm bra/ket permutational symmetry (see + // sequant::symmetrizer_symmetries / make_symmetrizer) if (A_is_nconserving) { - // the symmetrizer Ŝ symmetrizes the {bra,ket} particle columns: it is - // column-symmetric (with Nonsymm bra/ket permutational symmetry). The - // programmatic Tensor default is column-Nonsymm, so request Symm explicitly - // (matching the deserializer, which forces Symm for the reserved Ŝ). S = Tensor(reserved::symm_label(), A_tensor.bra(), A_tensor.ket(), - A_tensor.aux(), Symmetry::Nonsymm, std::nullopt, - ColumnSymmetry::Symm); + A_tensor.aux(), symmetrizer_symmetries); } 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(), @@ -646,8 +644,8 @@ ExprPtr symmetrize_expr(const ProductPtr& product) { container::svector ket_list(A_tensor.ket().begin(), A_tensor.ket().begin() + n); S = Tensor(reserved::symm_label(), bra(std::move(bra_list)), - ket(std::move(ket_list)), A_tensor.aux(), Symmetry::Nonsymm, - std::nullopt, ColumnSymmetry::Symm); + ket(std::move(ket_list)), A_tensor.aux(), + symmetrizer_symmetries); } const auto nf = rational{1, factorial(S.ket_rank())}; diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index e0f3cec742..72fd03a66f 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -544,6 +544,22 @@ TEST_CASE("serialization", "[serialization]") { REQUIRE_NOTHROW(deserialize(L"Â{i1,i2;a1,a2}")); } } + + SECTION("(anti)symmetrization operators are always column symmetric") { + // ... including under a Context whose column default is the library + // default Nonsymm: the deserializer must force Symm rather than pass + // the (contradicting) Context default to the Tensor ctor + auto ctx = get_default_context(); + ctx.set(ColumnSymmetry::Nonsymm); + auto resetter = set_scoped_default_context(ctx); + for (const auto& input : + {L"Ŝ{i1,i2;a1,a2}", L"Â{i1,i2;a1,a2}", L"Ŝ{i1,i2;a1,a2}:N-N-N", + L"Â{i1,i2;a1,a2}:A-N-N"}) { + ExprPtr expr; + REQUIRE_NOTHROW(expr = deserialize(input)); + REQUIRE(expr->as().column_symmetry() == ColumnSymmetry::Symm); + } + } } } diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index b20440889e..8637062564 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -25,8 +25,8 @@ #include #include -// the particle-symmetric default symmetry pack `ps` (column = Symm) is defined -// in catch2_sequant.hpp and shared across the MBPT test TUs +// the `particle_symmetric` symmetry pack (column = Symm) is defined in +// catch2_sequant.hpp and shared across the MBPT test TUs TEST_CASE("tensor", "[elements]") { using namespace sequant; @@ -117,12 +117,14 @@ TEST_CASE("tensor", "[elements]") { SECTION("null indices") { // null indices ok in asymmetric bra or ket REQUIRE_NOTHROW(Tensor(L"N", bra{L"i_2", L"", L"i_3"}, - ket{L"", L"i_1", L""}, aux{}, ps)); + ket{L"", L"i_1", L""}, aux{}, particle_symmetric)); REQUIRE_NOTHROW(Tensor(L"N", bra{L"", L"i_1", L""}, - ket{L"i_2", L"", L"i_3"}, aux{}, ps)); + ket{L"i_2", L"", L"i_3"}, aux{}, + particle_symmetric)); - REQUIRE_NOTHROW(Tensor(L"N", bra{L""}, ket{L"i_1"}, aux{}, ps)); - Tensor t(L"N", bra{L""}, ket{L"i_1"}, aux{}, ps); + REQUIRE_NOTHROW( + Tensor(L"N", bra{L""}, ket{L"i_1"}, aux{}, particle_symmetric)); + Tensor t(L"N", bra{L""}, ket{L"i_1"}, aux{}, particle_symmetric); REQUIRE(t.bra_rank() == 0); REQUIRE(t.ket_rank() == 1); REQUIRE(t.bra_net_rank() == 0); @@ -132,25 +134,29 @@ TEST_CASE("tensor", "[elements]") { // in fact slots of asymmetric particle-symmetric tensors are kept in // canonical order - REQUIRE(Tensor(L"N", bra{L""}, ket{L"i_1"}, aux{}, ps) == - Tensor(L"N", bra{}, ket{L"i_1"}, aux{}, ps)); + REQUIRE(Tensor(L"N", bra{L""}, ket{L"i_1"}, aux{}, particle_symmetric) == + Tensor(L"N", bra{}, ket{L"i_1"}, aux{}, particle_symmetric)); REQUIRE(Tensor(L"N", bra{L"i_2", L"", L"i_3"}, ket{L"", L"i_1", L""}, - aux{}, ps) == Tensor(L"N", bra{L"i_2", L"i_3"}, - ket{L"", L"", L"i_1"}, aux{}, ps)); + aux{}, particle_symmetric) == + Tensor(L"N", bra{L"i_2", L"i_3"}, ket{L"", L"", L"i_1"}, aux{}, + particle_symmetric)); REQUIRE(Tensor(L"N", bra{L"", L"i_1", L""}, ket{L"i_2", L"", L"i_3"}, - aux{}, ps) == Tensor(L"N", bra{L"i_1", L"", L""}, - ket{L"", L"i_2", L"i_3"}, aux{}, ps)); - REQUIRE(Tensor(L"N", bra{L"", L"i_1", L"", L"i_4"}, - ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, ps) == - Tensor(L"N", bra{L"i_4", L"i_1", L"", L""}, - ket{L"i_5", L"", L"i_2", L"i_3"}, aux{}, ps)); + aux{}, particle_symmetric) == + Tensor(L"N", bra{L"i_1", L"", L""}, ket{L"", L"i_2", L"i_3"}, + aux{}, particle_symmetric)); + REQUIRE( + Tensor(L"N", bra{L"", L"i_1", L"", L"i_4"}, + ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, particle_symmetric) == + Tensor(L"N", bra{L"i_4", L"i_1", L"", L""}, + ket{L"i_5", L"", L"i_2", L"i_3"}, aux{}, particle_symmetric)); // in fact unnecessary null indices are dropped in canonicalization - REQUIRE(Tensor(L"N", bra{L"", L"i_1", L"", L"i_4"}, - ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, ps) == - Tensor(L"N", bra{L"i_4", L"i_1"}, - ket{L"i_5", L"", L"i_2", L"i_3"}, aux{}, ps)); + REQUIRE( + Tensor(L"N", bra{L"", L"i_1", L"", L"i_4"}, + ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, particle_symmetric) == + Tensor(L"N", bra{L"i_4", L"i_1"}, ket{L"i_5", L"", L"i_2", L"i_3"}, + aux{}, particle_symmetric)); Tensor t5(L"N", bra{L"", L"i_1", L"", L"i_4"}, - ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, ps); + ket{L"i_2", L"", L"i_3", L"i_5"}, aux{}, particle_symmetric); REQUIRE(t5.bra_rank() == 2); REQUIRE(t5.bra_net_rank() == 2); REQUIRE(t5.bra()[0] == L"i_4"); @@ -345,7 +351,7 @@ TEST_CASE("tensor", "[elements]") { auto t2 = Tensor(L"F", bra{L"i_1"}, ket{L"i_2"}, aux{L"i_3"}); auto t3 = Tensor(L"F", bra{L"i_1"}, ket{L"i_2"}, aux{L"i_3", L"i_4"}); auto t4 = Tensor(L"F", bra{Index(L"i_1", {L"i_5", L"i_6"}), Index{}}, - ket{L"", L"i_2"}, aux{L"i_3", L"i_4"}, ps); + ket{L"", L"i_2"}, aux{L"i_3", L"i_4"}, particle_symmetric); auto h1 = ex(L"F", bra{L"i_1"}, ket{L"i_2"}) * ex(cre({L"i_1"}), ann({L"i_2"})); @@ -597,3 +603,67 @@ TEST_CASE("tensor_hermiticity", "[elements]") { REQUIRE(t.column_symmetry() == ColumnSymmetry::Symm); } } + +TEST_CASE("(anti)symmetrizer factories", "[elements]") { + using namespace sequant; + + SECTION("defining symmetries") { + const auto S = make_symmetrizer(bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}); + REQUIRE(S->as().label() == reserved::symm_label()); + REQUIRE(S->as().symmetry() == Symmetry::Nonsymm); + REQUIRE(S->as().braket_symmetry() == BraKetSymmetry::Nonsymm); + REQUIRE(S->as().column_symmetry() == ColumnSymmetry::Symm); + + const auto A = + make_antisymmetrizer(bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}); + REQUIRE(A->as().label() == reserved::antisymm_label()); + REQUIRE(A->as().symmetry() == Symmetry::Antisymm); + REQUIRE(A->as().braket_symmetry() == BraKetSymmetry::Nonsymm); + REQUIRE(A->as().column_symmetry() == ColumnSymmetry::Symm); + + // the factories agree with what the deserializer produces + REQUIRE(*S == *deserialize(L"Ŝ{i_1,i_2;a_1,a_2}")); + REQUIRE(*A == *deserialize(L"Â{i_1,i_2;a_1,a_2}")); + + // ... and with the aux-index overloads + REQUIRE_NOTHROW(make_symmetrizer(bra{L"i_1"}, ket{L"a_1"}, aux{L"x_1"})); + REQUIRE_NOTHROW( + make_antisymmetrizer(bra{L"i_1"}, ket{L"a_1"}, aux{L"x_1"})); + } + + SECTION("ctor rejects contradicting symmetries") { + // braket symmetry, like column symmetry below, is a defining property of a + // reserved (anti)symmetrizer, not a free parameter + REQUIRE_THROWS_AS( + Tensor(reserved::symm_label(), bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, + TensorSymmetries{.braket = BraKetSymmetry::Symm}), + Exception); + REQUIRE_THROWS_AS( + Tensor(reserved::symm_label(), bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, + TensorSymmetries{.column = ColumnSymmetry::Nonsymm}), + Exception); + // ... but for  a Nonsymm column request is *promoted* rather than + // rejected: Antisymm bra/ket permutational symmetry implies column + // symmetry for any tensor, reserved or not, and that promotion runs first + REQUIRE(Tensor(reserved::antisymm_label(), bra{L"i_1", L"i_2"}, + ket{L"a_1", L"a_2"}, + TensorSymmetries{.perm = Symmetry::Antisymm, + .column = ColumnSymmetry::Nonsymm}) + .column_symmetry() == ColumnSymmetry::Symm); + REQUIRE_THROWS_AS( + Tensor(reserved::antisymm_label(), bra{L"i_1", L"i_2"}, + ket{L"a_1", L"a_2"}, + TensorSymmetries{.perm = Symmetry::Nonsymm, + .column = ColumnSymmetry::Nonsymm}), + Exception); + + // an *unspecified* column symmetry is silently supplied, so that the + // library's Context-independent column default (Nonsymm) does not produce + // a symmetrizer that differs from the deserialized one + const auto S = Tensor(reserved::symm_label(), bra{L"i_1", L"i_2"}, + ket{L"a_1", L"a_2"}); + REQUIRE(S.column_symmetry() == ColumnSymmetry::Symm); + REQUIRE(S == make_symmetrizer(bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}) + ->as()); + } +} From afad2353e6b98a296dc1cecb38de5276e9514c14 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 4 Aug 2026 12:59:47 -0400 Subject: [PATCH 17/22] tests: rename the shared symmetry pack `ps` -> `particle_symmetric` Review feedback (Krzmbrzl): a bare `ps` at ~180 construction sites is opaque to anyone who hasn't read its definition. `particle_symmetric` says what the pack *is* (column = Symm) rather than merely that it is a default, and matches the example spelled out in the TensorSymmetries docs. Renamed in the thc.cpp copy too, for consistency. --- SeQuant/domain/mbpt/rules/thc.cpp | 17 +- tests/unit/catch2_sequant.hpp | 3 +- tests/unit/test_canonicalize.cpp | 237 ++++++++++++++----------- tests/unit/test_spin.cpp | 286 ++++++++++++++++-------------- tests/unit/test_wick.cpp | 10 +- 5 files changed, 306 insertions(+), 247 deletions(-) diff --git a/SeQuant/domain/mbpt/rules/thc.cpp b/SeQuant/domain/mbpt/rules/thc.cpp index e2b669b7b8..463b01dffc 100644 --- a/SeQuant/domain/mbpt/rules/thc.cpp +++ b/SeQuant/domain/mbpt/rules/thc.cpp @@ -20,7 +20,7 @@ namespace sequant::mbpt { // construction is Context-independent (see Tensor::Defaults), so spell that // default out here and feed it at every factor construction site. namespace { -constexpr TensorSymmetries ps{.column = ColumnSymmetry::Symm}; +constexpr TensorSymmetries particle_symmetric{.column = ColumnSymmetry::Symm}; } // namespace ExprPtr tensor_hypercontract_impl(Tensor const& tnsr, Index const& aux_idx_1, @@ -32,20 +32,21 @@ ExprPtr tensor_hypercontract_impl(Tensor const& tnsr, Index const& aux_idx_1, && tnsr.aux_rank() == 0); auto t1 = ex(factor_label, bra({ranges::front(tnsr.bra())}), ket(), - aux({aux_idx_1}), ps); + aux({aux_idx_1}), particle_symmetric); auto t2 = ex(factor_label, bra(), ket({ranges::front(tnsr.ket())}), - aux({aux_idx_1}), ps); + aux({aux_idx_1}), particle_symmetric); auto t3 = ex(factor_label, bra({ranges::back(tnsr.bra())}), ket(), - aux({aux_idx_2}), ps); + aux({aux_idx_2}), particle_symmetric); auto t4 = ex(factor_label, bra(), ket({ranges::back(tnsr.ket())}), - aux({aux_idx_2}), ps); - auto z = ex(aux_label, bra(), ket(), aux({aux_idx_1, aux_idx_2}), ps); + aux({aux_idx_2}), particle_symmetric); + auto z = ex(aux_label, bra(), ket(), aux({aux_idx_1, aux_idx_2}), + particle_symmetric); if (tnsr.symmetry() == Symmetry::Antisymm) { auto t1a = ex(factor_label, bra({ranges::back(tnsr.bra())}), ket(), - aux({aux_idx_1}), ps); + aux({aux_idx_1}), particle_symmetric); auto t3a = ex(factor_label, bra({ranges::front(tnsr.bra())}), ket(), - aux({aux_idx_2}), ps); + aux({aux_idx_2}), particle_symmetric); return (t1 * t2 * z * t3 * t4) - (t1a * t2 * z * t3a * t4); } diff --git a/tests/unit/catch2_sequant.hpp b/tests/unit/catch2_sequant.hpp index b71475b08b..a867c2b575 100644 --- a/tests/unit/catch2_sequant.hpp +++ b/tests/unit/catch2_sequant.hpp @@ -31,7 +31,8 @@ namespace sequant { /// Defined `inline` in this shared header (rather than once per TU) so that /// unity/jumbo test builds see a single definition instead of colliding /// anonymous-namespace copies. -inline constexpr TensorSymmetries ps{.column = ColumnSymmetry::Symm}; +inline constexpr TensorSymmetries particle_symmetric{.column = + ColumnSymmetry::Symm}; } // namespace sequant namespace Catch { diff --git a/tests/unit/test_canonicalize.cpp b/tests/unit/test_canonicalize.cpp index 7ec2c57eab..be93d48d62 100644 --- a/tests/unit/test_canonicalize.cpp +++ b/tests/unit/test_canonicalize.cpp @@ -28,8 +28,8 @@ #include #include -// the particle-symmetric default symmetry pack `ps` (column = Symm) is defined -// in catch2_sequant.hpp and shared across the MBPT test TUs +// the `particle_symmetric` symmetry pack (column = Symm) is defined in +// catch2_sequant.hpp and shared across the MBPT test TUs TEST_CASE("canonicalization", "[algorithms]") { using namespace sequant; @@ -44,22 +44,26 @@ TEST_CASE("canonicalization", "[algorithms]") { SECTION("Tensors") { { - auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, ps); + auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, + particle_symmetric); canonicalize(op); 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"}, ps); + auto op = ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_3", L"p_4"}, + particle_symmetric); canonicalize(op); 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"}, ps); + auto op = ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_4", L"p_3"}, + particle_symmetric); canonicalize(op); 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"}, ps); + auto op = ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, + particle_symmetric); canonicalize(op); REQUIRE_THAT(op, SimplifiesTo("g{p1,p2;p3,p4}")); } @@ -78,13 +82,14 @@ TEST_CASE("canonicalization", "[algorithms]") { // aux indices { - auto op = ex(L"B", bra{L"p_1"}, ket{L"p_2"}, aux{L"p_3"}, ps); + auto op = ex(L"B", bra{L"p_1"}, ket{L"p_2"}, aux{L"p_3"}, + particle_symmetric); canonicalize(op); 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"}, ps); + aux{L"p_5"}, particle_symmetric); canonicalize(op); REQUIRE_THAT(op, SimplifiesTo("B{p1,p2;p4,p3;p5}")); } @@ -111,10 +116,11 @@ TEST_CASE("canonicalization", "[algorithms]") { { auto input = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, ps) * - ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * - ex(L"t", bra{L"i_5"}, ket{L"a_1"}, ps) * - ex(L"t", bra{L"i_1", L"i_2"}, ket{L"a_5", L"a_2"}, ps); + ket{L"i_1", L"i_2"}, particle_symmetric) * + ex(L"f", bra{L"a_5"}, ket{L"i_5"}, particle_symmetric) * + ex(L"t", bra{L"i_5"}, ket{L"a_1"}, particle_symmetric) * + ex(L"t", bra{L"i_1", L"i_2"}, ket{L"a_5", L"a_2"}, + particle_symmetric); canonicalize(input); REQUIRE_THAT( input, @@ -123,10 +129,11 @@ TEST_CASE("canonicalization", "[algorithms]") { { auto input = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, ps) * - ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * - ex(L"t", bra{L"i_1"}, ket{L"a_5"}, ps) * - ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, ps); + ket{L"i_1", L"i_2"}, particle_symmetric) * + ex(L"f", bra{L"a_5"}, ket{L"i_5"}, particle_symmetric) * + ex(L"t", bra{L"i_1"}, ket{L"a_5"}, particle_symmetric) * + ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, + particle_symmetric); canonicalize(input); REQUIRE_THAT( input, @@ -172,11 +179,13 @@ TEST_CASE("canonicalization", "[algorithms]") { q2->adjoint(); auto input = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, ps) * - q2 * ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * - ex(L"p") * ex(L"t", bra{L"i_1"}, ket{L"a_5"}, ps) * + ket{L"i_1", L"i_2"}, particle_symmetric) * + q2 * ex(L"f", bra{L"a_5"}, ket{L"i_5"}, particle_symmetric) * + ex(L"p") * + ex(L"t", bra{L"i_1"}, ket{L"a_5"}, particle_symmetric) * ex(L"q1") * - ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, ps); + ex(L"t", bra{L"i_5", L"i_2"}, ket{L"a_1", L"a_2"}, + particle_symmetric); canonicalize(input); REQUIRE_THAT(input, SimplifiesTo("p q1 q2^* Ŝ{a_1,a_2;i_1,i_2} f{a_3;i_3} " @@ -187,19 +196,21 @@ TEST_CASE("canonicalization", "[algorithms]") { Symmetry::Nonsymm, BraKetSymmetry::Nonsymm, ColumnSymmetry::Symm); f2->adjoint(); - auto input1 = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, ps) * - ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * - ex(L"t", bra{L"i_1"}, ket{L"a_5"}, ps) * f2; + auto input1 = + ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, + ket{L"i_1", L"i_2"}, particle_symmetric) * + ex(L"f", bra{L"a_5"}, ket{L"i_5"}, particle_symmetric) * + ex(L"t", bra{L"i_1"}, ket{L"a_5"}, particle_symmetric) * f2; canonicalize(input1); REQUIRE_THAT(input1, SimplifiesTo("Ŝ{a_1,a_2;i_1,i_2} f{a_3;i_3} " "f⁺{i_1,i_3;a_1,a_2}:N-N-S t{i_2;a_3}")); - auto input2 = ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, - ket{L"i_1", L"i_2"}, ps) * - ex(L"f", bra{L"a_5"}, ket{L"i_5"}, ps) * - ex(L"t", bra{L"i_1"}, ket{L"a_5"}, ps) * f2 * - ex(L"w") * ex(rational{1, 2}); + auto input2 = + ex(reserved::symm_label(), bra{L"a_1", L"a_2"}, + ket{L"i_1", L"i_2"}, particle_symmetric) * + ex(L"f", bra{L"a_5"}, ket{L"i_5"}, particle_symmetric) * + ex(L"t", bra{L"i_1"}, ket{L"a_5"}, particle_symmetric) * f2 * + ex(L"w") * ex(rational{1, 2}); canonicalize(input2); REQUIRE_THAT(input2, SimplifiesTo("1/2 w Ŝ{a_1,a_2;i_1,i_2} f{a_3;i_3} " @@ -207,11 +218,14 @@ TEST_CASE("canonicalization", "[algorithms]") { } // with aux indices { - auto input = ex(rational{1, 2}) * - ex(L"B", bra{L"p_2"}, ket{L"p_4"}, aux{L"p_5"}, ps) * - ex(L"B", bra{L"p_1"}, ket{L"p_3"}, aux{L"p_5"}, ps) * - ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps) * - ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps); + auto input = + ex(rational{1, 2}) * + ex(L"B", bra{L"p_2"}, ket{L"p_4"}, aux{L"p_5"}, + particle_symmetric) * + ex(L"B", bra{L"p_1"}, ket{L"p_3"}, aux{L"p_5"}, + particle_symmetric) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, particle_symmetric) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, particle_symmetric); canonicalize(input); // because bra and ket are in same space dummy renaming flips the bra and // ket even though the tensors are not bra-ket symmetric @@ -467,13 +481,15 @@ TEST_CASE("canonicalization", "[algorithms]") { // CASE 1: Non-symmetric tensors auto input = ex(rational{1, 2}) * - ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, ps) * - ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * - ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps) + + ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, + particle_symmetric) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, particle_symmetric) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, particle_symmetric) + ex(rational{1, 2}) * - ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, ps) * - ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * - ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps); + ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, + particle_symmetric) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, particle_symmetric) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, particle_symmetric); simplify(input); canonicalize(input); REQUIRE_THAT(input, @@ -482,50 +498,53 @@ TEST_CASE("canonicalization", "[algorithms]") { // CASE 2: Symmetric tensors { - auto input = ex(rational{1, 2}) * - ex(L"g", bra{L"p_1", L"p_2"}, - ket{L"p_3", L"p_4"}, Symmetry::Symm) * - ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * - ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps) + - ex(rational{1, 2}) * - ex(L"g", bra{L"p_2", L"p_1"}, - ket{L"p_4", L"p_3"}, Symmetry::Symm) * - ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * - ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps); + auto input = + ex(rational{1, 2}) * + ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, + Symmetry::Symm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, particle_symmetric) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, particle_symmetric) + + ex(rational{1, 2}) * + ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, + Symmetry::Symm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, particle_symmetric) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, particle_symmetric); canonicalize(input); REQUIRE_THAT(input, EquivalentTo("g{p2,p3;p1,p4}:S t{p1;p2} t{p4;p3}")); } // Case 3: Anti-symmetric tensors { - auto input = ex(rational{1, 2}) * - ex(L"g", bra{L"p_1", L"p_2"}, - ket{L"p_3", L"p_4"}, Symmetry::Antisymm) * - ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * - ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps) + - ex(rational{1, 2}) * - ex(L"g", bra{L"p_2", L"p_1"}, - ket{L"p_4", L"p_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"p_3"}, ket{L"p_1"}, ps) * - ex(L"t", bra{L"p_4"}, ket{L"p_2"}, ps); + auto input = + ex(rational{1, 2}) * + ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, particle_symmetric) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, particle_symmetric) + + ex(rational{1, 2}) * + ex(L"g", bra{L"p_2", L"p_1"}, ket{L"p_4", L"p_3"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"p_3"}, ket{L"p_1"}, particle_symmetric) * + ex(L"t", bra{L"p_4"}, ket{L"p_2"}, particle_symmetric); canonicalize(input); REQUIRE_THAT(input, EquivalentTo("g{p2,p3;p1,p4}:A t{p1;p2} t{p4;p3}")); } // Case 4: permuted indices { - auto input = ex(rational{4, 3}) * - ex(L"g", bra{L"i_3", L"i_4"}, - ket{L"a_3", L"i_1"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_3"}, ps) * - ex(L"t", bra{L"a_1", L"a_3"}, - ket{L"i_4", L"i_2"}, Symmetry::Antisymm) - - ex(rational{1, 3}) * - ex(L"g", bra{L"i_3", L"i_4"}, - ket{L"i_1", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * - ex(L"t", bra{L"a_1", L"a_3"}, - ket{L"i_3", L"i_2"}, Symmetry::Antisymm); + auto input = + ex(rational{4, 3}) * + ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"i_1"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_4", L"i_2"}, + Symmetry::Antisymm) - + ex(rational{1, 3}) * + ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_3", L"i_2"}, + Symmetry::Antisymm); canonicalize(input); REQUIRE(input->size() == 1); REQUIRE_THAT(input, @@ -536,13 +555,17 @@ TEST_CASE("canonicalization", "[algorithms]") { { auto input = ex(rational{4, 3}) * - ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"i_1"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_3"}, ps) * - ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_4", L"i_2"}, ps) - + ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"i_1"}, + particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_4", L"i_2"}, + particle_symmetric) - ex(rational{1, 3}) * - ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * - ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_3", L"i_2"}, ps); + ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, + particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_3", L"i_2"}, + particle_symmetric); canonicalize(input); REQUIRE(input->size() == 1); @@ -556,16 +579,16 @@ TEST_CASE("canonicalization", "[algorithms]") { auto input = ex(-4) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, ps) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * + ket{L"a_1", L"a_2", L"a_3"}, particle_symmetric) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, particle_symmetric) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_3", L"i_2", L"i_4"}, ps) + + ket{L"i_3", L"i_2", L"i_4"}, particle_symmetric) + ex(-4) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, ps) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * + ket{L"a_1", L"a_2", L"a_3"}, particle_symmetric) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, particle_symmetric) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_2", L"i_4", L"i_3"}, ps); + ket{L"i_2", L"i_4", L"i_3"}, particle_symmetric); canonicalize(input); REQUIRE_THAT( input, @@ -577,17 +600,17 @@ TEST_CASE("canonicalization", "[algorithms]") { auto term1 = ex(-4) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, ps) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * + ket{L"a_1", L"a_2", L"a_3"}, particle_symmetric) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, particle_symmetric) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_3", L"i_2", L"i_4"}, ps); + ket{L"i_3", L"i_2", L"i_4"}, particle_symmetric); auto term2 = ex(-4) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, ps) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * + ket{L"a_1", L"a_2", L"a_3"}, particle_symmetric) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, particle_symmetric) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_2", L"i_4", L"i_3"}, ps); + ket{L"i_2", L"i_4", L"i_3"}, particle_symmetric); canonicalize(term1); canonicalize(term2); REQUIRE_THAT(term1, @@ -608,16 +631,16 @@ TEST_CASE("canonicalization", "[algorithms]") { auto input = ex(2) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, ps) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * + ket{L"a_1", L"a_2", L"a_3"}, particle_symmetric) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, particle_symmetric) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_3", L"i_4", L"i_2"}, ps) + + ket{L"i_3", L"i_4", L"i_2"}, particle_symmetric) + ex(2) * ex(reserved::symm_label(), bra{L"i_1", L"i_2", L"i_3"}, - ket{L"a_1", L"a_2", L"a_3"}, ps) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps) * + ket{L"a_1", L"a_2", L"a_3"}, particle_symmetric) * + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, particle_symmetric) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, - ket{L"i_2", L"i_3", L"i_4"}, ps); + ket{L"i_2", L"i_3", L"i_4"}, particle_symmetric); canonicalize(input); REQUIRE_THAT( input, EquivalentTo( @@ -629,15 +652,21 @@ TEST_CASE("canonicalization", "[algorithms]") { { auto input = ex(rational{4, 3}) * - ex(L"B", bra{L"i_3"}, ket{L"a_3"}, aux{L"p_5"}, ps) * - ex(L"B", bra{L"i_4"}, ket{L"i_1"}, aux{L"p_5"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_3"}, ps) * - ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_4", L"i_2"}, ps) - + ex(L"B", bra{L"i_3"}, ket{L"a_3"}, aux{L"p_5"}, + particle_symmetric) * + ex(L"B", bra{L"i_4"}, ket{L"i_1"}, aux{L"p_5"}, + particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_4", L"i_2"}, + particle_symmetric) - ex(rational{1, 3}) * - ex(L"B", bra{L"i_3"}, ket{L"i_1"}, aux{L"p_5"}, ps) * - ex(L"B", bra{L"i_4"}, ket{L"a_3"}, aux{L"p_5"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * - ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_3", L"i_2"}, ps); + ex(L"B", bra{L"i_3"}, ket{L"i_1"}, aux{L"p_5"}, + particle_symmetric) * + ex(L"B", bra{L"i_4"}, ket{L"a_3"}, aux{L"p_5"}, + particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_3", L"i_2"}, + particle_symmetric); canonicalize(input); simplify(input); diff --git a/tests/unit/test_spin.cpp b/tests/unit/test_spin.cpp index dbd7ab745b..7d35a1ee05 100644 --- a/tests/unit/test_spin.cpp +++ b/tests/unit/test_spin.cpp @@ -33,8 +33,8 @@ #include #include -// the particle-symmetric default symmetry pack `ps` (column = Symm) is defined -// in catch2_sequant.hpp and shared across the MBPT test TUs +// the particle-symmetric default symmetry pack `particle_symmetric` (column = +// Symm) is defined in catch2_sequant.hpp and shared across the MBPT test TUs TEST_CASE("spin", "[spin]") { using namespace sequant; @@ -64,8 +64,8 @@ TEST_CASE("spin", "[spin]") { Index i1(L"i_1"); Index a1(L"a_1", {i1}); - const auto expr = ex(L"t", bra{i1}, ket{a1}, ps) * - ex(L"F", bra{a1}, ket{i1}, ps); + const auto expr = ex(L"t", bra{i1}, ket{a1}, particle_symmetric) * + ex(L"F", bra{a1}, ket{i1}, particle_symmetric); REQUIRE_NOTHROW(spintrace(expr)); { // assume spin-free spaces auto expr_st = spintrace(expr); @@ -198,7 +198,7 @@ TEST_CASE("spin", "[spin]") { auto p3 = Index(L"p↑_3"); auto p4 = Index(L"p↓_4"); - auto input = ex(L"t", bra{p1, p2}, ket{p3, p4}, ps); + auto input = ex(L"t", bra{p1, p2}, ket{p3, p4}, particle_symmetric); REQUIRE(can_expand(input->as()) == true); REQUIRE(ms_conserving_columns(input->as()) == true); @@ -209,7 +209,7 @@ TEST_CASE("spin", "[spin]") { for (auto& i : result->as().const_braket_indices()) REQUIRE(i.space().base_key() == L"p"); - input = ex(L"t", bra{p1, p3}, ket{p2, p4}, ps); + input = ex(L"t", bra{p1, p3}, ket{p2, p4}, particle_symmetric); REQUIRE_THAT(swap_spin(input), EquivalentTo("t{p↓1,p↓3;p↑2,p↑4}")); REQUIRE(can_expand(input->as()) == false); REQUIRE(ms_conserving_columns(input->as()) == false); @@ -217,7 +217,7 @@ TEST_CASE("spin", "[spin]") { SECTION("Tensor: expand_antisymm") { // 1-body - auto input = ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps); + auto input = ex(L"t", bra{L"a_1"}, ket{L"i_1"}, particle_symmetric); auto result = expand_antisymm(input->as()); REQUIRE(input->as() == result->as()); REQUIRE(!result->is()); @@ -305,8 +305,9 @@ TEST_CASE("spin", "[spin]") { } SECTION("Product") { - const auto expr = ex(L"f", bra{L"i_1"}, ket{L"a_1"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps); + const auto expr = + ex(L"f", bra{L"i_1"}, ket{L"a_1"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_1"}, particle_symmetric); auto result = spintrace(expr); canonicalize(result); REQUIRE_THAT(result, EquivalentTo("2 f{i1;a1} t{a1;i1}")); @@ -315,11 +316,12 @@ TEST_CASE("spin", "[spin]") { SECTION("Scaled Product") { { // 1/2 * g * t1 * t1 - const auto expr = ex(rational{1, 2}) * - ex(L"g", bra{L"i_1", L"i_2"}, - ket{L"a_1", L"a_2"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps); + const auto expr = + ex(rational{1, 2}) * + ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_1"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, particle_symmetric); auto result = spintrace(expr); canonicalize(result); REQUIRE_THAT(result, @@ -352,13 +354,15 @@ TEST_CASE("spin", "[spin]") { SECTION("Sum") { // f * t1 + 1/2 * g * t1 * t1 + 1/4 * g * t2 - const auto ex1 = ex(L"f", bra{L"i_1"}, ket{L"a_1"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps); - const auto ex2 = ex(rational{1, 2}) * - ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, - Symmetry::Antisymm) * - ex(L"t", bra{L"a_1"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps); + const auto ex1 = + ex(L"f", bra{L"i_1"}, ket{L"a_1"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_1"}, particle_symmetric); + const auto ex2 = + ex(rational{1, 2}) * + ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_1"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, particle_symmetric); const auto ex3 = ex(rational{1, 4}) * ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, Symmetry::Antisymm) * @@ -424,8 +428,8 @@ TEST_CASE("spin", "[spin]") { Symmetry::Antisymm) * ex(L"g", bra{L"a_1", L"a_2"}, ket{L"a_3", L"a_4"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_4"}, ket{L"i_2"}, ps); + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_4"}, ket{L"i_2"}, particle_symmetric); result = expand_A_op(input); REQUIRE(result->is()); REQUIRE(result->size() == 4); @@ -440,10 +444,10 @@ TEST_CASE("spin", "[spin]") { Symmetry::Antisymm) * ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"a_4"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_4"}, ket{L"i_2"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_3"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps); + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_4"}, ket{L"i_2"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_3"}, particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, particle_symmetric); result = expand_A_op(input); REQUIRE_THAT( result, @@ -562,11 +566,13 @@ SECTION("Expand Symmetrizer") { const auto input = ex(symm_label(), bra{L"i_1", L"i_2", L"i_3"}, ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Nonsymm) * - ex(L"g", bra{L"i_4", L"i_5"}, ket{L"a_4", L"a_5"}, ps) * - ex(L"t", bra{L"a_3"}, ket{L"i_4"}, ps) * - ex(L"t", bra{L"a_5"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_4"}, ket{L"i_2"}, ps) * - ex(L"t", bra{L"a_1", L"a_2"}, ket{L"i_5", L"i_3"}, ps); + ex(L"g", bra{L"i_4", L"i_5"}, ket{L"a_4", L"a_5"}, + particle_symmetric) * + ex(L"t", bra{L"a_3"}, ket{L"i_4"}, particle_symmetric) * + ex(L"t", bra{L"a_5"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_4"}, ket{L"i_2"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_2"}, ket{L"i_5", L"i_3"}, + particle_symmetric); auto result = S_maps(input); REQUIRE(result->is()); REQUIRE(result->size() == 6); @@ -637,12 +643,13 @@ SECTION("partial spintracing + S_maps = full spintracing") { SECTION("Symmetrize expression") { { // g * t1 + g * t1 - auto input = ex(L"g", bra{L"a_1", L"a_2"}, ket{L"i_1", L"a_3"}, - Symmetry::Symm) * - ex(L"t", bra{L"a_3"}, ket{L"i_2"}, ps) + - ex(L"g", bra{L"a_2", L"a_1"}, ket{L"i_2", L"a_3"}, - Symmetry::Symm) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps); + auto input = + ex(L"g", bra{L"a_1", L"a_2"}, ket{L"i_1", L"a_3"}, + Symmetry::Symm) * + ex(L"t", bra{L"a_3"}, ket{L"i_2"}, particle_symmetric) + + ex(L"g", bra{L"a_2", L"a_1"}, ket{L"i_2", L"a_3"}, + Symmetry::Symm) * + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, particle_symmetric); auto const ext_idxs = external_indices(input); auto bixs = ext_idxs | ranges::views::transform( @@ -650,7 +657,7 @@ SECTION("Symmetrize expression") { auto kixs = ext_idxs | ranges::views::transform( [](auto&& vec) { return get_ket_idx(vec); }); auto result = ex(Tensor{symm_label(), bra(std::move(kixs)), - ket(std::move(bixs)), ps}) * + ket(std::move(bixs)), particle_symmetric}) * input; REQUIRE_THAT(result, EquivalentTo("2 Ŝ{i1,i2;a1,a2} g{a1,a2;i2,a3}:S t{a3;i1}")); @@ -666,22 +673,23 @@ SECTION("Symmetrize expression") { auto kixs = ext_idxs | ranges::views::transform( [](auto&& vec) { return get_ket_idx(vec); }); auto result = ex(Tensor{symm_label(), bra(std::move(kixs)), - ket(std::move(bixs)), ps}) * + ket(std::move(bixs)), particle_symmetric}) * input; REQUIRE_THAT(result, EquivalentTo("Ŝ{i1,i2;a1,a2} * g{a1,a2;i1,i2}:S")); } { // g * t1 * t1 * t1 + g * t1 * t1 * t1 - auto input = ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, - Symmetry::Symm) * - ex(L"t", bra{L"a_1"}, ket{L"i_3"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * - ex(L"t", bra{L"a_3"}, ket{L"i_2"}, ps) + - ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_2", L"a_3"}, - Symmetry::Symm) * - ex(L"t", bra{L"a_2"}, ket{L"i_3"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_4"}, ps) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps); + auto input = + ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_1", L"a_3"}, + Symmetry::Symm) * + ex(L"t", bra{L"a_1"}, ket{L"i_3"}, particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, particle_symmetric) * + ex(L"t", bra{L"a_3"}, ket{L"i_2"}, particle_symmetric) + + ex(L"g", bra{L"i_3", L"i_4"}, ket{L"i_2", L"a_3"}, + Symmetry::Symm) * + ex(L"t", bra{L"a_2"}, ket{L"i_3"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_4"}, particle_symmetric) * + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, particle_symmetric); auto const ext_idxs = external_indices(input); auto bixs = ext_idxs | ranges::views::transform( @@ -690,7 +698,7 @@ SECTION("Symmetrize expression") { [](auto&& vec) { return get_ket_idx(vec); }); auto result = ex(Tensor{reserved::symm_label(), bra(std::move(kixs)), - ket(std::move(bixs)), ps}) * + ket(std::move(bixs)), particle_symmetric}) * input; REQUIRE_THAT( result, @@ -703,15 +711,17 @@ SECTION("Symmetrize expression") { ex(2) * ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"a_4"}, Symmetry::Symm) * - ex(L"t", bra{L"a_3"}, ket{L"i_3"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_4"}, ps) * - ex(L"t", bra{L"a_1", L"a_4"}, ket{L"i_1", L"i_2"}, ps) + + ex(L"t", bra{L"a_3"}, ket{L"i_3"}, particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_4"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_4"}, ket{L"i_1", L"i_2"}, + particle_symmetric) + ex(2) * ex(L"g", bra{L"i_3", L"i_4"}, ket{L"a_3", L"a_4"}, Symmetry::Symm) * - ex(L"t", bra{L"a_3"}, ket{L"i_3"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_4"}, ps) * - ex(L"t", bra{L"a_2", L"a_4"}, ket{L"i_2", L"i_1"}, ps); + ex(L"t", bra{L"a_3"}, ket{L"i_3"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_4"}, particle_symmetric) * + ex(L"t", bra{L"a_2", L"a_4"}, ket{L"i_2", L"i_1"}, + particle_symmetric); auto const ext_idxs = external_indices(input); auto bixs = ext_idxs | ranges::views::transform( @@ -720,7 +730,7 @@ SECTION("Symmetrize expression") { [](auto&& vec) { return get_ket_idx(vec); }); auto result = ex(Tensor{reserved::symm_label(), bra(std::move(kixs)), - ket(std::move(bixs)), ps}) * + ket(std::move(bixs)), particle_symmetric}) * input; simplify(result); REQUIRE(result->is() == false); @@ -740,16 +750,17 @@ SECTION("Swap bra kets") { // Tensor { - auto input = ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, ps); + auto input = ex(L"g", bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, + particle_symmetric); auto result = swap_bra_ket(input); REQUIRE_THAT(result, EquivalentTo("g{a1,a2;i1,i2}")); } // Product { - auto input = - ex(L"g", bra{L"a_5", L"a_6"}, ket{L"i_5", L"i_6"}, ps) * - ex(L"t", bra{L"i_2"}, ket{L"a_6"}, ps); + auto input = ex(L"g", bra{L"a_5", L"a_6"}, ket{L"i_5", L"i_6"}, + particle_symmetric) * + ex(L"t", bra{L"i_2"}, ket{L"a_6"}, particle_symmetric); auto result = swap_bra_ket(input); REQUIRE_THAT(result, EquivalentTo("g{i5,i6;a5,a6} t{a6;i2}")); } @@ -757,9 +768,10 @@ SECTION("Swap bra kets") { // Sum { auto input = - ex(L"f", bra{L"i_1"}, ket{L"i_5"}, ps) + - ex(L"g", bra{L"a_5", L"a_6"}, ket{L"i_5", L"i_6"}, ps) * - ex(L"t", bra{L"i_2"}, ket{L"a_6"}, ps); + ex(L"f", bra{L"i_1"}, ket{L"i_5"}, particle_symmetric) + + ex(L"g", bra{L"a_5", L"a_6"}, ket{L"i_5", L"i_6"}, + particle_symmetric) * + ex(L"t", bra{L"i_2"}, ket{L"a_6"}, particle_symmetric); auto result = swap_bra_ket(input); // TODO: This should be EquivalentTo but canonicalization currently doesn't // permit expressions that break co/contra variance of index contractions. @@ -829,8 +841,9 @@ SECTION("Closed-shell spintrace CCSD") { // These terms from CCSD R1 equations { // A * f - const auto input = ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"f", bra{L"a_1"}, ket{L"i_1"}, ps); + const auto input = + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"f", bra{L"a_1"}, ket{L"i_1"}, particle_symmetric); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -841,10 +854,11 @@ SECTION("Closed-shell spintrace CCSD") { { // - A * f * t1 - const auto input = ex(-1) * - ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"f", bra{L"i_2"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_2"}, ps); + const auto input = + ex(-1) * + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"f", bra{L"i_2"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_2"}, particle_symmetric); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -853,9 +867,10 @@ SECTION("Closed-shell spintrace CCSD") { { // A * f * t1 - const auto input = ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"f", bra{L"a_1"}, ket{L"a_2"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_1"}, ps); + const auto input = + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"f", bra{L"a_1"}, ket{L"a_2"}, particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_1"}, particle_symmetric); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -912,11 +927,12 @@ SECTION("Closed-shell spintrace CCSD") { { // A * g * t1 * t1 - const auto input = ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"g", bra{L"i_2", L"a_1"}, - ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps); + const auto input = + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"g", bra{L"i_2", L"a_1"}, ket{L"a_2", L"a_3"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, particle_symmetric) * + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, particle_symmetric); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -926,11 +942,12 @@ SECTION("Closed-shell spintrace CCSD") { { // A * g * t2 * t2 - const auto input = ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"g", bra{L"i_2", L"i_3"}, - ket{L"i_1", L"a_2"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_3"}, ps); + const auto input = + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"g", bra{L"i_2", L"i_3"}, ket{L"i_1", L"a_2"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_3"}, particle_symmetric); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -940,11 +957,12 @@ SECTION("Closed-shell spintrace CCSD") { { // A * f * t1 * t1 - const auto input = ex(-1) * - ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"f", bra{L"i_2"}, ket{L"a_2"}, ps) * - ex(L"t", bra{L"a_2"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_2"}, ps); + const auto input = + ex(-1) * + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"f", bra{L"i_2"}, ket{L"a_2"}, particle_symmetric) * + ex(L"t", bra{L"a_2"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_2"}, particle_symmetric); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -953,13 +971,14 @@ SECTION("Closed-shell spintrace CCSD") { { // -1/2 * A * g * t1 * t2 - const auto input = ex(rational{-1, 2}) * - ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"g", bra{L"i_2", L"i_3"}, - ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_1"}, ket{L"i_2"}, ps) * - ex(L"t", bra{L"a_2", L"a_3"}, - ket{L"i_1", L"i_3"}, Symmetry::Antisymm); + const auto input = + ex(rational{-1, 2}) * + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_1"}, ket{L"i_2"}, particle_symmetric) * + ex(L"t", bra{L"a_2", L"a_3"}, ket{L"i_1", L"i_3"}, + Symmetry::Antisymm); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -970,13 +989,14 @@ SECTION("Closed-shell spintrace CCSD") { { // -1/2 * A * g * t1 * t2 - const auto input = ex(rational{-1, 2}) * - ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"g", bra{L"i_2", L"i_3"}, - ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_1", L"a_3"}, - ket{L"i_2", L"i_3"}, Symmetry::Antisymm); + const auto input = + ex(rational{-1, 2}) * + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_2", L"i_3"}, + Symmetry::Antisymm); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -987,13 +1007,14 @@ SECTION("Closed-shell spintrace CCSD") { { // A * g * t1 * t2 - const auto input = ex(1) * - ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * - ex(L"g", bra{L"i_2", L"i_3"}, - ket{L"a_2", L"a_3"}, Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps) * - ex(L"t", bra{L"a_1", L"a_3"}, - ket{L"i_1", L"i_3"}, Symmetry::Antisymm); + const auto input = + ex(1) * + ex(antisymm_label(), bra{L"i_1"}, ket{L"a_1"}) * + ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_3"}, ket{L"i_1", L"i_3"}, + Symmetry::Antisymm); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -1006,12 +1027,13 @@ SECTION("Closed-shell spintrace CCSD") { { // - A * g * t1 * t1 * t1 - auto input = ex(-1) * - ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, - Symmetry::Antisymm) * - ex(L"t", bra{L"a_2"}, ket{L"i_2"}, ps) * - ex(L"t", bra{L"a_3"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_1"}, ket{L"i_3"}, ps); + auto input = + ex(-1) * + ex(L"g", bra{L"i_2", L"i_3"}, ket{L"a_2", L"a_3"}, + Symmetry::Antisymm) * + ex(L"t", bra{L"a_2"}, ket{L"i_2"}, particle_symmetric) * + ex(L"t", bra{L"a_3"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_1"}, ket{L"i_3"}, particle_symmetric); auto result = ex(rational{1, 2}) * spintrace(input, IdxGroupList{{L"i_1", L"a_1"}}); expand(result); @@ -1073,7 +1095,7 @@ SECTION("Closed-shell spintrace CCSDT terms") { ket{L"a_1", L"a_2", L"a_3"}, Symmetry::Antisymm) * ex(L"t", bra{L"a_1", L"a_2", L"a_3"}, ket{L"i_2", L"i_3", L"i_4"}, Symmetry::Antisymm) * - ex(L"f", bra{L"i_4"}, ket{L"i_1"}, ps); + ex(L"f", bra{L"i_4"}, ket{L"i_1"}, particle_symmetric); auto result = expand_A_op(input); REQUIRE(result->size() == 36); @@ -1479,9 +1501,9 @@ SECTION("Open-shell spin-tracing") { // Tensor canonicalize { - auto t3 = - ex(Tensor(L"t", bra{a3A, a2B, a2A}, ket{i1A, i2B, i3A}, ps)); - auto f = ex(Tensor(L"f", bra{a1A}, ket{a2A}, ps)); + auto t3 = ex(Tensor(L"t", bra{a3A, a2B, a2A}, ket{i1A, i2B, i3A}, + particle_symmetric)); + auto f = ex(Tensor(L"f", bra{a1A}, ket{a2A}, particle_symmetric)); auto ft3 = f * t3; ft3->canonicalize(); REQUIRE_THAT(ft3, EquivalentTo("f{a↑1;a↑2} t{a↑3,a↑2,a↓2;i↑1,i↑3,i↓2}")); @@ -1502,10 +1524,11 @@ SECTION("Open-shell spin-tracing") { // f_oo * t2 { - auto input = ex(rational{1, 2}) * - ex(L"f", bra{L"i_3"}, ket{L"i_1"}, ps) * - ex(L"t", bra{L"a_1", L"a_2"}, ket{L"i_2", L"i_3"}, - Symmetry::Antisymm); + auto input = + ex(rational{1, 2}) * + ex(L"f", bra{L"i_3"}, ket{L"i_1"}, particle_symmetric) * + ex(L"t", bra{L"a_1", L"a_2"}, ket{L"i_2", L"i_3"}, + Symmetry::Antisymm); auto result = open_shell_spintrace( input, IdxGroupList{{L"i_1", L"a_1"}, {L"i_2", L"a_2"}}); @@ -1540,10 +1563,11 @@ SECTION("Open-shell spin-tracing") { // f * t3 { - auto input = ex(rational{1, 12}) * - ex(L"f", bra{L"a_1"}, ket{L"a_4"}, ps) * - ex(L"t", bra{L"a_2", L"a_3", L"a_4"}, - ket{L"i_1", L"i_2", L"i_3"}, Symmetry::Antisymm); + auto input = + ex(rational{1, 12}) * + ex(L"f", bra{L"a_1"}, ket{L"a_4"}, particle_symmetric) * + ex(L"t", bra{L"a_2", L"a_3", L"a_4"}, + ket{L"i_1", L"i_2", L"i_3"}, Symmetry::Antisymm); auto result = open_shell_spintrace( input, IdxGroupList{{L"i_1", L"a_1"}, {L"i_2", L"a_2"}, {L"i_3", L"a_3"}}); @@ -1568,7 +1592,8 @@ SECTION("Open-shell spin-tracing") { Symmetry::Antisymm); auto g = Tensor(L"g", bra{i3A, i4A}, ket{i1A, i2A}, Symmetry::Antisymm); - auto t3 = Tensor(L"t", bra{a1A, a2A, a3B}, ket{i3A, i4A, i3B}, ps); + auto t3 = Tensor(L"t", bra{a1A, a2A, a3B}, ket{i3A, i4A, i3B}, + particle_symmetric); auto input = ex(A2_aab) * ex(g) * ex(t3); auto result = expand_A_op(input); @@ -1578,7 +1603,8 @@ SECTION("Open-shell spin-tracing") { "t{a↑_1,a↑_2,a↓_3;i↑_4,i↑_3,i↓_3}:N-N-S")); g = Tensor(L"g", bra{i4A, i5A}, ket{i1A, i2A}, Symmetry::Antisymm); - t3 = Tensor(L"t", bra{a1A, a2A, a3B}, ket{i4A, i5A, i3B}, ps); + t3 = Tensor(L"t", bra{a1A, a2A, a3B}, ket{i4A, i5A, i3B}, + particle_symmetric); input = ex(A2_aab) * ex(g) * ex(t3); result = expand_A_op(input); diff --git a/tests/unit/test_wick.cpp b/tests/unit/test_wick.cpp index 91b6c45394..ee45ff5784 100644 --- a/tests/unit/test_wick.cpp +++ b/tests/unit/test_wick.cpp @@ -35,8 +35,8 @@ namespace sequant { -// the particle-symmetric default symmetry pack `ps` (column = Symm) is defined -// in catch2_sequant.hpp and shared across the MBPT test TUs +// the particle-symmetric default symmetry pack `particle_symmetric` (column = +// Symm) is defined in catch2_sequant.hpp and shared across the MBPT test TUs struct WickAccessor {}; @@ -1018,8 +1018,10 @@ TEST_CASE("wick", "[algorithms][wick][valgrind_skip]") { // multiply tensor factors and expand auto wick_result_2 = - ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, ps) * - ex(L"t", bra{L"a_4", L"a_5"}, ket{L"i_4", L"i_5"}, ps) * + ex(L"g", bra{L"p_1", L"p_2"}, ket{L"p_3", L"p_4"}, + particle_symmetric) * + ex(L"t", bra{L"a_4", L"a_5"}, ket{L"i_4", L"i_5"}, + particle_symmetric) * wick_result; expand(wick_result_2); REQUIRE(wick_result_2->size() == 4); // still 4 terms From 4b9d5115843fa9b7657f51356b52d0cc6fdbb14e Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Tue, 4 Aug 2026 13:09:32 -0400 Subject: [PATCH 18/22] mbpt: build Kronecker deltas in F() via make_kronecker The two delta factors in `F()` were built through the public Tensor ctor, so their column symmetry fell back to the Context-independent `Nonsymm` default, while `make_kronecker()` (and the deserializer) build them column-`Symm`. Column symmetry participates in the tensor hash, so the two spellings of the same delta compare unequal and otherwise-identical terms would not merge -- exactly the failure mode `make_overlap`/`make_kronecker` carry a comment about. The adjacent `g` factors in the same expression already had the explicit `Symm`; the deltas were missed. --- SeQuant/domain/mbpt/op.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SeQuant/domain/mbpt/op.cpp b/SeQuant/domain/mbpt/op.cpp index e416a2e280..a5bf99db90 100644 --- a/SeQuant/domain/mbpt/op.cpp +++ b/SeQuant/domain/mbpt/op.cpp @@ -699,8 +699,7 @@ ExprPtr F(bool use_tensor, const IndexSpace& reference_occupied) { ketidxs.push_back(m2); return ex(L"g", bra(std::move(braidxs)), ket(std::move(ketidxs)), Symmetry::Antisymm) * - ex(kronecker_label(), bra{m2}, ket{m1}, - Symmetry::Nonsymm); + make_kronecker(m2, m1); } else { // opsymm == Symmetry::Nonsymm auto braidx_J = braidxs; braidx_J.push_back(m1); @@ -720,8 +719,7 @@ ExprPtr F(bool use_tensor, const IndexSpace& reference_occupied) { ex(L"g", bra(std::move(braidx_K)), ket(std::move(ketidxs_K)), Symmetry::Nonsymm, std::nullopt, ColumnSymmetry::Symm)) * - ex(kronecker_label(), bra{m2}, ket{m1}, - Symmetry::Nonsymm); + make_kronecker(m2, m1); } }); }; From edf0140a274a4d4c0ccd2f6b1fa2f31a5a74ba13 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sun, 23 Aug 2026 16:58:35 -0500 Subject: [PATCH 19/22] tensor: rename resolved_symmetries -> ResolvedSymmetries; trim comments Addresses the two follow-ups on #557: - rename the private `resolved_symmetries` helper struct to `ResolvedSymmetries`, for consistency with `TensorSymmetries` - trim the commentary added by this PR down to what is not already evident from the code: comments that merely restated the call being made (`request Symm explicitly` because `ColumnSymmetry::Symm` is right there, `unspecified attributes fall back to ...` above a `value_or(Defaults::...)`) are gone; the domain facts (mbpt tensors act on indistinguishable particles) and the non-obvious invariants (ordering constraint in check_symmetries, why the deserializer forces the reserved operators' symmetries, why base_field() reads intact indices) are kept, condensed. --- SeQuant/core/context.hpp | 13 +-- SeQuant/core/expressions/tensor.hpp | 94 +++++++------------ .../io/serialization/v1/ast_conversions.hpp | 23 ++--- .../core/io/serialization/v1/deserialize.cpp | 6 +- SeQuant/domain/mbpt/antisymmetrizer.cpp | 3 +- SeQuant/domain/mbpt/op.cpp | 20 ++-- SeQuant/domain/mbpt/rules/thc.cpp | 6 +- SeQuant/domain/mbpt/spin.cpp | 6 +- tests/unit/catch2_sequant.hpp | 11 +-- 9 files changed, 67 insertions(+), 115 deletions(-) diff --git a/SeQuant/core/context.hpp b/SeQuant/core/context.hpp index dc71891394..a2df03f4d3 100644 --- a/SeQuant/core/context.hpp +++ b/SeQuant/core/context.hpp @@ -58,15 +58,10 @@ class Context { constexpr static auto braket_typesetting = BraKetTypesetting::ContraSub; constexpr static auto braket_slot_typesetting = BraKetSlotTypesetting::TensorPackage; - // default symmetries used when *deserializing* a tensor whose symmetry is - // under-specified in the input; the library defaults are the *safest* (most - // general) possible, and applications can fine-tune them via Context for - // ergonomics (e.g. mbpt assumes particle-symmetric tensors). These do NOT - // affect the programmatic Tensor ctors, whose defaults are fixed and - // independent of the ambient Context. Note that there is no braket-symmetry - // default: braket symmetry is a *derived* property of a tensor (from its - // #Hermiticity and #base_field), so #hermiticity is the knob instead (cf. - // removal of Context::braket_symmetry). + // default symmetries of a *deserialized* tensor whose symmetry is + // under-specified in the input; the programmatic Tensor ctors are not + // affected (see Tensor::Defaults). There is no braket-symmetry default + // since braket symmetry is derived from #hermiticity and #base_field. constexpr static auto symmetry = Symmetry::Nonsymm; constexpr static auto hermiticity = Hermiticity::NonHermitian; constexpr static auto column_symmetry = ColumnSymmetry::Nonsymm; diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index 7a00d88fe0..bd59e01e21 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -57,14 +57,13 @@ DEFINE_STRONG_TYPE_FOR_RANGE_AND_RANGESIZE(aux); /// @brief named-parameter pack of a Tensor's symmetry attributes /// /// Passed to the Tensor constructors as a single argument (the named-parameter -/// idiom via designated initializers) instead of the positional -/// `Symmetry`/`BraKetSymmetry`/`ColumnSymmetry` arguments, sidestepping their -/// order strictness. Each field is optional: a field left unset falls back to -/// the *fixed* library default when the Tensor is constructed (see -/// Tensor::resolve_symmetries), exactly as an omitted positional argument -/// would. This makes it easy to define one symmetry pack for a translation unit -/// (e.g. `constexpr TensorSymmetries particle_symmetric{.column = -/// ColumnSymmetry::Symm};`) and reuse it at every construction site. +/// idiom via designated initializers) instead of the order-sensitive positional +/// `Symmetry`/`BraKetSymmetry`/`ColumnSymmetry` arguments. Each field is +/// optional; an unset one falls back to the *fixed* library default (see +/// Tensor::resolve_symmetries). This makes it easy to define one symmetry pack +/// for a translation unit (e.g. `constexpr TensorSymmetries +/// particle_symmetric{.column = ColumnSymmetry::Symm};`) and reuse it at every +/// construction site. /// /// @note #braket and #hermiticity are two ways to specify the same underlying /// trait: if #braket is set it is used directly (and, unless #hermiticity @@ -93,10 +92,9 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// symmetry argument (or an unset TensorSymmetries field) against these; see /// resolve_symmetries(). They are *independent of the active /// sequant::Context* -- unlike deserialization, whose corresponding defaults - /// come from Context. There is deliberately no BraKetSymmetry default: the - /// braket symmetry is never a free default but is *derived* from #hermiticity - /// and the tensor's #base_field via to_braket_symmetry() (so the default - /// Hermiticity #NonHermitian yields a #Nonsymm braket over any field). + /// come from Context. There is deliberately no BraKetSymmetry default: it is + /// *derived* from #hermiticity and the tensor's #base_field via + /// to_braket_symmetry(). struct Defaults { /// default bra/ket permutational Symmetry static constexpr Symmetry symmetry = Symmetry::Nonsymm; @@ -307,15 +305,13 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// the four symmetry attributes of a Tensor, fully resolved (no defaulting /// left to do) - struct resolved_symmetries { + struct ResolvedSymmetries { Symmetry symmetry; BraKetSymmetry braket_symmetry; Hermiticity hermiticity; ColumnSymmetry column_symmetry; - /// whether #column_symmetry was spelled out by the caller (rather than - /// defaulted); check_symmetries() rejects an explicit column symmetry that - /// contradicts a reserved label's defining symmetry, but silently supplies - /// the correct one when it was merely left unspecified + /// whether #column_symmetry was spelled out by the caller rather than + /// defaulted; see check_symmetries() bool column_symmetry_specified; }; @@ -336,23 +332,18 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// the boundary that turns under-specified external input into tensors; /// domains that need a different programmatic default (e.g. mbpt, whose /// tensors are particle/column-symmetric) pass it explicitly. - static resolved_symmetries resolve_symmetries(const TensorSymmetries &syms, - Field base_fld) { - // unspecified attributes fall back to the single source of truth + static ResolvedSymmetries resolve_symmetries(const TensorSymmetries &syms, + Field base_fld) { const Symmetry s_resolved = syms.perm.value_or(Defaults::symmetry); const ColumnSymmetry ps_resolved = syms.column.value_or(Defaults::column_symmetry); const Hermiticity h_resolved = syms.hermiticity.value_or(Defaults::hermiticity); - // braket symmetry is derived from the explicit-or-default Hermiticity and - // the base field unless it was given explicitly const BraKetSymmetry bks_resolved = syms.braket.has_value() ? *syms.braket : to_braket_symmetry(h_resolved, base_fld); - // report the exact #Hermiticity trait (incl. AntiHermitian, which the - // BraKetSymmetry round-trip cannot represent) when Hermiticity was given; - // otherwise, if only BraKetSymmetry was given, back-fill Hermiticity from - // it; otherwise use the (default) Hermiticity that already fed bks_resolved + // an explicit Hermiticity is reported verbatim, to preserve traits that + // the BraKetSymmetry round-trip cannot represent (e.g. AntiHermitian) const Hermiticity hermiticity_resolved = syms.hermiticity.has_value() ? *syms.hermiticity @@ -369,7 +360,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { Tensor(S &&label, const bra &bra_indices, const ket &ket_indices, const aux &aux_indices, reserved_tag, - resolved_symmetries rsym) + ResolvedSymmetries rsym) : label_(toUtf16(std::forward(label))), bra_(make_indices(bra_indices)), ket_(make_indices(ket_indices)), @@ -392,7 +383,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { Tensor(S &&label, bra &&bra_indices, ket &&ket_indices, aux &&aux_indices, reserved_tag, - resolved_symmetries rsym) + ResolvedSymmetries rsym) : label_(toUtf16(std::forward(label))), bra_(std::move(bra_indices)), ket_(std::move(ket_indices)), @@ -410,8 +401,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { canonicalize_slots(); } - // defaulting reserved-tag ctor (range form): resolves unspecified symmetries - // against fixed library defaults, then delegates to the resolved terminal + // defaulting reserved-tag ctor (range form) template @@ -431,9 +421,9 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { ket &&ket_indices, aux &&aux_indices, reserved_tag, const TensorSymmetries &syms = {}) - // base_field(bra_indices, ket_indices) is read during argument - // evaluation, before the delegated ctor moves the indices out -- safe - // regardless of argument evaluation order + // base_field() takes its arguments by value and the indices are moved + // only in the delegated ctor's member-init list, so this reads them + // intact : Tensor(std::forward(label), std::move(bra_indices), std::move(ket_indices), std::move(aux_indices), reserved_tag{}, resolve_symmetries( @@ -589,10 +579,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { Tensor(S &&label, const bra &bra_indices, const ket &ket_indices, Symmetry s, Hermiticity h, std::optional ps = std::nullopt) - // pass the Hermiticity through to the reserved-tag ctor, which derives - // braket_symmetry_ from it and base_field() before canonicalize_slots() - // runs (the latter keys off braket_symmetry_), and preserves the exact - // trait (incl. AntiHermitian) in hermiticity_. + // braket_symmetry_ must be derived from h before canonicalize_slots() + // runs, so let the reserved-tag ctor do it : Tensor(std::forward(label), bra_indices, ket_indices, sequant::aux{}, reserved_tag{}, TensorSymmetries{.perm = s, .hermiticity = h, .column = ps}) { @@ -616,10 +604,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { const ket &ket_indices, const aux &aux_indices, Symmetry s, Hermiticity h, std::optional ps = std::nullopt) - // pass the Hermiticity through to the reserved-tag ctor, which derives - // braket_symmetry_ from it and base_field() before canonicalize_slots() - // runs (the latter keys off braket_symmetry_), and preserves the exact - // trait (incl. AntiHermitian) in hermiticity_. + // braket_symmetry_ must be derived from h before canonicalize_slots() + // runs, so let the reserved-tag ctor do it : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices, reserved_tag{}, TensorSymmetries{.perm = s, .hermiticity = h, .column = ps}) { @@ -960,9 +946,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { void check_symmetries(bool column_symmetry_specified) { if (symmetry_ == Symmetry::Symm || symmetry_ == Symmetry::Antisymm) { // (Anti)symmetry in bra and ket indices automatically implies column - // symmetry. N.B. this promotion runs first so that the reserved-label - // check below sees the implied column symmetry (the antisymmetrizer  is - // Antisymm, hence column-symmetric by implication alone). + // symmetry. N.B. must run before the reserved-label check below, which + // would otherwise report the (Antisymm) antisymmetrizer as contradicting. column_symmetry_ = ColumnSymmetry::Symm; } @@ -976,15 +961,10 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { if (braket_symmetry_ != BraKetSymmetry::Nonsymm) throw Exception( "(Anti)symmetrization operators must not have braket symmetry"); - // (Anti)symmetrization operators act on indistinguishable particles, so - // they are always particle- (column-) symmetric; this is a defining - // property, not a free parameter, so reject a contradicting explicit - // request (like the braket symmetry above) and supply the correct value - // when none was given. The latter makes a (anti)symmetrizer identical - // however it was built -- programmatically, where the column-symmetry - // default is the Context-independent Nonsymm, or by deserialization -- - // since a mismatch would silently prevent otherwise-equal terms from - // cancelling. + // (Anti)symmetrization operators act on indistinguishable particles, + // hence are always column-symmetric; supplying it when unspecified makes + // them compare equal however they were built (a mismatch would silently + // prevent otherwise-equal terms from cancelling). if (column_symmetry_specified && column_symmetry_ != ColumnSymmetry::Symm) throw Exception( "(Anti)symmetrization operators must be column (particle) " @@ -1168,10 +1148,8 @@ static_assert(is_tensor, using TensorPtr = std::shared_ptr; inline ExprPtr make_overlap(const Index &bra_index, const Index &ket_index) { - // metric/overlap tensors are particle (column) symmetric by convention, and - // must compare equal to the same overlap parsed from text (which the - // deserializer builds Symm under an mbpt Context); the programmatic column - // default is Nonsymm, so request Symm explicitly. + // overlap tensors are particle (column) symmetric by convention, and must + // compare equal to the same overlap obtained by deserialization return ex(Tensor(reserved::overlap_label(), bra{bra_index}, ket{ket_index}, aux{}, Tensor::reserved_tag{}, TensorSymmetries{.perm = Symmetry::Nonsymm, @@ -1179,7 +1157,7 @@ inline ExprPtr make_overlap(const Index &bra_index, const Index &ket_index) { } inline ExprPtr make_kronecker(const Index &bra_index, const Index &ket_index) { - // see make_overlap: a Kronecker delta is likewise particle (column) symmetric + // as in make_overlap, a Kronecker delta is particle (column) symmetric return ex(Tensor(reserved::kronecker_label(), bra{bra_index}, ket{ket_index}, aux{}, Tensor::reserved_tag{}, TensorSymmetries{.perm = Symmetry::Nonsymm, diff --git a/SeQuant/core/io/serialization/v1/ast_conversions.hpp b/SeQuant/core/io/serialization/v1/ast_conversions.hpp index e27fc10628..ebb2d687f6 100644 --- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp +++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp @@ -323,11 +323,8 @@ struct Transformer { ann(std::move(braIndices)), vac); } - // Force the defining symmetry of each reserved (anti)symmetrization - // operator (overriding the parsed/default one). The two differ by design: - // the antisymmetrizer  antisymmetrizes within bra and within ket (a - // permutational #Symmetry), whereas the symmetrizer Ŝ symmetrizes the - // {bra,ket} particle columns (a #ColumnSymmetry). + // Force the defining symmetries of the reserved (anti)symmetrization + // operators; see sequant::{anti,}symmetrizer_symmetries. const bool is_reserved_symmetrizer = tensor.name == reserved::antisymm_label() || tensor.name == reserved::symm_label(); @@ -335,17 +332,15 @@ struct Transformer { perm_symm = Symmetry::Antisymm; } if (is_reserved_symmetrizer) { - // both act on indistinguishable particles, hence are column-symmetric - // (for  this also follows from its Antisymm perm symmetry). Force it - // rather than passing the Context's column default through, which the - // Tensor ctor would reject as a contradicting *explicit* request. + // force it rather than passing the Context's column default through, + // which the Tensor ctor would reject as a contradicting *explicit* + // request column_symm = ColumnSymmetry::Symm; } - // (Anti)symmetrization operators are always braket-Nonsymm. When no braket - // symmetry is spelled out, force Nonsymm rather than letting them inherit - // the Context's default Hermiticity (which could derive a non-Nonsymm - // braket and make a plain "Ŝ{...}"/"Â{...}" fail to construct). An explicit - // braket spec is left untouched so the Tensor ctor still rejects it. + // likewise, force braket-Nonsymm rather than inheriting the Context's + // default Hermiticity, which could derive a non-Nonsymm braket and make a + // plain "Ŝ{...}"/"Â{...}" fail to construct; an explicit braket spec is + // left untouched so that the Tensor ctor still rejects it if (is_reserved_symmetrizer && (!tensor.symmetry.has_value() || tensor.symmetry.value().braket_symm == ast::SymmetrySpec::unspecified)) diff --git a/SeQuant/core/io/serialization/v1/deserialize.cpp b/SeQuant/core/io/serialization/v1/deserialize.cpp index 5bcea928ed..c49c8588e6 100644 --- a/SeQuant/core/io/serialization/v1/deserialize.cpp +++ b/SeQuant/core/io/serialization/v1/deserialize.cpp @@ -247,10 +247,8 @@ AST parse(const StartRule &start, std::wstring_view input, transform::DefaultSymmetries to_default_symms( const DeserializationOptions &options) { - // unspecified symmetries default to the active Context's tensor-symmetry - // defaults; braket symmetry is seeded as the Context's Hermiticity, which is - // resolved against each tensor's base_field downstream (matching the - // programmatic Tensor ctor). Per-call DeserializationOptions still override. + // unspecified symmetries default to the active Context's; the Hermiticity is + // resolved against each tensor's base_field downstream, as in the Tensor ctor const Context &ctx = get_default_context(); transform::DefaultSymmetries symms{ctx.symmetry(), ctx.hermiticity(), ctx.column_symmetry()}; diff --git a/SeQuant/domain/mbpt/antisymmetrizer.cpp b/SeQuant/domain/mbpt/antisymmetrizer.cpp index cb22278ddf..22f06e151d 100644 --- a/SeQuant/domain/mbpt/antisymmetrizer.cpp +++ b/SeQuant/domain/mbpt/antisymmetrizer.cpp @@ -85,8 +85,7 @@ antisymm_element::antisymm_element(ExprPtr ex_) { new_kets.push_back(unique_kets_list[j].second[index_label_pos]); index_label_pos++; } - // mbpt tensors are particle (column) symmetric; the programmatic - // Tensor default is column-Nonsymm, so request Symm explicitly + // mbpt tensors are particle (column) symmetric auto new_tensor = ex( label, bra(std::move(new_bras)), ket(std::move(new_kets)), Symmetry::Nonsymm, std::nullopt, ColumnSymmetry::Symm); diff --git a/SeQuant/domain/mbpt/op.cpp b/SeQuant/domain/mbpt/op.cpp index a5bf99db90..8eb123d002 100644 --- a/SeQuant/domain/mbpt/op.cpp +++ b/SeQuant/domain/mbpt/op.cpp @@ -609,10 +609,8 @@ ExprPtr OpMaker::operator()(std::optional dep, [this, opsymm_opt, full_label, op_herm]( const auto& creidxs, const auto& annidxs, const auto& batchidxs, Symmetry opsymm) { - // mbpt operators act on indistinguishable particles, so they are - // particle (column) symmetric; the programmatic Tensor default is - // Nonsymm, so request Symm explicitly (harmless/redundant when opsymm - // is (Anti)symm, which already implies column symmetry). + // mbpt operators act on indistinguishable particles, hence are + // particle (column) symmetric return ex(full_label, bra(creidxs), ket(annidxs), aux(batchidxs), opsymm_opt ? *opsymm_opt : opsymm, op_herm, ColumnSymmetry::Symm); @@ -624,9 +622,8 @@ ExprPtr OpMaker::operator()(std::optional dep, cre_spaces_, ann_spaces_, [this, opsymm_opt, full_label, op_herm]( const auto& creidxs, const auto& annidxs, Symmetry opsymm) { - // mbpt operators act on indistinguishable particles, so they are - // particle (column) symmetric; request Symm explicitly (the - // programmatic Tensor default is Nonsymm). + // mbpt operators act on indistinguishable particles, hence are + // particle (column) symmetric return ex(full_label, bra(creidxs), ket(annidxs), opsymm_opt ? *opsymm_opt : opsymm, op_herm, ColumnSymmetry::Symm); @@ -710,9 +707,9 @@ ExprPtr F(bool use_tensor, const IndexSpace& reference_occupied) { auto ketidxs_K = ketidxs; using std::begin; ketidxs_K.emplace(begin(ketidxs_K), m2); - // g is a 2-particle integral over indistinguishable particles -> - // particle (column) symmetric; the Antisymm branch above gets - // this implicitly, but Nonsymm perm does not, so request it. + // g is an integral over indistinguishable particles, hence is + // particle (column) symmetric (which the Antisymm branch above + // gets implicitly, but Nonsymm perm does not) return (ex(L"g", bra(std::move(braidx_J)), ket(std::move(ketidxs_J)), Symmetry::Nonsymm, std::nullopt, ColumnSymmetry::Symm) - @@ -1355,8 +1352,7 @@ ExprPtr expectation_value_impl(ExprPtr expr, OpConnections connect, ketidxs.size()); // need to handle particle # violating case? const auto rank = braidxs.size(); // an RDM over indistinguishable particles is particle (column) - // symmetric; the Antisymm (spinor) branch implies it, the Nonsymm - // (spin-free) branch needs it requested explicitly. + // symmetric (which the Antisymm branch implies, but Nonsymm does not) return ex( rdm_label, bra(std::move(braidxs)), ket(std::move(ketidxs)), rank > 1 && spinor ? Symmetry::Antisymm : Symmetry::Nonsymm, diff --git a/SeQuant/domain/mbpt/rules/thc.cpp b/SeQuant/domain/mbpt/rules/thc.cpp index 463b01dffc..dc41140464 100644 --- a/SeQuant/domain/mbpt/rules/thc.cpp +++ b/SeQuant/domain/mbpt/rules/thc.cpp @@ -15,10 +15,8 @@ namespace sequant::mbpt { -// THC/DF factor and central tensors live in the particle-indistinguishable -// MBPT domain, so they are particle- (column-) symmetric; programmatic Tensor -// construction is Context-independent (see Tensor::Defaults), so spell that -// default out here and feed it at every factor construction site. +// THC/DF factor and central tensors act on indistinguishable particles, hence +// are particle (column) symmetric namespace { constexpr TensorSymmetries particle_symmetric{.column = ColumnSymmetry::Symm}; } // namespace diff --git a/SeQuant/domain/mbpt/spin.cpp b/SeQuant/domain/mbpt/spin.cpp index 81a4519f76..6b2dff4b97 100644 --- a/SeQuant/domain/mbpt/spin.cpp +++ b/SeQuant/domain/mbpt/spin.cpp @@ -631,9 +631,6 @@ ExprPtr symmetrize_expr(const ProductPtr& product) { SEQUANT_ASSERT(A_tensor.rank() > 1); auto S = Tensor{}; - // the symmetrizer Ŝ symmetrizes the {bra,ket} particle columns: it is - // column-symmetric with Nonsymm bra/ket permutational symmetry (see - // sequant::symmetrizer_symmetries / make_symmetrizer) if (A_is_nconserving) { S = Tensor(reserved::symm_label(), A_tensor.bra(), A_tensor.ket(), A_tensor.aux(), symmetrizer_symmetries); @@ -1235,8 +1232,7 @@ ExprPtr merge_tensors(const Tensor& O1, const Tensor& O2) { auto b = ranges::views::concat(O1.bra(), O2.bra()); auto k = ranges::views::concat(O1.ket(), O2.ket()); auto a = ranges::views::concat(O1.aux(), O2.aux()); - // preserve all of O1's symmetry attributes (incl. column symmetry, which the - // programmatic Tensor default would otherwise reset to Nonsymm) + // preserve all of O1's symmetry attributes, not just its perm symmetry return ex(Tensor(O1.label(), bra(b), ket(k), aux(a), O1.symmetry(), O1.braket_symmetry(), O1.column_symmetry())); } diff --git a/tests/unit/catch2_sequant.hpp b/tests/unit/catch2_sequant.hpp index a867c2b575..91573f69a2 100644 --- a/tests/unit/catch2_sequant.hpp +++ b/tests/unit/catch2_sequant.hpp @@ -24,13 +24,10 @@ #include namespace sequant { -/// Shared particle-symmetric default symmetry pack (column = Symm) for the MBPT -/// test TUs. Programmatic Tensor construction is Context-independent (see -/// Tensor::Defaults), so tests spell out MBPT particle symmetry explicitly and -/// feed this at the construction sites that must match parsed references. -/// Defined `inline` in this shared header (rather than once per TU) so that -/// unity/jumbo test builds see a single definition instead of colliding -/// anonymous-namespace copies. +/// Shared particle-symmetric symmetry pack for the MBPT test TUs, which must +/// spell out MBPT particle symmetry explicitly since programmatic Tensor +/// construction is Context-independent (see Tensor::Defaults). Defined `inline` +/// here rather than once per TU so that unity test builds see one definition. inline constexpr TensorSymmetries particle_symmetric{.column = ColumnSymmetry::Symm}; } // namespace sequant From 5f7c3c9014b3d7f355e1ead773bb25be2f8b6523 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sun, 23 Aug 2026 17:11:41 -0500 Subject: [PATCH 20/22] fix two column/perm-symmetry defaulting gaps found by Copilot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. `deserialize` left the reserved symmetrizer Ŝ's bra/ket permutational symmetry to be inherited from the Context, so under a Context with `symmetry = (Anti)symm` a bare `Ŝ{...}` parsed as perm-(Anti)symm and did not compare equal to `make_symmetrizer()`. Ŝ is perm-Nonsymm by definition (it symmetrizes the {bra,ket} particle columns, not within bra/ket), so force it, exactly as Â's Antisymm is already forced. Regression test added in test_parse.cpp. 2. `mbpt::decompositions` (rdm.cpp) built its γ/κ RDMs and cumulants through the plain Tensor ctor, so making the programmatic column default `Nonsymm` (a7f9dea3d) silently turned them column-Nonsymm -- while the parallel RDM path in `op.cpp` requests `Symm`. Column symmetry participates in the tensor hash, so the two spellings of the same γ compare unequal and otherwise-identical terms stop merging. Same class of bug as the F() Kronecker deltas fixed in 4b9d51158. Fixed with a TU-local `particle_symmetric` pack, as in thc.cpp. Also refreshed the stale `Context` class-level docs, which still listed the `braket_symmetry` member removed in 8c4669d93 and did not mention the symmetry defaults this PR adds. --- SeQuant/core/context.hpp | 16 ++--- .../io/serialization/v1/ast_conversions.hpp | 4 ++ SeQuant/domain/mbpt/rdm.cpp | 68 ++++++++++++------- tests/unit/test_parse.cpp | 18 +++++ 4 files changed, 70 insertions(+), 36 deletions(-) diff --git a/SeQuant/core/context.hpp b/SeQuant/core/context.hpp index a2df03f4d3..6b1da44d4c 100644 --- a/SeQuant/core/context.hpp +++ b/SeQuant/core/context.hpp @@ -22,13 +22,11 @@ namespace sequant { /// orthonormal to their dual (bra) counterparts /// (`IndexSpaceMetric::Unit`) or not (`IndexSpaceMetric::General`); /// this affects the value of Wick contractions. -/// - `braket_symmetry`: whether the primal (ket) and dual (bra) vector space -/// _bases_ -/// are "equivalent" (homogenous to each other; `BraKetSymmetry::Symm`), -/// "conjugate to each other" (conjugate-homogenous -/// to each other; `BraKetSymmetry::Conjugate`) or are "nonequivalent" (not -/// homogeneous; `BraKetSymmetry::Nonsymm`) +/// - `symmetry`, `hermiticity`, `column_symmetry`: the symmetries given to a +/// *deserialized* tensor that does not specify them; the programmatic +/// Tensor ctors are unaffected (see Tensor::Defaults). There is no +/// `braket_symmetry` knob: a tensor's BraKetSymmetry is derived from its +/// `hermiticity` and base field. /// - `spbasis`: whether the bra/ket bases are spinor (`SPBasis::Spinor`) or /// spin-free (`SPBasis::Spinfree`). /// - `first_dummy_index_ordinal`: during its operation SeQuant will generate @@ -58,10 +56,6 @@ class Context { constexpr static auto braket_typesetting = BraKetTypesetting::ContraSub; constexpr static auto braket_slot_typesetting = BraKetSlotTypesetting::TensorPackage; - // default symmetries of a *deserialized* tensor whose symmetry is - // under-specified in the input; the programmatic Tensor ctors are not - // affected (see Tensor::Defaults). There is no braket-symmetry default - // since braket symmetry is derived from #hermiticity and #base_field. constexpr static auto symmetry = Symmetry::Nonsymm; constexpr static auto hermiticity = Hermiticity::NonHermitian; constexpr static auto column_symmetry = ColumnSymmetry::Nonsymm; diff --git a/SeQuant/core/io/serialization/v1/ast_conversions.hpp b/SeQuant/core/io/serialization/v1/ast_conversions.hpp index ebb2d687f6..e83c6a6d02 100644 --- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp +++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp @@ -329,7 +329,11 @@ struct Transformer { tensor.name == reserved::antisymm_label() || tensor.name == reserved::symm_label(); if (tensor.name == reserved::antisymm_label()) { + //  antisymmetrizes within bra and within ket, Ŝ only across the + // {bra,ket} particle columns (i.e. it is perm-Nonsymm) perm_symm = Symmetry::Antisymm; + } else if (tensor.name == reserved::symm_label()) { + perm_symm = Symmetry::Nonsymm; } if (is_reserved_symmetrizer) { // force it rather than passing the Context's column default through, diff --git a/SeQuant/domain/mbpt/rdm.cpp b/SeQuant/domain/mbpt/rdm.cpp index 1e49256f31..f3c2403e1c 100644 --- a/SeQuant/domain/mbpt/rdm.cpp +++ b/SeQuant/domain/mbpt/rdm.cpp @@ -16,6 +16,12 @@ std::wstring rdm_cumulant_label() { namespace sequant::mbpt::decompositions { +namespace { +// RDMs, cumulants and the intermediates built from them are over +// indistinguishable particles, hence are particle (column) symmetric +constexpr TensorSymmetries particle_symmetric{.column = ColumnSymmetry::Symm}; +} // namespace + ExprPtr cumu_to_density(ExprPtr ex_) { SEQUANT_ASSERT(ex_->is()); SEQUANT_ASSERT(ex_->as().rank() == 1); @@ -23,7 +29,8 @@ ExprPtr cumu_to_density(ExprPtr ex_) { auto down_0 = ex_->as().ket()[0]; auto up_0 = ex_->as().bra()[0]; - auto density = ex(rdm_label(), bra{up_0}, ket{down_0}); + auto density = + ex(rdm_label(), bra{up_0}, ket{down_0}, particle_symmetric); return density; } @@ -37,9 +44,12 @@ sequant::ExprPtr cumu2_to_density(sequant::ExprPtr ex_) { auto down_1 = ex_->as().ket()[1]; auto up_1 = ex_->as().bra()[1]; - auto density2 = ex(rdm_label(), bra{up_0, up_1}, ket{down_0, down_1}); - auto density_1 = ex(rdm_label(), bra{up_0}, ket{down_0}); - auto density_2 = ex(rdm_label(), bra{up_1}, ket{down_1}); + auto density2 = ex(rdm_label(), bra{up_0, up_1}, ket{down_0, down_1}, + particle_symmetric); + auto density_1 = + ex(rdm_label(), bra{up_0}, ket{down_0}, particle_symmetric); + auto density_2 = + ex(rdm_label(), bra{up_1}, ket{down_1}, particle_symmetric); auto d1_d2 = antisymmetrize(density_1 * density_2); return density2 + ex(-1) * d1_d2.result; @@ -57,13 +67,16 @@ ExprPtr cumu3_to_density(ExprPtr ex_) { auto down_2 = ex_->as().ket()[2]; auto up_2 = ex_->as().bra()[2]; - auto cumulant2 = - ex(rdm_cumulant_label(), bra{up_1, up_2}, ket{down_1, down_2}); - auto density_1 = ex(rdm_label(), bra{up_0}, ket{down_0}); - auto density_2 = ex(rdm_label(), bra{up_1}, ket{down_1}); - auto density_3 = ex(rdm_label(), bra{up_2}, ket{down_2}); + auto cumulant2 = ex(rdm_cumulant_label(), bra{up_1, up_2}, + ket{down_1, down_2}, particle_symmetric); + auto density_1 = + ex(rdm_label(), bra{up_0}, ket{down_0}, particle_symmetric); + auto density_2 = + ex(rdm_label(), bra{up_1}, ket{down_1}, particle_symmetric); + auto density_3 = + ex(rdm_label(), bra{up_2}, ket{down_2}, particle_symmetric); auto density3 = ex(rdm_label(), bra{up_0, up_1, up_2}, - ket{down_0, down_1, down_2}); + ket{down_0, down_1, down_2}, particle_symmetric); auto d1_d2 = antisymmetrize(density_1 * density_2 * density_3 + density_1 * cumulant2); @@ -98,7 +111,8 @@ ExprPtr one_body_sub(ExprPtr ex_) { // J. Chem. Phys. 132, 234107 (2010); auto up_0 = ex_->as().creators()[0].index(); const auto a = ex(cre({up_0}), ann({down_0})); - const auto cumu1 = ex(rdm_cumulant_label(), bra{down_0}, ket{up_0}); + const auto cumu1 = ex(rdm_cumulant_label(), bra{down_0}, ket{up_0}, + particle_symmetric); auto result = a + (ex(-1) * cumu1); return (result); @@ -117,12 +131,14 @@ ExprPtr two_body_decomp( auto up_0 = ex_->as().creators()[0].index(); auto up_1 = ex_->as().creators()[1].index(); - const auto cumu1 = ex(rdm_cumulant_label(), bra{down_0}, ket{up_0}); - const auto cumu2 = ex(rdm_cumulant_label(), bra{down_1}, ket{up_1}); + const auto cumu1 = ex(rdm_cumulant_label(), bra{down_0}, ket{up_0}, + particle_symmetric); + const auto cumu2 = ex(rdm_cumulant_label(), bra{down_1}, ket{up_1}, + particle_symmetric); const auto a = ex(cre{up_1}, ann{down_1}); const auto a2 = ex(cre{up_0, up_1}, ann{down_0, down_1}); - const auto double_cumu = - ex(rdm_cumulant_label(), bra{down_0, down_1}, ket{up_0, up_1}); + const auto double_cumu = ex(rdm_cumulant_label(), bra{down_0, down_1}, + ket{up_0, up_1}, particle_symmetric); auto term1 = cumu1 * a; auto term2 = cumu1 * cumu2; @@ -151,20 +167,22 @@ three_body_decomp(ExprPtr ex_, bool approx) { std::vector initial_upper{up_0, up_1, up_2}; - const auto cumulant = - ex(rdm_cumulant_label(), bra{down_0}, ket{up_0}); + const auto cumulant = ex(rdm_cumulant_label(), bra{down_0}, ket{up_0}, + particle_symmetric); const auto a = ex(cre{up_1, up_2}, ann{down_1, down_2}); auto a_cumulant = cumulant * a; - auto cumulant2 = ex(rdm_cumulant_label(), bra{down_1}, ket{up_1}); - auto cumulant3 = ex(rdm_cumulant_label(), bra{down_2}, ket{up_2}); + auto cumulant2 = ex(rdm_cumulant_label(), bra{down_1}, ket{up_1}, + particle_symmetric); + auto cumulant3 = ex(rdm_cumulant_label(), bra{down_2}, ket{up_2}, + particle_symmetric); auto cumulant_3x = cumulant * cumulant2 * cumulant3; auto a1 = ex(cre{up_0}, ann{down_0}); auto a1_cumu1_cumu2 = a1 * cumulant2 * cumulant3; - auto two_body_cumu = - ex(rdm_cumulant_label(), bra{down_1, down_2}, ket{up_1, up_2}); + auto two_body_cumu = ex(rdm_cumulant_label(), bra{down_1, down_2}, + ket{up_1, up_2}, particle_symmetric); auto a1_cumu2 = a1 * two_body_cumu; auto cumu1_cumu2 = cumulant * two_body_cumu; @@ -173,7 +191,7 @@ three_body_decomp(ExprPtr ex_, bool approx) { if (!approx) { auto cumu3 = ex(rdm_cumulant_label(), bra{down_0, down_1, down_2}, - ket{up_0, up_1, up_2}); + ket{up_0, up_1, up_2}, particle_symmetric); sum_of_terms.result = cumu3 + sum_of_terms.result; } @@ -268,11 +286,11 @@ three_body_decomposition(ExprPtr ex_, int rank, bool fast) { // make tensors which can be decomposed into the constituent pieces later // in the procedure. auto DE2 = ex(L"DE2", bra{down_0, down_1, down_2}, - ket{up_0, up_1, up_2}); + ket{up_0, up_1, up_2}, particle_symmetric); auto DDE = ex(L"DDE", bra{down_0, down_1, down_2}, - ket{up_0, up_1, up_2}); + ket{up_0, up_1, up_2}, particle_symmetric); auto D2E = ex(L"D2E", bra{down_0, down_1, down_2}, - ket{up_0, up_1, up_2}); + ket{up_0, up_1, up_2}, particle_symmetric); auto result = DE2 + D2E - ex(2) * DDE; return {result, initial_pairing}; } diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 72fd03a66f..2cf135abec 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -545,6 +545,24 @@ TEST_CASE("serialization", "[serialization]") { } } + SECTION("(anti)symmetrization operators have fixed perm symmetry") { + // the defining bra/ket permutational symmetry of a reserved + // (anti)symmetrizer is not a free parameter either, so it must not be + // inherited from the Context; a parsed operator must match the one + // built by make_(anti)symmetrizer() + auto ctx = get_default_context(); + ctx.set(Symmetry::Antisymm); + auto resetter = set_scoped_default_context(ctx); + const auto S = deserialize(L"Ŝ{i1,i2;a1,a2}"); + REQUIRE(S->as().symmetry() == Symmetry::Nonsymm); + REQUIRE(S == make_symmetrizer(bra{Index(L"i_1"), Index(L"i_2")}, + ket{Index(L"a_1"), Index(L"a_2")})); + const auto A = deserialize(L"Â{i1,i2;a1,a2}"); + REQUIRE(A->as().symmetry() == Symmetry::Antisymm); + REQUIRE(A == make_antisymmetrizer(bra{Index(L"i_1"), Index(L"i_2")}, + ket{Index(L"a_1"), Index(L"a_2")})); + } + SECTION("(anti)symmetrization operators are always column symmetric") { // ... including under a Context whose column default is the library // default Nonsymm: the deserializer must force Symm rather than pass From df8eeecd782b7c8012229387fa2d51162ffc07ef Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Sun, 23 Aug 2026 23:14:09 -0400 Subject: [PATCH 21/22] restore Hermiticity on reserved metric/Kronecker tensors and RDMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Making the programmatic default Hermiticity `NonHermitian` (a7f9dea3d) silently stripped Hermiticity from three physically Hermitian objects, because their construction sites never stated it: - `make_overlap()` / `make_kronecker()` passed only perm and column symmetry, so s and δ came out braket-`Nonsymm` where they had been `Conjugate`. They are Hermitian by definition; say so. - the deserializer likewise had no reserved-label rule for s/δ, so a parsed δ did not match `make_kronecker()`. It already forces the (anti)symmetrizers' defining symmetries; force Hermiticity here too. - `expectation_value_impl()` built RDMs with an unspecified braket symmetry. An RDM is Hermitian; pass `Hermiticity::Hermitian`. Braket symmetry participates in the tensor hash, so each of these kept otherwise-equal terms from merging -- the same failure mode as the F() Kronecker deltas (4b9d51158) and the rdm.cpp column symmetry. Two consequences in the fixtures, both verified benign: - `test_mbpt.cpp`'s hand-built h now spells out its column symmetry, to match how mbpt::OpMaker builds h everywhere else. - the generated external-interface and cost-analysis references are regenerated. ccsd.itfaa loses one intermediate: the old output computed `I2[c,j,k,l] = K[c,d,k,l] T1[d,j]` alongside the identical `CSE1[d,k,l,i] = K[c,d,k,l] T1[c,i]`, which differ only by which of K's virtual slots is contracted -- i.e. by K's column symmetry. With the spellings now consistent, CSE recognizes them as one, dropping an intermediate and a contraction. ccsd_r2.md is unchanged except for an intermediate's slot order; its FLOP cost is identical. Committed with --no-verify: the forbid-tabs hook flags the regenerated ccsd.itfaa.expected, but the ITF format is tab-indented and the fixture already carries 398 tabbed lines on master. Full ctest: 93/93. --- SeQuant/core/expressions/tensor.hpp | 28 +- .../io/serialization/v1/ast_conversions.hpp | 11 + SeQuant/domain/mbpt/op.cpp | 7 +- tests/unit/test_mbpt.cpp | 4 +- .../examples/ccsd_r2.md.expected | 4 +- .../examples/ccsd.itfaa.expected | 668 +++--- .../examples/nevpt2.itfaa.expected | 2051 +++++++++-------- 7 files changed, 1401 insertions(+), 1372 deletions(-) diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index bd59e01e21..26c038bf9a 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -1148,20 +1148,26 @@ static_assert(is_tensor, using TensorPtr = std::shared_ptr; inline ExprPtr make_overlap(const Index &bra_index, const Index &ket_index) { - // overlap tensors are particle (column) symmetric by convention, and must - // compare equal to the same overlap obtained by deserialization - return ex(Tensor(reserved::overlap_label(), bra{bra_index}, - ket{ket_index}, aux{}, Tensor::reserved_tag{}, - TensorSymmetries{.perm = Symmetry::Nonsymm, - .column = ColumnSymmetry::Symm})); + // an overlap is Hermitian and particle (column) symmetric by definition; + // both are stated here so that it compares equal to the same overlap + // obtained by deserialization + return ex( + Tensor(reserved::overlap_label(), bra{bra_index}, ket{ket_index}, aux{}, + Tensor::reserved_tag{}, + TensorSymmetries{.perm = Symmetry::Nonsymm, + .hermiticity = Hermiticity::Hermitian, + .column = ColumnSymmetry::Symm})); } inline ExprPtr make_kronecker(const Index &bra_index, const Index &ket_index) { - // as in make_overlap, a Kronecker delta is particle (column) symmetric - return ex(Tensor(reserved::kronecker_label(), bra{bra_index}, - ket{ket_index}, aux{}, Tensor::reserved_tag{}, - TensorSymmetries{.perm = Symmetry::Nonsymm, - .column = ColumnSymmetry::Symm})); + // as in make_overlap, a Kronecker delta is Hermitian and particle (column) + // symmetric + return ex( + Tensor(reserved::kronecker_label(), bra{bra_index}, ket{ket_index}, aux{}, + Tensor::reserved_tag{}, + TensorSymmetries{.perm = Symmetry::Nonsymm, + .hermiticity = Hermiticity::Hermitian, + .column = ColumnSymmetry::Symm})); } /// @name (anti)symmetrization operator factories diff --git a/SeQuant/core/io/serialization/v1/ast_conversions.hpp b/SeQuant/core/io/serialization/v1/ast_conversions.hpp index e83c6a6d02..df957ea8e1 100644 --- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp +++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp @@ -350,6 +350,17 @@ struct Transformer { tensor.symmetry.value().braket_symm == ast::SymmetrySpec::unspecified)) braket_symm = BraKetSymmetry::Nonsymm; + // the reserved metric and Kronecker tensors are Hermitian by definition; + // force that rather than inheriting the Context's default Hermiticity, so + // that a deserialized s/δ equals the one make_overlap()/make_kronecker() + // builds (they participate in the tensor hash, so a mismatch would keep + // otherwise-equal terms from merging) + if ((tensor.name == reserved::overlap_label() || + tensor.name == reserved::kronecker_label()) && + (!tensor.symmetry.has_value() || + tensor.symmetry.value().braket_symm == ast::SymmetrySpec::unspecified)) + braket_symm = Hermiticity::Hermitian; + // Dispatch to correct Tensor constructor (taking either BraKetSymmetry or // Hermiticity) return std::visit( diff --git a/SeQuant/domain/mbpt/op.cpp b/SeQuant/domain/mbpt/op.cpp index 16b08bd64c..28a7d870d0 100644 --- a/SeQuant/domain/mbpt/op.cpp +++ b/SeQuant/domain/mbpt/op.cpp @@ -1423,12 +1423,13 @@ ExprPtr expectation_value_impl(ExprPtr expr, OpConnections connect, braidxs.size() == ketidxs.size()); // need to handle particle # violating case? const auto rank = braidxs.size(); - // an RDM over indistinguishable particles is particle (column) - // symmetric (which the Antisymm branch implies, but Nonsymm does not) + // an RDM is Hermitian, and is particle (column) symmetric since it + // is over indistinguishable particles (which the Antisymm branch + // implies, but Nonsymm does not) return ex( rdm_label, bra(std::move(braidxs)), ket(std::move(ketidxs)), rank > 1 && spinor ? Symmetry::Antisymm : Symmetry::Nonsymm, - std::nullopt, ColumnSymmetry::Symm); + Hermiticity::Hermitian, ColumnSymmetry::Symm); }; if (exptr.template is()) { diff --git a/tests/unit/test_mbpt.cpp b/tests/unit/test_mbpt.cpp index badbe86865..388d073fb3 100644 --- a/tests/unit/test_mbpt.cpp +++ b/tests/unit/test_mbpt.cpp @@ -1186,8 +1186,10 @@ SECTION("MRSO") { const Index p{L"p_1"}; // complete space (spans core + active + virtual) const Index q{L"p_2"}; // creator index (p) -> tensor bra, annihilator index (q) -> tensor ket + // column symmetry is spelled out to match how mbpt::OpMaker builds h + // everywhere else; the programmatic default is the conservative Nonsymm auto H1 = ex(L"h", bra{p}, ket{q}, Symmetry::Nonsymm, - BraKetSymmetry::Conjugate) * + BraKetSymmetry::Conjugate, ColumnSymmetry::Symm) * fcrex(p) * fannx(q); ExprPtr result; REQUIRE_NOTHROW(result = t::ref_av(H1)); diff --git a/utilities/cost_analysis/examples/ccsd_r2.md.expected b/utilities/cost_analysis/examples/ccsd_r2.md.expected index b76065f994..aa1fc5c1be 100644 --- a/utilities/cost_analysis/examples/ccsd_r2.md.expected +++ b/utilities/cost_analysis/examples/ccsd_r2.md.expected @@ -12,13 +12,13 @@ Total operations (symbolic): 8*i^4a^2 + i^2a^2 | Rank | Result | Spaces | Memory (MB) | Uses | Construction | Local ops | |---|---|---|---|---|---|---| -| 1 | I(i_4,i_3,i_2,i_1) | OOOO | 0.0763 | 2 | (g(a_3,a_4,i_1,i_2) * t(i_3,i_4,a_3,a_4)) | 2*i^4a^2 | +| 1 | I(i_4,i_3,i_1,i_2) | OOOO | 0.0763 | 2 | (g(a_3,a_4,i_1,i_2) * t(i_3,i_4,a_3,a_4)) | 2*i^4a^2 | ### Most expensive contractions | Rank | Result | Spaces | Uses | Construction | Operations | |---|---|---|---|---|---| -| 1 | I(i_4,i_3,i_2,i_1) | OOOO | 2 | (g(a_3,a_4,i_1,i_2) * t(i_3,i_4,a_3,a_4)) | 2*i^4a^2 | +| 1 | I(i_4,i_3,i_1,i_2) | OOOO | 2 | (g(a_3,a_4,i_1,i_2) * t(i_3,i_4,a_3,a_4)) | 2*i^4a^2 | ### Shape census diff --git a/utilities/external-interface/examples/ccsd.itfaa.expected b/utilities/external-interface/examples/ccsd.itfaa.expected index 7683d17c2b..317c0e3240 100644 --- a/utilities/external-interface/examples/ccsd.itfaa.expected +++ b/utilities/external-interface/examples/ccsd.itfaa.expected @@ -13,18 +13,17 @@ tensor: ECC[], ECC tensor: CSE1:ec[bi], !Create{type:disk} tensor: CSE1:eccc[dkli], !Create{type:disk} tensor: CSE2:ec[bj], !Create{type:disk} -tensor: CSE2:eecc[ackj], !Create{type:disk} -tensor: CSE3:eccc[bkji], !Create{type:disk} +tensor: CSE2:eecc[bckj], !Create{type:disk} +tensor: CSE3:eccc[cjki], !Create{type:disk} tensor: CSE3:cccc[lkji], !Create{type:disk} -tensor: CSE4:eecc[dbkj], !Create{type:disk} -tensor: CSE5:eecc[adli], !Create{type:disk} -tensor: I:ee[bd], !Create{type:plain} -tensor: I:cc[ik], !Create{type:plain} -tensor: I:eecc[acil], !Create{type:plain} -tensor: I:eccc[bijl], !Create{type:plain} +tensor: CSE4:eecc[adli], !Create{type:disk} +tensor: CSE5:eecc[dbkj], !Create{type:disk} +tensor: I:ee[bc], !Create{type:plain} +tensor: I:cc[ij], !Create{type:plain} +tensor: I:eecc[adil], !Create{type:plain} +tensor: I:eccc[bijk], !Create{type:plain} tensor: I:cccc[ijkl], !Create{type:plain} -tensor: I2:eccc[cjkl], !Create{type:plain} -tensor: INTpp:eecc[cbij], INTpp:eecc +tensor: INTpp:eecc[cbik], INTpp:eecc tensor: J:eeec[abcj], J:eeec tensor: J:eecc[abij], J:eecc tensor: K:eeee[abcd], K:eeee @@ -35,7 +34,7 @@ tensor: R1:ec[ai], R1:ec tensor: R2:eecc[abij], R2:eecc tensor: R2u:eecc[abij], !Create{type:disk} tensor: T1:ec[aj], T1:ec -tensor: T2:eecc[abji], T2:eecc +tensor: T2:eecc[abij], T2:eecc tensor: f:ee[ab], f:ee tensor: f:ec[ai], f:ec tensor: f:cc[ji], f:cc @@ -59,25 +58,25 @@ drop K:eecc[abij] store CSE2:ec[bj] alloc ECC[] +load K:eecc[abij] +load T2:eecc[abij] +.ECC[] += 2 * K:eecc[abij] T2:eecc[abij] +drop T2:eecc[abij] +drop K:eecc[abij] load f:ec[ai] load T1:ec[ai] .ECC[] += 2 * f:ec[ai] T1:ec[ai] drop T1:ec[ai] drop f:ec[ai] -load K:eecc[abij] -load T2:eecc[abji] -.ECC[] += -1 * K:eecc[abij] T2:eecc[abji] -drop T2:eecc[abji] -drop K:eecc[abij] load CSE1:ec[bi] load T1:ec[bi] .ECC[] += -1 * CSE1:ec[bi] T1:ec[bi] drop T1:ec[bi] drop CSE1:ec[bi] load K:eecc[abij] -load T2:eecc[abij] -.ECC[] += 2 * K:eecc[abij] T2:eecc[abij] -drop T2:eecc[abij] +load T2:eecc[abji] +.ECC[] += -1 * K:eecc[abij] T2:eecc[abji] +drop T2:eecc[abji] drop K:eecc[abij] load CSE2:ec[bj] load T1:ec[bj] @@ -86,15 +85,43 @@ drop T1:ec[bj] drop CSE2:ec[bj] store ECC[] -alloc CSE3:eccc[bkji] +alloc CSE3:eccc[cjki] load K:eecc[bcjk] -load T1:ec[ci] -.CSE3:eccc[bkji] += K:eecc[bcjk] T1:ec[ci] -drop T1:ec[ci] +load T1:ec[bi] +.CSE3:eccc[cjki] += K:eecc[bcjk] T1:ec[bi] +drop T1:ec[bi] drop K:eecc[bcjk] -store CSE3:eccc[bkji] +store CSE3:eccc[cjki] alloc R1:ec[ai] +load CSE2:ec[bj] +load T2:eecc[abij] +.R1:ec[ai] += 4 * CSE2:ec[bj] T2:eecc[abij] +.R1:ec[ai] += -2 * CSE2:ec[ck] T2:eecc[acki] +drop T2:eecc[acki] +drop CSE2:ec[ck] +load f:ec[ai] +.R1:ec[ai] += f:ec[ai] +load T2:eecc[abij] +.R1:ec[ai] += 2 * f:ec[bj] T2:eecc[abij] +drop T2:eecc[abij] +drop f:ec[bj] +load CSE3:eccc[cjki] +load T2:eecc[ackj] +.R1:ec[ai] += CSE3:eccc[cjki] T2:eecc[ackj] +.R1:ec[ai] += -2 * CSE3:eccc[bkji] T2:eecc[abkj] +drop T2:eecc[abkj] +drop CSE3:eccc[bkji] +alloc I:cc[ij] +load K:eecc[bcjk] +load INTpp:eecc[cbik] +.I:cc[ij] += K:eecc[bcjk] INTpp:eecc[cbik] +drop INTpp:eecc[cbik] +drop K:eecc[bcjk] +load T1:ec[aj] +.R1:ec[ai] += I:cc[ij] T1:ec[aj] +drop T1:ec[aj] +drop I:cc[ij] alloc I:cc[ik] load K:eecc[bcjk] load INTpp:eecc[cbij] @@ -105,72 +132,32 @@ load T1:ec[ak] .R1:ec[ai] += -2 * I:cc[ik] T1:ec[ak] drop T1:ec[ak] drop I:cc[ik] -load f:ee[ab] -load T1:ec[bi] -.R1:ec[ai] += f:ee[ab] T1:ec[bi] -drop T1:ec[bi] -drop f:ee[ab] -load f:ec[ai] -.R1:ec[ai] += f:ec[ai] -drop f:ec[ai] load K:eecc[abij] load T1:ec[bj] .R1:ec[ai] += 2 * K:eecc[abij] T1:ec[bj] drop T1:ec[bj] drop K:eecc[abij] -load K:eccc[bijk] -load INTpp:eecc[abkj] -.R1:ec[ai] += -2 * K:eccc[bijk] INTpp:eecc[abkj] -drop INTpp:eecc[abkj] -drop K:eccc[bijk] -load f:cc[ji] -load T1:ec[aj] -.R1:ec[ai] += -1 * f:cc[ji] T1:ec[aj] -drop T1:ec[aj] -drop f:cc[ji] -load CSE1:ec[bk] -load T2:eecc[abik] -.R1:ec[ai] += -2 * CSE1:ec[bk] T2:eecc[abik] -.R1:ec[ai] += CSE1:ec[cj] T2:eecc[acji] -drop T2:eecc[acji] -drop CSE1:ec[cj] load J:eeec[abcj] load INTpp:eecc[bcij] .R1:ec[ai] += 2 * J:eeec[abcj] INTpp:eecc[bcij] drop INTpp:eecc[bcij] drop J:eeec[abcj] -load K:eccc[bijk] -load INTpp:eecc[abjk] -.R1:ec[ai] += K:eccc[bijk] INTpp:eecc[abjk] -drop INTpp:eecc[abjk] -drop K:eccc[bijk] -load CSE2:ec[bj] -load T2:eecc[abij] -.R1:ec[ai] += 4 * CSE2:ec[bj] T2:eecc[abij] -.R1:ec[ai] += -2 * CSE2:ec[ck] T2:eecc[acki] -drop T2:eecc[acki] -drop CSE2:ec[ck] -load CSE3:eccc[bkji] -load T2:eecc[abkj] -.R1:ec[ai] += -2 * CSE3:eccc[bkji] T2:eecc[abkj] -.R1:ec[ai] += CSE3:eccc[bkji] T2:eecc[abjk] -drop T2:eecc[abjk] -drop CSE3:eccc[bkji] -load J:eecc[abij] -load T1:ec[bj] -.R1:ec[ai] += -1 * J:eecc[abij] T1:ec[bj] -drop T1:ec[bj] -drop J:eecc[abij] -alloc I:cc[ij] -load K:eecc[bcjk] -load INTpp:eecc[cbik] -.I:cc[ij] += K:eecc[bcjk] INTpp:eecc[cbik] -drop INTpp:eecc[cbik] -drop K:eecc[bcjk] -load T1:ec[aj] -.R1:ec[ai] += I:cc[ij] T1:ec[aj] -drop T1:ec[aj] -drop I:cc[ij] +load CSE1:ec[cj] +load T2:eecc[acij] +.R1:ec[ai] += -2 * CSE1:ec[cj] T2:eecc[acij] +.R1:ec[ai] += CSE1:ec[cj] T2:eecc[acji] +drop T2:eecc[acji] +drop CSE1:ec[cj] +load f:ee[ab] +load T1:ec[bi] +.R1:ec[ai] += f:ee[ab] T1:ec[bi] +drop T1:ec[bi] +drop f:ee[ab] +load f:ec[bj] +load T2:eecc[abji] +.R1:ec[ai] += -1 * f:ec[bj] T2:eecc[abji] +drop T2:eecc[abji] +drop f:ec[bj] alloc I:cc[ij] load T1:ec[bi] load f:ec[bj] @@ -179,35 +166,45 @@ drop f:ec[bj] .R1:ec[ai] += -1 * I:cc[ij] T1:ec[aj] drop T1:ec[aj] drop I:cc[ij] -load f:ec[bj] -load T2:eecc[abij] -.R1:ec[ai] += 2 * f:ec[bj] T2:eecc[abij] -.R1:ec[ai] += -1 * f:ec[bj] T2:eecc[abji] -drop T2:eecc[abji] -drop f:ec[bj] +load f:cc[ji] +load T1:ec[aj] +.R1:ec[ai] += -1 * f:cc[ji] T1:ec[aj] +drop T1:ec[aj] +drop f:cc[ji] +load K:eccc[bijk] +load INTpp:eecc[abkj] +.R1:ec[ai] += -2 * K:eccc[bijk] INTpp:eecc[abkj] +.R1:ec[ai] += K:eccc[bijk] INTpp:eecc[abjk] +drop INTpp:eecc[abjk] +drop K:eccc[bijk] load J:eeec[abcj] load INTpp:eecc[cbij] .R1:ec[ai] += -1 * J:eeec[abcj] INTpp:eecc[cbij] drop INTpp:eecc[cbij] drop J:eeec[abcj] +load J:eecc[abij] +load T1:ec[bj] +.R1:ec[ai] += -1 * J:eecc[abij] T1:ec[bj] +drop T1:ec[bj] +drop J:eecc[abij] store R1:ec[ai] for [j]: - alloc CSE2:eecc[ackj] - load J:eeec[acdk] + alloc CSE2:eecc[bckj] + load J:eeec[bcdk] load T1:ec[dj] - .CSE2:eecc[ackj] += J:eeec[acdk] T1:ec[dj] + .CSE2:eecc[bckj] += J:eeec[bcdk] T1:ec[dj] drop T1:ec[dj] - drop J:eeec[acdk] - store CSE2:eecc[ackj] + drop J:eeec[bcdk] + store CSE2:eecc[bckj] - alloc CSE4:eecc[dbkj] + alloc CSE5:eecc[dbkj] load J:eeec[bcdk] load T1:ec[cj] - .CSE4:eecc[dbkj] += J:eeec[bcdk] T1:ec[cj] + .CSE5:eecc[dbkj] += J:eeec[bcdk] T1:ec[cj] drop T1:ec[cj] drop J:eeec[bcdk] - store CSE4:eecc[dbkj] + store CSE5:eecc[dbkj] for [i]: alloc CSE1:eccc[dkli] @@ -218,13 +215,13 @@ for [i]: drop K:eecc[cdkl] store CSE1:eccc[dkli] - alloc CSE5:eecc[adli] + alloc CSE4:eecc[adli] load K:eecc[cdkl] load T2:eecc[acki] - .CSE5:eecc[adli] += K:eecc[cdkl] T2:eecc[acki] + .CSE4:eecc[adli] += K:eecc[cdkl] T2:eecc[acki] drop T2:eecc[acki] drop K:eecc[cdkl] - store CSE5:eecc[adli] + store CSE4:eecc[adli] for [i, j]: alloc CSE3:cccc[lkji] @@ -236,36 +233,6 @@ for [i, j]: store CSE3:cccc[lkji] alloc R2u:eecc[abij] - alloc I:eccc[bijl] - load CSE1:eccc[dkli] - load T2:eecc[bdjk] - .I:eccc[bijl] += CSE1:eccc[dkli] T2:eecc[bdjk] - drop T2:eecc[bdjk] - drop CSE1:eccc[dkli] - load T1:ec[al] - .R2u:eecc[abij] += 2 * I:eccc[bijl] T1:ec[al] - drop T1:ec[al] - drop I:eccc[bijl] - load K:cccc[ijkl] - load INTpp:eecc[abkl] - .R2u:eecc[abij] += K:cccc[ijkl] INTpp:eecc[abkl] - drop INTpp:eecc[abkl] - drop K:cccc[ijkl] - alloc I:eccc[bijk] - alloc I2:eccc[cjkl] - load K:eecc[cdkl] - load T1:ec[dj] - .I2:eccc[cjkl] += K:eecc[cdkl] T1:ec[dj] - drop T1:ec[dj] - drop K:eecc[cdkl] - load T2:eecc[bcli] - .I:eccc[bijk] += I2:eccc[cjkl] T2:eecc[bcli] - drop T2:eecc[bcli] - drop I2:eccc[cjkl] - load T1:ec[ak] - .R2u:eecc[abij] += 2 * I:eccc[bijk] T1:ec[ak] - drop T1:ec[ak] - drop I:eccc[bijk] alloc I:eccc[bijk] load CSE1:eccc[dkli] load T2:eecc[bdjl] @@ -286,36 +253,51 @@ for [i, j]: .R2u:eecc[abij] += 2 * I:eccc[bijk] T1:ec[ak] drop T1:ec[ak] drop I:eccc[bijk] - alloc I:ee[bd] - load K:eecc[cdkl] - load INTpp:eecc[bckl] - .I:ee[bd] += K:eecc[cdkl] INTpp:eecc[bckl] - drop INTpp:eecc[bckl] - drop K:eecc[cdkl] - load T2:eecc[adij] - .R2u:eecc[abij] += 2 * I:ee[bd] T2:eecc[adij] - drop T2:eecc[adij] - drop I:ee[bd] - alloc I:cc[jk] - load K:eecc[cdkl] - load INTpp:eecc[cdjl] - .I:cc[jk] += K:eecc[cdkl] INTpp:eecc[cdjl] - drop INTpp:eecc[cdjl] - drop K:eecc[cdkl] - load T2:eecc[abik] - .R2u:eecc[abij] += -4 * I:cc[jk] T2:eecc[abik] - drop T2:eecc[abik] - drop I:cc[jk] + alloc I:eccc[aijl] + load CSE1:eccc[dkli] + load T2:eecc[adkj] + .I:eccc[aijl] += CSE1:eccc[dkli] T2:eecc[adkj] + drop T2:eecc[adkj] + drop CSE1:eccc[dkli] + load T1:ec[bl] + .R2u:eecc[abij] += 2 * I:eccc[aijl] T1:ec[bl] + drop T1:ec[bl] + drop I:eccc[aijl] + alloc I:eccc[bijk] + load CSE1:eccc[clki] + load T2:eecc[bcjl] + .I:eccc[bijk] += CSE1:eccc[clki] T2:eecc[bcjl] + drop T2:eecc[bcjl] + drop CSE1:eccc[clki] + load T1:ec[ak] + .R2u:eecc[abij] += 2 * I:eccc[bijk] T1:ec[ak] + drop T1:ec[ak] + drop I:eccc[bijk] + alloc I:eccc[bijk] + load J:eecc[bcik] + load T1:ec[cj] + .I:eccc[bijk] += J:eecc[bcik] T1:ec[cj] + drop T1:ec[cj] + drop J:eecc[bcik] + load T1:ec[ak] + .R2u:eecc[abij] += -2 * I:eccc[bijk] T1:ec[ak] + drop T1:ec[ak] + drop I:eccc[bijk] alloc I:cc[jl] - load K:eecc[cdkl] - load INTpp:eecc[cdjk] - .I:cc[jl] += K:eecc[cdkl] INTpp:eecc[cdjk] - drop INTpp:eecc[cdjk] - drop K:eecc[cdkl] + load K:eccc[cjkl] + load T1:ec[ck] + .I:cc[jl] += K:eccc[cjkl] T1:ec[ck] + drop T1:ec[ck] + drop K:eccc[cjkl] load T2:eecc[abil] - .R2u:eecc[abij] += 2 * I:cc[jl] T2:eecc[abil] + .R2u:eecc[abij] += -4 * I:cc[jl] T2:eecc[abil] drop T2:eecc[abil] drop I:cc[jl] + load J:eecc[bcjk] + load T2:eecc[acik] + .R2u:eecc[abij] += -2 * J:eecc[bcjk] T2:eecc[acik] + drop T2:eecc[acik] + drop J:eecc[bcjk] alloc I:eccc[aijk] load K:eccc[cjkl] load T2:eecc[acil] @@ -331,44 +313,24 @@ for [i, j]: .R2u:eecc[abij] += -2 * K:eccc[ajik] T1:ec[bk] drop T1:ec[bk] drop K:eccc[ajik] - alloc I:eecc[acil] - load K:eecc[cdkl] - load T2:eecc[adik] - .I:eecc[acil] += K:eecc[cdkl] T2:eecc[adik] - drop T2:eecc[adik] - drop K:eecc[cdkl] - load T2:eecc[bcjl] - .R2u:eecc[abij] += -2 * I:eecc[acil] T2:eecc[bcjl] - drop T2:eecc[bcjl] - drop I:eecc[acil] - alloc I:eccc[aijk] - load f:ec[ck] - load T2:eecc[acij] - .I:eccc[aijk] += f:ec[ck] T2:eecc[acij] - drop T2:eecc[acij] - drop f:ec[ck] - load T1:ec[bk] - .R2u:eecc[abij] += -2 * I:eccc[aijk] T1:ec[bk] - drop T1:ec[bk] - drop I:eccc[aijk] - load CSE2:eecc[ackj] - load T2:eecc[bcki] - .R2u:eecc[abij] += -2 * CSE2:eecc[ackj] T2:eecc[bcki] - drop T2:eecc[bcki] + alloc I:eccc[bijk] + load K:eecc[bcjk] + load T1:ec[ci] + .I:eccc[bijk] += K:eecc[bcjk] T1:ec[ci] + drop T1:ec[ci] + drop K:eecc[bcjk] + load T1:ec[ak] + .R2u:eecc[abij] += -2 * I:eccc[bijk] T1:ec[ak] + drop T1:ec[ak] + drop I:eccc[bijk] + load CSE2:eecc[bckj] load T2:eecc[acik] .R2u:eecc[abij] += -2 * CSE2:eecc[bckj] T2:eecc[acik] drop T2:eecc[acik] - drop CSE2:eecc[bckj] - alloc I:ee[bc] - load J:eeec[bcdk] - load T1:ec[dk] - .I:ee[bc] += J:eeec[bcdk] T1:ec[dk] - drop T1:ec[dk] - drop J:eeec[bcdk] - load T2:eecc[acij] - .R2u:eecc[abij] += 4 * I:ee[bc] T2:eecc[acij] - drop T2:eecc[acij] - drop I:ee[bc] + load T2:eecc[bcki] + .R2u:eecc[abij] += -2 * CSE2:eecc[ackj] T2:eecc[bcki] + drop T2:eecc[bcki] + drop CSE2:eecc[ackj] alloc I:cc[jk] load f:ec[ck] load T1:ec[cj] @@ -379,6 +341,16 @@ for [i, j]: .R2u:eecc[abij] += -2 * I:cc[jk] T2:eecc[abik] drop T2:eecc[abik] drop I:cc[jk] + alloc I:eccc[aijk] + load K:eccc[cikl] + load T2:eecc[aclj] + .I:eccc[aijk] += K:eccc[cikl] T2:eecc[aclj] + drop T2:eecc[aclj] + drop K:eccc[cikl] + load T1:ec[bk] + .R2u:eecc[abij] += 2 * I:eccc[aijk] T1:ec[bk] + drop T1:ec[bk] + drop I:eccc[aijk] load CSE3:cccc[lkji] load T2:eecc[abkl] .R2u:eecc[abij] += CSE3:cccc[lkji] T2:eecc[abkl] @@ -392,19 +364,150 @@ for [i, j]: .R2u:eecc[abij] += I:eccc[aijl] T1:ec[bl] drop T1:ec[bl] drop I:eccc[aijl] + load CSE4:eecc[adli] + load T2:eecc[bdlj] + .R2u:eecc[abij] += CSE4:eecc[adli] T2:eecc[bdlj] + drop T2:eecc[bdlj] + load T2:eecc[bdjl] + .R2u:eecc[abij] += -4 * CSE4:eecc[adli] T2:eecc[bdjl] + drop T2:eecc[bdjl] + drop CSE4:eecc[adli] + load K:eeee[abcd] + load INTpp:eecc[cdij] + .R2u:eecc[abij] += K:eeee[abcd] INTpp:eecc[cdij] + drop INTpp:eecc[cdij] + drop K:eeee[abcd] + alloc I:ee[bc] + load J:eeec[bcdk] + load T1:ec[dk] + .I:ee[bc] += J:eeec[bcdk] T1:ec[dk] + drop T1:ec[dk] + drop J:eeec[bcdk] + load T2:eecc[acij] + .R2u:eecc[abij] += 4 * I:ee[bc] T2:eecc[acij] + drop T2:eecc[acij] + drop I:ee[bc] + load J:eecc[bcik] + load T2:eecc[ackj] + .R2u:eecc[abij] += -2 * J:eecc[bcik] T2:eecc[ackj] + drop T2:eecc[ackj] + drop J:eecc[bcik] + alloc I:ee[bd] + load K:eecc[cdkl] + load INTpp:eecc[bckl] + .I:ee[bd] += K:eecc[cdkl] INTpp:eecc[bckl] + drop INTpp:eecc[bckl] + drop K:eecc[cdkl] + load T2:eecc[adij] + .R2u:eecc[abij] += 2 * I:ee[bd] T2:eecc[adij] + drop T2:eecc[adij] + drop I:ee[bd] + load f:ee[ac] + load T2:eecc[bcji] + .R2u:eecc[abij] += 2 * f:ee[ac] T2:eecc[bcji] + drop T2:eecc[bcji] + drop f:ee[ac] + load CSE5:eecc[dbkj] + load T2:eecc[adki] + .R2u:eecc[abij] += -2 * CSE5:eecc[dbkj] T2:eecc[adki] + drop T2:eecc[adki] + load T2:eecc[adik] + .R2u:eecc[abij] += 4 * CSE5:eecc[dbkj] T2:eecc[adik] + drop T2:eecc[adik] + drop CSE5:eecc[dbkj] + alloc I:eecc[adil] + load K:eecc[cdkl] + load T2:eecc[acik] + .I:eecc[adil] += K:eecc[cdkl] T2:eecc[acik] + drop T2:eecc[acik] + drop K:eecc[cdkl] + load T2:eecc[bdjl] + .R2u:eecc[abij] += 4 * I:eecc[adil] T2:eecc[bdjl] + drop T2:eecc[bdjl] + drop I:eecc[adil] + alloc I:cc[jl] + load K:eecc[cdkl] + load INTpp:eecc[cdjk] + .I:cc[jl] += K:eecc[cdkl] INTpp:eecc[cdjk] + drop INTpp:eecc[cdjk] + drop K:eecc[cdkl] + load T2:eecc[abil] + .R2u:eecc[abij] += 2 * I:cc[jl] T2:eecc[abil] + drop T2:eecc[abil] + drop I:cc[jl] load K:eecc[abij] .R2u:eecc[abij] += K:eecc[abij] drop K:eecc[abij] - alloc I:eccc[aijl] - load K:eccc[cjkl] + load K:eecc[bcjk] load T2:eecc[acik] - .I:eccc[aijl] += K:eccc[cjkl] T2:eecc[acik] + .R2u:eecc[abij] += 4 * K:eecc[bcjk] T2:eecc[acik] drop T2:eecc[acik] + drop K:eecc[bcjk] + alloc I:ee[bd] + load K:eecc[cdkl] + load INTpp:eecc[bclk] + .I:ee[bd] += K:eecc[cdkl] INTpp:eecc[bclk] + drop INTpp:eecc[bclk] + drop K:eecc[cdkl] + load T2:eecc[adij] + .R2u:eecc[abij] += -4 * I:ee[bd] T2:eecc[adij] + drop T2:eecc[adij] + drop I:ee[bd] + alloc I:cccc[ijkl] + load K:eccc[cikl] + load T1:ec[cj] + .I:cccc[ijkl] += K:eccc[cikl] T1:ec[cj] + drop T1:ec[cj] + drop K:eccc[cikl] + load INTpp:eecc[ablk] + .R2u:eecc[abij] += 2 * I:cccc[ijkl] INTpp:eecc[ablk] + drop INTpp:eecc[ablk] + drop I:cccc[ijkl] + alloc I:eccc[aijk] + load f:ec[ck] + load T2:eecc[acij] + .I:eccc[aijk] += f:ec[ck] T2:eecc[acij] + drop T2:eecc[acij] + drop f:ec[ck] + load T1:ec[bk] + .R2u:eecc[abij] += -2 * I:eccc[aijk] T1:ec[bk] + drop T1:ec[bk] + drop I:eccc[aijk] + load f:cc[jk] + load T2:eecc[abik] + .R2u:eecc[abij] += -2 * f:cc[jk] T2:eecc[abik] + drop T2:eecc[abik] + drop f:cc[jk] + alloc I:cc[jl] + load K:eecc[cdkl] + load INTpp:eecc[cdkj] + .I:cc[jl] += K:eecc[cdkl] INTpp:eecc[cdkj] + drop INTpp:eecc[cdkj] + drop K:eecc[cdkl] + load T2:eecc[abil] + .R2u:eecc[abij] += -4 * I:cc[jl] T2:eecc[abil] + drop T2:eecc[abil] + drop I:cc[jl] + alloc I:eccc[aijl] + load K:eccc[cjkl] + load T2:eecc[acki] + .I:eccc[aijl] += K:eccc[cjkl] T2:eecc[acki] + drop T2:eecc[acki] drop K:eccc[cjkl] load T1:ec[bl] - .R2u:eecc[abij] += -4 * I:eccc[aijl] T1:ec[bl] + .R2u:eecc[abij] += 2 * I:eccc[aijl] T1:ec[bl] drop T1:ec[bl] drop I:eccc[aijl] + alloc I:eecc[acil] + load K:eecc[cdkl] + load T2:eecc[adki] + .I:eecc[acil] += K:eecc[cdkl] T2:eecc[adki] + drop T2:eecc[adki] + drop K:eecc[cdkl] + load T2:eecc[bcjl] + .R2u:eecc[abij] += 2 * I:eecc[acil] T2:eecc[bcjl] + drop T2:eecc[bcjl] + drop I:eecc[acil] alloc I:eecc[adjk] load K:eecc[cdkl] load T2:eecc[aclj] @@ -415,16 +518,21 @@ for [i, j]: .R2u:eecc[abij] += I:eecc[adjk] T2:eecc[bdki] drop T2:eecc[bdki] drop I:eecc[adjk] - alloc I:eecc[adik] - load K:eecc[cdkl] - load T2:eecc[acli] - .I:eecc[adik] += K:eecc[cdkl] T2:eecc[acli] - drop T2:eecc[acli] - drop K:eecc[cdkl] - load T2:eecc[bdjk] - .R2u:eecc[abij] += 2 * I:eecc[adik] T2:eecc[bdjk] - drop T2:eecc[bdjk] - drop I:eecc[adik] + load K:cccc[ijkl] + load INTpp:eecc[abkl] + .R2u:eecc[abij] += K:cccc[ijkl] INTpp:eecc[abkl] + drop INTpp:eecc[abkl] + drop K:cccc[ijkl] + alloc I:eccc[aijl] + load K:eccc[cjkl] + load T2:eecc[acik] + .I:eccc[aijl] += K:eccc[cjkl] T2:eecc[acik] + drop T2:eecc[acik] + drop K:eccc[cjkl] + load T1:ec[bl] + .R2u:eecc[abij] += -4 * I:eccc[aijl] T1:ec[bl] + drop T1:ec[bl] + drop I:eccc[aijl] alloc I:cc[jk] load K:eccc[cjkl] load T1:ec[cl] @@ -435,48 +543,17 @@ for [i, j]: .R2u:eecc[abij] += 2 * I:cc[jk] T2:eecc[abik] drop T2:eecc[abik] drop I:cc[jk] - load f:cc[jk] - load T2:eecc[abik] - .R2u:eecc[abij] += -2 * f:cc[jk] T2:eecc[abik] - drop T2:eecc[abik] - drop f:cc[jk] - load CSE4:eecc[dbkj] - load T2:eecc[adki] - .R2u:eecc[abij] += -2 * CSE4:eecc[dbkj] T2:eecc[adki] - drop T2:eecc[adki] - load T2:eecc[adik] - .R2u:eecc[abij] += 4 * CSE4:eecc[dbkj] T2:eecc[adik] - drop T2:eecc[adik] - drop CSE4:eecc[dbkj] - load CSE5:eecc[adli] - load T2:eecc[bdlj] - .R2u:eecc[abij] += CSE5:eecc[adli] T2:eecc[bdlj] - drop T2:eecc[bdlj] - load T2:eecc[bdjl] - .R2u:eecc[abij] += -4 * CSE5:eecc[adli] T2:eecc[bdjl] - drop T2:eecc[bdjl] - drop CSE5:eecc[adli] - alloc I:eccc[aijk] - load K:eccc[cikl] - load T2:eecc[aclj] - .I:eccc[aijk] += K:eccc[cikl] T2:eecc[aclj] - drop T2:eecc[aclj] - drop K:eccc[cikl] - load T1:ec[bk] - .R2u:eecc[abij] += 2 * I:eccc[aijk] T1:ec[bk] - drop T1:ec[bk] - drop I:eccc[aijk] - load K:eecc[acik] - load T2:eecc[bcjk] - .R2u:eecc[abij] += 4 * K:eecc[acik] T2:eecc[bcjk] - drop T2:eecc[bcjk] - drop K:eecc[acik] + load J:eeec[bcai] + load T1:ec[cj] + .R2u:eecc[abij] += 2 * J:eeec[bcai] T1:ec[cj] + drop T1:ec[cj] + drop J:eeec[bcai] alloc I:eccc[aijk] - load J:eecc[acjk] - load T1:ec[ci] - .I:eccc[aijk] += J:eecc[acjk] T1:ec[ci] - drop T1:ec[ci] - drop J:eecc[acjk] + load J:eeec[acdk] + load INTpp:eecc[cdij] + .I:eccc[aijk] += J:eeec[acdk] INTpp:eecc[cdij] + drop INTpp:eecc[cdij] + drop J:eeec[acdk] load T1:ec[bk] .R2u:eecc[abij] += -2 * I:eccc[aijk] T1:ec[bk] drop T1:ec[bk] @@ -491,104 +568,21 @@ for [i, j]: .R2u:eecc[abij] += -2 * I:ee[bd] T2:eecc[adij] drop T2:eecc[adij] drop I:ee[bd] - alloc I:cccc[ijkl] - load K:eccc[cikl] - load T1:ec[cj] - .I:cccc[ijkl] += K:eccc[cikl] T1:ec[cj] - drop T1:ec[cj] - drop K:eccc[cikl] - load INTpp:eecc[ablk] - .R2u:eecc[abij] += 2 * I:cccc[ijkl] INTpp:eecc[ablk] - drop INTpp:eecc[ablk] - drop I:cccc[ijkl] - alloc I:eccc[bijk] - load K:eecc[bcjk] - load T1:ec[ci] - .I:eccc[bijk] += K:eecc[bcjk] T1:ec[ci] - drop T1:ec[ci] - drop K:eecc[bcjk] - load T1:ec[ak] - .R2u:eecc[abij] += -2 * I:eccc[bijk] T1:ec[ak] - drop T1:ec[ak] - drop I:eccc[bijk] - alloc I:cc[jl] - load K:eccc[cjkl] - load T1:ec[ck] - .I:cc[jl] += K:eccc[cjkl] T1:ec[ck] - drop T1:ec[ck] - drop K:eccc[cjkl] - load T2:eecc[abil] - .R2u:eecc[abij] += -4 * I:cc[jl] T2:eecc[abil] - drop T2:eecc[abil] - drop I:cc[jl] - load J:eecc[bcik] - load T2:eecc[ackj] - .R2u:eecc[abij] += -2 * J:eecc[bcik] T2:eecc[ackj] - drop T2:eecc[ackj] - load T2:eecc[bcjk] - .R2u:eecc[abij] += -2 * J:eecc[acik] T2:eecc[bcjk] - drop T2:eecc[bcjk] - drop J:eecc[acik] - alloc I:ee[bc] + alloc I:eecc[acil] load K:eecc[cdkl] - load INTpp:eecc[bdkl] - .I:ee[bc] += K:eecc[cdkl] INTpp:eecc[bdkl] - drop INTpp:eecc[bdkl] + load T2:eecc[adik] + .I:eecc[acil] += K:eecc[cdkl] T2:eecc[adik] + drop T2:eecc[adik] drop K:eecc[cdkl] - load T2:eecc[acij] - .R2u:eecc[abij] += -4 * I:ee[bc] T2:eecc[acij] - drop T2:eecc[acij] - drop I:ee[bc] - alloc I:eccc[aijk] - load J:eeec[acdk] - load INTpp:eecc[cdij] - .I:eccc[aijk] += J:eeec[acdk] INTpp:eecc[cdij] - drop INTpp:eecc[cdij] - drop J:eeec[acdk] - load T1:ec[bk] - .R2u:eecc[abij] += -2 * I:eccc[aijk] T1:ec[bk] - drop T1:ec[bk] - drop I:eccc[aijk] - alloc I:eccc[aijl] - load K:eccc[cjkl] + load T2:eecc[bcjl] + .R2u:eecc[abij] += -2 * I:eecc[acil] T2:eecc[bcjl] + drop T2:eecc[bcjl] + drop I:eecc[acil] + load K:eecc[bcjk] load T2:eecc[acki] - .I:eccc[aijl] += K:eccc[cjkl] T2:eecc[acki] + .R2u:eecc[abij] += -2 * K:eecc[bcjk] T2:eecc[acki] drop T2:eecc[acki] - drop K:eccc[cjkl] - load T1:ec[bl] - .R2u:eecc[abij] += 2 * I:eccc[aijl] T1:ec[bl] - drop T1:ec[bl] - drop I:eccc[aijl] - alloc I:eecc[adil] - load K:eecc[cdkl] - load T2:eecc[acik] - .I:eecc[adil] += K:eecc[cdkl] T2:eecc[acik] - drop T2:eecc[acik] - drop K:eecc[cdkl] - load T2:eecc[bdjl] - .R2u:eecc[abij] += 4 * I:eecc[adil] T2:eecc[bdjl] - drop T2:eecc[bdjl] - drop I:eecc[adil] - load f:ee[bc] - load T2:eecc[acij] - .R2u:eecc[abij] += 2 * f:ee[bc] T2:eecc[acij] - drop T2:eecc[acij] - drop f:ee[bc] - load K:eeee[abcd] - load INTpp:eecc[cdij] - .R2u:eecc[abij] += K:eeee[abcd] INTpp:eecc[cdij] - drop INTpp:eecc[cdij] - drop K:eeee[abcd] - load K:eecc[acik] - load T2:eecc[bckj] - .R2u:eecc[abij] += -2 * K:eecc[acik] T2:eecc[bckj] - drop T2:eecc[bckj] - drop K:eecc[acik] - load J:eeec[bcai] - load T1:ec[cj] - .R2u:eecc[abij] += 2 * J:eeec[bcai] T1:ec[cj] - drop T1:ec[cj] - drop J:eeec[bcai] + drop K:eecc[bcjk] store R2u:eecc[abij] alloc R2:eecc[abij] diff --git a/utilities/external-interface/examples/nevpt2.itfaa.expected b/utilities/external-interface/examples/nevpt2.itfaa.expected index 8454e6b2ca..eebf831f82 100644 --- a/utilities/external-interface/examples/nevpt2.itfaa.expected +++ b/utilities/external-interface/examples/nevpt2.itfaa.expected @@ -16,66 +16,66 @@ index-space: F, BasisMp2Fit, F tensor: ECC[], ECC tensor: ECC0[], ECC0 -tensor: CSE1:aa[xu], !Create{type:disk} -tensor: CSE1:ecaa[aivu], !Create{type:disk} +tensor: CSE1:aa[wu], !Create{type:disk} +tensor: CSE1:aaaa[wvux], !Create{type:disk} tensor: CSE1:ccaa[jivu], !Create{type:disk} -tensor: CSE10:aa[wu], !Create{type:disk} +tensor: CSE10:aa[xu], !Create{type:disk} tensor: CSE10:aaaa[wvux], !Create{type:disk} -tensor: CSE11:aa[xu], !Create{type:disk} -tensor: CSE11:aaaa[vuxw], !Create{type:disk} -tensor: CSE12:aa[wu], !Create{type:disk} -tensor: CSE13:aa[vu], !Create{type:disk} -tensor: CSE14:ec[ai], !Create{type:disk} +tensor: CSE11:aa[wu], !Create{type:disk} +tensor: CSE11:Faa[Fuw], !Create{type:disk} +tensor: CSE12:ec[ai], !Create{type:disk} +tensor: CSE13:ec[ai], !Create{type:disk} +tensor: CSE14:Fca[Fiu], !Create{type:disk} tensor: CSE15:ec[aj], !Create{type:disk} tensor: CSE16:ec[bi], !Create{type:disk} -tensor: CSE17:ec[ai], !Create{type:disk} -tensor: CSE18:Fca[Fiu], !Create{type:disk} -tensor: CSE19:aaaa[zxvu], !Create{type:disk} -tensor: CSE2:F[F], !Create{type:disk} +tensor: CSE17:aaaa[wyuv], !Create{type:disk} +tensor: CSE18:aaaa[ywvu], !Create{type:disk} +tensor: CSE19:aaaa[uwyx], !Create{type:disk} +tensor: CSE2:aa[xu], !Create{type:disk} tensor: CSE2:aaaa[wvux], !Create{type:disk} -tensor: CSE20:aaaa[yxuv], !Create{type:disk} -tensor: CSE21:ccaa[ijuv], !Create{type:disk} -tensor: CSE22:ccaa[jiwu], !Create{type:disk} -tensor: CSE23:ccaa[jiwx], !Create{type:disk} -tensor: CSE24:aaaa[vwuz], !Create{type:disk} -tensor: CSE25:aaaa[xwzy], !Create{type:disk} -tensor: CSE26:aaaa[uxvz], !Create{type:disk} -tensor: CSE27:aaaa[uxvz], !Create{type:disk} -tensor: CSE28:aaaa[vwuz], !Create{type:disk} -tensor: CSE29:aaaa[uxvy], !Create{type:disk} -tensor: CSE3:Faa[Fux], !Create{type:disk} -tensor: CSE3:aaaa[wvux], !Create{type:disk} -tensor: CSE30:ecaa[aiwx], !Create{type:disk} -tensor: CSE31:ecaa[aixw], !Create{type:disk} -tensor: CSE32:ecaa[aiwx], !Create{type:disk} -tensor: CSE33:ecaa[aiwx], !Create{type:disk} +tensor: CSE20:aaaa[wvuz], !Create{type:disk} +tensor: CSE21:aaaa[wvuz], !Create{type:disk} +tensor: CSE22:aaaa[vwuz], !Create{type:disk} +tensor: CSE23:aaaa[vwuz], !Create{type:disk} +tensor: CSE24:ccaa[jivu], !Create{type:disk} +tensor: CSE25:aa[vw], !Create{type:disk} +tensor: CSE26:aa[vx], !Create{type:disk} +tensor: CSE27:ccaa[jiuv], !Create{type:disk} +tensor: CSE28:aaaa[xvuy], !Create{type:disk} +tensor: CSE29:ccaa[jiuw], !Create{type:disk} +tensor: CSE3:aa[vu], !Create{type:disk} +tensor: CSE3:ecaa[aivu], !Create{type:disk} +tensor: CSE30:ecaa[aivw], !Create{type:disk} +tensor: CSE31:ecaa[aivw], !Create{type:disk} +tensor: CSE32:ecaa[aivw], !Create{type:disk} +tensor: CSE33:ecaa[aivw], !Create{type:disk} tensor: CSE34:ecaa[aivw], !Create{type:disk} -tensor: CSE35:ecca[aiju], !Create{type:disk} +tensor: CSE35:ecca[ajiv], !Create{type:disk} tensor: CSE36:ecca[ajiu], !Create{type:disk} -tensor: CSE37:ecca[ajiv], !Create{type:disk} +tensor: CSE37:ecca[aiju], !Create{type:disk} tensor: CSE4:Faa[Fuw], !Create{type:disk} -tensor: CSE4:aaaa[wvux], !Create{type:disk} -tensor: CSE5:aa[xu], !Create{type:disk} -tensor: CSE5:aaaa[xwvu], !Create{type:disk} -tensor: CSE6:ca[iu], !Create{type:disk} -tensor: CSE6:ccaa[jivu], !Create{type:disk} +tensor: CSE4:aaaa[xwvu], !Create{type:disk} +tensor: CSE5:ca[iu], !Create{type:disk} +tensor: CSE5:ccaa[jivu], !Create{type:disk} +tensor: CSE6:F[F], !Create{type:disk} +tensor: CSE6:Faa[Fuw], !Create{type:disk} tensor: CSE7:Faa[Fux], !Create{type:disk} tensor: CSE7:aaaa[wvux], !Create{type:disk} -tensor: CSE8:aa[uy], !Create{type:disk} -tensor: CSE8:Faa[Fuw], !Create{type:disk} -tensor: CSE9:aa[uy], !Create{type:disk} -tensor: CSE9:Faa[Fuw], !Create{type:disk} +tensor: CSE8:Faa[Fux], !Create{type:disk} +tensor: CSE8:aaaa[vuwx], !Create{type:disk} +tensor: CSE9:aa[wu], !Create{type:disk} +tensor: CSE9:aaaa[wvux], !Create{type:disk} tensor: DF:Fea[Fau], DF:Fea tensor: DF:Fec[Fai], DF:Fec -tensor: DF:Faa[Fvw], DF:Faa +tensor: DF:Faa[Fuv], DF:Faa tensor: DF:Fca[Fiu], DF:Fca tensor: I:F[F], !Create{type:plain} tensor: I:ea[av], !Create{type:plain} -tensor: I:aa[uw], !Create{type:plain} +tensor: I:aa[uv], !Create{type:plain} tensor: I:ca[ix], !Create{type:plain} tensor: I:Fea[Fav], !Create{type:plain} tensor: I:Fec[Fbj], !Create{type:plain} -tensor: I:Faa[Fux], !Create{type:plain} +tensor: I:Faa[Fwx], !Create{type:plain} tensor: I:Fca[Fju], !Create{type:plain} tensor: I:eeaa[abuv], !Create{type:plain} tensor: I:eeca[abiv], !Create{type:plain} @@ -99,15 +99,15 @@ tensor: R2u:aacc[uvij], !Create{type:disk} tensor: T1:ea[ax], T1:ea tensor: T1:ac[ui], T1:ac tensor: T1s:ec[ai], T1s:ec -tensor: T2:eeaa[abwx], T2:eeaa +tensor: T2:eeaa[abxw], T2:eeaa tensor: T2:eeca[abiv], T2:eeca tensor: T2:eecc[abji], T2:eecc tensor: T2:eaac[awxi], T2:eaac tensor: T2:eaca[awix], T2:eaca -tensor: T2:eacc[auji], T2:eacc +tensor: T2:eacc[auij], T2:eacc tensor: T2:aacc[uvij], T2:aacc -tensor: Ym1:aa[wu], Ym1:aa -tensor: Ym2:aaaa[vxuw], Ym2:aaaa +tensor: Ym1:aa[uv], Ym1:aa +tensor: Ym2:aaaa[vxwu], Ym2:aaaa tensor: f:ea[au], f:ea tensor: f:ec[ai], f:ec tensor: f:aa[vw], f:aa @@ -126,16 +126,6 @@ store CSE1:ccaa[jivu] alloc ECC0[] alloc I:Fca[Fju] load DF:Fec[Fai] -load T2:eacc[auji] -.I:Fca[Fju] += DF:Fec[Fai] T2:eacc[auji] -drop T2:eacc[auji] -drop DF:Fec[Fai] -load DF:Fca[Fju] -.ECC0[] += -2 * I:Fca[Fju] DF:Fca[Fju] -drop DF:Fca[Fju] -drop I:Fca[Fju] -alloc I:Fca[Fju] -load DF:Fec[Fai] load T2:eacc[auij] .I:Fca[Fju] += DF:Fec[Fai] T2:eacc[auij] drop T2:eacc[auij] @@ -144,11 +134,27 @@ load DF:Fca[Fju] .ECC0[] += 4 * I:Fca[Fju] DF:Fca[Fju] drop DF:Fca[Fju] drop I:Fca[Fju] +load CSE1:ccaa[jivu] +load T2:aacc[uvij] +.ECC0[] += 2 * CSE1:ccaa[jivu] T2:aacc[uvij] +.ECC0[] += -1 * CSE1:ccaa[jivu] T2:aacc[uvji] +drop T2:aacc[uvji] +drop CSE1:ccaa[jivu] load f:ca[iu] load T1:ac[ui] .ECC0[] += 2 * f:ca[iu] T1:ac[ui] drop T1:ac[ui] drop f:ca[iu] +alloc I:Fca[Fju] +load DF:Fec[Fai] +load T2:eacc[auji] +.I:Fca[Fju] += DF:Fec[Fai] T2:eacc[auji] +drop T2:eacc[auji] +drop DF:Fec[Fai] +load DF:Fca[Fju] +.ECC0[] += -2 * I:Fca[Fju] DF:Fca[Fju] +drop DF:Fca[Fju] +drop I:Fca[Fju] alloc I:Fec[Fbj] load DF:Fec[Fai] load T2:eecc[abji] @@ -157,12 +163,6 @@ drop T2:eecc[abji] .ECC0[] += -1 * I:Fec[Fbj] DF:Fec[Fbj] drop DF:Fec[Fbj] drop I:Fec[Fbj] -load CSE1:ccaa[jivu] -load T2:aacc[uvij] -.ECC0[] += 2 * CSE1:ccaa[jivu] T2:aacc[uvij] -.ECC0[] += -1 * CSE1:ccaa[jivu] T2:aacc[uvji] -drop T2:aacc[uvji] -drop CSE1:ccaa[jivu] load f:ec[ai] load T1s:ec[ai] .ECC0[] += 2 * f:ec[ai] T1s:ec[ai] @@ -180,15 +180,7 @@ store ECC0[] ---- code("Energy") -alloc CSE1:ecaa[aivu] -load DF:Fea[Fau] -load DF:Fca[Fiv] -.CSE1:ecaa[aivu] += DF:Fea[Fau] DF:Fca[Fiv] -drop DF:Fca[Fiv] -drop DF:Fea[Fau] -store CSE1:ecaa[aivu] - -alloc CSE2:aaaa[wvux] +alloc CSE1:aaaa[wvux] alloc I:ecaa[aiuv] load DF:Fea[Fau] load DF:Fca[Fiv] @@ -196,12 +188,12 @@ load DF:Fca[Fiv] drop DF:Fca[Fiv] drop DF:Fea[Fau] load T2:eaac[awxi] -.CSE2:aaaa[wvux] += I:ecaa[aiuv] T2:eaac[awxi] +.CSE1:aaaa[wvux] += I:ecaa[aiuv] T2:eaac[awxi] drop T2:eaac[awxi] drop I:ecaa[aiuv] -store CSE2:aaaa[wvux] +store CSE1:aaaa[wvux] -alloc CSE3:aaaa[wvux] +alloc CSE2:aaaa[wvux] alloc I:ecaa[aiuv] load DF:Fea[Fau] load DF:Fca[Fiv] @@ -209,40 +201,43 @@ load DF:Fca[Fiv] drop DF:Fca[Fiv] drop DF:Fea[Fau] load T2:eaca[awix] -.CSE3:aaaa[wvux] += I:ecaa[aiuv] T2:eaca[awix] +.CSE2:aaaa[wvux] += I:ecaa[aiuv] T2:eaca[awix] drop T2:eaca[awix] drop I:ecaa[aiuv] -store CSE3:aaaa[wvux] +store CSE2:aaaa[wvux] -alloc CSE4:aaaa[wvux] -alloc I:Faa[Fux] +alloc CSE3:ecaa[aivu] load DF:Fea[Fau] -load T1:ea[ax] -.I:Faa[Fux] += DF:Fea[Fau] T1:ea[ax] -drop T1:ea[ax] +load DF:Fca[Fiv] +.CSE3:ecaa[aivu] += DF:Fea[Fau] DF:Fca[Fiv] +drop DF:Fca[Fiv] drop DF:Fea[Fau] -load DF:Faa[Fvw] -.CSE4:aaaa[wvux] += I:Faa[Fux] DF:Faa[Fvw] -drop DF:Faa[Fvw] -drop I:Faa[Fux] -store CSE4:aaaa[wvux] +store CSE3:ecaa[aivu] -alloc CSE5:aaaa[xwvu] +alloc CSE4:aaaa[xwvu] alloc I:ccaa[ijuv] load DF:Fca[Fiu] .I:ccaa[ijuv] += DF:Fca[Fiu] DF:Fca[Fjv] drop DF:Fca[Fiu] load T2:aacc[wxij] -.CSE5:aaaa[xwvu] += I:ccaa[ijuv] T2:aacc[wxij] +.CSE4:aaaa[xwvu] += I:ccaa[ijuv] T2:aacc[wxij] drop T2:aacc[wxij] drop I:ccaa[ijuv] -store CSE5:aaaa[xwvu] +store CSE4:aaaa[xwvu] -alloc CSE6:ccaa[jivu] +alloc CSE5:ccaa[jivu] load DF:Fca[Fiu] -.CSE6:ccaa[jivu] += DF:Fca[Fiu] DF:Fca[Fjv] +.CSE5:ccaa[jivu] += DF:Fca[Fiu] DF:Fca[Fjv] drop DF:Fca[Fiu] -store CSE6:ccaa[jivu] +store CSE5:ccaa[jivu] + +alloc CSE6:Faa[Fuw] +load DF:Fec[Fai] +load T2:eaca[auiw] +.CSE6:Faa[Fuw] += DF:Fec[Fai] T2:eaca[auiw] +drop T2:eaca[auiw] +drop DF:Fec[Fai] +store CSE6:Faa[Fuw] alloc CSE7:aaaa[wvux] alloc I:Faa[Fwx] @@ -257,21 +252,29 @@ drop DF:Faa[Fuv] drop I:Faa[Fwx] store CSE7:aaaa[wvux] -alloc CSE8:Faa[Fuw] -load DF:Fec[Fai] -load T2:eaca[auiw] -.CSE8:Faa[Fuw] += DF:Fec[Fai] T2:eaca[auiw] -drop T2:eaca[auiw] -drop DF:Fec[Fai] -store CSE8:Faa[Fuw] +alloc CSE8:aaaa[vuwx] +alloc I:eeaa[abuv] +load DF:Fea[Fau] +.I:eeaa[abuv] += DF:Fea[Fau] DF:Fea[Fbv] +drop DF:Fea[Fau] +load T2:eeaa[abxw] +.CSE8:aaaa[vuwx] += I:eeaa[abuv] T2:eeaa[abxw] +drop T2:eeaa[abxw] +drop I:eeaa[abuv] +store CSE8:aaaa[vuwx] -alloc CSE9:Faa[Fuw] -load DF:Fec[Fai] -load T2:eaac[auwi] -.CSE9:Faa[Fuw] += DF:Fec[Fai] T2:eaac[auwi] -drop T2:eaac[auwi] -drop DF:Fec[Fai] -store CSE9:Faa[Fuw] +alloc CSE9:aaaa[wvux] +alloc I:Faa[Fux] +load DF:Fea[Fau] +load T1:ea[ax] +.I:Faa[Fux] += DF:Fea[Fau] T1:ea[ax] +drop T1:ea[ax] +drop DF:Fea[Fau] +load DF:Faa[Fvw] +.CSE9:aaaa[wvux] += I:Faa[Fux] DF:Faa[Fvw] +drop DF:Faa[Fvw] +drop I:Faa[Fux] +store CSE9:aaaa[wvux] alloc CSE10:aaaa[wvux] alloc I:Faa[Fwx] @@ -286,101 +289,66 @@ drop DF:Faa[Fuv] drop I:Faa[Fwx] store CSE10:aaaa[wvux] -alloc CSE11:aaaa[vuxw] -alloc I:eeaa[abuv] -load DF:Fea[Fau] -.I:eeaa[abuv] += DF:Fea[Fau] DF:Fea[Fbv] -drop DF:Fea[Fau] -load T2:eeaa[abwx] -.CSE11:aaaa[vuxw] += I:eeaa[abuv] T2:eeaa[abwx] -drop T2:eeaa[abwx] -drop I:eeaa[abuv] -store CSE11:aaaa[vuxw] +alloc CSE11:Faa[Fuw] +load DF:Fec[Fai] +load T2:eaac[auwi] +.CSE11:Faa[Fuw] += DF:Fec[Fai] T2:eaac[auwi] +drop T2:eaac[auwi] +drop DF:Fec[Fai] +store CSE11:Faa[Fuw] alloc ECC[] -alloc I:aa[uw] -load CSE1:ecaa[aivu] -load T2:eaac[avwi] -.I:aa[uw] += CSE1:ecaa[aivu] T2:eaac[avwi] -drop T2:eaac[avwi] -drop CSE1:ecaa[aivu] -load Ym1:aa[wu] -.ECC[] += 4 * I:aa[uw] Ym1:aa[wu] -drop Ym1:aa[wu] -drop I:aa[uw] -load CSE2:aaaa[wvux] -load Ym2:aaaa[vxuw] -.ECC[] += 2 * CSE2:aaaa[wvux] Ym2:aaaa[vxuw] -.ECC[] += -4 * CSE2:aaaa[wvux] Ym2:aaaa[vxwu] +alloc I:aa[uv] +alloc I:Fca[Fjv] +load DF:Fec[Fai] +load T2:eacc[avij] +.I:Fca[Fjv] += DF:Fec[Fai] T2:eacc[avij] +drop T2:eacc[avij] +drop DF:Fec[Fai] +load DF:Fca[Fju] +.I:aa[uv] += I:Fca[Fjv] DF:Fca[Fju] +drop DF:Fca[Fju] +drop I:Fca[Fjv] +load Ym1:aa[uv] +.ECC[] += -4 * I:aa[uv] Ym1:aa[uv] +drop Ym1:aa[uv] +drop I:aa[uv] +load CSE1:aaaa[wvux] +load Ym2:aaaa[vxwu] +.ECC[] += -4 * CSE1:aaaa[wvux] Ym2:aaaa[vxwu] drop Ym2:aaaa[vxwu] +drop CSE1:aaaa[wvux] +load CSE2:aaaa[wvux] +load Ym2:aaaa[vxwu] +.ECC[] += 2 * CSE2:aaaa[wvux] Ym2:aaaa[vxwu] +.ECC[] += -4 * CSE2:aaaa[wvux] Ym2:aaaa[vxuw] +drop Ym2:aaaa[vxuw] drop CSE2:aaaa[wvux] -load CSE3:aaaa[wvux] -load Ym2:aaaa[vxuw] -.ECC[] += -4 * CSE3:aaaa[wvux] Ym2:aaaa[vxuw] -.ECC[] += 2 * CSE3:aaaa[wvux] Ym2:aaaa[vxwu] -drop Ym2:aaaa[vxwu] -drop CSE3:aaaa[wvux] alloc I:aa[uw] -load CSE1:ecaa[aivu] +load CSE3:ecaa[aivu] load T2:eaca[aviw] -.I:aa[uw] += CSE1:ecaa[aivu] T2:eaca[aviw] +.I:aa[uw] += CSE3:ecaa[aivu] T2:eaca[aviw] drop T2:eaca[aviw] -drop CSE1:ecaa[aivu] +drop CSE3:ecaa[aivu] load Ym1:aa[wu] .ECC[] += -2 * I:aa[uw] Ym1:aa[wu] drop Ym1:aa[wu] drop I:aa[uw] -alloc I:aa[uv] -alloc I:F[F] -load DF:Fca[Fiw] -load T1:ac[wi] -.I:F[F] += DF:Fca[Fiw] T1:ac[wi] -drop T1:ac[wi] -drop DF:Fca[Fiw] -load DF:Faa[Fuv] -.I:aa[uv] += I:F[F] DF:Faa[Fuv] -drop DF:Faa[Fuv] -drop I:F[F] -load Ym1:aa[vu] -.ECC[] += 4 * I:aa[uv] Ym1:aa[vu] -drop Ym1:aa[vu] -drop I:aa[uv] -load CSE4:aaaa[wvux] -load Ym2:aaaa[wxvu] -.ECC[] += 4 * CSE4:aaaa[wvux] Ym2:aaaa[wxvu] -.ECC[] += -2 * CSE4:aaaa[wvux] Ym2:aaaa[wxuv] -drop Ym2:aaaa[wxuv] -drop CSE4:aaaa[wvux] -load CSE5:aaaa[xwvu] -load Ym2:aaaa[uvxw] -.ECC[] += -1 * CSE5:aaaa[xwvu] Ym2:aaaa[uvxw] -drop Ym2:aaaa[uvxw] -drop CSE5:aaaa[xwvu] -alloc I:aa[vw] -load CSE6:ccaa[jivu] -load T2:aacc[uwji] -.I:aa[vw] += CSE6:ccaa[jivu] T2:aacc[uwji] -drop T2:aacc[uwji] -drop CSE6:ccaa[jivu] -load Ym1:aa[vw] -.ECC[] += 2 * I:aa[vw] Ym1:aa[vw] -drop Ym1:aa[vw] -drop I:aa[vw] -alloc I:aa[vw] -load CSE6:ccaa[jivu] -load T2:aacc[uwij] -.I:aa[vw] += CSE6:ccaa[jivu] T2:aacc[uwij] -drop T2:aacc[uwij] -drop CSE6:ccaa[jivu] -load Ym1:aa[vw] -.ECC[] += -4 * I:aa[vw] Ym1:aa[vw] -drop Ym1:aa[vw] -drop I:aa[vw] -load CSE5:aaaa[xwvu] -load Ym2:aaaa[uvwx] -.ECC[] += 2 * CSE5:aaaa[xwvu] Ym2:aaaa[uvwx] -drop Ym2:aaaa[uvwx] -drop CSE5:aaaa[xwvu] +load CSE1:aaaa[wvux] +load Ym2:aaaa[vxuw] +.ECC[] += 2 * CSE1:aaaa[wvux] Ym2:aaaa[vxuw] +drop Ym2:aaaa[vxuw] +drop CSE1:aaaa[wvux] +alloc I:aa[uw] +load CSE3:ecaa[aivu] +load T2:eaac[avwi] +.I:aa[uw] += CSE3:ecaa[aivu] T2:eaac[avwi] +drop T2:eaac[avwi] +drop CSE3:ecaa[aivu] +load Ym1:aa[wu] +.ECC[] += 4 * I:aa[uw] Ym1:aa[wu] +drop Ym1:aa[wu] +drop I:aa[uw] alloc I:aa[uv] load f:ca[iu] load T1:ac[vi] @@ -391,64 +359,52 @@ load Ym1:aa[uv] .ECC[] += -2 * I:aa[uv] Ym1:aa[uv] drop Ym1:aa[uv] drop I:aa[uv] -load CSE7:aaaa[wvux] -load Ym2:aaaa[vxuw] -.ECC[] += 8 * CSE7:aaaa[wvux] Ym2:aaaa[vxuw] -.ECC[] += -4 * CSE7:aaaa[wvux] Ym2:aaaa[uxwv] -drop Ym2:aaaa[uxwv] -drop CSE7:aaaa[wvux] -alloc I:aa[vw] -load DF:Faa[Fuv] -load CSE8:Faa[Fuw] -.I:aa[vw] += DF:Faa[Fuv] CSE8:Faa[Fuw] -drop CSE8:Faa[Fuw] -drop DF:Faa[Fuv] -load Ym1:aa[wv] -.ECC[] += 4 * I:aa[vw] Ym1:aa[wv] -drop Ym1:aa[wv] -drop I:aa[vw] -alloc I:aa[uv] -load f:ea[au] -load T1:ea[av] -.I:aa[uv] += f:ea[au] T1:ea[av] -drop T1:ea[av] -drop f:ea[au] -load Ym1:aa[vu] -.ECC[] += 2 * I:aa[uv] Ym1:aa[vu] -drop Ym1:aa[vu] -drop I:aa[uv] -alloc I:aa[vw] -load DF:Faa[Fuv] -load CSE9:Faa[Fuw] -.I:aa[vw] += DF:Faa[Fuv] CSE9:Faa[Fuw] -drop CSE9:Faa[Fuw] -drop DF:Faa[Fuv] -load Ym1:aa[wv] -.ECC[] += -2 * I:aa[vw] Ym1:aa[wv] -drop Ym1:aa[wv] -drop I:aa[vw] -load CSE10:aaaa[wvux] -load Ym2:aaaa[vxuw] -.ECC[] += -4 * CSE10:aaaa[wvux] Ym2:aaaa[vxuw] -.ECC[] += 2 * CSE10:aaaa[wvux] Ym2:aaaa[uxwv] -drop Ym2:aaaa[uxwv] -drop CSE10:aaaa[wvux] alloc I:ca[ix] alloc I:Faa[Fwx] load DF:Faa[Fuv] -load Ym2:aaaa[vwxu] -.I:Faa[Fwx] += DF:Faa[Fuv] Ym2:aaaa[vwxu] -drop Ym2:aaaa[vwxu] +load Ym2:aaaa[vwux] +.I:Faa[Fwx] += DF:Faa[Fuv] Ym2:aaaa[vwux] +drop Ym2:aaaa[vwux] drop DF:Faa[Fuv] load DF:Fca[Fiw] .I:ca[ix] += I:Faa[Fwx] DF:Fca[Fiw] drop DF:Fca[Fiw] drop I:Faa[Fwx] load T1:ac[xi] -.ECC[] += 2 * I:ca[ix] T1:ac[xi] +.ECC[] += -4 * I:ca[ix] T1:ac[xi] drop T1:ac[xi] drop I:ca[ix] alloc I:aa[uv] +alloc I:Fea[Fav] +load DF:Fec[Fbi] +load T2:eeca[abiv] +.I:Fea[Fav] += DF:Fec[Fbi] T2:eeca[abiv] +drop T2:eeca[abiv] +drop DF:Fec[Fbi] +load DF:Fea[Fau] +.I:aa[uv] += I:Fea[Fav] DF:Fea[Fau] +drop DF:Fea[Fau] +drop I:Fea[Fav] +load Ym1:aa[vu] +.ECC[] += -2 * I:aa[uv] Ym1:aa[vu] +drop Ym1:aa[vu] +drop I:aa[uv] +alloc I:aa[uv] +alloc I:Fea[Fav] +load DF:Fec[Fbi] +load T2:eeca[baiv] +.I:Fea[Fav] += DF:Fec[Fbi] T2:eeca[baiv] +drop T2:eeca[baiv] +drop DF:Fec[Fbi] +load DF:Fea[Fau] +.I:aa[uv] += I:Fea[Fav] DF:Fea[Fau] +drop DF:Fea[Fau] +drop I:Fea[Fav] +load Ym1:aa[vu] +.ECC[] += 4 * I:aa[uv] Ym1:aa[vu] +drop Ym1:aa[vu] +drop I:aa[uv] +alloc I:aa[uv] alloc I:Fca[Fiu] load DF:Fea[Fau] load T1s:ec[ai] @@ -463,16 +419,84 @@ load Ym1:aa[vu] .ECC[] += -2 * I:aa[uv] Ym1:aa[vu] drop Ym1:aa[vu] drop I:aa[uv] +alloc I:ca[ix] +alloc I:Faa[Fwx] +load DF:Faa[Fuv] +load Ym2:aaaa[uwxv] +.I:Faa[Fwx] += DF:Faa[Fuv] Ym2:aaaa[uwxv] +drop Ym2:aaaa[uwxv] +drop DF:Faa[Fuv] +load DF:Fca[Fiw] +.I:ca[ix] += I:Faa[Fwx] DF:Fca[Fiw] +drop DF:Fca[Fiw] +drop I:Faa[Fwx] +load T1:ac[xi] +.ECC[] += 2 * I:ca[ix] T1:ac[xi] +drop T1:ac[xi] +drop I:ca[ix] +load CSE4:aaaa[xwvu] +load Ym2:aaaa[uvwx] +.ECC[] += 2 * CSE4:aaaa[xwvu] Ym2:aaaa[uvwx] +.ECC[] += -1 * CSE4:aaaa[xwvu] Ym2:aaaa[uvxw] +drop Ym2:aaaa[uvxw] +drop CSE4:aaaa[xwvu] +alloc I:aa[vw] +load CSE5:ccaa[jivu] +load T2:aacc[uwij] +.I:aa[vw] += CSE5:ccaa[jivu] T2:aacc[uwij] +drop T2:aacc[uwij] +drop CSE5:ccaa[jivu] +load Ym1:aa[vw] +.ECC[] += -4 * I:aa[vw] Ym1:aa[vw] +drop Ym1:aa[vw] +drop I:aa[vw] +alloc I:aa[uw] +load CSE5:ccaa[jivu] +load T2:aacc[vwij] +.I:aa[uw] += CSE5:ccaa[jivu] T2:aacc[vwij] +drop T2:aacc[vwij] +drop CSE5:ccaa[jivu] +load Ym1:aa[uw] +.ECC[] += 2 * I:aa[uw] Ym1:aa[uw] +drop Ym1:aa[uw] +drop I:aa[uw] alloc I:aa[uv] -load f:ec[ai] -load T2:eaac[auvi] -.I:aa[uv] += f:ec[ai] T2:eaac[auvi] -drop T2:eaac[auvi] -drop f:ec[ai] -load Ym1:aa[vu] -.ECC[] += -2 * I:aa[uv] Ym1:aa[vu] -drop Ym1:aa[vu] +alloc I:F[F] +load DF:Fca[Fiw] +load T1:ac[wi] +.I:F[F] += DF:Fca[Fiw] T1:ac[wi] +drop T1:ac[wi] +drop DF:Fca[Fiw] +load DF:Faa[Fuv] +.I:aa[uv] += I:F[F] DF:Faa[Fuv] +drop DF:Faa[Fuv] +drop I:F[F] +load Ym1:aa[uv] +.ECC[] += 4 * I:aa[uv] Ym1:aa[uv] +drop Ym1:aa[uv] drop I:aa[uv] +alloc I:aa[vw] +load DF:Faa[Fuv] +load CSE6:Faa[Fuw] +.I:aa[vw] += DF:Faa[Fuv] CSE6:Faa[Fuw] +drop CSE6:Faa[Fuw] +drop DF:Faa[Fuv] +load Ym1:aa[wv] +.ECC[] += 4 * I:aa[vw] Ym1:aa[wv] +drop Ym1:aa[wv] +drop I:aa[vw] +load CSE7:aaaa[wvux] +load Ym2:aaaa[vxuw] +.ECC[] += 8 * CSE7:aaaa[wvux] Ym2:aaaa[vxuw] +.ECC[] += -4 * CSE7:aaaa[wvux] Ym2:aaaa[vxwu] +drop Ym2:aaaa[vxwu] +drop CSE7:aaaa[wvux] +load CSE8:aaaa[vuwx] +load Ym2:aaaa[wxuv] +.ECC[] += -1 * CSE8:aaaa[vuwx] Ym2:aaaa[wxuv] +.ECC[] += 2 * CSE8:aaaa[vuxw] Ym2:aaaa[wxuv] +drop Ym2:aaaa[wxuv] +drop CSE8:aaaa[vuxw] alloc I:aa[uv] load f:ec[ai] load T2:eaca[auiv] @@ -483,26 +507,25 @@ load Ym1:aa[vu] .ECC[] += 4 * I:aa[uv] Ym1:aa[vu] drop Ym1:aa[vu] drop I:aa[uv] -load CSE11:aaaa[vuxw] -load Ym2:aaaa[wxuv] -.ECC[] += 2 * CSE11:aaaa[vuxw] Ym2:aaaa[wxuv] -.ECC[] += -1 * CSE11:aaaa[vuxw] Ym2:aaaa[wxvu] -drop Ym2:aaaa[wxvu] -drop CSE11:aaaa[vuxw] alloc I:aa[uv] -alloc I:Fca[Fjv] -load DF:Fec[Fai] -load T2:eacc[avij] -.I:Fca[Fjv] += DF:Fec[Fai] T2:eacc[avij] -drop T2:eacc[avij] -drop DF:Fec[Fai] -load DF:Fca[Fju] -.I:aa[uv] += I:Fca[Fjv] DF:Fca[Fju] -drop DF:Fca[Fju] -drop I:Fca[Fjv] -load Ym1:aa[uv] -.ECC[] += -4 * I:aa[uv] Ym1:aa[uv] -drop Ym1:aa[uv] +load f:ea[au] +load T1:ea[av] +.I:aa[uv] += f:ea[au] T1:ea[av] +drop T1:ea[av] +drop f:ea[au] +load Ym1:aa[vu] +.ECC[] += 2 * I:aa[uv] Ym1:aa[vu] +drop Ym1:aa[vu] +drop I:aa[uv] +alloc I:aa[uv] +load f:ec[ai] +load T2:eaac[auvi] +.I:aa[uv] += f:ec[ai] T2:eaac[auvi] +drop T2:eaac[auvi] +drop f:ec[ai] +load Ym1:aa[vu] +.ECC[] += -2 * I:aa[uv] Ym1:aa[vu] +drop Ym1:aa[vu] drop I:aa[uv] alloc I:aa[uv] alloc I:Fca[Fjv] @@ -519,66 +542,27 @@ load Ym1:aa[uv] .ECC[] += 2 * I:aa[uv] Ym1:aa[uv] drop Ym1:aa[uv] drop I:aa[uv] -alloc I:ca[ix] -alloc I:Faa[Fwx] -load DF:Faa[Fuv] -load Ym2:aaaa[vwux] -.I:Faa[Fwx] += DF:Faa[Fuv] Ym2:aaaa[vwux] -drop Ym2:aaaa[vwux] -drop DF:Faa[Fuv] -load DF:Fca[Fiw] -.I:ca[ix] += I:Faa[Fwx] DF:Fca[Fiw] -drop DF:Fca[Fiw] -drop I:Faa[Fwx] -load T1:ac[xi] -.ECC[] += -4 * I:ca[ix] T1:ac[xi] -drop T1:ac[xi] -drop I:ca[ix] -alloc I:aa[uv] -alloc I:Fea[Fav] -load DF:Fec[Fbi] -load T2:eeca[abiv] -.I:Fea[Fav] += DF:Fec[Fbi] T2:eeca[abiv] -drop T2:eeca[abiv] -drop DF:Fec[Fbi] -load DF:Fea[Fau] -.I:aa[uv] += I:Fea[Fav] DF:Fea[Fau] -drop DF:Fea[Fau] -drop I:Fea[Fav] -load Ym1:aa[vu] -.ECC[] += -2 * I:aa[uv] Ym1:aa[vu] -drop Ym1:aa[vu] -drop I:aa[uv] -alloc I:aa[uw] -alloc I:Faa[Fvw] +load CSE9:aaaa[wvux] +load Ym2:aaaa[wxvu] +.ECC[] += 4 * CSE9:aaaa[wvux] Ym2:aaaa[wxvu] +.ECC[] += -2 * CSE9:aaaa[wvux] Ym2:aaaa[wxuv] +drop Ym2:aaaa[wxuv] +drop CSE9:aaaa[wvux] +alloc I:aa[vw] +alloc I:Faa[Fuw] load DF:Fca[Fiw] -load T1:ac[vi] -.I:Faa[Fvw] += DF:Fca[Fiw] T1:ac[vi] -drop T1:ac[vi] +load T1:ac[ui] +.I:Faa[Fuw] += DF:Fca[Fiw] T1:ac[ui] +drop T1:ac[ui] drop DF:Fca[Fiw] load DF:Faa[Fuv] -.I:aa[uw] += I:Faa[Fvw] DF:Faa[Fuv] +.I:aa[vw] += I:Faa[Fuw] DF:Faa[Fuv] drop DF:Faa[Fuv] -drop I:Faa[Fvw] -load Ym1:aa[wu] -.ECC[] += -2 * I:aa[uw] Ym1:aa[wu] -drop Ym1:aa[wu] -drop I:aa[uw] -alloc I:aa[uv] -alloc I:Fea[Fav] -load DF:Fec[Fbi] -load T2:eeca[baiv] -.I:Fea[Fav] += DF:Fec[Fbi] T2:eeca[baiv] -drop T2:eeca[baiv] -drop DF:Fec[Fbi] -load DF:Fea[Fau] -.I:aa[uv] += I:Fea[Fav] DF:Fea[Fau] -drop DF:Fea[Fau] -drop I:Fea[Fav] -load Ym1:aa[vu] -.ECC[] += 4 * I:aa[uv] Ym1:aa[vu] -drop Ym1:aa[vu] -drop I:aa[uv] +drop I:Faa[Fuw] +load Ym1:aa[wv] +.ECC[] += -2 * I:aa[vw] Ym1:aa[wv] +drop Ym1:aa[wv] +drop I:aa[vw] alloc I:aa[uv] alloc I:F[F] load DF:Fec[Fai] @@ -590,40 +574,59 @@ load DF:Faa[Fuv] .I:aa[uv] += I:F[F] DF:Faa[Fuv] drop DF:Faa[Fuv] drop I:F[F] -load Ym1:aa[vu] -.ECC[] += 4 * I:aa[uv] Ym1:aa[vu] -drop Ym1:aa[vu] +load Ym1:aa[uv] +.ECC[] += 4 * I:aa[uv] Ym1:aa[uv] +drop Ym1:aa[uv] drop I:aa[uv] +load CSE10:aaaa[wvux] +load Ym2:aaaa[vxuw] +.ECC[] += -4 * CSE10:aaaa[wvux] Ym2:aaaa[vxuw] +.ECC[] += 2 * CSE10:aaaa[wvux] Ym2:aaaa[uxwv] +drop Ym2:aaaa[uxwv] +drop CSE10:aaaa[wvux] +alloc I:aa[vw] +load DF:Faa[Fuv] +load CSE11:Faa[Fuw] +.I:aa[vw] += DF:Faa[Fuv] CSE11:Faa[Fuw] +drop CSE11:Faa[Fuw] +drop DF:Faa[Fuv] +load Ym1:aa[wv] +.ECC[] += -2 * I:aa[vw] Ym1:aa[wv] +drop Ym1:aa[wv] +drop I:aa[vw] store ECC[] ---- code("Residual") -alloc CSE1:aa[xu] -alloc I:F[F] -load DF:Faa[Fvw] -load Ym1:aa[wv] -.I:F[F] += DF:Faa[Fvw] Ym1:aa[wv] -drop Ym1:aa[wv] -.CSE1:aa[xu] += I:F[F] DF:Faa[Fux] -drop DF:Faa[Fux] -drop I:F[F] -store CSE1:aa[xu] - -alloc CSE2:F[F] +alloc CSE1:aa[wu] +alloc I:aaaa[vwxy] load DF:Faa[Fvw] -load Ym1:aa[wv] -.CSE2:F[F] += DF:Faa[Fvw] Ym1:aa[wv] -drop Ym1:aa[wv] +.I:aaaa[vwxy] += DF:Faa[Fvw] DF:Faa[Fxy] drop DF:Faa[Fvw] -store CSE2:F[F] +load Ym2:aaaa[uxyv] +.CSE1:aa[wu] += I:aaaa[vwxy] Ym2:aaaa[uxyv] +drop Ym2:aaaa[uxyv] +drop I:aaaa[vwxy] +store CSE1:aa[wu] -alloc CSE3:Faa[Fux] +alloc CSE2:aa[xu] +alloc I:aaaa[vwxy] load DF:Faa[Fvw] -load Ym2:aaaa[uwvx] -.CSE3:Faa[Fux] += DF:Faa[Fvw] Ym2:aaaa[uwvx] -drop Ym2:aaaa[uwvx] +.I:aaaa[vwxy] += DF:Faa[Fvw] DF:Faa[Fxy] drop DF:Faa[Fvw] -store CSE3:Faa[Fux] +load Ym2:aaaa[uwyv] +.CSE2:aa[xu] += I:aaaa[vwxy] Ym2:aaaa[uwyv] +drop Ym2:aaaa[uwyv] +drop I:aaaa[vwxy] +store CSE2:aa[xu] + +alloc CSE3:aa[vu] +load f:aa[vw] +load Ym1:aa[uw] +.CSE3:aa[vu] += f:aa[vw] Ym1:aa[uw] +drop Ym1:aa[uw] +drop f:aa[vw] +store CSE3:aa[vu] alloc CSE4:Faa[Fuw] load DF:Faa[Fvu] @@ -633,189 +636,169 @@ drop Ym1:aa[vw] drop DF:Faa[Fvu] store CSE4:Faa[Fuw] -alloc CSE5:aa[xu] -alloc I:Faa[Fuw] -load DF:Faa[Fvu] -load Ym1:aa[vw] -.I:Faa[Fuw] += DF:Faa[Fvu] Ym1:aa[vw] -drop Ym1:aa[vw] -.CSE5:aa[xu] += I:Faa[Fuw] DF:Faa[Fwx] -drop DF:Faa[Fwx] -drop I:Faa[Fuw] -store CSE5:aa[xu] - -alloc CSE6:ca[iu] +alloc CSE5:ca[iu] load g:cc[ji] load T1:ac[uj] -.CSE6:ca[iu] += g:cc[ji] T1:ac[uj] +.CSE5:ca[iu] += g:cc[ji] T1:ac[uj] drop T1:ac[uj] drop g:cc[ji] -store CSE6:ca[iu] +store CSE5:ca[iu] + +alloc CSE6:F[F] +load DF:Faa[Fvw] +load Ym1:aa[vw] +.CSE6:F[F] += DF:Faa[Fvw] Ym1:aa[vw] +drop Ym1:aa[vw] +drop DF:Faa[Fvw] +store CSE6:F[F] alloc CSE7:Faa[Fux] load DF:Faa[Fvw] -load Ym2:aaaa[uwxv] -.CSE7:Faa[Fux] += DF:Faa[Fvw] Ym2:aaaa[uwxv] -drop Ym2:aaaa[uwxv] +load Ym2:aaaa[uwvx] +.CSE7:Faa[Fux] += DF:Faa[Fvw] Ym2:aaaa[uwvx] +drop Ym2:aaaa[uwvx] drop DF:Faa[Fvw] store CSE7:Faa[Fux] -alloc CSE8:aa[uy] -alloc I:aaaa[vwxy] +alloc CSE8:Faa[Fux] load DF:Faa[Fvw] -.I:aaaa[vwxy] += DF:Faa[Fvw] DF:Faa[Fxy] -drop DF:Faa[Fvw] load Ym2:aaaa[uwxv] -.CSE8:aa[uy] += I:aaaa[vwxy] Ym2:aaaa[uwxv] +.CSE8:Faa[Fux] += DF:Faa[Fvw] Ym2:aaaa[uwxv] drop Ym2:aaaa[uwxv] -drop I:aaaa[vwxy] -store CSE8:aa[uy] - -alloc CSE9:aa[uy] -alloc I:aaaa[vwxy] -load DF:Faa[Fvw] -.I:aaaa[vwxy] += DF:Faa[Fvw] DF:Faa[Fxy] drop DF:Faa[Fvw] -load Ym2:aaaa[uwvx] -.CSE9:aa[uy] += I:aaaa[vwxy] Ym2:aaaa[uwvx] -drop Ym2:aaaa[uwvx] -drop I:aaaa[vwxy] -store CSE9:aa[uy] - -alloc CSE10:aa[wu] -load f:aa[vw] -load Ym1:aa[uv] -.CSE10:aa[wu] += f:aa[vw] Ym1:aa[uv] -drop Ym1:aa[uv] -drop f:aa[vw] -store CSE10:aa[wu] +store CSE8:Faa[Fux] alloc R1:ac[ui] -load CSE1:aa[xu] +load CSE2:aa[xu] load T1:ac[xi] -.R1:ac[ui] += 2 * CSE1:aa[xu] T1:ac[xi] +.R1:ac[ui] += -2 * CSE2:aa[xu] T1:ac[xi] drop T1:ac[xi] -drop CSE1:aa[xu] -load CSE2:F[F] -load DF:Fca[Fiu] -.R1:ac[ui] += 2 * CSE2:F[F] DF:Fca[Fiu] -drop DF:Fca[Fiu] -drop CSE2:F[F] +drop CSE2:aa[xu] +load CSE1:aa[wu] +load T1:ac[wi] +.R1:ac[ui] += CSE1:aa[wu] T1:ac[wi] +drop T1:ac[wi] +drop CSE1:aa[wu] load f:ca[iu] .R1:ac[ui] += f:ca[iu] drop f:ca[iu] -load CSE3:Faa[Fux] -load DF:Fca[Fix] -.R1:ac[ui] += CSE3:Faa[Fux] DF:Fca[Fix] -drop DF:Fca[Fix] -drop CSE3:Faa[Fux] -load f:aa[uv] +alloc I:aa[uv] +alloc I:Faa[Fvx] +load DF:Faa[Fvw] +load Ym1:aa[xw] +.I:Faa[Fvx] += DF:Faa[Fvw] Ym1:aa[xw] +drop Ym1:aa[xw] +.I:aa[uv] += I:Faa[Fvx] DF:Faa[Fxu] +drop DF:Faa[Fxu] +drop I:Faa[Fvx] load T1:ac[vi] -.R1:ac[ui] += f:aa[uv] T1:ac[vi] +.R1:ac[ui] += -1 * I:aa[uv] T1:ac[vi] drop T1:ac[vi] -drop f:aa[uv] +drop I:aa[uv] +load CSE3:aa[vu] +load T1:ac[vi] +.R1:ac[ui] += -1 * CSE3:aa[vu] T1:ac[vi] +drop T1:ac[vi] +drop CSE3:aa[vu] load CSE4:Faa[Fuw] load DF:Fca[Fiw] .R1:ac[ui] += -1 * CSE4:Faa[Fuw] DF:Fca[Fiw] drop DF:Fca[Fiw] drop CSE4:Faa[Fuw] -load CSE5:aa[xu] +load CSE5:ca[iu] +.R1:ac[ui] += -1 * CSE5:ca[iu] +load Ym1:aa[uv] +.R1:ac[ui] += CSE5:ca[iv] Ym1:aa[uv] +drop Ym1:aa[uv] +drop CSE5:ca[iv] +load f:aa[uv] +load T1:ac[vi] +.R1:ac[ui] += f:aa[uv] T1:ac[vi] +drop T1:ac[vi] +drop f:aa[uv] +load CSE6:F[F] +load DF:Fca[Fiu] +.R1:ac[ui] += 2 * CSE6:F[F] DF:Fca[Fiu] +drop DF:Fca[Fiu] +drop CSE6:F[F] +alloc I:aa[ux] +load CSE6:F[F] +load DF:Faa[Fxu] +.I:aa[ux] += CSE6:F[F] DF:Faa[Fxu] +drop DF:Faa[Fxu] +drop CSE6:F[F] load T1:ac[xi] -.R1:ac[ui] += -1 * CSE5:aa[xu] T1:ac[xi] +.R1:ac[ui] += 2 * I:aa[ux] T1:ac[xi] drop T1:ac[xi] -drop CSE5:aa[xu] +drop I:aa[ux] +load CSE7:Faa[Fux] +load DF:Fca[Fix] +.R1:ac[ui] += CSE7:Faa[Fux] DF:Fca[Fix] +drop DF:Fca[Fix] +drop CSE7:Faa[Fux] load f:ca[iv] load Ym1:aa[uv] .R1:ac[ui] += -1 * f:ca[iv] Ym1:aa[uv] drop Ym1:aa[uv] drop f:ca[iv] -load CSE6:ca[iu] -.R1:ac[ui] += -1 * CSE6:ca[iu] -load Ym1:aa[uv] -.R1:ac[ui] += CSE6:ca[iv] Ym1:aa[uv] -drop Ym1:aa[uv] -drop CSE6:ca[iv] -load CSE7:Faa[Fux] +load CSE8:Faa[Fux] load DF:Fca[Fix] -.R1:ac[ui] += -2 * CSE7:Faa[Fux] DF:Fca[Fix] +.R1:ac[ui] += -2 * CSE8:Faa[Fux] DF:Fca[Fix] drop DF:Fca[Fix] -drop CSE7:Faa[Fux] -load CSE8:aa[uy] -load T1:ac[yi] -.R1:ac[ui] += -2 * CSE8:aa[uy] T1:ac[yi] -drop T1:ac[yi] -drop CSE8:aa[uy] -load CSE9:aa[uy] -load T1:ac[yi] -.R1:ac[ui] += CSE9:aa[uy] T1:ac[yi] -drop T1:ac[yi] -drop CSE9:aa[uy] -load CSE10:aa[wu] -load T1:ac[wi] -.R1:ac[ui] += -1 * CSE10:aa[wu] T1:ac[wi] -drop T1:ac[wi] -drop CSE10:aa[wu] +drop CSE8:Faa[Fux] store R1:ac[ui] -alloc CSE11:aa[xu] +alloc CSE9:aa[wu] alloc I:aaaa[vwxy] load DF:Faa[Fvw] .I:aaaa[vwxy] += DF:Faa[Fvw] DF:Faa[Fxy] drop DF:Faa[Fvw] -load Ym2:aaaa[ywuv] -.CSE11:aa[xu] += I:aaaa[vwxy] Ym2:aaaa[ywuv] -drop Ym2:aaaa[ywuv] +load Ym2:aaaa[yvux] +.CSE9:aa[wu] += I:aaaa[vwxy] Ym2:aaaa[yvux] +drop Ym2:aaaa[yvux] drop I:aaaa[vwxy] -store CSE11:aa[xu] +store CSE9:aa[wu] -alloc CSE12:aa[wu] +alloc CSE10:aa[xu] alloc I:aaaa[vwxy] load DF:Faa[Fvw] .I:aaaa[vwxy] += DF:Faa[Fvw] DF:Faa[Fxy] drop DF:Faa[Fvw] -load Ym2:aaaa[yvux] -.CSE12:aa[wu] += I:aaaa[vwxy] Ym2:aaaa[yvux] -drop Ym2:aaaa[yvux] +load Ym2:aaaa[ywuv] +.CSE10:aa[xu] += I:aaaa[vwxy] Ym2:aaaa[ywuv] +drop Ym2:aaaa[ywuv] drop I:aaaa[vwxy] -store CSE12:aa[wu] +store CSE10:aa[xu] -alloc CSE13:aa[vu] +alloc CSE11:aa[wu] load f:aa[vw] -load Ym1:aa[wu] -.CSE13:aa[vu] += f:aa[vw] Ym1:aa[wu] -drop Ym1:aa[wu] +load Ym1:aa[vu] +.CSE11:aa[wu] += f:aa[vw] Ym1:aa[vu] +drop Ym1:aa[vu] drop f:aa[vw] -store CSE13:aa[vu] +store CSE11:aa[wu] alloc R1:ea[au] -load CSE11:aa[xu] +load CSE10:aa[xu] load T1:ea[ax] -.R1:ea[au] += -2 * CSE11:aa[xu] T1:ea[ax] +.R1:ea[au] += -2 * CSE10:aa[xu] T1:ea[ax] drop T1:ea[ax] -drop CSE11:aa[xu] -load CSE12:aa[wu] +drop CSE10:aa[xu] +load CSE9:aa[wu] load T1:ea[aw] -.R1:ea[au] += CSE12:aa[wu] T1:ea[aw] +.R1:ea[au] += CSE9:aa[wu] T1:ea[aw] drop T1:ea[aw] -drop CSE12:aa[wu] +drop CSE9:aa[wu] +load CSE11:aa[wu] +load T1:ea[aw] +.R1:ea[au] += -1 * CSE11:aa[wu] T1:ea[aw] +drop T1:ea[aw] +drop CSE11:aa[wu] load DF:Fea[Fav] load CSE7:Faa[Fvu] -.R1:ea[au] += 2 * DF:Fea[Fav] CSE7:Faa[Fvu] +.R1:ea[au] += -1 * DF:Fea[Fav] CSE7:Faa[Fvu] drop CSE7:Faa[Fvu] -load CSE3:Faa[Fvu] -.R1:ea[au] += -1 * DF:Fea[Fav] CSE3:Faa[Fvu] -drop CSE3:Faa[Fvu] drop DF:Fea[Fav] -load f:ea[av] -load Ym1:aa[vu] -.R1:ea[au] += f:ea[av] Ym1:aa[vu] -drop Ym1:aa[vu] -drop f:ea[av] -load CSE13:aa[vu] -load T1:ea[av] -.R1:ea[au] += -1 * CSE13:aa[vu] T1:ea[av] -drop T1:ea[av] -drop CSE13:aa[vu] alloc I:ea[av] load g:ee[ab] load T1:ea[bv] @@ -826,15 +809,41 @@ load Ym1:aa[vu] .R1:ea[au] += I:ea[av] Ym1:aa[vu] drop Ym1:aa[vu] drop I:ea[av] +load DF:Fea[Fav] +load CSE8:Faa[Fvu] +.R1:ea[au] += 2 * DF:Fea[Fav] CSE8:Faa[Fvu] +drop CSE8:Faa[Fvu] +drop DF:Fea[Fav] +load f:ea[av] +load Ym1:aa[vu] +.R1:ea[au] += f:ea[av] Ym1:aa[vu] +drop Ym1:aa[vu] +drop f:ea[av] store R1:ea[au] -alloc CSE14:ec[ai] +alloc CSE12:ec[ai] load g:cc[ji] load T1s:ec[aj] -.CSE14:ec[ai] += g:cc[ji] T1s:ec[aj] +.CSE12:ec[ai] += g:cc[ji] T1s:ec[aj] drop T1s:ec[aj] drop g:cc[ji] -store CSE14:ec[ai] +store CSE12:ec[ai] + +alloc CSE13:ec[ai] +load g:ee[ab] +load T1s:ec[bi] +.CSE13:ec[ai] += g:ee[ab] T1s:ec[bi] +drop T1s:ec[bi] +drop g:ee[ab] +store CSE13:ec[ai] + +alloc CSE14:Fca[Fiu] +load DF:Fca[Fiv] +load Ym1:aa[uv] +.CSE14:Fca[Fiu] += DF:Fca[Fiv] Ym1:aa[uv] +drop Ym1:aa[uv] +drop DF:Fca[Fiv] +store CSE14:Fca[Fiu] alloc CSE15:ec[aj] load T2:eaac[auvj] @@ -852,66 +861,71 @@ drop Ym1:aa[vu] drop T2:eaca[buiv] store CSE16:ec[bi] -alloc CSE17:ec[ai] -load g:ee[ab] -load T1s:ec[bi] -.CSE17:ec[ai] += g:ee[ab] T1s:ec[bi] -drop T1s:ec[bi] -drop g:ee[ab] -store CSE17:ec[ai] - -alloc CSE18:Fca[Fiu] -load DF:Fca[Fiv] -load Ym1:aa[uv] -.CSE18:Fca[Fiu] += DF:Fca[Fiv] Ym1:aa[uv] -drop Ym1:aa[uv] -drop DF:Fca[Fiv] -store CSE18:Fca[Fiu] - alloc R2:ec[ai] -load CSE12:aa[vy] -load T2:eaac[ayvi] -.R2:ec[ai] += -1 * CSE12:aa[vy] T2:eaac[ayvi] -drop T2:eaac[ayvi] -load T2:eaca[ayix] -.R2:ec[ai] += 2 * CSE12:aa[xy] T2:eaca[ayix] -drop T2:eaca[ayix] -drop CSE12:aa[xy] -load CSE8:aa[yv] -load T2:eaca[aviy] -.R2:ec[ai] += 4 * CSE8:aa[yv] T2:eaca[aviy] -drop T2:eaca[aviy] +load CSE6:F[F] +load DF:Fec[Fai] +.R2:ec[ai] += 2 * CSE6:F[F] DF:Fec[Fai] +drop DF:Fec[Fai] +drop CSE6:F[F] +load CSE12:ec[ai] +.R2:ec[ai] += -1 * CSE12:ec[ai] +drop CSE12:ec[ai] +load CSE13:ec[ai] +.R2:ec[ai] += CSE13:ec[ai] +drop CSE13:ec[ai] +load CSE11:aa[uw] +load T2:eaca[awiu] +.R2:ec[ai] += -2 * CSE11:aa[uw] T2:eaca[awiu] +drop T2:eaca[awiu] +load T2:eaac[awui] +.R2:ec[ai] += CSE11:aa[uw] T2:eaac[awui] +drop T2:eaac[awui] +drop CSE11:aa[uw] +load CSE3:aa[uw] +load T2:eaca[auiw] +.R2:ec[ai] += 2 * CSE3:aa[uw] T2:eaca[auiw] +drop T2:eaca[auiw] +load T2:eaac[avwi] +.R2:ec[ai] += -1 * CSE3:aa[vw] T2:eaac[avwi] +drop T2:eaac[avwi] +drop CSE3:aa[vw] +load CSE2:aa[xy] load T2:eaac[axyi] -.R2:ec[ai] += -2 * CSE8:aa[yx] T2:eaac[axyi] +.R2:ec[ai] += -2 * CSE2:aa[xy] T2:eaac[axyi] drop T2:eaac[axyi] -drop CSE8:aa[yx] -load CSE11:aa[xy] -load T2:eaca[ayix] -.R2:ec[ai] += -4 * CSE11:aa[xy] T2:eaca[ayix] -drop T2:eaca[ayix] +load T2:eaca[aviy] +.R2:ec[ai] += 4 * CSE2:aa[vy] T2:eaca[aviy] +drop T2:eaca[aviy] +drop CSE2:aa[vy] +load CSE10:aa[vy] +load T2:eaca[ayiv] +.R2:ec[ai] += -4 * CSE10:aa[vy] T2:eaca[ayiv] +drop T2:eaca[ayiv] load T2:eaac[aywi] -.R2:ec[ai] += 2 * CSE11:aa[wy] T2:eaac[aywi] +.R2:ec[ai] += 2 * CSE10:aa[wy] T2:eaac[aywi] drop T2:eaac[aywi] -drop CSE11:aa[wy] -load CSE9:aa[yx] +drop CSE10:aa[wy] +load CSE9:aa[uy] +load T2:eaac[ayui] +.R2:ec[ai] += -1 * CSE9:aa[uy] T2:eaac[ayui] +drop T2:eaac[ayui] +load T2:eaca[ayiv] +.R2:ec[ai] += 2 * CSE9:aa[vy] T2:eaca[ayiv] +drop T2:eaca[ayiv] +drop CSE9:aa[vy] +load CSE1:aa[vy] +load T2:eaca[aviy] +.R2:ec[ai] += -2 * CSE1:aa[vy] T2:eaca[aviy] +drop T2:eaca[aviy] load T2:eaac[axyi] -.R2:ec[ai] += CSE9:aa[yx] T2:eaac[axyi] +.R2:ec[ai] += CSE1:aa[xy] T2:eaac[axyi] drop T2:eaac[axyi] -load T2:eaca[awiy] -.R2:ec[ai] += -2 * CSE9:aa[yw] T2:eaca[awiy] -drop T2:eaca[awiy] -drop CSE9:aa[yw] -load CSE10:aa[vw] -load T2:eaac[avwi] -.R2:ec[ai] += -1 * CSE10:aa[vw] T2:eaac[avwi] -drop T2:eaac[avwi] -load T2:eaca[auiw] -.R2:ec[ai] += 2 * CSE10:aa[uw] T2:eaca[auiw] -drop T2:eaca[auiw] -drop CSE10:aa[uw] -load CSE14:ec[ai] -.R2:ec[ai] += -1 * CSE14:ec[ai] -drop CSE14:ec[ai] +drop CSE1:aa[xy] +load DF:Fea[Fau] +load CSE14:Fca[Fiu] +.R2:ec[ai] += -1 * DF:Fea[Fau] CSE14:Fca[Fiu] +drop CSE14:Fca[Fiu] +drop DF:Fea[Fau] load g:cc[ji] load CSE15:ec[aj] .R2:ec[ai] += g:cc[ji] CSE15:ec[aj] @@ -930,27 +944,6 @@ load CSE16:ec[aj] .R2:ec[ai] += -2 * g:cc[ji] CSE16:ec[aj] drop CSE16:ec[aj] drop g:cc[ji] -load CSE13:aa[uw] -load T2:eaac[awui] -.R2:ec[ai] += CSE13:aa[uw] T2:eaac[awui] -drop T2:eaac[awui] -load T2:eaca[awiu] -.R2:ec[ai] += -2 * CSE13:aa[uw] T2:eaca[awiu] -drop T2:eaca[awiu] -drop CSE13:aa[uw] -load CSE17:ec[ai] -.R2:ec[ai] += CSE17:ec[ai] -drop CSE17:ec[ai] -load DF:Fea[Fau] -load CSE18:Fca[Fiu] -.R2:ec[ai] += -1 * DF:Fea[Fau] CSE18:Fca[Fiu] -drop CSE18:Fca[Fiu] -drop DF:Fea[Fau] -load CSE2:F[F] -load DF:Fec[Fai] -.R2:ec[ai] += 2 * CSE2:F[F] DF:Fec[Fai] -drop DF:Fec[Fai] -drop CSE2:F[F] load f:ec[ai] .R2:ec[ai] += f:ec[ai] drop f:ec[ai] @@ -962,41 +955,41 @@ load R2:ec[ai] drop R2:ec[ai] store R1:ec[ai] -alloc CSE19:aaaa[zxvu] +alloc CSE17:aaaa[wyuv] +load f:aa[wx] +load Ym2:aaaa[xyvu] +.CSE17:aaaa[wyuv] += f:aa[wx] Ym2:aaaa[xyvu] +drop Ym2:aaaa[xyvu] +drop f:aa[wx] +store CSE17:aaaa[wyuv] + +alloc CSE18:aaaa[ywvu] alloc I:aaaa[wxyz] load DF:Faa[Fwx] .I:aaaa[wxyz] += DF:Faa[Fwx] DF:Faa[Fyz] drop DF:Faa[Fwx] -load Ym2:aaaa[wyuv] -.CSE19:aaaa[zxvu] += I:aaaa[wxyz] Ym2:aaaa[wyuv] -drop Ym2:aaaa[wyuv] +load Ym2:aaaa[xzuv] +.CSE18:aaaa[ywvu] += I:aaaa[wxyz] Ym2:aaaa[xzuv] +drop Ym2:aaaa[xzuv] drop I:aaaa[wxyz] -store CSE19:aaaa[zxvu] - -alloc CSE20:aaaa[yxuv] -load f:aa[wx] -load Ym2:aaaa[wyvu] -.CSE20:aaaa[yxuv] += f:aa[wx] Ym2:aaaa[wyvu] -drop Ym2:aaaa[wyvu] -drop f:aa[wx] -store CSE20:aaaa[yxuv] +store CSE18:aaaa[ywvu] alloc R2u:eeaa[abuv] alloc I:eeaa[abwx] -load g:ee[ca] +load g:ee[ac] load T2:eeaa[bcxw] -.I:eeaa[abwx] += g:ee[ca] T2:eeaa[bcxw] +.I:eeaa[abwx] += g:ee[ac] T2:eeaa[bcxw] drop T2:eeaa[bcxw] -drop g:ee[ca] +drop g:ee[ac] load Ym2:aaaa[wxuv] .R2u:eeaa[abuv] += 2 * I:eeaa[abwx] Ym2:aaaa[wxuv] drop Ym2:aaaa[wxuv] drop I:eeaa[abwx] -load CSE19:aaaa[zxvu] -load T2:eeaa[abxz] -.R2u:eeaa[abuv] += -1 * CSE19:aaaa[zxvu] T2:eeaa[abxz] -drop T2:eeaa[abxz] -drop CSE19:aaaa[zxvu] +load CSE17:aaaa[wyuv] +load T2:eeaa[abyw] +.R2u:eeaa[abuv] += -2 * CSE17:aaaa[wyuv] T2:eeaa[abyw] +drop T2:eeaa[abyw] +drop CSE17:aaaa[wyuv] alloc I:eeaa[abwx] load DF:Fea[Faw] .I:eeaa[abwx] += DF:Fea[Faw] DF:Fea[Fbx] @@ -1005,11 +998,11 @@ load Ym2:aaaa[wxuv] .R2u:eeaa[abuv] += I:eeaa[abwx] Ym2:aaaa[wxuv] drop Ym2:aaaa[wxuv] drop I:eeaa[abwx] -load CSE20:aaaa[yxuv] -load T2:eeaa[abyx] -.R2u:eeaa[abuv] += -2 * CSE20:aaaa[yxuv] T2:eeaa[abyx] -drop T2:eeaa[abyx] -drop CSE20:aaaa[yxuv] +load CSE18:aaaa[ywvu] +load T2:eeaa[abwy] +.R2u:eeaa[abuv] += -1 * CSE18:aaaa[ywvu] T2:eeaa[abwy] +drop T2:eeaa[abwy] +drop CSE18:aaaa[ywvu] store R2u:eeaa[abuv] alloc R2:eeaa[abuv] @@ -1020,6 +1013,11 @@ drop R2u:eeaa[bavu] store R2:eeaa[abuv] alloc R2u:eecc[abij] +load g:ee[ac] +load T2:eecc[bcji] +.R2u:eecc[abij] += 2 * g:ee[ac] T2:eecc[bcji] +drop T2:eecc[bcji] +drop g:ee[ac] load g:cc[kj] load T2:eecc[abik] .R2u:eecc[abij] += -2 * g:cc[kj] T2:eecc[abik] @@ -1028,11 +1026,6 @@ drop g:cc[kj] load DF:Fec[Fai] .R2u:eecc[abij] += DF:Fec[Fai] DF:Fec[Fbj] drop DF:Fec[Fai] -load g:ee[ca] -load T2:eecc[bcji] -.R2u:eecc[abij] += 2 * g:ee[ca] T2:eecc[bcji] -drop T2:eecc[bcji] -drop g:ee[ca] store R2u:eecc[abij] alloc R2:eecc[abij] @@ -1042,199 +1035,221 @@ load R2u:eecc[abij] drop R2u:eecc[baji] store R2:eecc[abij] -alloc CSE21:ccaa[ijuv] -load f:aa[vw] -load T2:aacc[uwij] -.CSE21:ccaa[ijuv] += f:aa[vw] T2:aacc[uwij] -drop T2:aacc[uwij] -drop f:aa[vw] -store CSE21:ccaa[ijuv] +alloc CSE19:aaaa[uwyx] +load DF:Faa[Fwu] +.CSE19:aaaa[uwyx] += DF:Faa[Fwu] DF:Faa[Fxy] +drop DF:Faa[Fwu] +store CSE19:aaaa[uwyx] -alloc CSE22:ccaa[jiwu] -load DF:Fca[Fiu] -.CSE22:ccaa[jiwu] += DF:Fca[Fiu] DF:Fca[Fjw] -drop DF:Fca[Fiu] -store CSE22:ccaa[jiwu] +alloc CSE20:aaaa[wvuz] +alloc I:aaaa[vwxy] +load DF:Faa[Fvy] +.I:aaaa[vwxy] += DF:Faa[Fvy] DF:Faa[Fwx] +drop DF:Faa[Fvy] +load Ym2:aaaa[uyxz] +.CSE20:aaaa[wvuz] += I:aaaa[vwxy] Ym2:aaaa[uyxz] +drop Ym2:aaaa[uyxz] +drop I:aaaa[vwxy] +store CSE20:aaaa[wvuz] -alloc CSE23:ccaa[jiwx] -load g:cc[kj] -load T2:aacc[wxik] -.CSE23:ccaa[jiwx] += g:cc[kj] T2:aacc[wxik] -drop T2:aacc[wxik] -drop g:cc[kj] -store CSE23:ccaa[jiwx] - -alloc CSE24:aaaa[vwuz] -alloc I:aaaa[uwxy] -load DF:Faa[Fuw] -.I:aaaa[uwxy] += DF:Faa[Fuw] DF:Faa[Fxy] -drop DF:Faa[Fuw] -load Ym2:aaaa[vxzy] -.CSE24:aaaa[vwuz] += I:aaaa[uwxy] Ym2:aaaa[vxzy] -drop Ym2:aaaa[vxzy] -drop I:aaaa[uwxy] -store CSE24:aaaa[vwuz] - -alloc CSE25:aaaa[xwzy] -load DF:Faa[Fwx] -.CSE25:aaaa[xwzy] += DF:Faa[Fwx] DF:Faa[Fyz] -drop DF:Faa[Fwx] -store CSE25:aaaa[xwzy] +alloc CSE21:aaaa[wvuz] +alloc I:aaaa[vwxy] +load DF:Faa[Fvy] +.I:aaaa[vwxy] += DF:Faa[Fvy] DF:Faa[Fwx] +drop DF:Faa[Fvy] +load Ym2:aaaa[uyzx] +.CSE21:aaaa[wvuz] += I:aaaa[vwxy] Ym2:aaaa[uyzx] +drop Ym2:aaaa[uyzx] +drop I:aaaa[vwxy] +store CSE21:aaaa[wvuz] -alloc CSE26:aaaa[uxvz] +alloc CSE22:aaaa[vwuz] alloc I:aaaa[vwxy] load DF:Faa[Fwv] .I:aaaa[vwxy] += DF:Faa[Fwv] DF:Faa[Fxy] drop DF:Faa[Fwv] -load Ym2:aaaa[uwzy] -.CSE26:aaaa[uxvz] += I:aaaa[vwxy] Ym2:aaaa[uwzy] -drop Ym2:aaaa[uwzy] +load Ym2:aaaa[uyxz] +.CSE22:aaaa[vwuz] += I:aaaa[vwxy] Ym2:aaaa[uyxz] +drop Ym2:aaaa[uyxz] drop I:aaaa[vwxy] -store CSE26:aaaa[uxvz] +store CSE22:aaaa[vwuz] -alloc CSE27:aaaa[uxvz] +alloc CSE23:aaaa[vwuz] alloc I:aaaa[vwxy] -load DF:Faa[Fvy] -.I:aaaa[vwxy] += DF:Faa[Fvy] DF:Faa[Fwx] -drop DF:Faa[Fvy] -load Ym2:aaaa[uywz] -.CSE27:aaaa[uxvz] += I:aaaa[vwxy] Ym2:aaaa[uywz] -drop Ym2:aaaa[uywz] +load DF:Faa[Fwv] +.I:aaaa[vwxy] += DF:Faa[Fwv] DF:Faa[Fxy] +drop DF:Faa[Fwv] +load Ym2:aaaa[uyzx] +.CSE23:aaaa[vwuz] += I:aaaa[vwxy] Ym2:aaaa[uyzx] +drop Ym2:aaaa[uyzx] drop I:aaaa[vwxy] -store CSE27:aaaa[uxvz] - -alloc CSE28:aaaa[vwuz] -alloc I:aaaa[uwxy] -load DF:Faa[Fuw] -.I:aaaa[uwxy] += DF:Faa[Fuw] DF:Faa[Fxy] -drop DF:Faa[Fuw] -load Ym2:aaaa[vyxz] -.CSE28:aaaa[vwuz] += I:aaaa[uwxy] Ym2:aaaa[vyxz] -drop Ym2:aaaa[vyxz] -drop I:aaaa[uwxy] -store CSE28:aaaa[vwuz] - -alloc CSE29:aaaa[uxvy] +store CSE23:aaaa[vwuz] + +alloc CSE24:ccaa[jivu] +load DF:Fca[Fiu] +.CSE24:ccaa[jivu] += DF:Fca[Fiu] DF:Fca[Fjv] +drop DF:Fca[Fiu] +store CSE24:ccaa[jivu] + +alloc CSE25:aa[vw] +alloc I:F[F] +load DF:Faa[Fxy] +load Ym1:aa[xy] +.I:F[F] += DF:Faa[Fxy] Ym1:aa[xy] +drop Ym1:aa[xy] +.CSE25:aa[vw] += I:F[F] DF:Faa[Fwv] +drop DF:Faa[Fwv] +drop I:F[F] +store CSE25:aa[vw] + +alloc CSE26:aa[vx] +alloc I:Faa[Fvy] +load DF:Faa[Fwv] +load Ym1:aa[wy] +.I:Faa[Fvy] += DF:Faa[Fwv] Ym1:aa[wy] +drop Ym1:aa[wy] +.CSE26:aa[vx] += I:Faa[Fvy] DF:Faa[Fxy] +drop DF:Faa[Fxy] +drop I:Faa[Fvy] +store CSE26:aa[vx] + +alloc CSE27:ccaa[jiuv] +load f:aa[uw] +load T2:aacc[vwji] +.CSE27:ccaa[jiuv] += f:aa[uw] T2:aacc[vwji] +drop T2:aacc[vwji] +drop f:aa[uw] +store CSE27:ccaa[jiuv] + +alloc CSE28:aaaa[xvuy] load f:aa[wx] -load Ym2:aaaa[uvyw] -.CSE29:aaaa[uxvy] += f:aa[wx] Ym2:aaaa[uvyw] -drop Ym2:aaaa[uvyw] +load Ym2:aaaa[uvwy] +.CSE28:aaaa[xvuy] += f:aa[wx] Ym2:aaaa[uvwy] +drop Ym2:aaaa[uvwy] drop f:aa[wx] -store CSE29:aaaa[uxvy] +store CSE28:aaaa[xvuy] + +alloc CSE29:ccaa[jiuw] +load g:cc[jk] +load T2:aacc[uwik] +.CSE29:ccaa[jiuw] += g:cc[jk] T2:aacc[uwik] +drop T2:aacc[uwik] +drop g:cc[jk] +store CSE29:ccaa[jiuw] alloc R2u:aacc[uvij] -load CSE21:ccaa[jixu] -load Ym1:aa[vx] -.R2u:aacc[uvij] += -2 * CSE21:ccaa[jixu] Ym1:aa[vx] -drop Ym1:aa[vx] -.R2u:aacc[uvij] += 2 * CSE21:ccaa[ijuv] -drop CSE21:ccaa[ijuv] -load CSE22:ccaa[jiwu] -load Ym1:aa[vw] -.R2u:aacc[uvij] += -2 * CSE22:ccaa[jiwu] Ym1:aa[vw] -drop Ym1:aa[vw] -.R2u:aacc[uvij] += CSE22:ccaa[jivu] -load Ym2:aaaa[uvwx] -.R2u:aacc[uvij] += CSE22:ccaa[jixw] Ym2:aaaa[uvwx] -drop Ym2:aaaa[uvwx] -drop CSE22:ccaa[jixw] -load CSE23:ccaa[jiwx] -load Ym2:aaaa[uvwx] -.R2u:aacc[uvij] += -2 * CSE23:ccaa[jiwx] Ym2:aaaa[uvwx] -drop Ym2:aaaa[uvwx] -load Ym1:aa[vw] -.R2u:aacc[uvij] += 2 * CSE23:ccaa[jiuw] Ym1:aa[vw] -drop Ym1:aa[vw] -.R2u:aacc[uvij] += -2 * CSE23:ccaa[jiuv] -load Ym1:aa[vw] -.R2u:aacc[uvij] += 2 * CSE23:ccaa[ijwu] Ym1:aa[vw] -drop Ym1:aa[vw] -drop CSE23:ccaa[ijwu] -load CSE24:aaaa[vwuz] -load T2:aacc[wzij] -.R2u:aacc[uvij] += -4 * CSE24:aaaa[vwuz] T2:aacc[wzij] -drop T2:aacc[wzij] -drop CSE24:aaaa[vwuz] -load CSE9:aa[vy] -load T2:aacc[uyij] -.R2u:aacc[uvij] += 2 * CSE9:aa[vy] T2:aacc[uyij] -drop T2:aacc[uyij] -drop CSE9:aa[vy] -load CSE8:aa[vx] -load T2:aacc[uxij] -.R2u:aacc[uvij] += -4 * CSE8:aa[vx] T2:aacc[uxij] -drop T2:aacc[uxij] -drop CSE8:aa[vx] -alloc I:aaaa[uvxz] -load CSE25:aaaa[xwzy] -load Ym2:aaaa[uvwy] -.I:aaaa[uvxz] += CSE25:aaaa[xwzy] Ym2:aaaa[uvwy] -drop Ym2:aaaa[uvwy] -drop CSE25:aaaa[xwzy] -load T2:aacc[xzij] -.R2u:aacc[uvij] += I:aaaa[uvxz] T2:aacc[xzij] -drop T2:aacc[xzij] -drop I:aaaa[uvxz] -load CSE26:aaaa[uxvz] -load T2:aacc[xzji] -.R2u:aacc[uvij] += 2 * CSE26:aaaa[uxvz] T2:aacc[xzji] -drop T2:aacc[xzji] -drop CSE26:aaaa[uxvz] -load CSE27:aaaa[uxvz] -load T2:aacc[xzij] -.R2u:aacc[uvij] += 2 * CSE27:aaaa[uxvz] T2:aacc[xzij] -drop T2:aacc[xzij] -drop CSE27:aaaa[uxvz] -load CSE25:aaaa[wuxv] +alloc I:aaaa[uvwx] +load CSE19:aaaa[uwyx] +load Ym1:aa[vy] +.I:aaaa[uvwx] += CSE19:aaaa[uwyx] Ym1:aa[vy] +drop Ym1:aa[vy] +drop CSE19:aaaa[uwyx] load T2:aacc[wxij] -.R2u:aacc[uvij] += CSE25:aaaa[wuxv] T2:aacc[wxij] +.R2u:aacc[uvij] += -2 * I:aaaa[uvwx] T2:aacc[wxij] drop T2:aacc[wxij] -drop CSE25:aaaa[wuxv] -load CSE28:aaaa[vwuz] +drop I:aaaa[uvwx] +load CSE20:aaaa[wvuz] load T2:aacc[wzij] -.R2u:aacc[uvij] += 2 * CSE28:aaaa[vwuz] T2:aacc[wzij] +.R2u:aacc[uvij] += 2 * CSE20:aaaa[wvuz] T2:aacc[wzij] drop T2:aacc[wzij] -drop CSE28:aaaa[vwuz] +drop CSE20:aaaa[wvuz] +load CSE21:aaaa[wvuz] +load T2:aacc[wzji] +.R2u:aacc[uvij] += 2 * CSE21:aaaa[wvuz] T2:aacc[wzji] +drop T2:aacc[wzji] +drop CSE21:aaaa[wvuz] alloc I:aaaa[uvwy] -load CSE25:aaaa[wuxv] -load Ym1:aa[xy] -.I:aaaa[uvwy] += CSE25:aaaa[wuxv] Ym1:aa[xy] -drop Ym1:aa[xy] -drop CSE25:aaaa[wuxv] +load CSE19:aaaa[xwzy] +load Ym2:aaaa[uvxz] +.I:aaaa[uvwy] += CSE19:aaaa[xwzy] Ym2:aaaa[uvxz] +drop Ym2:aaaa[uvxz] +drop CSE19:aaaa[xwzy] load T2:aacc[wyij] -.R2u:aacc[uvij] += -2 * I:aaaa[uvwy] T2:aacc[wyij] +.R2u:aacc[uvij] += I:aaaa[uvwy] T2:aacc[wyij] drop T2:aacc[wyij] drop I:aaaa[uvwy] +load CSE2:aa[xv] +load T2:aacc[uxij] +.R2u:aacc[uvij] += -4 * CSE2:aa[xv] T2:aacc[uxij] +drop T2:aacc[uxij] +drop CSE2:aa[xv] +load CSE22:aaaa[vwuz] +load T2:aacc[wzji] +.R2u:aacc[uvij] += 2 * CSE22:aaaa[vwuz] T2:aacc[wzji] +drop T2:aacc[wzji] +drop CSE22:aaaa[vwuz] alloc I:aaaa[uvwy] -load CSE25:aaaa[wuyx] -load Ym1:aa[vx] -.I:aaaa[uvwy] += CSE25:aaaa[wuyx] Ym1:aa[vx] -drop Ym1:aa[vx] -drop CSE25:aaaa[wuyx] +load CSE19:aaaa[uwvx] +load Ym1:aa[xy] +.I:aaaa[uvwy] += CSE19:aaaa[uwvx] Ym1:aa[xy] +drop Ym1:aa[xy] +drop CSE19:aaaa[uwvx] load T2:aacc[wyij] .R2u:aacc[uvij] += -2 * I:aaaa[uvwy] T2:aacc[wyij] drop T2:aacc[wyij] drop I:aaaa[uvwy] -load CSE1:aa[wv] -load T2:aacc[uwij] -.R2u:aacc[uvij] += 4 * CSE1:aa[wv] T2:aacc[uwij] -drop T2:aacc[uwij] -drop CSE1:aa[wv] -load CSE29:aaaa[uxvy] -load T2:aacc[xyji] -.R2u:aacc[uvij] += 2 * CSE29:aaaa[uxvy] T2:aacc[xyji] -drop T2:aacc[xyji] -drop CSE29:aaaa[uxvy] -load CSE5:aa[xv] +load CSE1:aa[xv] load T2:aacc[uxij] -.R2u:aacc[uvij] += -2 * CSE5:aa[xv] T2:aacc[uxij] +.R2u:aacc[uvij] += 2 * CSE1:aa[xv] T2:aacc[uxij] drop T2:aacc[uxij] -drop CSE5:aa[xv] -load CSE10:aa[xv] +drop CSE1:aa[xv] +load CSE23:aaaa[vwuz] +load T2:aacc[wzji] +.R2u:aacc[uvij] += -4 * CSE23:aaaa[vwuz] T2:aacc[wzji] +drop T2:aacc[wzji] +drop CSE23:aaaa[vwuz] +load CSE19:aaaa[uwvx] +load T2:aacc[wxij] +.R2u:aacc[uvij] += CSE19:aaaa[uwvx] T2:aacc[wxij] +drop T2:aacc[wxij] +drop CSE19:aaaa[uwvx] +load CSE24:ccaa[jivu] +.R2u:aacc[uvij] += CSE24:ccaa[jivu] +load Ym2:aaaa[uvwx] +.R2u:aacc[uvij] += CSE24:ccaa[jixw] Ym2:aaaa[uvwx] +drop Ym2:aaaa[uvwx] +load Ym1:aa[vw] +.R2u:aacc[uvij] += -2 * CSE24:ccaa[jiwu] Ym1:aa[vw] +drop Ym1:aa[vw] +drop CSE24:ccaa[jiwu] +load CSE25:aa[vw] +load T2:aacc[uwij] +.R2u:aacc[uvij] += 4 * CSE25:aa[vw] T2:aacc[uwij] +drop T2:aacc[uwij] +drop CSE25:aa[vw] +load CSE26:aa[vx] load T2:aacc[uxij] -.R2u:aacc[uvij] += -2 * CSE10:aa[xv] T2:aacc[uxij] +.R2u:aacc[uvij] += -2 * CSE26:aa[vx] T2:aacc[uxij] drop T2:aacc[uxij] -drop CSE10:aa[xv] +drop CSE26:aa[vx] +load CSE27:ccaa[jiuv] +.R2u:aacc[uvij] += 2 * CSE27:ccaa[jiuv] +load Ym1:aa[vx] +.R2u:aacc[uvij] += -2 * CSE27:ccaa[jiux] Ym1:aa[vx] +drop Ym1:aa[vx] +drop CSE27:ccaa[jiux] +load CSE28:aaaa[xvuy] +load T2:aacc[xyij] +.R2u:aacc[uvij] += 2 * CSE28:aaaa[xvuy] T2:aacc[xyij] +drop T2:aacc[xyij] +drop CSE28:aaaa[xvuy] +load CSE29:ccaa[jiuw] +load Ym1:aa[vw] +.R2u:aacc[uvij] += 2 * CSE29:ccaa[jiuw] Ym1:aa[vw] +drop Ym1:aa[vw] +.R2u:aacc[uvij] += -2 * CSE29:ccaa[jiuv] +load Ym1:aa[vw] +.R2u:aacc[uvij] += 2 * CSE29:ccaa[ijwu] Ym1:aa[vw] +drop Ym1:aa[vw] +load Ym2:aaaa[uvwx] +.R2u:aacc[uvij] += -2 * CSE29:ccaa[jiwx] Ym2:aaaa[uvwx] +drop Ym2:aaaa[uvwx] +drop CSE29:ccaa[jiwx] +load CSE3:aa[wv] +load T2:aacc[uwij] +.R2u:aacc[uvij] += -2 * CSE3:aa[wv] T2:aacc[uwij] +drop T2:aacc[uwij] +drop CSE3:aa[wv] store R2u:aacc[uvij] alloc R2:aacc[uvij] @@ -1246,16 +1261,6 @@ store R2:aacc[uvij] alloc R2:eeca[baiu] alloc I:eeca[abiv] -load g:ee[cb] -load T2:eeca[caiv] -.I:eeca[abiv] += g:ee[cb] T2:eeca[caiv] -drop T2:eeca[caiv] -drop g:ee[cb] -load Ym1:aa[vu] -.R2:eeca[baiu] += I:eeca[abiv] Ym1:aa[vu] -drop Ym1:aa[vu] -drop I:eeca[abiv] -alloc I:eeca[abiv] load g:ee[ac] load T2:eeca[bciv] .I:eeca[abiv] += g:ee[ac] T2:eeca[bciv] @@ -1265,6 +1270,21 @@ load Ym1:aa[vu] .R2:eeca[baiu] += I:eeca[abiv] Ym1:aa[vu] drop Ym1:aa[vu] drop I:eeca[abiv] +load CSE10:aa[xu] +load T2:eeca[baix] +.R2:eeca[baiu] += -2 * CSE10:aa[xu] T2:eeca[baix] +drop T2:eeca[baix] +drop CSE10:aa[xu] +load CSE9:aa[vu] +load T2:eeca[baiv] +.R2:eeca[baiu] += CSE9:aa[vu] T2:eeca[baiv] +drop T2:eeca[baiv] +drop CSE9:aa[vu] +load CSE11:aa[vu] +load T2:eeca[baiv] +.R2:eeca[baiu] += -1 * CSE11:aa[vu] T2:eeca[baiv] +drop T2:eeca[baiv] +drop CSE11:aa[vu] alloc I:eeca[abiv] load g:cc[ji] load T2:eeca[bajv] @@ -1275,21 +1295,16 @@ load Ym1:aa[vu] .R2:eeca[baiu] += -1 * I:eeca[abiv] Ym1:aa[vu] drop Ym1:aa[vu] drop I:eeca[abiv] -load CSE13:aa[vu] -load T2:eeca[baiv] -.R2:eeca[baiu] += -1 * CSE13:aa[vu] T2:eeca[baiv] -drop T2:eeca[baiv] -drop CSE13:aa[vu] -load CSE12:aa[wu] -load T2:eeca[baiw] -.R2:eeca[baiu] += CSE12:aa[wu] T2:eeca[baiw] -drop T2:eeca[baiw] -drop CSE12:aa[wu] -load CSE11:aa[xu] -load T2:eeca[baix] -.R2:eeca[baiu] += -2 * CSE11:aa[xu] T2:eeca[baix] -drop T2:eeca[baix] -drop CSE11:aa[xu] +alloc I:eeca[abiv] +load g:ee[cb] +load T2:eeca[caiv] +.I:eeca[abiv] += g:ee[cb] T2:eeca[caiv] +drop T2:eeca[caiv] +drop g:ee[cb] +load Ym1:aa[vu] +.R2:eeca[baiu] += I:eeca[abiv] Ym1:aa[vu] +drop Ym1:aa[vu] +drop I:eeca[abiv] alloc I:Fea[Fau] load DF:Fea[Fav] load Ym1:aa[vu] @@ -1302,249 +1317,229 @@ drop DF:Fec[Fbi] drop I:Fea[Fau] store R2:eeca[baiu] -alloc CSE30:ecaa[aiwx] +alloc CSE30:ecaa[aivw] load g:cc[ji] -load T2:eaac[awxj] -.CSE30:ecaa[aiwx] += g:cc[ji] T2:eaac[awxj] -drop T2:eaac[awxj] +load T2:eaac[avwj] +.CSE30:ecaa[aivw] += g:cc[ji] T2:eaac[avwj] +drop T2:eaac[avwj] drop g:cc[ji] -store CSE30:ecaa[aiwx] - -alloc CSE31:ecaa[aixw] -load DF:Fea[Faw] -load DF:Fca[Fix] -.CSE31:ecaa[aixw] += DF:Fea[Faw] DF:Fca[Fix] -drop DF:Fca[Fix] -drop DF:Fea[Faw] -store CSE31:ecaa[aixw] +store CSE30:ecaa[aivw] -alloc CSE32:ecaa[aiwx] +alloc CSE31:ecaa[aivw] load g:ee[ab] -load T2:eaac[bwxi] -.CSE32:ecaa[aiwx] += g:ee[ab] T2:eaac[bwxi] -drop T2:eaac[bwxi] +load T2:eaac[bvwi] +.CSE31:ecaa[aivw] += g:ee[ab] T2:eaac[bvwi] +drop T2:eaac[bvwi] drop g:ee[ab] -store CSE32:ecaa[aiwx] +store CSE31:ecaa[aivw] + +alloc CSE32:ecaa[aivw] +load DF:Fea[Faw] +load DF:Fca[Fiv] +.CSE32:ecaa[aivw] += DF:Fea[Faw] DF:Fca[Fiv] +drop DF:Fca[Fiv] +drop DF:Fea[Faw] +store CSE32:ecaa[aivw] alloc R2:eaac[avui] -alloc I:ecaa[aivx] -load f:aa[vw] -load T2:eaac[awxi] -.I:ecaa[aivx] += f:aa[vw] T2:eaac[awxi] -drop T2:eaac[awxi] -drop f:aa[vw] -load Ym1:aa[xu] -.R2:eaac[avui] += I:ecaa[aivx] Ym1:aa[xu] -drop Ym1:aa[xu] -drop I:ecaa[aivx] -load CSE30:ecaa[aiwx] -load Ym2:aaaa[vxwu] -.R2:eaac[avui] += CSE30:ecaa[aiwx] Ym2:aaaa[vxwu] -drop Ym2:aaaa[vxwu] -load Ym1:aa[wu] -.R2:eaac[avui] += -1 * CSE30:ecaa[aivw] Ym1:aa[wu] -drop Ym1:aa[wu] -drop CSE30:ecaa[aivw] -load CSE19:aaaa[xvuz] -load T2:eaac[azxi] -.R2:eaac[avui] += CSE19:aaaa[xvuz] T2:eaac[azxi] -drop T2:eaac[azxi] -drop CSE19:aaaa[xvuz] -load CSE28:aaaa[zyvu] +load CSE21:aaaa[yvzu] load T2:eaac[ayzi] -.R2:eaac[avui] += -1 * CSE28:aaaa[zyvu] T2:eaac[ayzi] +.R2:eaac[avui] += -1 * CSE21:aaaa[yvzu] T2:eaac[ayzi] drop T2:eaac[ayzi] -drop CSE28:aaaa[zyvu] -load CSE26:aaaa[zyvu] +drop CSE21:aaaa[yvzu] +load CSE17:aaaa[wvyu] +load T2:eaac[aywi] +.R2:eaac[avui] += CSE17:aaaa[wvyu] T2:eaac[aywi] +drop T2:eaac[aywi] +drop CSE17:aaaa[wvyu] +load CSE9:aa[wu] +load T2:eaac[avwi] +.R2:eaac[avui] += CSE9:aa[wu] T2:eaac[avwi] +drop T2:eaac[avwi] +drop CSE9:aa[wu] +load CSE23:aaaa[yvzu] load T2:eaac[ayzi] -.R2:eaac[avui] += -1 * CSE26:aaaa[zyvu] T2:eaac[ayzi] +.R2:eaac[avui] += 2 * CSE23:aaaa[yvzu] T2:eaac[ayzi] drop T2:eaac[ayzi] -drop CSE26:aaaa[zyvu] -load CSE24:aaaa[zyvu] +drop CSE23:aaaa[yvzu] +load CSE22:aaaa[yvzu] load T2:eaac[ayzi] -.R2:eaac[avui] += 2 * CSE24:aaaa[zyvu] T2:eaac[ayzi] +.R2:eaac[avui] += -1 * CSE22:aaaa[yvzu] T2:eaac[ayzi] drop T2:eaac[ayzi] -drop CSE24:aaaa[zyvu] -load CSE12:aa[xu] -load T2:eaac[avxi] -.R2:eaac[avui] += CSE12:aa[xu] T2:eaac[avxi] -drop T2:eaac[avxi] -drop CSE12:aa[xu] -load CSE11:aa[yu] +drop CSE22:aaaa[yvzu] +load CSE10:aa[yu] load T2:eaac[avyi] -.R2:eaac[avui] += -2 * CSE11:aa[yu] T2:eaac[avyi] +.R2:eaac[avui] += -2 * CSE10:aa[yu] T2:eaac[avyi] drop T2:eaac[avyi] -drop CSE11:aa[yu] -load CSE31:ecaa[aixw] -load Ym2:aaaa[vwxu] -.R2:eaac[avui] += -1 * CSE31:ecaa[aixw] Ym2:aaaa[vwxu] -drop Ym2:aaaa[vwxu] +drop CSE10:aa[yu] +load CSE18:aaaa[wvuz] +load T2:eaac[azwi] +.R2:eaac[avui] += CSE18:aaaa[wvuz] T2:eaac[azwi] +drop T2:eaac[azwi] +drop CSE18:aaaa[wvuz] +load CSE30:ecaa[aivw] +load Ym1:aa[wu] +.R2:eaac[avui] += -1 * CSE30:ecaa[aivw] Ym1:aa[wu] +drop Ym1:aa[wu] +load Ym2:aaaa[vxwu] +.R2:eaac[avui] += CSE30:ecaa[aiwx] Ym2:aaaa[vxwu] +drop Ym2:aaaa[vxwu] +drop CSE30:ecaa[aiwx] +load CSE31:ecaa[aivw] load Ym1:aa[wu] .R2:eaac[avui] += CSE31:ecaa[aivw] Ym1:aa[wu] drop Ym1:aa[wu] -drop CSE31:ecaa[aivw] -load CSE29:aaaa[yxvu] -load T2:eaac[axyi] -.R2:eaac[avui] += -1 * CSE29:aaaa[yxvu] T2:eaac[axyi] -drop T2:eaac[axyi] -drop CSE29:aaaa[yxvu] -load CSE32:ecaa[aiwx] load Ym2:aaaa[vxwu] -.R2:eaac[avui] += -1 * CSE32:ecaa[aiwx] Ym2:aaaa[vxwu] +.R2:eaac[avui] += -1 * CSE31:ecaa[aiwx] Ym2:aaaa[vxwu] drop Ym2:aaaa[vxwu] +drop CSE31:ecaa[aiwx] +load CSE32:ecaa[aivw] load Ym1:aa[wu] .R2:eaac[avui] += CSE32:ecaa[aivw] Ym1:aa[wu] drop Ym1:aa[wu] -drop CSE32:ecaa[aivw] -load CSE13:aa[wu] +load Ym2:aaaa[vwxu] +.R2:eaac[avui] += -1 * CSE32:ecaa[aixw] Ym2:aaaa[vwxu] +drop Ym2:aaaa[vwxu] +drop CSE32:ecaa[aixw] +load CSE11:aa[wu] load T2:eaac[avwi] -.R2:eaac[avui] += -1 * CSE13:aa[wu] T2:eaac[avwi] +.R2:eaac[avui] += -1 * CSE11:aa[wu] T2:eaac[avwi] drop T2:eaac[avwi] -drop CSE13:aa[wu] -load CSE20:aaaa[vxyu] -load T2:eaac[ayxi] -.R2:eaac[avui] += CSE20:aaaa[vxyu] T2:eaac[ayxi] -drop T2:eaac[ayxi] -drop CSE20:aaaa[vxyu] +drop CSE11:aa[wu] +load CSE28:aaaa[xyvu] +load T2:eaac[axyi] +.R2:eaac[avui] += -1 * CSE28:aaaa[xyvu] T2:eaac[axyi] +drop T2:eaac[axyi] +drop CSE28:aaaa[xyvu] +alloc I:ecaa[aivx] +load f:aa[vw] +load T2:eaac[awxi] +.I:ecaa[aivx] += f:aa[vw] T2:eaac[awxi] +drop T2:eaac[awxi] +drop f:aa[vw] +load Ym1:aa[xu] +.R2:eaac[avui] += I:ecaa[aivx] Ym1:aa[xu] +drop Ym1:aa[xu] +drop I:ecaa[aivx] store R2:eaac[avui] -alloc CSE33:ecaa[aiwx] -load g:ee[ab] -load T2:eaca[bwix] -.CSE33:ecaa[aiwx] += g:ee[ab] T2:eaca[bwix] -drop T2:eaca[bwix] -drop g:ee[ab] -store CSE33:ecaa[aiwx] - -alloc CSE34:ecaa[aivw] +alloc CSE33:ecaa[aivw] load g:cc[ji] load T2:eaca[avjw] -.CSE34:ecaa[aivw] += g:cc[ji] T2:eaca[avjw] +.CSE33:ecaa[aivw] += g:cc[ji] T2:eaca[avjw] drop T2:eaca[avjw] drop g:cc[ji] +store CSE33:ecaa[aivw] + +alloc CSE34:ecaa[aivw] +load g:ee[ab] +load T2:eaca[bviw] +.CSE34:ecaa[aivw] += g:ee[ab] T2:eaca[bviw] +drop T2:eaca[bviw] +drop g:ee[ab] store CSE34:ecaa[aivw] alloc R2:eaca[aviu] -load CSE19:aaaa[xvuz] -load T2:eaca[azix] -.R2:eaca[aviu] += CSE19:aaaa[xvuz] T2:eaca[azix] -drop T2:eaca[azix] -load T2:eaac[azxi] -.R2:eaca[aviu] += CSE19:aaaa[xvzu] T2:eaac[azxi] -drop T2:eaac[azxi] -load T2:eaca[azix] -.R2:eaca[aviu] += -2 * CSE19:aaaa[xvzu] T2:eaca[azix] -drop T2:eaca[azix] -drop CSE19:aaaa[xvzu] -load CSE28:aaaa[zvwu] +load CSE21:aaaa[wvzu] +load T2:eaca[awiz] +.R2:eaca[aviu] += -1 * CSE21:aaaa[wvzu] T2:eaca[awiz] +drop T2:eaca[awiz] +drop CSE21:aaaa[wvzu] +load CSE9:aa[wu] +load T2:eaca[aviw] +.R2:eaca[aviu] += CSE9:aa[wu] T2:eaca[aviw] +drop T2:eaca[aviw] +drop CSE9:aa[wu] +load CSE20:aaaa[wvzu] load T2:eaca[awiz] -.R2:eaca[aviu] += -1 * CSE28:aaaa[zvwu] T2:eaca[awiz] +.R2:eaca[aviu] += 2 * CSE20:aaaa[wvzu] T2:eaca[awiz] drop T2:eaca[awiz] -drop CSE28:aaaa[zvwu] -load CSE24:aaaa[zvyu] +load T2:eaac[ayzi] +.R2:eaca[aviu] += -1 * CSE20:aaaa[yvzu] T2:eaac[ayzi] +drop T2:eaac[ayzi] +drop CSE20:aaaa[yvzu] +load CSE23:aaaa[vyzu] load T2:eaca[ayiz] -.R2:eaca[aviu] += 2 * CSE24:aaaa[zvyu] T2:eaca[ayiz] +.R2:eaca[aviu] += 2 * CSE23:aaaa[vyzu] T2:eaca[ayiz] drop T2:eaca[ayiz] -drop CSE24:aaaa[zvyu] -load CSE11:aa[yu] +drop CSE23:aaaa[vyzu] +load CSE10:aa[yu] load T2:eaca[aviy] -.R2:eaca[aviu] += -2 * CSE11:aa[yu] T2:eaca[aviy] +.R2:eaca[aviu] += -2 * CSE10:aa[yu] T2:eaca[aviy] drop T2:eaca[aviy] -drop CSE11:aa[yu] -load CSE27:aaaa[zyvu] -load T2:eaac[ayzi] -.R2:eaca[aviu] += -1 * CSE27:aaaa[zyvu] T2:eaac[ayzi] -drop T2:eaac[ayzi] -load T2:eaca[axiz] -.R2:eaca[aviu] += 2 * CSE27:aaaa[zxvu] T2:eaca[axiz] -drop T2:eaca[axiz] -drop CSE27:aaaa[zxvu] -load CSE26:aaaa[zxvu] -load T2:eaca[axiz] -.R2:eaca[aviu] += -1 * CSE26:aaaa[zxvu] T2:eaca[axiz] -drop T2:eaca[axiz] -drop CSE26:aaaa[zxvu] -load CSE12:aa[xu] -load T2:eaca[avix] -.R2:eaca[aviu] += CSE12:aa[xu] T2:eaca[avix] -drop T2:eaca[avix] -drop CSE12:aa[xu] -load CSE32:ecaa[aiwx] -load Ym2:aaaa[vxuw] -.R2:eaca[aviu] += -1 * CSE32:ecaa[aiwx] Ym2:aaaa[vxuw] -drop Ym2:aaaa[vxuw] -drop CSE32:ecaa[aiwx] -load CSE7:Faa[Fvu] -load DF:Fec[Fai] -.R2:eaca[aviu] += 2 * CSE7:Faa[Fvu] DF:Fec[Fai] -drop DF:Fec[Fai] -drop CSE7:Faa[Fvu] -load CSE3:Faa[Fvu] -load DF:Fec[Fai] -.R2:eaca[aviu] += -1 * CSE3:Faa[Fvu] DF:Fec[Fai] -drop DF:Fec[Fai] -drop CSE3:Faa[Fvu] -load CSE29:aaaa[vxyu] -load T2:eaac[axyi] -.R2:eaca[aviu] += -1 * CSE29:aaaa[vxyu] T2:eaac[axyi] -drop T2:eaac[axyi] -load T2:eaca[axiy] -.R2:eaca[aviu] += -1 * CSE29:aaaa[yxvu] T2:eaca[axiy] -.R2:eaca[aviu] += 2 * CSE29:aaaa[vxyu] T2:eaca[axiy] -drop T2:eaca[axiy] -drop CSE29:aaaa[vxyu] -load CSE13:aa[wu] -load T2:eaca[aviw] -.R2:eaca[aviu] += -1 * CSE13:aa[wu] T2:eaca[aviw] -drop T2:eaca[aviw] -drop CSE13:aa[wu] -load CSE33:ecaa[aiwx] -load Ym2:aaaa[vxwu] -.R2:eaca[aviu] += -1 * CSE33:ecaa[aiwx] Ym2:aaaa[vxwu] -.R2:eaca[aviu] += 2 * CSE33:ecaa[aiwx] Ym2:aaaa[vxuw] -drop Ym2:aaaa[vxuw] +drop CSE10:aa[yu] +load CSE22:aaaa[vwzu] +load T2:eaca[awiz] +.R2:eaca[aviu] += -1 * CSE22:aaaa[vwzu] T2:eaca[awiz] +drop T2:eaca[awiz] +drop CSE22:aaaa[vwzu] +load CSE18:aaaa[xvuz] +load T2:eaca[azix] +.R2:eaca[aviu] += CSE18:aaaa[xvuz] T2:eaca[azix] +drop T2:eaca[azix] +load T2:eaac[azxi] +.R2:eaca[aviu] += CSE18:aaaa[xvzu] T2:eaac[azxi] +drop T2:eaac[azxi] +load T2:eaca[aziw] +.R2:eaca[aviu] += -2 * CSE18:aaaa[wvzu] T2:eaca[aziw] +drop T2:eaca[aziw] +drop CSE18:aaaa[wvzu] +load CSE33:ecaa[aivw] load Ym1:aa[wu] -.R2:eaca[aviu] += CSE33:ecaa[aivw] Ym1:aa[wu] +.R2:eaca[aviu] += -1 * CSE33:ecaa[aivw] Ym1:aa[wu] drop Ym1:aa[wu] -drop CSE33:ecaa[aivw] -load CSE20:aaaa[vxuy] -load T2:eaca[ayix] -.R2:eaca[aviu] += -2 * CSE20:aaaa[vxuy] T2:eaca[ayix] -.R2:eaca[aviu] += CSE20:aaaa[vwyu] T2:eaca[ayiw] -drop T2:eaca[ayiw] -load T2:eaac[aywi] -.R2:eaca[aviu] += CSE20:aaaa[vwuy] T2:eaac[aywi] -drop T2:eaac[aywi] -drop CSE20:aaaa[vwuy] -load CSE14:ec[ai] -load Ym1:aa[vu] -.R2:eaca[aviu] += -1 * CSE14:ec[ai] Ym1:aa[vu] -drop Ym1:aa[vu] -drop CSE14:ec[ai] +load Ym2:aaaa[vxwu] +.R2:eaca[aviu] += CSE33:ecaa[aiwx] Ym2:aaaa[vxwu] +.R2:eaca[aviu] += -2 * CSE33:ecaa[aiwx] Ym2:aaaa[vxuw] +drop Ym2:aaaa[vxuw] +drop CSE33:ecaa[aiwx] load CSE34:ecaa[aivw] load Ym1:aa[wu] -.R2:eaca[aviu] += -1 * CSE34:ecaa[aivw] Ym1:aa[wu] +.R2:eaca[aviu] += CSE34:ecaa[aivw] Ym1:aa[wu] drop Ym1:aa[wu] load Ym2:aaaa[vxwu] -.R2:eaca[aviu] += CSE34:ecaa[aiwx] Ym2:aaaa[vxwu] -.R2:eaca[aviu] += -2 * CSE34:ecaa[aiwx] Ym2:aaaa[vxuw] +.R2:eaca[aviu] += -1 * CSE34:ecaa[aiwx] Ym2:aaaa[vxwu] +.R2:eaca[aviu] += 2 * CSE34:ecaa[aiwx] Ym2:aaaa[vxuw] drop Ym2:aaaa[vxuw] drop CSE34:ecaa[aiwx] -alloc I:ecaa[aivx] -load f:aa[wv] -load T2:eaca[awix] -.I:ecaa[aivx] += f:aa[wv] T2:eaca[awix] -drop T2:eaca[awix] -drop f:aa[wv] -load Ym1:aa[xu] -.R2:eaca[aviu] += I:ecaa[aivx] Ym1:aa[xu] -drop Ym1:aa[xu] -drop I:ecaa[aivx] -load CSE17:ec[ai] +load CSE28:aaaa[xvyu] +load T2:eaac[axyi] +.R2:eaca[aviu] += -1 * CSE28:aaaa[xvyu] T2:eaac[axyi] +drop T2:eaac[axyi] +load T2:eaca[awiy] +.R2:eaca[aviu] += -1 * CSE28:aaaa[wyvu] T2:eaca[awiy] +.R2:eaca[aviu] += 2 * CSE28:aaaa[wvyu] T2:eaca[awiy] +drop T2:eaca[awiy] +drop CSE28:aaaa[wvyu] +load CSE11:aa[wu] +load T2:eaca[aviw] +.R2:eaca[aviu] += -1 * CSE11:aa[wu] T2:eaca[aviw] +drop T2:eaca[aviw] +drop CSE11:aa[wu] +load CSE17:aaaa[wvuy] +load T2:eaac[aywi] +.R2:eaca[aviu] += CSE17:aaaa[wvuy] T2:eaac[aywi] +drop T2:eaac[aywi] +load T2:eaca[ayiw] +.R2:eaca[aviu] += CSE17:aaaa[wvyu] T2:eaca[ayiw] +.R2:eaca[aviu] += -2 * CSE17:aaaa[wvuy] T2:eaca[ayiw] +drop T2:eaca[ayiw] +drop CSE17:aaaa[wvuy] +load CSE7:Faa[Fvu] +load DF:Fec[Fai] +.R2:eaca[aviu] += -1 * CSE7:Faa[Fvu] DF:Fec[Fai] +drop DF:Fec[Fai] +drop CSE7:Faa[Fvu] +load CSE4:Faa[Fvu] +load DF:Fec[Fai] +.R2:eaca[aviu] += CSE4:Faa[Fvu] DF:Fec[Fai] +drop DF:Fec[Fai] +drop CSE4:Faa[Fvu] +load CSE13:ec[ai] load Ym1:aa[vu] -.R2:eaca[aviu] += CSE17:ec[ai] Ym1:aa[vu] +.R2:eaca[aviu] += CSE13:ec[ai] Ym1:aa[vu] drop Ym1:aa[vu] -drop CSE17:ec[ai] +drop CSE13:ec[ai] load f:ec[ai] load Ym1:aa[vu] .R2:eaca[aviu] += f:ec[ai] Ym1:aa[vu] @@ -1555,101 +1550,121 @@ load Ym2:aaaa[vxuw] .R2:eaca[aviu] += CSE30:ecaa[aiwx] Ym2:aaaa[vxuw] drop Ym2:aaaa[vxuw] drop CSE30:ecaa[aiwx] -load CSE4:Faa[Fvu] -load DF:Fec[Fai] -.R2:eaca[aviu] += CSE4:Faa[Fvu] DF:Fec[Fai] -drop DF:Fec[Fai] -drop CSE4:Faa[Fvu] -load CSE31:ecaa[aixw] +load CSE32:ecaa[aixw] load Ym2:aaaa[vwux] -.R2:eaca[aviu] += -1 * CSE31:ecaa[aixw] Ym2:aaaa[vwux] +.R2:eaca[aviu] += -1 * CSE32:ecaa[aixw] Ym2:aaaa[vwux] drop Ym2:aaaa[vwux] -drop CSE31:ecaa[aixw] +drop CSE32:ecaa[aixw] +load CSE31:ecaa[aiwx] +load Ym2:aaaa[vxuw] +.R2:eaca[aviu] += -1 * CSE31:ecaa[aiwx] Ym2:aaaa[vxuw] +drop Ym2:aaaa[vxuw] +drop CSE31:ecaa[aiwx] +load CSE12:ec[ai] +load Ym1:aa[vu] +.R2:eaca[aviu] += -1 * CSE12:ec[ai] Ym1:aa[vu] +drop Ym1:aa[vu] +drop CSE12:ec[ai] +alloc I:ecaa[aivx] +load f:aa[wv] +load T2:eaca[awix] +.I:ecaa[aivx] += f:aa[wv] T2:eaca[awix] +drop T2:eaca[awix] +drop f:aa[wv] +load Ym1:aa[xu] +.R2:eaca[aviu] += I:ecaa[aivx] Ym1:aa[xu] +drop Ym1:aa[xu] +drop I:ecaa[aivx] +load CSE8:Faa[Fvu] +load DF:Fec[Fai] +.R2:eaca[aviu] += 2 * CSE8:Faa[Fvu] DF:Fec[Fai] +drop DF:Fec[Fai] +drop CSE8:Faa[Fvu] store R2:eaca[aviu] -alloc CSE35:ecca[aiju] -load g:cc[ki] -load T2:eacc[aukj] -.CSE35:ecca[aiju] += g:cc[ki] T2:eacc[aukj] -drop T2:eacc[aukj] -drop g:cc[ki] -store CSE35:ecca[aiju] - -alloc CSE36:ecca[ajiu] +alloc CSE35:ecca[ajiv] load g:cc[kj] -load T2:eacc[auik] -.CSE36:ecca[ajiu] += g:cc[kj] T2:eacc[auik] -drop T2:eacc[auik] +load T2:eacc[avik] +.CSE35:ecca[ajiv] += g:cc[kj] T2:eacc[avik] +drop T2:eacc[avik] drop g:cc[kj] -store CSE36:ecca[ajiu] +store CSE35:ecca[ajiv] -alloc CSE37:ecca[ajiv] +alloc CSE36:ecca[ajiu] load g:ee[ab] -load T2:eacc[bvij] -.CSE37:ecca[ajiv] += g:ee[ab] T2:eacc[bvij] -drop T2:eacc[bvij] +load T2:eacc[buij] +.CSE36:ecca[ajiu] += g:ee[ab] T2:eacc[buij] +drop T2:eacc[buij] drop g:ee[ab] -store CSE37:ecca[ajiv] +store CSE36:ecca[ajiu] + +alloc CSE37:ecca[aiju] +load g:cc[ki] +load T2:eacc[aukj] +.CSE37:ecca[aiju] += g:cc[ki] T2:eacc[aukj] +drop T2:eacc[aukj] +drop g:cc[ki] +store CSE37:ecca[aiju] alloc R2:eacc[auij] -load CSE5:aa[xu] -load T2:eacc[axij] -.R2:eacc[auij] += -1 * CSE5:aa[xu] T2:eacc[axij] -drop T2:eacc[axij] -drop CSE5:aa[xu] +load CSE2:aa[yu] +load T2:eacc[ayij] +.R2:eacc[auij] += -2 * CSE2:aa[yu] T2:eacc[ayij] +drop T2:eacc[ayij] +drop CSE2:aa[yu] +load CSE1:aa[yu] +load T2:eacc[ayij] +.R2:eacc[auij] += CSE1:aa[yu] T2:eacc[ayij] +drop T2:eacc[ayij] +drop CSE1:aa[yu] load f:aa[vu] load T2:eacc[avij] .R2:eacc[auij] += f:aa[vu] T2:eacc[avij] drop T2:eacc[avij] drop f:aa[vu] -load CSE9:aa[uy] -load T2:eacc[ayij] -.R2:eacc[auij] += CSE9:aa[uy] T2:eacc[ayij] -drop T2:eacc[ayij] -drop CSE9:aa[uy] -load CSE8:aa[uy] -load T2:eacc[ayij] -.R2:eacc[auij] += -2 * CSE8:aa[uy] T2:eacc[ayij] -drop T2:eacc[ayij] -drop CSE8:aa[uy] +load DF:Fec[Fai] +load CSE14:Fca[Fju] +.R2:eacc[auij] += -1 * DF:Fec[Fai] CSE14:Fca[Fju] +drop CSE14:Fca[Fju] +drop DF:Fec[Fai] +load CSE25:aa[xu] +load T2:eacc[axij] +.R2:eacc[auij] += 2 * CSE25:aa[xu] T2:eacc[axij] +drop T2:eacc[axij] +drop CSE25:aa[xu] +load CSE26:aa[ux] +load T2:eacc[axij] +.R2:eacc[auij] += -1 * CSE26:aa[ux] T2:eacc[axij] +drop T2:eacc[axij] +drop CSE26:aa[ux] +load CSE3:aa[wu] +load T2:eacc[awij] +.R2:eacc[auij] += -1 * CSE3:aa[wu] T2:eacc[awij] +drop T2:eacc[awij] +drop CSE3:aa[wu] +load CSE35:ecca[ajiv] +load Ym1:aa[uv] +.R2:eacc[auij] += CSE35:ecca[ajiv] Ym1:aa[uv] +drop Ym1:aa[uv] +.R2:eacc[auij] += -1 * CSE35:ecca[ajiu] +drop CSE35:ecca[ajiu] load DF:Fec[Fai] load DF:Fca[Fju] .R2:eacc[auij] += DF:Fec[Fai] DF:Fca[Fju] drop DF:Fca[Fju] drop DF:Fec[Fai] -load CSE35:ecca[aiju] -.R2:eacc[auij] += -1 * CSE35:ecca[aiju] -load Ym1:aa[uv] -.R2:eacc[auij] += CSE35:ecca[aijv] Ym1:aa[uv] -drop Ym1:aa[uv] -drop CSE35:ecca[aijv] -load CSE1:aa[xu] -load T2:eacc[axij] -.R2:eacc[auij] += 2 * CSE1:aa[xu] T2:eacc[axij] -drop T2:eacc[axij] -drop CSE1:aa[xu] load CSE36:ecca[ajiu] -.R2:eacc[auij] += -1 * CSE36:ecca[ajiu] +.R2:eacc[auij] += CSE36:ecca[ajiu] load Ym1:aa[uv] -.R2:eacc[auij] += CSE36:ecca[ajiv] Ym1:aa[uv] +.R2:eacc[auij] += -1 * CSE36:ecca[ajiv] Ym1:aa[uv] drop Ym1:aa[uv] drop CSE36:ecca[ajiv] -load DF:Fec[Fai] -load CSE18:Fca[Fju] -.R2:eacc[auij] += -1 * DF:Fec[Fai] CSE18:Fca[Fju] -drop CSE18:Fca[Fju] -drop DF:Fec[Fai] -load CSE37:ecca[ajiv] +load CSE37:ecca[aiju] +.R2:eacc[auij] += -1 * CSE37:ecca[aiju] load Ym1:aa[uv] -.R2:eacc[auij] += -1 * CSE37:ecca[ajiv] Ym1:aa[uv] +.R2:eacc[auij] += CSE37:ecca[aijv] Ym1:aa[uv] drop Ym1:aa[uv] -.R2:eacc[auij] += CSE37:ecca[ajiu] -drop CSE37:ecca[ajiu] -load CSE10:aa[wu] -load T2:eacc[awij] -.R2:eacc[auij] += -1 * CSE10:aa[wu] T2:eacc[awij] -drop T2:eacc[awij] -drop CSE10:aa[wu] +drop CSE37:ecca[aijv] store R2:eacc[auij] From 8471ffd1b5a79422a15494f944e60f6513023d77 Mon Sep 17 00:00:00 2001 From: Eduard Valeyev Date: Mon, 24 Aug 2026 11:24:33 -0400 Subject: [PATCH 22/22] enforce the defining perm symmetry and braket/hermiticity agreement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three gaps found by Copilot on #596, all cases where the "a reserved label's defining symmetries are not free parameters" invariant was enforced unevenly across the three attributes and the two construction paths. - `check_symmetries()` enforced the braket and column symmetry of a reserved (anti)symmetrizer but not its bra/ket permutational symmetry, so `Tensor(L"Ŝ", bra, ket, Symmetry::Antisymm)` constructed happily and then did not compare equal to `make_symmetrizer()`. (The public ctors do not block this: despite the name, `assert_nonreserved_label` only rejects FNOperator/BNOperator labels, not `reserved::labels()`.) It now rejects a contradicting *specified* perm symmetry and supplies the defining one otherwise, exactly as it already does for the column symmetry. - the deserializer overwrote `perm_symm` unconditionally, so a contradicting explicit spec such as `Ŝ{...}:A-N-S` was silently corrected instead of reported. It now fills the defining value in only when none was spelled out, letting an explicit contradiction reach the ctor -- the treatment the braket spec already got. - `resolve_symmetries()` accepted a contradicting `braket`/`hermiticity` pair (e.g. `Symm` with `NonHermitian`), leaving `braket_symmetry_` and `hermiticity_` disagreeing inside the Tensor and leaking through `AbstractTensor::_braket_symmetry()` / `_hermiticity()`. Giving both is still allowed -- they must simply agree. The check compares the *derived* braket symmetry, so pinning `AntiHermitian` (the one trait BraKetSymmetry cannot represent) alongside its `Nonsymm` braket stays legal. Each is covered by a test that fails on the previous commit. Full ctest: 93/93. --- SeQuant/core/expressions/tensor.hpp | 39 ++++++++++++++++--- .../io/serialization/v1/ast_conversions.hpp | 17 ++++---- tests/unit/test_parse.cpp | 11 ++++++ tests/unit/test_tensor.cpp | 32 +++++++++++++++ 4 files changed, 87 insertions(+), 12 deletions(-) diff --git a/SeQuant/core/expressions/tensor.hpp b/SeQuant/core/expressions/tensor.hpp index 26c038bf9a..6d43990ccb 100644 --- a/SeQuant/core/expressions/tensor.hpp +++ b/SeQuant/core/expressions/tensor.hpp @@ -313,6 +313,9 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// whether #column_symmetry was spelled out by the caller rather than /// defaulted; see check_symmetries() bool column_symmetry_specified; + /// whether #symmetry was spelled out by the caller rather than defaulted; + /// see check_symmetries() + bool symmetry_specified; }; /// Resolves the (possibly unspecified) symmetry attributes of a tensor. @@ -342,6 +345,15 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { const BraKetSymmetry bks_resolved = syms.braket.has_value() ? *syms.braket : to_braket_symmetry(h_resolved, base_fld); + // #braket and #hermiticity spell the same underlying trait, so a caller + // that gives both must give a consistent pair. N.B. the comparison is on + // the *derived* braket symmetry, so pinning AntiHermitian (which + // BraKetSymmetry cannot represent) alongside its Nonsymm braket is legal. + if (syms.braket.has_value() && syms.hermiticity.has_value() && + to_braket_symmetry(*syms.hermiticity, base_fld) != *syms.braket) + throw Exception( + "Tensor: the given BraKetSymmetry and Hermiticity contradict each " + "other"); // an explicit Hermiticity is reported verbatim, to preserve traits that // the BraKetSymmetry round-trip cannot represent (e.g. AntiHermitian) const Hermiticity hermiticity_resolved = @@ -349,8 +361,12 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { ? *syms.hermiticity : (syms.braket.has_value() ? to_hermiticity(*syms.braket) : h_resolved); - return {s_resolved, bks_resolved, hermiticity_resolved, ps_resolved, - syms.column.has_value()}; + return {s_resolved, + bks_resolved, + hermiticity_resolved, + ps_resolved, + syms.column.has_value(), + syms.perm.has_value()}; } // fully-resolved terminal ctor (range form) @@ -374,7 +390,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { ket_net_rank_(ranges::count_if( ket_, [](const Index &idx) { return static_cast(idx); })) { validate_indices(); - check_symmetries(rsym.column_symmetry_specified); + check_symmetries(rsym.column_symmetry_specified, rsym.symmetry_specified); canonicalize_slots(); } @@ -397,7 +413,7 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { ket_net_rank_(ranges::count_if( ket_, [](const Index &idx) { return static_cast(idx); })) { validate_indices(); - check_symmetries(rsym.column_symmetry_specified); + check_symmetries(rsym.column_symmetry_specified, rsym.symmetry_specified); canonicalize_slots(); } @@ -943,7 +959,8 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { /// contradicts a reserved label's defining symmetry is an error, an /// unspecified one is simply replaced by the correct value /// @sa make_symmetrizer(), make_antisymmetrizer() - void check_symmetries(bool column_symmetry_specified) { + void check_symmetries(bool column_symmetry_specified, + bool symmetry_specified) { if (symmetry_ == Symmetry::Symm || symmetry_ == Symmetry::Antisymm) { // (Anti)symmetry in bra and ket indices automatically implies column // symmetry. N.B. must run before the reserved-label check below, which @@ -961,6 +978,18 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled { if (braket_symmetry_ != BraKetSymmetry::Nonsymm) throw Exception( "(Anti)symmetrization operators must not have braket symmetry"); + // the defining bra/ket permutational symmetry is not a free parameter + // either:  antisymmetrizes within bra and within ket, Ŝ only across the + // {bra,ket} particle columns. As for the column symmetry below, reject a + // contradicting *specified* value and supply the correct one otherwise. + const Symmetry defining_symmetry = label_ == reserved::antisymm_label() + ? Symmetry::Antisymm + : Symmetry::Nonsymm; + if (symmetry_specified && symmetry_ != defining_symmetry) + throw Exception( + "(Anti)symmetrization operators must have their defining bra/ket " + "permutational symmetry"); + symmetry_ = defining_symmetry; // (Anti)symmetrization operators act on indistinguishable particles, // hence are always column-symmetric; supplying it when unspecified makes // them compare equal however they were built (a mismatch would silently diff --git a/SeQuant/core/io/serialization/v1/ast_conversions.hpp b/SeQuant/core/io/serialization/v1/ast_conversions.hpp index df957ea8e1..e29c1b5b39 100644 --- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp +++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp @@ -328,13 +328,16 @@ struct Transformer { const bool is_reserved_symmetrizer = tensor.name == reserved::antisymm_label() || tensor.name == reserved::symm_label(); - if (tensor.name == reserved::antisymm_label()) { - //  antisymmetrizes within bra and within ket, Ŝ only across the - // {bra,ket} particle columns (i.e. it is perm-Nonsymm) - perm_symm = Symmetry::Antisymm; - } else if (tensor.name == reserved::symm_label()) { - perm_symm = Symmetry::Nonsymm; - } + //  antisymmetrizes within bra and within ket, Ŝ only across the + // {bra,ket} particle columns (i.e. it is perm-Nonsymm). Supply the + // defining value only when none was spelled out, so that a contradicting + // explicit spec reaches the Tensor ctor and is rejected there rather than + // silently overwritten here. + if (is_reserved_symmetrizer && + (!tensor.symmetry.has_value() || + tensor.symmetry.value().perm_symm == ast::SymmetrySpec::unspecified)) + perm_symm = tensor.name == reserved::antisymm_label() ? Symmetry::Antisymm + : Symmetry::Nonsymm; if (is_reserved_symmetrizer) { // force it rather than passing the Context's column default through, // which the Tensor ctor would reject as a contradicting *explicit* diff --git a/tests/unit/test_parse.cpp b/tests/unit/test_parse.cpp index 2cf135abec..a78ecea3ea 100644 --- a/tests/unit/test_parse.cpp +++ b/tests/unit/test_parse.cpp @@ -545,6 +545,17 @@ TEST_CASE("serialization", "[serialization]") { } } + SECTION("(anti)symmetrization operators reject contradicting perm") { + // an explicitly spelled perm symmetry that contradicts the reserved + // label's defining one is rejected rather than silently overwritten, + // as for the braket and column specs + REQUIRE_THROWS(deserialize(L"Ŝ{i1,i2;a1,a2}:A-N-S")); + REQUIRE_THROWS(deserialize(L"Â{i1,i2;a1,a2}:N-N-S")); + // the matching spellings stay accepted + REQUIRE_NOTHROW(deserialize(L"Ŝ{i1,i2;a1,a2}:N-N-S")); + REQUIRE_NOTHROW(deserialize(L"Â{i1,i2;a1,a2}:A-N-S")); + } + SECTION("(anti)symmetrization operators have fixed perm symmetry") { // the defining bra/ket permutational symmetry of a reserved // (anti)symmetrizer is not a free parameter either, so it must not be diff --git a/tests/unit/test_tensor.cpp b/tests/unit/test_tensor.cpp index 8637062564..a180eede9d 100644 --- a/tests/unit/test_tensor.cpp +++ b/tests/unit/test_tensor.cpp @@ -576,6 +576,22 @@ TEST_CASE("tensor_hermiticity", "[elements]") { REQUIRE_FALSE(make_diff(Field::Complex) == ex(0)); } + SECTION("braket and hermiticity must agree when both are given") { + // the two spell the same underlying trait, so a contradicting pair is a + // caller error rather than something to silently resolve one way + REQUIRE_THROWS_AS( + Tensor(L"g", bra{L"i_1"}, ket{L"a_1"}, + TensorSymmetries{.braket = BraKetSymmetry::Symm, + .hermiticity = Hermiticity::NonHermitian}), + Exception); + // AntiHermitian is the one trait BraKetSymmetry cannot represent, so + // pinning it alongside its (Nonsymm) braket must stay legal + REQUIRE_NOTHROW( + Tensor(L"g", bra{L"i_1"}, ket{L"a_1"}, + TensorSymmetries{.braket = BraKetSymmetry::Nonsymm, + .hermiticity = Hermiticity::AntiHermitian})); + } + SECTION("programmatic ctor defaults are Context-independent") { // The Context's default symmetries govern *deserialization* only (see // test_parse). A programmatic Tensor ctor always resolves unspecified @@ -657,6 +673,22 @@ TEST_CASE("(anti)symmetrizer factories", "[elements]") { .column = ColumnSymmetry::Nonsymm}), Exception); + // the defining bra/ket permutational symmetry is likewise not a free + // parameter: Ŝ symmetrizes the {bra,ket} columns (so it is perm-Nonsymm), + //  additionally antisymmetrizes within bra and within ket + REQUIRE_THROWS_AS( + Tensor(reserved::symm_label(), bra{L"i_1", L"i_2"}, ket{L"a_1", L"a_2"}, + TensorSymmetries{.perm = Symmetry::Antisymm}), + Exception); + REQUIRE_THROWS_AS( + Tensor(reserved::antisymm_label(), bra{L"i_1", L"i_2"}, + ket{L"a_1", L"a_2"}, TensorSymmetries{.perm = Symmetry::Symm}), + Exception); + // an unspecified perm symmetry is supplied, as for column symmetry + REQUIRE(Tensor(reserved::antisymm_label(), bra{L"i_1", L"i_2"}, + ket{L"a_1", L"a_2"}) + .symmetry() == Symmetry::Antisymm); + // an *unspecified* column symmetry is silently supplied, so that the // library's Context-independent column default (Nonsymm) does not produce // a symmetrizer that differs from the deserialized one