Repository navigation
Structures
Status: two slices landed: orders and floors, then algebras (below). A head requires structure; a type provides it. Min works on anything
with a total order, Floor on anything whose order has ticks, Basis on anything that is a
finite-dimensional algebra -- including a type a user declares in a notebook, as long as it
says (and satisfies) what it is.
Today Min, Round, Clamp are compute-engine's numeric heads, widened to any so that
lists and our statistics' definitions get through (Manifest, "What tightening
taught us"). That says nothing about what they need. What they need is mathematical
structure, and each needs a precise, small amount of it:
| head | needs | why |
|---|---|---|
Min, Max, Sort, Ordering
|
a total order | pick or arrange by comparison |
Min, Max of a partial order |
a lattice |
Min is the meet: subsets under inclusion (intersection), partitions under dominance |
Min of nothing |
a bounded order | the empty meet is the top element |
Clamp(x, lo, hi) |
a lattice | Max(lo, Min(x, hi)) |
Floor, Ceil
|
ticks: an order with a Galois-connected discrete subset | below |
Round (half up, half down) |
ticks and midpoints | "nearest" needs the point halfway between two ticks |
Round (half to even, Wolfram's default) |
+ a parity on the ticks | which of two tied ticks is even |
Round (half away from zero), IntegerPart, FractionalPart
|
+ a zero | truncation goes toward it |
Round(x, a), Floor(x, a)
|
ticks that are multiples of a
|
a scaling action on the ticks |
Sign, Abs
|
an ordered additive commutative group | a zero and a negation; Abs(x) = Max(x, -x)
|
Basis, AlgebraDimension, Element, the ordered product |
a finite-dimensional algebra | what the algebra package's providers answer today |
Random |
something sampleable | what Random's sampler registry answers today |
Floor and Ceil are adjoints. Take an ordered set Floor is the right adjoint of Ceil the left:
They exist exactly when every element is bracketed by ticks and the ticks are locally finite
-- every region of a, whole days in a timestamp, and, in a product order, the Gaussian integers inside the
complex numbers, which is why Wolfram's Floor(2.5 + 3.7 i) is 2 + 3 i: the same
adjunction, componentwise. This is Mathlib's definition (FloorSemiring, FloorRing by a
Galois connection), minus its requirement that
Round needs more than order: "nearest" compares two gaps, which takes either a metric or a
midpoint between consecutive ticks; each tie-breaking rule then needs its own extra piece, as
in the table.
compute-engine already has what this needs (probed against 0.139):
-
declareProtocol(name, { functions: { Compare: "(Self, Self) -> integer" } })declares a protocol, and each member becomes a head that dispatches on its argument's type. -
declareProtocolImplementation(type, protocol, { functions: { Compare: … } })makes a type conform. The type can be built in (real-- andintegerthrough it), nominal (our carriers), or an application of a head (expression<Chain>). - A signature constrains a type variable:
(T, T) -> T where T is LinearOrder. A call with a type that doesn't conform is aprotocol-constraint-unsatisfiederror, not a silent fallthrough. - A protocol with no members is a marker: structure with nothing to compute (a tick parity that is just "the ticks are ℤ").
- In Epsil,
DeclareProtocolandDeclareConformanceare statements, so a user can make their own type conform from a notebook.
What it lacks: refinement. There is no way to say Lattice extends PartialOrder, so the
hierarchy states its refinements as data, and a conformance to a protocol declares its
parents' conformances too. (An upstream candidate.)
Mathlib's concepts and axioms, with its abbreviations spelled out (OrderedAdditiveCommutativeGroup,
not OrderedAddCommGroup); aliases later if wanted. Where we go past Mathlib, the row says
"extension": Mathlib is the spec we inherit proofs from, so every departure is marked.
| protocol | refines | members | laws (checked by Plausible) | Mathlib |
|---|---|---|---|---|
PartialOrder |
-- |
Compare (-1, 0, 1, or NaN if incomparable) |
reflexive, antisymmetric, transitive | PartialOrder |
LinearOrder |
PartialOrder |
-- | total: never incomparable | LinearOrder |
Lattice |
PartialOrder |
GreatestLowerBound, LeastUpperBound
|
greatest lower / least upper bounds | Lattice |
BoundedOrder |
PartialOrder |
Top, Bottom
|
extremal | BoundedOrder |
FloorOrder |
PartialOrder |
LowerTick, UpperTick
|
the Galois connections above | extension |
AffineMidpoint |
-- | Midpoint |
between, equidistant | extension (midpoint) |
MidpointOrder |
FloorOrder, AffineMidpoint
|
-- | ticks with midpoints between them | extension |
TickParity |
FloorOrder |
IsEvenTick |
alternates along consecutive ticks | extension |
Ring |
-- | (compute-engine's Add, Multiply, Negate) |
a ring | Ring |
FloorRing |
LinearOrder, Ring, FloorOrder
|
IntegerFloor, IntegerCeil
|
the Galois connections with ℤ | FloorRing |
ProductOrder |
PartialOrder |
Coordinates, WithCoordinates
|
the order and every operation are componentwise | extension (Prod) |
OrderedAdditiveCommutativeGroup |
PartialOrder |
(compute-engine's Add, Negate, 0) |
a group, order-compatible | OrderedAddCommGroup |
FiniteDimensionalAlgebra |
-- |
Basis, AlgebraDimension, HasElement
|
-- | FiniteDimensional |
Sampleable |
-- | Sample |
draws lie in the domain | -- |
A member becomes a head of its own, so its name must be free: a member named like an existing
head (Floor, Join, Infimum, LessEqual) is silently shadowed by it and never dispatches;
one named like a record another package keeps collides with it in the manifest (Components
is a FindStat endofunction statistic's stub record; hence Coordinates).
So the public heads -- Min, Max, Clamp, Floor, Ceil, Round, Sign -- stay
compute-engine's and take a plain number down the native path; anything else goes to the
members of whichever protocols its type conforms to. A product order (the complex numbers, and
vectors later) is the case a single Compare can't round through -- one component above the
midpoint and the other below -- so it has its own protocol, and every generic head works
componentwise over it.
Floors, after Mathlib and past it. Mathlib's FloorRing needs a ring: its floor is an
integer, and its round is the floor when 2 fract(x) < 1, else the ceiling, so ties go up.
We follow it exactly. Below it sits FloorOrder, our extension: ticks in any order, no ring,
so whole days or multiples of a step can floor too; a floor ring is a floor order whose ticks
are its integers. MidpointOrder and TickParity extend that, for rounding without a ring's
arithmetic and for Wolfram's half-to-even ties. Midpoint itself is affine, not an order notion
(Mathlib's midpoint, on an affine space where 2 is invertible), so it has a protocol of its own,
AffineMidpoint, which numbers, complex numbers and lists conform to: lists componentwise, so
vectors, matrices and Points have midpoints as in Wolfram (#481). Round compares against the midpoint when there
is one, and uses Mathlib's round otherwise.
A generic head's signature constrains its argument and its handler calls the members:
Min: (T+) -> T where T is LinearOrder, and Min folds Compare. Numbers keep
compute-engine's native path -- real conforms, and its implementation is the native
arithmetic -- so nothing numeric gets slower; the generic path is what a non-number takes.
That is also dispatch (Manifest, "Overloads across packages") for these heads: no
more wrapOperator chains where each package takes over Min for its own values. The package
declares its type's conformance; the head is written once.
- A protocol gets a record like a head's:
refines, members and their types, laws (as Epsil predicates), the Mathlib name. - A type's record gets
conformsTo: [LinearOrder, Lattice, …]. - A head's requirements are already in its type (
where T is LinearOrder).
So the manifest answers "which heads work on my type" and "what does my type need to add to
get Round" without loading code, and the census completeness test grows a clause: every
declared conformance is on the type's record.
A conformance is a claim: antisymmetry, the adjunction, a midpoint between its ticks. Plausible
(Plausible) samples every conforming type -- through Sampleable, which most of our
carriers are -- and checks each protocol's laws, so "any type with enough structure" is a
promise we can keep, including for a type declared in a notebook.
-
@enumeratio/structures, a new package: the protocols, their records, and the generic heads' handlers. -
algebrabecame theFiniteDimensionalAlgebraprotocol, and is gone (second slice, below). -
Random's sampler registry (@enumeratio/engine'srandom.ts) becomesSampleable. - The
Min/Round/Clampwidenings in collections go away, replaced by conformances.
The order and tick families -- Min, Max, Clamp, Floor, Ceil, Round -- generic,
over four kinds of type: real (native), strings (a linear order), the complex numbers (the
product order, so ticks are the Gaussian integers and Floor(2.5 + 3.7i) is 2 + 3i), and
integer partitions under dominance (a lattice that is not a total order: Min is the meet,
Max the join). The package's tests add a type declared in the test itself, with ticks and
a tick parity, and it gets Floor, Min and half-even Round with no other code.
How it runs:
- A generic head takes a real number, or anything with an unknown in it, down the native path.
Anything else goes to the protocol members, called through compute-engine's own dispatch, so
a conformance declared in Epsil (
DeclareConformance) answers the same as one declared in TypeScript. When no member answers, the native handler has the call as before. -
Roundties go to the even tick when the ticks have a parity, else up. Numbers keep compute-engine's half-away-from-zero; the other modes (andRound(x, a)) are still to come. - Collections' complex-rounding special case is gone: the complex numbers'
ProductOrderconformance covers it.
What it taught us:
-
Selfbinds to the first argument's exact type.Compare(5, 1/2)is anincompatible-typeerror:Selfbecomesinteger, and1/2isn't one. The generic heads never send numbers through members, so they don't hit it, but a member called by name does. The fix belongs upstream:Selfshould widen to the conforming type. -
No "does T conform to P" query. compute-engine dispatches, but doesn't expose whether a
type conforms. We call the member and treat
protocol-implementation-missingas no.
algebra and Sampleable follow.
@enumeratio/algebra was a provider registry. It is gone. Each algebra family mints the type
its algebras' names carry and conforms it to FiniteDimensionalAlgebra:
| type | names |
|---|---|
clifford_algebra |
CliffordAlgebra, its sibling constructors, Quaternions and the other named algebras |
diagram_algebra |
PartitionAlgebra, BrauerAlgebra, TemperleyLiebAlgebra, … |
group_algebra |
GroupAlgebra |
hecke_algebra |
HeckeAlgebra |
graded_hopf_algebra |
NSymAlgebra, QSymAlgebra
|
incidence_algebra |
IncidenceAlgebra |
path_algebra |
PathAlgebra |
Basis and AlgebraDimension dispatch on that type, and Element(x, A) asks HasElement
when A's type conforms. AlgebraSignature is a Clifford algebra's own, so it is
hypercomplex's head over clifford_algebra.
A type whose values name an algebra is a Sage parent more than a Mathlib typeclass: in
Mathlib the algebra is the type of its elements. The element-level structure -- the product as
a Ring -- waits until an algebra's elements have types to dispatch on. Until then the ordered
product (NonCommutativeMultiply, GeometricProduct, CircleTimes) stays a registry of
products, each declining what isn't its own, in @enumeratio/structures.
Since #417, @enumeratio/structures owns the generic carrier machinery that
domains used to hold:
-
declareCarriers(ce, carriers)mints each carrier's nominal type and held constructor. -
declareCarrierPluralsmints the plural type-space name (Permutations,GaussianIntegers). -
declareCarrierElementwiresElementover the carriers. -
attachConversionadds a converter as an overload of the target carrier's constructor. -
contentsOfreads a carrier value's contents. - The data type is
CarrierDeclaration, renamed fromDomain; a deprecatedDomainalias stays. - Around those sit the extend, representation (
Render/ParseAs), restriction andComposepieces.
Each owning package calls these with its own carrier data. Since #420,
declareCarriers also declares the plurals and Element by default (opt out with
{ plurals: false }), and every engine declares the plurals, the reference engine
included. Only combinatorics still declares them separately: a carrier's plural
(Permutations) can collide with the collection family of the same name, which
declareCollections mints right after. Where two packages would mint the same plural, a
registry settles it. For who owns which carrier, see Packages § Carrier ownership.
This is the layer compute-engine's protocols were built to carry, and they have no hierarchy
yet. Once the order, tick and algebraic protocols have settled here and we're happy with them,
they go to compute-engine as an issue (Upstreaming): the hierarchy, refinement as a
protocol feature, the Self widening and a conformance query. Nothing is proposed until then.