eval: add out-of-band SlotSymmetry descriptor carrier on EvalExpr (Phase 0)#567
eval: add out-of-band SlotSymmetry descriptor carrier on EvalExpr (Phase 0)#567evaleev wants to merge 11 commits into
Conversation
|
Wouldn't it make more sense to make In fact, the most reusable way to implement this is to make use of |
When we are ready to support general symmetries it will make sense to reuse this in AbstactTensor API and Tensor.
Presumably you mean symmetry is specified by a representation. I'm not sure over what actually (since numbers alone are not enough, presumably over operators that include complex conjugation etc.) I do not think I'm able to bite all of this off at once. Consider this a modeling exercise that will inform future design decisions but meanwhile will be sufficient for some improvements. Since it took us 3 attempts to get TN canonicalizer, I assume this will take at least this many iterations. |
But why not put it there from the get-go? It seems that would implicitly also cover the eval expr case by means of every node having a result tensor.
No, I mean symmetry represented as (basically) a list of individual permutations (including sign). Not that much different from storing indices of permutable elements, really. From a superficial glance, it seems like the new symmetry object introduced by this PR mostly re-invents what libPerm already does. Perhaps it would we worth it to have a quick in-person chat about this? |
I do not have a complete picture in my mind of how these concepts interplay. E.g. what are the "column" arrangement of the intermediate's slots? Do we need to deduce the column structure of tensor network? etc.
General symmetry handling will need to consider more general model than this (which is OK for real tensors with simple permutational symmetries), e.g. need for permutations to result in complex conjugation + nonabelian symmetries (in this case representation of the symmetry is not representable by plus or minus 1). |
|
Extension to complex conjugation is only an extension of the permutation class in libPerm. Also, libPerm doesn't make assumptions about Abelian-ness so non-abelian should not cause problems.
With libPerm we could represent individual index permutations so the tensor layout doesn't have to be anything specific in order to represent what we might want to refer to as a "column symmetry" in any given context. |
…ase 0) Introduce a SlotSymmetry descriptor struct (column_groups / bra_groups / ket_groups, each a permutation group with a sign) and carry it on EvalExpr as an out-of-band, default-constructed member with a slot_symmetry() accessor. This is the data carrier only: no deduction logic, no mutators. The field is write-default-only and is intentionally NOT referenced by any identity or serialization path (canonicalize_slots, EvalExpr hashing, the TensorNetwork bliss-graph builder, or export), and no intermediate result tensor's symmetry tag is touched. Later phases will populate and consume it.
…hase 0)
Add from_leaf_tensor(Tensor const&), which maps a leaf tensor's permutational-
symmetry attributes onto a SlotSymmetry descriptor over its slot positions:
- ColumnSymmetry::Symm -> one ColumnGroup over the matched (bra[c],ket[c])
columns (c in [0, min(bra_rank,ket_rank))), sign +1;
- Symmetry::Symm/Antisymm -> a bra_group and a ket_group over the respective
bundles (rank >= 2), sign +1/-1;
- Nonsymm everywhere -> empty.
Unpaired bra/ket slots and aux are never placed in a ColumnGroup.
The EvalExpr Tensor-leaf ctor now populates slot_symmetry_ from this. The field
remains out-of-band: nothing outside the unit test reads it, so leaf nodes that
now carry a non-empty descriptor do not perturb hashing, canonicalization, CSE,
or export (the existing eval/optimize suites pass unchanged).
…er (Phase 0) Add deduce_slot_symmetry(left, right, result), realizing the spec column-group inheritance rule: the result's matched columns form one ColumnGroup iff every result-bra index traces to one operand's column-grouped slots, every result-ket index traces to one operand's column-grouped slots, and the contraction between those operands pairs symmetrically (the PPL / giant matched-pair-swap pattern). Index tracing is by external label, sufficient for the Phase-0 acceptance cases. Wire it into binarize's make_prod: the tensor*tensor branch sets the result descriptor from deduce_slot_symmetry; the scalar*tensor branch and the trailing scalar-multiplied-result branch pass the tensor operand's descriptor through unchanged (a scalar factor preserves the slot layout). The descriptor is set via a new EvalOpSetter::set_slot_symmetry mutator and remains out-of-band: it does not touch the result tensor, its hash, canon indices, or connectivity graph, so the eval/optimize suites pass unchanged.
b1b0f39 to
9c98fcd
Compare
it still seems that P.S. another potential concern raised by an agentic review: to deduce symmetry of a Sum we need to determine the intersection of group representations of multiple terms. It says this is nontrivial for a general permutation case assumed by libPerm. |
…ity, merge-indices passthrough, proto-index aliasing) C1 (Critical): column-group identity collapse - SlotLoc::column_grouped bool replaced with optional<size_t> column_group_idx - column_locations returns which operand ColumnGroup each column belongs to - deduce_slot_symmetry clusters on 4-tuple (bra_supplier, bra_group_idx, ket_supplier, ket_group_idx) so columns from distinct source groups in the same operand are never merged into one oversized ColumnGroup I1 (Important): passthrough descriptor survives merge_indices flattening - Three sites in eval_expr.cpp (scalar*tensor, Sum make_sum, trailing scalar) previously copied the operand's descriptor unconditionally - Fixed with a rank-match guard: only propagate when result bra_rank == operand bra_rank && result ket_rank == operand ket_rank; under merge_indices both result ranks are 0 so the guard fires and the result gets an empty descriptor matching deduce_slot_symmetry behavior I2 (Important): trace maps alias proto-indices - All trace maps in slot_symmetry.cpp keyed on label() which strips the proto-index suffix; a_1<i_1> and a_1<i_2> collided under key "a_1" - Fixed by switching all four maps (column_locations x2, rbra_pos/rket_pos builders, trace lambda) to full_label() keys; lookups and emplace are consistent on both sides I3 (hardening): added comment at make_sum site documenting that intersect relies on positional comparison and the I3 invariant holds because the Sum result is built from left.canon_indices() Tests added (test_slot_symmetry.cpp): - C1 regression: 3-tensor outer-product (L*R)*P with L,R each ColumnSymm; verifies the result carries exactly 2 separate ColumnGroups of size 2 - I1 merge_indices: scalar*tensor with BinarizationOptions.merge_indices=true gives an empty descriptor - I2 proto-index: L*R product where L's bra contains a_1<i_1> and a_1<i_2>; verifies the inherited bra_group has two distinct slot positions - Symm(+1) leaf: Symmetry::Symm leaf gives bra_group and ket_group with sign +1
…soundness) Proto-indexed externals break the flat index->slot trace's bijectivity assumption (an index also living inside another slot's proto-list couples slots the trace treats as independent), which could yield a wrong symmetry. deduce_slot_symmetry now declines (empty descriptor) when any operand or result external carries proto-indices. Repeated identical factors are left unguarded: they are a provable false negative (deduced group is always a subset of the true symmetry), so a guard would only drop correct claims; a test documents that incompleteness.
Records the corrected design for symmetry deduction (superseding the Phase-0 hand-rolled rules' approach): deduction via canonicalize_slots canonicalize-and-compare probes on the TN colored graph (handles proto-index dependencies and emergent/repeated-tensor symmetry that the flat trace cannot); storage-sector canonicalization by block-sort / small-group orbit-min, NOT Butler-Portugal (which solves the symbolic double-coset problem the TN canonicalizer already replaces); target descriptor = signed permutation group + optional conjugation character; mono-term in scope, multi-term/multidimensional- irrep out of scope. Documents the interim proto-index soundness guard.
WARNING
This is an experimental sandbox playing in which should lead to sufficient understanding of the problem. The max goal is to figure out the algorithm for symmetry detection and min goal is to generate enough unit tests. The initial agentic-generated solution is incredibly naive, but hopefully subsequent iterations will produce something more robust.
Summary
Phase 0 of per-intermediate permutational-symmetry deduction: add the data carrier only, no deduction logic yet.
SeQuant/core/eval/slot_symmetry.hpp: aSlotSymmetrydescriptor structcolumn_groups(over matched (bra[c], ket[c]) columns),bra_groups,ket_groups; each group carries a signempty()and an order-insensitiveoperator==EvalExprgains a default-constructed, out-of-bandslot_symmetry_member plus aslot_symmetry()accessorOut-of-band constraint (encoding 1a)
The new field is write-default-only and is intentionally not referenced by any identity/serialization path:
canonicalize_slots,EvalExprhashing, theTensorNetworkbliss-graph builder, or export. No intermediate result tensor's symmetry tag is touched (make_tensor_wo_symmetriesis unchanged).grepconfirmsslot_symmetry_/slot_symmetry()appear only in the three intended files. Later phases will populate and consume it.Test plan
TDD: failing test first (RED:
slot_symmetry.hppnot found), then implement (GREEN).tests/unit/test_slot_symmetry.cpp(tag[slot_symmetry]): default-empty descriptor,operator==, carrier present + empty on a leafEvalExpr, and non-empty != empty. 5 assertions, pass.[optimize],[EvalExpr],[EvalNode]-> 486 assertions in 11 test cases, all pass;[slot_symmetry]passes. (This branch also includes the[bubble]Debug-timeout fix as its parent commit, so the optimize suite now completes in Debug.)[optimize]323 assertions;[slot_symmetry],[EvalExpr],[EvalNode]170 assertions).