Skip to content

Symbolic conjugation redesign: canonical braket-conjugate fold, per-tensor conj report, core Re/Im algebra - #602

Open
kshitij-05 wants to merge 33 commits into
masterfrom
kshitij/feature/conjugation-symbolic
Open

kshitij-05 wants to merge 33 commits into
masterfrom
kshitij/feature/conjugation-symbolic

Conversation

@kshitij-05

Copy link
Copy Markdown
Member

Supersedes the symbolic half of #591; evaluation/export changes follow in a separate PR.

Canonicalization

  • BraKetSymmetry::Conjugate tensors fold onto a single canonical bra/ket orientation: per-tensor in DefaultTensorCanonicalizer::canonicalize_braket (unfold-first, marker-convergent), and in TensorNetworkV3 where orientation is graph-dictated (content rule demoted to a tie-break on equal bra/ket space multisets), with a post-relabel refold so canonicalize is idempotent.
  • Conjugation is part of TN identity: conj markers are colored into create_graph bundles, gated by new braket_foldable / braket_conjugate_foldable predicates (c-number + symmetry checks in one place instead of five hand-rolled copies).
  • SlotCanonicalizationMetadata::conjugated_tensors (input ordinals) reports which tensors need conjugation, replacing the single conj parity bit (kept one cycle as [[deprecated]]; intended to dissolve into TreeIndex when it lands).
  • CanonicalizeSlotsOptions struct with defaulted named_index_compare; the 3-arg canonicalize_slots remains as a deprecated forwarder, in-tree call sites migrated.

Conjugation algebra

  • Core Re/Im expression nodes (core/expressions/complex.hpp) with eager rules: linearity, real-scalar hoist, i-rotation, composition table, conj action.
  • fold_conjugate_pairs: exact A + A* → 2·Re(A) / A − A* → 2i·Im(A), hash-bucketed matching, pluggable conjugate op; fold_conjugate_pairs_of_real_sum kept as a back-compat wrapper. Auto-fold in simplify() is behind SimplifyOptions::FoldConjugatePairs, default No until evaluators understand Re/Im.
  • Total sequant::conjugate(ExprPtr) (distributes over Product/Sum, rejects operator content) and is_hermitian_network().
  • Tensor::with_slots rebuild API carries conjugated() through tensor-rebuilding transforms (remove_spin, swap_bra_ket, expand_antisymm, swap_spin) — the root-cause class of earlier marker-loss bugs.

#591 review objections addressed: per-tensor conj report instead of one network-level bool; orientation dictated by the canonicalized graph; conj handled in regular canonicalization, not only canonicalize_slots; params bundled into an options struct with the foldability predicate.

Not in this PR: evaluation (lazy conj), export/ITF golden churn, anti-conjugate BraKetSymmetry for time reversal (TODO recorded at the predicate).

Tests: new tests/unit/test_conjugation.cpp catalogue (atoms, Sum/Product, TN, eval-boundary compat); full ctest green on Debug+THROW (246/246) and Release+THROW (279/279).

The loop started at named_indices.size(), assuming the leading edges are
the named ones. A named index that is not an edge (e.g. a pure proto
index) shifted that cutoff onto an anonymous edge; the skipped edge's
ordinal was then handed to another edge of the same space, yielding a
non-injective rewrite that duplicated a slot index. Latent until the
Conjugate braket fold reordered the edge sort.
Mirror Variable and Power: Tensor gains a conjugated_ marker -- in the
hash (contributing only when set, so unconjugated tensors hash
identically to before), in static_equal and static_less_than (T orders
before conj(T)), rendered as ^* on the label in to_latex and in the v1
serializer (label^*{...}, matching the Variable spelling; deserializer
grammar extension deferred -- conjugation currently arises only from
canonicalization at runtime, never from parsed input).

conjugate() toggles the marker and touches no slots; adjoint() is
deliberately unchanged (for BraKetSymmetry::Conjugate the swap IS the
adjoint, the conj being carried by the symmetry relation, so the marker
commutes through it -- certified by the new test).

