Private API
This page documents the internal functions and types of SymbolicAWEModels.jl. These are not part of the public API and may change without notice. They are listed here for developers and for those interested in the model's internal workings.
Core types and constructors
SymbolicAWEModels.SerializedModel — Type
@with_kw mutable struct SerializedModel{...}A type-stable container for the compiled and serialized components of a SymbolicAWEModel.
This struct holds the products of the ModelingToolkit.jl compilation process, now organized into nested attribute structs (ProbWithAttributes, etc.). This simplifies the structure and improves serialization robustness.
set_hash::Vector{UInt8}sys_struct_hash::Vector{UInt8}full_sys::Union{Nothing, ModelingToolkitBase.System}: Unsimplified system of the mtk model Default: nothingdefaults::AbstractVector: Default: Pair{Num, Any}[]inputs::Union{Symbolics.Arr, Vector{Symbolics.Num}}: Symbolic representation of the control inputs. Default: Num[]outputs::Union{Symbolics.Arr, Vector{Symbolics.Num}}: Outputs of the linearization and control function. Default: Num[]prob::Union{Nothing, SymbolicAWEModels.ProbWithAttributes}: Container for the ODE problem and its getters/setters. Default: nothinglin_prob::Union{Nothing, SymbolicAWEModels.LinProbWithAttributes}: Container for the linearization problem and its components. Default: nothingcontrol_functions::Union{Nothing, SymbolicAWEModels.ControlFuncWithAttributes}: Container for the control functions. Default: nothing
SymbolicAWEModels.SimFloat — Type
const SimFloat = Float64This type is used for all real variables, used in the Simulation. Possible alternatives: Float32, Double64, Dual Other types than Float64 or Float32 do require support of Julia types by the solver.
SymbolicAWEModels.KVec3 — Type
const KVec3 = MVector{3, SimFloat}
Basic 3-dimensional vector, stack allocated, mutable.
SymbolicAWEModels.SVec3 — Type
const SVec3 = SVector{3, SimFloat}
Basic 3-dimensional vector, stack allocated, immutable.
SymbolicAWEModels.InplaceGetter — Type
InplaceGetter{F, B, G}A single zero-allocation getter that both reads and scatters all per-step component state. fn is an in-place MTK observed function fn(buf, u, p, t) over the concatenation of every component's output arrays; buf is a preallocated flat buffer reused each call; groups is a tuple of ScatterGroups that write the freshly-computed buffer straight into the SystemStructure fields. One spec drives both the buffer layout and the scatter.
SymbolicAWEModels.ScatterGroup — Type
ScatterGroup{Sel, Fns, Views}One component group of an InplaceGetter. selector(sys_struct) returns the group's component vector (e.g. sys_struct.points); copyfns is a tuple of (component, view) -> _ closures, one per output array, each copying that array's slice into the component's struct field; views is the matching tuple of zero-copy reshaped views into the getter's buffer.
SymbolicAWEModels.create_vsm_wing — Function
create_vsm_wing(set::Settings, vsm_set::VortexStepMethod.VSMSettings;
prn=true, sort_sections=true, n_unrefined_sections=nothing)Create a VortexStepMethod.Wing geometry object from the settings provided.
This function checks for .obj and .dat files in the model directory. If found, it uses VortexStepMethod.ObjWing(obj_path, dat_path) to load the wing, passing n_unrefined_sections through (nothing leaves the choice to ObjWing, which defaults to one unrefined section per panel boundary). Otherwise, it falls back to loading from aero_geometry.yaml.
SymbolicAWEModels.build_vsm_engine — Function
build_vsm_engine(set, vsm_set, dynamics_type; point_to_vsm_point=nothing,
wing_segments=nothing, aero_scale_chord=0.0, aero_z_offset=0.0,
n_unrefined_sections=nothing)Build a VSMEngine: create the VortexStepMethod vsm_wing/vsm_aero/ vsm_solver and size the linearization state vectors. Aero-state sizes are placeholders for RIGID_DYNAMICS (using n_unrefined_sections as the twistsurface-count proxy) and resized by SystemStructure once twistsurfaces are resolved.
Keywords
point_to_vsm_point,wing_segments: VSM structural↔panel maps.aero_scale_chord,aero_z_offset: VSM force/panel adjustments.n_unrefined_sections: Forwarded tocreate_vsm_wing. Whennothing(default),ObjWingpicks its own default (one unrefined section per panel boundary).
State management and model simplification
SymbolicAWEModels.copy! — Function
copy!(sys1::SystemStructure, sys2::SystemStructure)Copy the dynamic state from one SystemStructure (sys1) to another (sys2).
This function is designed to transfer the state (positions, velocities, etc.) between two system models, which can have different levels of fidelity. For example, it can copy the state from a detailed multi-segment tether model (sys1) to a simplified single-segment model (sys2).
The function handles several cases:
- If
sys1andsys2have the same structure, it performs a direct copy of all point states. - If
sys2is a simplified (1-segment per tether) version ofsys1, it copies the positions and velocities of the tether endpoints. - It also copies the state of wings, twist_surfaces, winches, and pulleys where applicable.
SymbolicAWEModels.reinit! — Function
reinit!(transforms::AbstractVector{Transform}, sys_struct::SystemStructure;
update_vel=true)Apply transforms to all components in a SystemStructure.
Expects pos_w to already be set (via copy_cad_to_world! and optionally apply_tether_init_stretched_lens! from reinit!(sys_struct, set; ...)). Applies: translate (from pos_w) → azimuth/elevation → heading.
reinit!(sys_struct::SystemStructure, set::Settings; kwargs...)Re-initialize a SystemStructure from a Settings object.
This function resets various component states (e.g., winch lengths, twist_surface twists, pulley positions) to their initial values as defined in the Settings object. It is typically called before starting a new simulation run.
Pulley lengths are initialized proportionally based on current segment lengths: pulley.len = segment1.len / (segment1.len+segment2.len) * pulley.sum_len
Keyword Arguments
ignore_l0::Bool=false: If true, recalculate segment rest lengths from current positionsremake_vsm::Bool=false: If true, recreate VSM wing, aerodynamics, and solver from settings. This is useful after modifyingaero_geometry.yamlor other VSM-related configuration files. For PARTICLEDYNAMICS wings, also rebuilds the `pointtovsmpoint` mapping.apply_transforms::Bool=true: If false, skip applying spatial transforms (translate, rotate, heading) during reinitialization.apply_tether_lengths::Bool=true: If false, skip scaling point positions to matchtether.init_stretched_len.prn::Bool=true: If true, print info messages (e.g. when several root tethers are placed to their mean stretched length).
reinit!(sam, prob, solver; kwargs...) -> (ODEIntegrator, Bool)Reset the ODE integrator from new initial conditions without rebuilding the symbolic model. See init! for adaptive, reset_integrator, lin_vsm, and vsm_min_wind.
SymbolicAWEModels.reposition! — Function
reposition!(transforms::AbstractVector{Transform},
sys_struct::SystemStructure)Update the system's spatial orientation based on its current position, preserving velocities.
Unlike reinit!, uses current world positions (pos_w) as the starting point (no reset from CAD coordinates, no tether length scaling). Heading uses the tangential sphere frame, consistent with reinit!.
SymbolicAWEModels.update_sys_struct! — Function
update_sys_struct!(s::SymbolicAWEModel, sys_struct::SystemStructure, integ=s.integrator)Updates the high-level SystemStructure from the low-level integrator state vector.
This function reads the raw state vector from the ODE integrator and uses the generated getter functions to populate the human-readable fields in the SystemStructure. This synchronization step is crucial for making the simulation results accessible.
SymbolicAWEModels.get_set_hash — Function
get_set_hash(set::Settings; fields)Calculates a SHA1 hash for structural fields in the Settings object. This is used to check if a cached compiled model is still valid.
Structural Fields (affect symbolic equations):
:segments: Number of tether segments (affects state vector size):model: Kite model name (affects geometry):foil_file: Airfoil data file (affects VSM setup):physical_model: Model type (ram, simpleram, 4attach_ram):winch_model: Winch dynamics model (affects winch equations)
Runtime Fields (don't affect compilation, excluded from hash):
:profile_law: Wind profile law (evaluated at runtime via symbolic function):wind_vec,:elevation: Initial conditions- Other runtime parameters
SymbolicAWEModels.get_sys_struct_hash — Function
get_sys_struct_hash(sys_struct::SystemStructure)Calculates a SHA1 hash for the topology and structure of a SystemStructure. This is used to check if a cached compiled model is still valid.
Includes all structural properties that affect the symbolic equations:
- Point connectivity and types (STATIC, DYNAMIC, WING, BODY_STATIC)
- Segment connectivity
- TwistSurface structure and types (STATIC, DYNAMIC)
- Pulley constraints and types
- Tether topology
- Winch configuration
- Wing topology, connectivity, aerodynamic model type (RIGIDDYNAMICS vs PARTICLEDYNAMICS), and aero mode
- Transform hierarchy
Excludes runtime-configurable properties like masses, lengths, stiffnesses.
Physics and geometry helpers
SymbolicAWEModels.WindFactor — Type
WindFactor(am, profile_law)Callable wind-shear factor, used as a callable flat parameter w(pos_z): the ratio of wind speed at height pos_z to the ground value, from atmospheric model am under profile_law (1.0 when profile_law == 0). ForwardDiff.Dual-safe in pos_z. Read live from sys_struct via WindFactorReader.
SymbolicAWEModels.WindFactorReader — Type
WindFactorReader()Serializable flat-param reader producing a WindFactor from the live sys_struct's atmospheric model and wind profile law.
SymbolicAWEModels.calc_angle_of_attack — Function
calc_angle_of_attack(va_wing_b)Calculate the angle of attack [rad] from the apparent wind vector va_wing_b in the body frame.
SymbolicAWEModels.calc_heading — Function
calc_heading(sys::SystemStructure)Calculate heading angles for all wings using the tangential sphere frame method. Returns a vector of heading angles, one per wing.
calc_heading(R_b_to_w, wing_pos)Calculate heading angle using the tangential sphere frame.
Projects the body x-axis onto the tangent plane of the tether sphere at wing_pos. Heading is measured from the elevation direction (xt, away from zenith) toward the azimuthal direction (yt). Heading = 0 when the kite nose points toward the ground station.
SymbolicAWEModels.calc_R_t_to_w — Function
calc_R_t_to_w(wing_pos)Calculate the rotation matrix from the local tether frame (_t) to the world frame (_w).
The tether frame is a local spherical coordinate system:
- z-axis: Aligned with the tether (radial direction).
- y-axis: Azimuthal direction, parallel to the XY plane.
- x-axis: Elevation direction, tangent to the sphere (
y × z).
SymbolicAWEModels.calc_R_v_to_w — Function
calc_R_v_to_w(wing_pos, e_x)Calculate the rotation matrix from the view frame (_v) to the world frame (_w).
The view frame is defined with its z-axis pointing from the origin to the wing, and its x-axis aligned with the wing's x-axis projected onto the view plane.
SymbolicAWEModels.cad_to_body_frame — Function
cad_to_body_frame(wing::Wing, pos)Transform a position from the CAD frame to the wing's body frame.
SymbolicAWEModels.calc_pos — Function
calc_pos(wing::Wing, gamma, frac)Calculate a position on the kite based on spanwise (gamma) and chordwise (frac) parameters.
SymbolicAWEModels.calc_winch_force — Function
calc_winch_force(sys, winch_vel, winch_acc, set_values)Calculate the tensile force on each winch tether from its motion and motor torque, using the default-component motor dynamics inverted for the force connector.
Reads friction from the live winch struct (populated each step from the component output), so the formula matches whatever the component reported even if the component overrides friction.
SymbolicAWEModels.quaternion_to_rotation_matrix — Function
quaternion_to_rotation_matrix(q)Convert a quaternion q (scalar-first format [w, x, y, z]) to a 3x3 rotation matrix.
SymbolicAWEModels.rotation_matrix_to_quaternion — Function
rotation_matrix_to_quaternion(R)Convert a 3x3 rotation matrix R to a quaternion (scalar-first format [w, x, y, z]), selecting the numerically stable branch (largest of the trace / diagonal). Written with ifelse and & (not if/&&) so it evaluates symbolically on Num as well as on concrete Float64 — a single symbolic expression, computed once and common-subexpression-shared across components, with no @register_symbolic. The unselected branches' sqrt arguments are clamped to ≥0 so they never error; the selected branch always has a positive radicand.
SymbolicAWEModels.rotate_v_around_k — Function
rotate_v_around_k(v, k, θ)Rotate vector v around axis k by angle θ using Rodrigues' rotation formula.
SymbolicAWEModels.smooth_norm — Function
smooth_norm(v, eps=1e-12)Differentiable norm: sqrt(sum(abs2, v) + eps^2).
SymbolicAWEModels.smooth_normalize — Function
smooth_normalize(vec)Differentiable normalization: vec / smooth_norm(vec).
SymbolicAWEModels.apply_heading — Function
apply_heading(vec, R_t_to_w, curr_R_t_to_w, heading)Apply a heading rotation to a vector.
SymbolicAWEModels.get_rot_pos — Function
get_rot_pos(transform::Transform, bodies, points)Get the world position of the rotating object (body or point).
SymbolicAWEModels.get_base_pos — Function
get_base_pos(transform, transforms, bodies, points)Get (base_pos, curr_base_pos) for a transform.
For chained transforms (base_transform): returns the parent's current world position and CAD position, so T = base_pos - curr_base_pos shifts child points by the same displacement the parent transform applied.
For direct transforms (base_pos + base_point): returns the user-specified position and the base point's current position.
SymbolicAWEModels.calc_aoa — Function
calc_aoa(s::SymbolicAWEModel)Angle of attack [rad] of the first wing, dispatched on its aero mode (calc_aoa(::AbstractAeroModel, wing)). NaN if the mode defines no AoA.
calc_aoa(mode::AbstractAeroModel, wing) -> SimFloatAngle of attack [rad] for wing under aero mode. Defaults to NaN (undefined); VSM modes read the mid-span geometric AoA (wrapped to [-π, π]) and AeroPlate derives it from the body-frame apparent wind.
Equations and system management
SymbolicAWEModels.create_sys! — Function
create_sys!(s::SymbolicAWEModel, system::SystemStructure; prn=true)Create the full ModelingToolkit.ODESystem for the AWE model.
This is the main top-level function that orchestrates the generation of the entire set of differential-algebraic equations (DAEs). It calls specialized sub-functions to build the equations for each part of the system (forces, wing dynamics, scalar kinematics, linearized aerodynamics) and assembles them into a single System.
Arguments
s::SymbolicAWEModel: The main model object to be populated with the system.system::SystemStructure: The physical structure definition.prn::Bool=true: If true, print progress information during system creation.
Returns
set_values: The symbolic variable representing the control inputs (winch torques).
SymbolicAWEModels.scalar_eqs! — Function
scalar_eqs!(s, eqs, params; kwargs...)Generate equations for derived scalar kinematic quantities useful for control and analysis.
This includes elevation, azimuth, heading, course, angle of attack, and their time derivatives, as well as apparent wind calculations.
Arguments
s::SymbolicAWEModel: The main model object.eqs: Accumulating equation vector.kwargs...: Symbolic variables for the system's state.
Returns
eqs: The updated list of system equations.
SymbolicAWEModels.wing_eqs! — Function
wing_eqs!(s, eqs, defaults, params, initial; kwargs...)Generate the differential equations for the wing's rigid body dynamics.
For RIGID_DYNAMICS wings:
- ODE state:
com_w,com_vel,Q_p_to_w,ω_p(principal frame) - Wing-specific loads (aero transport, tether, damping) and pinning constraints are assembled here, then the generic 6-DOF integration is delegated to
rigid_body_eqs!.
For PARTICLE_DYNAMICS wings:
- No rigid body dynamics (handled by DYNAMIC points)
R_b_to_wfrom structural ref points- Principal frame variables set to zero/aliases
SymbolicAWEModels.rigid_body_eqs! — Function
rigid_body_eqs!(eqs, defaults, idx; kwargs...)Append the 6-DOF rigid body equations for body idx to eqs and its initial-condition defaults. Given a total force_w at the center of mass and moment_w about it (both world frame), integrate quaternion attitude and COM translation, and emit the body-frame output.
This generator knows nothing about aerodynamics, pinning constraints, or damping. Those are the caller's concern: a caller imposes them by passing the ω_kinematic/d_ω_p/d_com_w/d_com_vel integration overrides. With the overrides left at their defaults the body integrates freely.
State (principal frame, integrated)
com_w, com_vel, Q_p_to_w, ω_p.
Required keyword arguments
force_w,moment_w: length-3Numvectors, total force at / moment about the COM in world frame.inertia_p: length-3 principal inertia;mass: scalar.R_b_to_p: constant body→principal rotation;com_offset_b: COM offset in the body frame (origin→COM).- State / output array variables (indexed
[.., idx]internally):com_w,com_vel,Q_p_to_w,ω_p,com_acc,α_p,R_p_to_w,moment_p,Q_p_vel,R_b_to_w,wing_pos,wing_vel,wing_acc,ω_b,α_b,Q_b_to_w. - Initial conditions:
initial_com_w,initial_com_vel,initial_Q_p_to_w,initial_ω_p—initial.*view paths bound to the integrated state.
Optional integration overrides (default to the unconstrained body)
ω_kinematic: angular velocity used in quaternion kinematics (defaultω_p).d_ω_p: RHS ofD(ω_p)(defaultα_p).d_com_w: RHS ofD(com_w)(defaultcom_vel).d_com_vel: RHS ofD(com_vel)(defaultcom_acc).
SymbolicAWEModels.body_eqs! — Function
body_eqs!(eqs, defaults, bodies, params, initial; kwargs...)Generate the differential equations for each plain Body (no aero). Loads are the accumulated joint wrench (body_force/body_moment, filled by joint_eqs!) plus gravity (-g·mass at the COM) and the external wrench (ext_force_w world, ext_force_b/ext_moment_b body). STATIC bodies are frozen; fix_sphere confines the COM to a sphere about the world origin; damping is per-axis angular damping.
SymbolicAWEModels.joint_eqs! — Function
joint_eqs!(eqs, elastic_joints, params; kwargs...)For each ElasticJoint, compute the restoring wrench from the relative pose of the two anchors (in body A's frame) and accumulate it — equal and opposite — into body_force/body_moment (the same accumulators body_eqs! reads). The relative rotation uses the small-angle vector extraction, exact for the small per-joint rotations of a stiff chain.
SymbolicAWEModels.timoshenko_joint_eqs! — Function
timoshenko_joint_eqs!(eqs, timoshenko_joints, params; kwargs...)For each TimoshenkoJoint, build a corotational element frame, extract the small per-node deformations (axial stretch, chord-relative rotations) relative to the rest geometry, evaluate the consistent Timoshenko stiffness (axial, torsion, and two bending planes with the shear reduction Φ = 12·EI/(k·GA·L²)) — each rigidity either constant or a callable of its strain/curvature (timoshenko_rigidity) — and accumulate the restoring wrench — equal and opposite, transported to each COM — into body_force/body_moment (the same accumulators body_eqs! reads). Damping resists the relative node velocity and spin.
SymbolicAWEModels.init_rigid_body! — Function
init_rigid_body!(body::Body)Derive the body's principal-frame ODE state from its body-frame initial conditions via init_principal_state!.
SymbolicAWEModels.n_orient_frames — Function
Number of orientation frames (wings + rigid bodies, at least 1).
SymbolicAWEModels.aero_eqs! — Function
aero_eqs!(s, eqs; kwargs...)
-> (eqs, aero_subsystems)Instantiate and wire each wing's aero component. Returns the list of component subsystems to attach to the parent System.
SymbolicAWEModels.point_eqs! — Function
point_eqs!(s, eqs, defaults, points, segments, twist_surfaces, wings, params, initial;
R_b_to_w, wing_vel, wind_vec_gnd, twist_angle,
pos, vel, acc, point_force, point_mass, spring_force_vec, drag_force, l0,
spring_sum_force, point_drag_force, total_drag,
disturb_force, tether_r, chord_b, fixed_pos, normal, pos_b,
fix_point_sphere, fix_static, body_frame_damping, world_frame_damping,
va_point_b, va_point_w, wind_at_point, height,
aero_force_point_b,
twist_surface_y_airf)Generate equations for all point types (STATIC, DYNAMIC, WING).
Arguments
s::SymbolicAWEModel: The main model object (for atmospheric model).eqs,defaults: Accumulating vectors for the MTK system.points,segments,twist_surfaces,wings: System components.R_b_to_w: Symbolic rotation matrix (body to world).wing_vel: Symbolic wing center of mass velocity.wind_vec_gnd: Symbolic ground-level wind vector.twist_angle: Symbolic twist_surface twist angle.pos,vel,acc: Pre-declared point state variables.point_force,point_mass: Pre-declared point force and mass variables.spring_force_vec,drag_force,l0: Pre-declared segment force variables.spring_sum_force: Pre-declared accumulated spring/drag forces variable.- Other variables: Various point-specific symbolic variables.
body_force,body_moment: Mutable arrays to accumulate WING-point loads onto bodies.
Returns
- Tuple
(eqs, defaults)with updated equation vectors. Note:body_forceandbody_momentare modified in-place.
SymbolicAWEModels.segment_eqs! — Function
segment_eqs!(s, eqs, points, segments, pulleys, tethers, bodies, params;
pos, vel, wind_vec_gnd, spring_force_vec, drag_force, l0,
pulley_len, tether_len)Generate equations for segment spring-damper forces and aerodynamic drag.
Arguments
s::SymbolicAWEModel: The main model object (for atmospheric model).eqs: Accumulating equation vector for the MTK system.points,segments,pulleys,tethers,bodies: System components.pos,vel: Symbolic point state variables.wind_vec_gnd: Symbolic ground-level wind vector.spring_force_vec,drag_force,l0: Pre-declared segment force variables.pulley_len,tether_len: Symbolic state variables for pulley and tether lengths.
Returns
- Tuple
(eqs, len, spring_force)with updated equation vector and the segment length and spring force variables for use by other components.
SymbolicAWEModels.refresh_aero! — Function
refresh_aero!(sam::SymbolicAWEModel; vsm_min_wind=0.5)Refresh each wing's aerodynamic state, dispatching on the wing's aero mode (refresh_rigid_aero! / refresh_particle_aero!). Runs on the low-frequency VSM-update schedule (vsm_interval), not the compiled RHS. Reads per-point apparent wind from the points (populated by update_sys_struct!, which always runs first), so it does not re-extract integrator state.
RIGID_DYNAMICS VSM modes: compute wind-axis coefficients (CL, CD, CS, CM, cm) at the operating point, plus the ForwardDiff Jacobian over [α, β, ω₁, ω₂, ω₃, θ_twist…] (AeroLinearized) or the frozen forces (AeroDirect).
PARTICLE_DYNAMICS VSM modes: full nonlinear VSM solve with per-point force distribution. Non-VSM modes (AeroNone/AeroPlate) are no-ops, so callers should gate this on has_vsm_wing.
SymbolicAWEModels.sync_aero_density! — Function
sync_aero_density!(wing, am)Set the wing's VSM solver air density to calc_rho(am, wing.pos_w[3]), the same altitude-dependent density the symbolic RHS uses to dimensionalize aero forces (see aero_eqs.jl). Keeps the VSM solve and the model consistent on dynamic pressure. No-op for non-VSM aero modes.
SymbolicAWEModels.jacobian — Function
jacobian(f::Function, x::AbstractVector, ϵ::AbstractVector) -> MatrixNumerically compute the Jacobian of a vector-valued function f at point x.
This function uses a simple forward finite difference method to approximate the partial derivatives of f with respect to each component of x.
Arguments
f::Function: The function to differentiate (y = f(x)).x::AbstractVector: The point at which to evaluate the Jacobian.ϵ::AbstractVector: A vector of perturbation sizes for each component ofx.
Returns
Matrix: The Jacobian matrixJ, whereJ[i, j] = ∂f[i] / ∂x[j].
SymbolicAWEModels.load_serialized_model! — Function
load_serialized_model!(sam, model_path; remake=false, reload=false)Load a serialized model from disk if it is valid.
A model is considered valid if its settings and system structure hashes match the current ones in the SymbolicAWEModel object (sam).
Arguments
sam::SymbolicAWEModel: The main model object.model_path::String: The path to the serialized model file.remake::Bool: If true, forces the model to be considered invalid, triggering a rebuild.reload::Bool: If true, forces reloading from disk even if the model is already in memory.
Returns
trueif a valid model was successfully loaded intosam.serialized_model,falseotherwise.
SymbolicAWEModels.maybe_create_lin_prob! — Function
maybe_create_lin_prob!(sam, outputs; ...)Create and cache the LinearizationProblem if it does not exist or if the outputs have changed.
Arguments
sam::SymbolicAWEModel: The main model object.outputs: A vector of output variables for the linearization.create_lin_prob::Bool: Flag to enable/disable creation.outputs_changed::Bool: Flag indicating if the output vector has changed.prn::Bool: Flag to enable/disable printing of progress messages.
Returns
trueif a new problem was created,falseotherwise.
SymbolicAWEModels.maybe_create_control_functions! — Function
maybe_create_control_functions!(sam, outputs; ...)Create and cache the control functions if they do not exist or if the outputs have changed.
Arguments
sam::SymbolicAWEModel: The main model object.outputs: A vector of output variables for the control functions.create_control_func::Bool: Flag to enable/disable creation.outputs_changed::Bool: Flag indicating if the output vector has changed.prn::Bool: Flag to enable/disable printing of progress messages.
Returns
trueif new functions were created,falseotherwise.
SymbolicAWEModels.maybe_create_prob! — Function
maybe_create_prob!(sam; create_prob=true, prn=true)Create and cache the ODEProblem if it does not already exist.
This function compiles the full system, creates the ODEProblem, and generates the necessary getter/setter functions.
Arguments
sam::SymbolicAWEModel: The main model object.create_prob::Bool: A flag to enable or disable the creation of the problem.prn::Bool: A flag to enable or disable printing of progress messages.
Returns
trueif a new problem was created,falseotherwise.
SymbolicAWEModels.has_custom_component — Function
has_custom_component(sys_struct)Return true when the system has a non-default winch model or a wing using a custom aero model, in which case the compiled model cannot be reused from cache and must be rebuilt.
SymbolicAWEModels.generate_control_funcs — Function
generate_control_funcs(model, inputs, outputs)Generate in-place and out-of-place control functions from a ModelingToolkit system.
This function wraps ModelingToolkit.generate_control_function and ModelingToolkit.build_explicit_observed_function to create the necessary functions for simulation and analysis.
Arguments
model: The fullODESystem.inputs: A vector of input variables.outputs: A vector of output variables.
Returns
- A
NamedTuplecontaining the generated functions (f_oop,f_ip,h_oop,h_ip), system dimensions (nu,nx,ny), and symbolic variables (dvs,psym,io_sys).
SymbolicAWEModels.generate_lin_getters — Function
generate_lin_getters(sys)Generate setter functions for the parameters of a linearized system.
Arguments
sys: The linearized ModelingToolkit system.
Returns
- A
NamedTuplecontaining the setter function for the winch set-points (set_set_values).
SymbolicAWEModels.generate_prob_getters — Function
generate_prob_getters(sys_struct, sys)Generate getter and setter functions for the state variables of the full system model.
These functions provide a convenient way to access and modify the state and parameters of the compiled ODESystem (sys).
Arguments
sys_struct::SystemStructure: The structure defining the system topology.sys::ODESystem: The compiled ModelingToolkit system.
Returns
- A
NamedTuplecontaining various getter and setter functions for different parts of the system state.
SymbolicAWEModels.scatter_spec — Function
scatter_spec(selector, pairs...)Describe one component group: selector(sys_struct) yields its component vector and each sys_array => copyfn pair maps a symbolic output array to a (component, view) -> _ closure that writes that array's slice into the component's struct field. This is the single source of truth — both the buffer layout and the scatter derive from the same ordered list.
SymbolicAWEModels.build_inplace_getter — Function
build_inplace_getter(sys, specs)Build one InplaceGetter from the per-group specs (see scatter_spec). All component output arrays are concatenated into a single MTK in-place observed function so shared subexpressions (e.g. the spring/force network) are computed once.
SymbolicAWEModels.build_grouped_views — Function
build_grouped_views(buf, group_shapes)Build a tuple (per group) of tuples (per output array) of zero-copy reshaped views into buf. Flat order is groups-in-order, arrays-in-order, column-major within each array — shared by build_inplace_getter and deserialization so layouts always match.
SymbolicAWEModels.copy_vec! — Function
Copy column idx of the matrix view v into the mutable vector field in place.
SymbolicAWEModels.scatter_component — Function
Apply each (component, view) copy closure for one component (tuple recursion).
SymbolicAWEModels.scatter_groups — Function
Scatter every group into sys_struct (tuple recursion over heterogeneous groups).
SymbolicAWEModels.LinProbWithAttributes — Type
@with_kw struct LinProbWithAttributes{SetLinSetValues, SetLinSys, SetLinSet, LinOut}A container for the general-purpose linearization problem and the resulting full linearized model (A,B,C,D matrices).
prob::Any: Linearization problem of the mtk model.set_set_values::Any
SymbolicAWEModels.ProbWithAttributes — Type
@with_kw struct ProbWithAttributes{...}A container for the main Ordinary Differential Equation (ODE) problem and its associated getter and setter functions for the full, nonlinear physical state.
SymbolicAWEModels.ControlFuncWithAttributes — Type
@with_kw struct ControlFuncWithAttributes{FIP, FOOP, HIP, HOOP, DVS, PSYM}A container for callable control functions and their symbolic representations, generated from the full system model.
f_ip::Any: In-place dynamics function f(dx, x, u, p, t).f_oop::Any: Out-of-place dynamics function dx = f(x, u, p, t).h_ip::Any: In-place observation function h(y, x, u, p, t).h_oop::Any: Out-of-place observation function y = h(x, u, p, t).nu::Int64: Number of inputs (u).nx::Int64: Number of states (x).ny::Int64: Number of outputs (y).dvs::Any: The symbolic state vector.psym::Any: The symbolic parameter vector.io_sys::ModelingToolkitBase.System: The generated input-output system.
Utility and internal functions
SymbolicAWEModels.get_model_name — Function
get_model_name(set::Settings, sys_struct::SystemStructure; precompile=false)Constructs a unique filename for the serialized model based on its configuration. The filename includes the SymbolicAWEModels version, Julia version, physical model, wing type, dynamics type, and component counts to ensure that the correct cached model is loaded.
SymbolicAWEModels.calc_height — Function
calc_height(s::SymbolicAWEModel)Returns the height (z-position) [m] of the wing.
SymbolicAWEModels.pos — Function
pos(s::SymbolicAWEModel)Returns a vector of the position vectors [m] for each point in the system.
SymbolicAWEModels.spring_forces — Function
spring_forces(s::SymbolicAWEModel)Returns the spring force [N] for each tether segment.
SymbolicAWEModels.create_model_archive — Function
create_model_archive(source_dir, archive_path)Finds all model*.bin files in the source_dir, copies them to a temporary directory, and compresses that directory into a .tar.gz archive at the specified archive_path.
SymbolicAWEModels.filecmp — Function
filecmp(path1::AbstractString, path2::AbstractString) -> BoolCompare two files byte-by-byte to check if they are identical.
SymbolicAWEModels.extract_model_archive — Function
extract_model_archive(archive_path, dest_dir)Safely decompress a .tar.gz file by first extracting to a temporary directory and then copying the contents to the final destination.
Arguments
archive_path::String: The path to the.tar.gzfile to be extracted.dest_dir::String: The path to the target directory.
SymbolicAWEModels.copy_bin — Function
copy_bin()Copy all example scripts to the folder "bin" (it will be created if it doesn't exist).
SymbolicAWEModels.copy_examples — Function
copy_examples()Copy all example scripts to the folder "examples" (it will be created if it doesn't exist).
SymbolicAWEModels.copy_data — Function
copy_data()Copy all data scripts to the folder "data" (it will be created if it doesn't exist).
SymbolicAWEModels.copy_dir — Function
copy_dir(src_dir, dst_dir)Copies all files from src_dir to dst_dir. Overwrites existing files if force=true. Creates dst_dir if it does not exist.
SymbolicAWEModels.get_example_packages — Function
get_example_packages()Get the list of packages from examples/Project.toml, excluding SymbolicAWEModels itself. This ensures init_module installs the correct dependencies for running examples.
SymbolicAWEModels.make_lin_sys_state — Function
make_lin_sys_state(y::AbstractVector, sam::SymbolicAWEModel, t::Real)Construct a SysState for logging linear state-space simulation output y (ordered as sam.outputs).
Base overloads (internal use)
SymbolicAWEModels.SAM_FIELDS — Constant
SAM_FIELDSTuple of field names that are direct fields of SymbolicAWEModel (as opposed to fields delegated to the nested serialized_model). Used by getproperty and setproperty! to dispatch field access correctly.
Base.getindex — Function
Access item by numeric index.
Access item by symbolic name.
Base.getindex(x::ModelingToolkit.Symbolics.Arr, idxs::Vector{Int64})Extend Base.getindex to allow indexing a symbolic array with a vector of integer indices, which is not natively supported by ModelingToolkit.
Base.getproperty — Function
Base.getproperty(pa::ProbWithAttributes, sym::Symbol)Overloads getproperty to provide convenient access to the simplified system (sys) contained within the ODE problem's function definition.
Base.setproperty! — Function
Base.setproperty!(sam::SymbolicAWEModel, sym::Symbol, val)Overloads setproperty! to allow direct setting of fields within the nested serialized_model. This allows you to change properties of the compiled model as if they were fields of the SymbolicAWEModel itself.
Serialization.serialize — Method
serialize(s, g::InplaceGetter)Julia's serializer does not preserve SubArray.parent === buf sharing, so serialize the generated function plus a cheap layout and rebuild the aliased buffer and views on load.
YAML loader internals
SymbolicAWEModels.get_field_or_nothing — Function
get_field_or_nothing(::Type{T}, row::NamedTuple,
field::Symbol) where TConvert field to type T if present, otherwise return nothing.
Examples
get_field_or_nothing(Int64, row, :idx) # -> Int64 or nothing
get_field_or_nothing(Tuple{Int64,Int64}, row, :pair)
# -> (Int64, Int64) or nothingSymbolicAWEModels.convert_to_type — Function
convert_to_type(::Type{T}, value) where TConvert value to type T. Handles special cases like Tuples.
SymbolicAWEModels.resolve_references — Function
resolve_references(row::NamedTuple, property_tables::Dict{String, Dict{String, NamedTuple}})Resolve string references in a row by looking them up in property tables. If a field value is a String, check if it exists as a key in any property table. If found, merge those properties into the current row (current row takes precedence).
SymbolicAWEModels.calculate_derived_properties! — Function
calculate_derived_properties!(props::Dict{Symbol, Any})Calculate derived properties like unit_stiffness and unit_damping from material properties. Modifies props in-place.
SymbolicAWEModels.extract_args — Function
extract_args(row, args_spec, mappings)Extract positional constructor arguments from a YAML row.
For each name in args_spec, this helper first checks for a mapping in mappings, then falls back to row[arg_name]. Throws an error if a required argument is missing.
SymbolicAWEModels.call_yaml_constructor — Function
call_yaml_constructor(Constructor, row::NamedTuple,
args_spec, kwargs_spec; mappings=Dict())Generic YAML-to-constructor caller. Extracts positional args and kwargs from YAML row and calls constructor.
Arguments
Constructor: Constructor function to callrow::NamedTuple: Parsed YAML rowargs_spec::Vector{Symbol}: Names for positional argskwargs_spec::Vector{Symbol}: Names for kwargs
Keyword Arguments
mappings::Dict{Symbol, Function}: Mapping functions that take the row and return the arg value
Example
row = (idx=1, x=0.0, y=0.0, z=0.0, type="STATIC")
point = call_yaml_constructor(Point, row,
[:idx, :pos_cad, :type], # positional args
[:extra_mass, :wing_idx]; # kwargs
mappings=Dict(
:pos_cad => r -> [Float64(r.x),
Float64(r.y), Float64(r.z)],
:type => r -> parse_dynamics_type(
String(r.type))
))SymbolicAWEModels.parse_tether_init — Function
parse_tether_init(row, tether_name)
-> (stretched_length, tether_force, stretch_frac)Read init_stretched_length, init_tether_force and init_stretch_frac from a tether YAML row (each nothing if absent). Errors if the deprecated init_unstretched_length field is present — the unstretched rest length is now derived from the placed stretched length with init_tether_force or init_stretch_frac.
SymbolicAWEModels.yaml_row_name — Function
yaml_row_name(row, i)Name for a YAML row: its name field (as a Symbol) when present, else the 1-based row index i. Shared by the body/joint loaders so components can be referenced either by name or by position.
SymbolicAWEModels.yaml_block_empty — Function
yaml_block_empty(data, key) -> Booltrue when the top-level YAML key is absent or holds no data rows.
SymbolicAWEModels.yaml_vec3 — Function
yaml_vec3(row, field) -> KVec3 or nothingRead a 3-element vector field, or nothing when absent.
SymbolicAWEModels.yaml_matrix3 — Function
yaml_matrix3(value) -> Matrix{SimFloat}Convert a nested [[…],[…],[…]] YAML value into a 3×3 matrix (rows preserved).
SymbolicAWEModels.yaml_ref_field — Function
yaml_ref_field(row, field, to_ref) -> reference or nothingResolve an optional reference column field (e.g. :transform_idx) through to_ref, or nothing when the field is absent or empty.
SymbolicAWEModels.load_property_table — Function
load_property_table(data, key) -> Dict{String, NamedTuple}Index a reference table (materials, elements, segment_properties) by its name column, so resolve_references can merge shared properties.
SymbolicAWEModels.load_yaml_bodies — Function
load_yaml_bodies(data, yaml_to_ref) -> Vector{Body}Build the plain rigid Bodys from a bodies YAML block (empty when the block is absent). Field names mirror the Body constructor: required name, mass, pos, and one of inertia_principal (3-vector) or inertia (3×3); optional type (DYNAMIC/STATIC), transform_idx, vel, Q_b_to_w (4-vector), omega_b, com_offset_b, damping (scalar or 3-vector), fix_sphere, ext_force_w, ext_force_b, ext_moment_b, principal_frame_method.
SymbolicAWEModels.load_yaml_joints — Function
load_yaml_joints(::Type{Joint}, data, key, required, optional; yaml_to_ref)Build two-body joints of type Joint from the key YAML block (empty when the block is absent). Every row needs body_a, body_b and each required scalar; anchor_a/anchor_b (3-vectors) and each optional scalar are read when present. Shared by the TimoshenkoJoint and ElasticJoint loaders — scalar fields from YAML are linear; callable/nonlinear laws are supplied programmatically.
SystemStructure internals
SymbolicAWEModels.segment_cad_length — Function
segment_cad_length(segment::Segment, points)Compute segment length from endpoint pos_cad positions.
SymbolicAWEModels.segment_world_length — Function
segment_world_length(segment::Segment, points)Compute segment length from endpoint pos_w positions.
SymbolicAWEModels.tether_ordered_point_idxs — Function
tether_ordered_point_idxs(tether, segments)Point indices along the tether, ordered from start_point_idx through each segment's far endpoint to the end point.
SymbolicAWEModels.tether_anchor_free — Function
tether_anchor_free(tether, boundary)Return (anchor_idx, free_idx) for a root tether: the endpoint in boundary (STATIC/winch points) is the anchor, the other is free. Returns (nothing, nothing) if neither endpoint is on a boundary, or if both are (a both-fixed tether cannot be placed; the caller warns and skips it).
SymbolicAWEModels.rigid_point_siblings — Function
rigid_point_siblings(points, wings)Map each point index that rides a rigid body to the set of all points sharing that body, so downstream traversal moves them as one unit. Covers WING points of a RIGID_DYNAMICS wing (grouped by wing_idx) and BODY_STATIC points (grouped by body_idx). These points carry no inter-point segments, so the set captures their connectivity.
SymbolicAWEModels.tether_downstream_idxs — Function
tether_downstream_idxs(tether, segments, boundary, from_idx,
anchor_idx, rigid_siblings)Breadth-first set of point indices reachable from from_idx (the tether's free end) through segments outside this tether and through rigid_siblings, stopping at boundary points. These are the points that must translate with the free end when the tether is repositioned. Errors if traversal reaches anchor_idx (a loop back to the anchor).
SymbolicAWEModels.twist_surface_tethers_by_overlap — Function
twist_surface_tethers_by_overlap(specified, reach)Cluster the specified tethers with a union-find over reach (point indices each tether touches): tethers whose reaches intersect share structure and land in the same cluster. Returns a vector of tether vectors, one per cluster.
SymbolicAWEModels.tether_unit_stiffness — Function
tether_unit_stiffness(tether, segments)Return the common per-unit-length stiffness [N] of the tether's segments. Errors if the segments are not uniform, since the spring inversion in apply_tether_init_forces! assumes a single stiffnesys_state.
SymbolicAWEModels.apply_cluster_init_stretched_len! — Function
apply_cluster_init_stretched_len!(cluster, points, segments,
downstream, boundary; prn=true)Reposition one cluster of root tethers so each sits at its init_stretched_len standoff. Each tether contributes the displacement that would move its free end onto the target length along the anchor→free direction; the free end and everything downstream of it are translated by the mean of those displacements, then interior points are redistributed proportionally along each tether. For a multi-tether cluster, logs an @info when prn.
SymbolicAWEModels.apply_tether_init_stretched_lens! — Function
apply_tether_init_stretched_lens!(sys_struct::SystemStructure; prn=true)Scale pos_w so each tether with an explicit init_stretched_len sits at that standoff. Call after copy_cad_to_world!. Rest length is derived separately by apply_tether_init_forces!.
Only tethers with one endpoint on a boundary (STATIC or winch point) are placed; that endpoint is the fixed anchor (start or end). Scaling runs from the anchor toward the free end, translating everything downstream of it. A tether with neither endpoint anchored is an error. Roots feeding one structure form a cluster, placed by their mean displacement (length and direction).
Errors if a downstream segment connects back to the anchor.
SymbolicAWEModels.init_unstretched_len — Function
init_unstretched_len(tether, segments) -> SimFloatDerived initial unstretched (rest) length from the tether's current (placed) stretched length stretched = Σ segment lengths:
init_stretch_fracset:len = stretch_frac · stretched(< 1pre-stretch,1neutral,> 1slack).- otherwise from
init_tether_force(default 0):len = stretched · (1 − force / unit_stiffness)(zero-velocity, tension branch). Force 0 giveslen = stretched(no tension).
Errors if both init_stretch_frac and init_tether_force are set, if stretch_frac ≤ 0, if force < 0, if force ≥ unit_stiffness, or if the segments have non-uniform unit_stiffness.
SymbolicAWEModels.apply_tether_init_forces! — Function
apply_tether_init_forces!(sys_struct::SystemStructure)Set every tether's len to its init_unstretched_len. Must be called after segment world lengths are current.
SymbolicAWEModels.joint_endpoint_frames — Function
joint_endpoint_frames(joint, bodies) -> (R_a, R_b, anchor_a_w, anchor_b_w)World rotations of the two connected bodies and the world positions of the joint's two anchors, from the current (placed) poses. Shared by every init_joint_rest!.
SymbolicAWEModels.init_joint_rest! — Function
init_joint_rest!(joint, bodies)Capture joint's rest reference from the current (placed) body poses, so the as-placed (CAD) geometry is unstrained and the joint wrench is exactly zero at initialization. One method per joint type; a new joint type adds a method.
ElasticJoint: rest anchor offset (body-A frame) and rest relative rotationR_a' R_b.TimoshenkoJoint: rest length (if unset) and per-node orientations relative to the corotational element frame.
SymbolicAWEModels.timoshenko_element_frame — Function
timoshenko_element_frame(x_a, x_b, R_a) -> (e1, e2, e3, len)Orthonormal corotational element frame: e1 along the chord from node A to node B, e2/e3 from node A's y-axis projected transverse to the chord. len is the current chord length. Generic over numeric and symbolic inputs.
SymbolicAWEModels.assign_indices_and_resolve! — Function
assign_indices_and_resolve!(components, name_dicts)Assign indices to all components based on their position in the vectors, and resolve all references to indices.
SymbolicAWEModels.resolve_ref — Function
resolve_ref(ref::NameRef, name_dict::Dict{Symbol, Int64}, component_type::String) -> Int64Resolve a reference (name or index) to an index using the name dictionary. If ref is an integer, returns it directly. If ref is a symbol, looks up in dictionary.
SymbolicAWEModels.resolve_ref_spec — Function
resolve_ref_spec(spec, name_dict, component_type) -> Union{Int64, Vector{Int64}, Nothing}Resolve a reference point specification (single ref or vector of refs) to indices.
SymbolicAWEModels.validate_sys_struct — Function
validate_sys_struct(sys_struct::SystemStructure)Validate a SystemStructure for common configuration errors.
This function checks for issues that can cause initialization failures or numerical problems during simulation. It emits warnings for suspicious configurations and throws assertions for definite errors.
Validations Performed
Point Validations
- NaN extra_mass (error)
- Negative extra_mass (warning)
- Non-positive total_mass for DYNAMIC points (error) - checked before NaN position
- NaN position (error) - often caused by zero mass
Wing Validations
- Non-positive mass (error) - checked before NaN position
- Zero or near-zero principal inertia components on RIGID_DYNAMICS wings (error/warning)
- NaN inertia values (error)
- Empty twistsurface list for RIGIDDYNAMICS wings (warning)
- NaN position (error) - often caused by zero mass/inertia
Winch Validations
- Zero or negative inertia_total (error)
- Very small inertia_total (warning)
- NaN inertia_total (error)
- Non-positive drum_radius (error)
- Non-positive gear_ratio (error)
Segment Validations
- Unusual diameter outside (0, 1) m range (warning)
- Non-positive rest length l0 (error)
- Zero or negative stiffness (warning)
- Negative damping (warning)
Pulley Validations
- Zero total length constraint (error)
TwistSurface Validations
- Inconsistent momentfrac across twistsurfaces (error)
SymbolicAWEModels.build_name_dict — Function
build_name_dict(items::Vector) -> Dict{Symbol, Int64}Build a name→index dictionary from a vector of items with optional name fields. Items with name=nothing are skipped. Integer names are converted to Symbols.
SymbolicAWEModels.setup_wing_frame! — Function
setup_wing_frame!(wing, points; prn=true)Compute a wing's body frame (R_b_to_c, pos_cad) and, for RIGID_DYNAMICS, its COM offset and principal inertia, from the WING points and ref points. This is dynamics/geometry only — independent of the aero mode, which does its own mode-specific setup afterwards in setup_aero!.
Without ref points the body frame keeps the CAD orientation (origin at the COM).
SymbolicAWEModels.auto_create_twist_surfaces! — Function
auto_create_twist_surfaces!(wing, points, twist_surfaces; prn=false)Auto-create one DYNAMIC TwistSurface per LE/TE structural section for a section-coupled RIGIDDYNAMICS VSM wing that has none, append them to `twistsurfaces, setwing.twistsurfaceidxs`, and resize the wing's aero arrays.
SymbolicAWEModels.compute_twist_surface_geometry! — Function
compute_twist_surface_geometry!(wing, twist_surfaces, points)For each of wing's twist surfaces with an unset chord, derive its leading-edge position, chord vector, and spanwise airfoil axis from the nearest VSM refined section (body frame). Used for auto-created twist surfaces.
SymbolicAWEModels.setup_particle_point_mapping! — Function
setup_particle_point_mapping!(wing, points, twist_surfaces)For a VSM PARTICLE_DYNAMICS wing, build the structural↔panel point mapping and LE/TE wing_segments if not already set. Errors if the required body-frame z_ref_points/y_ref_points are missing.
SymbolicAWEModels.identify_wing_segments — Function
identify_wing_segments(wing_points; twist_surfaces=nothing, wing_twist_surface_idxs=nothing)Identify wing segments (LE/TE pairs) from WING-type points.
When twist_surfaces and wing_twist_surface_idxs are provided, uses twistsurface `pointidxsto determine LE (pointidxs[1]) and TE (pointidxs[end]`) for each section. Falls back to a consecutive-pair heuristic (sorted by point index) when twist_surfaces are unavailable.
In both paths an x-coordinate check swaps LE/TE if needed (LE has smaller pos_cad[1]).
Arguments
wing_points::AbstractVector{Point}: WING-type points for a wing.
Keyword Arguments
twist_surfaces::Union{Nothing, AbstractVector{TwistSurface}}: All twistsurfaces in the system (indexed by `wingtwistsurfaceidxs`).wing_twist_surface_idxs::Union{Nothing, AbstractVector{<:Integer}}: Indices intotwist_surfacesbelonging to this wing.
Returns
Vector{Tuple{Int64, Int64}}: (lepointidx, tepointidx) pairs.
SymbolicAWEModels.match_aero_sections_to_structure! — Function
match_aero_sections_to_structure!(wing, points; twist_surfaces)Reconcile a wing's aerodynamic sections with its structural geometry.
RIGIDDYNAMICS wings own their aero panel geometry (mesh- or YAML-defined) and keep it; only the twistsurface→section mapping (wing.wing_segments) is recorded. PARTICLEDYNAMICS wings deform with their structural points, so each unrefined section is rebuilt onto its structural LE/TE pair: a 1:1 copy when counts match, otherwise `usepriorpolarand existingrefinedsections` are required to preserve polars.
Keyword Arguments
twist_surfaces::AbstractVector{TwistSurface}: TwistSurfaces used for LE/TE identification viaidentify_wing_segments.
SymbolicAWEModels.compute_spatial_twist_surface_mapping! — Function
compute_spatial_twist_surface_mapping!(the_wing, twist_surfaces, points)Partition the wing's unrefined VSM sections among its twistsurfaces by spatial proximity: each unrefined section is assigned to the single closest twistsurface (by distance between section centre and twist_surface centre, both in body frame).
When n_twist_surfaces == n_unrefined this is the same 1:1 mapping as before. When n_twist_surfaces < n_unrefined a twistsurface may own several adjacent sections; its single twist DOF then drives all of them as a rigid unit. The case `ntwistsurfaces > nunrefined` is rejected — a twist DOF without a section to drive would be undefined.
SymbolicAWEModels.copy_cad_to_world! — Function
copy_cad_to_world!(points, bodies; update_vel=true)Copy CAD geometry to world frame for ALL points and bodies. Sets pos_w = pos_cad (and Q_b_to_w to initial CAD orientation for bodies). Must be called before reinit!(transforms, ...).
SymbolicAWEModels.adjust_vsm_panels_to_origin! — Function
adjust_vsm_panels_to_origin!(vsm_wing, origin_offset)Adjust VSM panel positions when body frame origin changes.
When RIGIDDYNAMICS wings are loaded from YAML, the panel positions in aerogeometry.yaml are specified in an absolute body frame. However, the body frame origin is adjusted to the mean position of all WING points. This function updates all panel positions to be relative to the new origin by subtracting the offset.
Arguments
vsm_wing: VortexStepMethod.Wing with sections to adjustorigin_offset: Vector [x, y, z] to subtract from panel positions
SymbolicAWEModels.apply_aero_z_offset! — Function
apply_aero_z_offset!(vsm_wing, aero_z_offset)Apply z-axis offset to VSM panel positions in body frame.
For RIGID_DYNAMICS wings, this shifts the aerodynamic center of pressure in the positive z-direction (body frame) to adjust the moment arm. This is applied AFTER the COM adjustment.
Arguments
vsm_wing: VortexStepMethod.Wing with sections to adjustaero_z_offset: Distance to shift panels in +z direction [m]
SymbolicAWEModels.calc_particle_dynamics_wing_frame — Function
calc_particle_dynamics_wing_frame(points, z_ref_points, y_ref_points, origin)Calculate Rbto_w rotation matrix and origin position from structural point positions.
Algorithm
- Weighted ref point positions
- Z-axis (normal): zp1 → zp2
- X-axis (chord): Y_temp × Z
- Y-axis (span): Z × X (orthogonal, right-handed)
- Origin from weighted
originref points
SymbolicAWEModels.principal_frame — Function
principal_frame(inertia) -> (inertia_principal, R_to_principal)Diagonalise a 3×3 symmetric inertia tensor. Returns the principal moments inertia_principal and the rotation R_to_principal mapping the input frame to the principal frame, so that R · inertia · R' = Diagonal(inertia_principal).
The principal axes are permuted and signed to align as closely as possible with the input-frame axes: a near-diagonal tensor gives R ≈ I, and a body symmetric about a coordinate plane gives a pure rotation about the normal of that plane. R is always a proper rotation (det = +1).
SymbolicAWEModels.calc_inertia_y_rotation — Function
calc_inertia_y_rotation(I_tensor) -> (inertia_principal, R_to_principal)Diagonalize a 3×3 inertia tensor via a closed-form rotation about the Y axis, returning (moments, R) in the same format as principal_frame.
The rotation angle is θ = atan(2·I₁₃, I₁₁ − I₃₃) / 2, zeroing out the I[1,3] / I[3,1] cross terms while leaving the Y axis unchanged.
Unlike principal_frame (full 3-axis eigendecomposition + permutation search), this is a unique, closed-form solution — the right choice for a wing symmetric about the XZ-plane (no Y products of inertia), where the generic permutation search is ambiguous when two principal moments are close.
SymbolicAWEModels.PrincipalFrameMethod — Type
PrincipalFrameMethodStrategy for computing the principal frame from an inertia tensor.
EIGEN_DECOMP: full 3-axis eigendecomposition + permutation search (principal_frame). General-purpose; correct for any body.Y_ROTATION: closed-form rotation about Y only (calc_inertia_y_rotation). Use for wings symmetric about the XZ-plane where the generic permutation search is ambiguous when two principal moments are close.
SymbolicAWEModels.init_principal_state! — Function
init_principal_state!(obj)Derive the principal-frame ODE state (com_w, com_vel, Q_p_to_w, ω_p) from the body-frame initial conditions (pos_w, vel_w, Q_b_to_w, ω_b) of a rigid body or RIGID_DYNAMICS wing, with R_p_to_w = R_b_to_w * R_b_to_p'. Shared by init_rigid_body! and the rigid branch of init_principal_frame! (for a RIGID_DYNAMICS wing R_b_to_p = R_p_to_c' * R_b_to_c, so the two agree).
SymbolicAWEModels.is_wing — Function
A wing is a Body carrying aerodynamics; Body{AeroNone} is a plain body.
SymbolicAWEModels.wing_dynamics — Function
wing_dynamics(dynamics_type::WingType) -> Type{<:WingDynamics}Map a WingType to its WingDynamics tag.
SymbolicAWEModels.WingDynamics — Type
abstract type WingDynamicsPhantom tag for a wing's dynamics, carried as the second type parameter of Wing. It mirrors the dynamics_type field but lifts the rigid/particle distinction into the type domain so aero (and other) methods can dispatch on RigidWing/ParticleWing instead of branching on dynamics_type. Subtypes: RigidDynamics, ParticleDynamics.
SymbolicAWEModels.RigidDynamics — Type
Dynamics tag for a RIGID_DYNAMICS wing (integrated 6-DOF body).
SymbolicAWEModels.ParticleDynamics — Type
Dynamics tag for a PARTICLE_DYNAMICS wing (frame fitted from points).
SymbolicAWEModels.rotate_vsm_sections! — Function
rotate_vsm_sections!(vsm_wing, R)Rotate all VSM section LE/TE points by rotation matrix R.
Used during initialization to transform sections from CAD frame to body frame. After the first step, refresh_aero!() updates positions from pos_b (already in body frame).
SymbolicAWEModels.AERO_SCALE_CHORD — Constant
const AERO_SCALE_CHORD = 0.0Baseline chord-based aero scaling for PARTICLEDYNAMICS wings; effective multiplier is `1 + (wing.aeroscale_chord or this default)`.
SymbolicAWEModels.body_vsm_engine — Function
body_vsm_engine(body::Body, sym::Symbol)Return the body's VSM engine; errors if the aero mode carries none (e.g. AeroNone/AeroPlate).
SymbolicAWEModels.expand_auto_tethers! — Function
expand_auto_tethers!(points, segments, tethers, set)For Route 2 tethers (auto-generation), create intermediate DYNAMIC points and segments. Must be called before assign_indices_and_resolve!.
Detects Route 2 tethers by checking start_point_ref !== nothing and segment_refs names not yet present in segments.
SymbolicAWEModels.WeightedRefPoints — Type
WeightedRefPointsWeighted combination of reference points for body-frame definition. Supports single points, equal-weight averaging, and arbitrary weight combinations.
Fields
refs: Unresolved names/indices (filled at construction)ids: Resolved point indices (filled byresolve!)weights: Normalized weights (sum to 1.0)
SymbolicAWEModels.resolve! — Function
resolve!(ref_pt::WeightedRefPoints, name_dict, type)Resolve symbolic refs to integer indices, filling ref_pt.ids. No-op if refs is empty (already resolved).
SymbolicAWEModels.validate_weights! — Function
Warn and normalize if weights don't sum to 1.
SymbolicAWEModels.SegmentType — Type
SegmentType `POWER_LINE` `STEERING_LINE` `BRIDLE`NamedCollection internals
SymbolicAWEModels.names — Function
names(nc::NamedCollection)Return a vector of all symbolic names in order of their indices. Names are nothing for unnamed items.
SymbolicAWEModels.get_idx — Function
get_idx(nc::NamedCollection, name::Symbol)Get the numeric index for a given symbolic name.
SymbolicAWEModels.get_name — Function
get_name(nc::NamedCollection, idx::Integer)Get the symbolic name for a given numeric index, or nothing if unnamed.
Base.values — Method
Get all values (same as iterating).
Base.haskey — Method
Check if a symbolic name exists in the collection.
Base.setindex! — Method
Set item by numeric index.
Base.setindex! — Method
Set item by symbolic name.
Equation builders
SymbolicAWEModels.tether_eqs! — Function
tether_eqs!(eqs, tethers; len, spring_force)Generate equations for tether stretched length and average spring force.
Arguments
eqs: Accumulating equation vector.tethers: Collection of Tether objects.len: Symbolic segment length variable.spring_force: Symbolic segment spring force variable.
Returns
- Updated
eqsvector with tether equations.
SymbolicAWEModels.pulley_eqs! — Function
pulley_eqs!(eqs, defaults, pulleys, segments, params;
spring_force, pulley_len, pulley_vel)Generate equations for pulley dynamics (rope distribution over pulleys).
Arguments
eqs,defaults: Accumulating vectors for the MTK system.pulleys: Collection of Pulley objects.segments: Collection of Segment objects (for mass calculation).spring_force: Symbolic segment spring force variable.pulley_len,pulley_vel: Symbolic pulley state variables.
Returns
- Tuple
(eqs, defaults)with updated equation vectors.
SymbolicAWEModels.winch_eqs! — Function
winch_eqs!(eqs, defaults, winches, tethers, segments, points,
sys_struct, params;
spring_force_vec, set_values, tether_len,
winch_vel, winch_acc, winch_force_vec, winch_friction)Generate equations for winch motor dynamics and per-tether length state, and return the list of ODESystem subsystems to attach to the parent system.
For each winch:
- Sum spring force vectors of the segments meeting the winch point (sign-aware via segment
point_idxs) →winch_force_vec. - Instantiate the winch component via
winch_component(winch.model, …). - Validate the connector contract with
validate_winch_component. - Bind connectors to the parent variables (including
subsys.len ~ mean(tether_len[tether_idx] for tether_idx in winch.tether_idxs)) and integrateD(winch_vel) = ifelse(brake > 0.5, 0, winch_acc). Whenwinch.speed_controlledis true,winch_accis forced to 0 (ignoringsubsys.acc) so velocity is prescribed viawinch.vel.
For each tether:
- With winch:
D(tether_len) = ifelse(brake > 0.5, 0, winch_vel). - Without winch:
D(tether_len) = 0.
SymbolicAWEModels.twist_surface_eqs! — Function
twist_surface_eqs!(eqs, defaults, twist_surfaces, bodies, params;
R_b_to_w, fix_wing, twist_angle, twist_ω, twist_surface_aero_moment,
point_force, twist_surface_y_airf, twist_surface_chord, twist_surface_le_pos)Generate equations for deformable wing twist_surface twist dynamics.
Arguments
eqs,defaults: Accumulating vectors for the MTK system.twist_surfaces: Collection of TwistSurface objects (deformable wing sections).bodies: Collection ofBodyobjects (the twist_surface owners).R_b_to_w: Symbolic rotation matrix (body to world).fix_wing: Symbolic boolean for fixing wing dynamics.twist_angle,twist_ω: Symbolic twist state variables.twist_surface_aero_moment: Symbolic aerodynamic moment on twist_surfaces.point_force: Symbolic point force variable.twist_surface_y_airf,twist_surface_chord,twist_surface_le_pos: Symbolic twist_surface geometry variables.
Returns
- Tuple
(eqs, defaults)with updated equation vectors.
SymbolicAWEModels.validate_twist_surface_modes — Function
validate_twist_surface_modes(twist_surfaces, bodies)Check that each twist_surface's twist mode is coherent with its owning body's dynamics and its point count. Errors loudly on an inconsistent combination:
DYNAMICtwist is an added rigid-body deformation DOF and needs a bridle couple, so it requires aRIGID_DYNAMICSbody and ≥2 points.- A 1-point twist_surface has no bridle couple to oppose a twist moment, so its only coherent twist is prescribed → must be
STATIC. STATICtwist on aPARTICLE_DYNAMICSbody cannot move free particles, so it is only coherent for a single point.
Plate aerodynamics internals
SymbolicAWEModels.load_plate_wing — Function
load_plate_wing(row, idx, data, set, wing_type, aero_mode,
yaml_to_ref, yaml_parse_ref_points,
yaml_parse_origin, twist_surfaces)Load a flat-plate wing from a YAML wing row + surfaces block. Each surface becomes a 1-point STATIC TwistSurface appended to twist_surfaces; the wing references them by name. CL/CD interpolations come from Settings polar data.
SymbolicAWEModels.plate_corners — Function
plate_corners(twist_surface, point_pos_w, R_b_to_w) -> NTuple{4, Vector}World-frame corners of a flat-plate section's display quad. The section's structural point sits at quarter chord; the quad is a square of side sqrt(area) spanned by the twisted chord direction and y_airf, so the quad area matches the section's area.
Aero-mode interface
SymbolicAWEModels.vsm_engine — Function
vsm_engine(mode::AbstractAeroModel) -> Union{Nothing, VSMEngine}The mode's VSMEngine (VSM geometry + linearization state), or nothing for modes without one. After construction every AbstractVSMAero carries an engine (require_vsm_engine enforces this in setup_aero!); nothing only occurs for non-VSM modes and bare pre-construction markers. Used for the wing's VSM property forwarding and the VSM-settings loading; per-mode behaviour goes through the dispatch hooks instead.
SymbolicAWEModels.has_vsm_engine — Function
has_vsm_engine(mode::AbstractAeroModel) -> Booltrue if vsm_engine(mode) returns an engine. Derived, not a dispatch point — implement vsm_engine instead.
SymbolicAWEModels.require_vsm_engine — Function
require_vsm_engine(mode, wing) -> VSMEngineReturn the mode's VSMEngine, erroring with construction advice when it is missing (a bare AeroDirect()/AeroLinearized() marker attached to a wing that was not built via VSMWing). Called once, in setup_aero!; after construction every AbstractVSMAero is guaranteed to carry an engine.
SymbolicAWEModels.couples_to_sections — Function
couples_to_sections(mode::AbstractAeroModel) -> Booltrue if the mode needs per-section twist surfaces (auto-creation and aero-section matching). VSM modes (AbstractVSMAero) do.
SymbolicAWEModels.has_vsm_wing — Function
has_vsm_wing(sys_struct::SystemStructure) -> Booltrue if any wing carries a VSM aero mode (AbstractVSMAero) whose state must be refreshed each vsm_interval. When false, refresh_aero! is a no-op for every wing, so callers skip it and the trailing parameter sync.
SymbolicAWEModels.provides_aero_override — Function
provides_aero_override(mode::AbstractAeroModel) -> Booltrue if the mode supplies frozen body-frame force/moment overrides read by the compiled RHS (the stored-force path of AeroDirect).
SymbolicAWEModels.stores_point_force — Function
stores_point_force(mode::AbstractAeroModel) -> Booltrue if a WING point's aero_force_b is meaningful for this mode. AeroNone returns false (it produces no force).
SymbolicAWEModels.aero_mode_tag — Function
aero_mode_tag(mode::AbstractAeroModel) -> StringShort identifier for the mode in the compiled-model cache filename. Required: the AbstractAeroModel fallback errors, so every aero mode must declare its own tag (no silent default that could collide two distinct modes on one cache file). Built-ins: "lin", "dir", "none", "plate".
SymbolicAWEModels.calc_side_slip — Function
calc_side_slip(wing) -> SimFloatSide-slip angle [rad] from the body-frame apparent wind. Pure geometry — the same formula for every aero mode, so it does not dispatch.
SymbolicAWEModels.validate_aero_component — Function
validate_aero_component(subsys, wing)Check the built aero subsys exposes the connectors the wiring layer needs for the wing's dynamics_type; error naming the missing connector otherwise.
SymbolicAWEModels.validate_aero_structure — Function
validate_aero_structure(mode, wing, points; prn=false)Check structural invariants the mode's compiled equations rely on (run at build). Default no-op; VSM PARTICLE_DYNAMICS wings verify the structural↔panel point mapping exists, covers every WING point, and matches 2 × n_sections points.
SymbolicAWEModels.remake_aero! — Function
remake_aero!(mode, wing, set, vsm_set, points, twist_surfaces)Rebuild the mode's aero engine from set/vsm_set (the remake_vsm path in reinit!, used after editing settings). Default no-op; VSM modes recreate the VSM wing/aero/solver, re-transform sections to the body frame, re-match aero sections to structure, and rebuild the twist-surface / point mappings.
remake_aero!(mode::ContinuousAero, wing, set, vsm_set, points,
twist_surfaces)The generic VSM remake plus a rebuild of the mesh maps (the VSM wing geometry objects are replaced, invalidating the panel indexing).
SymbolicAWEModels.setup_aero! — Function
setup_aero!(mode, wing, points, twist_surfaces; prn=false)Construction-time aero setup for wing, dispatched on its aero mode (default no-op). VSM modes transform the VSM panels into the body frame and, for section-coupled wings, auto-create twist surfaces, match aero sections to structure, and build the twist-surface / structural↔panel mappings. A custom mode adds a method to participate in construction without editing the SystemStructure constructor. Runs after setup_wing_frame! (which sets the body frame).
setup_aero!(mode::ContinuousAero, wing, points, twist_surfaces; prn=false)The generic VSM particle setup plus the ContinuousAero mesh maps (build_mesh_maps!).
SymbolicAWEModels.attach_engine! — Function
attach_engine!(mode, engine) -> modeAttach a freshly built VSMEngine to a VSM aero mode during wing construction. Built-in modes reconstruct (so the concrete engine type lands in the wing's type parameter, removing the abstract-engine dispatch from the RHS); the default mutates a custom mode in place.
SymbolicAWEModels.resize_aero_state! — Function
resize_aero_state!(mode, wing)Resize the mode's per-wing aero state after wing.twist_surface_idxs is resolved (name resolution can change the twist-surface count the initial sizing estimated from n_unrefined). Default no-op; VSM modes resize aero_y/aero_x/aero_jac for RIGID_DYNAMICS wings.
SymbolicAWEModels.init_aero_state! — Function
init_aero_state!(mode, wing, va_b_init)Initialize the mode's aero state from the initial body-frame apparent wind va_b_init (runs in update_sys_struct!, before the first refresh). Default no-op; VSM modes write the operating-point angles α, β into aero_y.
SymbolicAWEModels.normalized_inertia — Function
normalized_inertia(mode::AbstractAeroModel, wing, points)
-> (com_cad, inertia)Normalized (per-unit-mass) inertia of the wing body about its COM in the CAD frame, with inertia in [m²] — multiply by the wing's mass for the physical tensor [kg·m²]. inertia is nothing when there is no mass to normalize by. The default normalizes the WING-point point-mass inertia (normalized_point_inertia); VSM modes with an ObjWing mesh return the per-unit-mass mesh tensor as-is (its COM is -T_cad_body) and fall back to the point masses otherwise.
SymbolicAWEModels.normalized_point_inertia — Function
normalized_point_inertia(wing, points) -> (com_cad, inertia)Per-unit-mass inertia of the wing's WING points treated as point masses (extra_mass), normalized by their total mass. Exact under the construction invariant wing.mass == sum of WING-point masses (the constructor distributes set.mass onto the points). With zero total mass, com_cad is the unweighted centroid and inertia is nothing.
SymbolicAWEModels.n_aero_log_points — Function
n_aero_log_points(mode, wing) -> IntNumber of extra SysState log slots the mode contributes for wing (visualization geometry such as panel corners). Default 0; VSM modes log 4 corners per panel. Must match what write_aero_log_points! writes.
n_aero_log_points(::AeroPlate, wing) -> Int4 quad corners per flat-plate section (plate_corners).
SymbolicAWEModels.write_aero_log_points! — Function
write_aero_log_points!(mode, wing, sys_struct, sys_state, point_idx,
zoom) -> IntWrite the mode's log points (world frame, scaled by zoom) into sys_state.X/Y/Z starting after point_idx; return the last index written. Default writes nothing; VSM modes write the panel corners, AeroPlate writes each section's display quad.
write_aero_log_points!(mode::ContinuousAero, wing, sys_struct, sys_state,
point_idx, zoom)Log the panel corners the force model reconstructs (strut interpolation + frozen billow offset), not the raw VSM mesh, so the plot shows the geometry the dynamics use.
write_aero_log_points!(::AeroPlate, wing, sys_struct, sys_state,
point_idx, zoom) -> IntLog each flat-plate section's display quad (plate_corners).
SymbolicAWEModels.read_aero_log_points! — Function
read_aero_log_points!(mode, wing, sys_struct, sys_state, point_idx) -> IntInverse of write_aero_log_points!: restore the mode's state from the logged points starting after point_idx; return the last index consumed (the slots must be skipped even when unused). Default consumes nothing; VSM PARTICLE_DYNAMICS modes read the panel corners back (rigid wings recompute panels from twist instead and only skip their slots).
read_aero_log_points!(::AeroPlate, wing, sys_struct, sys_state,
point_idx) -> IntSkip the quad-corner slots: plate corners are derived from the restored structural point positions, so nothing is read back.
SymbolicAWEModels.restore_aero_twist! — Function
restore_aero_twist!(mode, wing, twist_surfaces)Re-apply the (already restored) twist-surface angles to the mode's geometry when loading a SysState log frame. Default no-op; VSM RIGID_DYNAMICS modes deform the unrefined sections and reinit the panels.
SymbolicAWEModels.plot_wing_aero! — Function
plot_wing_aero!(ax, sys, wing, mode::AbstractAeroModel;
use_observables=false, geometry_obs=nothing)Render wing's aero geometry into ax, dispatched on its aero mode: VSM modes plot their panels via VortexStepMethod's recipe, flat-plate modes draw their section quads in the same style (red mesh, black borders). The default draws nothing — add a method for a custom mode to render its own geometry. With use_observables, the plot re-reads the live structure on every geometry_obs trigger (live plots and replay). Returns the plot object, or nothing when nothing was drawn. Defined in the Makie extension.
SymbolicAWEModels.update_wing_aero_plot! — Function
update_wing_aero_plot!(wing, mode::AbstractAeroModel)Per-frame update of wing's aero plot, dispatched on its aero mode. Default no-op; VSM modes push the current pose into the panel-mesh observables. Modes drawn through the geometry observable (flat-plate quads) need no update here. Defined in the Makie extension.
SymbolicAWEModels.load_wing — Function
load_wing(mode::AbstractAeroModel, row, idx, data, set, wing_type, vsm_set,
yaml_to_ref, yaml_parse_ref_points, yaml_parse_origin, twist_surfaces)Build a wing from a parsed YAML row, dispatched on its aero mode. The default (VSM-backed modes) builds a VSMWing; AeroPlate builds a flat-plate wing via load_plate_wing. Add a method to load a wing for a custom aero mode.
SymbolicAWEModels.yaml_n_unrefined_sections — Function
yaml_n_unrefined_sections(row)Number of unrefined VSM sections for a wing row: the explicit n_unrefined_sections field when given, else the number of listed twist_surfaces, else nothing (ObjWing then picks its own default).
VSM and aerodynamics internals
SymbolicAWEModels.refresh_rigid_aero! — Function
refresh_rigid_aero!(mode, wing, am, twist_surfaces; vsm_min_wind=0.5)Refresh a RIGID_DYNAMICS wing's aero state, dispatched on its aero mode:
AeroNone/ any non-VSM mode → no-op (fallback).AeroLinearized→ compute the baseline coefficients (rigid_aero_baseline!) and theForwardDiffJacobiand(coeffs)/d(inputs)intowing.aero_jac.AeroDirect→ compute the baseline coefficients and apply the frozen body-frame force/moment; belowvsm_min_windeverything is zeroed.
refresh_rigid_aero!(::AeroDirect, wing, am, twist_surfaces; vsm_min_wind=0.5)Direct rigid-wing refresh. Computes the baseline coefficients and applies the resulting frozen body-frame force/moment (apply_direct_forces!), which the RHS holds constant until the next refresh. Below vsm_min_wind the coefficients, Jacobian, force, moment, and per-twist-surface moments are zeroed.
refresh_rigid_aero!(::AeroLinearized, wing, am, twist_surfaces; vsm_min_wind=0.5)Linearized rigid-wing refresh. Computes the baseline wind-axis coefficients at the operating point (rigid_aero_baseline!), then the ForwardDiff Jacobian d(coeffs)/d(inputs) and stores it in wing.aero_jac. The compiled RHS uses that Jacobian to reconstruct forces via a first-order Taylor expansion about the operating point.
SymbolicAWEModels.refresh_particle_aero! — Function
refresh_particle_aero!(mode, wing, points, va_point_b_vals; vsm_min_wind=0.5)Refresh a PARTICLE_DYNAMICS wing's aero state, dispatched on its aero mode:
AeroNone/ any non-VSM mode → no-op (fallback).AeroDirect→ full nonlinear VSM solve with per-section apparent wind, then distribute panel forces onto the wing's structural points (distribute_panel_forces_to_points!); belowvsm_min_windthe point forces are zeroed.AeroLinearized→ unsupported (errors).
refresh_particle_aero!(::AeroDirect, wing, points, va_point_b_vals; vsm_min_wind=0.5)Direct particle-wing refresh. Runs the full nonlinear VSM solve using each section's apparent wind (averaged from its LE/TE point velocities in va_point_b_vals), then distributes the resulting panel forces onto the wing's structural points (distribute_panel_forces_to_points!). Below vsm_min_wind the point forces are zeroed.
refresh_particle_aero!(::AeroLinearized, wing, points, va_point_b_vals; vsm_min_wind=0.5)Unsupported: AeroLinearized is not implemented for PARTICLE_DYNAMICS wings and errors. Use AeroDirect (per-point nonlinear VSM) for particle wings.
refresh_particle_aero!(::ContinuousAero, wing, points, va_point_b_vals;
vsm_min_wind=0.5)Circulation-only refresh: update the VSM geometry from the structure, set the per-panel apparent wind, run VortexStepMethod.solve_base! (no calc_forces!, no Jacobian), and freeze the per-refined-panel induced velocity (store_induced_velocity!). Below vsm_min_wind the induced velocity is zeroed; the symbolic forces remain live (and vanish with the dynamic pressure).
SymbolicAWEModels.count_aero_log_points — Function
count_aero_log_points(wings) -> IntTotal extra SysState log slots contributed by the wings' aero modes (n_aero_log_points): 4 per VSM panel for VSM modes, 4 per section quad for flat-plate wings, 0 by default.
SymbolicAWEModels.build_point_to_vsm_point_mapping — Function
build_point_to_vsm_point_mapping(wing_points::AbstractVector{Point}, wing::Body)Build 1:1 mapping from structural WING points to VSM wing section points (LE/TE) using closest-point distance.
For each VSM section point (LE/TE), finds the closest structural point. Distances are computed in body frame: structural pos_cad is transformed via wing.R_b_to_c' and wing.pos_cad before being compared against section LE/TE points (which already live in body frame after match_aero_sections_to_structure!).
Constraint
Requires: length(wing_points) == 2 * length(wing.vsm_wing.unrefined_sections)
SymbolicAWEModels.update_vsm_wing_from_structure! — Function
update_vsm_wing_from_structure!(wing::Body, points::AbstractVector{Point})Update VSM section points (LE/TE) directly from structural point positions using 1:1 mapping.
This creates two-way coupling: structural deformation → VSM sections → aero forces.
Algorithm
Uses direct 1:1 correspondence between structural points and VSM section points:
- For each structural WING point:
- Calculate current position in body frame: posb = Rbtow' * (pos_w - origin)
- Find corresponding VSM section point (LE or TE) via wing.pointtovsm_point
- Set VSM section point directly: section.LEpoint = posb (or TE_point)
Notes
- Section points are stored in body frame coordinates
wing.R_b_to_wandwing.pos_ware updated each timestep from structural geometry (symbolic equations)- To get world coordinates:
world_pos = wing.R_b_to_w * section.LE_point + wing.pos_w
Arguments
wing::Body: Wing with PARTICLE_DYNAMICS typepoints::AbstractVector{Point}: All structural points (will filter for WING type)
SymbolicAWEModels.distribute_panel_forces_to_points! — Function
distribute_panel_forces_to_points!(wing::Body, points::AbstractVector{Point})Distribute VSM forces to structural points using refined panel forces.
After VSM solve, each refined panel force/moment is split into corner-node forces (moment-preserving about the chosen reference) and then aggregated to the structural LE/TE points of the parent section (1:1 mapping).
Algorithm
- Initialize all WING point aero_forces to zero
- Build inverse mapping from section → LE/TE structural point indices
- For each refined panel of this wing:
- Get panel force/moment from solver solution (body frame)
- Map panel to its parent section using
refined_panel_mapping - Split to LE/TE forces with
compute_aerostruc_loads - Accumulate forces at the corresponding structural points
Arguments
wing::Body: Wing with PARTICLE_DYNAMICS type and solved VSM statepoints::AbstractVector{Point}: All structural points (will filter for WING type)
SymbolicAWEModels.rigid_aero_baseline! — Function
rigid_aero_baseline!(wing, twist_surfaces; vsm_min_wind=0.5)Compute the operating point and baseline wind-axis coefficients for one wing: writes wing.aero_y / wing.aero_x and updates twist_surfaces[gidx].aero_moment. Returns the context (va_mag, section counts, moment_frac, shadow_ref, y0) the mode-specific reduction (refresh_rigid_aero!) needs for the Jacobian.
SymbolicAWEModels.apply_direct_forces! — Function
Apply direct forces from wind-axis coefficients.
SymbolicAWEModels.vsm_aero_coeffs — Function
vsm_aero_coeffs(wing, y, va_mag, n_unrefined, n_twist_surfaces,
twist_surface_idxs, twist_surfaces, moment_frac, shadow_ref;
gamma_init=nothing) -> VectorRun one VSM solve at operating-point input y = [α, β, ω₁, ω₂, ω₃, θ_twist…] and return the wind-axis coefficient vector [CL, CD, CS, CM₁, CM₂, CM₃, cm_twist…]. ForwardDiff.Dual-aware via vsm_solve_objects: for a Dual eltype it solves on a cached dual shadow of the VSM solver, so the same routine yields the Jacobian under AD.
SymbolicAWEModels.vsm_solve_objects — Function
vsm_solve_objects(wing, ::Type{T}, shadow_ref) -> (body_aero, solver, wing)The VSM solve objects vsm_aero_coeffs runs on, selected by the input eltype T. A value pass (Float64) uses the wing's real objects. A ForwardDiff pass feeds Dual numbers through the solve to get the Jacobian, but the real objects' buffers are Float64 and can't hold Duals — so for a Dual eltype we solve on a Dual-typed "shadow" of the solver/aero. The shadow is expensive, so it is built lazily and cached in shadow_ref, keyed by the Dual eltype (rebuilt if it changes); use_gamma_prev warm-starts each perturbed solve from the previous circulation.
SymbolicAWEModels.safe_vsm_solve! — Function
NaN/Inf-guarded solve!. Checks both Dual value and partials. On non-finite or non-converged result, zero gamma and return false.
SymbolicAWEModels.finite_full — Function
finite_full(x) -> Booltrue if x is finite. For a ForwardDiff.Dual it also checks every partial, so a NaN/Inf derivative is caught — used by safe_vsm_solve! to reject a bad VSM solve during the Jacobian pass, not just the value pass.
SymbolicAWEModels.set_particle_panel_va! — Function
set_particle_panel_va!(wing, va_point_b_vals)Set the per-panel apparent wind on the wing's VSM BodyAerodynamics from the per-point body-frame apparent wind: each section's va is the mean of its LE/TE point values, refined panels take their parent section's value. Falls back to the wing-level va_b when the structural↔panel mapping is missing.
SymbolicAWEModels.build_mesh_maps! — Function
build_mesh_maps!(mode::ContinuousAero)Size the frozen induced-velocity buffer and copy the refined-section → strut interpolation (refined_section_left_idx / refined_section_weight from the VSM wing): refined section s lies at w·strut[left] + (1−w)·strut[left+1]. Also freeze the per-section billow offset — the refined-section position minus that straight-line interpolation, in the body frame — which is nonzero only for the BILLOWING distribution (the trailing edge bulges off the strut line). All are constants of the mesh and are baked into the symbolic equations (store_billow_offsets!).
SymbolicAWEModels.store_billow_offsets! — Function
store_billow_offsets!(mode::ContinuousAero)Freeze each refined section's body-frame displacement off the straight strut line: refined_pos − (w·strut[left] + (1−w)·strut[left+1]) for both LE and TE. Zero for in-line distributions (SPLIT_PROVIDED, COSINE, …); nonzero only where BILLOWING bulges the trailing edge between ribs.
SymbolicAWEModels.store_induced_velocity! — Function
store_induced_velocity!(mode::ContinuousAero, body_aero, gamma)Freeze the converged circulation: each refined panel's induced velocity is AIC · gamma, the same product the VSM gamma loop converged on.
SymbolicAWEModels.reconstruct_sections_b — Function
reconstruct_sections_b(mode::ContinuousAero, wing, points)Body-frame refined-section LE/TE positions exactly as the force model builds them: the live strut points interpolated by the frozen mesh weights plus the frozen billow offset. Mirrors the symbolic geometry in aero_component so the plotted panels are the ones the dynamics actually use (and a wrong billow offset shows up visually).
SymbolicAWEModels.ContinuousPolar — Type
ContinuousPolar(body_aero, coef)Callable polar for ContinuousAero, used as a callable flat parameter p(panel_idx, α): looks up refined panel panel_idx and evaluates the VSM coefficient function coef (calculate_cl/calculate_cd/calculate_cm) at angle of attack α. The panel is typeasserted concrete so the polar dispatches statically with no boxing in the compiled RHS; ForwardDiff.Dual-safe in α.
Heading and geometry
SymbolicAWEModels.solve_heading_rotation — Function
solve_heading_rotation(R_b_to_w, target_heading, wing_pos)Calculate the rotation angle around the radial axis needed to achieve target_heading.
With the tangential sphere heading, rotating around the radial axis by θ simply shifts the heading by θ, so the solution is target_heading - current_heading.
SymbolicAWEModels.get_ref_position_from_points — Function
get_ref_position_from_points(points, ref_pt; field=:pos_w)Weighted position from structural points. field selects which point coordinate to read (:pos_w or :pos_cad).
SymbolicAWEModels.sym_calc_R_t_to_w — Function
sym_calc_R_t_to_w(wing_pos)Symbolic version of calc_R_t_to_w that uses explicit element access to avoid slice scalarization issues.
SymbolicAWEModels.wrap_to_pi — Function
wrap_to_pi(angle)Wrap angle to [-π, π] range.
Transform internals
SymbolicAWEModels.apply_azimuth_elevation! — Function
apply_azimuth_elevation!(transform, points, bodies, base_pos; update_vel=false)Apply the azimuth/elevation rotation of a single transform to all components in it (points and bodies). Returns (curr_R_t_to_w, R_t_to_w) for use in the heading step.
SymbolicAWEModels.apply_heading! — Function
apply_heading!(transform, points, bodies,
curr_R_t_to_w, R_t_to_w, base_pos)Apply heading rotation to all components in a single transform. Rotates around the radial axis through base_pos (not the origin). Uses the reference body's R_b_to_w for the no-ref-points orientation source. After copy_cad_to_world!, this equals R_b_to_c (for reinit!), or the current world orientation (for reposition!). Bodies in the transform rotate with the same heading delta; a transform without a body target applies no heading (matching point behavior).
SymbolicAWEModels.finalize_transforms! — Function
finalize_transforms!(points, bodies)Finalize transforms: update PARTICLEDYNAMICS body frames from structural point positions, then compute principal frame ODE state for every body (RIGIDDYNAMICS bodies re-derived from the transformed pos_w/Q_b_to_w).
Flat parameters
SymbolicAWEModels.read_path — Function
read_path(obj, path)Walk path (a tuple of Symbol fields and Int indices) from obj, e.g. read_path(sys_struct, (:wings, 1, :aero, :engine, :aero_jac)).
SymbolicAWEModels.PathReader — Type
PathReader(path)Serialisable closure reading a fixed path from a sys_struct at sync time (holds only the path tuple, never the struct).
SymbolicAWEModels.ParamEntry — Type
ParamEntryOne flattened parameter: the symbolic param, a read(sys_struct) callable that returns its live value, and a kind (:scalar, :array, or :callable).
SymbolicAWEModels.ParamRegistry — Type
ParamRegistrySingle source of truth for the flattened parameters. Built during equation generation; the params view records one ParamEntry per distinct field it reads (memoised by cache key). After compilation it drives sync_params!.
SymbolicAWEModels.make_param — Function
Numeric scalar parameter named name (default value).
SymbolicAWEModels.make_array_param — Function
Numeric vector parameter name[1:n] (default value).
Numeric matrix parameter name[1:m,1:n] (default value).
SymbolicAWEModels.make_callable_param — Function
Callable parameter name (invoked symbolically as name(x); default value). For a leaf that is a function/interpolation/polar — MTK codegens the call and ForwardDiff differentiates through it, so no @register_symbolic is needed.
SymbolicAWEModels.leaf_param! — Function
leaf_param!(reg, key, name, reader, value)Create (once, memoised on key) and record the flat parameter for a leaf value. Numeric scalars/arrays become data params; any other (callable) leaf — an interpolation or polar — becomes a callable param applied as name(x). reader reads the live value from a sys_struct at sync time.
SymbolicAWEModels.param_computed! — Function
param_computed!(reg, name, reader)Escape hatch for a value that is not a plain field read — reader(sys_struct) computes it (e.g. a WindFactorReader building a callable wind-factor from the atmospheric model). reader must be a named struct (serialisable), not a closure over sys_struct.
SymbolicAWEModels.param_descend — Function
Types the view descends through (everything else is a leaf).
SymbolicAWEModels.ParamView — Type
Top-level params view wrapping a ParamRegistry. params.segments[i].l0 mirrors sys_struct.segments[i].l0 (build-time only).
SymbolicAWEModels.PathView — Type
A partial path into sys_struct being resolved to a parameter.
SymbolicAWEModels.ParamGroup — Type
ParamGroupA setp setter plus the readers and preallocated value buffer for one parameter kind. eltype is SimFloat for numeric scalars, Any for arrays/callables.
SymbolicAWEModels.ParamSync — Type
Bundle of the per-kind sync groups (each may be nothing).
SymbolicAWEModels.survivor_index — Function
survivor_index(sys) -> Dict{String, param}Map each parameter surviving mtkcompile to its name, keyed by both the full name and the leaf name (after the last ₊ namespace separator) so a registry's bare param matches its namespaced counterpart from a subsystem.
SymbolicAWEModels.build_param_sync — Function
build_param_sync(sys, registry) -> ParamSync | NothingBuild the per-kind sync groups from the compiled system and the registry. Pruned parameters (no surviving equation references them) are dropped, so a setter never touches a parameter absent from the buffer.
SymbolicAWEModels.sync_params! — Function
sync_params!(sync, target, sys_struct)Copy every flattened field from the live sys_struct into target's parameter buffers (target is an ODEProblem or an ODEIntegrator). A no-op when there are no flattened parameters.
SymbolicAWEModels.joint_stiffness_term — Function
joint_stiffness_term(joint, params, kind, Δ)Restoring force/moment for one joint DOF, read as a flat parameter: a Real stiffness is a numeric scalar param (k·Δ); an interpolation is a callable param applied as k(Δ). kind: 1=axial, 2=shear, 3=torsion, 4=bending.
SymbolicAWEModels.timoshenko_rigidity — Function
timoshenko_rigidity(joint, params, field, arg)Effective rigidity for one TimoshenkoJoint mode, read as a flat parameter: a Real rigidity is a numeric scalar param used directly; a callable is a callable param evaluated at the mode's strain/curvature arg. field is one of :EA, :GA, :GJ, :EIy, :EIz.
Initial conditions
SymbolicAWEModels.InitialEntry — Type
InitialEntryOne initial-condition binding: the scalar state-variable terms vars (e.g. [pos[1,i], pos[2,i], pos[3,i]]) and a read(sys_struct) returning their live value (scalar or vector, element-aligned with vars).
SymbolicAWEModels.InitialRegistry — Type
InitialRegistryBuild-time record of every bind_initial! call. Transient: it lives only during equation generation and drives build_initial_sync after compilation.
SymbolicAWEModels.InitialView — Type
Top-level initial view wrapping an InitialRegistry. initial.points[i].pos_w mirrors sys_struct.points[i].pos_w.
SymbolicAWEModels.InitialPath — Type
A partial path into sys_struct identifying an initial-condition field.
SymbolicAWEModels.bind_initial! — Function
bind_initial!(initial_path, state_var) -> Vector{Pair}Record that struct field initial_path (e.g. initial.points[i].pos_w) provides the initial condition for state_var (a scalar state term or a collected vector of them, e.g. pos[:, i]). Returns the constant default pair(s) (build-time numeric value) to splice into the system defaults, which makes MTK expose a settable Initial(state_var).
SymbolicAWEModels.ElementReader — Type
ElementReader(base, index)Serialisable reader returning the index-th element of base(sys_struct). Used to map an array-valued struct field onto per-element Initial parameters (scalars are indexed at 1, which is a no-op).
SymbolicAWEModels.InitialSync — Type
InitialSyncA setp setter for a list of Initial(state) parameters, their per-element readers, and a preallocated value buffer.
SymbolicAWEModels.build_initial_sync — Function
build_initial_sync(sys, registry) -> InitialSync | NothingBuild the Initial-parameter sync from the compiled system and the registry. A bound variable is kept when Initial(var) is a real parameter of sys — this holds for surviving unknowns and for observed variables mtkcompile solves during initialization (their Initial is an initialization constraint); only variables removed entirely are dropped, so the setter never touches an absent Initial parameter.
SymbolicAWEModels.sync_initial! — Function
sync_initial!(sync, prob, sys_struct)Copy every bound initial condition from the live sys_struct onto prob's Initial parameters. Must run before a fresh init/solve (a reinit! with reinit_dae does not re-read Initial). A no-op when there are none.
Other internals
SymbolicAWEModels.init_principal_frame! — Function
init_principal_frame!(bodies, points)Compute principal frame ODE state from body frame. Must be called after body frame (pos_w, R_b_to_w, vel_w, ω_b) is fully initialized.
Sets: com_w, Q_p_to_w, com_vel, ω_p (derived from body frame), and pos_b for RIGID_DYNAMICS wing points (body frame, relative to COM).
SymbolicAWEModels.init_body_frame_from_ref_points! — Function
init_body_frame_from_ref_points!(wing, points; prn=true)Initialize wing body frame (Rbtoc, poscad) from z/y reference points. Shared by VSMWing PARTICLE_DYNAMICS and PlateWing.
SymbolicAWEModels.get_rot_pos_cad — Function
get_rot_pos_cad(transform::Transform, bodies, points)Get the CAD-frame position of the rotating object (body or point). Used by get_base_pos to compute the translation offset for chained transforms.
KiteUtils.Logger — Method
KiteUtils.Logger(sam::SymbolicAWEModel, steps::Int)Constructs a Logger from a SymbolicAWEModel with the correct number of points.
This convenience constructor automatically calculates the total number of points including VSM panel corners (4 corners per panel) and creates a Logger with the appropriate size.
Arguments
sam::SymbolicAWEModel: The AWE model to create a logger for.steps::Int: The number of time steps to allocate for logging.
Returns
Logger: A new logger with size for all points including panel corners.
Example
logger = Logger(sam, 1000) # Instead of Logger(length(sam.sys_struct.points), 1000)