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 (5 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>
|
Done in d165c7f: both Solver(body_aero; kwargs...) and Solver(body_aero, settings) still work but warn on use and name the replacement. Nothing in src, tests, examples or docs calls them any more. |
…the- Main's #352 added is_with_viscous_drag_correction to the deprecated Solver(body_aero, settings); it now travels through solver_kwargs, so Solver(settings) carries it too. Its new test builds its solvers from panel and section counts rather than the deprecated constructor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…the- Main's #349 renamed the apparent-wind locals to va_vec in the lines this branch moved to Solver(n_panels, n_unrefined_sections); both kept. 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>
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)main:test/solver/test_viscous_drag_correction.jl's new "solver settings switch it on" red with the field removed fromsolver_kwargs, green with it. After the second merge (28d4e11, Name the apparent wind va, va_vec and va_dist everywhere except the public API #349'sva_vecrename), that file withtest/solver/test_solver.jl,test/body_aerodynamics/test_results.jlandtest/body_aerodynamics/test_body_aerodynamics.jl: 5048/5048, no warning in the output;mwes/mwe_warntype.jlrunstest/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.jl7/7 (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 meshesmain(12c11f4, Bound and trailing vortices share one segment kernel, so the trailing core induces azimuthal velocity #351 and linearize and make_dual_shadow take a BodyAerodynamics with several wings #355): the only conflict wasCHANGELOG.md. Thelinearize_bodytest helper linearize and make_dual_shadow take a BodyAerodynamics with several wings #355 added calledSolver(body_aero; ...), so it now sums the counts over the wings assolve_wingsbeside it does. On Julia 1.13.0:test/body_aerodynamics/test_body_aerodynamics.jl4885/4885,test/filament/test_bound_filament.jl225/225,test/solver/test_solver.jl57/57,test/solver/test_forwarddiff.jl7/7, no "deprecated" in the outputDocumentationjob on da666bf) · no REUSE in this repo · up to date withmainat 086ee32Julia 1.12 - ubuntu. Before that, oneJulia 1.12cell per run failed the test_forwarddiff.jl's POLAR_MATRICES check flakes at ~4%, and the oper #287 check (test/solver/test_forwarddiff.jl:95). The PR run failed on ubuntu (0.046025390626216266), and a freshworkflow_dispatchon the branch failed on Windows (0.046430340012943265).mainat 086ee32 fails the same check on its own push run, with that same Windows value bit for bit. Every failing cell built the NeuralFoil table whosenorm_fdis 2.6237859245…. Every cell that built the other table (2.6186324991…) passes, withnorm_fwd2.618632581116621 on branch andmainalike. Amaindispatch passed all cells because its Windows runner built that other table. Not this diff; the evidence is on test_forwarddiff.jl's POLAR_MATRICES check flakes at ~4%, and the oper #287. The re-run built the 2.6186… table and passed. Earlier: 28d4e11 all jobs passed on the first attempt; d165c7f needed a second attempt atJulia 1.12 - ubuntuafter the test_forwarddiff.jl's POLAR_MATRICES check flakes at ~4%, and the oper #287 flake (test/solver/test_forwarddiff.jl:87,0.0420681631196805)examples/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. The changelog's two### Fixedsections from the merge withmainare now one.Closes #153 · task
VortexStepMethod.jl-153