formoniq: FEEC at any polynomial degree - #131
Draft
luiswirth wants to merge 3 commits into
Draft
Conversation
luiswirth
force-pushed
the
multialgebra-tensor
branch
from
July 29, 2026 14:35
c62ae74 to
802e9ff
Compare
luiswirth
force-pushed
the
higher-order-feec
branch
from
July 29, 2026 14:35
b92e7ac to
fb7eb12
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
P^-_r Λ^k is Sym^r (x) Λ^k in barycentric coordinates, so the trimmed family needs no construction of its own: it is the tensor the algebra already builds, read at a degree. Barycentric coordinates are what make the coefficient factor homogeneous, since sum_i λ_i = 1 supplies the missing variable and dim P_r(RR^n) = dim Sym^r(RR^(n+1)), so no graded layer sits above it and a polynomial degree is one number exactly as a grade is. The two structural operators are one operation in opposite directions. d moves a degree from the coefficients to the blade, the Koszul operator moves it back, and dκ + κd = (r+k) id on the homogeneous pieces is what cuts out the trimmed spaces. Whitney forms fall out as k! κ(dλ_σ), so the familiar deletion formula is a consequence rather than a definition. The geometric decomposition is the direct sum over subsimplices, and it is what makes a global space out of the local ones. Two cells sharing a face give that face's degrees of freedom the same global index with no communication and no orientation convention, because a dof is named intrinsically to its attachment and a skeleton stores every simplex colex-sorted. Nothing here consults a cell ordering. Assembly goes through an explicit local-to-global map, so one routine serves every degree and the first-order case, where a dof is a k-simplex, is that map rather than a different mechanism. The mass matrix is integrated, not quadratured: the integrand is polynomial in the barycentric coordinates and their moments are the closed form n! α! / (|α| + n)!, so there is no rule to be exact to. `TrimmedComplex` implements the same `HilbertComplex` as the first-order one, so every problem written against that interface runs at any degree unchanged. `WhitneyComplex` is its r = 1 case and a test pins them together on real meshes: the same dof counts, mass matrices and exterior derivative. Eigenvalue convergence comes out at the predicted O(h^(2r)), rates 2, 4 and 6 for r = 1, 2, 3 across dimensions and grades, measured against the exact bottom of the spectrum rather than a Richardson extrapolation, since at r = 3 the error reaches roundoff within two refinements and a self-convergence estimate cannot tell that from a bug. 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
luiswirth
force-pushed
the
higher-order-feec
branch
from
July 29, 2026 16:51
fb7eb12 to
62c438f
Compare
Owner
Author
|
Parking this. #130 is in, and the next work is architectural: Both touch the layers this builds on, so rebasing this first would mean doing it twice. Revisiting once the ladder settles. The work is complete and green as it stands; nothing here is in doubt. |
luiswirth
marked this pull request as draft
July 29, 2026 18:47
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.
Stacked on #130, which it needs for the tensor layer. Review that one
first; the base will retarget to
mainonce it merges.One commit, passing fmt, clippy, test and doc.
What it is
P⁻_r Λ^kisSym^r ⊗ Λ^kin barycentric coordinates, so the trimmedfamily needs no construction of its own: it is the tensor the algebra
already builds, read at a degree.
Barycentric coordinates are what make the coefficient factor homogeneous.
Σ λ_i = 1supplies the missing variable, sodim P_r(ℝ^n) = binom(n+r, n) = dim Sym^r(ℝ^(n+1)), no graded layer sitsabove a homogeneous factor, and a polynomial degree is one number exactly
as a grade is.
The two structural operators are one operation in opposite directions.
dmoves a degree from the coefficients to the blade, the Koszuloperator moves it back, and
dκ + κd = (r+k) idon the homogeneouspieces is what cuts out the trimmed spaces. Whitney forms fall out as
k! κ(dλ_σ), so the familiar deletion formulaW_σ = k! Σ_i (-1)^i λ_{σ_i} dλ_{σ∖σ_i}is a consequence of what κ doesto a blade rather than a definition.
The geometric decomposition
P⁻_r Λ^k(K) = ⊕_{f ⊆ K} int_f, a direct sum over every subsimplex, eachsummand the piece whose support is all of
fand no smaller face. Thatis what makes the global space a subspace of
HΛ^krather than a directsum of unrelated cell spaces.
Two cells sharing a face give that face's degrees of freedom the same
global index with no communication and no orientation convention: a dof
is named by data intrinsic to
f(a subsimplex offand a monomial inf's own barycentric coordinates, both in positions withinf), and askeleton stores every simplex colex-sorted, so both cells see the same
order. Nothing here consults a
CellOrdering.Assembly
Through an explicit local-to-global map, so one routine serves every
degree and the first-order case, where a dof is a k-simplex, is that map
rather than a different mechanism.
The mass matrix is integrated, not quadratured. The integrand is
polynomial in the barycentric coordinates and their moments are the
closed form
n! α! / (|α| + n)!, so there is no rule to be exact to andno order to check.
HodgeMassElmatis this atr = 1.TrimmedCompleximplements the sameHilbertComplex, so every problemwritten against that interface — source, eigenvalue, heat, wave, Dirac —
runs at any degree with no solver touched.
tests/higher_order.rsexercises all five at
r = 2, each against a law rather than a storednumber.
Convergence
Hodge-Laplace eigenvalues on
[0, π]^nunder the absolute boundarycondition, against the exact bottom of the spectrum:
Rates come out at 2, 4 and 6 across dimensions and grades, matching the
predicted
O(h^(2r)).Compared against the exact eigenvalue rather than a Richardson
extrapolation on purpose: at
r = 3the error reaches roundoff withintwo refinements, and a self-convergence estimate cannot tell that from a
bug. Getting the exact value right needed deriving it: the Hodge
Laplacian on the box splits into scalar Laplacians with a Dirichlet
factor on each direction in
Iand a Neumann factor on each outside it,so the smallest nonzero eigenvalue is
max(k, 1)— not the specificeigenform's
λ = n, which the first run wrongly compared against.Assembly costs 2.1 ms, 13 ms and 77 ms for r = 1, 2, 3 on a fixed 3D
mesh; space setup 193 µs, 559 µs and 1.22 ms.
Ties to trusted code
The
r = 1case is identical, not merely close, and three tests say so:the trimmed basis is pointwise the Whitney basis, the reference
disexactly the simplicial coboundary with entries in {−1, 0, 1}, and the
assembled dof counts, mass matrices and differential equal the Whitney
complex's on real meshes.
Ported to the new tensor layer
PolyFormisSym^r ⊗ Λᵏover the barycentricℝⁿ⁺¹, which is nowstated in the slots rather than in a tensor-wide dimension: both slots
carry
cell_dim + 1explicitly. That the coefficient and blade factorslive over the same enlarged space, and that
at_baryleaves it bypulling back along
unit_difbarys, is visible in the types.What the tests caught
dwas scattered wrongly. It filled each column froma single cell, but a basis function's derivative is supported on all
cells containing its dof, so the scatter now averages over the
multiplicity. Caught by the r=1-equals-Whitney test.
Simplexthrough a 64-bitCombinationof global vertex indices.Caught by the p-convergence run, not by a test — no small-mesh test can
reach it.
Q_r ⊗ Hwas Kroneckered by hand under a comment asserting theorder matched the tensor's strides, with nothing enforcing it.
Reversing it yields a symmetric positive definite matrix of the right
shape and the wrong meaning, so no shape check catches it. Now one
function and one test.
Not done
A higher-order L² error against an analytic solution needs an interpolant
into
P⁻_r Λ^k. The convergence study uses eigenvalues, which need none,but a source-problem convergence table would want it.