multialgebra: the tensor power and its two quotients, as one construction - #130
Merged
Conversation
luiswirth
force-pushed
the
multialgebra-tensor
branch
from
July 29, 2026 13:19
9bd675a to
cec83ff
Compare
luiswirth
force-pushed
the
multialgebra-tensor
branch
from
July 29, 2026 13:34
cec83ff to
c62ae74
Compare
luiswirth
force-pushed
the
multialgebra-tensor
branch
from
July 29, 2026 14:35
c62ae74 to
802e9ff
Compare
…tion Λ^k and Sym^k are the two quotients of V^⊗k by a character of S_k. The characters factor through the abelianization, which is Z/2, so those two are the complete list rather than a pair someone picked, and the free power is the unquotiented object above them. All three live here as one construction, with a single `Symmetry` carrying the distinction and every operation written once over them. The representation follows the mathematics rather than being imposed on it. The two quotients share one: the shift w_i -> w_i + i takes a weakly increasing word to a strictly increasing one, so in shifted form a multiset is a set and one bitset serves both, with ranking, enumeration, deletion and the complement becoming the same bit operations. The free power has no symmetry to exploit and therefore no compression, and its index is a word ranked in the radix, carrying its alphabet because a positional rank needs one. That the free family costs more is its information content. `multiindex` is restructured to match. It had three representations for two families: `Combination` a u64 bitset, `Composition` a heap-allocated parts vector, and `MonoIndex` the shifted bitset that already represents both. The first two become newtypes over `MonoIndex`, which is what they always were, so they enforce the family without adding to the representation. A `Combination` then reaches 128 indices rather than 64, and a `Composition` stops allocating. The four index families are the classical square: `Combination` and `Composition` for order irrelevant without and with repetition, `Permutation` and `Word` for order relevant, and the first three are the bases of Λ^k, Sym^k and V^⊗k. A tensor is a list of slots. `Factor` is the functor, with no dimension, which is what lets `Factor::induced` describe both ends of a rectangular map from one value. `Slot` is that functor with the space it is over and the side it is built from, so symmetry, variance and dimension are all per slot. A `Tensor` is then its slots, its strides and its components: the shape a dense array has, with a symmetry and a variance per axis on top, and an all-free tensor is exactly that array. Variance being per slot is what makes a mixed tensor expressible, and with it the metric-free trace: tr on V* (x) V needs no inner product, and a uniform variance could only reach it by raising an index through g^-1. Dimension being per slot is what makes a rectangular map V* (x) W expressible, which previously had to live outside the algebra as a bare matrix. Variance is therefore data, and `Tensor` has no type parameter. It is the one datum with no representational footprint, since dim Λ^k(V) = dim Λ^k(V*), so nothing derives it and no shape check catches a wrong one: the operations check it and construction states it. Uniform variance is what buys functoriality along an arbitrary linear map, a mixed tensor transporting only along an isomorphism, and `pullback` refuses it rather than pretending. The `exterior` crate goes with the type parameter. `ExteriorElement` was a newtype whose invariant made `hodge_star` total, and with variance as data `MultiForm` cannot even be an alias, since `Tensor<Covariant>` would admit Sym^3. Its free functions and its laws move here. d and the Koszul operator are one operation, `transfer`, in its two directions. Binary contraction is `tensor` then one `trace` per pair, so multi-contraction is repeated tracing rather than a concept of its own, and matrix composition and the duality pairing are that operation run to different depths. Pairings are free functions: a pairing is a bilinear map on two spaces and privileges neither argument, which method syntax would deny. Adds the wedge pairing Λ^k x Λ^(n-k) -> RR, the duality the exterior algebra carries that is not the star: metric-free, needing only a top grade, and nondegenerate. The star is exactly what turns it into the inner product, <α, ⋆β>_∧ = <α, β> vol, and that is where the metric enters. `to_free` forgets each slot's symmetry, so Λ and Sym are visibly compressed representations of subspaces of the free power and the embedding is a map of representations rather than a serialization: a test says it commutes with pullback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017vrTsAZUQySdXrVuXizLts
Chain lived in simplicial and Cochain in derham, and the two never met. A cochain assigns an integral to each simplex, so summing it against a chain's coefficients integrates over the chain, and that pairing is what makes C^k the dual of C_k rather than a space of the same dimension. Stated as the law that carries the content, <d w, c> = <w, boundary c>, which is discrete Stokes and the reason the coboundary is the transpose of the boundary. Metric-free, orientation-free, geometry-free. Chain gained the boundary operator it lacked, exact over ZZ since the incidence coefficients are +-1. `l2_pairing` is the other one, and the test says they disagree. One needs the mass matrix and hence a geometry, the other only the incidence, so a refactor routing either through the other now fails rather than looking plausible. The nilpotency test needed a guard that one step does not already vanish, and the obvious guard is wrong: <d w, boundary c> = <d d w, c> is zero by the adjunction being tested, so it pairs against a plain chain a grade up instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017vrTsAZUQySdXrVuXizLts
luiswirth
force-pushed
the
multialgebra-tensor
branch
from
July 29, 2026 16:51
802e9ff to
bb1f33e
Compare
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.
Closes #129.
Two commits, each standing on its own and each passing fmt, clippy, test
and doc. Higher-order FEEC is built on this and is #131, stacked on this
branch so it can be reviewed separately.
Λ^k, Sym^k and V^⊗k as one construction
Λ^k and Sym^k are the two quotients of
V^⊗kby a character of S_k.A character lands in an abelian group, so it factors through the
abelianization, and S_k^ab is Z/2 for k ≥ 2 — those two are the
complete list, not a pair someone picked.
V^⊗kis the unquotientedobject above them, and all three are held here as one construction with a
single
Symmetrycarrying the distinction.The representation follows the mathematics rather than being imposed on
it. The two quotients share one: the shift
w_i ↦ w_i + itakes a weaklyincreasing word to a strictly increasing one, so in shifted form a
multiset is a set and one bitset serves both — ranking, enumeration,
deletion and the complement become the same bit operations. The free
power has no symmetry to exploit and therefore no compression: its index
is a word ranked in the radix, carrying its alphabet because a positional
rank needs one.
That the free family costs more is its information content, not a defect,
and the type tells you which you are paying for.
What stays outside is Schur functors:
R_ijkl = R_klijis cut out bya higher-dimensional irrep of S_k, not by a character, and its basis is
no longer a word. Three independent things agree on that boundary — the
character classification, the monotone-word representation, and the
alphabet-independent rank.
Shape
Factorstays dimension-free, which is what letsFactor::induceddescribe both ends of a rectangular map from one value.
Slotcarriessymmetry, variance and dimension, so a
Tensoris slots plus stridesplus components — the shape a dense array has, with a symmetry and a
variance per axis on top. An all-free tensor is that array.
Two things fall out that were previously unexpressible:
tronV* ⊗ Vneeds no inner product, butunder a uniform variance it was reachable only by raising an index
through
g⁻¹— a signature asking for more structure than the conceptneeds.
V → Was[Λ¹V*@n, Λ¹W@m], which previouslyhad to live outside the algebra as a bare matrix.
Variance is therefore data and
Tensorhas no type parameter. It is theone datum with no representational footprint —
dim Λᵏ(V) = dim Λᵏ(V*)— so nothing derives it and no shape check catches a wrong one: the
operations check it, construction states it. Uniform variance is what
buys functoriality along an arbitrary map; a mixed tensor transports only
along an isomorphism, and
pullbackrefuses it rather than pretending.CLAUDE.md's invariant 4 is rewritten accordingly, and says plainly that
variance was type-level and deliberately is not any more.
multiindexrestructured to matchIt held three representations for two families:
Combinationau64bitset,
Compositiona heap-allocated parts vector, andMonoIndexthe shifted bitset that already represents both. The first two are now
newtypes over
MonoIndex, which is what they always were — forbiddingrepetition makes the shift zero, so the stored word is the set.
Combinationreaches 128 indices rather than 64 (the cap that produced asilent mesh-size bug), and
Compositionstops allocating.The four families are the classical square, and the first three are the
bases of the three symmetry types:
Combination→ ΛᵏComposition→ SymᵏPermutation→ the Sₖ actionWord→ V^⊗kOperations
dand the Koszul operator are one operation,transfer, in its twodirections. Binary contraction is
tensorthen onetraceper pair, somulti-contraction is repeated tracing rather than a concept of its own,
and matrix composition and the duality pairing are that operation run to
different depths.
Pairings are free functions: a pairing is a bilinear map on two spaces
and privileges neither argument, which method syntax denies. Adds the
wedge pairing
Λᵏ × Λⁿ⁻ᵏ → ℝ, the duality the exterior algebracarries that is not the star — metric-free, needing only a top grade,
and nondegenerate. The star is exactly what turns it into the inner
product,
⟨α, ⋆β⟩_∧ = ⟨α, β⟩ vol, and that is where the metric enters.to_freeforgets each slot's symmetry, so Λ and Sym are visiblycompressed representations of subspaces of the free power. A test says it
commutes with pullback, which is what makes it a map of representations
rather than a serialization.
The
exteriorcrate goesExteriorElementwas a newtype whose invariant madehodge_startotal,and with variance as data
MultiFormcannot even be an alias, sinceTensor<Covariant>would admitSym³. Its free functions and its lawsmove into
multialgebra. The READMEs are updated with it: the root onelinked to
crates/exterior/README.md, now deleted.The discrete dualities
Chainlived insimplicialandCochaininderham, and the twonever met. The pairing that makes
C^kthe dual ofC_kis now there,stated as the law that carries the content:
discrete Stokes, and the reason the coboundary is the transpose of the
boundary.
l2_pairingis the metric one beside it, with a test assertingthe two disagree on a nontrivial input — one needs the mass matrix
and a geometry, the other only the incidence.
Performance
Measured, not assumed. The element-level algebra started 5–8× slower than
the standalone implementation; three causes found by measurement (an
allocating enumerator, basis-tuple materialization, a Kronecker
intermediate) brought it to 1.4–1.7×, and end to end assembly is +2% in
3D with a no-algebra control confirming the harness.
Adding the free family then regressed the monotone path 1.3–4×, which
was invisible until benchmarked. Cause: an enum as wide as its largest
variant, so every alternating basis element paid for a family it never
used. Packing
Wordas its own radix rank (a word is a positionalnumber) made both variants the same size and recovered most of it —
contraction is back to ~1.1×,
wedgesits at ~1.25×. A benchmark is nowcommitted so it cannot drift invisibly again.
What the tests caught
in this PR. A factor is a position in a shape, not the space it
currently denotes: transferring into a degree-zero symmetric slot must
give
Sym¹, notΛ¹. The Koszul homotopy formula caught it.contracted against a form, so it must be a vector. The new variance
check caught it — exactly the construction-site hazard the design
predicts.
contract/mergedivided by zero on a trivial-space factor, caught bythe Lie derivative test.
to_freemust accumulate, not assign. On a symmetric slot severalof the
k!orderings land on one entry and their sum is themultiplicity; the law
⟨free(a), free(b)⟩ = (∏ kᵢ!)⟨a, b⟩holds overboth families only under the right convention.
⟨dω, ∂c⟩ = ⟨ddω, c⟩is zero by the adjunction under test, so the guard disproved the
theorem.
Every law was checked to be falsifiable before being trusted.
Not done
componentsisf64; complex-valued forms arewhat time-harmonic problems need, and the
Vector<T = f64>aliasalready anticipates it.
contract_withmaterializes the tensor product rather than fusing.wedgeis ~1.25× slower than before the free family. Removing the lastenum branch means duplicated per-slot loops, which is not obviously
worth it at that margin.