This is the representation half of making the canonicalizer's
conjugation byproduct symbolic: instead of a network-level bool
(SlotCanonicalizationMetadata::conj / EvalExpr::canon_conj_), the
orientation fold will toggle conjugated_ on the tensor itself, giving
per-tensor granularity by construction.
…tarred spellings via EvalOp::Adjoint

EvalExpr(Tensor) no longer folds BraKetSymmetry::Conjugate leaves onto one
bra/ket orientation (flat: TensorBlockCanonicalizer with the fold disabled;
ToT: new CanonicalizeSlotsOptions/CreateGraphOptions::fold_conjugate_braket
knob giving orientation-sensitive bundle colors and no conj byproduct):
leaves keep their as-written spelling, so leaf yielders and evaluators need
no conjugation awareness. Folding fresh leaves onto one orientation-shared
cache slot is the lazy-conj eval follow-up; the conjugation marker still
colors the graph, so C*C vs C*·C stay distinct (the cache-aliasing fix).
An already-starred spelling is served through an explicit EvalOp::Adjoint
node over its unmarked value-orientation operand, mirroring the U+207A
adjoint-label channel.

Fixes the eval_with_tiledarray / ta_tot_adjoint_end_to_end CI failures;
catalogue tests updated to this contract.

@Krzmbrzl Krzmbrzl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo, all non-template function implementations should go into the corresponding cpp file instead of in the header. Otherwise, we end up with headers coupled so strongly to each other that we start having to play tricks to use the productively.

Comment thread SeQuant/core/expressions/complex.hpp Outdated
Comment thread SeQuant/core/tensor_network/v3.cpp Outdated
Comment on lines +429 to +435
// a Conjugate half-tensor (empty bra or ket bundle) has no vertex for the
// empty bundle, so its recorded canonical bundle position is a
// value-initialized sentinel -- the graph cannot decide its orientation;
// the content-based refold after the lexicographic relabel decides it
if (braket_symmetry(tensor) == BraKetSymmetry::Conjugate &&
(bra_rank(tensor) == 0 || ket_rank(tensor) == 0))
continue;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this specific to conjugate braket symmetry? In other words, why does this not cause any issues for BraKetSymmetry::Symm?

@kshitij-05 kshitij-05 Sep 1, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it should be obvious that in BraKetSymmetry::Symm reorienting is not value-changing, but in Conjugate it is. I have updated the comments to explicitly state why this is...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we can't reorient T{a1;} to T{;a1}* though, which seems to be what this code prevents 👀

Comment thread SeQuant/core/tensor_network/v3.cpp Outdated
if (canonical_bra_ket_bundle_order[i][0] >
canonical_bra_ket_bundle_order[i][1]) {
canonical_bra_ket_bundle_order[i][1] &&
!ranges::equal(tensor._bra(), tensor._ket())) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this extra check guard against? Imo, we shouldn't try to be too clever at this point and just transfer all information from the graph canonicalization to the expression. Anything else always bears risk of breaking canonicalization due to introducing input-dependence of the "canonical" form.

@kshitij-05 kshitij-05 Sep 1, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a skip exactly to avoid input dependence. Can you explain more precisely what you mean by "just transfer all information from the graph canonicalization to the expression"?

How do you suggest we do tie-breaking over indistinguishable vertices, e.g., diagonal tensors with identical bra and ket colors when BraKetSymmetry::Conjugate? @evaleev

@Krzmbrzl Krzmbrzl Sep 2, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain more precisely what you mean by "just transfer all information from the graph canonicalization to the expression"?

What I mean by that is that if the graph canonicalization indicates that we should swap bra and ket indices (which is what canonical_bra_ket_bundle_order[i][0] > canonical_bra_ket_bundle_order[i][1] checks for), we should do it without adding logic that only does it only sometimes. Aka.: Only take the information from the graph and don't add extra conditions.

