Skip to content

Commit 99cddc4

Browse files
1-Bort-1claude
andcommitted
Merge origin/main into agent/330-add-rigid-body-stability-derivatives-and
set_va!(body_aero, settings) keeps both sides: the inflow comes from apparent_wind, and the body turns at settings.condition.yaw_rate about body_aero.reference_point, as #353 made it. Both Added entries in the changelog are kept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 parents b50a2b5 + 7724c0d commit 99cddc4

14 files changed

Lines changed: 426 additions & 260 deletions

CHANGELOG.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,19 @@
77
- `stability_derivatives` gives the force and moment coefficients and their derivatives
88
with respect to angle of attack and sideslip, and `trim_angle` the angles of attack at
99
which `CMy` changes sign, with the slope that says whether each trim is stable.
10+
- `set_va!(body_aero, va_vec, omega; reference_point)` turns the body about
11+
`reference_point` [m] instead of the origin. The point is stored on
12+
`BodyAerodynamics`, starts at the origin, and is kept by later `set_va!`, `reinit!`
13+
and `linearize` calls until it is given again.
1014
- Spanwise-flow viscous drag correction (Gaunaa et al. 2024,
1115
doi:10.1088/1742-6596/2767/2/022068): each section gets a drag increment and a force
1216
along its span from the flow across it, in `solve!`, `solve` and `linearize`. Opt-in
1317
via `is_with_viscous_drag_correction` (default `false`) on the solver settings.
18+
- `plot_section_polars(body_aero; panels, alphas, delta)` draws cl, cd and cm against α
19+
per panel through `calculate_cl`/`calculate_cd`/`calculate_cm`, for every aero model
20+
and at flap deflection `delta`, in one figure instead of one coefficient per call.
21+
- `linearize` takes a `BodyAerodynamics` with more than one wing; `theta_idxs` and
22+
`delta_idxs` then run over the unrefined sections of all wings in order.
1423

1524
### Changed
1625

@@ -21,11 +30,13 @@
2130

2231
### Fixed
2332

33+
- `set_va!(body_aero, settings)` applies `condition.yaw_rate` as a turn rate about the
34+
body z axis; it was read from the settings file and ignored.
2435
- The `VSMSolution` docstring gives `lift_dist`, `drag_dist` and `panel_moment_dist` in
2536
the per-unit-span units they hold, [N/m] and [Nm/m], instead of [N] and [Nm].
26-
27-
### Fixed
28-
37+
- Inside its vortex core, `velocity_3D_trailing_vortex!` induces an azimuthal velocity
38+
instead of a radial one. Only points within the millimetre-scale Oseen core of a
39+
panel's chordwise trailing segment were affected.
2940
- With `artificial_damping` on, an iteration whose circulation is already smooth no longer
3041
re-applies the previous iteration's damping correction.
3142

docs/src/private_functions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ panel_loads
6767
```@docs
6868
velocity_3D_bound_vortex!
6969
velocity_3D_trailing_vortex!
70+
velocity_3D_vortex_segment!
7071
velocity_3D_trailing_vortex_semiinfinite!
7172
calculate_velocity_induced_bound_2D!
7273
calculate_velocity_induced_single_ring_semiinfinite!
@@ -87,6 +88,7 @@ calculate_filaments_for_plotting
8788
### Mesh refinement and billowing
8889
```@docs
8990
unrefined_deform!
91+
unrefined_section_range
9092
deform!
9193
compute_refined_panel_mapping!
9294
compute_refined_section_interpolation!
@@ -278,6 +280,7 @@ panel_contour
278280
panel_normal
279281
plate_hinge_local
280282
panel_plate_geometry
283+
panel_polar_curves
281284
PLATE_FACES
282285
Makie.plot!(ax, panel::VortexStepMethod.Panel)
283286
Makie.plot!(ax, body::VortexStepMethod.BodyAerodynamics)

docs/src/settings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ condition:
3232
wind_speed: 10.0 # free-stream velocity magnitude [m/s]
3333
alpha: 5.0 # angle of attack [°]
3434
beta: 0.0 # sideslip angle [°]
35-
yaw_rate: 0.0 # yaw rate [°/s]
35+
yaw_rate: 0.0 # turn rate about the body z axis [°/s]
3636

