ogdoad is a Rust research playground for Clifford algebras, quadratic forms,
and combinatorial-game arithmetic, with optional Python bindings. It is built
around one observation: the exotic number systems it implements — surreals,
nimbers, p-adics, Witt vectors, Laurent series — are not a grab bag. They are
cells of one table, and the same structures recur from cell to cell with the
characteristic and the place swapped. The code is organized to make those
symmetries visible.
The central constraint is mathematical, not architectural. Conway games under disjunctive sum form an abelian group, not a scalar ring — Conway multiplication is defined only on the number/nimber subclasses. A Clifford algebra needs a commutative scalar ring, so this project does not build Clifford algebras over all games. It builds a generic Clifford engine over the commutative scalar worlds adjacent to game theory, and a forms layer that classifies the result.
Every backend is a cell in a table with two axes:
- place — where the number lives (Archimedean, p-adic, finite,
transfinite), and whether it is a field or its ring of integers. This is how
src/scalar/is organized. - characteristic — which classification theory applies (char 0 / odd / 2).
This is how
src/forms/is organized.
The axes are independent; the two pillars are complementary readings of the same objects. The place axis pairs each field with its ring of integers:
| field | ring of integers | |
|---|---|---|
| Archimedean (char 0) | Rational Q |
Integer Z |
| transfinite | Surreal (No) |
Omnific (Oz) |
| p-adic (char 0) | Qp, Qq |
Zp, WittVec |
| function field (char p) | RationalFunction F_q(t) |
Poly F_q[t] |
| finite | Fp, Fpn, Nimber |
— |
The pairing is structural, not decorative: the HasFractionField /
HasRingOfIntegers trait pair makes Z⊂Q, Oz⊂No, Zp⊂Qp, W_N⊂Qq, and F_q[t]⊂F_q(t)
explicit in the type system (with Z[i]⊂Q[i] following for free via the surcomplex
transport). The rest of the local-field data is structural too — the valuation
and uniformizer (Valued), and the residue field k = O/m with angular
component and Teichmüller section (ResidueField) — so the whole package
(K, O, m, k, Γ, π) lives in the type system rather than the comments.
char 0 ↔ char 2. Classifying a quadratic form is one theory split by
char F. Over a real-closed field it is the 8-fold periodic Cl(p,q) table
(M_n(R/C/H)); in characteristic 2 the quadratic and polar forms part ways and
the same role is played by the Arf invariant and the Brauer–Wall group. On the
finite char-2 legs (Nimber, supported Fpn<2,N>, the documented finite ordinal
windows) a nonsingular form carries both the Arf classifier and the
BW(F_{2^m}) ≅ Z/2 class, under the same XOR law. The classifier façade picks
the leg from the scalar type at compile time, so metric.classify() /
.bw_class() are one call across every implemented leg.
surreal No ↔ ordinal On2. The surreals (a char-0 field) and the ordinal
nimbers (a char-2 non-field) are mirror images: both are Cantor-normal-form towers
over recursive exponents, sharing one canonicalizer. They differ in exactly three
places — the exponent order, the coefficient merge (+ vs XOR), and the zero
test — which is why the shared code is a function, not a type. No is where
infinite and infinitesimal Clifford metrics live; On2 is the proper-class char-2
field. The mirror reads out again at the games layer: NumberGame (a transfinite
surreal-valued game) and NimberGame (a transfinite Nim heap ⋆α carried by its
ordinal Grundy value) are the two views, one per characteristic.
the ×ばつ2 functor table. Orthogonal to the place table, there are four ways to grow a field, and all four corners are filled:
| residue-extending | value-extending | |
|---|---|---|
| algebraic | Surcomplex (adjoin i) |
Ramified (adjoin π = π^{1/e}) |
| transcendental | Gauss (adjoin a unit t) |
Laurent (adjoin a uniformizer t) |
Laurent over a finite field is the equal-characteristic mirror of Qp;
Ramified is the ramified twin of the unramified Qq. The finite separable
extensions among these carry a uniform relative trace/norm (FieldExtension):
the algebraic-closure functor Surcomplex, the finite tower Fpn/Fp, the
unramified Qq/Qp, and the nim-field Nimber/F_2 (= F_{2^128}) — one interface
for the norm map that feeds Hilbert symbols, the Brauer–Wall group, and Hermitian
forms. The cyclic-Galois refinement (CyclicGaloisExtension, adding a basis and
the generator σ) feeds the twisted trace form Tr_{E/F}(x·σ^k(x)), which
lands back in the classifiers — the binary norm form over Surcomplex, trace
forms over Qq and Fpn, and the Gold form Tr(x^{1+2^a}) over the
nim-fields, Arf-classified. The same Galois data also builds Frobenius linear maps
in clifford::frobenius, so the scalar trace maps and the Clifford outermorphism
spectra share one basis-level computation.
local ↔ global. The Springer decomposition appears across the complete valued
fields, and the value group controls the answer: over the surreals the value group
is 2-divisible (W(No) = W(R) = Z), but over Q_p, the unramified Q_q, and
F_q((t)) it is Z, so two residue layers survive (W(Q_p) = W(F_p)2). The
discretely-valued legs share one generic engine keyed on the ResidueField
trait; the surreal leg keeps its own, exactly because its value group is divisible
— that mismatch is the symmetry, not a gap. The adelic layer then glues the
local data: Hasse–Minkowski isotropy over Q and Hilbert reciprocity
∏_v (a,b)_v = +1. The same package recurs in equal characteristic over the
global function field F_q(t): the tame Hilbert symbol at each monic-irreducible
place plus the degree place ∞, reciprocity, and Hasse–Minkowski — and here it is
exact (no precision model), the char-p mirror of the Q stack. Both global
fields answer one interface: the GlobalField trait states the places, the
local Hilbert symbol, reciprocity, and Hasse–Minkowski once, with Q and F_q(t)
as its two implementors.
The integral leg carries its own local/global echo: even lattices produce
discriminant quadratic modules, Milgram Gauss-sum phases, and rational or mod-2
Clifford metrics, making the lattice signature, the real Brauer–Wall mod-8 cycle,
and the Clifford classifier directly comparable in the core. The same leg crosses
the code/theta boundary — binary codes feed Construction A lattices, exact theta
series are identified inside C[E4, E6], D16+ and E8 ⊕ E8 share the E42
theta series, Leech is pinned by rootlessness in weight 12, and discriminant forms
expose Weil S/T matrices with the Milgram phase recovered from the standard
conjugate S prefactor.
the games bridge. Red/blue/green Hackenbush is the one object that reads out
as a surreal (blue − red), a nimber (all-green = Nim), or a general partizan game
— and nim-multiplication itself is realized by Conway's Turning-Corners coin game.
This is the seam where the game pillar meets the scalar pillar. And thermography
itself is tropical arithmetic: the option folds are the tropical ⊕ and
cooling is the tropical ⊗, with the two scaffold walls living in the dual
(max,+)/(min,+) semirings — named in scalar/tropical.rs (a Semiring, not a
Scalar: an idempotent ⊕ has no inverse) and machine-checked equal to the golden
thermograph.
In characteristic 2 the quadratic form and its polar form carry different data. The engine stores them separately:
e_i^2 = q_i # the quadratic form
e_i e_j + e_j e_i = b_ij # the polar / anticommutator (alternating: b_ii = 0)
For nimbers -1 = 1, so an orthogonal basis with b = 0 gives a commutative
Clifford product; a nonzero off-diagonal b[(i,j)] is what makes a
characteristic-2 example noncommutative. Collapsing q and b into one symmetric
form would silently throw away the entire point of the nimber backend. (An optional
third field a lifts the engine to a general, non-symmetric bilinear form.)
On nonsingular metrics over the finite char-2 legs, the form layer also exposes the
Brauer–Wall class as the same Arf/Witt Z/2 datum: hyperbolic planes are zero, the
anisotropic plane has class one, and orthogonal sum / graded tensor adds by XOR.
The spinor module has a separate characteristic-2 representation path: it never uses
the char-0 1⁄2(1+w) idempotent, accepts nonsingular polar forms such as the
hyperbolic plane with null-square generators, takes blade idempotents like e_i e_j
when they shrink a left ideal, and otherwise falls back honestly to the complete
left-regular action.
Requires Rust and Python ≥ 3.9.
python3 -m venv .venv .venv/bin/pip install maturin VIRTUAL_ENV=.venv .venv/bin/maturin develop .venv/bin/python demo.py
import ogdoad as pl # characteristic-2 nimber Clifford: non-orthogonal => noncommutative A = pl.NimberAlgebra(q=[pl.Nimber(2), pl.Nimber(3)], b={(0, 1): 1}) e0, e1 = A.gen(0), A.gen(1) e0 * e1 + e1 * e0 # *1 (the anticommutator b[(0,1)]) # surreal metric: infinite and infinitesimal squares are exact S = pl.SurrealAlgebra(q=[pl.omega(), pl.epsilon()]) (S.gen(0) * S.gen(1)) ** 2 # -1 # the games bridge: Hackenbush reads out as a surreal OR a nimber B, G = pl.Color.blue(), pl.Color.green() pl.Hackenbush.string([B, B]).value() # a surreal number pl.Hackenbush.string([G, G]).grundy() # a nimber (all-green = Nim) # char 0 <-> char 2: a classification on each leg pl.classify_real(1, 3) # Cl(1,3) over R, the 8-fold table pl.arf_nimber(A) # the char-2 mirror invariant pl.bw_class_nimber(A) # the char-2 Brauer-Wall class, if nonsingular # local <-> global: Hasse-Minkowski + Hilbert reciprocity over Q pl.is_isotropic_q([1, 1, 1]) # False (anisotropic over Q) pl.hilbert_product((-1, 1), (-1, 1)) # +1 (reciprocity)
The Python surface is runtime-friendly parity: every backend that is a plain
runtime type is bound, while open-ended const-generic families (arbitrary
Qp<P,K>, Qq<P,N,F>, ...) stay Rust-only unless they get an explicit fixed
dispatch slice. See src/py/AGENTS.md for the full bound
surface and the binding-scope policy.
Run the Rust tour without Python:
cargo run --example tour
A pure Rust math core, generic over a Scalar trait, with PyO3 per-backend
bindings on top. Each src/ pillar has its own AGENTS.md with the file-by-file
breakdown:
src/scalar/— theScalartrait and every coefficient world, grouped by place.src/clifford/— the multivector engine, geometric product, and the GA layer (versors, outermorphisms, Hopf/divided-power structures, conformal/projective GA, spinors, Frobenius linear maps, including the characteristic-2 nimber spinors).src/forms/— the quadratic-form classifiers across the characteristic trichotomy, plus Witt/Brauer–Wall, the Springer trio,local_global/for Hasse–Minkowski and Hilbert symbols, andintegral/for lattices, genus, discriminant forms, Weil matrices, codes/Construction A, theta/modular forms,D16+, and Leech.src/games/— normal-, misère-, and loopy-play impartial games, short partizan games, thermography/atomic weight, Hackenbush, and the exterior algebra of the game group.src/py/— the optional PyO3 bindings behind thepythonfeature.src/linalg/— crate-private shared linear algebra (exact integer HNF/Smith, F2/nim-field rank, generic field solves), consumed by the pillars above.
See AGENTS.md for the working-notes summary, OPEN.md for the genuine research
problems, ROADMAP.md for the implemented and proposed cross-pillar bridges, and
writeups/goldarf.tex for the draft note on the Gold/Arf game thread.
The narrow mathematical thread in OPEN.md and writeups/goldarf.tex is not a
claim of a new Clifford classification theorem. It is an investigation of
game-built quadratic forms in the nimber backend:
- Turning-Corners games realize nim multiplication.
- Frobenius squaring and traces are built from nim multiplication and XOR.
- Gold-style trace forms
Tr(λ · x^{1+2^a})are therefore expressible from game-value operations. - The Arf invariant gives the standard zero-count bias for a quadratic zero set.
- The open question is whether a natural, non-tautological game rule has such a
zero set as its P-positions. Current probes span normal play, misère quotient,
interactive (
kernel), loopy (Draw-set), and bent-form searches; they narrow the target but do not solve it.
This is active research code with tests, examples, and experiments. Treat green
tests as regression evidence, not as a proof of the mathematical program. CI runs
cargo fmt --check, cargo clippy --all-targets (warning-clean), cargo test,
cargo check --features python, cargo check --examples, and cargo doc --no-deps
(intra-doc links kept warning-clean).
Scope boundaries worth stating plainly:
Nimber(u128)is exactlyF_{2^128}. It contains the nim subfields of degree dividing 128; it is not the proper-class field of all nimbers.Ordinalnim-addition is general on the represented CNF terms, and it implementsScalarfor Clifford experiments inside the checked Kummer boundary. Nim-multiplication is implemented belowω^(ω^ω)when every carry uses the verified excess table: DiMuro throughα_uforu ≤ 43, plus the locally certifiedα_47; a carry needing a prime past that table returnsNone.Surrealuses finite support and rational coefficients — the honest truncation of true CNF. Non-monomial inverses are infinite Hahn series and are not represented.Qp,Qq,Laurent,Ramified,Gauss, andAdeleare finite-precision (capped-relative) models, not exact infinite-memory local fields. They are useful for local/global form experiments and excluded from the exact-ring fuzz.ExactScalar/ExactFieldScalar/PrecisionScalarname that exact-vs-capped boundary explicitly. They are opt-in markers, notScalarsupertraits.- Fixed-width integer payloads are consistently
u128/i128for arithmetic carriers, residues, invariants, counts, and budgets.usizeis used for indices, dimensions, and platform ABI hooks. - The Gold/Arf game thread is conditional: if a game has P-set
{Q = 0}, Arf predicts the win-bias. No non-tautological natural game with that P-set has been found.
License: AGPL-3.0-or-later (see LICENSE).