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:
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:
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:
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:
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:
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\):
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")