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:

  • wing::Wing: The Wing to which a section shall be added
  • LE_point::PosVector: PosVector of the point on the side of the leading edge
  • TE_point::PosVector: PosVector of the point on the side of the trailing edge
  • aero_model::AeroModel: AeroModel
  • aero_data::AeroData: See AeroData
source
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 aerodynamics

Distribution Methods

  • LINEAR: Linear interpolation between sections
  • COSINE: Cosine spacing (more panels near tips)
  • SPLIT_PROVIDED: Split each unrefined section into sub-panels
  • UNCHANGED: 1:1 copy when nunrefinedsections == n_panels+1

Keyword Arguments

  • recompute_mapping::Bool=true: Recompute the mapping from refined panels to unrefined sections
  • sort_sections::Bool=true: Sort sections by spanwise position using global LE_point[2] (Y-axis). Disable for structural ordering.

Effects

  1. Populates wing.refined_sections (n_panels+1 sections)
  2. Populates wing.non_deformed_sections (copy of refined_sections for deformation reference)
  3. Computes wing.refined_panel_mapping (panel → unrefined section mapping)
  4. Resizes wing.theta_dist and wing.delta_dist to 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)
source
VortexStepMethod.load_polar_dataFunction
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, or nothing if 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_VECTORS if data is loaded, or INVISCID if 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
)
source

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]
source
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 modify
  • settings::VSMSettings: Settings object containing flight conditions

Example

settings = VSMSettings("path/to/settings.yaml")
body_aero = BodyAerodynamics([wing])
set_va!(body_aero, settings)
source
CommonSolve.solveFunction
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.

source
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

source
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 not
  • va=[15.0, 0.0, 0.0]: Apparent wind vector
  • omega=zeros(3): Turn rate in kite body frame x y and z

Returns

nothing

source
VortexStepMethod.linearizeFunction
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 T

Compute 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 wing
  • y::Vector{T}: Input vector at operating point containing control angles and/or kinematic states

Keyword Arguments

  • theta_idxs: Indices in y for twist angles (one per unrefined section, default: 1:4)
  • delta_idxs: Indices in y for trailing edge deflections (one per unrefined section, default: nothing)
  • va_idxs: Indices in y for apparent wind velocity [vx, vy, vz] (default: nothing)
  • omega_idxs: Indices in y for angular velocity [ωx, ωy, ωz] (default: nothing)
  • aero_coeffs::Bool: Return force/moment coefficients instead of dimensional values (default: false)
  • kwargs...: Additional arguments passed to solve!

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...]

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]
source
VortexStepMethod.calculate_resultsFunction
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

source

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_geometryFunction
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 plot
  • title: 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)
source
VortexStepMethod.plot_distributionFunction
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 coordinates
  • results_list: List of result dictionaries
  • label_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)
source
VortexStepMethod.plot_polarsFunction
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 solvers
  • body_aero_list: List of wing aerodynamics objects
  • label_list: List of labels for each configuration

Keyword arguments

  • literature_path_list: Optional paths to literature data files
  • angle_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 title
  • data_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)
source
VortexStepMethod.plot_polar_dataFunction
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)
source
VortexStepMethod.plot_combined_analysisFunction
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 solver
  • body_aero: BodyAerodynamics object
  • results: Solution dictionary from solve()

Keyword arguments

  • labels: Optional label string or label vector. If a vector with length length(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 for labels.
  • 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)
source

Helper Functions

VortexStepMethod.save_plotFunction
save_plot(fig, save_path, title; data_type=".png")

Save a Makie figure to a file.

Arguments

  • fig: Makie Figure object
  • save_path: Path to save the plot
  • title: Title of the plot

Keyword arguments

  • data_type: File extension (default: ".png", also supports ".jpeg")
source
VortexStepMethod.show_plotFunction
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
source