Design optimization and exploration of a multirotor drone (ISAE)#

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 use and modify the sizing code of the UAV to explore the design space.

Modular sizing code#

Here we use the sizing code but decomposed into several ExplicitComponent of OpenMDAO. You might notice an improvement in computational cost and robustness compared to the previous sizing code that used a single ExplicitComponent to wrap all the sizing code. The models are separated by components and located in different Python files /models/openmdao.

# Import libraries
import numpy as np
import openmdao.api as om

from models.openmdao.scenarios import Scenarios
from models.openmdao.propeller import Propeller
from models.openmdao.motor import Motor
from models.openmdao.battery import Battery
from models.openmdao.esc import ESC
from models.openmdao.frame import Frame
from models.openmdao.objectives import Objectives
from models.openmdao.constraints import Constraints

The inputs of the whole sizing code are stored in dicts that we be passed to the problem before the run. Here are the default values seperated by:

  • specifications

  • design assumtpions

  • design variable initialization

However, one of the objective of the notebook is to modify these input values.

# Specifications
specifications_default = {
    "M_pay": 50.0,  # [kg] load mass
    "a_to": 0.25 * 9.81,  # [m/s**2] acceleration
    "t_hov_spec": 25.0,  # [min] time of hover flight
    "MTOW": 360.0,  # [kg] maximal mass allowed
}

# Design assumptions
design_assumptions_default = {
    "N_arm" : 4,  # [-] number of arms
    "N_pro_arm" : 1,  # [-] number of propeller per arm (1 or 2)
    "M_bat_ref" : 0.329,  # [kg] mass
    "E_bat_ref" : 220.0 * 3600.0 * 0.329,  # [J]
    "C_bat_ref" : 5,  # [Ah] Capacity
    "I_bat_max_ref" : 50 * 5,  # [A] max discharge current
    "P_esc_ref" : 3108.0,  # [W] Power
    "M_esc_ref" : 0.115,  # [kg] Mass
    "T_nom_mot_ref" : 2.32,  # [N*m] rated torque
    "T_max_mot_ref" : 85.0 / 70.0 * 2.32,  # [N*m] max torque
    "R_mot_ref" : 0.03,  # [ohm] resistance
    "M_mot_ref" : 0.575,  # [kg] mass
    "K_T_ref" : 0.03,  # [N*m/A] torque coefficient
    "T_mot_fr_ref" : 0.03,  # [N*m] friction torque (zero load, nominal speed)
    "sigma_max" : (
        280e6 / 4.0
    ),  # [Pa] Composite max stress (2 reduction for dynamic, 2 reduction for stress concentration)
    "rho_s" : 1700.0,  # [kg/m**3] Volumic mass of aluminum
    "rho_air" : 1.18,  # [kg/m**3] Air density
    "ND_max" : 105000.0 / 60.0 * 0.0254,  # [Hz.m] Max speed limit (N.D max) for APC MR propellers
    "D_pro_ref" : 11.0 * 0.0254,  # [m] Reference propeller diameter
    "M_pro_ref" : 0.53 * 0.0283,  # [kg] Reference propeller mass
}

# Design variables initial values 

design_variables_default = {
    "beta_pro": 0.33,  # pitch/diameter ratio of the propeller
    "k_os": 3.2,  # over sizing coefficient on the load mass
    "k_ND": 1.2,  # slow down propeller coef : ND = NDmax / k_ND
    "k_mot": 1.0,  # over sizing coefficient on the motor torque
    "k_speed_mot": 1.2,  # adaption winding coef on the motor speed
    "k_mb": 1.0,  # ratio battery/load mass
    "k_vb": 1.0,  # oversizing coefficient for voltage evaluation
    "k_D": 0.5,  # aspect ratio D_in/D_out for the beam of the frame
}

Similar to the previous notebook, we define the models to include in the problem as well as the optimization formulation.

prob = om.Problem()

group = om.Group()
group.add_subsystem("scenarios", Scenarios(), promotes=["*"])
group.add_subsystem("propeller", Propeller(), promotes=["*"])
group.add_subsystem("motor", Motor(), promotes=["*"])
group.add_subsystem("battery", Battery(), promotes=["*"])
group.add_subsystem("esc", ESC(), promotes=["*"])
group.add_subsystem("frame", Frame(), promotes=["*"])
group.add_subsystem("objectives", Objectives(), promotes=["*"])
group.add_subsystem("constraints", Constraints(), promotes=["*"])


