Rotary-Wing Aerodynamics Digital Twin

The Rotary-Wing Aerodynamics Digital Twin (VectorizedAerodynamics) models high-fidelity flight dynamics and propulsion power consumption for multi-rotor unmanned aerial vehicles (UAVs) in 3D urban airspaces.

Theoretical Foundation

UrbanMARL implements the analytical rotary-wing UAV power consumption model established by Zeng, Zhang, and Lim (IEEE Wireless Communications, 2016) and Zeng and Zhang (IEEE Transactions on Wireless Communications, 2017).

Unlike simplified linear models that assume power is strictly proportional to velocity (\(P \propto \|\mathbf{v}\|\)), realistic rotary-wing aerodynamics exhibit a non-monotonic U-shaped power curve: hovering requires substantial induced power, moderate forward speeds benefit from translational lift (reducing induced power), and high forward speeds suffer from steep parasite drag.

Propulsion Power Formulations

Instantaneous aerodynamic propulsion power \(P_{\text{prop}}(\mathbf{v})\) is calculated as the sum of four physical components:

\[P_{\text{prop}}(\mathbf{v}) = P_{\text{blade}}(v_h) + P_{\text{induced}}(v_h) + P_{\text{parasite}}(v_h) + P_{\text{vert}}(v_z)\]

where \(v_h = \sqrt{v_x^2 + v_y^2}\) is the horizontal forward airspeed, and \(v_z\) is the vertical climb/descent velocity.

1. Blade Profile Power

Power required to overcome profile drag of the spinning rotor blades:

\[P_{\text{blade}}(v_h) = P_0 \left(1 + \frac{3 v_h^2}{U_{\text{tip}}^2}\right)\]

where \(P_0\) is the blade profile power in hover, and \(U_{\text{tip}} = \Omega R\) is the rotor blade tip speed.

2. Induced Power

Power required to produce aerodynamic thrust counteracting gravity:

\[P_{\text{induced}}(v_h) = P_i \left(\sqrt{1 + \frac{v_h^4}{4 v_0^4}} - \frac{v_h^2}{2 v_0^2}\right)^{1/2}\]

where \(P_i\) is the induced power in hover, and \(v_0\) is the mean rotor induced velocity in hovering flight.

3. Parasite Drag Power

Power required to overcome fuselage aerodynamic form drag through the air:

\[P_{\text{parasite}}(v_h) = \frac{1}{2} d_0 \rho s A v_h^3\]

where \(d_0\) is fuselage drag ratio, \(\rho\) is ambient air density (\(1.225\text{ kg/m}^3\)), \(s\) is rotor solidity, and \(A\) is total rotor disc area.

4. Vertical Flight Power

Gravitational work performed during climb or saved during descent:

\[P_{\text{vert}}(v_z) = m \cdot g \cdot v_z\]

where \(m\) is UAV gross mass (kg) and \(g = 9.81\text{ m/s}^2\). Note that descent power is lower-bounded to account for motor idling limits during autorotation/descent.

Standard Aerodynamic Parameters

Default physical parameters calibrated for standard commercial quadrotors (e.g., DJI Matrice 300 / Phantom 4):

Parameter

Physical Meaning

Default Value

Unit

\(P_0\)

Blade profile power in hover

79.86

W

\(P_i\)

Induced power in hover

88.63

W

\(U_{\text{tip}}\)

Rotor blade tip speed

120.0

m/s

\(v_0\)

Mean induced velocity in hover

4.03

m/s

\(d_0\)

Fuselage aerodynamic drag ratio

0.60

dimensionless

\(\rho\)

Ambient air density at sea level

1.225

\(\text{kg/m}^3\)

\(s\)

Rotor disc solidity

0.05

dimensionless

\(A\)

Total rotor disc area

0.503

\(\text{m}^2\)

\(m\)

Gross UAV mass

2.0

kg

Battery Depletion Dynamics

The UAV onboard battery state \(E(t)\) evolves over discrete time steps \(\Delta t\):

\[E(t+1) = \max\left(0, \; E(t) - \left[ P_{\text{prop}}(\mathbf{v}(t)) + P_{\text{MEC}}(t) + P_{\text{comm}}(t) \right] \cdot \Delta t \right)\]

where \(P_{\text{MEC}}\) is computational power expended by the onboard mobile edge computing server, and \(P_{\text{comm}}\) is radio transmission power.

Python Usage Example

VectorizedAerodynamics operates directly on batched PyTorch tensors across CPU and GPU:

import torch
from urbanmarl.models.aerodynamics import VectorizedAerodynamics

# Initialize aerodynamic engine
aero = VectorizedAerodynamics(
    p0=79.86,
    pi=88.63,
    u_tip=120.0,
    v0=4.03,
    mass_kg=2.0,
    device=torch.device("cuda" if torch.cuda.is_available() else "cpu"),
)

# Batched 3D velocities: shape (batch_size=64, num_uavs=4, 3)
velocities = torch.tensor([
    [0.0, 0.0, 0.0],    # Hover: ~168.5 W
    [10.0, 0.0, 0.0],   # Cruise (10 m/s): ~155.2 W (minimum power speed)
    [25.0, 0.0, 0.0],   # High-speed (25 m/s): ~310.8 W (parasite drag dominated)
    [5.0, 0.0, 2.0],    # Climbing at 2 m/s: hover + kinetic + gravitational work
], device=aero.device).unsqueeze(0).repeat(64, 1, 1)

# Calculate instantaneous power in Watts: shape (64, 4, 1)
power_watts = aero.compute_propulsion_power(velocities)

# Integrate energy consumed over time step dt = 0.5s: shape (64, 4, 1)
energy_joules = aero.compute_energy(velocities, dt=0.5)

print(f"Hover Power:   {power_watts[0, 0].item():.2f} W")
print(f"Cruise Power:  {power_watts[0, 1].item():.2f} W")
print(f"High-Speed:    {power_watts[0, 2].item():.2f} W")