Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ lib/**/Manifest.toml

# Generated test geometries (regenerated on demand by ram_air_matrix_wing)
test/generated/

# Generated aero tables and run logs
*.arrow
446 changes: 284 additions & 162 deletions CHANGELOG.md

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions docs/src/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ deform_section
analyze_section
analyze_sweep
neuralfoil_aero
deform_kulfan
chord_residual
chord_line
control_point_deflection
panel_kulfan_parameters
refresh_live_polars!
deform_live_shapes!
apply_live_shapes!
polar_drift
compare_live_polar
refresh_live_pressure!
live_surface_friction!
contour_shape_matrix
live_shape_offset!
generate_aero_matrices
generate_polar_from_coordinates
generate_polar_from_dat
Expand Down
10 changes: 10 additions & 0 deletions docs/src/private_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ update_non_deformed_sections!
### Aerodynamic data and Cp
```@docs
calculate_new_aero_data
set_sampled_polar!
decode_surface_velocity
live_xfoil_solver
xfoil_ramp
sampled_value
assemble_polar_matrix
load_matrix_polar_data
read_aero_matrix
Expand Down Expand Up @@ -153,8 +158,11 @@ smooth_turning!
load_neuralfoil_model
neuralfoil_section
neuralfoil_fused_output
fused_output
decode_coefficients
nn_forward
prepare_inputs
fill_case_input!
flip_inputs
flip_outputs
squared_mahalanobis_distance
Expand All @@ -179,6 +187,7 @@ contour_arc
arc_at_chord
trailing_edge_speed
velocity_knots
contour_pressure
fill_node_nans!
```