prob.model = group

prob.driver = om.ScipyOptimizeDriver()
prob.driver.options["optimizer"] = "SLSQP"
prob.driver.options["maxiter"] = 100
prob.driver.options["tol"] = 1e-8

prob.model.add_design_var("beta_pro", lower=0.3, upper=0.6)
prob.model.add_design_var("k_os", lower=1.0, upper=10.0)
prob.model.add_design_var("k_ND", lower=1.0, upper=10.0)
prob.model.add_design_var("k_mot", lower=1.0, upper=10.0)
prob.model.add_design_var("k_speed_mot", lower=1.0, upper=10.0)
prob.model.add_design_var("k_mb", lower=0.1, upper=10.0)
prob.model.add_design_var("k_vb", lower=1.0, upper=10.0)
prob.model.add_design_var("k_D", lower=0.01, upper=0.99)

prob.model.add_constraint("cons_1", lower=0.)
prob.model.add_constraint("cons_2", lower=0.)
prob.model.add_constraint("cons_3", lower=0.)
prob.model.add_constraint("cons_4", lower=0.)
prob.model.add_constraint("cons_5", lower=0.)
prob.model.add_constraint("cons_6", lower=0.)
prob.model.add_constraint('cons_7', lower=0.)

# prob.model.add_objective('t_hov', scaler=-0.1)
prob.model.add_objective("M_total_real", scaler=0.1)

prob.setup()
<openmdao.core.problem.Problem at 0x7fd49a97f280>

Now we set the inputs based on the values provided by the dictionnaries.

# Setup initial values
for name, value in {**specifications_default, **design_assumptions_default, **design_variables_default}.items():
    prob.set_val(name, value)