How do you suggest we do tie-breaking over indistinguishable vertices, e.g., diagonal tensors with identical bra and ket colors when BraKetSymmetry::Conjugate?

This comes down to whether we write t{a1;a1} or t{a1;a1}*, right? Ideally, the graph would be constructed in a way that reflects this symmetry, then the graph canonicalization includes tie-breaking. Otherwise, we could check for this during the second-pass of canonicalization after graph-canonicalization and deterministically choose one of the two. This can even happen after index relabeling as the bra==ket property will be retained through that.

Comment thread SeQuant/core/tensor_network/v3.cpp Outdated
Comment on lines +651 to +659
// Re-apply the per-tensor braket orientation fold now that indices carry
// their FINAL labels: the fold's full-space-tie decision is
// label-sensitive, and a decision taken on pre-relabel labels need not
// be a fixed point of the relabeled expression. The fold is convergent
// (it decides on the VALUE orientation), so this pass makes the whole
// canonicalization idempotent.
for (auto &tensor_ptr : tensors_) {
DefaultTensorCanonicalizer::canonicalize_braket(*tensor_ptr);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wasn't this needed before (i.e. for BraKetSymmetry::Symm)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As stated in the previous comment reply

Comment thread SeQuant/domain/mbpt/spin.cpp Outdated
Comment thread tests/unit/catch2_sequant.hpp Outdated
// Symm, keeping string fixtures coherent with ctor-built tensors.
const sequant::io::serialization::DeserializationOptions opts{
.def_perm_symm = sequant::Symmetry::Nonsymm,
.def_braket_symm = sequant::Hermiticity::Hermitian};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should apply hermitian symmetry by default. Afaik the only tensors for which this (almost) always applies are the integrals. The default braket symmetry will become Nonsymm once the default symmetry PR is merged.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is to be changed once #596 lands.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it needed before that? 👀

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did these have to be changed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tensor's symmetries were not explicitly mentioned. But configuring with a complex field now changes the values of the TN due to BraKetSymmetry::Conjugate. I believe this would be addressed in #596

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is about the default symmetries used by deserialization, right?

Comment thread SeQuant/core/expressions/tensor.hpp Outdated
Comment thread SeQuant/core/expressions/tensor.hpp Outdated
…s + sound fixes

Docs (the reviewer's why-only-for-Conjugate questions answered in place):
for Symm braket symmetry both orientations denote the SAME value, so
orientation decisions are label-independent and any spelling is harmless;
for Conjugate the orientations denote CONJUGATE values, so a swap must
toggle the elementwise-conjugation marker, sentinel/automorphic graph
verdicts must not be acted on, and the full-space-tie label tie-break makes
the post-relabel refold necessary. Also: vertex-painter comment explains why
the marker perturbs only marked tensors' colors (an unconditional combine
would reshuffle every marker-free network's canonical form); conj/swap/
adjoint algebra table moved into Tensor's class docs and adjoint() docs
spell out how it composes with conjugated(); catch2 braket fallback
documented as mirroring the ctor default; cost_analysis README explains the
explicit :N-N-S pins.

Code:
- drop SlotCanonicalizationMetadata::conj (no consumers since the eval
  boundary went fold-free); tests assert conjugated_tensors instead
- canonicalize_braket: single three-way space comparison
- mbpt::swap_bra_ket: in-place _swap_bra_ket on a copy instead of a
  with_slots rebuild
- complex.hpp: non-template implementations moved to new complex.cpp
- canonicalize_slots 3-arg forwarder un-deprecated (kept, documented)
PR #596 makes the programmatic Tensor ctor defaults fixed conservative
(Tensor::Defaults, NonHermitian) and moves the deserializer defaults into
the Context; braket symmetry is derived from hermiticity and base field.
Make this branch independent of the ambient deserializer defaults so the
two PRs compose in either order:

- ta_tot_adjoint_end_to_end: pin the ToT leaves' braket symmetry (:N-C-S)
  explicitly -- the test's premise is a Conjugate (Hermitian) leaf, not
  whatever the ambient default resolves to (identical behavior today)
- catch2 to_expression: concrete TODO(PR #596) on the Hermitian fallback
  (it mirrors the ctor default; flips or is dropped in lockstep)
- cost_analysis README: default-agnostic wording for the explicit :N-N-S
  pins (they state physical facts, independent of ambient defaults)

The remaining eval_node/tapp test sites added by this branch already pin
{.def_braket_symm = Hermiticity::NonHermitian} explicitly, matching #596's
future default.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Several core paths currently assume a conjugated tensor must be braket-Conjugate (and/or use Adjoint semantics), which can assert or mis-handle legitimate ^* usage and should be gated or generalized.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR redesigns SeQuant’s symbolic conjugation and braket-orientation canonicalization by (1) making conjugation a first-class tensor identity marker, (2) folding Conjugate bra/ket orientations into a canonical spelling (with per-tensor reporting), and (3) adding core symbolic Re/Im algebra plus exact folding of conjugate summand pairs.

Changes:

  • Canonical braket-conjugate folding (graph- and content-based), with per-tensor conjugated_tensors reporting and options-based canonicalize_slots.
  • Introduces first-class tensor conjugation marker ^* (parsing + serialization), propagation through rebuild/transforms, and graph coloring support.
  • Adds core RealPart/ImagPart nodes and fold_conjugate_pairs, plus targeted test updates and a new conjugation test catalogue.
File summaries
File Description
utilities/cost_analysis/README.md Documents explicit symmetry pinning in equation files to avoid default-Hermitian fold surprises.
utilities/cost_analysis/examples/df_r1.inp Pins tensor symmetries in example input to keep networks physically non-Hermitian.
utilities/cost_analysis/examples/ccsd_r2.inp Pins tensor symmetries in example input to keep networks physically non-Hermitian.
tests/unit/test_wick.cpp Updates expected canonical spellings to swapped+starred forms under new folding.
tests/unit/test_tensor.cpp Adds unit tests for tensor conjugation marker behavior (hash/order/serialize/adjoint).
tests/unit/test_tensor_network.cpp Updates network tests for V3-only support and conjugate-fold metadata expectations.
tests/unit/test_spin.cpp Introduces a real-orbital scoped context helper and updates spintrace expectations.
tests/unit/test_parse.cpp Adds parse/roundtrip coverage for t^*{...} tensor syntax.
tests/unit/test_mbpt.cpp Updates LaTeX/structure expectations for swapped+starred Hermitian spellings.
tests/unit/test_mbpt_cc.cpp Makes ext-index assertions robust to fold-induced bra/ket placement changes.
tests/unit/test_extract_subtrees.cpp Pins deserializer defaults to NonHermitian to keep tree-shape tests stable.
tests/unit/test_export.cpp Pins export test defaults to NonHermitian to prevent fold-driven reshaping.
tests/unit/test_export_python.cpp Pins tensor constructors/deserialization to NonHermitian for deterministic export fixtures.
tests/unit/test_eval_tapp.cpp Pins eval tests’ deserialization defaults to NonHermitian for stable IR shapes.
tests/unit/test_eval_ta.cpp Adds end-to-end adjoint serving coverage for starred ToT leaves.
tests/unit/test_eval_node.cpp Pins eval-node mechanics tests to NonHermitian and adds conjugate-fold mechanics section.
tests/unit/test_eval_expr.cpp Expands eval-boundary conjugation/fold behavior tests and aliasing regressions.
tests/unit/test_eval_btas.cpp Pins BTAS eval tests to NonHermitian defaults for stability.
tests/unit/test_conjugation.cpp New comprehensive test catalogue for conjugation identities, folding, and TN behavior.
tests/unit/test_canonicalize.cpp Updates canonicalization tests for explicit non-Hermitian tensors and new options APIs.
tests/unit/CMakeLists.txt Registers new unit test source test_conjugation.cpp.
tests/unit/catch2_sequant.hpp Aligns deserialize defaults with ctor defaults (Hermitian braket fallback).
SeQuant/domain/mbpt/spin.cpp Preserves conjugation marker through mbpt transforms (swap/remove spin/expand/swap spin).
SeQuant/domain/mbpt/rules/thc.cpp Normalizes folded tensors to value orientation before THC transform rebuilds.
SeQuant/domain/mbpt/rules/df.cpp Normalizes folded tensors to value orientation before DF transform rebuilds.
SeQuant/domain/mbpt/rules/csv.cpp Normalizes folded tensors to value orientation before CSV transform rebuilds.
SeQuant/domain/mbpt/biorthogonalization.cpp Migrates canonicalize_slots call to options struct form.
SeQuant/core/tensor_network/vertex_painter.hpp Adds optional conjugation coloring parameter to vertex painting.
SeQuant/core/tensor_network/vertex_painter.cpp Implements conditional conjugation coloring in vertex hashes.
SeQuant/core/tensor_network/v3.hpp Adds conjugated_tensors reporting and CanonicalizeSlotsOptions.
SeQuant/core/tensor_network/v3.cpp Implements conjugate-braket fold rules, idempotent refold, and metadata reporting.
SeQuant/core/tensor_canonicalizer.hpp Centralizes foldability predicates and adds fold_conjugate controls.
SeQuant/core/tensor_canonicalizer.cpp Implements canonical Conjugate folding with marker-convergent unfolding and tie-breaks.
SeQuant/core/options.hpp Adds SimplifyOptions::FoldConjugatePairs option.
SeQuant/core/options.cpp Implements SimplifyOptions::copy_and_set(FoldConjugatePairs).
SeQuant/core/optimize/single_term_detail.hpp Migrates canonicalize_slots to options struct usage.
SeQuant/core/io/serialization/v1/serialize.cpp Serializes tensor conjugation marker as label^*{...}.
SeQuant/core/io/serialization/v1/deserialize.cpp Parses optional ^* marker after tensor labels.
SeQuant/core/io/serialization/v1/ast.hpp Extends tensor AST node with conjugated flag.
SeQuant/core/io/serialization/v1/ast_conversions.hpp Applies parsed conjugation marker to constructed Tensor; asserts operators cannot be conjugated.
SeQuant/core/expressions/tensor.hpp Adds conjugation marker storage, LaTeX rendering, rebuild API, and value_oriented() helper.
SeQuant/core/expressions/expr_algorithms.hpp Declares conjugate, fold_conjugate_pairs, and is_hermitian_network APIs.
SeQuant/core/expressions/expr_algorithms.cpp Implements conjugation distribution, conjugate-pair folding, simplify hook, and hermitian-network check.
SeQuant/core/expressions/complex.hpp Introduces RealPart/ImagPart nodes and builders with eager rules.
SeQuant/core/expressions/complex.cpp Implements RealPart/ImagPart behavior, hashing, and eager builder logic.
SeQuant/core/expressions/abstract_tensor.hpp Adds virtual hooks for elementwise conjugation marker in AbstractTensor interface.
SeQuant/core/eval/eval_expr.cpp Disables conjugate fold at eval boundary, colors graphs by conjugation, and serves starred spellings via unary IR.
CMakeLists.txt Adds new core complex expression sources to the build.
Review details
  • Files reviewed: 48/48 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread SeQuant/core/eval/eval_expr.cpp Outdated
Comment thread SeQuant/core/expressions/expr_algorithms.cpp
Comment thread SeQuant/core/expressions/tensor.hpp Outdated
…onjugate fold

Addresses the PR review round: the marker (sequant::conjugate,
deserialized ^*) can land on leaves of any braket symmetry, but
value_oriented and binarize assumed it was only the Conjugate-fold
byproduct.

- value_oriented: total over the marker x braket-symmetry grid --
  Conjugate unfolds, Symm clears the value-redundant marker, Nonsymm
  throws std::logic_error (was an elidable SEQUANT_ASSERT that would
  otherwise silently swap slots)
- binarize(Tensor): marked-Nonsymm leaves refused up front, BEFORE the
  '⁺' label channel (which would otherwise serve a still-marked bare
  leaf, dropping the conjugation); marked-Symm leaves served unmarked;
  the Conjugate channel reuses value_oriented and shares one
  make_adjoint_node builder with the '⁺' channel
- leaf-hash rule centralized in hash_terminal_tensor: the marker enters
  the hash only where value-distinctive (Nonsymm), so t and t* no
  longer alias one cache slot; dead strip-for-hash copy in the EvalExpr
  ctor removed
- conjugate(Product): comment justifying Flatten::No (mirrors
  Product::clone)
- docs/includes: fold_conjugate_braket doc no longer names the deleted
  metadata.conj field; complex.hpp sheds constant.hpp; <compare> moved
  into the std include block

New regression tests: value_oriented_totality ([conjugation]) and the
'starred non-Conjugate leaves' section in test_eval_expr.
… batching, kind() rename) into kshitij/feature/conjugation-symbolic
@Krzmbrzl
Krzmbrzl added this pull request to stack #608 September 9, 2026 15:16
…ymmetries #596) into kshitij/feature/conjugation-symbolic

Master wins wherever it covers the same fix; the branch's extras are ported
onto master's structures:
- tests follow master's expectations where the two sides drifted
  (tensor-network C-entry and index-vector cases, the v3 canonicalization
  hunks, the MBPT L/R starred expectations), with an explicit :N-C-N
  Hermitian entry kept for the conjugation coverage
- Tensor::value_oriented moved out of line into tensor.cpp; the adjoint /
  conjugation-marker composition rule documented on the class and tested
- VertexPainter keeps the conditional colour+conjugation combine (the
  unconditional form was measured to churn eight fixtures)
- utilities/cost_analysis and external-interface examples taken from master
- test_utilities cross-comparison made order-independent
kshitij-05 added a commit that referenced this pull request Sep 16, 2026
…erged) into kshitij/feature/conjugation-eval

Brings master's batched DAG evaluation (#613) and default tensor symmetries
(#596) into the conjugation-eval branch on top of the merged #602.

The eval layer is resolved the way the round2 merge (0843463) resolved the
same conflicts, restricted to what this branch carries: eval.hpp, result.hpp
and the btas/dryrun/tapp/tiledarray result backends, cache_manager.hpp,
ordered_executor.hpp, eval_node{,_compare}.hpp and canon_transform.hpp are
taken from that resolution verbatim; eval_expr.{hpp,cpp} likewise, with the
Kramers-fold leaf paths (kramers_folded_, the flip on retrieval) left out
since this branch has no Kramers partner spaces. That carries the transform
model this branch is about onto master's structures: master's EvalOp::Adjoint
IR node is not emitted -- conjugation and bra-ket swap ride on the leaf's
CanonTransform -- and the three evaluation defects the port surfaced are
fixed here as in round2 (the leaf transform applied in fetch_leaf_traced, the
ket-rank antisymmetrization of a bra-ket-swapped root, the product-fold phase
composed from the flattened factors only).

Tests: master's and this branch's cases are both kept where they conflicted;
the sum-slot identity test follows master's multiset semantics (A + B and
B + A share a slot), the Adjoint-op section asserts the stored canonical
spelling, the export fixtures follow the canonical result layout, and the
optimize() calls that use the deprecated overload are marked as such.
Non-TA suite 271/271; TA-enabled [eval]/[tot]/[view]/[conj-transform] 64/64.
kshitij-05 added a commit that referenced this pull request Sep 16, 2026
…) into kshitij/feature/kramers-tracing-round2

