Functions for creating the geometry
VortexStepMethod.add_section! — Function
add_section!(wing::Wing, LE_point::PosVector, TE_point::PosVector,
aero_model, aero_data::AeroData=nothing)Add a new section to the wing.
Arguments:
VortexStepMethod.refine! — Function
refine!(wing::AbstractWing; recompute_mapping=true, sort_sections=true)Refine the wing aerodynamic mesh from unrefined sections to refined sections.
This function interpolates the wing geometry from a coarse set of unrefined sections to a fine mesh of refined sections (npanels+1 sections) based on the wing's spanwisedistribution setting. It also populates nondeformedsections which enables deformation support via unrefined_deform!.
Required Workflow
Must be called after wing construction and before creating BodyAerodynamics:
wing = Wing("wing.yaml"; n_panels=40) # or ObjWing(...) or manual Wing
refine!(wing) # Refine mesh
body_aero = BodyAerodynamics([wing]) # Create aerodynamicsDistribution Methods
LINEAR: Linear interpolation between sectionsCOSINE: Cosine spacing (more panels near tips)SPLIT_PROVIDED: Split each unrefined section into sub-panelsUNCHANGED: 1:1 copy when nunrefinedsections == n_panels+1
Keyword Arguments
recompute_mapping::Bool=true: Recompute the mapping from refined panels to unrefined sectionssort_sections::Bool=true: Sort sections by spanwise position using globalLE_point[2](Y-axis). Disable for structural ordering.
Effects
- Populates
wing.refined_sections(n_panels+1 sections) - Populates
wing.non_deformed_sections(copy of refined_sections for deformation reference) - Computes
wing.refined_panel_mapping(panel → unrefined section mapping) - Resizes
wing.theta_distandwing.delta_distto n_panels
Example
# YAML wing
wing = Wing("wing.yaml"; n_panels=40)
refine!(wing)
body_aero = BodyAerodynamics([wing])
# After refinement, deformation is supported
unrefined_deform!(wing, theta_angles, delta_angles)VortexStepMethod.calculate_span — Function
calculate_span(wing::AbstractWing)Calculate wing span along spanwise direction.
Returns: Float64: Wing span
VortexStepMethod.calculate_projected_area — Function
calculate_projected_area(wing::AbstractWing, z_plane_vector=[0.0, 0.0, 1.0])Calculate projected wing area onto plane defined by normal vector.
Returns: Float64: Projected area
VortexStepMethod.load_polar_data — Function
load_polar_data(csv_file_path::String) -> Tuple{Union{Nothing, Tuple{Vector{Float64}, Vector{Float64}, Vector{Float64}, Vector{Float64}}}, Symbol}Load aerodynamic polar data from a CSV file using only readlines.
The CSV file must contain a header row with columns for alpha, cl, cd, and cm (case-insensitive, order arbitrary). Each subsequent row should contain numeric values for these columns.
Arguments
csv_file_path::String: Path to the CSV file containing polar data.
Returns
- A tuple
(aero_data, model_type)where:aero_data: A tuple of vectors(alpha, cl, cd, cm)if the file is valid, ornothingif invalid or missing.alpha: Angle of attack in degrees (converted to radians internally).cl: Lift coefficient.cd: Drag coefficient.cm: Moment coefficient.
model_type:POLAR_VECTORSif data is loaded, orINVISCIDif not.
Behavior
- If the file is missing, empty, or invalid, a warning is issued and
(nothing, INVISCID)is returned.
Example
# Create a YAML-based wing from configuration file
wing = Wing(
"path/to/wing_config.yaml";
n_panels=40,
n_groups=4
)Setting the inflow conditions and solving
VortexStepMethod.set_va! — Function
set_va!(body_aero::BodyAerodynamics, va::VelVector, omega=zeros(MVec3))Set velocity array and update wake filaments.
Arguments
- body_aero::BodyAerodynamics: The BodyAerodynamics struct to modify
va::VelVector: Velocity vector of the apparent wind speed [m/s]omega::VelVector: Turn rate vector around x y and z axis [rad/s]
set_va!(body_aero::BodyAerodynamics, settings::VSMSettings)Set velocity array from VSM settings configuration.
This convenience method extracts flight conditions from VSMSettings and constructs the velocity vector in the body reference frame based on:
- Wind speed from settings.condition.wind_speed
- Angle of attack from settings.condition.alpha (converted from degrees)
- Sideslip angle from settings.condition.beta (converted from degrees)
The velocity vector is constructed as:
- Xb (forward): windspeed * cos(α) * cos(β)
- Yb (right): windspeed * sin(β)
- Zb (down): windspeed * sin(α) * cos(β)
Arguments
body_aero::BodyAerodynamics: The aerodynamic body to modifysettings::VSMSettings: Settings object containing flight conditions
Example
settings = VSMSettings("path/to/settings.yaml")
body_aero = BodyAerodynamics([wing])
set_va!(body_aero, settings)CommonSolve.solve — Function
solve(solver::Solver, body_aero::BodyAerodynamics, gamma_distribution=nothing;
log=false, reference_point=solver.reference_point)Main solving routine for the aerodynamic model. Reference point is in the kite body (KB) frame. See also: solve!
Arguments:
- solver::Solver: The solver to use, could be a VSM or LLT solver. See: Solver
- body_aero::BodyAerodynamics: The aerodynamic body. See: BodyAerodynamics
- gamma_distribution: Initial circulation vector or nothing; Length: Number of segments. [m²/s]
Keyword Arguments:
- log=false: If true, print the number of iterations and other info.
- referencepoint=solver.referencepoint
Returns
A dictionary with the results.
CommonSolve.solve! — Function
solve!(solver::Solver, body_aero::BodyAerodynamics, gamma_distribution=solver.sol.gamma_distribution;
log=false, reference_point=solver.reference_point, moment_frac=0.1)Main solving routine for the aerodynamic model. Reference point is in the kite body (KB) frame. This version is modifying the solver.sol struct and is faster than the solve function which returns a dictionary.
Arguments:
- solver::Solver: The solver to use, could be a VSM or LLT solver. See: Solver
- body_aero::BodyAerodynamics: The aerodynamic body. See: BodyAerodynamics
- gamma_distribution: Initial circulation vector or nothing; Length: Number of segments. [m²/s]
Keyword Arguments:
- log=false: If true, print the number of iterations and other info.
- referencepoint=solver.referencepoint
- moment_frac=0.1: X-coordinate of normalized panel around which the moment distribution should be calculated.
Returns
The solution of type VSMSolution
VortexStepMethod.reinit! — Method
reinit!(body_aero::BodyAerodynamics; init_aero, va, omega, refine_mesh, recompute_mapping, sort_sections)Initialize a BodyAerodynamics struct in-place by setting up panels and coefficients.
Arguments
body_aero::BodyAerodynamics: The structure to initialize
Keyword Arguments
init_aero::Bool: Whether to initialize the aero data or notva=[15.0, 0.0, 0.0]: Apparent wind vectoromega=zeros(3): Turn rate in kite body frame x y and z
Returns
nothing
VortexStepMethod.linearize — Function
linearize(solver::Solver, body_aero::BodyAerodynamics, y::Vector{T};
theta_idxs=1:4, delta_idxs=nothing, va_idxs=nothing, omega_idxs=nothing,
aero_coeffs=false, kwargs...) where TCompute Jacobian matrix of aerodynamic outputs with respect to control and kinematic inputs using finite differences. Used for control system design and linear stability analysis.
The function uses automatic differentiation with finite differences to compute ∂outputs/∂inputs. Deformations are applied to the wing's unrefined sections (the original sections before mesh refinement), with each control angle affecting one unrefined section.
Arguments
solver::Solver: Solver instance (must be configured for the wing)body_aero::BodyAerodynamics: Body aerodynamics with exactly one wingy::Vector{T}: Input vector at operating point containing control angles and/or kinematic states
Keyword Arguments
theta_idxs: Indices inyfor twist angles (one per unrefined section, default: 1:4)delta_idxs: Indices inyfor trailing edge deflections (one per unrefined section, default: nothing)va_idxs: Indices inyfor apparent wind velocity [vx, vy, vz] (default: nothing)omega_idxs: Indices inyfor angular velocity [ωx, ωy, ωz] (default: nothing)aero_coeffs::Bool: Return force/moment coefficients instead of dimensional values (default: false)kwargs...: Additional arguments passed tosolve!
Index Validation
The lengths of theta_idxs and delta_idxs (if provided) must match wing.n_unrefined_sections. Unrefined sections are the original wing sections before mesh refinement for aerodynamic analysis.
Caching
The function caches previous deformation angles to avoid redundant unrefined_deform! calls during Jacobian computation. When the same angles are encountered, geometry deformation is skipped.
Returns
jac::Matrix{Float64}: Jacobian matrix (m×n) where m = 6 + nunrefinedsections, n = length(y)results::Vector{Float64}: Output vector at operating point- If
aero_coeffs=false: [Fx, Fy, Fz, Mx, My, Mz, momentunrefineddist...] - If
aero_coeffs=true: [CFx, CFy, CFz, CMx, CMy, CMz, cmunrefineddist...]
- If
Example
# Create deformable wing with 4 unrefined sections
wing = ObjWing("kite.obj", "airfoil.dat"; n_unrefined_sections=4)
body_aero = BodyAerodynamics([wing], va=[15.0, 0, 0])
solver = Solver(body_aero)
# Operating point: 4 twist angles + velocity + angular rates
y_op = [zeros(4); # theta angles [rad]
[15.0, 0.0, 0.0]; # va [m/s]
zeros(3)] # omega [rad/s]
# Compute Jacobian
jac, results = linearize(solver, body_aero, y_op;
theta_idxs=1:4,
va_idxs=5:7,
omega_idxs=8:10,
aero_coeffs=true
)
# jac is (10×10): [6 force/moment coeffs + 4 unrefined moment coeffs] × [4 theta + 3 va + 3 omega]VortexStepMethod.calculate_results — Function
calculate_results(body_aero::BodyAerodynamics, gamma_new,
density, aerodynamic_model_type::Model,
core_radius_fraction, mu,
alpha_dist, v_a_dist,
chord_array, x_airf_array,
y_airf_array, z_airf_array,
va_array, va_norm_array,
va_unit_array, panels::Vector{Panel},
is_only_f_and_gamma_output::Bool)Calculate final aerodynamic results. Reference point is in the kite body (KB) frame.
Returns: Dict: Results including forces, coefficients and distributions
Main Plotting Functions
The plotting functions are implemented as package extensions. They are available when GLMakie (or ControlPlots) is loaded before VortexStepMethod. The examples use GLMakie.
VortexStepMethod.plot_geometry — Function
plot_geometry(body_aero::BodyAerodynamics, title;
data_type=".png", save_path=nothing,
is_save=false, is_show=false,
view_elevation=15, view_azimuth=-120, use_tex=false)Plot wing geometry from different viewpoints using Makie.
Arguments:
body_aero: the BodyAerodynamics to plottitle: plot title
Keyword arguments:
data_type: File extension (default: ".png", also supports ".jpeg")save_path: Path for saving (default: nothing)is_save: Whether to save (default: false)is_show: Whether to display (default: false)view_elevation: View elevation angle [°] (default: 15)view_azimuth: View azimuth angle [°] (default: -120)use_tex: Ignored for Makie (default: false)
VortexStepMethod.plot_distribution — Function
plot_distribution(y_coordinates_list, results_list, label_list;
title="spanwise_distribution", data_type=".png",
save_path=nothing, is_save=false, is_show=true, use_tex=false)Plot spanwise distributions of aerodynamic properties using Makie.
Arguments
y_coordinates_list: List of spanwise coordinatesresults_list: List of result dictionarieslabel_list: List of labels for different results
Keyword arguments
title: Plot title (default: "spanwise_distribution")data_type: File extension (default: ".png", also supports ".jpeg")save_path: Path to save plots (default: nothing)is_save: Whether to save (default: false)is_show: Whether to display (default: true)use_tex: Ignored for Makie (default: false)
VortexStepMethod.plot_polars — Function
plot_polars(solver_list, body_aero_list, label_list;
literature_path_list=String[],
angle_range=range(0, 20, 2), angle_type="angle_of_attack",
angle_of_attack=0.0, side_slip=0.0, v_a=10.0,
title="polar", data_type=".png", save_path=nothing,
is_save=true, is_show=true, use_tex=false)Plot polar data comparing different solvers using Makie.
Arguments
solver_list: List of aerodynamic solversbody_aero_list: List of wing aerodynamics objectslabel_list: List of labels for each configuration
Keyword arguments
literature_path_list: Optional paths to literature data filesangle_range: Range of angles [°]angle_type: "angleofattack" or "sideslip" (default: angleof_attack)angle_of_attack: AoA [rad] (default: 0.0)side_slip: Side slip angle [rad] (default: 0.0)v_a: Wind speed [m/s] (default: 10.0)title: Plot titledata_type: File extension (default: ".png", also supports ".jpeg")save_path: Path to save (default: nothing)is_save: Whether to save (default: true)is_show: Whether to display (default: true)use_tex: Ignored for Makie (default: false)
VortexStepMethod.plot_polar_data — Function
plot_polar_data(body_aero::BodyAerodynamics;
alphas=collect(deg2rad.(-5:0.3:25)),
delta_tes=collect(deg2rad.(-5:0.3:25)),
is_show=true, use_tex=false)Plot polar data (Cl, Cd, Cm) as 3D surfaces using Makie.
Arguments
body_aero: Wing aerodynamics struct
Keyword arguments
alphas: Range of AoA values [rad] (default: -5° to 25° in 0.3° steps)delta_tes: Range of trailing edge angles [rad] (default: -5° to 25° in 0.3° steps)is_show: Whether to display (default: true)use_tex: Ignored for Makie (default: false)
VortexStepMethod.plot_combined_analysis — Function
plot_combined_analysis(solver, body_aero, results;
solver_label="VSM",
angle_range=range(0,20,length=20),
angle_type="angle_of_attack",
angle_of_attack=0.0, side_slip=0.0, v_a=10.0,
title="Combined Analysis",
view_elevation=15, view_azimuth=-120,
is_show=true, use_tex=false,
literature_path_list=String[],
data_type=".png", save_path=nothing, is_save=false)Create combined multi-panel figure with geometry, polar data, distributions, and polars.
Arguments
solver: Aerodynamic solverbody_aero: BodyAerodynamics objectresults: Solution dictionary from solve()
Keyword arguments
labels: Optional label string or label vector. If a vector with lengthlength(solver)+length(literature_path_list)is provided, the tail is used for literature labels; otherwise literature labels default to file basenames.solver_label: Backward-compatible alias forlabels.angle_range: Range of angles for polars (default: range(0,20,length=20))angle_type: "angleofattack" or "sideslip" (default: "angleof_attack")angle_of_attack: AoA in degrees (default: 0.0)side_slip: Side slip in degrees (default: 0.0)v_a: Wind speed in m/s (default: 10.0)title: Overall figure title (default: "Combined Analysis")view_elevation: Geometry view elevation [°] (default: 15)view_azimuth: Geometry view azimuth [°] (default: -120)is_show: Display figure (default: true)use_tex: Ignored for Makie (default: false)literature_path_list: Paths to literature CSV files (default: String[])data_type: File extension (default: ".png", also supports ".jpeg")save_path: Directory path to save files (default: nothing)is_save: Save plots to files (default: false)
Helper Functions
VortexStepMethod.save_plot — Function
save_plot(fig, save_path, title; data_type=".png")Save a Makie figure to a file.
Arguments
fig: Makie Figure objectsave_path: Path to save the plottitle: Title of the plot
Keyword arguments
data_type: File extension (default: ".png", also supports ".jpeg")
VortexStepMethod.show_plot — Function
show_plot(fig; dpi=130)Display a Makie figure.
Arguments
fig: Makie Figure object
Keyword arguments
dpi: Dots per inch for the figure (default: 130) - currently unused in Makie