Skip to content

Build a Solver from VSMSettings or from panel and section counts, deprecate the body_aero constructors, and check the sizes in solve! - #340

Merged
1-Bart-1 merged 8 commits into
mainfrom
agent/153-remove-the-parameter-body-aero-from-the-
Sep 20, 2026
Merged

1-Bart-1 merged 8 commits into
mainfrom
agent/153-remove-the-parameter-body-aero-from-the-

Conversation

@1-Bort-1

@1-Bort-1 1-Bort-1 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

TL;DR

Solver(settings) and Solver(n_panels, n_unrefined_sections) build a solver without a BodyAerodynamics. Solver(body_aero; kwargs...) and Solver(body_aero, settings) still work but are deprecated and warn on use, because they suggest the solver keeps the body. Since a solver can now be paired with any body, solve! checks the pairing up front and throws a DimensionMismatch naming both sizes. Before, a mismatch either failed deep inside the solve or returned silently wrong results.

What changed

Solver(n_panels, n_unrefined_sections, T=Float64; kwargs...) is the one constructor the others call. Solver(settings) sums n_panels over settings.wings and counts the wing_sections rows in each wing's geometry_file, which is the number of sections Wing(geometry_file) creates. Its solver_settings supply defaults, and keyword arguments override them.

Bart asked on the thread to keep the body_aero constructor but mark it deprecated. Both forms that take a body_aero now call Base.depwarn(...; force=true), so the warning shows even at the REPL's default --depwarn=no, once per call site. The message names the replacement. I deprecated Solver(body_aero, settings) too, since the issue is about the body_aero parameter and that form has it as well. Solver(settings) replaces it wherever the body was built from those settings.

Nothing in the package calls the deprecated forms any more:

  • make_dual_shadow uses Solver(P, U, TD; ...), also for the several-wing body linearize and make_dual_shadow take a BodyAerodynamics with several wings #355 lets it take.
  • Tests and examples with a body built from settings use Solver(settings). That includes examples/billowing.jl, which used to map solver_settings into keyword arguments by hand. data/TUDELFT_V3_KITE/vsm_settings_coarse.yaml named the 37-section aero_geometry.yaml, but it describes and the example flies the 10-section aero_geometry_coarse_discretisation.yaml, so it now names that file. Nothing else reads it.
  • The rest build their wings in code with no settings file, and use Solver(wing.n_panels, wing.n_unrefined_sections; ...) with the same keyword arguments as before.
  • examples/V3_neuralfoil.jl pairs a NeuralFoil wing with the CFD settings' solver fields. It now loads a second VSMSettings pointing at the generated YAML, as examples/V3_kite.jl already does.

The mapping from SolverSettings to keyword arguments was written out inline in Solver(body_aero, settings). It is now solver_kwargs, shared by both settings constructors. #352 added is_with_viscous_drag_correction to that inline mapping after this branch was opened; merging main moved it into solver_kwargs, so Solver(settings) carries it, and a testset in test/solver/test_viscous_drag_correction.jl fails if it is dropped. That file's own solvers now come from panel and section counts. The hasproperty(ss, :reference_point) branch is gone: SolverSettings has no such field, so it always fell back to the default.

The issue calls the second size n_groups. It is n_unrefined_sections here, the name Wing and the Solver{P, U, T} parameter already use.

How a mismatch failed before

I ran solve! on main with a solver one size off from the solver_test_wing body:

  • One extra panel: DimensionMismatch: array could not be broadcast to match destination, raised partway through the solve.
  • One extra unrefined section: no error. cl_unrefined_dist came back with one entry left at zero.

check_dimensions runs first in solve_base!, which solve!, solve and linearize all go through. It measured 0 bytes allocated.

Where I'd push back

Without a body_aero constructor, a body that wasn't built from settings means spelling out wing.n_panels, wing.n_unrefined_sections, and for several wings a sum over them. There is also no public way to give such a body the solver fields of a VSMSettings: examples/V3_neuralfoil.jl needed a second settings object for that. Solver(n_panels, n_unrefined_sections, settings) would close that gap. I left it out because nobody asked for it.