Round2 already carries every change conjugation-eval made to the conflicted
files (eval_expr, tensor.hpp, optimize.cpp, tensor_network v3 and their
tests), so those resolve to round2's versions; the cost-analysis report keeps
master's text. Taken from the branch: its Re/Im optimize() tests and the
lazy-conj eval design/plan documents.
@kshitij-05
kshitij-05 requested a lite review from Copilot September 19, 2026 15:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…ket fold

The intermediate's canonical layout is computed at the eval boundary, where
the leaves keep their as-written orientation (the leaf ctor already disables
the fold). With the fold on, a BraKetSymmetry::Conjugate operand's bra/ket
bundles are orientation-free in the product's graph, which shifts the
canonical order of the named indices away from the operands' own layout.
Since master #596 the overlap `s` is Hermitian by default (Conjugate over the
spinor basis), so shape_provider_general_product's ToT*ToT result came out
with inner order a_2,a_1 instead of the natural a_1,a_2: TA cannot emit that
inner permutation, the shaped-product hook declined, and the zeroed tile was
not zero. The fold buys no node identity here either: the product hash already
carries the orientation-sensitive leaf hashes.
kshitij-05 and others added 8 commits September 20, 2026 23:17
…d diagonal tensors by content; canonicalize from the value orientation

Review follow-ups for the conjugate braket fold:

