Tutorial 14 - Procedure#
Welcome to this tutorial outlining the Procedure for an optimization problem in RCAIDE. This guide will walk you through the code, explain its components, and highlight where modifications can be made to customize the simulation for different vehicle designs. This tutorial is part of the Optimize tutorial.
1. Header and Imports#
The Imports section imports the necessary libraries and functions for the tutorial. These include, but are not limited to:
numpy for numerical operations and arrays
Units and Data for unit conversions and data handling
design_turbofan for turbofan design
[1]:
import numpy as np
import RCAIDE
from RCAIDE.Framework.Core import Units, Data
from RCAIDE.Framework.Analyses.Process import Process
from RCAIDE.Library.Methods.Powertrain.Propulsors.Turbofan_Propulsor import design_turbofan
Matplotlib created a temporary cache directory at /var/folders/r4/4h9x29hj6f95kyhjsltxc6_00000gn/T/matplotlib-a33yrch_ because the default path (/Users/aidanmolloy/.matplotlib) is not a writable directory; it is highly recommended to set the MPLCONFIGDIR environment variable to a writable directory, in particular to speed up the import of Matplotlib and to better support multiprocessing.
Fontconfig warning: ignoring UTF-8: not a valid region tag
Matplotlib is building the font cache; this may take a moment.
Setup#
The ``setup`` function creates the analysis procedure. It first creates a process container and assigns the update_aircraft and weight functions to it. It then creates a second process container and assigns the design_mission function to it. Finally, it assigns the post_process function to the analysis procedure.
[2]:
def setup():
# ------------------------------------------------------------------
# Analysis Procedure
# ------------------------------------------------------------------
# size the base config
procedure = Process()
procedure.update_aircraft = update_aircraft
# find the weights
procedure.weights = weight
# performance studies
procedure.missions = Process()
procedure.missions.design_mission = design_mission
# post process the results
procedure.post_process = post_process
return procedure
Find Target Range#
The ``find_target_range`` function calculates the cruise range of the mission by summing the distances of the climb and descent segments and subtracting this from the design range. It unpacks each of the segments from the mission and assigns each to its own variable. It then uses trigonometric functions to calculate the distance of each segment. Finally, it assigns the cruise range to the cruise segment.
[3]:
def find_target_range(nexus,mission):
segments = mission.segments
climb_1 = segments['climb_1']
climb_2 = segments['climb_2']
climb_3 = segments['climb_3']
descent_1 = segments['descent_1']
descent_2 = segments['descent_2']
descent_3 = segments['descent_3']
x_climb_1 = climb_1.altitude_end/np.tan(np.arcsin(climb_1.climb_rate/climb_1.air_speed))
x_climb_2 = (climb_2.altitude_end-climb_1.altitude_end)/np.tan(np.arcsin(climb_2.climb_rate/climb_2.air_speed))
x_climb_3 = (climb_3.altitude_end-climb_2.altitude_end)/np.tan(np.arcsin(climb_3.climb_rate/climb_3.air_speed))
x_descent_1 = (climb_3.altitude_end-descent_1.altitude_end)/np.tan(np.arcsin(descent_1.descent_rate/descent_1.air_speed))
x_descent_2 = (descent_1.altitude_end-descent_2.altitude_end)/np.tan(np.arcsin(descent_2.descent_rate/descent_2.air_speed))
x_descent_3 = (descent_2.altitude_end-descent_3.altitude_end)/np.tan(np.arcsin(descent_3.descent_rate/descent_3.air_speed))
cruise_range = mission.design_range-(x_climb_1+x_climb_2+x_climb_3+x_descent_1+x_descent_2+x_descent_3)
segments['cruise'].distance = cruise_range
return nexus
Design Mission#
Design mission takes the base mission and sets the design range to 1500 nmi. It then calls the find_target_range function to calculate the cruise range and evaluate the mission.
[4]:
def design_mission(nexus):
mission = nexus.missions.base
mission.design_range = 1500.*Units.nmi
find_target_range(nexus,mission)
results = nexus.results
results.base = mission.evaluate()
return nexus
Update Aircraft#
The update aircraft function updates the aircraft and engines based on the new airspeed and altitude from the otpimization problem.
It first unpacks the vehicle and new conditions which are used to calculate derived values including the differential pressure and mach number. These updated values are then used to redesign the enginesnd wing.
Overall, this function serves as the interface between the optimization problem and the aircraft design which is being adjusted.
[5]:
def update_aircraft(nexus):
configs = nexus.vehicle_configurations
base = configs.base
# find conditions
air_speed = nexus.missions.base.segments['cruise'].air_speed
altitude = nexus.missions.base.segments['climb_3'].altitude_end
atmosphere = RCAIDE.Framework.Analyses.Atmospheric.US_Standard_1976()
freestream = atmosphere.compute_values(altitude)
freestream0 = atmosphere.compute_values(6000.*Units.ft) #cabin altitude
diff_pressure = np.max(freestream0.pressure-freestream.pressure,0)
fuselage = base.fuselages['tube_fuselage']
fuselage.differential_pressure = diff_pressure
# now size engine
mach_number = air_speed/freestream.speed_of_sound
for config in configs:
config.wings.horizontal_stabilizer.areas.reference = (26.0/92.0)*config.wings.main_wing.areas.reference
for wing in config.wings:
wing = RCAIDE.Library.Methods.Geometry.Planform.wing_planform(wing)
wing.areas.exposed = 0.8 * wing.areas.wetted
wing.areas.affected = 0.6 * wing.areas.reference
# redesign turbofan
for network in config.networks:
for propulsor in network.propulsors:
propulsor.design_mach_number = mach_number
design_turbofan(propulsor)
return nexus
Weight#
This function recalculates the weight of the vehicle using the Weights_Transport analysis. The first line unpacks the vehicle from the optimization container, nexus, and then runs this new vehicle through the weight analysis.
[6]:
def weight(nexus):
vehicle = nexus.vehicle_configurations.base
weight_analysis = RCAIDE.Framework.Analyses.Weights.Transport()
weight_analysis.vehicle = vehicle
weight = weight_analysis.evaluate()
return nexus
[ ]:
Post Process Results#
The ``post_process`` function takes the optimization problem and its results and calculates various parameters including x_zero_fuel_margin and base_mission_fuelburn which are appended to the summary section of nexus. Nexus is then returned.
[7]:
def post_process(nexus):
# Unpack data
vehicle = nexus.vehicle_configurations.base
results = nexus.results
summary = nexus.summary
nexus.total_number_of_iterations +=1
#throttle in design mission
max_throttle = 0
for i in range(len(results.base.segments)):
for network in results.base.segments[i].analyses.vehicle.networks:
for j , propulsor in enumerate(network.propulsors):
max_segment_throttle = np.max(results.base.segments[i].conditions.energy[propulsor.tag].throttle[:,0])
if max_segment_throttle > max_throttle:
max_throttle = max_segment_throttle
summary.max_throttle = max_throttle
# Fuel margin and base fuel calculations
design_landing_weight = results.base.segments[-1].conditions.weights.total_mass[-1]
design_takeoff_weight = vehicle.mass_properties.takeoff
zero_fuel_weight = vehicle.mass_properties.operating_empty
summary.max_zero_fuel_margin = (design_landing_weight - zero_fuel_weight)/zero_fuel_weight
summary.base_mission_fuelburn = design_takeoff_weight - results.base.segments['descent_3'].conditions.weights.total_mass[-1]
return nexus