6.13. Simulation models (Student version)#
Written by Marc Budinger (INSA Toulouse), Scott Delbecq (ISAE-SUPAERO) and Félix Pollet (ISAE-SUPAERO), Toulouse, France.
The objective of this notebook is to illustrate the need for implementing simulation models for design optimization. Simulation models enable to assess performance of a system in particular cases such as:
operational mission profile
specific maneuvers
failure situations
This notebook will focus on verifiying the choice of the electrical motor for a multirotor drone application. Indeed, to main torque constraints are the maximum torque (electromagnetic technological limit) and the nominal torque (thermal technological limit).
6.13.1. Mission profile#
The mission profile of the UAV is the following:
Climb
Cruise
Loiter
Descent
Hover
Climb
Cruise
Descent
The parameters of the mission profile are the following:
# Parameters
departure_ground_altitude = 0.0 # (m)
delivery_altitude = 100.0 # (m)
cruise_altitude = 300.0 # (m)
climb_speed = 10.0 # (m/s)
descent_speed = 5.0 # (m/s)
hover_duration = 30.0 # (s)
cruise_distance = 3000.0 # (m)
cruise_speed = 50.0 # (m/s)
loiter_duration = 120.0 # (s)
a_max = 2 * 9.81 # (m/s²)
time_step = 1.0 # seconds per step
Exercise 6.11
Code the function simulate_segment
.
import numpy as np
import pandas as pd
# Parameters
departure_ground_altitude = 0.0 # (m)
delivery_altitude = 100.0 # (m)
cruise_altitude = 300.0 # (m)
climb_speed = 10.0 # (m/s)
descent_speed = 5.0 # (m/s)
hover_duration = 30.0 # (s)
cruise_distance = 3000.0 # (m)
cruise_speed = 50.0 # (m/s)
loiter_duration = 120.0 # (s)
a_max = 2 * 9.81 # (m/s²)
time_step = 1.0 # seconds per step
def simulate_segment(v_init, a_init, v_target, dist, a_max, time_step):
"""
Simulates a 1D segment with acceleration limit.
- Start from v_init and a_init.
- Accelerate at a_max until v_target is reached (or distance runs out).
- Then maintain v_target until distance is covered.
Returns arrays of time, position, speed, and acceleration.
"""
# To be completed ...
return np.array(times), np.array(positions), np.array(speeds), np.array(accelerations)
# 1. Climb outbound (vertical)
vert_dist_climb_out = cruise_altitude - departure_ground_altitude
t_climb_out, alt_change_out, vel_climb_out, acc_climb_out = simulate_segment(
v_init=0.0, a_init=0.0, v_target=climb_speed, dist=vert_dist_climb_out, a_max=a_max, time_step=time_step
)
alt_climb_out = departure_ground_altitude + alt_change_out
# 2. Cruise outbound (horizontal)
t_cruise_out, dist_cruise_out, vel_cruise_out, acc_cruise_out = simulate_segment(
v_init=vel_climb_out[-1], a_init=acc_climb_out[-1], v_target=cruise_speed, dist=cruise_distance, a_max=a_max, time_step=time_step
)
alt_cruise_out = np.full_like(t_cruise_out, cruise_altitude)
# 3. Loiter (no movement)
t_loiter = np.arange(0, loiter_duration + time_step, time_step)
alt_loiter = np.full_like(t_loiter, cruise_altitude)
dist_loiter = np.full_like(t_loiter, dist_cruise_out[-1])
vel_loiter = np.zeros_like(t_loiter)
acc_loiter = np.zeros_like(t_loiter)
# 4. Descent outbound (vertical)
vert_dist_descent_out = cruise_altitude - delivery_altitude
t_descent_out, alt_change_des_out, vel_des_out, acc_des_out = simulate_segment(
v_init=vel_loiter[-1], a_init=acc_loiter[-1], v_target=descent_speed, dist=vert_dist_descent_out, a_max=a_max, time_step=time_step
)
alt_descent_out = cruise_altitude - alt_change_des_out
alt_descent_out[alt_descent_out < delivery_altitude] = delivery_altitude
dist_descent_out = np.full_like(t_descent_out, dist_loiter[-1])
# 5. Hover outbound at delivery altitude (no movement)
t_hover_out = np.arange(0, hover_duration + time_step, time_step)
alt_hover_out = np.full_like(t_hover_out, delivery_altitude)
dist_hover_out = np.full_like(t_hover_out, dist_descent_out[-1])
vel_hover_out = np.zeros_like(t_hover_out)
acc_hover_out = np.zeros_like(t_hover_out)
# 6. Climb inbound (vertical)
vert_dist_climb_in = cruise_altitude - delivery_altitude
t_climb_in, alt_change_climb_in, vel_climb_in, acc_climb_in = simulate_segment(
v_init=vel_hover_out[-1], a_init=acc_hover_out[-1], v_target=climb_speed, dist=vert_dist_climb_in, a_max=a_max, time_step=time_step
)
alt_climb_in = delivery_altitude + alt_change_climb_in
alt_climb_in[alt_climb_in > cruise_altitude] = cruise_altitude
dist_climb_in = np.full_like(t_climb_in, dist_hover_out[-1])
# 7. Cruise inbound (horizontal)
t_cruise_in, dist_cruise_in_val, vel_cruise_in, acc_cruise_in = simulate_segment(
v_init=vel_climb_in[-1], a_init=acc_climb_in[-1], v_target=cruise_speed, dist=cruise_distance, a_max=a_max, time_step=time_step
)
alt_cruise_in = np.full_like(t_cruise_in, cruise_altitude)
dist_cruise_in = dist_climb_in[-1] + dist_cruise_in_val
# 8. Descent inbound (vertical)
vert_dist_descent_in = cruise_altitude - departure_ground_altitude
t_descent_in, alt_change_des_in, vel_des_in, acc_des_in = simulate_segment(
v_init=vel_cruise_in[-1], a_init=acc_cruise_in[-1], v_target=descent_speed, dist=vert_dist_descent_in, a_max=a_max, time_step=time_step
)
alt_descent_in = cruise_altitude - alt_change_des_in
alt_descent_in[alt_descent_in < departure_ground_altitude] = departure_ground_altitude
dist_descent_in = np.full_like(t_descent_in, dist_cruise_in[-1])
# Time offsets
t0_climb_out = 0.0
t0_cruise_out = t0_climb_out + t_climb_out[-1]
t0_loiter = t0_cruise_out + t_cruise_out[-1]
t0_descent_out = t0_loiter + t_loiter[-1]
t0_hover_out = t0_descent_out + t_descent_out[-1]
t0_climb_in = t0_hover_out + t_hover_out[-1]
t0_cruise_in = t0_climb_in + t_climb_in[-1]
t0_descent_in = t0_cruise_in + t_cruise_in[-1]
time_climb_out_abs = t0_climb_out + t_climb_out
time_cruise_out_abs = t0_cruise_out + t_cruise_out
time_loiter_abs = t0_loiter + t_loiter
time_descent_out_abs = t0_descent_out + t_descent_out
time_hover_out_abs = t0_hover_out + t_hover_out
time_climb_in_abs = t0_climb_in + t_climb_in
time_cruise_in_abs = t0_cruise_in + t_cruise_in
time_descent_in_abs = t0_descent_in + t_descent_in
segment_climb_out = np.full_like(time_climb_out_abs, "Climb outbound", dtype=object)
segment_cruise_out = np.full_like(time_cruise_out_abs, "Cruise outbound", dtype=object)
segment_loiter = np.full_like(time_loiter_abs, "Loiter", dtype=object)
segment_descent_out = np.full_like(time_descent_out_abs, "Descent outbound", dtype=object)
segment_hover_out = np.full_like(time_hover_out_abs, "Hover outbound", dtype=object)
segment_climb_in = np.full_like(time_climb_in_abs, "Climb inbound", dtype=object)
segment_cruise_in = np.full_like(time_cruise_in_abs, "Cruise inbound", dtype=object)
segment_descent_in = np.full_like(time_descent_in_abs, "Descent inbound", dtype=object)
time_all = np.concatenate([
time_climb_out_abs,
time_cruise_out_abs,
time_loiter_abs,
time_descent_out_abs,
time_hover_out_abs,
time_climb_in_abs,
time_cruise_in_abs,
time_descent_in_abs
])
alt_all = np.concatenate([
alt_climb_out,
alt_cruise_out,
alt_loiter,
alt_descent_out,
alt_hover_out,
alt_climb_in,
alt_cruise_in,
alt_descent_in
])
dist_all = np.concatenate([
np.zeros_like(t_climb_out), # no horizontal move in climb_out
dist_cruise_out,
dist_loiter,
dist_descent_out,
dist_hover_out,
dist_climb_in,
dist_cruise_in,
dist_descent_in
])
speed_all = np.concatenate([
vel_climb_out,
vel_cruise_out,
vel_loiter,
vel_des_out,
vel_hover_out,
vel_climb_in,
vel_cruise_in,
vel_des_in
])
acc_all = np.concatenate([
acc_climb_out,
acc_cruise_out,
acc_loiter,
acc_des_out,
acc_hover_out,
acc_climb_in,
acc_cruise_in,
acc_des_in
])
segment_all = np.concatenate([
segment_climb_out,
segment_cruise_out,
segment_loiter,
segment_descent_out,
segment_hover_out,
segment_climb_in,
segment_cruise_in,
segment_descent_in
])
df = pd.DataFrame({
"time": time_all,
"altitude": alt_all,
"horizontal_distance": dist_all,
"speed": speed_all,
"acceleration": acc_all,
"segment": segment_all
})
df = df.sort_values("time").reset_index(drop=True)
df
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[1], line 33
31 # 1. Climb outbound (vertical)
32 vert_dist_climb_out = cruise_altitude - departure_ground_altitude
---> 33 t_climb_out, alt_change_out, vel_climb_out, acc_climb_out = simulate_segment(
34 v_init=0.0, a_init=0.0, v_target=climb_speed, dist=vert_dist_climb_out, a_max=a_max, time_step=time_step
35 )
36 alt_climb_out = departure_ground_altitude + alt_change_out
38 # 2. Cruise outbound (horizontal)
Cell In[1], line 28, in simulate_segment(v_init, a_init, v_target, dist, a_max, time_step)
18 """
19 Simulates a 1D segment with acceleration limit.
20 - Start from v_init and a_init.
(...)
23 Returns arrays of time, position, speed, and acceleration.
24 """
26 # To be completed ...
---> 28 return np.array(times), np.array(positions), np.array(speeds), np.array(accelerations)
NameError: name 'times' is not defined
Lets now plot the resulting mission profile to verify its consistency.
import matplotlib.pyplot as plt
# Plot altitude vs time
plt.figure(figsize=(10,6))
segments = df['segment'].unique()
for seg in segments:
seg_data = df[df['segment'] == seg]
plt.plot(seg_data['time'], seg_data['altitude'], label=seg)
plt.title("Altitude vs Time", fontsize=16)
plt.xlabel("Time [s]", fontsize=14)
plt.ylabel("Altitude [m]", fontsize=14)
plt.legend()
plt.grid(True)
plt.show()
# Plot horizontal distance vs time
plt.figure(figsize=(10,6))
segments = df['segment'].unique()
for seg in segments:
seg_data = df[df['segment'] == seg]
plt.plot(seg_data['time'], seg_data['horizontal_distance'], label=seg)
plt.title("Horizontal Distance vs Time", fontsize=16)
plt.xlabel("Time [s]", fontsize=14)
plt.ylabel("Horizontal Distance [m]", fontsize=14)
plt.legend()
plt.grid(True)
plt.show()
# Plot speed vs time
plt.figure(figsize=(10,6))
segments = df['segment'].unique()
for seg in segments:
seg_data = df[df['segment'] == seg]
plt.plot(seg_data['horizontal_distance'], seg_data['altitude'], label=seg)
plt.title("Altitude vs Horizontal Distance", fontsize=16)
plt.xlabel("Horizontal Distance [m]", fontsize=14)
plt.ylabel("Altitude [m]", fontsize=14)
plt.legend()
plt.grid(True)
plt.show()
# Plot speed vs time
plt.figure(figsize=(10,6))
segments = df['segment'].unique()
for seg in segments:
seg_data = df[df['segment'] == seg]
plt.plot(seg_data['time'], seg_data['speed'], label=seg)
plt.title("Speed vs Time", fontsize=16)
plt.xlabel("Time [s]", fontsize=14)
plt.ylabel("Speed [m/s]", fontsize=14)
plt.legend()
plt.grid(True)
plt.show()
# Plot acceleration vs time
plt.figure(figsize=(10,6))
segments = df['segment'].unique()
for seg in segments:
seg_data = df[df['segment'] == seg]
plt.plot(seg_data['time'], seg_data['acceleration'], label=seg)
plt.title("Acceleration vs Time", fontsize=16)
plt.xlabel("Time [s]", fontsize=14)
plt.ylabel("Acceleration [m/s2]", fontsize=14)
plt.legend()
plt.grid(True)
plt.show()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 5
3 # Plot altitude vs time
4 plt.figure(figsize=(10,6))
----> 5 segments = df['segment'].unique()
6 for seg in segments:
7 seg_data = df[df['segment'] == seg]
NameError: name 'df' is not defined
<Figure size 1000x600 with 0 Axes>
6.13.2. Parameters of the system#
To simulate the propeller torque we have to consider several parameters for the system and the components.
6.13.2.1. UAV#
M_total = 1.35 # [kg] mass of the drone
6.13.2.2. Propeller#
D_pro = 10 * 0.0254 # [m] Propeller diameter
N_pro = 4 # [-] Number of propellers
# APC coefficients
# Ct=T/(rho * n**2 * D**4) (thrust coef.)
Ct = 0.1125 # [-] Thrust coefficient, propeller notation
# Cp=P/(rho * n**3 * D**5) (power coef.)
Cp = 0.0441 # [-] Power coefficient, propeller notation
6.13.2.3. Aerodynamics#
# Aerodynamics
rho = 1.18 # [kg/m^3] Air mass volumic (25°C)
CD_frame = 0.5 # [-] Frame drag coefficient
S_frame = 0.1 # [m^2] Frame surface
CD_pro = 0.015 # [-] Propeller drag coefficient
# UAV
M_total = 1.35 # [kg] mass of the drone
# Propeller
D_pro = 10 * 0.0254 # [m] Propeller diameter
N_pro = 4 # [-] Number of propellers
# APC coefficients
# Ct=T/(rho * n**2 * D**4) (thrust coef.)
Ct = 0.1125 # [-] Thrust coefficient, propeller notation
# Cp=P/(rho * n**3 * D**5) (power coef.)
Cp = 0.0441 # [-] Power coefficient, propeller notation
# Aerodynamics
rho = 1.18 # [kg/m^3] Air mass volumic (25°C)
CD_frame = 0.5 # [-] Frame drag coefficient
S_frame = 0.1 # [m^2] Frame surface
CD_pro = 0.015 # [-] Propeller drag coefficient
6.13.3. Verification of the thermal behaviour with the RMS torque#
Exercise 6.12
Determine the thrust, torque, speed and power of the propeller with respect to time.
# Compute propeller thrust and speed for each segment
thrust = np.zeros_like(df['time'])
speed = np.zeros_like(df['time'])
torque= np.zeros_like(df['time'])
power = np.zeros_like(df['time'])
# To be completed ...
df['thrust'] = thrust
df['speed'] = speed
df['torque'] = torque
df['power'] = power
# Plot thrust vs time
plt.figure(figsize=(10,6))
plt.plot(df['time'], df['thrust'])
plt.title("Propeller Thrust vs Time", fontsize=16)
plt.xlabel("Time [s]", fontsize=14)
plt.ylabel("Thrust [N]", fontsize=14)
plt.grid(True)
plt.show()
# Plot speed vs time
plt.figure(figsize=(10,6))
plt.plot(df['time'], df['speed'])
plt.title("Propeller Speed vs Time", fontsize=16)
plt.xlabel("Time [s]", fontsize=14)
plt.ylabel("Speed [rad/s]", fontsize=14)
plt.grid(True)
plt.show()
# Plot torque vs time
plt.figure(figsize=(10,6))
plt.plot(df['time'], df['torque'])
plt.title("Propeller Torque vs Time", fontsize=16)
plt.xlabel("Time [s]", fontsize=14)
plt.ylabel("Torque [Nm]", fontsize=14)
plt.grid(True)
plt.show()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[4], line 2
1 # Compute propeller thrust and speed for each segment
----> 2 thrust = np.zeros_like(df['time'])
3 speed = np.zeros_like(df['time'])
4 torque= np.zeros_like(df['time'])
NameError: name 'df' is not defined
# Compute RMS torque
rms_torque =
# Compute max torque
max_torque =
print(f"Max torque of {max_torque} [Nm] and RMS torque of {rms_torque} [Nm] ")
Cell In[5], line 2
rms_torque =
^
SyntaxError: invalid syntax
# Pandas package Importation
import pandas as pd
# Read the .csv file with bearing data
path = "https://raw.githubusercontent.com/SizingLab/sizing_course/main/laboratories/Lab-multirotor/assets/data/"
df = pd.read_excel(path + "AXI_mot_db_student.xlsx", engine="openpyxl")
# Print the head (first lines of the file)
df.head()
Name | Kv (rpm/v) | Pole (number) | Io (A) | r (omn) | weight (g) | Imax (A) | No s | Voltage | Inom_max (A) | Eta_max (%) | Kt (N.m/A) | Tnom (N.m) | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | AXI 2203/40VPP GOLD LINE | 2000 | 14 | 0.50 | 0.245 | 17.5 | 9.0 | 2 | 7.4 | 7.5 | 75 | 0.004775 | 0.033423 |
1 | AXI 2203/46 GOLD LINE | 1720 | 14 | 0.50 | 0.285 | 18.5 | 8.5 | 2 | 7.4 | 7.0 | 75 | 0.005552 | 0.036087 |
2 | AXI 2203/52 GOLD LINE | 1525 | 14 | 0.40 | 0.390 | 18.5 | 7.0 | 2 | 7.4 | 5.5 | 74 | 0.006262 | 0.031935 |
3 | AXI 2203/RACE GOLD LINE | 2300 | 14 | 0.55 | 0.220 | 18.5 | 9.0 | 3 | 11.1 | 7.5 | 74 | 0.004152 | 0.028855 |
4 | AXI 2204/54 GOLD LINE | 1400 | 14 | 0.35 | 0.320 | 25.9 | 7.5 | 3 | 11.1 | 6.0 | 77 | 0.006821 | 0.038538 |