Expand Down Expand Up @@ -225,6 +234,7 @@ map_airfoil_3d
fitted_airfoil_3d
generated_slices
airfoil_skin_geometry
panel_contour
panel_normal
plate_hinge_local
panel_plate_geometry
Expand Down
5 changes: 4 additions & 1 deletion docs/src/private_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CurrentModule = VortexStepMethod
### Wing Geometry, Panel and Aerodynamics
```@docs
Panel
KulfanParameters
PanelProperties
Filament
BoundFilament
Expand All @@ -23,7 +24,9 @@ LEI_AIRFOIL_BREUKELS
CurrentModule = VortexStepMethod.AirfoilAero
```
```@docs
KulfanParameters
KulfanBasis
LivePolarSettings
LivePolars
NeuralFoilModel
NeuralFoilResult
```
Expand Down
36 changes: 27 additions & 9 deletions ext/VortexStepMethodMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,34 @@ function Makie.plot!(ax, panel::VortexStepMethod.Panel; color=(:red, 0.2), R_b_w
return plots
end

"""
panel_contour(panel, section) -> (x, y)

The airfoil contour to draw a panel with: coordinates of its `live_shape` when a live
polar source has put one there, else the section's tabulated contour at the panel's
`delta`. The live branch reads the stored shape object itself, so what is drawn is what
was flown.
"""
function panel_contour(panel, section)
shape = panel.live_shape
isnothing(shape) || return VortexStepMethod.AirfoilAero.kulfan_to_coordinates(shape)
return VortexStepMethod.section_surface(section.section_aero, 0.0, panel.delta)[1:2]
end

"""
airfoil_skin_geometry(body; R_b_w=nothing, T_b_w=nothing) -> (vertices, faces, ribs)

Lofted airfoil skin of a `BodyAerodynamics`: each section's deflected contour
(`section_surface` at the panel's `delta`) is fitted between the panel's `corner_points`
by a 2D similarity, TE pinned so a deflection bulges the fore body up. The skin reflects
`delta` only when the geometry carries per-`delta` slices (`obj_to_yaml` with a
`delta_range`); with δ=0-only data it renders undeflected, a deliberate cue that the
deflected slices are missing. Transformed to world by `R_b_w`/`T_b_w`.
Lofted airfoil skin of a `BodyAerodynamics`: each section's contour is fitted between
the panel's `corner_points` by a 2D similarity, TE pinned so a deflection bulges the
fore body up.

The contour is the panel's own `live_shape` when it has one — the very
[`KulfanParameters`](@ref) object the live polar source deformed and handed to the
airfoil solver, not a re-derivation of it, so a deformation bug shows up in the picture
instead of being papered over. Otherwise it is `section_surface` at the panel's `delta`,
which reflects `delta` only when the geometry carries per-`delta` slices (`obj_to_yaml`
with a `delta_range`); with δ=0-only data it renders undeflected, a deliberate cue that
the deflected slices are missing. Transformed to world by `R_b_w`/`T_b_w`.
`vertices`/`faces` triangulate the skin between consecutive equal-node sections; `ribs` is
one closed contour polyline per section. Sections without contour data are skipped.
"""
Expand All @@ -169,10 +188,10 @@ function airfoil_skin_geometry(body; R_b_w=nothing, T_b_w=nothing)
n_panels = n - 1
n_panels < 1 && continue
for (i, section) in enumerate(sections)
isnothing(section.section_aero) && continue
panel_idx = panel_offset + min(i, n_panels)
panel_idx <= length(body.panels) || continue
panel = body.panels[panel_idx]
(isnothing(section.section_aero) && isnothing(panel.live_shape)) && continue
corners = panel.corner_points
corner1 = Point3f(corners[:, 1]); corner3 = Point3f(corners[:, 3])
corner2 = Point3f(corners[:, 2]); corner4 = Point3f(corners[:, 4])
Expand All @@ -183,8 +202,7 @@ function airfoil_skin_geometry(body; R_b_w=nothing, T_b_w=nothing)
chord_len = norm(chord)
chord_len < 1e-9 && continue
up = panel_normal(panel)
xs, ys, _, _ = VortexStepMethod.section_surface(section.section_aero,
0.0, panel.delta)
xs, ys = panel_contour(panel, section)
le_i = argmin(xs)
le_x = xs[le_i]; le_y = ys[le_i]
te_x = 0.5 * (xs[1] + xs[end]); te_y = 0.5 * (ys[1] + ys[end])
Expand Down
14 changes: 12 additions & 2 deletions src/VortexStepMethod.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export calculate_projected_area, calculate_span
export MVec3

export LLT, Model, VSM
export AeroModel, INVISCID, POLY, LEI_AIRFOIL_BREUKELS, POLAR_MATRICES, POLAR_VECTORS
export AeroModel, INVISCID, POLY, LEI_AIRFOIL_BREUKELS, POLAR_MATRICES, POLAR_VECTORS, SAMPLED
export KulfanParameters
export BILLOWING, COSINE, LINEAR, PanelDistribution, SPLIT_PROVIDED, UNCHANGED
export ELLIPTIC, InitialGammaDistribution, ZEROS
export FAILURE, FEASIBLE, INFEASIBLE, SolverStatus
Expand Down Expand Up @@ -237,7 +238,7 @@ Enumeration of the implemented wing types.
@enum WingType RECTANGULAR CURVED ELLIPTICAL

"""
AeroModel `POLY` `POLAR_VECTORS` `POLAR_MATRICES` `INVISCID`
AeroModel `POLY` `POLAR_VECTORS` `POLAR_MATRICES` `INVISCID` `SAMPLED`

Enumeration of the implemented aerodynamic models. See also: [AeroData](@ref)

Expand All @@ -247,6 +248,11 @@ Enumeration of the implemented aerodynamic models. See also: [AeroData](@ref)
- `POLAR_VECTORS`: Polar vectors as function of alpha (lookup tables with interpolation)
- `POLAR_MATRICES`: Polar matrices as function of alpha and delta (lookup tables with interpolation)
- INVISCID
- `SAMPLED`: cl/cd/cm sampled at ascending angles of attack per panel, interpolated
between them and held flat past either end. This is what a live polar source writes
each solve, see [`refresh_live_polars!`](@ref
VortexStepMethod.AirfoilAero.refresh_live_polars!). Samples are the polar, so a stall
knee inside the sampled range is represented rather than smoothed. Ignores `delta`.

`LEI_AIRFOIL_BREUKELS` is a deprecated alias of `POLY`.

Expand All @@ -257,6 +263,7 @@ where `alpha` is the angle of attack, `delta` is trailing edge angle.
POLAR_VECTORS
POLAR_MATRICES
INVISCID
SAMPLED
end

"""
Expand Down Expand Up @@ -339,6 +346,9 @@ Union of different definitions of the aerodynamic properties of a wing section.
- (`alpha_range`, `cl_vector`, `cd_vector`, `cm_vector`) for `POLAR_VECTORS`
- (`alpha_range`, `delta_range`, `cl_matrix`, `cd_matrix`, `cm_matrix`) for `POLAR_MATRICES`

`SAMPLED` carries no section-level data: it is written onto a panel at run time by a
live polar source, never read from a section.

where `alpha` is the angle of attack [rad], `delta` is trailing edge angle [rad], `cl` the lift coefficient,
`cd` the drag coefficient and `cm` the pitching moment coefficient. The camber of a kite refers to
the curvature of its airfoil shape. The camber is typically measured as the maximum distance
Expand Down
12 changes: 11 additions & 1 deletion src/airfoil_aero/AirfoilAero.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ using NPZ
using Xfoil
using Printf: @sprintf
using ..VortexStepMethod: SectionAero, interpolate_matrix_nans!, delta_suffix,
write_node_rows
write_node_rows, section_surface, set_sampled_polar!,
KulfanParameters, calculate_cl, calculate_cd,
calculate_cm

include("kulfan.jl")
include("deform.jl")
include("shrink_wrap.jl")
include("neuralfoil.jl")
include("live_polar.jl")
include("poly.jl")
include("airfoil_solvers/common.jl")
include("airfoil_solvers/xfoil_solver.jl")
Expand All @@ -29,6 +33,12 @@ export ShrinkWrap, shrink_wrap
export fit_kulfan_parameters, kulfan_to_coordinates
export NeuralFoilModel, NeuralFoilResult, load_neuralfoil_model
export neuralfoil_aero, neuralfoil_section
export KulfanBasis, deform_kulfan, control_point_deflection
export chord_residual, chord_line
export LivePolarSettings, LivePolars, panel_kulfan_parameters
export refresh_live_polars!, refresh_live_pressure!, live_surface_friction!
export contour_shape_matrix, live_shape_offset!
export polar_drift, compare_live_polar
export AbstractAirfoilSolver, XFoilSolver, NeuralFoilSolver
export SectionSolution, DeformedSection, deform_section, analyze_section, analyze_sweep
export create_2d_polars, generate_aero_matrices, generate_section_aero, lei_poly_coeffs
Expand Down
53 changes: 33 additions & 20 deletions src/airfoil_aero/airfoil_solvers/neuralfoil_solver.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,28 @@ function arc_at_chord(x, arc, indices, fractions)
end

"""
trailing_edge_speed(res, i, upper_arc, lower_arc, upper_te, lower_te) -> Float64
trailing_edge_speed(ue_upper, ue_lower, upper_arc, lower_arc, upper_te, lower_te)
-> Float64

One edge speed for both surfaces at the trailing edge: each surface's last two
stations extrapolated to its own trailing-edge arc length, the two magnitudes
averaged, so a sharp edge carries a single pressure (Kutta).
"""
function trailing_edge_speed(res, i, upper_arc, lower_arc, upper_te, lower_te)
function trailing_edge_speed(ue_upper, ue_lower, upper_arc, lower_arc,
upper_te, lower_te)
reach(a1, a2, v1, v2, target) =
v2 + (target - a2) / (a2 - a1 + eps()) * (v2 - v1)
n = length(upper_arc)
n < 2 && return abs(res.ue_upper[end, i])
n < 2 && return abs(ue_upper[end])
upper = reach(upper_arc[n - 1], upper_arc[n],
res.ue_upper[n - 1, i], res.ue_upper[n, i], upper_te)
ue_upper[n - 1], ue_upper[n], upper_te)
lower = reach(lower_arc[n - 1], lower_arc[n],
res.ue_lower[n - 1, i], res.ue_lower[n, i], lower_te)
ue_lower[n - 1], ue_lower[n], lower_te)
return (abs(upper) + abs(lower)) / 2
end