optimization_unsuccessful = prob.run_driver()
/opt/hostedtoolcache/Python/3.9.18/x64/lib/python3.9/site-packages/scipy/optimize/_optimize.py:284: RuntimeWarning: Values in x were outside bounds during a minimize step, clipping to bounds
  warnings.warn("Values in x were outside bounds during a "
Optimization terminated successfully    (Exit mode 0)
            Current function value: 10.377302309077761
            Iterations: 11
            Function evaluations: 12
            Gradient evaluations: 11
Optimization Complete
-----------------------------------

Please note that if the optimization is unsuccessful optimization_unsuccessful = True. This could be useful.

We can check the details of the optimization results:

results = prob.list_problem_vars(print_arrays=True,
                       desvar_opts=['lower', 'upper','min', 'max'],
                       cons_opts=['lower', 'upper', 'equals', 'min', 'max'],
                       objs_opts=['scaler'])
----------------
Design Variables
----------------
name         val           size  lower  upper  min         max         
-----------  ------------  ----  -----  -----  ----------  ---------- 
beta_pro     [0.3]         1     0.3    0.6    0.3         0.3         
k_os         [2.07546046]  1     1.0    10.0   2.07546046  2.07546046  
k_ND         [1.25998295]  1     1.0    10.0   1.25998295  1.25998295  
k_mot        [1.02941176]  1     1.0    10.0   1.02941176  1.02941176  
k_speed_mot  [1.26195253]  1     1.0    10.0   1.26195253  1.26195253  
k_mb         [0.64317582]  1     0.1    10.0   0.64317582  0.64317582  
k_vb         [1.]          1     1.0    10.0   1.0         1.0         
k_D          [0.99]        1     0.01   0.99   0.99        0.99        

-----------
Constraints
-----------
name    val                size  lower  upper  equals  min                 max                 
------  -----------------  ----  -----  -----  ------  ------------------  ------------------ 
cons_1  [-2.88650881e-10]  1     0.0    1e+30  None    -0.0                -0.0                
cons_2  [-9.7664099e-11]   1     0.0    1e+30  None    -0.0                -0.0                
cons_3  [3.60671493e-11]   1     0.0    1e+30  None    0.0                 0.0                 
cons_4  [1.01877114e+09]   1     0.0    1e+30  None    1018771135.3738992  1018771135.3738992  
cons_5  [3.17643194]       1     0.0    1e+30  None    3.17643194          3.17643194          
cons_6  [-7.82875986e-11]  1     0.0    1e+30  None    -0.0                -0.0                
cons_7  [256.22697691]     1     0.0    1e+30  None    256.22697691        256.22697691        

----------
Objectives
----------
name          val            size  scaler  
------------  -------------  ----  ------ 
M_total_real  [10.37730231]  1     0.1     

And check the details of the final inputs:

inputs = prob.model.list_inputs(prom_name=True)
70 Input(s) in 'model'

varname          val                prom_name    
---------------  -----------------  -------------
scenarios
  k_os           [2.07546046]       k_os         
  M_pay          [50.]              M_pay        
  N_pro_arm      [1.]               N_pro_arm    
  N_arm          [4.]               N_arm        
  a_to           [2.4525]           a_to         
propeller
  beta_pro       [0.3]              beta_pro     
  F_pro_to       [318.12917391]     F_pro_to     
  rho_air        [1.18]             rho_air      
  ND_max         [44.45]            ND_max       
  k_ND           [1.25998295]       k_ND         
  M_pro_ref      [0.014999]         M_pro_ref    
  D_pro_ref      [0.2794]           D_pro_ref    
  F_pro_hov      [254.50333913]     F_pro_hov    
motor
  k_mot          [1.02941176]       k_mot        
  T_pro_hov      [20.72734165]      T_pro_hov    
  k_vb           [1.]               k_vb         
  P_pro_to       [3616.45874658]    P_pro_to     
  k_speed_mot    [1.26195253]       k_speed_mot  
  Omega_pro_to   [139.58215416]     Omega_pro_to 
  M_mot_ref      [0.575]            M_mot_ref    
  T_nom_mot_ref  [2.32]             T_nom_mot_ref
  R_mot_ref      [0.03]             R_mot_ref    
  K_T_ref        [0.03]             K_T_ref      
  T_mot_fr_ref   [0.03]             T_mot_fr_ref 
  T_max_mot_ref  [2.81714286]       T_max_mot_ref
  Omega_pro_hov  [124.84607406]     Omega_pro_hov
  T_pro_to       [25.90917706]      T_pro_to     
battery
  k_mb           [0.64317582]       k_mb         
  M_pay          [50.]              M_pay        
  E_bat_ref      [260568.]          E_bat_ref    
  M_bat_ref      [0.329]            M_bat_ref    
  U_bat          [35.13984881]      U_bat        
  I_bat_max_ref  [250.]             I_bat_max_ref
  C_bat_ref      [5.]               C_bat_ref    
  P_el_mot_hov   [3226.16991799]    P_el_mot_hov 
  N_pro          [4.]               N_pro        
esc
  P_el_mot_to    [4599.19697756]    P_el_mot_to  
  U_bat          [35.13984881]      U_bat        
  U_mot_to       [35.13984881]      U_mot_to     
  M_esc_ref      [0.115]            M_esc_ref    
  P_esc_ref      [3108.]            P_esc_ref    
frame
  N_arm          [4.]               N_arm        
  D_pro          [1.58802402]       D_pro        
  F_pro_to       [318.12917391]     F_pro_to     
  N_pro_arm      [1.]               N_pro_arm    
  sigma_max      [70000000.]        sigma_max    
  k_D            [0.99]             k_D          
  rho_s          [1700.]            rho_s        
objectives
  C_bat          [579849.10864506]  C_bat        
  I_bat_hov      [386.56607243]     I_bat_hov    
  M_esc          [0.17017621]       M_esc        
  M_pro          [0.48453331]       M_pro        
  M_mot          [3.85166988]       M_mot        
  N_pro          [4.]               N_pro        
  M_pay          [50.]              M_pay        
  M_bat          [32.15879105]      M_bat        
  M_frame        [3.58871446]       M_frame      
constraints
  M_total        [103.77302309]     M_total      
  M_total_real   [103.77302309]     M_total_real 
  T_max_mot      [25.90917706]      T_max_mot    
  T_pro_to       [25.90917706]      T_pro_to     
  U_bat          [35.13984881]      U_bat        
  U_mot_to       [35.13984881]      U_mot_to     
  P_bat_max      [1.0187905e+09]    P_bat_max    
  P_el_mot_to    [4599.19697756]    P_el_mot_to  
  N_pro          [4.]               N_pro        
  U_esc          [38.31628075]      U_esc        
  t_hov          [25.]              t_hov        
  t_hov_spec     [25.]              t_hov_spec   
  MTOW           [360.]             MTOW         

And check the details of the final outputs:

outputs = prob.model.list_outputs()
54 Explicit Output(s) in 'model'

varname          val                  prom_name    
---------------  -------------------  -------------
scenarios
  M_total        [103.77302309]       M_total      
  N_pro          [4.]                 N_pro        
  F_pro_hov      [254.50333913]       F_pro_hov    
  F_pro_to       [318.12917391]       F_pro_to     
propeller
  C_t            [0.0859]             C_t          
  C_p            [0.02768]            C_p          
  D_pro          [1.58802402]         D_pro        
  n_pro_to       [22.2151898]         n_pro_to     
  Omega_pro_to   [139.58215416]       Omega_pro_to 
  M_pro          [0.48453331]         M_pro        
  P_pro_to       [3616.45874658]      P_pro_to     
  T_pro_to       [25.90917706]        T_pro_to     
  n_pro_hov      [19.86986981]        n_pro_hov    
  Omega_pro_hov  [124.84607406]       Omega_pro_hov
  P_pro_hov      [2587.72723046]      P_pro_hov    
  T_pro_hov      [20.72734165]        T_pro_hov    
motor
  T_nom_mot      [21.33696934]        T_nom_mot    
  U_bat          [35.13984881]        U_bat        
  K_T            [0.19949268]         K_T          
  M_mot          [3.85166988]         M_mot        
  R_mot          [0.05573106]         R_mot        
  T_mot_fr       [0.20095669]         T_mot_fr     
  T_max_mot      [25.90917706]        T_max_mot    
  I_mot_hov      [104.90759786]       I_mot_hov    
  U_mot_hov      [30.75249061]        U_mot_hov    
  P_el_mot_hov   [3226.16991799]      P_el_mot_hov 
  I_mot_to       [130.88266267]       I_mot_to     
  U_mot_to       [35.13984881]        U_mot_to     
  P_el_mot_to    [4599.19697756]      P_el_mot_to  
battery
  M_bat          [32.15879105]        M_bat        
  E_bat          [20375810.00827609]  E_bat        
  C_bat          [579849.10864506]    C_bat        
  I_bat_max      [28992455.43225309]  I_bat_max    
  P_bat_max      [1.0187905e+09]      P_bat_max    
  I_bat_hov      [386.56607243]       I_bat_hov    
esc
  P_esc          [4599.19697756]      P_esc        
  U_esc          [38.31628075]        U_esc        
  M_esc          [0.17017621]         M_esc        
frame
  alpha_sep      [1.57079633]         alpha_sep    
  L_arm          [1.12290255]         L_arm        
  D_out_arm      [0.1096737]          D_out_arm    
  D_in_arm       [0.10857696]         D_in_arm     
  M_arms         [1.43548578]         M_arms       
  M_body         [2.15322867]         M_body       
  M_frame        [3.58871446]         M_frame      
objectives
  t_hov          [25.]                t_hov        
  M_total_real   [103.77302309]       M_total_real 
constraints
  cons_1         [-2.88650881e-10]    cons_1       
  cons_2         [-9.7664099e-11]     cons_2       
  cons_3         [3.60671493e-11]     cons_3       
  cons_4         [1.01877114e+09]     cons_4       
  cons_5         [3.17643194]         cons_5       
  cons_6         [-7.82875986e-11]    cons_6       
  cons_7         [256.22697691]       cons_7       


0 Implicit Output(s) in 'model'

Now feel free to copy/duplicate and modify peaces of the previosu code to answer the different exercices.

Note

Please highlight during the exercice the design space where no physical solutions are found (if relevant).

Determining the physical limits of the design space#

Payload#

Exercise 18

Determine with the default requirements what is the payload value where the UAV design optimization does not find a solution?

# To be completed 

Please comment…

Take-off acceleration#

Exercise 19

Determine with the default requirements what is the take-off acceleration value where the UAV design optimization does not find a solution?

# To be completed 

Please comment…

Autonomy#

# To be completed 

Please comment…

UAV total mass (\(M_{total,real}\))#

Warning

From now on, deactivate the constraint on the MTOW.

\(M_{total,real}\) vs Autonomy#

# To be completed 

Please comment…

\(M_{total,real}\) vs Payload#

# To be completed 

Please comment…

Autonomy vs Payload#

# To be completed 

Please comment…

Model modifications#

Motor reference component#

# To be completed 

Please comment…

Battery reference component#

# To be completed 

Please comment…

Your turn (Bonus)#

# To be completed 

Please comment…