Skip to content

mbpt: closed-shell Kramers (time-reversal) CC tracer (round 2) - #566

Draft
kshitij-05 wants to merge 11 commits into
masterfrom
kshitij/feature/kramers-tracing-round2
Draft

mbpt: closed-shell Kramers (time-reversal) CC tracer (round 2)#566
kshitij-05 wants to merge 11 commits into
masterfrom
kshitij/feature/kramers-tracing-round2

Conversation

@kshitij-05

Copy link
Copy Markdown
Member

Summary

Adds a closed-shell Kramers (time-reversal-symmetry) tracer for relativistic
2-/4-component coupled cluster to mbpt, plus the eval-layer CSE fix needed to
evaluate the complex tensors it produces. This is the "round-2" rebuild of the
Kramers tracer: a small, self-contained API that mirrors the existing spin
tracer in structure but folds Kramers configurations under time reversal instead
of applying Ms conservation.

The tracer takes an all-spinor (Kramers-free) closed-shell expression and emits a
sum over time-reversal-canonical Kramers-block representatives, each with the
{sign, conjugation, permutation} transform that reconstructs the remaining
blocks. A consumer (here, MPQC's CCk / SpinorMP2) evaluates one representative
block per symmetry orbit and fills the rest from the transforms, so the
relativistic residual is computed on a reduced set of unique blocks (doubles:
16 → 5).

All numerics are validated downstream in MPQC against the full-spinor
("spinorbital") oracle: MP2, CCD, CCSD, and CCSDT all reproduce the spinor
energy
(e.g. HSeOH/6-31G X2C: CCD 2.6e-11, frozen CCSDT 2.1e-10). The unit
tests here cover the symbolic structure (orbit counts, fold classes,
reconstruction transforms).

What's added

SeQuant/domain/mbpt/spinor.{hpp,cpp} — the tracer and its orbit machinery:

API Role
closed_shell_kramers_trace(expr, ext_groups, fold_T, expand_g) Energy tracer: 2ⁿ Kramers configs, no Ms filter, global-T (conjugate) pairs folded to 2 Re[...].
closed_shell_kramers_CC_trace(expr, expand_g, use_T) CC residual tracer: A-expands the leading antisymmetrizer Â, folds external Kramers configs under the rank-general external antisymmetry + T. Returns one Kramers-labeled block per external representative.
kramers_config_orbits(n, bit_perms, use_T) Orbits of n-bit configs under bit-permutation generators + global time reversal (the orbit primitive).
kramers_external_generators(rank) The shared external-antisymmetry generator set (Sₖ adjacent transpositions of each external group) — single source used by the tracer, the amplitude allocator, and the leaf reconstruction.
kramers_external_blocks(n, antisym_perms, use_T, symm_perms) Like kramers_config_orbits but each member carries its reconstruction transform block(cfg) = sign·[conj]·permute(block(canonical)).
has_antisymmetrizer(expr) Predicate to route a residual (has Â) vs a fully-contracted energy (no Â).
RealPart / ImagPart (+ real_part / imaginary_part) Symbolic markers for the 2 Re fold; symbolic-only (no native TN evaluation yet — see below).

SeQuant/core/eval/eval_node_compare.hpp — split CSE cache keys by
canon_phase. For complex/Kramers tensors whose leaf value is reconstructed
downstream, two contractions can share a canonical colored graph yet evaluate to
genuinely different (not merely sign-flipped) tensors; without this they
over-merge under CSE and reuse the wrong intermediate. The split only ever
separates cache entries (never merges), so it is correctness-preserving and
real closed-shell paths (all phase +1) are unaffected.

tests/unit/test_spinor.cpp — orbit counts and reconstruction transforms:
MP2-energy fold, rank-general external folds (ranks 1/2/3), CCD R2 → 5 labeled
blocks, internal-fold classes (pp-ladder, ring, separable T2² quad), and the
kramers_external_blocks reconstruction transforms (antisymmetric residual and
raw-g forms). Build glue in CMakeLists.txt / tests/unit/CMakeLists.txt.

Known limitations / follow-ups

  • BraKetSymmetry::Conjugate is not yet exploited by TensorNetworkV3. The
    eval_node_compare canon_phase split is the interim correctness measure; the
    faithful fix is to carry conjugation through canonicalization (TODO noted in
    the header). This is the keystone for the planned next step — teaching the
    canonicalizer time-reversal symmetry so the downstream reconstruction machinery
    dissolves.
  • No native Re()/Im() evaluation of a tensor network — the tracer emits
    RealPart/ImagPart markers and the consumer either honors them or uses the
    fold_T=false form (sum the complex blocks, take the real part).
  • Hermiticity (bra↔ket + conj) folding is deferred; the global-T fold is the one
    applied here.

Testing

  • tests/unit/test_spinor.cpp — symbolic structure (orbit counts, fold classes,
    reconstruction transforms).
  • End-to-end numerical validation lives in the consuming MPQC branch
    (SpinorMP2 / CCk): Kramers-restricted MP2/CCD/CCSD/CCSDT all match the
    full-spinor energy to the SCF/precision floor.

Add spinor.{hpp,cpp}: a Kramers tracer for closed-shell relativistic
(2-/4-component) theories, structured like the naive spin tracer
spintrace_impl but folding whole-config time-reversal (T) partners into
RealPart-wrapped representatives inline. Reuses the Spin quantum number as
the Kramers label (alpha=up, beta=down); expands the integral g while
keeping the amplitude t antisymmetric; leaves particle-interchange (sigma)
to canonicalize. Also adds symbolic RealPart/ImagPart Expr markers.

External-index antisymmetry, hermiticity, and the per-column internal-T-reach
are deferred (CC phase).

test_spinor.cpp: traces the MP2 energy 1/4 g-bar t-bar into 7 canonical
Re[g.t-bar] representatives whose outer coefficients sum to 2^n.
…lic)

