Skip to content

Section slicing spreads stations by leading-edge arc length, which collapses to nothing in span at a raked tip #275

Description

@1-Bart-1

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions