Skip to content

Kramers and quaternion decomposition of spinor expressions#518

Draft
kshitij-05 wants to merge 4 commits into
masterfrom
kshitij/feature/spinor_decompose
Draft

Kramers and quaternion decomposition of spinor expressions#518
kshitij-05 wants to merge 4 commits into
masterfrom
kshitij/feature/spinor_decompose

Conversation

@kshitij-05
Copy link
Copy Markdown
Member

No description provided.

Introduces the Kramers symmetry quantum number for relativistic
2-component theories, mirroring the existing Spin QN structure:

  * space_qns.hpp: new `Kramers` enum (up/down/any/null) using bits
    7-8 of the QN bitmask. Spin and Kramers are mutually exclusive
    on a single index — an index is either spin-orbital (alpha/beta)
    or relativistic-spinor (kramers-up/kramers-down), reflecting the
    physical regime, not both.

  * spinor.{hpp,cpp}: new module hosting both the Kramers tracer
    (kramers_trace) and the upcoming quaternion decomposer (Phase 4).
    Kramers index annotations use ⇑/⇓ glyphs (U+21D1/U+21D3),
    distinct from spin's ↑/↓ so the QN families compose without
    label collision.

  * `make_kramers_up/dn/free(Index)` helpers parallel
    `make_spinalpha/beta/free`, going through the IndexSpace registry
    so spaces with the resulting (label, qns) tuple are reused.

  * `append_kramers(expr, replacements)` applies an Index→Index
    replacement map to all tensors in an expression (mirrors
    `append_spin` in `spin.cpp`).

  * `kramers_trace(expr)` MVP: collects every Kramers::any index in
    the input, enumerates 2^N configurations (each index pinned to
    up or down), and emits the resulting Sum of substituted copies.
    No per-tensor TRS canonicalization or cross-tensor folding yet
    — those iterations are deferred; the evaluator (LCAOFactory
    canonical-storage layer) already collapses Kramers blocks to
    orbit reps at integral-fetch time, so the MVP delivers correct
    energies for closed-shell relativistic MP2/CC even without
    symbolic folding. The Visscher Eq. 28 two-term form falls out
    of subsequent simplification commits.
Add complex-arithmetic primitives on tensor expressions, and use them
inside kramers_trace to rewrite TRS-paired Kramers configurations as
explicit conjugates of one another.

Conjugation infrastructure (groundwork for Phase 4 quaternion decompose
as well):

  * `toggle_conj_suffix(label)`: append/strip the `*` suffix that marks
    a tensor as complex-conjugated. (z*)* = z by toggling.

  * `conjugate(expr)`: recursively walks a SeQuant expression and
    conjugates Constants, toggles `*` on Tensors, and distributes over
    Products and Sums (Π zᵢ)* = Π zᵢ*  and  (Σ zᵢ)* = Σ zᵢ*.

  * `real_part(expr) = (expr + conjugate(expr)) / 2` and
    `imaginary_part(expr) = -i (expr - conjugate(expr)) / 2` —
    materialized as ordinary Sum/Product/Constant trees so any standard
    SeQuant evaluator can consume them; downstream callers can short-
    circuit the (T + T*) → 2 Re(T) collapse if their output is known
    to be real-scalar.

kramers_trace now applies the TRS rewrite:

  * Enumerate the 2^N Kramers configurations as before.
  * For each TRS pair (X, X̄), only build the lex-smaller X term;
    materialize the X̄ partner as `conjugate(T_X)` (using the X
    Kramers labels with `*`-suffixed tensor labels).
  * Sign analysis: T_X̄ = Π_i tensor_i_X̄ = Π_i ((-1)^{k_i} tensor_i*_X);
    for any all-internal-index contraction (every barred index appears in
    an even number of tensors) the (-1)^{Σ k_i} factor is +1, so
    T_X̄ ≡ conjugate(T_X) without an explicit sign.

Term count is unchanged (16 stays 16 for an MP2-shape input) but the
Sum now exposes the conjugate-pair structure symbolically, letting
evaluators short-circuit (one fetch + .conj()) and letting downstream
real-scalar callers collapse pairs to `2 Re(...)`.

The MVP enumeration path still falls out as a special case (when the
underlying expression has no Kramers::any indices, kramers_trace
returns it unchanged).
Replace the TRS-rewrite-only enumeration with the full open-shell
spin-trace pipeline applied to Kramers configs:

  1. Substitute K_up/K_dn for each index per the 2^N enumeration.
  2. expand_antisymm (skip_spinsymm=false — Kramers blocks have no
     orthogonality, so every permutation contributes).
  3. expand → flatten Products → rapid_simplify → canonicalize →
     rapid_simplify per term.
  4. Final cross-product expand + canonicalize so dummy renames fold
     equivalents across Kramers configurations.

The TRS pair rewrite via conjugate() is dropped from kramers_trace
because it created NonSymm tensors with `*`-suffixed labels that
canonicalize couldn't fold across Kramers configs (it cuts the term
count in half but blocks the deeper antisym fold). The conjugate /
real_part / imaginary_part scaffolding is kept as groundwork for the
upcoming quaternion decomposer (Phase 4) and as a building block for
caller-side `2 Re(...)` collapse in real-scalar consumers.

End-to-end on h2o-sq_mp2-x2c-631g.json: SQ_MP2 produces a 20-term Sum
(down from the 16-term raw enumeration after factoring in the antisym
expansion's 16x blowup). Energy preserved to 1 ULP. The 20-term
plateau reflects what canonicalize-via-dummy-rename can fold without
post-expansion antisym recognition or point-group symmetry filters
(which Visscher Eq. 28 relies on for its 2-term form). Going below
20 requires either a real-part collapse pass (caller-side) or
non-expanded antisym (which loses the open-shell-style structure).
@kshitij-05 kshitij-05 force-pushed the kshitij/feature/spinor_decompose branch from fce41f5 to 9d11e50 Compare May 14, 2026 16:15
Adds the symbolic machinery to take a closed-shell all-spinor expression
to a minimal mixed antisymm/non-antisymm Kramers-restricted form:

  - RealPart / ImagPart Expr nodes + conjugate / re / im helpers
  - flip_kramers + fold_conj_pairs: O(n) hash-bucketed TRS conjugate-pair
    fold, (A + A*) -> 2 Re(A)
  - antisymm_recombine: inverse of expand_antisymm; recombines
    direct/exchange NonSymm pairs into Kramers-restricted antisymm
    tensors, iterated to a fixed point
  - kramers_trace_cycles: cycle-driven tracer (contraction-graph cycle
    decomposition + multiset enumeration)
  - kramers_trace_burnside: orbit-representative enumeration under the
    antisymmetrizer index-permutation group, via bit-permutation orbits
  - cycle decomposition utilities (kramers_cycles, kramers_cycle_dump,
    cycle_canonical_*) for inspection and cross-validation

All three tracers feed the same fold_conj_pairs -> antisymm_recombine
pipeline and are value-equivalent; they coexist for cross-checking.
@kshitij-05 kshitij-05 force-pushed the kshitij/feature/spinor_decompose branch from 9d11e50 to 4355525 Compare May 14, 2026 17:49
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