"""
velocity_knots(res, i, x, arc, le) -> (knots, values)
velocity_knots(station_x, ue_upper, ue_lower, x, arc, le) -> (knots, values)

The whole contour's edge velocity as one curve of (signed arc length, signed
`ue/u∞`), strictly increasing in arc length.
Expand All @@ -109,35 +111,46 @@ than extrapolated off the end of one. The stagnation point enters as its own kno
where `ue` interpolated linearly between those two stations reaches zero — the
stagnation-point-flow result, `ue` being linear in arc length there.
"""
function velocity_knots(res, i, x, arc, le)
upper_arc = arc_at_chord(x, arc, 1:le, res.x)
lower_arc = arc_at_chord(x, arc, le:length(x), res.x)
speed = trailing_edge_speed(res, i, upper_arc, lower_arc, arc[1], arc[end])
lower_first, upper_first = abs(res.ue_lower[1, i]), abs(res.ue_upper[1, i])
function velocity_knots(station_x, ue_upper, ue_lower, x, arc, le)
upper_arc = arc_at_chord(x, arc, 1:le, station_x)
lower_arc = arc_at_chord(x, arc, le:length(x), station_x)
speed = trailing_edge_speed(ue_upper, ue_lower, upper_arc, lower_arc,
arc[1], arc[end])
lower_first, upper_first = abs(ue_lower[1]), abs(ue_upper[1])
stagnation = lower_arc[1] + lower_first / max(lower_first + upper_first, eps()) *
(upper_arc[1] - lower_arc[1])
knots = [arc[end]; reverse(lower_arc); stagnation; upper_arc; arc[1]]
values = [-speed; reverse(-abs.(res.ue_lower[:, i])); 0.0;
abs.(res.ue_upper[:, i]); speed]
values = [-speed; reverse(-abs.(ue_lower)); 0.0; abs.(ue_upper); speed]
order = sortperm(knots)
knots, values = knots[order], values[order]
keep = [1; [k for k in 2:length(knots) if knots[k] > knots[k - 1] + 1e-9]]
return knots[keep], values[keep]
end

