Skip to content

Aerodynamics

flapsim.aero.base

Composable aerodynamics: flow state, per-wing kinematics, model loads.

An :class:AeroModel turns one wing's kinematics into :class:WingLoads (a force at a point plus a pure moment, all in the wing aero frame). An :class:AeroEngine evaluates the shared kinematics once per wing, sums the loads of every model, applies :class:AeroModifier factors (for example ground effect) and writes the resulting wrench to MuJoCo. Plugins wrap the engine; see :mod:flapsim.aero.stack.

FlowState dataclass

Torso velocity relative to the air and the air properties, per step.

WingLoads dataclass

Loads of one model on one wing, in the wing aero frame.

force acts at point (relative to the wing body origin); moment is a pure couple. diagnostics are scalars published as wings/<wing>/<model>/<key>.

AeroModel

Base class of additive per-wing aerodynamic models.

bind(robot)

Called once when an engine is built for robot (optional hook).

Models that need to modify the MuJoCo model, for example to add an implicit added inertia to a joint, do it here.

reset()

Forget any history (called when the simulation resets).

AeroModifier

Multiplies the summed loads of a wing by a factor (ground effect ...).

AeroEngine

Evaluate a stack of models on every wing of a robot and apply them.

apply(flow=None)

Compute all loads for the current state and write them to MuJoCo.

flapsim.aero.models.quasi_steady

Translational quasi-steady normal force (Whitney & Wood 2010 / FlapX).

QuasiSteadyModel

Bases: AeroModel

Normal force F_N at the centre of pressure plus rotational damping.

Diagnostics: alpha, C_N, d_cp, M_aero, M_rd, chord_CoP, span_CoP, F_N_raw. options overrides the engine's :class:~flapsim.config.AeroOptions for this model only.

flapsim.aero.models.rotational_lift

Rotational circulation force (Sane & Dickinson 2002).

RotationalLift

Bases: AeroModel

F_rot = C_rot rho theta_dot * integral c(r)^2 |U(r)| dr along the normal.

C_rot = pi (0.75 - x0) by default, with x0 the hinge position as a fraction of the chord (Sane & Dickinson 2002, eq. 4). Positive when the wing pitches up relative to the oncoming flow. The force acts at the point_fraction of the chord behind the leading edge (default half chord) at the spanwise centre of pressure. Diagnostics: C_rot, circulation_integral.

flapsim.aero.models.added_mass

Added-mass (non-circulatory) loads of a thin flat-plate wing.

AddedMass

Bases: AeroModel

Reaction of the accelerated fluid on a thin flat-plate wing.

Per unit span the added mass of a flat plate moving normal to itself is m_a = rho pi c^2 / 4. The model splits its effect in two:

  • Explicit force from the prescribed accelerations: the normal flow at section r is U_n(r) = U_i1 r + U_i0 (stroke and body motion); its time derivative over one step gives F = integral m_a dU_n/dt dr along the normal, applied at the mid-chord of the c^2-weighted span centroid.
  • Implicit inertia for the passive feathering: the added rotational inertia about the hinge (:func:feathering_added_inertia) is added to the feathering joint's armature when the model is bound. Applying it as an explicit force from a finite-difference feathering acceleration is unstable for light wings, because the added inertia then lags the motion by one step; MuJoCo integrates armature implicitly.

include_feathering_inertia=False skips the armature change. The armature uses the manifest's air density at bind time. Diagnostics: dU_i1, dU_i0, added_inertia.

feathering_added_inertia(params, rho, c4=1.0)

Added rotational inertia of a flat plate about its feathering hinge.

Per unit span the plate carries m_a = rho pi c^2 / 4 at its mid-chord, a distance d_mid from the hinge, plus the added inertia rho pi c^4 / 128 about the mid-chord (Sedov). Integrated over the span with the planform integrals (c^2 -> z_cp0, c^4 -> c4).

flapsim.aero.models.wake_capture

Empirical wake-capture force after stroke reversal (Dickinson et al. 1999).

