Fold BraKetSymmetry::Conjugate bra<->ket orientations onto one eval cache slot (opt-in) - #591
Open
kshitij-05 wants to merge 5 commits into
Open
Fold BraKetSymmetry::Conjugate bra<->ket orientations onto one eval cache slot (opt-in)#591kshitij-05 wants to merge 5 commits into
kshitij-05 wants to merge 5 commits into
Conversation
Opt-in (exploit_conjugate): canonicalize_slots reports whether folding the two bra<->ket orientations of a Hermitian (BraKetSymmetry::Conjugate) tensor onto one canonical orientation introduced a conjugation, so eval-node CSE can share one cache slot between the orientations and serve the swapped one via an adjoint. Default off; no behavior change without the opt-in.
Layer the exploit_conjugate byproduct from canonicalize_slots onto the eval tree, so the two bra<->ket orientations of a Conjugate leaf share one cached value. EvalExpr(Tensor, exploit_conjugate) threads the flag into the ToT leaf's canonicalize_slots call and records the conjugation in a new canon_conj() bit. That bit is kept OUT of hash_value() (only canon_phase is folded there), so a Conjugate leaf and its bra<->ket swap hash identically and share a cache slot. binarize(Tensor, opts) then turns the conjugated orientation into an EvalOp::Adjoint over the bare canonical leaf, carrying the SAME canonical index order -- so the existing adjoint evaluator's result(post) = operand(pre).conj() degenerates to a pure elementwise conjugation (post == pre, no transpose) on retrieval. This reuses the tested '+'-adjoint machinery rather than adding a new eval op. Default off leaves every existing path byte-identical: the new binarize branch is skipped and canonicalize_slots is called with exploit_conjugate=false, exactly as before. Test [exploit_conjugate] (replaces the throwaway probe): a proto-indexed Conjugate leaf and its adjoint fold to one hash with exactly one carrying the byproduct; binarize wraps the swapped orientation in EvalOp::Adjoint over the shared bare leaf with matching canonical indices; off by default keeps the two distinct.
Extend the exploit_conjugate conjugation channel to the flat
(protoindex-free) block-canonicalization leaf path, so a flat
BraKetSymmetry::Conjugate tensor and its bra<->ket-swapped partner fold
onto one cached value the same way the ToT/canonicalize_slots path
already does. This is the path flat complex-field Conjugate leaves take.
TensorBlockCanonicalizer::apply() already folds the two bra<->ket
orientations of a Symm tensor (a free relabeling). Factor that
color-based swap into a shared orient_braket_by_color() (apply()'s Symm
branch reuses it, byte-for-byte unchanged) and add
fold_conjugate_braket(), which applies the same swap to a Conjugate
tensor and reports whether it swapped -- for Conjugate the swap carries a
conjugation (C{ket;bra} = conj(C{bra;ket})), so it is a byproduct the
caller must consume, not a free relabeling.
The EvalExpr flat-leaf ctor branch calls it under exploit_conjugate and
records the result in canon_conj_ (kept out of the hash, so the two
orientations share a cache slot); binarize(Tensor)'s existing
EvalOp::Adjoint wrap then serves the swapped orientation as a pure
elementwise conjugation on retrieval, exactly as for the ToT path.
Default off leaves every existing path byte-identical. Limitation:
equal-color bra/ket bundles (identical spaces) are not folded on the flat
path -- that needs a full index-pattern comparison, which only the
bliss/ToT path does; the flat color rule matches apply()'s Symm fold.
Test [exploit_conjugate] gains a flat-leaf section (C{a_1;i_1}:N-C-S)
mirroring the ToT checks: off -> distinct, on -> fold + exactly one
conjugated + Adjoint over the shared bare leaf.
EvalExpr(Tensor)'s canonicalize_slots call passed {} for
named_index_compare in order to reach the exploit_conjugate argument. An
empty comparator is NOT the declared default: canonicalize_slots then
falls back to an internal space()-only lambda, whereas the declared
default (default_idxptr_slottype_lesscompare) orders named indices by
proto-index count first. That proto-count-first order is what lays a
proto-indexed (ToT) coefficient's canon_indices out with occupieds
first -- a layout downstream coefficient-shape detectors rely on. So {}
silently mis-ordered them and broke such consumers.
Pass default_idxptr_slottype_lesscompare{} explicitly, restoring the
comparator every ToT leaf had before the exploit_conjugate arg was
threaded. Flat leaves (block-canon else-branch) are unaffected.
The flat-leaf exploit_conjugate channel folds the two bra<->ket orientations of a Conjugate tensor onto one cache slot, serving the swapped orientation via EvalOp::Adjoint. The ToT (proto-indexed) leaf path lacked the fold: TNV3's canonicalize_slots does not exploit conjugate braket symmetry, so the orientations landed in separate slots. Apply TensorBlockCanonicalizer::fold_conjugate_braket to the ToT leaf before the network canonicalization (the color rule is label-independent and proto-safe) and compose the byproduct into canon_conj_; the ToT TA Result backend already implements adjoint() (conj recurses into nested tiles). Off by default (exploit_conjugate opt-in unchanged). Test tot_conjugate_braket_fold: hashes fold, canon_conj marks the swapped orientation, no behavior change without the opt-in.
kshitij-05
requested review from
Krzmbrzl,
ajay-mk and
evaleev
and
a lite review from Copilot
and removed request for
Krzmbrzl
August 14, 2026 17:53
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces an opt-in exploit_conjugate path that canonicalizes BraKetSymmetry::Conjugate tensors so bra↔ket-swapped orientations share a single eval/cache identity, recording a conjugation “byproduct” and serving the swapped orientation via EvalOp::Adjoint.
Changes:
- Add
exploit_conjugateplumbing toTensorNetworkV3::canonicalize_slots()and graph construction, plus a network-levelconjbyproduct bit. - Extend
EvalExprwithcanon_conj()(excluded from hashing) and updatebinarize()to wrap conjugated-orientation leaves withEvalOp::Adjointwhen opted in. - Refactor tensor block canonicalization to factor out bra/ket orientation logic and add
fold_conjugate_braket(), with new unit tests covering flat and ToT leaf paths.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_tensor_network.cpp | Adds coverage for exploit_conjugate behavior in TN canonicalization metadata and hashing. |
| tests/unit/test_eval_expr.cpp | Adds end-to-end eval/binarize tests asserting leaf folding and Adjoint wrapping behavior when opted in. |
| tests/unit/test_canonicalize.cpp | Adds a ToT-focused regression test validating Conjugate folding behavior and opt-in default-off behavior. |
| SeQuant/core/tensor_network/v3.hpp | Extends canonicalization API/metadata with exploit_conjugate and conj byproduct reporting. |
| SeQuant/core/tensor_network/v3.cpp | Implements Conjugate bra/ket folding in graph coloring and computes the conjugation byproduct. |
| SeQuant/core/tensor_canonicalizer.hpp | Declares fold_conjugate_braket() and an extracted bra/ket orientation helper. |
| SeQuant/core/tensor_canonicalizer.cpp | Implements the extracted orientation logic and Conjugate fold helper; keeps Symm behavior unchanged. |
| SeQuant/core/eval/eval_expr.hpp | Adds EvalExpr(Tensor,bool) doc and canon_conj() API; extends BinarizationOptions. |
| SeQuant/core/eval/eval_expr.cpp | Implements canon_conj_ propagation, passes exploit_conjugate into TN canonicalization, and wraps swapped Conjugate leaves via EvalOp::Adjoint in binarize(). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
15
to
17
| #include <initializer_list> | ||
| #include <iostream> | ||
| #include <memory> |
Comment on lines
+255
to
+258
| auto md = [&cardinal](const std::wstring& s, bool exploit) { | ||
| TN tn(deserialize(s)); | ||
| return tn.canonicalize_slots(cardinal, nullptr, {}, exploit); | ||
| }; |
Comment on lines
+142
to
+145
| // Opt-in: fold the two bra<->ket orientations of a ToT Conjugate leaf | ||
| // onto one canonical orientation BEFORE the network canonicalization | ||
| // (TNV3 does not exploit conjugate braket symmetry itself), recording | ||
| // the conjugation byproduct exactly like the flat-leaf branch below. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Hermitian (
BraKetSymmetry::Conjugate) tensor and its bra↔ket-swapped partner are the same data up to complex conjugation, but the eval layer treated the two orientations as distinct leaves/subtrees, evaluating and caching both. This adds an opt-inexploit_conjugatechannel that folds the orientations onto one canonical form:TensorNetworkV3::canonicalize_slotsgains anexploit_conjugateargument and reports the conjugation byproduct of the orientation fold;EvalExprrecords the byproduct in a newcanon_conj()bit, kept out ofhash_value()so both orientations share a cache slot;binarizeserves the swapped orientation via the existingEvalOp::Adjointmachinery (pure elementwise conjugation on retrieval — both TA backends, flat and ToT, already implementResult::adjoint);TensorBlockCanonicalizerfactors its Symm-branch orientation choice intoorient_braket_by_color()and addsfold_conjugate_braket()for the Conjugate case (flat and ToT leaf paths);default_idxptr_slottype_lesscompare{}named-index comparator instead of{}(which silently selected the space-only fallback and mis-ordered proto-indexed canon_indices).Default off; no behavior change without the opt-in. Covered by
[exploit_conjugate]unit tests for both flat and ToT leaves; the full unit suite passes (311243 assertions in 88 cases).