- canonicalize_graph takes the graph's bra/ket bundle verdict as-is (no
  half-tensor skip, no identical-bundle skip). The tensors the graph cannot
  orient -- a half-tensor (no vertex for its empty bundle) and a diagonal
  tensor (automorphic bundles) -- are decided by a deterministic pass right
  after the loop (canonicalize_braket: unfold, orient by bundle spaces, never
  swap identical bundles), which is label-independent and therefore a fixed
  point of the relabeling.

- Before the graph is built, every marked Conjugate tensor is brought to its
  value orientation (T^*{q;p} -> T{p;q}). The canonical form is a function of
  the value, and the graph must not see which spelling the input used:
  h{p;q} γ^*{p;q} and γ{p;q} h^*{p;q}, one value, used to canonicalize to
  different marker placements. The marker re-emerges only from the
  graph-dictated swap and the label fold.

- The conjugation marker's vertex coloring stays conditional (only a marked
  tensor's core color is perturbed). Folding the marker into every core
  color unconditionally was tried and measured: bliss orders partition cells
  by color VALUE, so the shifted colors re-spell the canonical form of
  marker-free networks -- unit fixtures re-spelled and 8 byte-compared
  goldens broken (every external-interface ITF verify test and
  cost_analysis/ccsd_r2/verify; the ccsd ITF code factorizes into 9 CSE
  intermediates instead of 8, since CSE detection keys on the canonical
  labeling). The comment records the measurement; marker-free networks stay
  bit-identical to master.

