Build a Solver from VSMSettings or from panel and section counts, deprecate the body_aero constructors, and check the sizes in solve! - #340
Conversation
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
left a comment
There was a problem hiding this comment.
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 newSolver(n_panels, n_unrefined_sections, T)is the one constructor the others call, andsolver_kwargsis the inline mapping moved out unchanged (compared hunk by hunk) - Dropping
hasproperty(ss, :reference_point)changes nothing:src/settings.jlhas noreference_pointfield onSolverSettings, so that branch always fell back to the default check_dimensionscovers every entry point:solve!(solver.jl:314) andsolve(solver.jl:611) both go throughsolve_base!, where the check is now the first line- Counting
wing_sectionsrows gives the same number as the builtWing:add_section!andrefine!both setn_unrefined_sections = length(unrefined_sections), andnormalize_span_order!only reorders sections - Settings are splatted before the caller's
kwargs, so keyword arguments override them, and thedensity=1.0assertion tests exactly that - The mismatch tests cover both silent-failure cases the card reproduced on
main(one extra panel, one extra section), forsolve!andsolve, and check the message as well as the exception type - Naming
n_unrefined_sectionsinstead of the issue'sn_groupsfollows the existingWingfield and theSolver{P, U, T}parameter
Not good
src/solver.jl:217—Solver(settings)sums panels and sections over allsettings.wings, butWing(settings)builds onlywings[1]. With a two-wing settings file (such asvsm_settings_dual.yamlonce it has geometry files), the docs'Wing(settings)→BodyAerodynamics([wing])→Solver(settings)pattern throwsDimensionMismatch. Either use the same wing set in both, or say in the docstring thatSolver(settings)expects a body built from every wing.n_unrefined_sections(::WingSettings)loads the geometry YAML a second time to get a countWing(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 functionn_unrefined_sections; harmless today, but a trap for the next edit to that body - Line 203 of the constructor docstring ('and from its
solver_settingsthe fieldskwargsleaves unset') is hard to parse; one sentence per source of values would read better - Nothing tests the
ArgumentErrorfor a wing without ageometry_file, and nothing testsSolver(settings)on a settings file other thansolver_test_wing - The card's 'found on the way' item,
fail-fast: falsein CI.yml, is a deliberate matrix setting, not a defect; it does not need acleanup: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.
| 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) |
There was a problem hiding this comment.
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 Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Local full suite: PASS (7 min, Julia 1.13.0, one cell of the matrix) |
|
Mark the body_aero constructor deprecated (warn on use), but keep it. |
…parameter-body-aero-from-the-
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>
| function make_solver(body_aero) | ||
| Solver(body_aero; | ||
| function make_solver(wing) | ||
| Solver(wing.n_panels, wing.n_unrefined_sections; |
There was a problem hiding this comment.
why not use the settings-based constructor in the examples
There was a problem hiding this comment.
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.
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>
…nce point Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-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>
#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>
TL;DR
Solver(settings)andSolver(n_panels, n_unrefined_sections)build a solver without aBodyAerodynamics.Solver(body_aero; kwargs...)andSolver(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 aDimensionMismatchnaming 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)sumsn_panelsoversettings.wingsand counts thewing_sectionsrows in each wing'sgeometry_file, which is the number of sectionsWing(geometry_file)creates. Itssolver_settingssupply defaults, and keyword arguments override them.Bart asked on the thread to keep the
body_aeroconstructor but mark it deprecated. Both forms that take abody_aeronow callBase.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 deprecatedSolver(body_aero, settings)too, since the issue is about thebody_aeroparameter 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_shadowusesSolver(P, U, TD; ...), also for the several-wing body linearize and make_dual_shadow take a BodyAerodynamics with several wings #355 lets it take.Solver(settings). That includesexamples/billowing.jl, which used to mapsolver_settingsinto keyword arguments by hand.data/TUDELFT_V3_KITE/vsm_settings_coarse.yamlnamed the 37-sectionaero_geometry.yaml, but it describes and the example flies the 10-sectionaero_geometry_coarse_discretisation.yaml, so it now names that file. Nothing else reads it.Solver(wing.n_panels, wing.n_unrefined_sections; ...)with the same keyword arguments as before.examples/V3_neuralfoil.jlpairs a NeuralFoil wing with the CFD settings' solver fields. It now loads a secondVSMSettingspointing at the generated YAML, asexamples/V3_kite.jlalready does.The mapping from
SolverSettingsto keyword arguments was written out inline inSolver(body_aero, settings). It is nowsolver_kwargs, shared by both settings constructors. #352 addedis_with_viscous_drag_correctionto that inline mapping after this branch was opened; mergingmainmoved it intosolver_kwargs, soSolver(settings)carries it, and a testset intest/solver/test_viscous_drag_correction.jlfails if it is dropped. That file's own solvers now come from panel and section counts. Thehasproperty(ss, :reference_point)branch is gone:SolverSettingshas no such field, so it always fell back to the default.The issue calls the second size
n_groups. It isn_unrefined_sectionshere, the nameWingand theSolver{P, U, T}parameter already use.How a mismatch failed before
I ran
solve!onmainwith a solver one size off from thesolver_test_wingbody:DimensionMismatch: array could not be broadcast to match destination, raised partway through the solve.cl_unrefined_distcame back with one entry left at zero.check_dimensionsruns first insolve_base!, whichsolve!,solveandlinearizeall go through. It measured 0 bytes allocated.Where I'd push back
Without a
body_aeroconstructor, a body that wasn't built from settings means spelling outwing.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 aVSMSettings:examples/V3_neuralfoil.jlneeded 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 ann_unrefined_sectionsfield inWingSettings, but that is a second copy of a number that could drift from the file. A wing given onlyobj_file/dat_filegets anArgumentError, asWing(settings)does.Found on the way, left for a
cleanup:PR:.github/workflows/CI.yml:22setsfail-fast: false.Verification
main'ssrc/(outputs above)test/solver/test_solver.jl, sizes and new constructors: red onmain'ssrc/("3 passed, 0 failed, 4 errored"), green aftertest/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 withis_with_viscous_drag_correctionremoved fromsolver_kwargs, green with ittest/body_aerodynamics/test_body_aerodynamics.jltest/body_aerodynamics/test_results.jl30/30test/solver/test_flow_curvature.jltest/solver/test_moment_units.jl17/17test/solver/test_forwarddiff.jl(runsmake_dual_shadow)test/solver/test_unrefined_dist.jl40/40test/verification/test_verification.jl22/22test/solver/test_backend_comparison.jl15/15test/plotting/test_plotting.jl58/58pyramid_model,rectangular_wing,stall_model,billowing,linearize_check,benchexamples/billowing.jlonSolver(settings)(da666bf), run next to its previous version on Julia 1.13.0: both solversSolver{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%" becausebillowing_percentageis aFloat64inWingSettings.test/bench.jl(its packages are in neither local environment; the suite runs it), and theV3_kite,V3_neuralfoil,ram_air_kite,obj_to_yaml_kiteexamples, which generate polars from meshesmainat 7724c0d, on Julia 1.13.0:test/solver/test_forwarddiff.jl10/10,test/solver/test_solver.jl57/57,test/body_aerodynamics/test_body_aerodynamics.jl4903/4903 — 4970 passes, nothing failed or errored, no "deprecated" in the outputDocumentationjob) · no REUSE in this repo · up to date withmainat 7724c0dDocumentation,Julia 1.12on ubuntu, Windows and macOS,Julia 1.13on ubuntu,Test end-user and developer setup,codecov/patch. Every earlier commit here needed a re-run of oneJulia 1.12cell, 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 withnorm_fd2.6237859245…, onmainas 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 onmain, and this is the first run of the branch without a re-runexamples/V3_neuralfoil.jl'sSolver(settings_nf)is not run here. If the generated YAML's section count differed from the wing's,solve!would throwDimensionMismatchrather 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_sectionsandcheck_dimensions.src/yaml_geometry.jl+12 counts sections from aWingSettings.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 bySolver(settings).docs/src/examples.mdto the new constructors, plus the changelog.Closes #153 · task
VortexStepMethod.jl-153