Conversation
`velocity_3D_bound_vortex!` and `velocity_3D_trailing_vortex!` were two copies of the same Biot–Savart segment formula that differ only in how the core radius is chosen. #241 corrected the in-core projection in one copy only, so inside its core the trailing vortex still placed the field point along r1 x r0 (azimuthal) and induced a radial velocity. Both now compute their core radius and call `velocity_3D_vortex_segment!`, which projects radially. Outside the core the trailing velocity is bit-identical to before; inside it only the direction changes. The two `@debug` calls in the in-core branch are dropped: in the shared kernel they would have made the trailing in-core path ~2.5x slower. Fixes #333 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
- One source per equation: both public functions now only choose
epsilonand callvelocity_3D_vortex_segment!, so the Biot–Savart formula exists once; checked by readingsrc/filament.jlon the branch - The fix is correct: the in-core branch now puts the point on the core boundary along the radial vector
r1 - (r1·r0)r0/|r0|². The old trailing code usedr1×r0(andr2×r0, the same direction), which is azimuthal; checked against the removed lines - No new aliasing: the kernel now uses
work_vectors[1..7]where the old trailing code used[2..8], but the only src callers (panel.jl:682-703) passevaluation_point, a panel field, never a work vector - Unifying the two copies instead of patching the second is what §2 asks for, and the card gives the reason and the parity numbers (bit-identical outside the core, 3.6e-15 for bound)
- The test fails before the fix (15 failures = 3 in-core radii × 5 angles) and passes after. Its radii 0.99/1.01 × core radius cover both sides of the branch switch
- Docstrings fixed as a side benefit: the missing
!in both signatures, trailing whitespace, and each wrapper now says in one line how it picks its core radius - The changelog line states the user-visible change and its millimetre-scale reach; the new private function is listed in
private_functions.md, somakedocsstays green - The axial-vs-perpendicular core radius question is argued in the card and left unchanged in code, as the plan said;
axial_distancenow names what the old comment wrongly called perpendicular
Not good
test/filament/test_bound_filament.jl:255— This testset nearly copies 'Velocity is azimuthal (perpendicular to axis and radius)' at line 179, changing only the function called and the core radius. Since both functions now share one kernel, one loop over (function, core radius) pairs would state the invariant once instead of keeping two copies in step (§2, §7).velocity_3D_trailing_vortex!writesr1intowork_vectors[1]and the kernel recomputes it right away; this costs ~1 ns and is named in the card, but it is a wasted line in the hot loopsrc/filament.jl:210: the rewritten semi-infinite comment mostly restatesepsilon = sqrt(... abs(d_r1_Vf) * nVf / v_a); by the §3 deletion test it could go- The card says 'the comments in both trailing kernels now say axial distance', but in the finite kernel it is now a variable name, not a comment. The card is slightly off; the code is fine
- The new testset checks direction only. Magnitude and solid-body scaling for the trailing path are covered only indirectly, through the bound tests on the shared kernel
- Test locals
p,v,dare one-letter names (§3); they copy the neighbouring test, andr0 = [1.0, 0.0, 0.0]repeatsfilament.r0 - Removing the two
@debuglines is a small behaviour change outside the bug fix; it is named and measured in the card, so it is acceptable, but it is not in the changelog
claude, rubric CLEAN_CODE.md. A different lab from the implementer
on purpose: a reviewer sharing its blind spots would not flag its mistakes.
| end | ||
| end No newline at end of file | ||
|
|
||
| @testset "Trailing vortex velocity is azimuthal inside and outside the core" begin |
There was a problem hiding this comment.
MINOR: This testset nearly copies 'Velocity is azimuthal (perpendicular to axis and radius)' at line 179, changing only the function called and the core radius. Since both functions now share one kernel, one loop over (function, core radius) pairs would state the invariant once instead of keeping two copies in step (§2, §7).
There was a problem hiding this comment.
Fixed in 465d199: one testset now loops over both vortex functions, each at its own core radius.
|
Local full suite: PASS (7 min, Julia 1.12.7, one cell of the matrix) |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The bound and trailing azimuthal testsets differed only in the function called and its core radius; loop over both instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TL;DR
velocity_3D_trailing_vortex!andvelocity_3D_bound_vortex!were two copies of the same Biot–Savart segment formula, differing only in how the core radius is picked. #241 fixed the in-core projection in one copy; both now pick their core radius and call one kernel,velocity_3D_vortex_segment!, so the trailing core induces azimuthal velocity like the bound one.What was wrong
Inside its core the trailing vortex moved the field point onto the core boundary along
r1 × r0, which points azimuthally. The Biot–Savart velocity at that point is radial. Reproduced onmainwith the issue's setup (filament (0,0,0)→(1,0,0), Γ = 1,va = 1e-4, point at x = 0.5, distance r along y):The magnitude was already right; only the direction changes.
Why one kernel rather than patching the second copy
The bug existed because a fix reached one of two copies. Apart from the core radius (
core_radius_fraction · |r0|for bound, the Oseensqrt(4 α₀ ν |r1·r0|/|r0| / va)for trailing), the in-core and out-of-core branches were the same formula. Now the two public functions are three lines each and the formula exists once.src/filament.jlis 56 lines shorter.Checked against a copy of
main'sfilament.jlloaded in the same session, over 20 000 random filaments, points andvafrom 1e-5 to 10:The
@debuglines are goneThe bound kernel logged two
@debugmessages in its in-core branch. In the shared kernel they made the trailing in-core path ~2.5× slower (35.9 → 92 µs per 1000 calls, in one run whose conditions held), so I removed them. They only printed at debug level.Core radius: axial or perpendicular distance
The issue asks which is right. I'd keep the axial distance and changed nothing here.
|r1·r0|/|r0|is how far along the trailing filament the point sits, i.e. the vortex aget = s / vain Lamb–Oseen'sr_c = sqrt(4 α₀ ν t). Measuring from the perpendicular distancedinstead givesε = sqrt(c·d)withc = 4 α₀ ν / va, so the in-core testd < εbecomesd < c: a fixed core of 7.4 µm at 10 m/s that never grows along the wake, and inside it the velocity scales withsqrt(d/c)instead of linearly. awegroup/Vortex-Step-Method@ed6f158 made that switch as a side effect of reusing the namer_perpfor the radial vector. Its commit message argues only for the projection, not the core radius. What was wrong here was the comment, which called the axial length "the perpendicular component". The trailing function now names that lengthaxial_distance, and the semi-infinite kernel's comment says "axial distance".Verification
trailing r=0.05 vel=[0.0, 0.01357, 0.0](all radial inside the core)test/filament/test_bound_filament.jl: the azimuthal testset loops over bothvelocity_3D_bound_vortex!andvelocity_3D_trailing_vortex!, each at six radii (0.25–2 × its core radius) and five angles. Red against the pre-fixfilament.jl(trailing 70/90: the four in-core radii × five angles fail), green after (225/225)main(the Name the apparent wind va, va_vec and va_dist everywhere except the public API #349v_a→varename was the only conflict, insrc/filament.jl), green in a fresh juliaserver session:filament/test_bound_filament.jl225/225,filament/test_semi_infinite_filament.jl58/58,panel/test_panel.jl26/26,verification/test_verification.jl22/22,body_aerodynamics/test_body_aerodynamics.jl4870/4870. Before the merge alsosolver/test_solver.jlall,solver/test_forwarddiff.jl7/7,bench.jlallocation tests 21/21makedocs, no deploy) · up to date with main · REUSE: n/a (repo does not use it)test/solver/test_forwarddiff.jl:87at0.0460232000169766 < 0.0001(norm_fwd = 3.2832728432488474,norm_fd = 3.285180977112122).mainat 83867a3, this branch's merge base, failed that cell with the same three numbers to the last digit (run 35197849038), and the cells that passed show the same norms asmain's passing cells. That makes it flake test_forwarddiff.jl's POLAR_MATRICES check flakes at ~4%, and the oper #287, not this branch. The re-run passed and no code changed. The run on 30b0756 hit the same flake and also passed on re-runagent ci-local, Julia 1.12.7) on 3dc74fc: PASS in 8 min, 6638 passed, 1 broken (the existing@test_skipintest/ram_geometry/test_kite_geometry.jl)main's kernels vs this branch interleaved in one session, two runs whose conditions held, 0 allocations on both sides. Outside the core, where a solve evaluates: bound 17 → 17 ns, trailing 27 → 28 ns. Inside the core: bound 92–97 → 35–38 ns (the@debuglines), trailing 35–37 → 39–40 ns (+10%, the radial vector it now forms). Isolated 2× spikes on both sides are left out of these ranges.Box: 11–12 of 20 cores busy, 2.8–3.6 GHz, ≥20 GB free
velocity_3D_trailing_vortex!now formsr1twice per call, once for the core radius and once in the kernel. The outside-core benchmark above is the check on that.Scope
+78 / −123 across 4 files.
src/filament.jlis the kernel extraction plus the comment rewrites.test/filament/test_bound_filament.jlextends the existing azimuthal testset to the trailing vortex and importsALPHA0,NU.CHANGELOG.mdgets one Fixed line, andmain's two Fixed headings under Unreleased become one (tidy-up, no entry changed). The new private function is listed ondocs/src/private_functions.md.Closes #333 · task
VortexStepMethod.jl-333