Summary
station_indices (src/obj_adapter/obj_slice.jl:465) spreads its section targets
over equal leading-edge arc length:
return [argmin(abs.(arclen .- t)) for t in range(inner + d, outer - d, n)]
Near a closed or raked tip the leading edge runs almost straight downstream, so arc
length and span decouple completely. On a 16.3 m ram-air kite (SK100, 44 panels,
6.3 m max chord) the march_edges stations closest to the tip read:
station 251 | y=8.132 | d_arc=0.0816 d_span=0.0032 d_x=0.0807 | arc/span=25.87
station 252 | y=8.135 | d_arc=0.0816 d_span=0.0032 d_x=0.0807 | arc/span=25.87
station 255 | y=8.145 | d_arc=0.0816 d_span=0.0032 d_x=0.0807 | arc/span=25.87
Identical rows: the march has settled into walking along the tip cap, spending
25.87 m of arc per metre of span. That is faithful to the geometry — the min-x
crossing really does race aft as a section closes — but it means equal arc-length
targets are not equal anything in span.
What it does to the sections and panels
Sections bunch into the cap. With wingtip_distance = 0.05:
sec 40: y=7.607 sec 43: y=8.113
sec 41: y=7.893 sec 44: y=8.132
sec 42: y=8.069 sec 45: y=8.151
Four sections in the outermost 8 cm; sections 40->41 alone span 29 cm.
These are the panels, not just the sections. With n_panels = 44 and
n_sections = 45, refine! takes the length(unrefined_sections) == n_panels + 1
branch and calls copy_sections_to_refined!, so the declared BILLOWING
distribution is never reached and the mapping is 1:1 — measured
max |panel corner - section point| = 0.000e+00. Per panel:
panel | y_1 y_2 | d_span
1 | 8.121 8.110 | 0.0110 <- 1.1 cm
22 | ... | 0.5440 <- 54.4 cm
44 | -8.110 -8.121 | 0.0110
widest/narrowest = 49.5
The 1.1 cm panels are geometrically degenerate. Measured on the undeformed CAD wing
in a uniform freestream:
panel | sweep from freestream | x_airf . y_airf
1 | 10.7 deg | 0.996
2 | 24.5 deg | 0.935
21 | 88.5 deg | 0.028
44 | 10.7 deg | 0.996
The outermost panel's bound vortex sits 10.7 deg from the freestream and its
chordwise and spanwise axes are nearly parallel. 6 of 44 panels are below 45 deg.
Why it matters beyond those panels
q_dyn = 0.5*rho*|v x y_airf|^2 correctly gives such a panel sin^2(10.7) = 3.4%
of freestream, so it carries almost no load — that part is fine and should not be
"fixed" by orthogonalising y_airf, which would fabricate a factor of 29 in tip
force. The problem is conditioning. z_airf = normalize(x_airf x span_vec) is the
normalised cross product of two vectors 5 deg apart, so on this panel 1 cm of
corner motion swings z_airf by 33 deg and its angle of attack by 2.44 deg
(mid-span: 0.05 deg). Since the LOOP iteration solves
gamma_new[i] = 0.5 * v_a_dist[i]^2 / va_magw[i] * cl(alpha_i) * chord[i]
a panel with negligible force still injects a wildly swinging circulation into every
other panel's induced velocity. On a flexible wing this shows up downstream as all
panels going non-finite at once.
The existing workaround, and why it is a poor one
wingtip_distance does help, but being in arc length it inherits the same problem.
Measured panel-level through obj_to_yaml on the same mesh:
wtd | worst sweep | x.y | panels<45 | span ratio | half-span lost
0.05 | 10.7 deg | 0.996 | 6 | 49.5 | 0.0%
1.0 | 41.7 deg | 0.780 | 2 | 2.7 | 1.7%
1.4 | 66.0 deg | 0.449 | 0 | 2.7 | 4.2%
1.8 | 69.5 deg | 0.397 | 0 | 2.7 | 9.1%
2.0 | 70.7 deg | 0.377 | 0 | 2.5 | 9.1%
2.4 | 72.8 deg | 0.343 | 0 | 2.5 | 14.2%
It is strongly non-linear (0 -> 1.2 is nearly inert, because it is eating cap arc)
and it has plateaus (1.8 and 2.0 snap to the same march station). A user asking for
"1.2 m of tip inset" gets 4.9 cm of span.
Suggested fix
Place the targets by spanwise position rather than leading-edge arc length — either
march.le[i][2] directly, or the arc length of the quarter-chord line projected
into the span direction, which is also what refine_mesh_for_linear_cosine_distribution!
already uses for LINEAR/COSINE (src/wing_geometry.jl:1347). That makes the
section distribution mean the same thing on a raked tip as on a rectangular wing,
and makes wingtip_distance a predictable control.
Smaller and independent: station_indices takes min_chord_frac (default 0.01,
i.e. 1% of max chord = 6 cm here, which is why a 0.5 m-chord section survives), but
perpendicular_sections calls it as station_indices(m, n_sections; wingtip_distance)
and never passes it through, so it is unreachable from WingSettings. Plumbing it
would give a direct, geometrically meaningful way to drop degenerate tip stations.
Version
main @ v4.3.1 with #273 applied (quarter-chord normal), src/obj_adapter/obj_slice.jl.
Related: #272.
Summary
station_indices(src/obj_adapter/obj_slice.jl:465) spreads its section targetsover equal leading-edge arc length:
Near a closed or raked tip the leading edge runs almost straight downstream, so arc
length and span decouple completely. On a 16.3 m ram-air kite (SK100, 44 panels,
6.3 m max chord) the
march_edgesstations closest to the tip read:Identical rows: the march has settled into walking along the tip cap, spending
25.87 m of arc per metre of span. That is faithful to the geometry — the min-x
crossing really does race aft as a section closes — but it means equal arc-length
targets are not equal anything in span.
What it does to the sections and panels
Sections bunch into the cap. With
wingtip_distance = 0.05:Four sections in the outermost 8 cm; sections 40->41 alone span 29 cm.
These are the panels, not just the sections. With
n_panels = 44andn_sections = 45,refine!takes thelength(unrefined_sections) == n_panels + 1branch and calls
copy_sections_to_refined!, so the declaredBILLOWINGdistribution is never reached and the mapping is 1:1 — measured
max |panel corner - section point| = 0.000e+00. Per panel:The 1.1 cm panels are geometrically degenerate. Measured on the undeformed CAD wing
in a uniform freestream:
The outermost panel's bound vortex sits 10.7 deg from the freestream and its
chordwise and spanwise axes are nearly parallel. 6 of 44 panels are below 45 deg.
Why it matters beyond those panels
q_dyn = 0.5*rho*|v x y_airf|^2correctly gives such a panelsin^2(10.7) = 3.4%of freestream, so it carries almost no load — that part is fine and should not be
"fixed" by orthogonalising
y_airf, which would fabricate a factor of 29 in tipforce. The problem is conditioning.
z_airf = normalize(x_airf x span_vec)is thenormalised cross product of two vectors 5 deg apart, so on this panel 1 cm of
corner motion swings
z_airfby 33 deg and its angle of attack by 2.44 deg(mid-span: 0.05 deg). Since the LOOP iteration solves
a panel with negligible force still injects a wildly swinging circulation into every
other panel's induced velocity. On a flexible wing this shows up downstream as all
panels going non-finite at once.
The existing workaround, and why it is a poor one
wingtip_distancedoes help, but being in arc length it inherits the same problem.Measured panel-level through
obj_to_yamlon the same mesh:It is strongly non-linear (0 -> 1.2 is nearly inert, because it is eating cap arc)
and it has plateaus (1.8 and 2.0 snap to the same march station). A user asking for
"1.2 m of tip inset" gets 4.9 cm of span.
Suggested fix
Place the targets by spanwise position rather than leading-edge arc length — either
march.le[i][2]directly, or the arc length of the quarter-chord line projectedinto the span direction, which is also what
refine_mesh_for_linear_cosine_distribution!already uses for
LINEAR/COSINE(src/wing_geometry.jl:1347). That makes thesection distribution mean the same thing on a raked tip as on a rectangular wing,
and makes
wingtip_distancea predictable control.Smaller and independent:
station_indicestakesmin_chord_frac(default0.01,i.e. 1% of max chord = 6 cm here, which is why a 0.5 m-chord section survives), but
perpendicular_sectionscalls it asstation_indices(m, n_sections; wingtip_distance)and never passes it through, so it is unreachable from
WingSettings. Plumbing itwould give a direct, geometrically meaningful way to drop degenerate tip stations.
Version
main@ v4.3.1 with #273 applied (quarter-chord normal),src/obj_adapter/obj_slice.jl.Related: #272.