Extend spinor.{hpp,cpp} toward Kramers-restricted CCSD (symbolic phase):

- kramers_config_orbits: rank-agnostic n-bit configuration orbits under a set
  of bit-permutation generators + optional global time reversal T (the fold
  engine).
- closed_shell_kramers_CC_trace: stages 1-2 of the CCSD pipeline. Factor out
  the antisymmetrizer  (its bra/ket give the external virt/occ groups), fold
  the external indices (rank-general S_k transpositions + T) into the
  symmetry-unique blocks, and fold the internal contracted indices (enumerate
  + sum, sigma-merge via canonicalize). A-expand, g-expansion and the g TRS
  folds (stages 3-5), and the internal-T-reach, are deferred.
- kramers_external_blocks: per-member sign/conj/perm reconstruction transforms
  (compute the canonical block, fill the rest) -- the eval-time external
  reconstruction (notes' open item).

Validated symbolically (test_spinor.cpp): singles/doubles/triples external
folds (2/5/8 blocks); CCD driver/pp-ladder/ring/quad internal folds
(5 blocks; 3/4/9 internal classes -- the quad's 9-vs-10 noted); reconstruction
transforms (perm/conj<->config invariant, T-pair signs).
Add an optional symm_perms argument (sign +1 permutation generators, default
none) alongside antisym_perms (sign -1), so the eval-time external
reconstruction also covers a raw, non-antisymmetrized g leaf: it folds under
particle interchange sigma (a symmetry, sign +1) + T into 6 blocks, vs the
antisymmetric residual's {P_ab, P_ij} + T into 5. Tested (test_spinor.cpp): the
{sigma} + T case yields 6 blocks with the sigma members carrying sign +1.
Thin public wrapper over the file-local find_antisymmetrizer, so a caller (the
CC spintrace dispatch) can distinguish a residual equation (carries a leading Â)
from a fully contracted scalar (the energy, no Â) and route each to the right
Kramers tracer.
- closed_shell_kramers_CC_trace: fold each residual term over its OWN contracted
  indices, not the whole block over the union of all terms' internals. A term
  invariant under an index (e.g. the driver, which has no internal index) was
  over-counted 2^k. Only surfaces on the full multi-term R2 — the unit tests feed
  single terms, so single-term behavior is unchanged (114 [spinor] assertions
  still pass).
- closed_shell_kramers_trace: add fold_T (default true). With fold_T=false, emit
  every configuration (sigma-merged) verbatim with no RealPart wrapper — the
  complex config sum whose real part the caller takes — for evaluators that
  cannot evaluate Re() of a tensor network (the CCk energy observable).
When expand_g=false, the integral g is kept antisymmetric (ḡ) instead of
expanded to the level-1 raw-g form, so the evaluator fetches the factory [as]
block and the cross-Kramers antisymmetry is handled inside the integral. The CCk
energy path uses expand_g=false; g-expansion (level-1) remains the default and a
later optimization stage. Default expand_g=true preserves SpinorMP2 behavior.
The CCSD Kramers trace now A-expands the antisymmetrizer  (expand_A_op) into
explicit signed external permutations, so the full external antisymmetry —
cross-Kramers pairs included — is explicit per term. The earlier strip-Â +
within-block bitwise form could not antisymmetrize cross-Kramers t-dependent
terms (a post-hoc block sign only flips, it does not antisymmetrize).

Adds two flags: expand_g (expand each ḡ to raw NonSymm leaves so the evaluator
fetches raw ⟨..|..⟩ blocks instead of the factory [as] block, which omits the
cross-Kramers swap for mixed-Kramers pairs) and use_T (fold external configs
under global time reversal; default on).
The eval-node CSE identity (the canonical colored connectivity graph) is value-
invariant for ordinary tensors but not for complex tensors whose leaf value is
reconstructed downstream (e.g. relativistic Kramers t-amplitudes via an external
recon): two contractions can share a canonical graph yet evaluate to genuinely
different (NOT merely sign-flipped) tensors, surfacing as a +1/-1 canon_phase
collision. Keying canon_phase into the eval-node equality splits those apart.
This only ever splits cache entries (never merges), so it is correctness-
preserving; real closed-shell paths (all phase +1) are unaffected and lose no CSE.

Interim hack. TODO: replace with a faithful conjugation-aware eval-node identity
once TensorNetworkV3 exploits BraKetSymmetry::Conjugate (carry the conjugation
through canonicalization, the way canon_phase carries the sign).
compose() composed the reconstruction perm in reversed order
(tx.perm[g_perm[k]] instead of g_perm[tx.perm[k]]) for the convention
block(cfg)[v] = block(canon)[v_perm]. The two orders agree for the abelian
S1/S2 external groups of singles/doubles (all involutions), so CCD/CCSD were
unaffected, but differ for the non-abelian S3+ groups of triples and higher,
where 3-cycles reconstructed to the wrong tensor. Validated offline at ranks
1-4 against a fully antisymmetric + TRS reference (reversed order fails at
rank 3; this order passes to machine precision at every rank).
The S_rank adjacent-transposition generator set for the external Kramers fold
(virtual group [0,rank), occupied group [rank,2*rank)) was hand-built inline in
closed_shell_kramers_CC_trace and, MPQC-side, in two more places (the amplitude
allocator and the leaf reconstruction). Provide it as one shared helper and use
it in the tracer. Pure refactor.
@kshitij-05
kshitij-05 marked this pull request as draft June 30, 2026 16:28
Orphaned when 7f69992 switched A-handling to expand_A_op; its only
references are its own definition and a self-recursive call, so it is
unused. clang -Wunneeded-internal-declaration (-Werror) in SeQuant's
unit build rejects it (the MPQC build that validated the branch uses
looser flags, so it slipped through).
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.

1 participant