- SlotCanonicalizationMetadata::conjugated_tensors is gone: the swap toggles
  the tensor's own marker, which is what an evaluator reads.

Tests: the fold section checks the canonicalized tensors themselves (one
spelling per value, half-tensors and the diagonal case included), and a new
case pins that three spellings of one value canonicalize identically. The
MRSO expectation keeps h first with the marker on h: the value-orientation
prepass's choice.
…ffix

label() is now the bare array name; a trailing ⁺ is adopted into the
conjugated/transposed bits at construction and set_label. Printing,
hashing and graph colouring go through decorated_label(), so spellings,
hashes and canonical forms are unchanged. Adds the ^T spelling for the
transpose-only state and set_value_modifier() for slot-rebuilding
transforms that used to inherit the mark through label().

The conj∘adjoint state of a Nonsymm tensor is now spelled u^T
(ValueModifier::Transpose) rather than u⁺^*; test_tensor.cpp's
Klein-four-group section asserts the new spelling.
…rters and leaf hashes

adopt_adjoint_mark() now mirrors adjoint(): a ⁺ on a Hermitian label is
dropped rather than turned into modifier bits. The code generators name
arrays from decorated_label(), and hash_terminal_tensor keys on the
value modifier, so t, t^*, t^T and t⁺ keep distinct names and cache
slots and the Adjoint leaf hash is unchanged from before.
…nt() is their composition