WakeCapture

Bases: AeroModel

Transient normal force when the wing meets the wake it shed before reversal.

Empirical: when the relative flow U_i at the span centre of pressure changes sign, the velocity magnitude just before reversal U_rev is stored and a force C_wc 1/2 rho S U_rev^2 exp(-(t - t_rev) / tau) is applied along the new flow direction, with tau = tau_chords c / U_rev. C_wc and tau_chords are tuning constants (defaults 1.0 and 0.5); there is no first-principles value. Diagnostics: U_rev, elapsed.

flapsim.aero.models.ground_effect

Ground-effect lift augmentation as a multiplicative factor.

GroundEffect

Bases: AeroModifier

Scale a wing's loads by a factor that grows near the ground.

exponential: 1 + gain * exp(-h / (length_scale * R)) with h the height of the wing's origin above ground_height. cheeseman_bennett: the rotor-in-ground-effect formula 1 / (1 - (R / (4 h))^2), capped at max_factor. Both are empirical and tuned by the user.

flapsim.aero.stack

Aero plugins built on :class:~flapsim.aero.base.AeroEngine.

AeroStack

Bases: AeroPlugin

Sum several additive aero models on every wing, with modifiers.

models lists model names or {type: name, ...} mappings (see flapsim.aero.base.available_models()), modifiers likewise. Reads atmosphere/rho and atmosphere/wind_world when an atmosphere plugin publishes them. Publishes aero/F_world, aero/lift_total, aero/rho and per wing wings/<name>/<key> totals plus wings/<name>/<model>/<key> contributions. imposed_body_velocity and imposed_body_rates replace the torso velocity seen by the models (identification and testing only).

QuasiSteadyAero

Bases: AeroStack

Per-step quasi-steady blade-element forces (the single-model stack).

Publishes aero/F_world, aero/lift_total and, for every wing, wings/<name>/F_N, alpha, F_world, torque_world, cop_world and the quasi_steady/ contributions (C_N, M_aero, M_rd, chord_CoP, span_CoP). options are :class:~flapsim.config.AeroOptions fields.

flapsim.aero.atmosphere

Air density, steady wind, gusts and turbulence.

Atmosphere

Bases: AtmospherePlugin

Publish atmosphere/rho and atmosphere/wind_world every step.

rho defaults to the ISA density at altitude_m. wind is a steady world-frame vector; gust adds one of one_minus_cos (amplitude, start, duration, direction), sine (amplitude, frequency_hz, direction, phase) or step (amplitude, start, direction); turbulence adds an Ornstein-Uhlenbeck process per axis (sigma m/s, tau s, seed). Add this plugin before the aero plugins that should see the wind.

isa_density(altitude_m)

International Standard Atmosphere density in the troposphere [kg/m^3].

flapsim.aero.body_drag

Quadratic drag on bodies (fuselage, legs, tail).

BodyDrag

Bases: ForcePlugin

F = -1/2 rho C_d A |v| v per body axis, relative to the wind.

bodies maps body names to {cd, area} where each may be a scalar (isotropic) or a 3-vector in the body frame. Density comes from atmosphere/rho when published, else rho or the manifest. Forces are added to xfrc_applied at the body centre of mass, so place this plugin after any plugin that overwrites a body's wrench. Publishes <id>/<body>/F_world.

flapsim.aero.cycle_averaged

Cycle-averaged lift and control moments for floating-base hover studies.

The robot is treated as one rigid body. Lift k f^2 acts along the body thrust axis, tilted by the gimbal angles; gimbal angles also produce control moments, scaled by (f / f_ref)^2; linear and angular damping oppose the body velocity. All coefficients can be identified from the per-step model with :func:flapsim.aero.identify.identify_cycle_averaged (flapsim sweep) and loaded from the resulting parameter file.

CycleAveragedAero

Bases: AeroPlugin

hover_frequency_hz property

Frequency at which the cycle-averaged lift balances the robot weight.