3737
wings:
3838
- name: main_wing # label the wing carries into plots and output

examples/obj_to_yaml_kite.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,9 @@ if PLOT
8080
plot_geometry(body_aero, "Ram air kite (converted from .obj)"; is_show=true,
8181
view_elevation=15, view_azimuth=-120, use_tex=USE_TEX)
8282

83-
# Airfoils and per-section polars recovered from the converted geometry
83+
# Airfoils and panel polars recovered from the converted geometry
8484
plot_airfoils(geometry_file; symmetric=true, is_show=true)
85-
plot_section_polars(body_aero, :cl; is_show=true)
86-
plot_section_polars(body_aero, :cd; is_show=true)
85+
plot_section_polars(body_aero; panels=[1, 10], is_show=true)
8786

8887
plot_polars([solver], [body_aero], ["VSM (NeuralFoil polars from .obj)"];
8988
angle_range=range(-5, 20, length=26), v_a=va,

ext/VortexStepMethodMakieExt.jl

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module VortexStepMethodMakieExt
22
using MakieControlPlots.Makie, VortexStepMethod, LinearAlgebra, Statistics, DelimitedFiles
33
import MakieControlPlots
4-
import VortexStepMethod: calculate_filaments_for_plotting
4+
import VortexStepMethod: calculate_filaments_for_plotting, calculate_cl, calculate_cd,
5+
calculate_cm
56
import VortexStepMethod: ObjAdapter, AirfoilAero
67

78
export plot_geometry, plot_distribution, plot_polars, save_plot, show_plot,
@@ -1324,13 +1325,12 @@ function VortexStepMethod.plot_combined_analysis(
13241325
xlabel="α [°]",
13251326
ylabel="Cm")
13261327

1327-
cl_vals = [first_body.panels[1].cl_interp(a) for a in alphas]
1328-
cd_vals = [first_body.panels[1].cd_interp(a) for a in alphas]
1329-
cm_vals = [first_body.panels[1].cm_interp(a) for a in alphas]
1328+
panel = first_body.panels[1]
1329+
cl, cd, cm = panel_polar_curves([panel], alphas, panel.delta)
13301330

1331-
lines!(ax_cl_curve, alphas_deg, cl_vals; color=:blue, linewidth=2)
1332-
lines!(ax_cd_curve, alphas_deg, cd_vals; color=:red, linewidth=2)
1333-
lines!(ax_cm_curve, alphas_deg, cm_vals; color=:green, linewidth=2)
1331+
lines!(ax_cl_curve, alphas_deg, only(cl); color=:blue, linewidth=2)
1332+
lines!(ax_cd_curve, alphas_deg, only(cd); color=:red, linewidth=2)
1333+
lines!(ax_cm_curve, alphas_deg, only(cm); color=:green, linewidth=2)
13341334
end
13351335

13361336
# [2,1] Spanwise Distributions (3×3 grid)
@@ -1477,48 +1477,43 @@ function VortexStepMethod.plot_combined_analysis(
14771477
end
14781478

14791479
"""
1480-
plot_section_polars(body_aero, coefficient=:cl; is_show=true,
1481-
is_save=false, save_path=nothing, data_type=".png")
1480+
panel_polar_curves(panels, alphas, deltas) -> (cl, cd, cm)
1481+
1482+
Lift, drag and moment coefficients of each of `panels` over `alphas` [rad], each panel
1483+
at its flap deflection in `deltas` [rad] (or one shared deflection), as one vector per
1484+
panel per coefficient.
1485+
"""
1486+
function panel_polar_curves(panels, alphas, deltas)
1487+
cl = collect.(eachrow(calculate_cl.(panels, alphas', deltas)))
1488+
cd = collect.(eachrow(calculate_cd.(panels, alphas', deltas)))
1489+
cm = collect.(eachrow(calculate_cm.(panels, alphas', deltas)))
1490+
return cl, cd, cm
1491+
end
1492+
1493+
"""
1494+
plot_section_polars(body_aero; kwargs...)
14821495
14831496
Implementation of [`plot_section_polars`](@ref); rendered through `MakieControlPlots`.
14841497
"""
1485-
function VortexStepMethod.plot_section_polars(body_aero::BodyAerodynamics,
1486-
coefficient::Symbol=:cl; is_show::Bool=true, is_save::Bool=false,
1487-
save_path=nothing, data_type::String=".png")
1488-
1489-
coefficient in (:cl, :cd, :cm) ||
1490-
throw(ArgumentError("coefficient must be :cl, :cd, or :cm, got :$coefficient"))
1491-
idx = coefficient === :cl ? 2 : coefficient === :cd ? 3 : 4
1492-
label = uppercasefirst(string(coefficient))
1493-
1494-
alphas_deg = nothing
1495-
series = Vector{Float64}[]
1496-
labels = String[]
1497-
for wing in body_aero.wings
1498-
for (s, section) in enumerate(wing.unrefined_sections)
1499-
section.aero_model == POLAR_VECTORS || continue
1500-
aero = section.aero_data
1501-
aero === nothing && continue
1502-
section_alphas = rad2deg.(aero[1])
1503-
if isnothing(alphas_deg)
1504-
alphas_deg = collect(section_alphas)
1505-
elseif length(section_alphas) != length(alphas_deg)
1506-
@warn "section $s has a different α grid; plotting against the first section's α"
1507-
end
1508-
push!(series, Float64.(aero[idx]))
1509-
push!(labels, "section $s")
1510-
end
1511-
end
1512-
isempty(series) && error("No POLAR_VECTORS sections found in body")
1498+
function VortexStepMethod.plot_section_polars(body_aero::BodyAerodynamics;
1499+
panels=eachindex(body_aero.panels), alphas=deg2rad.(-20:0.5:30), delta=nothing,
1500+
is_show::Bool=true, is_save::Bool=false, save_path=nothing,
1501+
data_type::String=".png")
1502+
1503+
panel_indices = vcat(panels)
1504+
chosen_panels = body_aero.panels[panel_indices]
1505+
deltas = something.(delta, getproperty.(chosen_panels, :delta))
1506+
cl, cd, cm = panel_polar_curves(chosen_panels, alphas, deltas)
1507+
labels = ["panel $i ($(panel.aero_model))"
1508+
for (i, panel) in zip(panel_indices, chosen_panels)]
15131509

1514-
plt = MakieControlPlots.plot(alphas_deg, series;
1515-
xlabel="α [deg]", ylabel=label, title="$label per section",
1516-
labels=labels, disp=(is_show || is_save))
1510+
plt = MakieControlPlots.plotx(rad2deg.(alphas), cl, cd, cm;
1511+
xlabel="α [deg]", ylabels=["cl", "cd", "cm"], title="Section polars",
1512+
labels=[labels], disp=(is_show || is_save))
15171513

15181514
if is_save && !isnothing(save_path)
15191515
isdir(save_path) || mkpath(save_path)
1520-
MakieControlPlots.savefig(
1521-
joinpath(save_path, "section_polars_$(coefficient)$(data_type)"))
1516+
MakieControlPlots.savefig(joinpath(save_path, "section_polars$(data_type)"))
15221517
end
15231518
return plt
15241519
end

src/VortexStepMethod.jl

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,17 +175,22 @@ in sequence.
175175
function plot_combined_analysis end
176176

177177
"""
178-
plot_section_polars(body_aero::BodyAerodynamics, coefficient=:cl; kwargs...)
178+
plot_section_polars(body_aero::BodyAerodynamics; panels=eachindex(body_aero.panels),
179+
alphas=deg2rad.(-20:0.5:30), delta=nothing, kwargs...)
179180
180-
Plot one polar coefficient (`:cl`, `:cd`, or `:cm`) against angle of attack for
181-
every section of a wing using stored `POLAR_VECTORS` data. Rendered through
182-
`MakieControlPlots`.
181+
Plot the lift, drag and moment coefficients against angle of attack for the chosen
182+
`panels`, one curve per panel, as each panel's [`calculate_cl`](@ref),
183+
[`calculate_cd`](@ref) and [`calculate_cm`](@ref) evaluate them for its aero model.
184+
Rendered through `MakieControlPlots`; returns its plot object.
183185
184186
# Arguments
185187
- `body_aero`: the [`BodyAerodynamics`](@ref) to plot
186-
- `coefficient`: `:cl`, `:cd`, or `:cm` (default: `:cl`)
187188
188189
# Keyword arguments
190+
- `panels`: index or indices into `body_aero.panels` (default: all panels)
191+
- `alphas`: angles of attack [rad] (default: `deg2rad.(-20:0.5:30)`)
192+
- `delta`: flap deflection [rad] a `POLAR_MATRICES` panel is evaluated at
193+
(default: `nothing`, each panel's own `delta`)
189194
- `is_show`: whether to display (default: `true`)
190195
- `is_save`: whether to save (default: `false`)
191196
- `save_path`: directory to save the figure (default: `nothing`)

src/body_aerodynamics.jl

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Main structure for calculating aerodynamic properties of bodies. Use the constru
88
- wings::Vector{W}: A vector of wings of type `W <: AbstractWing`; a body can have multiple wings
99
- `va::MVec3` = zeros(MVec3): A vector of the apparent wind speed, see: [`MVec3`](@ref)
1010
- `omega`::MVec3 = zeros(MVec3): A vector of the turn rates around the kite body axes
11+
- `reference_point`::MVec3 = zeros(MVec3): The point `omega` turns the body about [m]
1112
- `gamma_distribution`=zeros(Float64, P): A vector of the circulation
1213
of the velocity field; Length: Number of segments. [m²/s]
1314
- `alpha_uncorrected`=zeros(Float64, P): angles of attack per panel
@@ -35,6 +36,7 @@ Main structure for calculating aerodynamic properties of bodies. Use the constru
3536
_va::MVector{3, T} = zeros(MVector{3, T})
3637
has_distributed_va::Bool = false
3738
omega::MVector{3, T} = zeros(MVector{3, T})
39+
reference_point::MVector{3, T} = zeros(MVector{3, T})
3840
gamma_distribution::MVector{P, T} = zeros(MVector{P, T})
3941
alpha_uncorrected::MVector{P, T} = zeros(MVector{P, T})
4042
alpha_corrected::MVector{P, T} = zeros(MVector{P, T})
@@ -155,6 +157,8 @@ function Base.setproperty!(obj::BodyAerodynamics, sym::Symbol, val)
155157
set_va!(obj, val)
156158
elseif sym === :omega
157159
set_va!(obj, obj._va, val)
160+
elseif sym === :reference_point
161+
set_va!(obj, obj._va, obj.omega; reference_point=val)
158162
else
159163
setfield!(obj, sym, val)
160164
end
@@ -252,6 +256,37 @@ function calculate_stall_angle_list!(stall_angles::AbstractVector,
252256
return nothing
253257
end
254258

259+
"""
260+
unrefined_section_range(body_aero::BodyAerodynamics, wing_idx)
261+
262+
Indices of the unrefined sections of wing `wing_idx` in a distribution that runs over the
263+
unrefined sections of all wings in order, such as `moment_unrefined_dist`.
264+
"""
265+
function unrefined_section_range(body_aero::BodyAerodynamics, wing_idx)
266+
offset = 0
267+
for i in 1:wing_idx-1
268+
offset += body_aero.wings[i].n_unrefined_sections
269+
end
270+
return offset .+ (1:body_aero.wings[wing_idx].n_unrefined_sections)
271+
end
272+
273+
"""
274+
unrefined_deform!(body_aero::BodyAerodynamics, theta_angles, delta_angles)
275+
276+
Deform each wing of `body_aero` by its entries of `theta_angles` and `delta_angles` [rad],
277+
which run over the unrefined sections of all wings in order; `nothing` leaves that angle
278+
unchanged. Call [`reinit!`](@ref) afterwards to update the panels.
279+
"""
280+
function unrefined_deform!(body_aero::BodyAerodynamics, theta_angles, delta_angles)
281+
for (wing_idx, wing) in enumerate(body_aero.wings)
282+
section_range = unrefined_section_range(body_aero, wing_idx)
283+
unrefined_deform!(wing,
284+
isnothing(theta_angles) ? nothing : view(theta_angles, section_range),
285+
isnothing(delta_angles) ? nothing : view(delta_angles, section_range))
286+
end
287+
return nothing
288+
end
289+
255290
"""
256291
reinit!(body_aero::BodyAerodynamics; init_aero, va, omega, refine_mesh, recompute_mapping, sort_sections)
257292
@@ -1052,44 +1087,32 @@ end
10521087

10531088

10541089
"""
1055-
set_va!(body_aero::BodyAerodynamics, va_vec::VelVector, omega=zeros(MVec3))
1090+
set_va!(body_aero::BodyAerodynamics, va_vec::VelVector, omega=zeros(MVec3);
1091+
reference_point=body_aero.reference_point)
10561092
1057-
Set velocity array and update wake filaments.
1093+
Set a uniform apparent wind and a body turn rate, and update the wake filaments. Each
1094+
panel sees `va_vec - omega × (control_point - reference_point)`.
10581095
10591096
# Arguments
10601097
- body_aero::BodyAerodynamics: The [`BodyAerodynamics`](@ref) struct to modify
10611098
- `va_vec::VelVector`: Velocity vector of the apparent wind speed [m/s]
10621099
- `omega::VelVector`: Turn rate vector around x y and z axis [rad/s]
1100+
- `reference_point`: Point the body turns about, stored on `body_aero` [m]
10631101
10641102
`omega` is also projected onto each panel's spanwise axis into
10651103
`pitch_rate_dist`, which the solver reads when `flow_curvature` is enabled.
10661104
"""
10671105
function set_va!(body_aero::BodyAerodynamics{P, W, T}, va_vec::AbstractVector,
1068-
omega=zeros(MVector{3, T})) where {P, W, T}
1069-
n_panels = length(body_aero.panels)
1070-
va_vec_dist = zeros(T, n_panels, 3)
1106+
omega=zeros(MVector{3, T});
1107+
reference_point=body_aero.reference_point) where {P, W, T}
10711108
body_aero.omega .= omega
1109+
body_aero.reference_point .= reference_point
10721110
set_pitch_rate_dist!(body_aero, omega)
10731111

1074-
if all(iszero, omega)
1075-
va_vec_dist .= reshape(va_vec, 1, 3)
1076-
else
1077-
idx = 1
1078-
for wing in body_aero.wings
1079-
panel_end = idx + wing.n_panels - 1
1080-
1081-
# Calculate velocities for each panel in this wing slice
1082-
for j in idx:panel_end
1083-
omega_va_vec = -omega × body_aero.panels[j].control_point
1084-
va_vec_dist[j, :] .= omega_va_vec .+ va_vec
1085-
end
1086-
idx = panel_end + 1
1087-
end
1088-
end
1089-
1090-
# Update panel velocities
1112+
va_vec_dist = zeros(T, P, 3)
10911113
for (i, panel) in enumerate(body_aero.panels)
1092-
panel.va .= va_vec_dist[i,:]
1114+
panel.va .= va_vec .- omega × (panel.control_point .- body_aero.reference_point)
1115+
va_vec_dist[i, :] .= panel.va
10931116
end
10941117

10951118
# Update wake elements
@@ -1147,7 +1170,8 @@ apparent_wind(alpha, beta, wind_speed) =
11471170
set_va!(body_aero::BodyAerodynamics, settings::VSMSettings)
11481171
11491172
Set the uniform inflow of `body_aero` to the [`apparent_wind`](@ref) at the `alpha` and
1150-
`beta` [°] and `wind_speed` [m/s] of `settings.condition`.
1173+
`beta` [°] and `wind_speed` [m/s] of `settings.condition`, turning the body about
1174+
`body_aero.reference_point` at its `yaw_rate` [°/s] about Z_b.
11511175
11521176
# Example
11531177
```julia
@@ -1160,5 +1184,5 @@ function set_va!(body_aero::BodyAerodynamics, settings::VSMSettings)
11601184
condition = settings.condition
11611185
va_vec = apparent_wind(deg2rad(condition.alpha), deg2rad(condition.beta),
11621186
condition.wind_speed)
1163-
set_va!(body_aero, va_vec)
1187+
set_va!(body_aero, va_vec, [0.0, 0.0, deg2rad(condition.yaw_rate)])
11641188
end

0 commit comments

Comments
 (0)