Files
OneCAD-Simulation/docs/verification.md
T
jyvikoandClaude Opus 5.5 a30d0b5c5b explicit: deformable contact and friction, verified by bars, sliding blocks, the patch test and Hertz
Segment-to-segment penalty contact between element faces: each secondary
face and each main face facing it are projected onto the secondary face's
plane, split into triangles, clipped (Sutherland–Hodgman) and integrated
with a three-point rule per sub-triangle (Puso–Laursen segmentation, the
penalty at the integration points as Fischer–Wriggers), so a uniform
pressure gives the consistent nodal forces on both meshes and the contact
patch test passes on non-matching meshes. Every contact runs both ways at
half the penalty: pairs with each surface as the secondary, general and
self contact face against face. A bucket grid of face boxes every
increment, faces integrated in parallel and their forces added in face
order: the same numbers on any thread count.

The penalty per area ε = s γ min(m/A)/Δt² comes from the lumped masses,
each node's directional area and the stable increment, bounded by
Gershgorin's row sums so contact never shortens the increment. Coulomb
friction with exponential decay by a regularised return map at the
secondary nodes; rigid walls take Coulomb impulses. Contact work and
friction dissipation join the energy balance; each contact reports its
peak force, first and last contact, deepest penetration, friction and the
nodes adjusted at the start (initial penetrations adjusted strain-free or
refused with the node, face and depth); a cap on contact faces.

Verified (crates/explicit/tests/contact.rs, tests/oracle/explicit-*):
colliding bars (2L/c +1.2%, ρcAv₀/2, KE kept to 0.005%, contact energy
back to 8e-5 of it), blocks sliding to a stop on a rigid wall and on a
plate under constant and decaying friction (0.005–0.2%), the patch test
(σ_zz uniform to 0.06%), two spheres colliding against Hertz (peak +2.3%,
duration −0.4%; the oracle case waits for a graded sphere mesher), a free
body untouched by general contact, determinism across threads. Oracle
measures gain `over` (ratios). Existing fixtures do not move.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
2026-10-14 12:49:45 -04:00

94 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Verification and validation
Every solver capability is checked against three kinds of reference, and every accepted result is
frozen so it cannot drift.
| Kind | What it checks | Sources |
|---|---|---|
| Theory | The code solves the equations right | Closed-form solutions (beams, plates, Lamé, Kirsch, waves, shocks, Poiseuille…), patch tests, convergence rates, mesh invariants |
| Experiment | The model predicts reality | Measurements published in papers and reports (Taylor bar, cavity and cylinder flows, FSI experiments…) |
| Published benchmarks | Agreement with established codes on standard problems | NAFEMS benchmarks, vendor benchmark and verification manuals (e.g. Abaqus Benchmarks and Verification manuals), other published reference solutions |
Rules:
- Each case is **our own model** of the problem, built from the published description (geometry,
material, loads, boundary conditions). We never run a commercial product and never copy its
input files, manual text or figures; only the published numbers are used, with a citation.
- Each case lives in `tests/oracle/<case>/`:
- `recipe.json` — the model as FieldCraft commands (or a SolidCraft recipe for geometry);
- `expected.json` — the reference values with `kind` (theory | experiment | benchmark),
`source` (citation: document, table or figure, page), `quantity`, `value`, `tolerance` and
the reason for that tolerance (mesh, measurement scatter, model differences);
- `fixture.json` — FieldCraft's accepted result, written by `cargo xtask oracle --bless <case>`
only after the case passes against `expected.json`.
- `cargo xtask oracle` runs every case, compares with `expected.json` (pass/fail with error) and
with `fixture.json` (any change beyond the fixture tolerance fails CI, even if the reference
still passes), and writes `docs/oracle.md` with the table of cases, sources, errors and status.
- A fixture is updated only deliberately (`--bless`), in a PR that says why the result moved
(a better element, a finer default mesh), never to hide a regression.
- The theory suite is the gate for every merge; experiment and benchmark cases run in CI too, with
their cited tolerances.
## The case format
`cargo xtask oracle` builds `fieldcraft-testkit`'s `oracle` binary (release) once and runs every
case through the command engine; the results are in [oracle.md](oracle.md).
```
cargo xtask oracle # every case; writes docs/oracle.md
cargo xtask oracle --case nafems-le1 # one case (repeatable); docs untouched
cargo xtask oracle --bless nafems-le1 # freeze a passing case's results (or --bless all)
cargo xtask oracle --quick # skip cases marked "slow"
```
- `recipe.json` is a script `fieldcraft-cli run` also accepts: a JSON array of
`{"command", "params"}`; a step may carry an `"id"` so a quantity can read its output. Meshes
come from `mesh.block` (mapped hex, tet or wedge blocks with arcs and elliptical edges, named
faces, edges and vertices that sets select), `mesh.tet.generate`, or nodes and elements written
one by one; once structured and automatic meshers land, cases may use them.
- `design.json` (optional) is a SolidCraft script that builds the geometry, opened before the
recipe (the test corpus's recipe form).
- `expected.json`:
```json
{"title": "…", "kind": "theory|benchmark|experiment",
"source": "document, section/table/figure, page",
"status": "active|pending", "requires": "the capability a pending case waits for",
"slow": false,
"quantities": [{"id": "syy_D", "quantity": "σ_yy at D (2, 0)",
"measure": {"command": "solve.probe", "params": {"step": "Load", "at": [[2, 0, 0]]}, "pointer": "/nodes/0/stress/1"},
"value": 92.7, "tolerance": {"relative": 0.01}, "reason": "why this tolerance",
"known_failure": "optional: why FieldCraft misses the reference today"}]}
```
A measure reads a JSON pointer either from a recipe step's output (`"from": "<id>"`) or from a
command run after the recipe (`"command"`, `"params"`); an optional `"scale"` multiplies it, and
an optional `"over"` (a measure of its own) divides it by another number of the same run, for
ratios such as the stress in one element over the stress in another.
A tolerance is `relative` (of the reference), `absolute`, both, or `"exact": true`. A pending
case cites its reference values (or `null` until transcribed) and has no recipe yet.
- `fixture.json` holds each passing quantity's accepted `value` and its `band`
(10⁻⁹ of the larger of value and reference, plus 10⁻³ of an absolute tolerance; a quantity whose
reference is zero, which is rounding noise, keeps its absolute tolerance as its band; an
ill-conditioned quantity may set a wider `fixture_relative` with a `fixture_reason`; a quantity
that depends on a mesh generated from geometry, which is not bit-identical across platforms, sets
`"fixture": false` with a `fixture_reason` and is gated by its reference tolerance alone): the result is
deterministic, the band only absorbs last-digit differences of the system maths library.
The run fails when a quantity misses its reference (unless it is a `known_failure`, which is
listed in oracle.md with its reason), when a known failure starts passing (remove the mark),
when a result leaves its fixture band, when a passing quantity has no fixture, or when a case
cannot run.
## Adding cases for a new capability
Every capability that lands brings its oracle cases in the same pull request:
1. Turn the pending cases that waited for it into active ones (write `recipe.json`, drop
`status`/`requires`, add `measure` and `tolerance` with its reason to each quantity), and add
theory cases for its closed-form solutions (patch test and convergence for an element; the
analytic problem for a load, constraint or procedure).
2. Choose meshes that are converged well inside the tolerance (show the refinement in the case's
README), or the mesh a benchmark prescribes.
3. Run `cargo xtask oracle --case <name>`; when it passes, `--bless <name>`; commit the case, its
fixture and the regenerated docs/oracle.md.
4. A case that misses its reference stays honest: keep the tolerance its reason supports and add
`known_failure` with what is missing. Never widen a tolerance to make a case pass.