flapsim.aero.identify

Identify cycle-averaged parameters from the per-step model.

The fixed-base robot is a virtual wind tunnel: sweep the stroke frequency to fit the lift coefficient, deflect each gimbal joint to measure how the mean force direction and the mean moment change per radian, and impose body velocities and rates on the aero stack to measure damping. The result is a flapsim.cycle_averaged/1 parameter file for :class:~flapsim.aero.cycle_averaged.CycleAveragedAero.

lift_sweep(make_sim, frequencies, *, amplitude_rad=None, settle=0.2, cycles=10.0, stroke_id='stroke', aero_id='aero')

Mean world force per frequency and the least-squares lift = k f^2 fit.

gimbal_sensitivity(make_sim, *, frequency_hz, joints=None, angle_rad=0.1, settle=0.2, cycles=10.0, stroke_id='stroke', gimbal_id='gimbal', aero_id='aero')

Per gimbal joint: d(mean force)/d(angle) and d(mean torque)/d(angle).

Central differences at +/- angle_rad; forces and torques are expressed in the root body frame.

damping_sensitivity(make_sim, *, frequency_hz, speed=0.5, rate=1.0, settle=0.2, cycles=10.0, stroke_id='stroke', aero_id='aero')

Linear and angular damping from imposed body velocities and rates.

Returns linear (-dF_i/dv_i) and angular (-dT_i/dw_i) in the root body frame, using central differences on the aero stack's imposed body velocity and rates.

identify_cycle_averaged(make_sim, *, frequencies=(20.0, 30.0, 40.0, 50.0), reference_frequency_hz=None, amplitude_rad=None, gimbal_angle_rad=0.1, include_damping=True, settle=0.2, cycles=10.0, stroke_id='stroke', gimbal_id='gimbal', aero_id='aero')

Run all sweeps and assemble a flapsim.cycle_averaged/1 parameter set.

flapsim.fluid.aero

Quasi-steady blade-element aerodynamics for one flapping wing.

Pure functions, no MuJoCo state. Inputs and outputs use SI units.

Wing aero frame

+X is the surface normal, +Y the feathering (span) axis and +Z = X x Y. The feathering axis runs along the leading edge and the chord extends from it along -Z, so the centre of pressure always lies at [0, side * span_CoP, -chord_CoP] with chord_CoP >= 0. Positive psi is a right-handed stroke rotation about side * Z and positive theta a right-handed feathering rotation about +Y; side is +1 for a left wing and -1 for its mirror image, which shares the same frame orientation at the neutral pose. U_i is the relative flow speed along +X at the span centre of pressure, so F_N carries the sign of the flow and M_aero = -d_cp * c * F_N always turns the trailing edge downstream, which is what drives passive feathering.

wrench follows MuJoCo's world-frame order [Fx, Fy, Fz, Tx, Ty, Tz] about the wing centre of mass. The formulation follows Whitney & Wood (2010) as used by the FlapX simulator.

wing_kinematics(params, side, psi, psi_dot, theta, theta_dot, linear_velocity_body, angular_velocity_body, options=None)

Relative-flow coefficients and angle of attack of one wing.

The flow along +X (U_i) and along +Z (U_o) is affine in the span coordinate: U = U1 * r + U0. The angle of attack is evaluated at the spanwise centre of pressure r_cp = R r33 / r22.

wing_loads_from_kinematics(params, kin, options=None)

Normal force, pitching moment and damping from :func:wing_kinematics.

wing_aero(params, side, psi, psi_dot, theta, theta_dot, linear_velocity_body, angular_velocity_body, options=None)

Calculate one wing's local aerodynamic force, moments and CoP.

world_wrench(wing, side, aero, read_only=False)

Convert one wing's local aero result into a world-frame wrench.

normal_force_coefficient(alpha)

FlapX C_N(alpha) empirical fit.

center_of_pressure_fraction(alpha)

FlapX chordwise d_cp(alpha) empirical fit.