The two modifier bits are normalized against the braket symmetry (Symm
clears both, Conjugate folds the transposition into the conjugation), so
adjoint() stays a pure swap for Hermitian tensors and the canonicalizer's
Conjugate fold is simply transpose(). AbstractTensor loses
_conjugate()/_conjugated(): an operator has an adjoint but no transpose
or conjugate; code that needs the bits goes through as_cnumber_tensor().
value_oriented() is total except for a conjugated Nonsymm tensor.
…t::Exception

The Adjoint state is served through EvalOp::Adjoint as before, a
Conjugate modifier on a Hermitian leaf through the same node over the
value-oriented operand, and Conjugate/Transpose on a Nonsymm leaf is
refused. hash_terminal_tensor hashes the decorated label so t and t⁺
keep distinct cache slots. The three std::logic_error sites the branch
introduced now throw sequant::Exception, per AGENTS.md.
…nt, stale comments

hash_terminal_tensor now keys on a set Conjugate/Transpose modifier for
every braket symmetry (g^*{i;a} denotes g{a;i}, so it must not share a
cache slot with g{i;a}); the ValueModifier enum moves above Tensor's
class doc so Doxygen attaches the doc to the class again; the
deserializer composes a ^*/^T suffix with a mark adopted from the label;
the painter goes through as_cnumber_tensor(); the graph-dictated
transpose asserts its operand; and the remaining test comments that
described the ⁺-in-label encoding are refreshed. Adds coverage for the
Transpose state's ordering and with_slots, and for canonicalization of
networks with a marked Nonsymm tensor.
evaleev and others added 6 commits September 21, 2026 23:23
…seModifier

