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..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,6 +56,9 @@ class Context {
constexpr static auto braket_typesetting = BraKetTypesetting::ContraSub;
constexpr static auto braket_slot_typesetting =
BraKetSlotTypesetting::TensorPackage;
+ 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 +87,14 @@ class Context {
/// the BraKetSlotTypesetting object
BraKetSlotTypesetting braket_slot_typesetting =
Defaults::braket_slot_typesetting;
+ /// the default bra/ket permutational Symmetry for deserialized tensors
+ Symmetry symmetry = Defaults::symmetry;
+ /// 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
+ /// deserialized tensors
+ ColumnSymmetry column_symmetry = Defaults::column_symmetry;
};
static Options make_default_options() { return {}; }
@@ -155,6 +164,15 @@ 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 deserialized tensors
+ Symmetry symmetry() const;
+ /// \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
+ /// deserialized tensors
+ ColumnSymmetry column_symmetry() const;
/// Sets the Vacuum for this context, convenient for chaining
/// \param vacuum Vacuum
@@ -195,6 +213,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 deserialized tensors
+ /// \return ref to `*this`, for chaining
+ Context& set(Symmetry symmetry);
+ /// 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 deserialized tensors
+ /// \return ref to `*this`, for chaining
+ Context& set(ColumnSymmetry column_symmetry);
private:
std::shared_ptr idx_space_reg_ = nullptr;
@@ -207,6 +235,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..bd59e01e21 100644
--- a/SeQuant/core/expressions/tensor.hpp
+++ b/SeQuant/core/expressions/tensor.hpp
@@ -54,9 +54,57 @@ 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 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
+/// 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: 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;
+ /// 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; }
@@ -255,56 +303,132 @@ class Tensor : public Expr, public AbstractTensor, public MutatableLabeled {
}
}
+ /// the four symmetry attributes of a Tensor, fully resolved (no defaulting
+ /// left to do)
+ struct ResolvedSymmetries {
+ Symmetry symmetry;
+ BraKetSymmetry braket_symmetry;
+ Hermiticity hermiticity;
+ ColumnSymmetry column_symmetry;
+ /// whether #column_symmetry was spelled out by the caller rather than
+ /// defaulted; see check_symmetries()
+ bool column_symmetry_specified;
+ };
+
+ /// 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 *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
+ /// 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 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);
+ const BraKetSymmetry bks_resolved =
+ syms.braket.has_value() ? *syms.braket
+ : to_braket_symmetry(h_resolved, base_fld);
+ // 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
+ : (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)
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)
+ ResolvedSymmetries 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(
ket_, [](const Index &idx) { return static_cast(idx); })) {
validate_indices();
- check_symmetries();
+ check_symmetries(rsym.column_symmetry_specified);
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)
+ ResolvedSymmetries 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(
ket_, [](const Index &idx) { return static_cast(idx); })) {
validate_indices();
- check_symmetries();
+ check_symmetries(rsym.column_symmetry_specified);
canonicalize_slots();
}
+ // defaulting reserved-tag ctor (range form)
+ template
+ Tensor(S &&label, const bra &bra_indices,
+ const ket &ket_indices,
+ const aux &aux_indices, reserved_tag,
+ const TensorSymmetries &syms = {})
+ : Tensor(std::forward(label), bra_indices, ket_indices, aux_indices,
+ reserved_tag{},
+ resolve_symmetries(
+ 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,
+ const TensorSymmetries &syms = {})
+ // 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(
+ syms, sequant::base_field(bra_indices, ket_indices))) {}
+
public:
/// constructs an uninitialized Tensor
/// @sa Tensor::operator bool()
@@ -328,17 +452,24 @@ 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,
- 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) {
+ reserved_tag{},
+ TensorSymmetries{.perm = s, .braket = bks_opt, .column = ps}) {
assert_nonreserved_label(label_);
}
@@ -349,19 +480,26 @@ 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, 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) {
+ reserved_tag{},
+ TensorSymmetries{.perm = s, .braket = bks_opt, .column = ps}) {
assert_nonreserved_label(label_);
}
@@ -370,18 +508,23 @@ 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,
- 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) {
+ std::move(ket_indices), sequant::aux{}, reserved_tag{},
+ TensorSymmetries{.perm = s, .braket = bks_opt, .column = ps}) {
assert_nonreserved_label(label_);
}
@@ -392,19 +535,24 @@ 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,
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) {
+ TensorSymmetries{.perm = s, .braket = bks_opt, .column = ps}) {
assert_nonreserved_label(label_);
}
@@ -420,66 +568,113 @@ 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,
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)
+ // 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}) {
+ 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 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,
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)
+ // 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}) {
+ assert_nonreserved_label(label_);
+ }
+
+ /// @}
+
+ /// @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) {
+ 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) {
+ 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) {
+ 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) {
+ assert_nonreserved_label(label_);
}
/// @}
@@ -743,25 +938,37 @@ 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. must run before the reserved-label check below, which
+ // would otherwise report the (Antisymm) antisymmetrizer as contradicting.
+ 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.
- 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
+ // 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,
+ // 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) "
+ "symmetric");
column_symmetry_ = ColumnSymmetry::Symm;
}
}
@@ -941,15 +1148,100 @@ 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{}));
+ ket{ket_index}, aux{}, Tensor::reserved_tag{},
+ TensorSymmetries{.perm = Symmetry::Nonsymm,
+ .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{}));
+ ket{ket_index}, aux{}, Tensor::reserved_tag{},
+ 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 11a6e1f7e8..e83c6a6d02 100644
--- a/SeQuant/core/io/serialization/v1/ast_conversions.hpp
+++ b/SeQuant/core/io/serialization/v1/ast_conversions.hpp
@@ -323,12 +323,32 @@ struct Transformer {
ann(std::move(braIndices)), vac);
}
- // Set required symmetries for symmetrization operators
+ // 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();
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,
+ // which the Tensor ctor would reject as a contradicting *explicit*
+ // request
column_symm = ColumnSymmetry::Symm;
}
+ // 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))
+ braket_symm = BraKetSymmetry::Nonsymm;
// Dispatch to correct Tensor constructor (taking either BraKetSymmetry or
// Hermiticity)
diff --git a/SeQuant/core/io/serialization/v1/deserialize.cpp b/SeQuant/core/io/serialization/v1/deserialize.cpp
index d9c07a7beb..c49c8588e6 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,11 @@ 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; 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()};
if (options.def_perm_symm.has_value()) {
std::get<0>(symms) = options.def_perm_symm.value();
diff --git a/SeQuant/domain/mbpt/antisymmetrizer.cpp b/SeQuant/domain/mbpt/antisymmetrizer.cpp
index 130df98a1d..22f06e151d 100644
--- a/SeQuant/domain/mbpt/antisymmetrizer.cpp
+++ b/SeQuant/domain/mbpt/antisymmetrizer.cpp
@@ -85,8 +85,10 @@ 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
+ 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 +346,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 +426,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..0c4b9174c8 100644
--- a/SeQuant/domain/mbpt/biorthogonalization.cpp
+++ b/SeQuant/domain/mbpt/biorthogonalization.cpp
@@ -651,8 +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); });
- ExprPtr S_tensor =
- ex(Tensor{reserved::symm_label(), bra(kixs), ket(bixs)});
+ 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/op.cpp b/SeQuant/domain/mbpt/op.cpp
index c69c20ece5..8eb123d002 100644
--- a/SeQuant/domain/mbpt/op.cpp
+++ b/SeQuant/domain/mbpt/op.cpp
@@ -609,9 +609,11 @@ 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, hence are
+ // particle (column) symmetric
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 +622,11 @@ 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, hence are
+ // particle (column) symmetric
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);
}
@@ -691,8 +696,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);
@@ -703,13 +707,16 @@ 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 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) -
+ 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)) *
- ex(kronecker_label(), bra{m2}, ket{m1},
- Symmetry::Nonsymm);
+ ket(std::move(ketidxs_K)), Symmetry::Nonsymm,
+ std::nullopt, ColumnSymmetry::Symm)) *
+ make_kronecker(m2, m1);
}
});
};
@@ -1344,9 +1351,12 @@ 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)
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/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/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..dc41140464 100644
--- a/SeQuant/domain/mbpt/rules/thc.cpp
+++ b/SeQuant/domain/mbpt/rules/thc.cpp
@@ -15,6 +15,12 @@
namespace sequant::mbpt {
+// THC/DF factor and central tensors act on indistinguishable particles, hence
+// are particle (column) symmetric
+namespace {
+constexpr TensorSymmetries particle_symmetric{.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 +30,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}));
+ aux({aux_idx_1}), particle_symmetric);
auto t2 = ex(factor_label, bra(), ket({ranges::front(tnsr.ket())}),
- aux({aux_idx_1}));
+ aux({aux_idx_1}), particle_symmetric);
auto t3 = ex(factor_label, bra({ranges::back(tnsr.bra())}), ket(),
- aux({aux_idx_2}));
+ aux({aux_idx_2}), particle_symmetric);
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}), 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}));
+ aux({aux_idx_1}), particle_symmetric);
auto t3a = ex(factor_label, bra({ranges::front(tnsr.bra())}), ket(),
- aux({aux_idx_2}));
+ aux({aux_idx_2}), particle_symmetric);
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..6b2dff4b97 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 =
@@ -633,7 +633,7 @@ ExprPtr symmetrize_expr(const ProductPtr& product) {
auto S = Tensor{};
if (A_is_nconserving) {
S = Tensor(reserved::symm_label(), A_tensor.bra(), A_tensor.ket(),
- A_tensor.aux(), Symmetry::Nonsymm);
+ 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(),
@@ -641,7 +641,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(),
+ symmetrizer_symmetries);
}
const auto nf = rational{1, factorial(S.ket_rank())};
@@ -1231,7 +1232,9 @@ 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, not just its perm symmetry
+ 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/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/catch2_sequant.hpp b/tests/unit/catch2_sequant.hpp
index 3dc0185ef1..91573f69a2 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,15 @@
#include
#include
+namespace sequant {
+/// 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
+
namespace Catch {
// Make sure Catch uses proper string representation for SeQuant types
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..be93d48d62 100644
--- a/tests/unit/test_canonicalize.cpp
+++ b/tests/unit/test_canonicalize.cpp
@@ -28,6 +28,9 @@
#include
#include
+// 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;
@@ -42,25 +45,25 @@ TEST_CASE("canonicalization", "[algorithms]") {
SECTION("Tensors") {
{
auto op = ex