Symbolic conjugation redesign: canonical braket-conjugate fold, per-tensor conj report, core Re/Im algebra - #602
kshitij-05 wants to merge 33 commits into
Conversation
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.
…round-trip is now lossless
…ity, and the conjugation marker
…ot report, eval leaf identity
…tions, is_hermitian_network
…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
left a comment
There was a problem hiding this comment.
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.
| // 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; |
There was a problem hiding this comment.
Why is this specific to conjugate braket symmetry? In other words, why does this not cause any issues for BraKetSymmetry::Symm?
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
I don't understand why we can't reorient T{a1;} to T{;a1}* though, which seems to be what this code prevents 👀
| 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())) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
| // 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); | ||
| } |
There was a problem hiding this comment.
Why wasn't this needed before (i.e. for BraKetSymmetry::Symm)?
There was a problem hiding this comment.
As stated in the previous comment reply
| // 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}; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Why is it needed before that? 👀
There was a problem hiding this comment.
Why did these have to be changed?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Ah, this is about the default symmetries used by deserialization, right?
…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.
There was a problem hiding this comment.
🟡 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_tensorsreporting and options-basedcanonicalize_slots. - Introduces first-class tensor conjugation marker
^*(parsing + serialization), propagation through rebuild/transforms, and graph coloring support. - Adds core
RealPart/ImagPartnodes andfold_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.
…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
…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
…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.
…) 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.
…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.
…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.
…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)
|
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. |
Supersedes the symbolic half of #591; evaluation/export changes follow in a separate PR.
Canonicalization
BraKetSymmetry::Conjugatetensors fold onto a single canonical bra/ket orientation: per-tensor inDefaultTensorCanonicalizer::canonicalize_braket(unfold-first, marker-convergent), and inTensorNetworkV3where orientation is graph-dictated (content rule demoted to a tie-break on equal bra/ket space multisets), with a post-relabel refold socanonicalizeis idempotent.create_graphbundles, gated by newbraket_foldable/braket_conjugate_foldablepredicates (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 singleconjparity bit (kept one cycle as[[deprecated]]; intended to dissolve intoTreeIndexwhen it lands).CanonicalizeSlotsOptionsstruct with defaultednamed_index_compare; the 3-argcanonicalize_slotsremains as a deprecated forwarder, in-tree call sites migrated.Conjugation algebra
Re/Imexpression nodes (core/expressions/complex.hpp) with eager rules: linearity, real-scalar hoist, i-rotation, composition table, conj action.fold_conjugate_pairs: exactA + A* → 2·Re(A)/A − A* → 2i·Im(A), hash-bucketed matching, pluggable conjugate op;fold_conjugate_pairs_of_real_sumkept as a back-compat wrapper. Auto-fold insimplify()is behindSimplifyOptions::FoldConjugatePairs, default No until evaluators understand Re/Im.sequant::conjugate(ExprPtr)(distributes over Product/Sum, rejects operator content) andis_hermitian_network().Tensor::with_slotsrebuild API carriesconjugated()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
BraKetSymmetryfor time reversal (TODO recorded at the predicate).Tests: new
tests/unit/test_conjugation.cppcatalogue (atoms, Sum/Product, TN, eval-boundary compat); full ctest green on Debug+THROW (246/246) and Release+THROW (279/279).