Source code for RCAIDE.Library.Methods.Powertrain.Converters.Rotor.Design.procedure_setup

# RCAIDE/Methods/Energy/Propulsors/Rotor_Design/procedure_setup.py
# 
# 
# Created:  Jul 2023, M. Clarke  

# ----------------------------------------------------------------------------------------------------------------------
#  IMPORT
# ----------------------------------------------------------------------------------------------------------------------  

# RCAIDE Imports 
import RCAIDE 
from RCAIDE.Framework.Core                                                        import Units  
from RCAIDE.Library.Methods.Noise.Frequency_Domain_Buildup.Rotor                  import compute_rotor_noise 
from RCAIDE.Framework.Analyses.Process                                            import Process   
from RCAIDE.Framework.Mission.Common                                              import Conditions
from RCAIDE.Library.Methods.Powertrain.Converters.Rotor.compute_rotor_performance import compute_rotor_performance 

# Python package imports   
import numpy as np 
import scipy as sp  
    
# ----------------------------------------------------------------------------------------------------------------------  
#  Procedure Setup 
# ----------------------------------------------------------------------------------------------------------------------    
[docs] def procedure_setup(): """ Creates a procedure for rotor blade optimization that defines the sequence of analysis steps. Parameters ---------- None Returns ------- procedure : RCAIDE.Framework.Analyses.Process Process object containing the following methods: - modify_rotor : function Updates blade geometry based on optimization variables - hover : function Analyzes rotor performance in hover conditions - oei : function Analyzes rotor performance in one-engine-inoperative conditions - cruise : function Analyzes rotor performance in cruise conditions (for prop-rotors) - post_process : function Processes results and computes objective function Notes ----- This function defines the sequence of analysis steps that are executed during each iteration of the rotor blade optimization process. The procedure follows a logical flow from geometry modification to performance analysis in different flight conditions, and finally to post-processing of results. The procedure includes the following steps: 1. modify_blade_geometry: Updates the blade chord and twist distributions based on the current optimization variables 2. run_rotor_hover: Analyzes the rotor performance in hover conditions, computing thrust, power, figure of merit, and noise 3. run_rotor_OEI: Analyzes the rotor performance in one-engine-inoperative conditions 4. run_rotor_cruise: Analyzes the rotor performance in cruise conditions (for prop-rotors) 5. post_process: Computes the objective function based on performance metrics and checks constraint violations Each step in the procedure accesses the Nexus object that contains the current state of the optimization, including the vehicle configurations, design variables, and previously computed results. **Major Assumptions** * The procedure assumes that the blade geometry is parameterized using the hyperparameter approach defined in the updated_blade_geometry function * Performance analysis includes both aerodynamic and acoustic metrics * The objective function balances performance and noise based on user-defined weights See Also -------- RCAIDE.Library.Methods.Powertrain.Converters.Rotor.Design.optimization_setup RCAIDE.Library.Methods.Powertrain.Converters.Rotor.compute_rotor_performance """ # size the base config procedure = Process() # mofify blade geometry procedure.modify_rotor = modify_blade_geometry # Run the rotor in hover procedure.hover = run_rotor_hover # Run the rotor in the oei condition procedure.oei = run_rotor_OEI # Run the rotor in cruise procedure.ruise = run_rotor_cruise # post process the results procedure.post_process = post_process return procedure
# ---------------------------------------------------------------------- # Update blade geometry # ----------------------------------------------------------------------
[docs] def modify_blade_geometry(nexus): """ Modifies geometry of prop-rotor blade Inputs: nexus - RCAIDE optmization framework with prop-rotor blade data structure [None] Outputs: procedure - optimization methodology [None] Assumptions: N/A Source: None """ # Pull out the vehicles vehicle_hover = nexus.vehicle_configurations.hover rotor_hover = vehicle_hover.networks.electric.propulsors.electric_rotor.rotor vehicle_oei = nexus.vehicle_configurations.oei rotor_oei = vehicle_oei.networks.electric.propulsors.electric_rotor.rotor if nexus.prop_rotor_flag: vehicle_cruise = nexus.vehicle_configurations.cruise rotor_cruise = vehicle_cruise.networks.electric.propulsors.electric_rotor.rotor airfoils = rotor_hover.airfoils a_loc = rotor_hover.airfoil_polar_stations # Update geometry of blade R = rotor_hover.tip_radius B = rotor_hover.number_of_blades r = rotor_hover.radius_distribution c = updated_blade_geometry(rotor_hover.radius_distribution/rotor_hover.tip_radius ,rotor_hover.chord_r,rotor_hover.chord_p,rotor_hover.chord_q,rotor_hover.chord_t) beta = updated_blade_geometry(rotor_hover.radius_distribution/rotor_hover.tip_radius ,rotor_hover.twist_r,rotor_hover.twist_p,rotor_hover.twist_q,rotor_hover.twist_t) # compute max thickness distribution blade_area = sp.integrate.cumulative_trapezoid(B*c, r-r[0]) sigma = blade_area[-1]/(np.pi*R**2) t_max = np.zeros(len(c)) t_c = np.zeros(len(c)) t_max = np.zeros(len(c)) if len(airfoils.keys())>0: for j,airfoil in enumerate(airfoils): a_geo = airfoil.geometry locs = np.where(np.array(a_loc) == j ) t_max[locs] = a_geo.max_thickness*c[locs] rotor_hover.chord_distribution = c rotor_hover.twist_distribution = beta rotor_hover.mid_chord_alignment = c/4. - c[0]/4. rotor_hover.max_thickness_distribution = t_max rotor_hover.thickness_to_chord = t_c rotor_hover.blade_solidity = sigma vehicle_hover.store_diff() rotor_oei.chord_distribution = rotor_hover.chord_distribution rotor_oei.twist_distribution = rotor_hover.twist_distribution rotor_oei.mid_chord_alignment = rotor_hover.mid_chord_alignment rotor_oei.max_thickness_distribution = rotor_hover.max_thickness_distribution rotor_oei.thickness_to_chord = rotor_hover.thickness_to_chord rotor_oei.blade_solidity = rotor_hover.blade_solidity vehicle_oei.store_diff() if nexus.prop_rotor_flag: rotor_cruise.chord_distribution = rotor_hover.chord_distribution rotor_cruise.twist_distribution = rotor_hover.twist_distribution rotor_cruise.mid_chord_alignment = rotor_hover.mid_chord_alignment rotor_cruise.max_thickness_distribution = rotor_hover.max_thickness_distribution rotor_cruise.thickness_to_chord = rotor_hover.thickness_to_chord rotor_cruise.blade_solidity = rotor_hover.blade_solidity vehicle_cruise.store_diff() return nexus
# ---------------------------------------------------------------------- # Update blade geometry # ----------------------------------------------------------------------
[docs] def updated_blade_geometry(chi,c_r,p,q,c_t): """ Computes planform function of twist and chord distributron using hyperparameters Inputs: chi - prop-rotor radius distribution [None] c_r - hyperparameter no. 1 [None] p - hyperparameter no. 2 [None] q - hyperparameter no. 3 [None] c_t - hyperparameter no. 4 [None] Outputs: x_lin - function distribution [None] Assumptions: N/A Source: Traub, Lance W., et al. "Effect of taper ratio at low reynolds number." Journal of Aircraft 52.3 (2015): 734-747. """ n = np.linspace(len(chi)-1,0,len(chi)) theta_n = n*(np.pi/2)/len(chi) y_n = chi[-1]*np.cos(theta_n) eta_n = np.abs(y_n/chi[-1]) x_cos = c_r*(1 - eta_n**p)**q + c_t*eta_n x_lin = np.interp(chi,eta_n, x_cos) return x_lin
# ---------------------------------------------------------------------- # Run the Rotor Hover # ----------------------------------------------------------------------
[docs] def run_rotor_hover(nexus): # Unpack network = nexus.vehicle_configurations.hover.networks.electric electric_rotor = network.propulsors.electric_rotor rotor = electric_rotor.rotor # Setup Test conditions alpha = rotor.optimization_parameters.multiobjective_aeroacoustic_weight speed = rotor.hover.design_freestream_velocity altitude = np.array([rotor.hover.design_altitude]) atmosphere = RCAIDE.Framework.Analyses.Atmospheric.US_Standard_1976() atmosphere_conditions = atmosphere.compute_values(altitude) segment = RCAIDE.Framework.Mission.Segments.Segment() conditions = RCAIDE.Framework.Mission.Common.Results() conditions.freestream.update(atmosphere_conditions) conditions.frames.inertial.velocity_vector = np.array([[0.,0.,speed]]) conditions.frames.body.transform_to_inertial = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., -1.]]]) conditions.frames.wind.transform_to_inertial = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]]) conditions.frames.planet.true_course = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]]) segment.state.conditions = conditions rotor.append_operating_conditions(segment,segment.state.conditions.energy,segment.state.conditions.noise) rotor_conditions = segment.state.conditions.energy.converters[rotor.tag] rotor_conditions.omega = (atmosphere_conditions.speed_of_sound*rotor.hover.design_tip_mach)/rotor.tip_radius rotor_conditions.blade_pitch_command[:,0] = rotor.hover.design_blade_pitch_command compute_rotor_performance(rotor,conditions) # Pack the results nexus.results.hover.thrust = -segment.state.conditions.energy.converters[rotor.tag].thrust[0,2] nexus.results.hover.torque = segment.state.conditions.energy.converters[rotor.tag].torque[0][0] nexus.results.hover.power = segment.state.conditions.energy.converters[rotor.tag].power[0][0] nexus.results.hover.power_c = segment.state.conditions.energy.converters[rotor.tag].power_coefficient[0][0] nexus.results.hover.thurst_c = segment.state.conditions.energy.converters[rotor.tag].thrust_coefficient[0][0] nexus.results.hover.omega = segment.state.conditions.energy.converters[rotor.tag].omega[0][0] nexus.results.hover.max_sectional_cl = np.max(segment.state.conditions.energy.converters[rotor.tag].lift_coefficient[0]) nexus.results.hover.mean_CL = np.mean(segment.state.conditions.energy.converters[rotor.tag].lift_coefficient[0]) nexus.results.hover.figure_of_merit = segment.state.conditions.energy.converters[rotor.tag].figure_of_merit[0][0] nexus.results.hover.efficiency = segment.state.conditions.energy.converters[rotor.tag].efficiency[0][0] nexus.results.hover.conditions = conditions # microphone locations ctrl_pts = 1 theta = rotor.optimization_parameters.noise_evaluation_angle S_hover = np.maximum(altitude[0],20*Units.feet) mic_positions_hover = np.array([[0.0 , S_hover*np.sin(theta) ,S_hover*np.cos(theta)]]) # Run noise model conditions.noise.relative_microphone_locations = np.repeat(mic_positions_hover[ np.newaxis,:,: ],1,axis=0) conditions.aerodynamics.angles.alpha = np.ones((ctrl_pts,1))* 0. * Units.degrees segment = RCAIDE.Framework.Mission.Segments.Segment() segment.state.conditions = conditions segment.state.conditions.expand_rows(ctrl_pts) noise = RCAIDE.Framework.Analyses.Noise.Frequency_Domain_Buildup() settings = noise.settings num_mic = len(conditions.noise.relative_microphone_locations[0]) conditions.noise.number_of_microphones = num_mic if alpha != 1: compute_rotor_noise(conditions.noise.relative_microphone_locations,rotor,segment,settings) nexus.results.hover.mean_SPL = np.mean(conditions.noise.converters[rotor.tag].SPL_dBA) else: nexus.results.hover.mean_SPL = 0 return nexus
# ---------------------------------------------------------------------- # One Engine Inoperative # ----------------------------------------------------------------------
[docs] def run_rotor_OEI(nexus): # Unpack network = nexus.vehicle_configurations.oei.networks.electric electric_rotor = network.propulsors.electric_rotor rotor = electric_rotor.rotor # Setup Test conditions speed = rotor.oei.design_freestream_velocity altitude = np.array([rotor.oei.design_altitude]) atmosphere = RCAIDE.Framework.Analyses.Atmospheric.US_Standard_1976() atmosphere_conditions = atmosphere.compute_values(altitude) segment = RCAIDE.Framework.Mission.Segments.Segment() conditions = RCAIDE.Framework.Mission.Common.Results() conditions.freestream.update(atmosphere_conditions) conditions.frames.inertial.velocity_vector = np.array([[0.,0.,speed]]) conditions.frames.body.transform_to_inertial = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., -1.]]]) conditions.frames.wind.transform_to_inertial = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]]) conditions.frames.planet.true_course = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]]) segment.state.conditions = conditions rotor.append_operating_conditions(segment,segment.state.conditions.energy,segment.state.conditions.noise) rotor_conditions = segment.state.conditions.energy.converters[rotor.tag] rotor_conditions.omega = (atmosphere_conditions.speed_of_sound*rotor.oei.design_tip_mach)/rotor.tip_radius rotor_conditions.blade_pitch_command[:,0] = rotor.oei.design_blade_pitch_command compute_rotor_performance(rotor,conditions) # Pack the results nexus.results.oei.thrust = -segment.state.conditions.energy.converters[rotor.tag].thrust[0,2] nexus.results.oei.torque = segment.state.conditions.energy.converters[rotor.tag].torque[0][0] nexus.results.oei.power = segment.state.conditions.energy.converters[rotor.tag].power[0][0] nexus.results.oei.power_c = segment.state.conditions.energy.converters[rotor.tag].power_coefficient[0][0] nexus.results.oei.omega = segment.state.conditions.energy.converters[rotor.tag].omega[0][0] nexus.results.oei.thurst_c = segment.state.conditions.energy.converters[rotor.tag].thrust_coefficient[0][0] nexus.results.oei.efficiency = segment.state.conditions.energy.converters[rotor.tag].efficiency[0][0] nexus.results.oei.conditions = conditions return nexus
# ---------------------------------------------------------------------- # Run the Rotor Cruise # ----------------------------------------------------------------------
[docs] def run_rotor_cruise(nexus): if nexus.prop_rotor_flag: network = nexus.vehicle_configurations.cruise.networks.electric electric_rotor = network.propulsors.electric_rotor rotor = electric_rotor.rotor alpha = rotor.optimization_parameters.multiobjective_aeroacoustic_weight # Setup Test conditions speed = rotor.cruise.design_freestream_velocity altitude = np.array([rotor.cruise.design_altitude]) atmosphere = RCAIDE.Framework.Analyses.Atmospheric.US_Standard_1976() atmosphere_conditions = atmosphere.compute_values(altitude) segment = RCAIDE.Framework.Mission.Segments.Segment() conditions = RCAIDE.Framework.Mission.Common.Results() conditions.freestream.update(atmosphere_conditions) conditions.frames.inertial.velocity_vector = np.array([[0.,0.,speed]]) conditions.frames.body.transform_to_inertial = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., -1.]]]) conditions.frames.wind.transform_to_inertial = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]]) conditions.frames.planet.true_course = np.array([[[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]]) segment.state.conditions = conditions rotor.append_operating_conditions(segment,segment.state.conditions.energy,segment.state.conditions.noise) rotor_conditions = segment.state.conditions.energy.converters[rotor.tag] rotor_conditions.omega = (atmosphere_conditions.speed_of_sound*rotor.cruise.design_tip_mach)/rotor.tip_radius rotor_conditions.blade_pitch_command[:,0] = rotor.cruise.design_blade_pitch_command compute_rotor_performance(rotor,conditions) # Pack the results nexus.results.cruise.thrust = -segment.state.conditions.energy.converters[rotor.tag].thrust[0,2] nexus.results.cruise.torque = segment.state.conditions.energy.converters[rotor.tag].torque[0][0] nexus.results.cruise.power = segment.state.conditions.energy.converters[rotor.tag].power[0][0] nexus.results.cruise.power_c = segment.state.conditions.energy.converters[rotor.tag].power_coefficient[0][0] nexus.results.cruise.omega = segment.state.conditions.energy.converters[rotor.tag].omega[0][0] nexus.results.cruise.thurst_c = segment.state.conditions.energy.converters[rotor.tag].thrust_coefficient[0][0] nexus.results.cruise.max_sectional_cl = np.max(segment.state.conditions.energy.converters[rotor.tag].lift_coefficient[0]) nexus.results.cruise.mean_CL = np.mean(segment.state.conditions.energy.converters[rotor.tag].lift_coefficient[0]) nexus.results.cruise.efficiency = segment.state.conditions.energy.converters[rotor.tag].efficiency[0][0] nexus.results.cruise.conditions = conditions # microphone locations ctrl_pts = 1 theta = rotor.optimization_parameters.noise_evaluation_angle S_cruise = np.maximum(altitude[0],20*Units.feet) mic_positions_cruise = np.array([[0.0 ,S_cruise*np.sin(theta) ,S_cruise*np.cos(theta)]]) # Run noise model conditions.noise.relative_microphone_locations = np.repeat(mic_positions_cruise[ np.newaxis,:,: ],1,axis=0) conditions.aerodynamics.angles.alpha = np.ones((ctrl_pts,1))* 0. * Units.degrees segment = RCAIDE.Framework.Mission.Segments.Segment() segment.state.conditions = conditions segment.state.conditions.expand_rows(ctrl_pts) noise = RCAIDE.Framework.Analyses.Noise.Frequency_Domain_Buildup() settings = noise.settings num_mic = len(conditions.noise.relative_microphone_locations[0]) conditions.noise.number_of_microphones = num_mic if alpha != 1: compute_rotor_noise(conditions.noise.relative_microphone_locations,rotor,segment,settings) nexus.results.cruise.mean_SPL = np.mean(conditions.noise.converters[rotor.tag].SPL_dBA) else: nexus.results.cruise.mean_SPL = 0 else: nexus.results.cruise.thrust = 0.0 nexus.results.cruise.torque = 0.0 nexus.results.cruise.power = 0.0 nexus.results.cruise.power_c = 0.0 nexus.results.cruise.thurst_c = 0.0 nexus.results.cruise.omega = 0.0 nexus.results.cruise.max_sectional_cl = 0.0 nexus.results.cruise.mean_CL = 0.0 nexus.results.cruise.efficiency = 0.0 nexus.results.cruise.mean_SPL = 0.0 nexus.results.cruise.noise_data = None return nexus
# ---------------------------------------------------------------------- # Post Process Results to give back to the optimizer # ----------------------------------------------------------------------
[docs] def post_process(nexus): rotor = nexus.vehicle_configurations.hover.networks.electric.propulsors.electric_rotor.rotor rotor_oei = nexus.vehicle_configurations.oei.networks.electric.propulsors.electric_rotor.rotor alpha = rotor.optimization_parameters.multiobjective_aeroacoustic_weight beta = rotor.optimization_parameters.multiobjective_performance_weight gamma = rotor.optimization_parameters.multiobjective_acoustic_weight ideal_SPL = rotor.optimization_parameters.ideal_SPL_dBA ideal_efficiency = rotor.optimization_parameters.ideal_efficiency ideal_FoM = rotor.optimization_parameters.ideal_figure_of_merit print_iter = nexus.print_iterations mean_CL_hover = nexus.results.hover.mean_CL omega_hover = nexus.results.hover.omega FM_hover = nexus.results.hover.figure_of_merit # q to p ratios summary = nexus.summary summary.max_sectional_cl_hover = nexus.results.hover.max_sectional_cl summary.chord_p_to_q_ratio = rotor.chord_p/rotor.chord_q summary.twist_p_to_q_ratio = rotor.twist_p/rotor.twist_q summary.blade_taper_constraint_1 = rotor.chord_distribution[-1]/rotor.chord_distribution[0] summary.blade_taper_constraint_2 = rotor.chord_distribution[-1]/rotor.chord_distribution[0] summary.blade_twist_constraint = rotor.twist_distribution [0] - rotor.twist_distribution [-1] summary.OEI_hover_thrust_power_residual = abs(nexus.results.oei.thrust - rotor.oei.design_thrust) / nexus.results.oei.thrust # thrust/power residuals if rotor.hover.design_thrust == None: summary.hover_thrust_power_residual = abs(nexus.results.hover.power - rotor.hover.design_power) / rotor.hover.design_power else: summary.hover_thrust_power_residual = abs(nexus.results.hover.thrust - rotor.hover.design_thrust) /rotor.hover.design_thrust # oei if rotor.oei.design_thrust == None: summary.oei_thrust_power_residual = abs(nexus.results.oei.power - rotor.oei.design_power) / rotor.oei.design_power else: summary.oei_thrust_power_residual = abs(nexus.results.oei.thrust - rotor.oei.design_thrust) /rotor.oei.design_thrust if nexus.prop_rotor_flag: if rotor.cruise.design_thrust == None: summary.cruise_thrust_power_residual = abs(nexus.results.cruise.power - rotor.cruise.design_power) /rotor.cruise.design_power else: summary.cruise_thrust_power_residual = abs(nexus.results.cruise.thrust - rotor.cruise.design_thrust) /rotor.cruise.design_thrust # ------------------------------------------------------- # OBJECTIVE FUNCTION # ------------------------------------------------------- performance_objective = ((ideal_FoM - FM_hover)/ideal_FoM)*beta + ((ideal_efficiency - nexus.results.cruise.efficiency)/ideal_efficiency)*(1-beta) acoustic_objective = ((nexus.results.hover.mean_SPL - ideal_SPL)/ideal_SPL)*gamma + ((nexus.results.cruise.mean_SPL - ideal_SPL)/ideal_SPL)*(1-gamma) summary.objective = (performance_objective*alpha + acoustic_objective*(1-alpha)) if nexus.prop_rotor_flag: rotor_cru = nexus.vehicle_configurations.cruise.networks.electric.propulsors.electric_rotor.rotor summary.max_sectional_cl_cruise = nexus.results.cruise.max_sectional_cl # ------------------------------------------------------- # PRINT ITERATION PERFOMRMANCE # ------------------------------------------------------- if print_iter: print("Aeroacoustic Weight : " + str(alpha)) print("Multiobj. Performance Weight : " + str(beta)) print("Multiobj. Acoustic Weight : " + str(gamma)) print("Performance Obj : " + str(performance_objective)) print("Acoustic Obj : " + str(acoustic_objective)) print("Aeroacoustic Obj : " + str(summary.objective)) print("Blade Taper : " + str(summary.blade_taper_constraint_1)) print("Hover RPM : " + str(omega_hover/Units.rpm)) if rotor.hover.design_thrust == None: print("Hover Power : " + str(nexus.results.hover.power)) if rotor.hover.design_power == None: print("Hover Thrust : " + str(nexus.results.hover.thrust)) print("Hover Average SPL : " + str(nexus.results.hover.mean_SPL)) print("Hover Tip Mach : " + str(rotor.hover.design_tip_mach)) print("Hover Thrust/Power Residual : " + str(summary.hover_thrust_power_residual)) print("Hover Figure of Merit : " + str(FM_hover)) print("Hover Max Sectional Cl : " + str(summary.max_sectional_cl_hover)) print("Hover Blade CL : " + str(mean_CL_hover)) print("OEI Thrust : " + str(nexus.results.oei.thrust)) print("OEI Thrust/Power Residual : " + str(summary.OEI_hover_thrust_power_residual)) print("OEI Tip Mach : " + str(rotor_oei.oei.design_tip_mach)) print("OEI Collective (deg) : " + str(rotor_oei.design_blade_pitch_command/Units.degrees)) if nexus.prop_rotor_flag: print("Cruise RPM : " + str(nexus.results.cruise.omega/Units.rpm)) print("Cruise Collective (deg) : " + str(rotor_cru.design_blade_pitch_command/Units.degrees)) if rotor_cru.cruise.design_thrust == None: print("Cruise Power : " + str(nexus.results.cruise.power)) if rotor_cru.cruise.design_power == None: print("Cruise Thrust : " + str(nexus.results.cruise.thrust)) print("Cruise Tip Mach : " + str(rotor_cru.cruise.design_tip_mach)) print("Cruise Thrust/Power Residual : " + str(summary.cruise_thrust_power_residual)) print("Cruise Efficiency : " + str(nexus.results.cruise.efficiency)) print("Cruise Max Sectional Cl : " + str(summary.max_sectional_cl_cruise)) print("Cruise Blade CL : " + str(nexus.results.cruise.mean_CL)) print("\n\n") return nexus