Skip to content

Check POLAR_MATRICES ForwardDiff against finite differences on affine tables, not NeuralFoil's ram-air polars - #378

Open
1-Bort-1 wants to merge 7 commits into
mainfrom
agent/360-flaky-polar-matrices-forwarddiff-test-on
Open

1-Bort-1 wants to merge 7 commits into
mainfrom
agent/360-flaky-polar-matrices-forwarddiff-test-on

Conversation

@1-Bort-1

Copy link
Copy Markdown
Contributor

TL;DR

The AutoForwardDiff matches AutoFiniteDiff (LOOP, POLAR_MATRICES) check now runs on a four-section arc whose polar tables are affine in (alpha, delta). Before, it ran on the NeuralFoil ram-air wing. That wing's tip polars are junk that changes with the runner's CPU, so each runner solved a different flow and the check was a coin flip.

What was wrong

The issue blamed alpha knots. The grid offset it suggested was already on main (-2:5:23), and the check still fired: 0.0571 on Windows in #372's run 35653997846. So I printed the converged panel alphas on every CI cell, from a throwaway diagnostic branch dispatched three times (since deleted). Runners converge to two different solutions of the same code:

table runners panel alphas [deg] norm(jac)
S1 Xeon 6973P (ubuntu 1.12), Apple M1, EPYC 9V45/9V74 (ubuntu 1.13) 29.04, 7.82, 5.99, 5.50, 5.45, 6.25, 9.10, 28.61 2.0742
S2 EPYC 7763 (windows 1.12, ubuntu 1.13) 17.94, 8.94, 6.44, 5.78, 5.51, 6.70, 11.01, 29.08 2.2112

