Skip to content

LOOP's rtol checks the iterate before the one it returns, and the fixed point is repelling at alpha=0 on solver_test_wing #315

Description

@1-Bort-1

test/solver/test_solver.jl:136, the test #306 added, asserts that the circulation LOOP returns has its own fixed-point residual under the solver's tolerance:

@test residual < solver.rtol * maximum(abs, gamma)

On the box it holds by 1.07x at the first of its two operating points — va = [10, 0, 0] on test/data/solver/wings/solver_test_wing.yaml, n_panels=4 then refine! — with residual/|gamma| = 9.35e-6 against rtol = 1e-5. The second point passes by 1.11x.

It is that close because the unrelaxed map is expansive there. Walking F from the returned iterate, residual/|gamma| goes

9.35e-6 -> 9.63e-6 -> 9.92e-6 -> 1.02e-5 -> 1.05e-5

a ratio of 1.030 per step, four steps running. With |F'| > 1 the relaxed iteration amplifies by 1 - rf(1 - |F'|) = 1 + 0.0009 at the default relaxation_factor = 0.03, so it does not contract either. gamma_loop! stops on max|gamma_new - gamma| / rf < rtol * ref, which is the residual at the previous iterate; the residual at the one it returns keeps creeping up for as long as the loop runs, and nothing pins down where it stops.

That is not academic. Pkg.test() on this same commit on this same box evaluated

Expression: residual < solver.rtol * maximum(abs, gamma)
 Evaluated: 0.0007693959246370952 < 2.597571653474259e-5

30x over, while maximum(abs, gamma) itself was only 1.4e-4 away from the value measured here (2.59757 against 2.59794). The same commit, same box, run through juliaserver instead: 6348 passed, 1 broken, 0 failed for the whole suite, and this testset 4/4. All seven GitHub checks are green on that commit. So the returned iterate lands somewhere different run to run while the solution it approximates barely moves, which is what a non-contracting iteration stopped on a step-size criterion does.

BLAS threading is not the axis: BLAS.set_num_threads(1), 4 and 10 give bit-identical residuals, 2.4290560565631836e-5 and 8.210448704915052e-5.

Two things worth separating, and I do not think the second is a bug in the test:

  1. rtol on LOOP currently means "the step, divided by the relaxation factor, at the iterate before the one returned". LOOP converges on the relaxed step, so rtol is 33x looser than it reads #284 removed the factor-of-1/rf part of that. What is left is the lag, and under a repelling map the drift. Measuring the residual at gamma_new rather than at gamma would cost one extra update_gamma_candidate! per iteration and would make rtol mean what the docstring says it means.
  2. A fixed point this iteration cannot contract onto is itself a finding about the operating point. alpha = 0 on a flat two-section wing is not an exotic one.

Found while merging main into #314, which carries no code, so this is main's solver and main's test. Filed rather than fixed: changing what rtol measures moves every LOOP result again, one release after #306 already did, and that is a call for @ufechner7.

Caveat on the numbers above: the Pkg.test() failure was seen once. The re-run I started to check whether it repeats was killed together with the worktree when the box ran low on memory, so I cannot say whether it is deterministic in that environment.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions