Add Li/Gaunaa spanwise artificial viscosity for post-stall stabilization#247
Merged
Conversation
26e1f9e to
c75da31
Compare
Port the opt-in implicit artificial-viscosity regularization (Li, Gaunaa, Pirrung & Lønbæk, TORQUE 2026) from the Python Vortex-Step-Method to the LOOP solver. Post-stall (negative lift-slope) circulation distributions otherwise develop non-physical sawtooth oscillations and never converge; the implicit scheme (I - diag(mu) L) gamma = F(gamma) stays stable at relaxation factors of order one. Adds the discrete spanwise Laplacian with second-order tip closures (Eq. 15), per-panel lift-slope evaluation, and gating so attached flow is a no-op. The expensive linear solve is gated on any(mu > 0) rather than on precomputed stall angles, since the Julia polar is an interpolation object. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
088e739 to
fbaf1f5
Compare
Member
Author
|
Rebased onto current |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The previous post-stall test reimplemented the whole scheme inline and never touched the solver, so the actual artificial-viscosity path in gamma_loop! had 0% patch coverage. - Extract the viscosity step from gamma_loop! into apply_artificial_viscosity!, so it is directly testable and no longer duplicated inline. - Solve in place with ldiv!(gamma, lu!(M), rhs) instead of M \ rhs, cutting the post-stall allocation from ~3.7 kB to the LU pivot vector; the attached hot path stays zero-alloc. - Replace the reimplementing test with a unit test of apply_artificial_viscosity! on a real post-stall wing (asserts fire/no-op, smoothing, and zero allocation on the attached path) and a solve! integration test (attached no-op is bit-identical to viscosity off; high-AoA solve stays finite). The firing test regenerates the NeuralFoil polar over alpha_range -5:40 deg because cl clamps flat past the tabulated range, so the default 15 deg polar never reaches negative lift slope and the feature never activates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…scosity # Conflicts: # ext/VortexStepMethodControlPlotsExt.jl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports the opt-in implicit artificial-viscosity regularization (Li, Gaunaa, Pirrung & Lønbæk, TORQUE 2026) from the Python Vortex-Step-Method into the Julia LOOP solver.
Why
Post-stall (negative lift-slope) circulation distributions develop non-physical sawtooth oscillations in the fixed-point
gamma_loop!and never converge. The implicit scheme(I - diag(mu) L) gamma = F(gamma)has the same steady solution as the explicit fixed point but stays stable at relaxation factors of order one, whereas the explicit stable step shrinks likeN^-2in post-stall.What
Solver: new opt-in fieldsis_with_artificial_viscosity(defaultfalse) andartificial_viscosity_factor(default0.035), threaded through theVSMSettingsand ForwardDiff dual-copy constructors.build_spanwise_laplacian!: discrete spanwise Laplacian with the second-order tip closures (Eq. 15) enforcinggamma -> 0at the tips.local_lift_slope!: per-paneldCl/dalphavia central difference ofcalculate_cl.gamma_loop!(LOOP): builds the Laplacian and planform area once, then when any panel is post-stall, solves(I - diag(mu) L) gamma = F(gamma)withmu_i = max(0, -k S Cl'_i / dz_i^2)before relaxation.SolverSettings: the two fields exposed for YAML config.Design note (difference from Python)
The Python version gates the expensive linear solve on per-panel stall angles read from raw polar arrays. In Julia the polar is an interpolation object, so the solve is instead gated on
any(mu .> 0)— behaviourally identical (it fires exactly in post-stall) without exposing internal polar storage.The feature is fully opt-in and a no-op in attached flow, so existing results are unchanged.
Tests
Ports both Python tests:
Verified locally: solver tests 14/14, ForwardDiff tests 7/7, and an integration smoke test confirming attached flow is bit-identical to baseline while a high-AoA case converges.
🤖 Generated with Claude Code