Skip to content

FEA: Solve every point with its twins, behind a unified validity CLI - #34

Merged
thclark merged 3 commits into
mainfrom
twins
Oct 27, 2026
Merged

thclark merged 3 commits into
mainfrom
twins

Conversation

@thclark

@thclark thclark commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Closes #11.

Builds the conditioning check into the solver and gives it the validity CLI designed in #11's design comment. Anomaly detection (Class D) follows the same pattern and is #12, to be done separately.

Twins in the solver

  • Every analysed point is also solved on five copies of its panels, jittered by −1, 0 or +1 ULP (seeds 1–5, the jitter the reference twins use). The five run in parallel, from the point's own initial state, so its verdict depends only on what its content id hashes. They never steer the base solve.
  • A point that is valid on Classes A and B but whose twins spread a reported quantity beyond 1e-4 is Invalid (IllConditioned), and its numbers are withheld.
  • Beyond only 1e-8, it stays valid and carries a warning.
  • Only values decide. A twin that did not converge raises a TwinsNotValid warning, never a reason.
  • The conditioning study (studies/conditioning, docs/validation/conditioning/) sets both thresholds in the gaps of the spread distribution, measured over the series and branch-coverage cases. Over 635 measured points, 12 are withheld and 19 get a warning. Its figures are drawn with ironlab 0.16.

The validity CLI

Flag What it does
--conditioning on|off|study on (default) runs the twins. off runs none, as XFOIL. study also writes every twin's record. Replaces --no-twins.
--allow-invalid Unchanged and orthogonal to every check: shows the numbers of invalid points of any class in results. This is what the issue's REPORT became.
--halt-on CRITERION[=N],... polar only. Halts a leg after N consecutive points meeting a criterion: xfoil, out-of-domain, unconverged, ill-conditioned or none. Each criterion keeps its own count.
--spread-warning-threshold, --spread-error-threshold Thresholds for Class C; 1e-8 and 1e-4 by default.
  • Study records go in a study array, with role: twin, twin_of, seed and an id of their own. With analyse -o or polar --distributions, each record also carries its twins' full records: jittered geometry, surface and boundary layer.
    • A study's records always carry their numbers, whatever their status, so everything computed can be traced. results is unchanged by a study.
  • classes on every record: out-of-domain (A), unconverged (B), ill-conditioned (C).
  • The default --halt-on xfoil is XFOIL's NSEQEX rule exactly, so default sweeps don't change. It is narrower than unconverged, which also counts the inviscid Newtons and converged points that passed through a NaN.
    • PolarConfig.max_consecutive_failures becomes halt_on, with a classifier supplied by the caller, because the thresholds belong to the output layer.
    • Halting decides which alphas are attempted, never the numbers of a point that is. The polar records each halt under halts.
  • Contradictory flags are usage errors, for example --halt-on ill-conditioned with --conditioning off. --halt-on anomalous is refused with a pointer to Add intelligent polar stepping algorithm #12.
  • Ids: base ids are unchanged. Twin ids hash the twin's own panels and seed.

On NACA 4412 at Re 3e6, --halt-on xfoil,ill-conditioned stops the downward leg at −18°, the point the conditioning study flags as ill-conditioned.

Docs

CLAUDE.md, the analyse and polar command pages (new section Halting a leg), solution validity (new sections Classes on records and Halting a polar, plus the study), the data model (brought up to date), terminology and the XFOIL name mapping (NSEQEX → HaltRule::XFOIL).

Test plan

  • cargo test --release --workspace: 376 passed, 0 failed. New tests cover twin ids, study records, classes, halt rules and the CLI's usage errors.
  • cargo fmt --check and cargo clippy --workspace --all-targets --all-features -- -D warnings
  • ci/no-deviations.sh, ci/validity-write-only.sh, ci/ignore-drift.sh
  • scripts/docs.sh build --clean and scripts/docs-check.py
  • CI

… study

Each analysed point is also solved on five copies of its panels jittered
by -1, 0 or +1 ULP (seeds 1-5, the jitter the reference twins use), in
parallel, from the same initial state, so a point's verdict depends only
on what its content id hashes. A point valid on Classes A and B whose
twins spread a reported quantity beyond the error threshold (1e-4) is
Invalid (IllConditioned); beyond only the warning threshold (1e-8) it
stays valid with a warning. Only values decide: twins that did not
converge raise a TwinsNotValid warning, never a reason. The twins never
steer the base solve.

The conditioning study (studies/conditioning, docs/validation/
conditioning/) measures every point's spread over the series and
branch-coverage cases and sets both thresholds in the gaps of the
distribution. Its figures are on ironlab 0.16.
…lt-on

The validity CLI of issue #11's design comment:

- --conditioning on|off|study replaces --no-twins. study writes every
  twin's record in a `study` array (role twin, twin_of, seed), and with
  analyse -o or polar --distributions each record's twins' full records.
  A study's records always carry their numbers; `results` still follows
  --allow-invalid, which is orthogonal to every check.
- A twin hashes its own panels and seed, so every twin record has an id
  of its own; it cites its point's predecessor. Base ids are unchanged.
- Every record lists the classes of its reasons: out-of-domain (A),
  unconverged (B), ill-conditioned (C).
- yfoil polar --halt-on CRITERION[=N],... halts a leg after N
  consecutive points of a class, one count per criterion. The default,
  xfoil, is XFOIL's NSEQEX rule exactly (narrower than Class B); none
  never halts. PolarConfig.max_consecutive_failures becomes halt_on, with
  a caller-supplied classifier; the polar records each halt.
- Contradictory flags are usage errors; anomalous points to issue #12.
Work through --halt-on xfoil,ill-conditioned on a NACA 4412 at Re 3e6,
which stops its downward leg at its first ill-conditioned point (-18
deg), with the console note and the polar's halts record, and show the
study record of that point's unconverged twin, which keeps its numbers.
Add classes to the contract example in solution validity and drop its
values: null, which the output leaves out rather than writing null.
@thclark
thclark merged commit 40844dd into main Oct 27, 2026
5 checks passed
@thclark
thclark deleted the twins branch October 27, 2026 15:43
thclark added a commit that referenced this pull request Oct 27, 2026
Main's unified validity CLI (#34) solves every point with its five 1-ULP twins by default and
passes the output options as one OutputOptions. The binding takes the same options with the same
defaults (conditioning, spread_warning_threshold, spread_error_threshold, and halt_on for polar),
so its records stay the CLI's byte for byte.

The CLI's halt-on and threshold parsing move into the library (HaltRule::parse_list,
Thresholds::from_options) so both use one copy, with the same messages. WebAssembly has no
threads, so on wasm targets (the module and the wasm32-wasip1 CLI) a point's twins run one after
another; they start from copies of the session and never steer the base, so the results are
the same. The bindings page's speed figures are remeasured with and without the twins.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build twin-based validity criterion to determine where xfoil/yfoil results are hypersensitive

1 participant