Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/book-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Deploy mdBook

on:
push:
branches: [main]
paths:
- "docs/book/**"
- ".github/workflows/book-pages.yml"
pull_request:
paths:
- "docs/book/**"
- ".github/workflows/book-pages.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: book-pages-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
# Body owned once by Atlas (ADR 0035); the caller passes only the
# built output directory. Enable Pages once per repository under
# Settings -> Pages -> Source: GitHub Actions.
uses: ryancinsight/atlas/.github/workflows/book-pages.yml@2d071b077d12357c158c1a2eb4854d540b7d6833
permissions:
contents: read
pages: write
id-token: write # OIDC Pages artifact flow; no deploy key.
with:
output-path: target/book/eunomia
54 changes: 54 additions & 0 deletions docs/book/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# eunomia: The Datatype Law of Atlas

Eunomia is the **datatype law** of the Atlas stack: the single source of
truth for the numeric and scalar datatype vocabulary every other Atlas crate
computes over. It owns the representations — and nothing else.

In myth, Eunomia ("good order") is the daughter of Themis, the *placement
law* of the stack ([`themis`](https://github.com/ryancinsight/themis)
owns NUMA/tier/worker locality). Where themis decides *where* data lives,
eunomia decides *what* data is.

## What eunomia owns

- **Scalar wrapper types** — `F16`, `Bf16`, `F32`, `F64`, `I8`/`I16`/`I32`,
and sub-byte `F4`/`F8`/`Bf4`/`Bf8` — with exact byte-layout guarantees and
one native conversion kernel providing exact widening and
round-to-nearest-ties-to-even narrowing across every reduced format.
- **`Complex<T>`** — the native `re + im·i` vocabulary type that replaces the
third-party `num_complex::Complex` across the stack.
- **Packed sub-byte formats** — `Packed4`/`PackedBf4`/`PackedF4` storage,
COW buffers, rkyv archival, and SIMD-accelerated unpack.
- **Conversion lattices** — `CastFrom`/`CastTo`.
- **Element traits** — `NumericElement`, `FloatElement`.
- **Scalar field traits** — `RealField`/`ComplexField` (the `nalgebra`
scalar-field analogues), so generic numeric code runs over `f32`/`f64` and
`Complex` without pulling in nalgebra.

## What it does not own

No computation kernels, allocation, scheduling, or backend code. No
vector/matrix/geometry types — those live in `leto` (CPU arrays) and
`hephaestus` (GPU). Execution, SIMD, and allocation belong to `hermes`,
`mnemosyne`, and `moirai`.
Comment on lines +16 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Resolve the ownership-boundary contradiction.

The ownership list includes a conversion kernel, COW buffers, and SIMD-accelerated unpack. The next section excludes computation kernels, allocation, and SIMD. These statements describe incompatible ownership boundaries. Narrow the exclusion to general-purpose execution, or state the datatype-local exceptions explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/book/README.md` around lines 16 - 33, Update the “What it does not own”
section to remove the contradiction with the documented conversion kernel, COW
buffers, and SIMD-accelerated unpack. Narrow the exclusions to general-purpose
computation, allocation, scheduling, backend, and execution concerns, while
explicitly preserving the datatype-local storage and conversion exceptions
already listed above.


## How to read this book

The book teaches the numeric foundation from first principles:

1. **Part I** — the scalar vocabulary: what each IEEE-754 format means, and
when to choose it;
2. **Part II** — the element and field traits that let generic code run over
any scalar;
3. **Part III** — conversion and casting: how values move between precisions
without silent precision loss;
4. **Part IV** — byte layout and packed formats: the reinterpretation
contract that GPU/FFI boundaries rely on;
5. **Part V** — numeric semantics: relative equality and element operations;
6. **Part VI** — where the crate sits in the Atlas stack and why the
dependency direction points inward.

Each chapter maps the theory onto the crate's public API, and worked examples
in `examples/` show the abstractions in use. This is the outline edition: the
chapter structure is complete, and the chapters themselves land as DoR items
per subsystem.
Comment on lines +51 to +54

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Describe the example files according to their current status.

This paragraph calls the files in examples/ worked examples, but the PR adds outline chapters. Replace “worked examples” with “planned example outlines” until those chapters contain worked code or results.

Proposed wording
- in `examples/` show the abstractions in use.
+ in `examples/` outline the planned abstractions and use cases.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Each chapter maps the theory onto the crate's public API, and worked examples
in `examples/` show the abstractions in use. This is the outline edition: the
chapter structure is complete, and the chapters themselves land as DoR items
per subsystem.
Each chapter maps the theory onto the crate's public API, and planned example outlines
in `examples/` outline the planned abstractions and use cases. This is the outline edition: the
chapter structure is complete, and the chapters themselves land as DoR items
per subsystem.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/book/README.md` around lines 51 - 54, Update the documentation paragraph
around the `examples/` reference to describe them as “planned example outlines”
rather than “worked examples,” while preserving the surrounding explanation and
outline-edition status.

38 changes: 38 additions & 0 deletions docs/book/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Summary

[Introduction](README.md)

# Part I — The Scalar Vocabulary

- [1. Floating-Point Scalar Types](scalar_types.md)
- [Example: Choosing a Precision](examples/choosing_precision.md)
- [2. Integer Scalar Types](integer_types.md)
- [3. Complex Numbers](complex_numbers.md)
- [Example: Complex Arithmetic in a Solver](examples/complex_arithmetic.md)

# Part II — Element Traits

- [4. NumericElement: The Monomorphization Extension Point](numeric_element.md)
- [5. FloatElement: The Transcendental Surface](float_element.md)
- [6. Scalar Fields: RealField and ComplexField](scalar_fields.md)
- [7. UnitScalar: The Physical-Unit Seam](unit_scalar.md)

# Part III — Conversion and Casting

- [8. The Cast Lattice: CastFrom and CastTo](cast_lattice.md)
- [9. The Native Conversion Kernel](conversion_kernel.md)
- [Example: Rounding Behaviour](examples/rounding_behaviour.md)

# Part IV — Layout and Packing

- [10. Byte Layout: Pod and Zeroable](byte_layout.md)
- [11. Packed Sub-byte Formats](packed_formats.md)

# Part V — Numeric Semantics

- [12. Relative Equality](relative_equality.md)
- [13. Element Operations](element_operations.md)

# Part VI — The Crate in the Stack

- [14. Position in the Atlas Stack](stack_position.md)
22 changes: 22 additions & 0 deletions docs/book/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# mdBook configuration for the eunomia datatype-law book.
# Build: mdbook build docs/book Serve: mdbook serve docs/book
# The chapter Markdown lives alongside this file (src = "."); the table of
# contents and part structure are defined in SUMMARY.md.
[book]
title = "eunomia: The Datatype Law of Atlas"
authors = ["Ryan Clanton"]
description = "The numeric and scalar vocabulary of the Atlas stack — IEEE-754 scalar wrappers, Complex<T>, packed sub-byte formats, element and field traits, and the native conversion kernel."
src = "."
language = "en"

[build]
build-dir = "../../target/book/eunomia"
create-missing = false

[output.html]
no-section-label = false
mathjax-support = true
Comment on lines +16 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files relevant to mdBook:"
git ls-files | rg '(^|/)book\.toml$|(^|/)SUMMARY\.md$|docs/book/' | sed -n '1,120p'

echo
echo "docs/book/book.toml:"
if [ -f docs/book/book.toml ]; then
  cat -n docs/book/book.toml
fi

echo
echo "docs/book/SUMMARY.md (first 180 lines):"
if [ -f docs/book/SUMMARY.md ]; then
  sed -n '1,180p' docs/book/SUMMARY.md | cat -n
fi

echo
echo "Search no-section-label usages:"
rg -n 'no-section-label|mathjax-support|site-url' .

Repository: ryancinsight/eunomia

Length of output: 3565


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Available mdbook version/tooling:"
command -v mdbook || true
mdbook --version 2>/dev/null || true

echo
echo "Check for local mdbook/docs tooling indicators:"
git ls-files | rg '(^|/)(cargo\.lock|package\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb|\.github/workflows/.*)' | sed -n '1,120p'

Repository: ryancinsight/eunomia

Length of output: 309


🌐 Web query:

mdBook output.html no-section-label true disable section labels

💡 Result:

To disable numeric section labels in your mdBook output, you should add the following configuration to your book.toml file under the [output.html] table [1][2]: [output.html] no-section-label = true Setting no-section-label to true removes numeric labels (such as "1." or "2.1") from the table of contents [1][3]. By default, this option is false [1][2]. Note: Development in the mdBook repository includes plans to rename this configuration key to section-label-toc with a inverted default behavior (defaulting to true) to improve clarity [4]. However, as of the current standard documentation, no-section-label = true remains the correct directive to disable these labels [1][2][5].

Citations:


Disable automatic section labels or remove the manual numbers.

docs/book/SUMMARY.md uses numbered chapter titles like 1. Floating-Point ScalarTypes, and no-section-label = false enables mdBook’s numeric section labels in the table of contents. This creates duplicate numbering. Set no-section-label = true if the manual numbers are intentional.

Proposed fix. ```diff -no-section-label = false +no-section-label = true ```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[output.html]
no-section-label = false
mathjax-support = true
[output.html]
no-section-label = true
mathjax-support = true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/book/book.toml` around lines 16 - 18, Update the [output.html]
configuration in book.toml to set no-section-label = true, preserving the
manually numbered chapter titles in docs/book/SUMMARY.md without duplicate
automatic labels.

site-url = "/eunomia/"
git-repository-url = "https://github.com/ryancinsight/eunomia"
edit-url-template = "https://github.com/ryancinsight/eunomia/edit/main/docs/book/{path}"
default-theme = "rust"
50 changes: 50 additions & 0 deletions docs/book/byte_layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 10. Byte Layout: Pod and Zeroable

## Governing equations

Reinterpreting a value as raw bytes is safe only when the type's layout
permits it. Two facts must hold:

1. **Zeroable** — the all-zero bit pattern is a valid, inhabited value
(this excludes types with a validity niche at zero, such as `NonZeroU32`
or `&T`).
2. **Pod** (plain-old-data) — every bit pattern of `size_of::<Self>()`
bytes is a valid `Self`, and the type carries no padding or invalid
representations.
Comment on lines +5 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Separate the Pod and Zeroable safety contracts.

bytes_of accepts only T: Pod in crates/eunomia/src/layout/bytes.rs:36-40; Zeroable is not required to view an existing value as bytes. Zeroable is required for all-zero construction through Zeroable::zeroed. State these as separate requirements, and require both only for APIs that perform both operations.

Proposed wording
-Reinterpreting a value as raw bytes is safe only when the type's layout
-permits it. Two facts must hold:
+Viewing an existing value as raw bytes requires a `Pod` layout.
+Creating a value from all-zero bytes requires `Zeroable`. APIs that perform
+both operations require both contracts.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/book/byte_layout.md` around lines 5 - 13, Update the “Zeroable” and
“Pod” section in docs/book/byte_layout.md to separate their safety contracts:
require only Pod when viewing an existing value as bytes via bytes_of, require
Zeroable for all-zero construction via Zeroable::zeroed, and state that APIs
performing both operations require both traits.


These layout facts are the datatype-law statement of *which representations
are safe to reinterpret as bytes* — the contract GPU device buffers, FFI
boundaries, and serialization all rely on.

## The crate's abstraction

Eunomia owns the native `Zeroable`/`Pod` vocabulary rather than borrowing it
from `bytemuck`:

```rust,ignore
pub unsafe trait Zeroable: Sized {
fn zeroed() -> Self { /* SAFETY: Self: Zeroable */ }
}

pub unsafe trait Pod: Sized {
// any bit pattern of size_of::<Self>() bytes is a valid Self
}
```

- **`unsafe` marker traits.** The compiler cannot verify the layout facts
they assert; every impl carries a `// SAFETY:` justification, and the
scalar wrappers' `const _` size/alignment assertions pin the layout the
impls rely on.
- **`bytemuck` bridge.** The `bytemuck` feature bridges these to
`bytemuck::{Pod, Zeroable}` for GPU/FFI boundaries that fix that contract.
- **Whole-vocabulary coverage.** `F16`/`Bf16`/`F32`/`F64`/`F4`/`F8`/`Bf4`/
`Bf8`/`I8`/`I16`/`I32` and `Complex<T>` (when `T` is) are `Pod`/`Zeroable`
— every value is a valid byte string.

## Outline of this chapter

- Why layout facts gate byte reinterpretation
- `Zeroable` and the zero-niche exclusion
- `Pod`: no padding, no invalid bit patterns
- The `// SAFETY:` discipline and the `const _` layout pins
- The `bytemuck` bridge for GPU/FFI boundaries
48 changes: 48 additions & 0 deletions docs/book/cast_lattice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 8. The Cast Lattice: CastFrom and CastTo

## Governing equations

Converting a value from one numeric representation to another is a *cast*.
Rust's `as` operator provides the primitive semantics: float-to-integer
conversions truncate toward zero and saturate values outside the destination
range. The lattice problem is to express "cast any scalar to any other
scalar" generically, so a kernel can accept an element of one precision and
deliver an element of another without naming every pair.

## The crate's abstraction

`CastFrom`/`CastTo` form the generic casting lattice:

```rust,ignore
pub trait CastFrom<T>: Copy {
fn cast_from(val: T) -> Self;
}

pub trait CastTo: Copy {
fn cast_to<U>(self) -> U
where
U: CastFrom<Self>,
{
U::cast_from(self)
}
}
```

- **Primitive semantics preserved.** Primitive numeric implementations
follow Rust's `as` conversion semantics — truncation toward zero for
float-to-integer, saturation outside the destination range.
- **One direction, both spellings.** `CastFrom` is the primitive direction;
`CastTo` is the blanket-enabled reverse spelling (`T::cast_from(self)`)
so callers can write `x.cast_to::<F32>()` or `F32::cast_from(x)`.
- **Lattice, not lossless promise.** A cast may lose precision — the lattice
says *how*, deterministically, not that it cannot lose. Precision-correct
conversions between float formats go through the native kernel (§9).

## Outline of this chapter

- The cast lattice: every scalar to every scalar, generically
- `as` semantics: truncation and saturation on the primitive boundaries
- `CastFrom` as the primitive direction, `CastTo` as the reverse spelling
- Lattice vs lossless conversion — when to use `cast_from` vs the native
kernel
- Generic kernels converting between precisions at their boundaries
50 changes: 50 additions & 0 deletions docs/book/complex_numbers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 3. Complex Numbers

## Governing equations

A complex number is the pair

$$z = \text{re} + \text{im}\cdot i,$$

with arithmetic defined field-wise for `Add`/`Sub`/`Neg` and by the complex
product and quotient for `Mul`/`Div`:

$$(a + bi)(c + di) = (ac - bd) + (ad + bc)i.$$

Complex values appear throughout Atlas where real arithmetic is insufficient
— phasors in acoustics, spectra in optics, and the eigen/signal surfaces in
linear algebra.

## The crate's abstraction

`Complex<T>` is the SSOT vocabulary type for `re + im·i`, replacing the
third-party `num_complex::Complex` across the stack:

```rust,ignore
#[repr(C)]
pub struct Complex<T> {
pub re: T,
pub im: T,
}

pub type Complex32 = Complex<f32>;
pub type Complex64 = Complex<f64>;
```

- **Layout-compatible.** `#[repr(C)]` `{ re, im }` with `bytemuck::Pod`/
`Zeroable` when `T` is, so values round-trip through GPU device buffers and
FFI boundaries identically to `num_complex::Complex`.
- **Field-wise semantics.** The imaginary component is *quadrature*, not a
second physical unit — the `UnitScalar` seam (§7) scales complex values
componentwise by a real coefficient.
- **Floating-point surface.** The complex module carries the arithmetic
(`ops`), constants (`consts`), the float surface (`float`), and reduction
helpers (`reduce`).

## Outline of this chapter

- Complex field arithmetic: `Add`/`Sub`/`Neg`/`Mul`/`Div`
- `#[repr(C)]` layout and the FFI/GPU round-trip guarantee
- `Complex32`/`Complex64` aliases and interop with the numeric stack
- The quadrature rule: one observable unit, no imaginary SI unit
- Worked example in [Complex Arithmetic in a Solver](examples/complex_arithmetic.md)
50 changes: 50 additions & 0 deletions docs/book/conversion_kernel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 9. The Native Conversion Kernel

## Governing equations

Converting between IEEE-754 formats of different widths is the operation
that decides whether a reduced-precision pipeline loses accuracy silently.
The correct rounding rule is round-to-nearest, ties-to-even (RNE): a real
value is mapped to the nearest representable value, and exact ties map to
the neighbour with an even significand. This bounds the conversion error at
half an ulp of the destination format — the smallest achievable.

## The crate's abstraction

Eunomia's conversion module is the native soft-float conversion SSOT — one
generic const-parameterized kernel converts between `f32` and any reduced
IEEE-754 binary format `(E, M)`:

```rust,ignore
use eunomia::convert::{narrow, widen};

// binary16 round-trips 1.0 exactly.
let one_f16 = narrow::<5, 10>(1.0f32.to_bits());
assert_eq!(one_f16, 0x3C00);
assert_eq!(f32::from_bits(widen::<5, 10>(one_f16)), 1.0);
```

| Format | `E` | `M` | Bias |
| --- | --- | --- | --- |
| `binary16` (`F16`) | 5 | 10 | 15 |
| `bfloat16` (`Bf16`) | 8 | 7 | 127 |

- **One implementation, all precisions.** `narrow::<E, M>` / `widen::<E, M>`
are const-parameterized over the format geometry, so every precision shares
one authoritative, monomorphized implementation — including the sub-byte
formats' conversions.
- **RNE guaranteed.** Round-to-nearest, ties-to-even is baked into the
kernel; finite-variant kernels (`narrow_finite`/`widen_finite`) are
provided where the format has no infinity/NaN encoding.
- **Replacement for `half`.** This kernel replaces `half`'s `f16`/`bf16`
conversions; `half` remains only as a dev-only differential oracle in
eunomia's own conversion tests.

## Outline of this chapter

- Round-to-nearest-ties-to-even and the half-ulp error bound
- Format geometry as const parameters `(E, M)`
- `narrow`/`widen` and the finite variants
- Why one kernel replaces the `half` crate (and how `half` survives as a
test oracle)
- Worked example in [Rounding Behaviour](examples/rounding_behaviour.md)
34 changes: 34 additions & 0 deletions docs/book/element_operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 13. Element Operations

## Governing equations

Element operations are the pointwise arithmetic of the scalar vocabulary —
the `Add`/`Sub`/`Mul`/`Div` (and their `Assign` forms) that kernels assume
of any element. For floats these are IEEE-754 operations with their rounding
rules; for integers, two's-complement wrap arithmetic; for complex values,
the field operations of §3.

## The crate's abstraction

The `ops` module provides the operation impls across the vocabulary:

- `ops::floats` — arithmetic for the float wrappers (`F16`/`Bf16`/`F32`/
`F64` and the sub-byte formats), with float-semantic ordering.
- `ops::ints` — arithmetic for `I8`/`I16`/`I32`, with exact wrap semantics.

Together with the operator supertraits on `NumericElement` (§4), this gives
generic kernels a complete, uniform arithmetic surface:

```rust,ignore
fn l2_norm_sq<T: NumericElement>(x: T, y: T) -> T {
x * x + y * y // Add, Mul, and AddAssign are assumed by the trait
}
```

## Outline of this chapter

- The element arithmetic surface: `Add`/`Sub`/`Mul`/`Div`/`Assign`
- Float operations and their rounding rules
- Integer wrap arithmetic and why it is exact
- Complex field operations
- Building kernels over the operator supertraits of `NumericElement`
Loading
Loading