"""
contour_pressure(station_x, ue_upper, ue_lower, x, arc, le) -> Vector{Float64}

`Cp` at every node of a closed contour `x` with signed arc length `arc` and nose at
`le`, from one case's edge-velocity ratios at NeuralFoil's `station_x`. Builds the
one-curve edge velocity ([`velocity_knots`](@ref)), interpolates it with a
shape-preserving monotone cubic in arc length, and squares it into `Cp = 1 - ue²`.
"""
function contour_pressure(station_x, ue_upper, ue_lower, x, arc, le)
knots, values = velocity_knots(station_x, ue_upper, ue_lower, x, arc, le)
speed = interpolate(knots, values, FritschButlandMonotonicInterpolation())
return [1 - speed(clamp(a, first(knots), last(knots)))^2 for a in arc]
end

"""
neuralfoil_contour_solution(alpha, res, i, x, y, le, arc, cf) -> SectionSolution

Assemble a full-contour [`SectionSolution`](@ref) for case `i` of a NeuralFoil sweep
`res`: build the one-curve edge velocity ([`velocity_knots`](@ref)), interpolate it
with a shape-preserving monotone cubic in arc length, and square it into
`Cp = 1 - ue²` at every contour node. `ue` is the interpolated quantity because it is
linear in arc length through a stagnation point, where `Cp` is quadratic.
`res`: reconstruct `Cp` on the contour nodes from the predicted edge velocities
([`contour_pressure`](@ref)), carrying the precomputed `cf`.
"""
function neuralfoil_contour_solution(alpha, res, i, x, y, le, arc, cf)
knots, values = velocity_knots(res, i, x, arc, le)
speed = interpolate(knots, values, FritschButlandMonotonicInterpolation())
cp = [1 - speed(clamp(a, first(knots), last(knots)))^2 for a in arc]
cp = contour_pressure(res.x, view(res.ue_upper, :, i),
view(res.ue_lower, :, i), x, arc, le)
return SectionSolution(alpha, res.cl[i], res.cd[i], res.cm[i],
res.confidence[i], x, y, cp, cf)
end
Expand Down
Loading
Loading