ConjugateModifier and TransposeModifier are the two Z2 factors of
ValueModifier; * is the group operation on all three types, and
`ConjugateModifier * TransposeModifier` embeds the factors into their
product (`Yes * Yes == Adjoint`). Tensor::conjugated_/transposed_ are
typed with these enums instead of bool: every toggle
(conjugate()/transpose()/adopt_adjoint_mark()/normalize_value_modifier())
becomes multiplication by the enum's Yes value instead of negation, and
value_modifier() is conjugated_ * transposed_. New conjugate_modifier()/
transpose_modifier() accessors expose the two factors directly;
set_value_modifier() decomposes a ValueModifier back into them via the
new free functions of the same names (qualified with sequant:: where the
members would otherwise shadow them).

The deserializer's AST-to-Tensor conversion composes an adopted label
mark with the parsed modifier via the same group operation
(tt.value_modifier() * ValueModifier{tensor.modifier}) instead of a raw
XOR on the underlying bytes.
…ugate_label and transpose_label

expr.hpp gains conjugate_label ("^*") and transpose_label ("^T") next to
adjoint_label. Tensor::to_latex and the v1 serializer's Tensor case now
build through these constants instead of "^*"/"^T" literals; Variable's
and Power's own "^*" spellings (elementwise conjugation, same suffix)
switch to conjugate_label too. No spelling changes.
Audit of the comments the branch touched between 9666e35 and this
point: ALL-CAPS emphasis becomes _word_ emphasis or plain lowercase, and
sentences that narrated an earlier bit encoding, an earlier PR round, or
"as before"/"as on master" now state what the code does and why instead.
No behavior changes.
…Hermitian one

g^*{i;a} is conj(g{i;a}) for any g and equals g{a;i} only when g is
Hermitian; the comment on hash_terminal_tensor read as if the latter held
in general.
Symbolic conjugation: decouple transpose from conjugation on Tensor (revision of #602)
…+ docs #607, AGENTS.md notes #618, dangling-ref fix #620, CLAUDE.md removal #621) into kshitij/feature/conjugation-symbolic
@evaleev

evaleev commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

More refinements are coming. Current design is not complete, e.g. it is not capable of expressing properties of tensor under conjugation that arise from the tensor "core" (e.g. operator) rather from the basis itself.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants