From 1d6fb2408d61a4cf4cef862aa59f93ba96520e13 Mon Sep 17 00:00:00 2001 From: 1-Bort-1 <323661610+1-Bort-1@users.noreply.github.com> Date: Wed, 30 Sep 2026 14:49:59 +0200 Subject: [PATCH 1/2] Map a refined panel midway between two sections to the outboard one compute_refined_panel_mapping! kept the first section on a tie, so a mirror-symmetric three-section wing put both midway panels on the same side. Distances within 1e-9 relative now tie, and the tie goes to the section farther from the middle of the span along spanwise_direction. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 +++ docs/src/private_functions.md | 2 ++ src/wing_geometry.jl | 43 +++++++++++++++++++----- test/wing_geometry/test_wing_geometry.jl | 10 ++++++ 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b303475..1c360a56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ ### Fixed +- A refined panel exactly between two unrefined sections maps to the one farther from + the middle of the span, so a mirror-symmetric wing maps mirror panels to mirror + sections; the first section in order used to win, which puts both midway panels of a + three-section wing on the same side. - The docs define the body frame as KiteUtils' `KA` frame: x from LE to TE, y towards the right tip, z = x × y up. The separate `KB` definition, whose `Z = Y × X` pointed down, is gone; the solver's frame is unchanged. CL, CD and CS are documented as diff --git a/docs/src/private_functions.md b/docs/src/private_functions.md index d8b0c836..7b179fc6 100644 --- a/docs/src/private_functions.md +++ b/docs/src/private_functions.md @@ -99,6 +99,8 @@ unrefined_section_range panel_range deform! compute_refined_panel_mapping! +section_centre_dist2 +section_span_position compute_refined_section_interpolation! copy_sections copy_sections_to_refined! diff --git a/src/wing_geometry.jl b/src/wing_geometry.jl index c849c001..3f4c0273 100644 --- a/src/wing_geometry.jl +++ b/src/wing_geometry.jl @@ -1013,13 +1013,37 @@ function refine!(wing::AbstractWing{T}; recompute_mapping=true, sort_sections=tr end +""" + section_centre_dist2(section, x, y, z) + +Squared distance from the centre of `section` (the midpoint of its LE and TE) to +`(x, y, z)`. +""" +function section_centre_dist2(section, x, y, z) + d1 = (section.LE_point[1] + section.TE_point[1]) * 0.5 - x + d2 = (section.LE_point[2] + section.TE_point[2]) * 0.5 - y + d3 = (section.LE_point[3] + section.TE_point[3]) * 0.5 - z + return d1 * d1 + d2 * d2 + d3 * d3 +end + +""" + section_span_position(wing, section) + +Position of the centre of `section` along `wing.spanwise_direction`. +""" +section_span_position(wing, section) = + sum(wing.spanwise_direction[k] * (section.LE_point[k] + section.TE_point[k]) * 0.5 + for k in 1:3) + """ compute_refined_panel_mapping!(wing::AbstractWing) Compute the mapping from refined panels to unrefined sections by finding the closest unrefined section for each refined panel (based on section center distance). Maps each refined panel index to its corresponding unrefined section index -(1 to n_unrefined_sections). +(1 to n_unrefined_sections). Sections within `1e-9` of the closest distance, relative, +tie, and a tie goes to the one farther along `spanwise_direction` from the middle of +the span, so a mirror-symmetric wing maps mirror panels to mirror sections. Works after refinement is complete. """ function compute_refined_panel_mapping!(wing::AbstractWing) @@ -1039,6 +1063,8 @@ function compute_refined_panel_mapping!(wing::AbstractWing) return nothing end + span_middle = 0.5 * sum(extrema(section_span_position(wing, u) + for u in wing.unrefined_sections)) # For each refined panel, find closest unrefined section # using scalar arithmetic to avoid MVec3 allocations for pi in 1:n_panels @@ -1052,16 +1078,17 @@ function compute_refined_panel_mapping!(wing::AbstractWing) r2.LE_point[3] + r2.TE_point[3]) * 0.25 min_dist = Inf + for u in wing.unrefined_sections + min_dist = min(min_dist, section_centre_dist2(u, rc1, rc2, rc3)) + end closest = Int16(1) + outboard = -Inf for ui in 1:n_unref u = wing.unrefined_sections[ui] - uc1 = (u.LE_point[1] + u.TE_point[1]) * 0.5 - uc2 = (u.LE_point[2] + u.TE_point[2]) * 0.5 - uc3 = (u.LE_point[3] + u.TE_point[3]) * 0.5 - d1 = rc1 - uc1; d2 = rc2 - uc2; d3 = rc3 - uc3 - dist = d1 * d1 + d2 * d2 + d3 * d3 - if dist < min_dist - min_dist = dist + section_centre_dist2(u, rc1, rc2, rc3) <= min_dist * (1 + 1e-9) || continue + offset = abs(section_span_position(wing, u) - span_middle) + if offset > outboard + outboard = offset closest = Int16(ui) end end diff --git a/test/wing_geometry/test_wing_geometry.jl b/test/wing_geometry/test_wing_geometry.jl index c78a5dba..3160ac05 100644 --- a/test/wing_geometry/test_wing_geometry.jl +++ b/test/wing_geometry/test_wing_geometry.jl @@ -624,6 +624,16 @@ end end end + + @testset "a panel midway between two sections maps outboard" begin + wing = Wing(10; spanwise_distribution=LINEAR) + add_section!(wing, [0.0, 1.0, 0.0], [1.0, 1.0, 0.0], INVISCID) + add_section!(wing, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], INVISCID) + add_section!(wing, [0.0, -1.0, 0.0], [1.0, -1.0, 0.0], INVISCID) + refine!(wing) + # panels 3 and 8 are centred at y = ±0.5, midway between two sections + @test wing.refined_panel_mapping == Int16[1, 1, 1, 2, 2, 2, 2, 3, 3, 3] + end end @testset "UNCHANGED preserves sections exactly" begin From 7c8910ecaf849944616f357169805994d0e748b7 Mon Sep 17 00:00:00 2001 From: 1-Bort-1 <323661610+1-Bort-1@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:19:39 +0200 Subject: [PATCH 2/2] Compute the tie-break span positions with scalar arithmetic, so the panel mapping allocates nothing refine! on the 20-panel LINEAR wing of test/bench.jl was back at 133 allocations (limit 70): the generator sum and extrema in the span-position tie-break allocated 64 per mapping. Now 0 per mapping and 69 per refine!. Co-Authored-By: Claude Opus 5.5 --- src/wing_geometry.jl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/wing_geometry.jl b/src/wing_geometry.jl index 3f4c0273..1a1cec65 100644 --- a/src/wing_geometry.jl +++ b/src/wing_geometry.jl @@ -1031,9 +1031,12 @@ end Position of the centre of `section` along `wing.spanwise_direction`. """ -section_span_position(wing, section) = - sum(wing.spanwise_direction[k] * (section.LE_point[k] + section.TE_point[k]) * 0.5 - for k in 1:3) +function section_span_position(wing, section) + direction = wing.spanwise_direction + return (direction[1] * (section.LE_point[1] + section.TE_point[1]) + + direction[2] * (section.LE_point[2] + section.TE_point[2]) + + direction[3] * (section.LE_point[3] + section.TE_point[3])) * 0.5 +end """ compute_refined_panel_mapping!(wing::AbstractWing) @@ -1063,8 +1066,12 @@ function compute_refined_panel_mapping!(wing::AbstractWing) return nothing end - span_middle = 0.5 * sum(extrema(section_span_position(wing, u) - for u in wing.unrefined_sections)) + span_min, span_max = Inf, -Inf + for u in wing.unrefined_sections + span = section_span_position(wing, u) + span_min, span_max = min(span_min, span), max(span_max, span) + end + span_middle = 0.5 * (span_min + span_max) # For each refined panel, find closest unrefined section # using scalar arithmetic to avoid MVec3 allocations for pi in 1:n_panels