That is 11° apart on panel 1, not a hair's width across a knot. The cause is in the tables. At n_sections=4, ram_air_matrix_wing's tip slices are ~70% thick (airfoils/1.dat spans −0.46 to 0.23 chord; this is #361). NeuralFoil's answer on them is out of distribution: Cd = 1 at every alpha, cl negative on one tip and flat on the other. An out-of-distribution network output moves with BLAS kernel roundoff, so the table, and with it the converged flow, depends on which CPU the job lands on. At zero sideslip the wing even carries a side-force coefficient of 0.128.

Locally, on one table, the solution is unique from every initial guess (elliptic, zero, mirrored, tip-stalled), so this is not a multiple-root problem inside the solver.

What changed

  • The testset builds affine_matrix_wing(8):
    • a 3 m-radius arc with sections at ±50° and ±17°;
    • cl, cd and cm affine in (alpha, delta) on a 5° grid.
  • Bilinear interpolation reproduces an affine table exactly, so the grid knots are not kinks, wherever a panel converges.
  • Every panel stays attached: 2.7–5.4° at the test's 7.5°.
  • The table is the same bits on every machine, and the NeuralFoil sweep leaves this test.
  • The solver, operating point, inputs, rtol and the < 1e-4 assertion are unchanged.

Locally, fwd vs FD agree to 5.8e-9 to 4.5e-8 at every aoa from 0° to 12°, including 7.5° + 1e-9°. No setting lands near a boundary.

Where I would push back

  • Not explained: in the failing Windows job, norm_fd was bit-identical to ubuntu 1.13's (2.0741507267422183), yet ForwardDiff gave 2.0576, which matches neither solution. Locally the Dual solve's primal is bit-identical to the Float64 one, repeated Dual Jacobians are identical, and filling every LazyBufferCache buffer with NaN changes nothing. All three diagnostic Windows cells landed on S2, and ForwardDiff matched there. This PR removes the junk-table, post-stall operating point that the mismatch only ever showed up on; it does not show that a Windows-only Dual difference cannot exist.
  • test/body_aerodynamics/test_results.jl checks linearize against the same NeuralFoil ram-air tables, with loose 5e-3/1e-1 tolerances. It has the same exposure but has not fired. It is left alone here; march_edges gives each station the previous station's leading-edge step, so a cut near a closing tip grazes the surface and reports an 80%-thick section #361 fixes the tip slices it depends on.
  • .github/workflows/CI.yml carries fail-fast: false, which AGENTS.md §4 wants removed in a cleanup: PR. Not touched here.

Verification

  • Reproduced first: the failing Windows cell 0.057102607132145794 < 0.0001 (run 35653997846). The flake could not be made to fail locally: the old test passed at 2.27e-8 on this box's table. The two-solution split above comes from diagnostic CI runs 35661076607, 35661081582 and 35661086944.
  • test/solver/test_forwarddiff.jl passes after the change: 10/10, POLAR_MATRICES rel. error 1.3e-8 (juliaserver, Julia 1.13.0). There is no local red before the change, because the failure depends on the runner.
  • Local CI mirror (agent ci-local, Julia 1.13, full Pkg.test): PASS in 5 min
  • GitHub CI, three dispatches of this commit (35662850870, 35662853738, 35662856131): PASS, 15/15 cells. norm_fwd = 2.84152755639267 in every cell, on Windows, macOS and ubuntu, Julia 1.12 and 1.13; norm_fd differs only in the 11th digit, on ubuntu 1.12
  • Docs: n/a (test only) · Changelog: n/a (test only) · up to date with main (9ed3cd6)
  • Risk: a Windows-only ForwardDiff difference, if one exists outside the junk-table case, is no longer exercised by this check.

Scope

+24 / −16 in test/solver/test_forwarddiff.jl: the affine_matrix_wing helper (+19) replaces the knot-offset comment and the ram_air_matrix_wing call. The locals are renamed from ram_* to matrix_*, since the wing is no longer the ram-air kite. src/ is untouched.

Closes #360 · task VortexStepMethod.jl-360

…il's

The ram-air wing's tip sections are ~70%-thick slices (#361) on which
NeuralFoil returns Cd=1 at every alpha, and those outputs differ with the
runner's CPU, so the LOOP solve lands on one of two different solutions
depending on the runner. Tables that are affine in (alpha, delta) make the
bilinear interpolation exact, keep every panel attached, and are the same
bits on every machine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@1-Bort-1 1-Bort-1 added agent:running Agent task state agent:ci Agent task state and removed agent:running Agent task state labels Sep 21, 2026

@1-Bort-1 1-Bort-1 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Independent review (advisory)

Verdict: APPROVE WITH COMMENTS · 2 inline, 0 off the diff

Good

  • The change goes after the cause: affine tables make bilinear interpolation exact, so no grid knot can be a kink, and the tables no longer depend on the CPU; the three-dispatch CI result in the card, with norm_fwd bit-identical across 15 cells, backs this
  • The solver settings, operating point, input indices, rtol and the < 1e-4 assertion are unchanged (the unchanged context lines confirm it), so no test was weakened
  • The knot-offset comment block is gone: it described a mechanism the card shows was wrong (the runners land on two solutions 11° apart, not near a knot)
  • No existing helper duplicates this one: rg finds only ram_air_matrix_wing in test/test_data_utils.jl (NeuralFoil) and a local two-section flat_wing closure in test_backend_comparison.jl
  • The card names the ram_*matrix_* rename and scopes it to the testset's locals; src/ is untouched and the diff stays at one idea

Not good

  • test/solver/test_forwarddiff.jl:8 — This is a two-line comment block explaining why the tables are affine, which is the kind of why §3/§5 route to the PR card. At most keep one line, e.g. 'affine tables: bilinear interpolation is exact'.
  • test/solver/test_forwarddiff.jl:79 — The Windows ForwardDiff norm of 2.0576, which matched neither converged solution while norm_fd was bit-identical to ubuntu, is left unexplained, and this swap is what stops it being exercised. Under §5 that needs its own issue linked from the PR, or a possible Dual-path bug goes silent with the flake.
  • The card's 'Where I would push back' items (test_results.jl has the same NeuralFoil exposure, fail-fast: false in CI.yml) are noted but not linked to an issue; #361 covers the first only by implication
  • The check now runs only on smooth attached flow with affine tables, so it no longer tests ForwardDiff through a real table's curvature or near stall; the card names this as the risk, but no other test picks that coverage up
  • affine_matrix_wing(n_panels) has a single caller that passes 8; the argument is harmless but a literal inside the helper would say the same thing

claude, rubric CLEAN_CODE.md. A different lab from the implementer
on purpose: a reviewer sharing its blind spots would not flag its mistakes.

Comment thread test/solver/test_forwarddiff.jl Outdated

relative_error(jac, reference) = maximum(abs.(jac .- reference)) / maximum(abs, reference)

# Four-section arc whose POLAR_MATRICES tables are affine in (alpha, delta), so the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MINOR: This is a two-line comment block explaining why the tables are affine, which is the kind of why §3/§5 route to the PR card. At most keep one line, e.g. 'affine tables: bilinear interpolation is exact'.

Comment thread test/solver/test_forwarddiff.jl Outdated
)
ram_body = BodyAerodynamics([ram_wing])
ram_solver = Solver(ram_wing.n_panels, ram_wing.n_unrefined_sections;
matrix_wing = affine_matrix_wing(8)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MINOR: The Windows ForwardDiff norm of 2.0576, which matched neither converged solution while norm_fd was bit-identical to ubuntu, is left unexplained, and this swap is what stops it being exercised. Under §5 that needs its own issue linked from the PR, or a possible Dual-path bug goes silent with the flake.

…ide it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@1-Bort-1

1-Bort-1 commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Local full suite: PASS (12 min, Julia 1.13.0, one cell of the matrix)

@1-Bort-1 1-Bort-1 added agent:queued Agent task state agent:running Agent task state and removed agent:running Agent task state agent:queued Agent task state labels Sep 21, 2026
@1-Bort-1 1-Bort-1 added agent:waiting-human Agent task state agent:queued Agent task state and removed agent:running Agent task state agent:waiting-human Agent task state labels Sep 22, 2026
@1-Bart-1

Copy link
Copy Markdown
Member

This is not the solution right and should be closed.

@1-Bort-1 1-Bort-1 added agent:running Agent task state and removed agent:queued Agent task state labels Sep 22, 2026
1-Bort-1 and others added 3 commits September 22, 2026 14:49
…ompiles for Dual{10} on Zen 4/5

panel_body_loads (@noinline) now returns panel_loads plus the moment about the
reference point, for calc_forces! and for the body_aerodynamics results loop,
which carried the same moment line. The POLAR_MATRICES testset also checks that
the Jacobian does not depend on the ForwardDiff chunk size.

Refs #360, #379

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

1-Bort-1 and others added 2 commits September 22, 2026 15:56
…bounds=yes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@1-Bort-1 1-Bort-1 added agent:waiting-human Agent task state and removed agent:running Agent task state labels Sep 22, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:waiting-human Agent task state

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flaky POLAR_MATRICES forwarddiff test on Windows / Julia 1.12 (knot-proximity in piecewise-linear polar interpolation)

2 participants