Solver(settings) reads each wing's geometry YAML a second time just to count rows. The alternative is an n_unrefined_sections field in WingSettings, but that is a second copy of a number that could drift from the file. A wing given only obj_file/dat_file gets an ArgumentError, as Wing(settings) does.

Found on the way, left for a cleanup: PR: .github/workflows/CI.yml:22 sets fail-fast: false.

Verification

  • Reproduced first on main's src/ (outputs above)
  • test/solver/test_solver.jl, sizes and new constructors: red on main's src/ ("3 passed, 0 failed, 4 errored"), green after
  • test/solver/test_solver.jl, deprecation: red with the previous commit's constructors (17 passed, 2 failed: Log Test Failed, no captured logs), green after (19/19, whole file passes)
  • test/solver/test_viscous_drag_correction.jl's "solver settings switch it on": red with is_with_viscous_drag_correction removed from solver_kwargs, green with it
  • Affected, all passing with no deprecation warning in the output:
    • test/body_aerodynamics/test_body_aerodynamics.jl
    • test/body_aerodynamics/test_results.jl 30/30
    • test/solver/test_flow_curvature.jl
    • test/solver/test_moment_units.jl 17/17
    • test/solver/test_forwarddiff.jl (runs make_dual_shadow)
    • test/solver/test_unrefined_dist.jl 40/40
    • test/verification/test_verification.jl 22/22
    • test/solver/test_backend_comparison.jl 15/15
    • test/plotting/test_plotting.jl 58/58
  • Examples run to the end, no warning: pyramid_model, rectangular_wing, stall_model, billowing, linearize_check, bench
  • examples/billowing.jl on Solver(settings) (da666bf), run next to its previous version on Julia 1.13.0: both solvers Solver{54, 10, Float64}, all 20 solver fields equal, flat CL 0.6594671534084141, billowed CL 0.31720547039800795 and CD 0.059889001416727046 in both. The one visible difference is the legend, which reads "5.0%" instead of "5%" because billowing_percentage is a Float64 in WingSettings.
  • Not run locally: test/bench.jl (its packages are in neither local environment; the suite runs it), and the V3_kite, V3_neuralfoil, ram_air_kite, obj_to_yaml_kite examples, which generate polars from meshes
  • Re-run after the merge with main at 7724c0d, on Julia 1.13.0: test/solver/test_forwarddiff.jl 10/10, test/solver/test_solver.jl 57/57, test/body_aerodynamics/test_body_aerodynamics.jl 4903/4903 — 4970 passes, nothing failed or errored, no "deprecated" in the output
  • Docs build clean (GitHub Documentation job) · no REUSE in this repo · up to date with main at 7724c0d
  • Local CI mirror on 4cd7a7e (Julia 1.13.0, one cell): PASS in 16 min, exit 0, and the word "deprecated" does not appear in the log
  • GitHub CI on 4cd7a7e: PASS, every check on the first attempt — Documentation, Julia 1.12 on ubuntu, Windows and macOS, Julia 1.13 on ubuntu, Test end-user and developer setup, codecov/patch. Every earlier commit here needed a re-run of one Julia 1.12 cell, always the test_forwarddiff.jl's POLAR_MATRICES check flakes at ~4%, and the oper #287 POLAR_MATRICES check and never anything this branch touches: the cells that failed were the ones whose runner built the NeuralFoil table with norm_fd 2.6237859245…, on main as much as here, and the numbers are on test_forwarddiff.jl's POLAR_MATRICES check flakes at ~4%, and the oper #287. Flaky POLAR_MATRICES forwarddiff test on Windows / Julia 1.12 (knot-proximity in piecewise-linear polar interpolation) #360 has since offset that test's alpha grid on main, and this is the first run of the branch without a re-run
  • Benchmark: n/a (one size check per solve, 0 bytes allocated)
  • Risk: examples/V3_neuralfoil.jl's Solver(settings_nf) is not run here. If the generated YAML's section count differed from the wing's, solve! would throw DimensionMismatch rather than give wrong numbers.

