solve! fills VSMSolution with everything solve() returned; solve() and calculate_results are removed - #392
solve! fills VSMSolution with everything solve() returned; solve() and calculate_results are removed#3921-Bort-1 wants to merge 5 commits into
Conversation
solve! now fills lift/drag/side, cl/cd/cs and their inflow-projected distributions, alpha_uncorrected, va_ref_vec, q_ref, rey, the areas, span, projected aspect ratio and both centers of pressure, allocation-free. Plotting, generate_polar_data, examples, docs and tests read the struct; solve() and calculate_results are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The setting's only consumer was solve()'s short Dict; it now keeps solve! from filling the projections, span and centers of pressure. Adds the changelog entries and wraps the lines over 92 characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1-Bort-1
left a comment
There was a problem hiding this comment.
Independent review (advisory)
Verdict: APPROVE WITH COMMENTS · 2 inline, 0 off the diff
Good
- One codepath:
calculate_resultsandsolveare deleted, andanalysis_fields!only projects thef_body_3Dthatcalc_forces!already built, so forces are not computed a second time (checked in src/solver.jl:648-689). - The mapping from each dictionary key to its field is complete and matches the card:
alpha_geometric_distis already filled insolve_base!/calc_forces!(src/solver.jl:395-442), so the old copy-back insolvehad no job left. calc_only_f_and_gammanow has a new meaning but is not a new option;linearizepasses it through to the Dual solver (src/solver.jl:1281), so the skip also applies there.- Deleted tests are covered elsewhere: the
solve!testset in test_moment_units still checks the k³ scaling ofmoment,m_body_3Dandmoment_coeffs, and the Dict-vs-struct parity tests go away together with the second path. find_center_of_pressure!usesbody_aero.work_vectorstyped onT, so the Dualbody_aero_dinlinearizedoes not narrow Duals into Float64 buffers.plot_combined_analysisplotsresults_spanwise_list(ext line 1363) beforegenerate_polar_dataoverwritessolver.sol(line 1392), so reusingsolver.soldoes not corrupt the spanwise plots.
Not good
src/solver.jl:626— With the defaultcalc_only_f_and_gamma=false, everysolve!in a KiteModels loop now runs the whole-body panel-intersection search, both center-of-pressure passes and the projections, which the removed comment kept out ofsolve!on purpose; the card has no timing to back this up, so either get a quiet-box number or say in the card that dynamic users should set the flag.test/solver/test_solver.jl:237— The skip test only shows that the fields stay at their zero defaults on a fresh solver, which would also pass if the flag skipped too much; the ¹ contract is 'kept at its last value', and that stays untested (solve with the flag off, flip it, solve at another inflow, assertclunchanged).- CHANGELOG lines 25-27 are a countermand that reads backwards ("no longer corrects … as
solve!already did;solvedid"); state that LLT never appliescorrect_aoa, and give the size of the shift. analysis_fields!drops the oldva_panel > 0/ total-areaArgumentErrorguards, so a panel with zero inflow now silently gives Inf/NaN incl_distributioninstead of throwing.compute_panel_center_of_pressures!got a!and a new signature but no docstring, while its siblingfind_center_of_pressure!got one and a place in private_functions.md.- test/solver/test_moment_units.jl leaves an empty line before the closing
endwhere thesolvetestset was removed. flat_plate_aerosits in the same file as the existingflat_plate_wing(test_solver.jl:271); the card does not say it searched for existing helpers, which §2 asks for.- In test_body_aerodynamics,
results_NEWandloop_solare now the same object (loop_solver.sol), so later asserts onresults_NEWread whatever the lastsolve!on that solver left there; it works today only because both calls use identical inputs.
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.sol.alpha_uncorrected .= alpha_dist | ||
| solver.sol.area_all_panels = area_all_panels | ||
| solver.sol.projected_area = projected_area | ||
| solver.is_only_f_and_gamma_output || |
There was a problem hiding this comment.
MINOR: With the default calc_only_f_and_gamma=false, every solve! in a KiteModels loop now runs the whole-body panel-intersection search, both center-of-pressure passes and the projections, which the removed comment kept out of solve! on purpose; the card has no timing to back this up, so either get a quiet-box number or say in the card that dynamic users should set the flag.
There was a problem hiding this comment.
Measured in 8a35a07's round, quiet window (bench-end: conditions held), flag off vs on in one process: solve! on a 40-panel rectangle 1.018× (IQR 1.012–1.026), on the V3 kite below the noise; calc_forces! alone 1.33–1.43×. The numbers are in the card, which also says a loop that never reads these fields can set calc_only_f_and_gamma.
| skipping = Solver(length(body_aero.panels), 2; is_only_f_and_gamma_output=true) | ||
| skipped = solve!(skipping, body_aero) | ||
| @test skipped.force ≈ sol.force | ||
| @test skipped.cl == 0.0 |
There was a problem hiding this comment.
MINOR: The skip test only shows that the fields stay at their zero defaults on a fresh solver, which would also pass if the flag skipped too much; the ¹ contract is 'kept at its last value', and that stays untested (solve with the flag off, flip it, solve at another inflow, assert cl unchanged).
There was a problem hiding this comment.
Fixed in 8a35a07: the test solves with the flag off, turns it on, solves at 10° instead of 5° and asserts cl, lift and cl_distribution are unchanged while force grew. It fails with the gate removed (cl 0.838 vs 0.420).
|
Local full suite: PASS (10 min, Julia 1.13.0, one cell of the matrix) |
… review The skip test now flips the flag on a solved solver and asserts the analysis fields keep their values at a new inflow. Adds the compute_panel_center_of_pressures! docstring, says where cl_distribution is NaN and that correct_aoa is VSM-only, restates the CHANGELOG line, and solves the loop case in test_body_aerodynamics once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Did you locally build the docs and make sure they still work? And run the examples? And check if the plotting functions still work? GLMakie has display on the box. |
…model solve! returns the solver's own VSMSolution, and plot_polars re-solves the same solver over the whole angle range, so the kept `results` held the 25° solution by the time plot_distribution drew it under the configured angle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Yes to all three. The docs build is clean. Every example runs under GLMakie except billowing.jl, which fails the same way on main (#398). The V3 plots render the same before and after. Running the examples turned up a bug: V3_kite.jl and pyramid_model.jl drew the 25° polar-sweep solution under the configured angle. Fixed in e9bd10c. |
Main's KA-frame docstrings meet this branch's removal of solve and calculate_results: the removed functions stay removed, the solve! docstring takes the KA wording, and the CHANGELOG keeps both sides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TL;DR
VSMSolutionnow carries every quantity thesolve()dictionary held, filled on everysolve!without allocating. Plotting,generate_polar_data, the examples, docs and tests read the struct, andsolve()andcalculate_resultsare gone (BREAKING). Before this, there were two codepaths computing the same forces, and they had already drifted apart.What changed
New fields, named after the old dictionary keys in lowercase as agreed on the thread:
lift,drag,side,cl,cd,cs,cl_distribution,cd_distribution,cs_distribution,alpha_uncorrected,va_ref_vec,q_ref,rey,area_all_panels,projected_area,wing_span,aspect_ratio_projected. The two center-of-pressure fieldscenter_of_pressureandpanel_cp_locationsare now filled too. Keys that already had a field map onto it:Fx… →force,Mx… →moment,cfx… →force_coeffs,cmx… →moment_coeffs,F_distribution→f_body_3D,M_distribution→m_body_3D,alpha_at_ac→alpha_dist,alpha_geometric→alpha_geometric_dist. The CHANGELOG lists this mapping for users.calc_forces!is now the only place forces are assembled. The projections that only the dictionary had (lift/drag/side on the reference inflow, and per-panel coefficients on each panel's own inflow), the span and both centers of pressure moved into one function,analysis_fields!. It projects thef_body_3Dthatcalc_forces!has already computed, so nothing is computed twice.Uwe asked for a way to skip the extra fields. The existing
calc_only_f_and_gammasetting now does that: it leaves them at their last value. Its only consumer was the short dictionary path ofsolve(), so it was about to become dead code. Giving it this meaning adds no new option and removes none. With the default (false), eachsolve!also computes the panel-intersection search, both centers of pressure and the projections. That adds 1.33–1.43× tocalc_forces!, but only 1.8 % to a wholesolve!on a 40-panel rectangle; on the V3 kite the difference is below the run-to-run noise. A simulation loop that never reads these fields can still set the flag.Where the numbers moved
I compared the old
solve()dictionary with the newsolve!struct on 24 configurations: rectangle, two perpendicular wings, and a dihedral inviscid wing; × VSM/LLT; ×correct_aoa; × {flow curvature, viscous drag, attached-trailed force}. That is every key in every configuration. In 18 configurations every key agrees to ≤ 2.2e-13 relative.The other 6 are LLT with
correct_aoa.solve!only ever corrected the angle of attack for VSM, whilesolve()corrected it for LLT too. The struct keeps thesolve!behaviour, which is the one KiteModels runs. Against the oldsolve(),clmoves ≤ 0.34 %,cd≤ 0.43 %,cmx≤ 4.5 %, andalpha_at_acchanged sign on some panels of the rotated wing.Found on the way
find_center_of_pressurewarned whenever the line of action missed every panel. That happened in 7 of the 24 configurations above, and would now happen on everysolve!in a simulation loop. It now leavesNaN, as documented on the field, and no longer warns. This removes the warning Warning: No intersection found with any panel in center-of-pressure calculation. #221 reports forpyramid_model.jl. The cause is the same: that model's line of action misses the canopy.spanwise_extentallocations: it built a generator that allocated 208 B per call.calc_forces!must stay at 0 B (existing tests assert it), so I rewrote it as a plain loop. It also runs insolve_base!for anELLIPTICinitial circulation, sosolve!on the V3 kite drops from 12 672 B to 2 080 B per call.calculate_resultsthrewArgumentErroron a panel with zero inflow or a body with zero area.solve!runs in simulation loops, so it does not throw there. A panel without inflow now getsNaNin its*_distributionentries, as the field docstring says. Zero total area already gaveInfinforce_coeffsonmain.solve!overwrites its solution:solve!returnssolver.sol, and the next call overwrites it.plot_combined_analysis,plot_polarsandgenerate_polar_datanow leavesolver.solat their last angle. The CHANGELOG andtips_and_tricks.mdsay so.V3_kite.jlandpyramid_model.jl. Both keptresults = solve!(solver, …), then ranplot_polarson the same solver, and only then drewplot_distribution([results]). So the spanwise plot showed the last polar angle under the configured angle's title: V3 kitecl0.7123 → 0.9398, with geometric α near 25° under "alpha 7.4". The dictionary had hidden this because it was a copy. e9bd10c draws the spanwise plot before the sweep in both. The other examples, the docs examples andplot_combined_analysis(spanwise panels drawn before its sweep) already did it in that order.billowing.jlfails under GLMakie, before and after this branch: it is the only example withSAVE_ALL = true. Under GLMakie, any plot function called withis_save=true, is_show=truethrowsGLMakie can not display a scene in multiple Screens. It reproduces on the base 675f0e7 in a fresh session, so it is not caused here. I filed it as Plot functions throw under GLMakie when is_save and is_show are both true #398.Where I would push back
cl_distribution(projected on the inflow, including every force term) now sits next to the existingcl_dist(airfoil coefficient at the local alpha). The docstring says which is which. Renaming either one is out of scope here.Verification
FieldError: type VSMSolution has no field cl/alpha_uncorrected, anditerate(::Nothing)for the center of pressure. They pass on this branch. New testset:solve! fills the reference inflow and the centers of pressure(reference inflow, q, Re, both centers of pressure, NaN through a gap, andcalc_only_f_and_gammakeepingcl,liftandcl_distributionwhen the inflow changes).test_solver,test_attached_trailed_force,test_viscous_drag_correction,test_wing_directions,test_moment_units,test_forwarddiff,test_body_aerodynamics,test_results,test_unrefined_dist,test_stability,test_flow_curvature,test_verification,test_settings,test_plotting(CairoMakie) andbench.jl. Thecalc_forces!0-byte assertions pass. After the review round (8a35a07) I re-rantest_solver,test_body_aerodynamicsandtest_moment_unitsand the docs build; all pass.docs/make.jl, 77 s), with only the existing size-threshold warnings.examples/menu.jl --run-allfrom the repo root, with GLMakie on the box's display. Every example runs exceptbillowing.jl, which is Plot functions throw under GLMakie when is_save and is_show are both true #398 and fails the same way on the base. After e9bd10c I re-ranV3_kite.jlandpyramid_model.jl; both ran.plot_geometry,plot_distribution,plot_polarsandplot_combined_analysisfor the V3 kite to PNG under GLMakie, with the same inputs on the base (thesolve()dictionary) and on this branch. Six of the seven PNGs are byte-identical. The seventh,plot_distribution, looks the same; its file differs by one byte becausecldiffers by 3e-16.main(e493bb1), re-run in juliaserver:test_polar_vectors(new onmain),test_solver,test_body_aerodynamics,test_results,test_moment_units,test_wing_directions,test_verification,test_plottingandbench.jl: all PASS. Docs build clean.Pkg.test(), Julia 1.13, one cell) at e493bb1: PASS (9 min, exit 0). GitHub CI at e493bb1: PASS on all seven checks.calc_forces!: 0 B → 0 Bsolve!on a 40-panel rectangle: 1 760 B → 1 760 Bsolve!on the V3 kite: 12 672 B → 2 080 Bbench-end: conditions held):solve!on a 40-panel rectangle: 156.3 µs vs 153.4 µs, ratio 1.018 (IQR 1.012–1.026)solve!on the V3 kite: ratio 0.91 (IQR 0.89–0.94), i.e. the cost is below the noisecalc_forces!alone: 1.33–1.43×conditions moved. The allocations above are the base-vs-branch evidence.calc_forces!(cl0.838 ≠ 0.420 at the new inflow), and passes with it.sol.center_of_pressure === nothing, or that keeps two results from one solver and expects them to differ.Scope
+359 / −612 across 29 files. Of this,
src+extis +183 / −415 (mostlycalculate_resultsandsolvedeleted). Tests are +85 / −126: Dict reads ported to fields, the Dict-vs-struct comparisons and thecalculate_resultsallocation benchmark deleted together with the code they covered, plus one new testset. Examples and docs are mechanicalsolve→solve!edits, plus the two movedplot_polarsblocks above. Before adding the test helperinviscid_plates_aero, I searchedtest/for inviscid rectangle helpers. The closest,flat_plate_wing, builds one wing on a stalling Breukels polar and cannot make the several gapped plates this test needs. Built on #389, now merged; e493bb1 mergesmainback in (#389, the KA-frame docstrings of #390 andborder_color). Its conflicts were docstring and CHANGELOG only:mainre-wordedsolve's andcalculate_results' docstrings, which this branch deletes, so the removal stands andsolve!'s docstring takes the KA wording. #391 (show_titleon the plots) touches the same Makie functions, so whichever lands second resolves that conflict. #393 and #396 also editcalc_forces!.Opened by
1-Bort-1, an AI agent working for @1-Bart-1.Closes #94 · task
VortexStepMethod.jl-94