diff --git a/README.md b/README.md index a5ce8c1..0859140 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A zero-dependency Rust library for physics, mathematics and engineering computation. -**[rust-physics-engine.mimsec.com](https://rust-physics-engine.mimsec.com)** — the +**[rust-physics-engine.mimsec.com](https://rust-physics-engine.mimsec.com)** has the guide, a searchable index of all 71 modules, and the full API documentation built from the current commit. @@ -982,7 +982,7 @@ python3 -m http.server --directory _site ``` That builds everything except `/api`, which is `cargo doc --no-deps` output -copied to `_site/api` — the workflow does it in one more step. +copied to `_site/api`. The workflow does it in one more step. ## License diff --git a/site/404.html b/site/404.html index 29510ef..c0aab8d 100644 --- a/site/404.html +++ b/site/404.html @@ -3,7 +3,7 @@ -Not found — rust_physics_engine +Not found · rust_physics_engine diff --git a/site/index.html b/site/index.html index bc49061..a9f3e44 100644 --- a/site/index.html +++ b/site/index.html @@ -3,11 +3,11 @@ -rust_physics_engine — checkable physics, mathematics and engineering in Rust - +rust_physics_engine + - + @@ -49,15 +49,17 @@
-

Zero dependencies · Rust 2021 · MIT

-

Physics and mathematics that
check their own answers

+

Zero dependencies · Rust 2021 · MIT

+

A zero-dependency Rust library for physics, + mathematics and engineering computation

- 6,365 public functions and methods across - 71 modules, and not one of them ships on - the strength of having run without panicking. Every routine here is written so - that something about it can be checked — against a closed form, a - conservation law, an independent implementation, or an exact identity over - integers. + The aim is not breadth for its own sake. Every routine here is written so that + something about it can be checked: against a closed form, against a + conservation law, against an independent implementation of the same quantity, or + against an exact identity over integers. A test that only asserts a function ran + is not evidence, and the test suite is built around that distinction. Where a + result is approximate the error has a stated bound; where it is exact the + assertion uses ==.

@@ -68,80 +70,53 @@

Physics and mathematics that
check their own answers
- 6,365public functions & methods + 6,365public functions and methods
71top-level modules
-
4,770tests passing
+
4,193unit tests
97.89%line coverage
-
20Kani proof harnesses
+
20Kani harnesses
0dependencies

- +
-

A test that only asserts a function ran is not evidence

-

- The aim is not breadth for its own sake. Breadth is what you get when the - thing you actually insist on — that every result has something to be - measured against — turns out to be possible in one field after another. -

+

At a glance

-
-
-

Stated error bounds

-

- Where a result is approximate the tolerance comes from the method's own - error term, not from a number tuned until the test went green. Where it is - exact the assertion uses ==. -

-
-
-

Failure you can tell apart

-

- Solvers return Result rather than panicking on - non-convergence, so a caller can distinguish “did not converge” from - “converged, to this”. -

-
-
-

NaN is rejected, not ignored

-

- Argument guards are written !(x > 0.0) rather than - x <= 0.0, so a NaN fails the check instead of sliding - through it. -

-
-
-

One table per constant

-

- Physical constants come from math::constants; anything - elsewhere is a re-export, and a test enforces the agreement. The values - fixed by the 2019 SI redefinition are exact. -

-
-
-

Dimensions in the type

-

- units::quantity carries dimensions alongside the number, so - adding a velocity to a time is a compile-time-shaped error rather than a - plausible-looking float. -

-
-
-

Exactness where it matters

-

- f64 throughout, except where it would be wrong to be: - exact works over arbitrary-precision integers and rationals, - and dimensional null spaces are computed over Rational. -

-
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Public functions and methods6,365 (4,124 free functions, 2,241 methods)
Public types431 structs, enums and traits
Top-level modules71 public, across 296 source files (see the + module map)
Source266,122 lines of Rust
Unit tests4,193
Property tests577, across 49 files
Line coverage97.89% (174,685 lines, 3,681 uncovered)
Function coverage99.33% (20,200 functions, 136 uncovered)
Formal verification20 Kani harnesses (13 in CI, 7 behind kani-slow)
Undefined behaviourMiri-clean; the crate contains no unsafe
API documentationevery module carries a //! summary; cargo doc is + warning-free, and CI denies rustdoc warnings
Dependenciesnone. Cargo.lock holds exactly one package
Edition2021, f64 throughout
@@ -150,11 +125,7 @@

Exactness where it matters

-

Install and run something

-

- The Rust snippet below is examples/readme_quickstart.rs, - compiled and run by CI on every push, so it cannot drift out of date. -

+

Install

@@ -181,14 +152,12 @@

Install and run something

-
-

Nothing else comes with it

-

- Cargo.lock holds exactly one package: this crate. No - transitive tree to audit, no version resolution to lose an afternoon to, - and no build script. -

-
+

Quick start

+

+ This snippet is + examples/readme_quickstart.rs, + compiled and run by CI, so it cannot drift out of date. +

@@ -209,7 +178,7 @@

Nothing else comes with it

// Quantities carry their dimensions, and addition checks them. let v = Quantity::new(3.0, Dim::new(1, 0, -1, 0, 0, 0, 0)); // m/s let t = Quantity::new(2.0, Dim::TIME); -let d = v.mul(&t).unwrap(); // 6 m — a length, exactly +let d = v.mul(&t).unwrap(); // 6 m, a length, exactly assert!(v.add(&t).is_err()); // a velocity is not a time // Exact rational arithmetic over arbitrary-precision integers. @@ -227,17 +196,17 @@

Nothing else comes with it

What's in it

- 71 public modules across - 296 source files. Search by name or by - subject — the summaries are the first line of each module's own - documentation, and the figures are parsed from the sources rather than - written down. Press / to jump to the filter. + 71 top-level modules, across + 296 source files. Summaries are the first + sentence of each module's //! documentation and the figures are + parsed from the sources, so nothing here is transcribed. Press / + to filter.

@@ -245,7 +214,7 @@

What's in it

- Module data is generated at build time — see + Module data is generated at build time. See the module map.

@@ -265,58 +234,61 @@

How it's tested

Unit tests 4,193

Every one asserts something checkable: a closed form, a conservation law, a - known reference value, or an exact identity — with a tolerance derived from - the method's error term. + known reference value, or an exact identity. Approximate results carry a + stated tolerance derived from the method's error term, not one tuned until + the test passed.

Property tests 577

- Randomised tests over invariants that must hold for every input: - transform round-trips, algebraic laws, leapfrog FDTD energy conservation, - and agreement between two independent implementations. + Randomized tests over invariants that must hold for every input, not + for a chosen example. They are what catch the cases nobody thought to write + down: round-trips, algebraic laws, conservation, and agreement between two + independent implementations of the same quantity.

-

Kani 20 harnesses

+

Formal verification 20 harnesses

- Model checking with CBMC: over a bounded input domain it proves - absence of panics, overflow and division by zero rather than sampling for - them. Thirteen run in CI; seven slower ones sit behind a feature flag. + Kani model-checks with CBMC: for a bounded input domain it proves absence of + panics, overflow and division by zero, rather than sampling. Thirteen run in + CI; seven that CBMC cannot decide in a CI-sized budget sit behind the + kani-slow feature.

-

Miri strict provenance

+

Miri no unsafe

- The crate contains no unsafe, so Miri is a backstop rather - than the primary check. It runs against core::, where index - and slice reasoning is densest. + The crate contains no unsafe, so Miri is a backstop rather than + the primary check. It runs under -Zmiri-strict-provenance against + core::, the interval and dual-number arithmetic, where index and + slice reasoning is densest.

-

Why 97.89% coverage is not the point

+

Why the coverage number is not the point

Coverage says a line ran, not that anything checked what it did. Some of the worst gaps this project has found were in lines with full coverage:

@@ -326,16 +298,55 @@

Why 97.89% coverage is not the point

+ +
+
+
+

Design

+
+ +
+
+ -
+

From Python

- Published to PyPI as numeria: 4,086 of the crate's 4,149 - free functions, 2,254 of its 2,277 methods, 416 of its 426 types and every - constant. The bindings are generated from this crate's source, so they cannot - fall behind it. + Published to PyPI as numeria: 4,086 of this crate's 4,149 free + functions, 2,254 of its 2,277 methods, 416 of its 426 types and every constant. + The bindings are generated from this crate's source, so they cannot fall behind + it.

@@ -357,57 +368,58 @@

From Python

numeria.
  • - Result errors become exceptions under one - PhysicsError root. + Result errors become exceptions under one PhysicsError + root.
  • - A Vec3 argument accepts a plain (x, y, z) tuple. + A Vec3 argument accepts (x, y, z).
  • - Complex, BigInt and Rational cross - over as Python's own complex, int and + Complex, BigInt and Rational cross over + as Python's own complex, int and fractions.Fraction.
  • - Anywhere the library takes a &dyn Fn, a Python callable - will do. + Anywhere the library takes a &dyn Fn, a Python callable will + do.
  • - -
    + +
    -

    Where to go next

    -

    Three doors, depending on what you came for.

    +

    Further reading

    -

    The guide →

    +

    The guide

    - A walk through the library by doing things with it: putting a spacecraft in - orbit, pulling a tone out of noise, solving a differential equation and - proving the answer converges at the predicted rate. Every code block is a - file CI compiles and runs. + Start here. A walk through the library by doing things with it: putting + a spacecraft in orbit, pulling a tone out of noise, solving a differential + equation and proving the answer converges at the predicted rate, and the tools + for not being wrong. Every code block is a file in examples/ that + CI compiles and runs, and the output shown is what it actually prints.

    -

    The module map →

    +

    The module map

    - A generated map of all 71 public modules - and every file beneath them, with line counts, public-item counts and - summaries. Produced from the sources; CI fails if it is out of date. + A generated map of every one of the 295 modules: a size-annotated tree, tables + by subject area, and a row per module with its line count, public item counts + and summary. Produced from the sources, so nothing in it is transcribed; CI + fails if it is out of date.

    -

    The API →

    +

    The API documentation

    - Full rustdoc, built from this commit. Every module carries a summary, and - CI builds the documentation with -D warnings, so a broken - intra-doc link fails the build rather than shipping. + The crate root carries an orientation page and every module has a summary. + Built here from the current commit with -D warnings, the same + flag CI uses, so a broken intra-doc link fails the build rather than shipping.

    diff --git a/site/main.js b/site/main.js index 6586cb6..93150c3 100644 --- a/site/main.js +++ b/site/main.js @@ -1,4 +1,4 @@ -/* rust_physics_engine — site behaviour. +/* rust_physics_engine site behaviour. No framework and no build step. Five independent pieces, each of which no-ops when the elements it wants are not on the page. */ @@ -195,7 +195,7 @@ return (m.name + ' ' + m.area + ' ' + m.summary).toLowerCase().indexOf(query) >= 0; }); grid.innerHTML = shown.length ? shown.map(card).join('') : - '

    No module matches that. Try a subject — ' + + '

    No module matches that. Try a subject: ' + 'wavelet, orbit, prime.

    '; if (counter) { counter.textContent = shown.length === window.MODULES.length diff --git a/site/styles.css b/site/styles.css index 6984119..ca36dbd 100644 --- a/site/styles.css +++ b/site/styles.css @@ -1,4 +1,4 @@ -/* rust_physics_engine — site styles. +/* rust_physics_engine site styles. No webfonts and no framework: the crate has no dependencies and the site loads nothing it does not serve itself. Everything below is driven by the tokens in :root, which is also how the light theme is a re-declaration @@ -182,6 +182,10 @@ h1 .grad { font-size: 1.13rem; color: var(--fg-muted); max-width: 62ch; margin: 0 0 32px; } .lede strong { color: var(--fg); font-weight: 600; } +.lede code { + font-family: var(--mono); font-size: .87em; color: var(--fg); + background: var(--panel-2); padding: .08em .35em; border-radius: 4px; +} .cta { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 40px; } .btn { @@ -446,3 +450,32 @@ td code, th code { white-space: nowrap; } .site-footer nav { display: flex; flex-wrap: wrap; gap: 20px; font-size: .87rem; align-items: flex-start; } .site-footer nav a { color: var(--fg-muted); } .site-footer nav a:hover { color: var(--fg); } + + +/* The front page's h1 is the crate's one-line description rather than a + slogan, so it needs a smaller step than a headline would. */ +.lede-h1 { font-size: clamp(1.75rem, 3.8vw, 2.75rem); line-height: 1.14; } + +/* A subheading and a paragraph inside a column that is not .prose. */ +h3.sub { font-size: 1rem; font-weight: 620; margin: 26px 0 8px; } +.prose-p { margin: 0; color: var(--fg-muted); font-size: .93rem; } +.prose-p code { font-family: var(--mono); font-size: .87em; } + +/* The At a glance table is a two-column definition list, so its row headers + sit in the first column rather than along the top. */ +.inner > .table-wrap th { + width: 1%; white-space: nowrap; vertical-align: top; + font-weight: 600; color: var(--fg); +} +.inner > .table-wrap td { color: var(--fg-muted); } +.inner > .table-wrap code { + font-family: var(--mono); font-size: .87em; + background: var(--panel-2); padding: .08em .35em; border-radius: 4px; +} + +/* One column of running text rather than a card grid. */ +.checklist.wide li { max-width: 82ch; } +.checklist code { + font-family: var(--mono); font-size: .87em; + background: var(--panel-2); padding: .08em .35em; border-radius: 4px; +} diff --git a/tools/build_site.py b/tools/build_site.py index 20ecd45..018c51c 100644 --- a/tools/build_site.py +++ b/tools/build_site.py @@ -70,7 +70,7 @@ def module_rows() -> tuple[list[dict], dict]: for top in sorted({m.split("::")[0] for m in mods if m != "lib"} & public): files = [m for name, m in mods.items() if name == top or name.startswith(f"{top}::")] - summary = mods.get(top, {}).get("summary", "—").replace(r"\|", "|") + summary = mods.get(top, {}).get("summary", "-").replace(r"\|", "|") rows.append({ "name": top, "area": area_of.get(top, "Other"), @@ -401,7 +401,7 @@ def build_doc_pages(out: str) -> None:
    """ with open(os.path.join(out, page), "w", encoding="utf-8") as fh: - fh.write(shell(f"{title} — rust_physics_engine", description, + fh.write(shell(f"{title} \u00b7 rust_physics_engine", description, article, page.replace(".html", "")))