Scope

+254 / −142 across 31 files against main.

  • src/solver.jl +90/−29: the core constructor, the two deprecated methods, solver_kwargs (moved out of inline code, not new logic), n_unrefined_sections and check_dimensions.
  • src/yaml_geometry.jl +12 counts sections from a WingSettings.
  • test/solver/test_viscous_drag_correction.jl (from Add the Gaunaa et al. 2024 spanwise-flow viscous drag correction as an opt-in solver setting #352): off the deprecated constructor, plus the settings testset.
  • examples/billowing.jl −44: its hand-written solver keyword mapping replaced by Solver(settings).
  • The rest moves call sites in tests, examples, the mwe and docs/src/examples.md to the new constructors, plus the changelog.

Closes #153 · task VortexStepMethod.jl-153

Solver(settings; kwargs...) takes the panel count from the wings' n_panels
and the unrefined-section count from the rows of each geometry_file, with
solver_settings as defaults that kwargs override. Solver(n_panels,
n_unrefined_sections, T) is the core the body_aero constructors now call.
solve_base! throws a DimensionMismatch when the body_aero does not match the
solver's panel and section counts.

Refs #153

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 16, 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 · 1 inline, 0 off the diff

Good

  • The card's plan matches the diff: Solver(body_aero; ...) is kept, the new Solver(n_panels, n_unrefined_sections, T) is the one constructor the others call, and solver_kwargs is the inline mapping moved out unchanged (compared hunk by hunk)
  • Dropping hasproperty(ss, :reference_point) changes nothing: src/settings.jl has no reference_point field on SolverSettings, so that branch always fell back to the default
  • check_dimensions covers every entry point: solve! (solver.jl:314) and solve (solver.jl:611) both go through solve_base!, where the check is now the first line
  • Counting wing_sections rows gives the same number as the built Wing: add_section! and refine! both set n_unrefined_sections = length(unrefined_sections), and normalize_span_order! only reorders sections
  • Settings are splatted before the caller's kwargs, so keyword arguments override them, and the density=1.0 assertion tests exactly that
  • The mismatch tests cover both silent-failure cases the card reproduced on main (one extra panel, one extra section), for solve! and solve, and check the message as well as the exception type
  • Naming n_unrefined_sections instead of the issue's n_groups follows the existing Wing field and the Solver{P, U, T} parameter

Not good

  • src/solver.jl:217Solver(settings) sums panels and sections over all settings.wings, but Wing(settings) builds only wings[1]. With a two-wing settings file (such as vsm_settings_dual.yaml once it has geometry files), the docs' Wing(settings)BodyAerodynamics([wing])Solver(settings) pattern throws DimensionMismatch. Either use the same wing set in both, or say in the docstring that Solver(settings) expects a body built from every wing.
  • n_unrefined_sections(::WingSettings) loads the geometry YAML a second time to get a count Wing(geometry_file) already derives; the card says so, but these are still two code paths for one number
  • Inside Solver(n_panels, n_unrefined_sections, ...) the positional argument hides the module function n_unrefined_sections; harmless today, but a trap for the next edit to that body
  • Line 203 of the constructor docstring ('and from its solver_settings the fields kwargs leaves unset') is hard to parse; one sentence per source of values would read better
  • Nothing tests the ArgumentError for a wing without a geometry_file, and nothing tests Solver(settings) on a settings file other than solver_test_wing
  • The card's 'found on the way' item, fail-fast: false in CI.yml, is a deliberate matrix setting, not a defect; it does not need a cleanup: PR
  • The local CI mirror result was never read and GitHub CI has not run, so the verification is incomplete

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 src/solver.jl
Solver(P, n_unrefined_sections(body_aero), T; kwargs...)

function Solver(settings::VSMSettings; kwargs...)
n_panels = sum(wing.n_panels for wing in settings.wings)

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: Solver(settings) sums panels and sections over all settings.wings, but Wing(settings) builds only wings[1]. With a two-wing settings file (such as vsm_settings_dual.yaml once it has geometry files), the docs' Wing(settings)BodyAerodynamics([wing])Solver(settings) pattern throws DimensionMismatch. Either use the same wing set in both, or say in the docstring that Solver(settings) expects a body built from every wing.

@codecov

codecov Bot commented Sep 16, 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 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 16, 2026
@1-Bort-1 1-Bort-1 added agent:ci Agent task state agent:review Agent task state and removed agent:running Agent task state agent:ci Agent task state labels Sep 16, 2026
@1-Bort-1

1-Bort-1 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

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

@1-Bart-1

Copy link
Copy Markdown
Member

Mark the body_aero constructor deprecated (warn on use), but keep it.

@1-Bort-1 1-Bort-1 added agent:queued Agent task state agent:running Agent task state and removed agent:review Agent task state agent:queued Agent task state labels Sep 16, 2026
Solver(body_aero; kwargs...) and Solver(body_aero, settings) keep working
but warn on use, pointing to Solver(settings) and
Solver(n_panels, n_unrefined_sections). make_dual_shadow, the examples,
the docs example and the tests build their solvers with those instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@1-Bort-1 1-Bort-1 added agent:queued Agent task state agent:running Agent task state and removed agent:queued Agent task state labels Sep 18, 2026
Comment thread examples/billowing.jl Outdated
function make_solver(body_aero)
Solver(body_aero;
function make_solver(wing)
Solver(wing.n_panels, wing.n_unrefined_sections;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why not use the settings-based constructor in the examples

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.

Done in da666bf: billowing.jl now builds both solvers with Solver(settings). It couldn't before because vsm_settings_coarse.yaml pointed at the 37-section aero_geometry.yaml, while the example flies the 10-section coarse file, so Solver(settings) would have been the wrong size. The settings file now names the coarse geometry. Same 20 solver fields and bit-identical CL/CD as before. The examples still on counts (rectangular_wing, stall_model, bench, linearize_check, ram_air_kite, obj_to_yaml_kite) build their wings in code and have no settings file to read.

1-Bort-1 and others added 2 commits September 18, 2026 12:29
CHANGELOG.md: keep main's Added/Changed/Fixed sections with this branch's
entries in each. The linearize_body helper main added called the
deprecated Solver(body_aero; ...); it now sums the panel and unrefined-section
counts over the wings, as solve_wings beside it does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The coarse V3 settings file named the 37-section aero_geometry.yaml while
it describes, and the example flies, the 10-section coarse discretisation.
It now names that file, so the example loads it as VSMSettings and builds
both solvers from it instead of mapping solver_settings by hand.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@1-Bort-1 1-Bort-1 added agent:ci Agent task state agent:review Agent task state and removed agent:running Agent task state agent:ci Agent task state labels Sep 20, 2026
@1-Bart-1
1-Bart-1 merged commit 0c94c6d into main Sep 20, 2026
7 checks passed
@1-Bart-1
1-Bart-1 deleted the agent/153-remove-the-parameter-body-aero-from-the- branch September 20, 2026 18:52
@1-Bort-1 1-Bort-1 added agent:done Agent task state and removed agent:review Agent task state labels Sep 20, 2026
1-Bort-1 added a commit that referenced this pull request Sep 20, 2026
…-and

The only conflict is the Added list in CHANGELOG.md: both sides' entries are
kept, this branch's stability_derivatives/trim_angle and main's Solver(settings)
from #340.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1-Bort-1 added a commit that referenced this pull request Sep 20, 2026
#340 deprecated Solver(body_aero); trimmable_wing now returns the body and a
solver built from wing.n_panels and wing.n_unrefined_sections, as the rest of
the suite does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:done Agent task state

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove the parameter body_aero from the constructor of the Solver

2 participants