def test_get_atmosphere_error(): """Test if 'get_atmosphere' raise an error when altitude is outside range (0-84000m) """ with pytest.raises(ValueError): get_atmosphere(-10) with pytest.raises(ValueError): get_atmosphere(85000)
def calculate_cl(ref_area, alt, mach, mass, load_fact = 1.05): """ Function to clacluate the lif coefficient (cl) Function 'calculate_cl' return the lift coefficent value for the given input (Reference Area, Altitude, Mach Number, Mass and Load Factor) Source: * Demonstration can be found in: /CEASIOMpy/lib/CLCalculator/doc/Calculate_CL.pdf Args: ref_area (float): Reference area [m^2] alt (float): Altitude [m] mach (float): Mach number [-] mass (float): Aircraft mass [kg] load_fact (float): Load Factor [-] (1.05 by default) Returns: target_cl (float): Lift coefficent [unit] """ # Get atmosphere values at this altitude Atm = get_atmosphere(alt) GAMMA = 1.401 # Air heat capacity ratio [-] # Calculate lift coeffienct weight = mass * Atm.grav dyn_pres = 0.5 * GAMMA * Atm.pres * mach**2 target_cl = weight * load_fact / (dyn_pres * ref_area) log.info('A lift coefficent (CL) of %f has been calculated' % target_cl) return target_cl
def test_get_atmosphere_84000m(): """Test atmosphere values at 84000m (max from standard atmosphere)""" atm = get_atmosphere(84000) assert atm.temp == approx(188.65) assert atm.pres == approx(0.4359809588843071) assert atm.dens == approx(8.050981206963134e-06) assert atm.visc == approx(1.2693534953091123e-05) assert atm.sos == approx(275.34263714506693) assert atm.re_len_ma == approx(174.6383812958875) assert atm.grav == approx(9.553079513419783)
def test_get_atmosphere_10000m(): """Test atmosphere values at 10000m """ atm = get_atmosphere(10000) assert atm.temp == approx(223.15) assert atm.pres == approx(26436.267593807635) assert atm.dens == approx(0.4127065373832877) assert atm.visc == approx(1.468841666984977e-05) assert atm.sos == approx(299.463) assert atm.re_len_ma == approx(8414143) assert atm.grav == approx(9.775937052994681)
def test_get_atmosphere_0m(): """Test atmosphere values at 0m (sea level)""" atm = get_atmosphere(0) assert atm.temp == 288.15 assert atm.pres == 101325.0 assert atm.dens == 1.225 assert atm.visc == approx(1.81205671028e-05) assert atm.sos == approx(340.294) assert atm.re_len_ma == approx(23004811) assert atm.grav == 9.80665
def get_cl(cpacs_path, cpacs_out_path): """ Function to calculate CL requiered as a function of the parameter found in the CPACS file. Function 'get_cl' find input value in the CPACS file, calculate the requiered CL (with calculate_cl) and save the CL value in /cpacs/toolspecific/CEASIOMpy/aerodynamics/su2/targetCL Args: cpacs_path (str): Path to CPACS file cpacs_out_path (str): Path to CPACS output file """ tixi = open_tixi(cpacs_path) # XPath definition model_xpath = '/cpacs/vehicles/aircraft/model' ref_area_xpath = model_xpath + '/reference/area' mtom_xpath = model_xpath + '/analyses/massBreakdown/designMasses/mTOM/mass' range_xpath = '/cpacs/toolspecific/CEASIOMpy/ranges' cruise_alt_xpath = range_xpath + '/cruiseAltitude' cruise_mach_xpath = range_xpath + '/cruiseMach' load_fact_xpath = range_xpath + '/loadFactor' su2_xpath = '/cpacs/toolspecific/CEASIOMpy/aerodynamics/su2' # Requiered input data from CPACS ref_area = get_value(tixi, ref_area_xpath) mtom = get_value(tixi, mtom_xpath) # Requiered input data that could be replace by a default value if missing cruise_alt = get_value_or_default(tixi, cruise_alt_xpath, 12000.0) cruise_mach = get_value_or_default(tixi, cruise_mach_xpath, 0.78) load_fact = get_value_or_default(tixi, load_fact_xpath, 1.05) # Get atmosphere from cruise altitude Atm = get_atmosphere(cruise_alt) # CL calculation target_cl = calculate_cl(ref_area, cruise_alt, cruise_mach, mtom, load_fact) # Save TargetCL create_branch(tixi, su2_xpath) create_branch(tixi, su2_xpath + '/targetCL') create_branch(tixi, su2_xpath + '/fixedCL') tixi.updateDoubleElement(su2_xpath + '/targetCL', target_cl, '%g') tixi.updateTextElement(su2_xpath + '/fixedCL', 'YES') log.info('Target CL has been saved in the CPACS file') close_tixi(tixi, cpacs_out_path)
def estimate_skin_friction_coef(wetted_area, wing_area, wing_span, mach, alt): """ Return an estimation of skin friction drag coefficient. Function 'estimate_skin_friction_coef' gives an estimation of the skin friction drag coefficient, based on an empirical formala (see source). Source: * Gerard W. H. van Es. "Rapid Estimation of the Zero-Lift Drag Coefficient of Transport Aircraft", Journal of Aircraft, Vol. 39, No. 4 (2002), pp. 597-599. https://doi.org/10.2514/2.2997 Args: wetted_area (float): Wetted Area of the entire aircraft [m^2] wing_area (float): Main wing area [m^2] wing_span (float): Main wing span [m] mach (float): Cruise Mach number [-] alt (float): Aircraft altitude [m] Returns: cd0 (float): Drag coefficient due to skin friction [-] """ # Get atmosphere values at this altitude Atm = get_atmosphere(alt) kinetic_visc = Atm.visc / Atm.dens # Get speed from Mach Number speed = mach * Atm.sos # Reynolds number based on the ratio Wetted Area / Wing Span reynolds_number = (wetted_area / wing_span) * speed / kinetic_visc log.info('Reynolds number:' + str(round(reynolds_number))) # Skin friction coefficient, formula from source (see function description) cfe = 0.00258 + 0.00102 * math.exp(-6.28*1e-9*reynolds_number) \ + 0.00295 * math.exp(-2.01*1e-8*reynolds_number) log.info('Skin friction coefficient:' + str(round(cfe, 5))) # Drag coefficient due to skin friction cd0 = cfe * wetted_area / wing_area log.info("Skin friction drag coefficient: " + str(cd0)) return cd0
def create_config(cpacs_path, cpacs_out_path, su2_mesh_path, config_output_path): """ Function to create configuration file for SU2 calculation Function 'create_config' create an SU2 configuration file from SU2 mesh data (marker) and CPACS file specific related parameter (/toolSpecific). For all other infomation the value from the default SU2 configuration file are used. A new configuration file will be saved in /ToolOutput/ToolOutput.cfg Source: * SU2 configuration file template https://github.com/su2code/SU2/blob/master/config_template.cfg Args: cpacs_path (str): Path to CPACS file cpacs_out_path (str): Path to CPACS output file su2_mesh_path (str): Path to SU2 mesh config_output_path (str): Path to the output configuration file """ DEFAULT_CONFIG_PATH = MODULE_DIR + '/files/DefaultConfig_v6.cfg' # Get value from CPACS tixi = open_tixi(cpacs_path) su2_xpath = '/cpacs/toolspecific/CEASIOMpy/aerodynamics/su2' # Reference values ref_xpath = '/cpacs/vehicles/aircraft/model/reference' ref_len = get_value(tixi, ref_xpath + '/length') ref_area = get_value(tixi, ref_xpath + '/area') # Fixed CL parameters fixed_cl_xpath = su2_xpath + '/fixedCL' target_cl_xpath = su2_xpath + '/targetCL' tixi, fixed_cl = get_value_or_default(tixi, fixed_cl_xpath, 'NO') tixi, target_cl = get_value_or_default(tixi, target_cl_xpath, 1.0) if fixed_cl == 'NO': # Get value from the aeroMap (1 point) active_aeroMap_xpath = su2_xpath + '/aeroMapUID' aeroMap_uid = get_value(tixi, active_aeroMap_xpath) aeroMap_path = tixi.uIDGetXPath(aeroMap_uid) apm_path = aeroMap_path + '/aeroPerformanceMap' #State = get_states(tixi,apm_path) #alt = State.alt_list alt = get_value(tixi, apm_path + '/altitude') mach = get_value(tixi, apm_path + '/machNumber') aoa = get_value(tixi, apm_path + '/angleOfAttack') aos = get_value(tixi, apm_path + '/angleOfSideslip') else: range_xpath = '/cpacs/toolspecific/CEASIOMpy/ranges' cruise_alt_xpath = range_xpath + '/cruiseAltitude' cruise_mach_xpath = range_xpath + '/cruiseMach' # value corresponding to fix CL calulation aoa = 0.0 # Will not be used aos = 0.0 tixi, mach = get_value_or_default(tixi, cruise_mach_xpath, 0.78) tixi, alt = get_value_or_default(tixi, cruise_alt_xpath, 12000) Atm = get_atmosphere(alt) pressure = Atm.pres temp = Atm.temp # Settings settings_xpath = '/cpacs/toolspecific/CEASIOMpy/aerodynamics/su2/settings' max_iter_xpath = settings_xpath + '/maxIter' cfl_nb_xpath = settings_xpath + '/cflNumber' mg_level_xpath = settings_xpath + '/multigridLevel' tixi, max_iter = get_value_or_default(tixi, max_iter_xpath, 200) tixi, cfl_nb = get_value_or_default(tixi, cfl_nb_xpath, 1.0) tixi, mg_level = get_value_or_default(tixi, mg_level_xpath, 3) # Mesh Marker bc_wall_xpath = '/cpacs/toolspecific/CEASIOMpy/aerodynamics/su2/boundaryConditions/wall' bc_wall_list = get_mesh_marker(su2_mesh_path) tixi = create_branch(tixi, bc_wall_xpath) bc_wall_str = ';'.join(bc_wall_list) tixi.updateTextElement(bc_wall_xpath, bc_wall_str) close_tixi(tixi, cpacs_out_path) # Open default configuration file try: config_file_object = open(DEFAULT_CONFIG_PATH, 'r') config_file_lines = config_file_object.readlines() config_file_object.close() log.info('Default configuration file has been found and read') except Exception: log.exception('Problem to open or read default configuration file') # Create a dictionary with all the parameters from the default config file config_dict = {} for line in config_file_lines: if '=' in line: (key, val) = line.split('=') if val.endswith('\n'): val = val[:-1] config_dict[key] = val config_dict_modif = config_dict # General parmeters config_dict_modif['MESH_FILENAME'] = su2_mesh_path config_dict_modif['REF_LENGTH'] = ref_len config_dict_modif['REF_AREA'] = ref_area # Settings config_dict_modif['EXT_ITER'] = int(max_iter) config_dict_modif['CFL_NUMBER'] = cfl_nb config_dict_modif['MGLEVEL'] = int(mg_level) config_dict_modif['AOA'] = aoa config_dict_modif['SIDESLIP_ANGLE'] = aos config_dict_modif['MACH_NUMBER'] = mach config_dict_modif['FREESTREAM_PRESSURE'] = pressure config_dict_modif['FREESTREAM_TEMPERATURE'] = temp # If calculation at CL fix (AOA will not be taken into account) config_dict_modif['FIXED_CL_MODE'] = fixed_cl config_dict_modif['TARGET_CL'] = target_cl config_dict_modif['DCL_DALPHA'] = '0.1' config_dict_modif['UPDATE_ALPHA'] = '8' config_dict_modif['ITER_DCL_DALPHA'] = '80' # Mesh Marker bc_wall_str = '(' + ','.join(bc_wall_list) + ')' config_dict_modif['MARKER_EULER'] = bc_wall_str config_dict_modif['MARKER_FAR'] = ' (Farfield)' config_dict_modif['MARKER_SYM'] = ' (0)' config_dict_modif['MARKER_PLOTTING'] = bc_wall_str config_dict_modif['MARKER_MONITORING'] = bc_wall_str config_dict_modif['MARKER_MOVING'] = bc_wall_str # Change value if needed or add new parameters in the config file for key, value in config_dict_modif.items(): line_nb = 0 # Double loop! There is probably a possibility to do something better. for i, line in enumerate(config_file_lines): if '=' in line: (key_def, val_def) = line.split('=') if key == key_def: line_nb = i break if not line_nb: config_file_lines.append(str(key) + ' = ' + str(value) + '\n') else: if val_def != config_dict_modif[key]: config_file_lines[line_nb] = str(key) + ' = ' \ + str(config_dict_modif[key]) + '\n' config_file_new = open(config_output_path, 'w') config_file_new.writelines(config_file_lines) config_file_new.close() log.info('ToolOutput.cfg has been written in /ToolOutput.')
def generate_su2_config(cpacs_path, cpacs_out_path, wkdir): """Function to create SU2 confif file. Function 'generate_su2_config' reads data in the CPACS file and generate configuration files for one or multible flight conditions (alt,mach,aoa,aos) Source: * SU2 config template: https://github.com/su2code/SU2/blob/master/config_template.cfg Args: cpacs_path (str): Path to CPACS file cpacs_out_path (str):Path to CPACS output file wkdir (str): Path to the working directory """ # Get value from CPACS tixi = cpsf.open_tixi(cpacs_path) tigl = cpsf.open_tigl(tixi) # Get SU2 mesh path su2_mesh_xpath = '/cpacs/toolspecific/CEASIOMpy/filesPath/su2Mesh' su2_mesh_path = cpsf.get_value(tixi,su2_mesh_xpath) # Get reference values ref_xpath = '/cpacs/vehicles/aircraft/model/reference' ref_len = cpsf.get_value(tixi,ref_xpath + '/length') ref_area = cpsf.get_value(tixi,ref_xpath + '/area') ref_ori_moment_x = cpsf.get_value_or_default(tixi,ref_xpath+'/point/x',0.0) ref_ori_moment_y = cpsf.get_value_or_default(tixi,ref_xpath+'/point/y',0.0) ref_ori_moment_z = cpsf.get_value_or_default(tixi,ref_xpath+'/point/z',0.0) # Get SU2 settings settings_xpath = SU2_XPATH + '/settings' max_iter_xpath = settings_xpath + '/maxIter' max_iter = cpsf.get_value_or_default(tixi, max_iter_xpath,200) cfl_nb_xpath = settings_xpath + '/cflNumber' cfl_nb = cpsf.get_value_or_default(tixi, cfl_nb_xpath,1.0) mg_level_xpath = settings_xpath + '/multigridLevel' mg_level = cpsf.get_value_or_default(tixi, mg_level_xpath,3) # Mesh Marker bc_wall_xpath = SU2_XPATH + '/boundaryConditions/wall' bc_wall_list = su2f.get_mesh_marker(su2_mesh_path) cpsf.create_branch(tixi, bc_wall_xpath) bc_wall_str = ';'.join(bc_wall_list) tixi.updateTextElement(bc_wall_xpath,bc_wall_str) # Fixed CL parameters fixed_cl_xpath = SU2_XPATH + '/fixedCL' fixed_cl = cpsf.get_value_or_default(tixi, fixed_cl_xpath,'NO') target_cl_xpath = SU2_XPATH + '/targetCL' target_cl = cpsf.get_value_or_default(tixi, target_cl_xpath,1.0) if fixed_cl == 'NO': active_aeroMap_xpath = SU2_XPATH + '/aeroMapUID' aeromap_uid = cpsf.get_value(tixi,active_aeroMap_xpath) log.info('Configuration file for ""' + aeromap_uid + '"" calculation will be created.') # Get parameters of the aeroMap (alt,ma,aoa,aos) Param = apmf.get_aeromap(tixi,aeromap_uid) param_count = Param.get_count() if param_count >= 1: alt_list = Param.alt mach_list = Param.mach aoa_list = Param.aoa aos_list = Param.aos else: raise ValueError('No parametre have been found in the aeroMap!') else: # if fixed_cl == 'YES': log.info('Configuration file for fixed CL calculation will be created.') range_xpath = '/cpacs/toolspecific/CEASIOMpy/ranges' # Parameters fixed CL calulation param_count = 1 # These parameters will not be used aoa_list = [0.0] aos_list = [0.0] cruise_mach_xpath= range_xpath + '/cruiseMach' mach = cpsf.get_value_or_default(tixi,cruise_mach_xpath,0.78) mach_list = [mach] cruise_alt_xpath= range_xpath + '/cruiseAltitude' alt = cpsf.get_value_or_default(tixi,cruise_alt_xpath,12000) alt_list = [alt] aeromap_uid = 'aeroMap_fixedCL_SU2' description = 'AeroMap created for SU2 fixed CL value of: ' + str(target_cl) apmf.create_empty_aeromap(tixi, aeromap_uid, description) Parameters = apmf.AeroCoefficient() Parameters.alt = alt_list Parameters.mach = mach_list Parameters.aoa = aoa_list Parameters.aos = aos_list apmf.save_parameters(tixi,aeromap_uid,Parameters) tixi.updateTextElement(SU2_XPATH+ '/aeroMapUID',aeromap_uid) # Get and modify the default configuration file cfg = su2f.read_config(DEFAULT_CONFIG_PATH) # General parmeters cfg['REF_LENGTH'] = ref_len cfg['REF_AREA'] = ref_area cfg['REF_ORIGIN_MOMENT_X'] = ref_ori_moment_x cfg['REF_ORIGIN_MOMENT_Y'] = ref_ori_moment_y cfg['REF_ORIGIN_MOMENT_Z'] = ref_ori_moment_z # Settings cfg['INNER_ITER'] = int(max_iter) cfg['CFL_NUMBER'] = cfl_nb cfg['MGLEVEL'] = int(mg_level) # Fixed CL mode (AOA will not be taken into account) cfg['FIXED_CL_MODE'] = fixed_cl cfg['TARGET_CL'] = target_cl cfg['DCL_DALPHA'] = '0.1' cfg['UPDATE_AOA_ITER_LIMIT'] = '50' cfg['ITER_DCL_DALPHA'] = '80' # TODO: correct value for the 3 previous parameters ?? # Mesh Marker bc_wall_str = '(' + ','.join(bc_wall_list) + ')' cfg['MARKER_EULER'] = bc_wall_str cfg['MARKER_FAR'] = ' (Farfield)' # TODO: maybe make that a variable cfg['MARKER_SYM'] = ' (0)' # TODO: maybe make that a variable? cfg['MARKER_PLOTTING'] = bc_wall_str cfg['MARKER_MONITORING'] = bc_wall_str cfg['MARKER_MOVING'] = '( NONE )' # TODO: when do we need to define MARKER_MOVING? cfg['DV_MARKER'] = bc_wall_str # Parameters which will vary for the different cases (alt,mach,aoa,aos) for case_nb in range(param_count): cfg['MESH_FILENAME'] = su2_mesh_path alt = alt_list[case_nb] mach = mach_list[case_nb] aoa = aoa_list[case_nb] aos = aos_list[case_nb] Atm = get_atmosphere(alt) pressure = Atm.pres temp = Atm.temp cfg['MACH_NUMBER'] = mach cfg['AOA'] = aoa cfg['SIDESLIP_ANGLE'] = aos cfg['FREESTREAM_PRESSURE'] = pressure cfg['FREESTREAM_TEMPERATURE'] = temp cfg['ROTATION_RATE'] = '0.0 0.0 0.0' config_file_name = 'ConfigCFD.cfg' case_dir_name = ''.join(['Case',str(case_nb).zfill(2), '_alt',str(alt), '_mach',str(round(mach,2)), '_aoa',str(round(aoa,1)), '_aos',str(round(aos,1))]) case_dir_path = os.path.join(wkdir,case_dir_name) if not os.path.isdir(case_dir_path): os.mkdir(case_dir_path) config_output_path = os.path.join(wkdir,case_dir_name,config_file_name) su2f.write_config(config_output_path,cfg) # Damping derivatives damping_der_xpath = SU2_XPATH + '/options/clalculateDampingDerivatives' damping_der = cpsf.get_value_or_default(tixi,damping_der_xpath,False) if damping_der: rotation_rate_xpath = SU2_XPATH + '/options/rotationRate' rotation_rate = cpsf.get_value_or_default(tixi,rotation_rate_xpath,1.0) cfg['GRID_MOVEMENT'] = 'ROTATING_FRAME' cfg['ROTATION_RATE'] = str(rotation_rate) + ' 0.0 0.0' os.mkdir(os.path.join(wkdir,case_dir_name+'_dp')) config_output_path = os.path.join(wkdir,case_dir_name+'_dp',config_file_name) su2f.write_config(config_output_path,cfg) cfg['ROTATION_RATE'] = '0.0 ' + str(rotation_rate) + ' 0.0' os.mkdir(os.path.join(wkdir,case_dir_name+'_dq')) config_output_path = os.path.join(wkdir,case_dir_name+'_dq',config_file_name) su2f.write_config(config_output_path,cfg) cfg['ROTATION_RATE'] = '0.0 0.0 ' + str(rotation_rate) os.mkdir(os.path.join(wkdir,case_dir_name+'_dr')) config_output_path = os.path.join(wkdir,case_dir_name+'_dr',config_file_name) su2f.write_config(config_output_path,cfg) log.info('Damping derivatives cases directory has been created.') # Control surfaces deflections control_surf_xpath = SU2_XPATH + '/options/clalculateCotrolSurfacesDeflections' control_surf = cpsf.get_value_or_default(tixi,control_surf_xpath,False) if control_surf: # Get deformed mesh list su2_def_mesh_xpath = SU2_XPATH + '/availableDeformedMesh' if tixi.checkElement(su2_def_mesh_xpath): su2_def_mesh_list = cpsf.get_string_vector(tixi,su2_def_mesh_xpath) else: log.warning('No SU2 deformed mesh has been found!') su2_def_mesh_list = [] for su2_def_mesh in su2_def_mesh_list: mesh_path = os.path.join(wkdir,'MESH',su2_def_mesh) config_dir_path = os.path.join(wkdir,case_dir_name+'_'+su2_def_mesh.split('.')[0]) os.mkdir(config_dir_path) cfg['MESH_FILENAME'] = mesh_path config_file_name = 'ConfigCFD.cfg' config_output_path = os.path.join(wkdir,config_dir_path,config_file_name) su2f.write_config(config_output_path,cfg) # TODO: change that, but if it is save in tooloutput it will be erease by results... cpsf.close_tixi(tixi,cpacs_path)
def dynamic_stability_analysis(cpacs_path, cpacs_out_path): """Function to analyse a full Aeromap Function 'dynamic_stability_analysis' analyses longitudinal dynamic stability and directionnal dynamic. Args: cpacs_path (str): Path to CPACS file cpacs_out_path (str):Path to CPACS output file plot (boolean): Choise to plot graph or not Returns: (#TODO put that in the documentation) * Adrvertisements certifying if the aircraft is stable or Not * In case of longitudinal dynamic UNstability or unvalid test on data: - Plot cms VS aoa for constant Alt, Mach and different aos - Plot cms VS aoa for const alt and aos and different mach - plot cms VS aoa for constant mach, AOS and different altitudes * In case of directionnal dynamic UNstability or unvalid test on data: - Pcot cml VS aos for constant Alt, Mach and different aoa - Plot cml VS aos for const alt and aoa and different mach - plot cml VS aos for constant mach, AOA and different altitudes * Plot one graph of cruising angles of attack for different mach and altitudes Make the following tests: * Check the CPACS path * For longitudinal dynamic stability analysis: - If there is more than one angle of attack for a given altitude, mach, aos - If cml values are only zeros for a given altitude, mach, aos - If there one aoa value which is repeated for a given altitude, mach, aos * For directionnal dynamic stability analysis: - If there is more than one angle of sideslip for a given altitude, mach, aoa - If cms values are only zeros for a given altitude, mach, aoa - If there one aos value which is repeated for a given altitude, mach, aoa """ # XPATH definition aeromap_uid_xpath = DYNAMIC_ANALYSIS_XPATH + '/aeroMapUid' aircraft_class_xpath = DYNAMIC_ANALYSIS_XPATH + '/class' # Classes 1 2 3 4 small, heavy ... aircraft_cathegory_xpath = DYNAMIC_ANALYSIS_XPATH + '/category' # flight phase A B C selected_mass_config_xpath = DYNAMIC_ANALYSIS_XPATH + '/massConfiguration' longi_analysis_xpath = DYNAMIC_ANALYSIS_XPATH + '/instabilityModes/longitudinal' direc_analysis_xpath = DYNAMIC_ANALYSIS_XPATH + '/instabilityModes/lateralDirectional' show_plot_xpath = DYNAMIC_ANALYSIS_XPATH + '/showPlots' save_plot_xpath = DYNAMIC_ANALYSIS_XPATH + '/savePlots' model_xpath = '/cpacs/vehicles/aircraft/model' ref_area_xpath = model_xpath + '/reference/area' ref_length_xpath = model_xpath + '/reference/length' flight_qualities_case_xpath = model_xpath + '/analyses/flyingQualities/fqCase' masses_location_xpath = model_xpath + '/analyses/massBreakdown/designMasses' # aircraft_class_xpath = flight_qualities_case_xpath + '/class' # Classes 1 2 3 4 small, heavy ... # aircraft_cathegory_xpath = flight_qualities_case_xpath + '/cathegory' # flight phase A B C # Ask user flight path angles : gamma_e thrust_available = None # Thrust data are not available flight_path_angle_deg = [ 0 ] # [-15,-10,-5,0,5,10,15] # The user should have the choice to select them !!!!!!!!!!!!!!!!!!!! flight_path_angle = [ angle * (np.pi / 180) for angle in flight_path_angle_deg ] # flight_path_angle in [rad] tixi = cpsf.open_tixi(cpacs_path) # Get aeromap uid aeromap_uid = cpsf.get_value(tixi, aeromap_uid_xpath) log.info('The following aeroMap will be analysed: ' + aeromap_uid) # Mass configuration: (Maximum landing mass, Maximum ramp mass (the maximum weight authorised for the ground handling), Take off mass, Zero Fuel mass) mass_config = cpsf.get_value(tixi, selected_mass_config_xpath) log.info('The aircraft mass configuration used for analysis is: ' + mass_config) # Analyses to do : longitudinal / Lateral-Directional longitudinal_analysis = cpsf.get_value(tixi, longi_analysis_xpath) lateral_directional_analysis = False # lateral_directional_analysis = cpsf.get_value(tixi, direc_analysis_xpath ) # Plots configuration with Setting GUI show_plots = cpsf.get_value_or_default(tixi, show_plot_xpath, False) save_plots = cpsf.get_value_or_default(tixi, save_plot_xpath, False) mass_config_xpath = masses_location_xpath + '/' + mass_config if tixi.checkElement(mass_config_xpath): mass_xpath = mass_config_xpath + '/mass' I_xx_xpath = mass_config_xpath + '/massInertia/Jxx' I_yy_xpath = mass_config_xpath + '/massInertia/Jyy' I_zz_xpath = mass_config_xpath + '/massInertia/Jzz' I_xz_xpath = mass_config_xpath + '/massInertia/Jxz' else: raise ValueError( 'The mass configuration : {} is not defined in the CPACS file !!!'. format(mass_config)) s = cpsf.get_value( tixi, ref_area_xpath ) # Wing area : s for non-dimonsionalisation of aero data. mac = cpsf.get_value( tixi, ref_length_xpath ) # ref length for non dimensionalisation, Mean aerodynamic chord: mac, # TODO: check that b = s / mac # TODO: find a way to get that xh = 10 # distance Aircaft cg-ac_horizontal-tail-plane. m = cpsf.get_value(tixi, mass_xpath) # aircraft mass dimensional I_xx = cpsf.get_value(tixi, I_xx_xpath) # X inertia dimensional I_yy = cpsf.get_value(tixi, I_yy_xpath) # Y inertia dimensional I_zz = cpsf.get_value(tixi, I_zz_xpath) # Z inertia dimensional I_xz = cpsf.get_value(tixi, I_xz_xpath) # XZ inertia dimensional aircraft_class = cpsf.get_value( tixi, aircraft_class_xpath) # aircraft class 1 2 3 4 flight_phase = cpsf.get_string_vector( tixi, aircraft_cathegory_xpath)[0] # Flight phase A B C Coeffs = apmf.get_aeromap( tixi, aeromap_uid ) # Warning: Empty uID found! This might lead to unknown errors! alt_list = Coeffs.alt mach_list = Coeffs.mach aoa_list = Coeffs.aoa aos_list = Coeffs.aos cl_list = Coeffs.cl cd_list = Coeffs.cd cs_list = Coeffs.cs cml_list = Coeffs.cml cms_list = Coeffs.cms cmd_list = Coeffs.cmd dcsdrstar_list = Coeffs.dcsdrstar dcsdpstar_list = Coeffs.dcsdpstar dcldqstar_list = Coeffs.dcldqstar dcmsdqstar_list = Coeffs.dcmsdqstar dcddqstar_list = Coeffs.dcddqstar dcmldqstar_list = Coeffs.dcmldqstar dcmddpstar_list = Coeffs.dcmddpstar dcmldpstar_list = Coeffs.dcmldpstar dcmldrstar_list = Coeffs.dcmldrstar dcmddrstar_list = Coeffs.dcmddrstar # All different vallues with only one occurence alt_unic = get_unic(alt_list) mach_unic = get_unic(mach_list) aos_unic = get_unic(aos_list) aoa_unic = get_unic(aoa_list) # TODO get from CPACS incrementalMap = False for alt in alt_unic: idx_alt = [i for i in range(len(alt_list)) if alt_list[i] == alt] Atm = get_atmosphere(alt) g = Atm.grav a = Atm.sos rho = Atm.dens for mach in mach_unic: print('Mach : ', mach) idx_mach = [ i for i in range(len(mach_list)) if mach_list[i] == mach ] u0, m_adim, i_xx, i_yy, i_zz, i_xz = adimensionalise( a, mach, rho, s, b, mac, m, I_xx, I_yy, I_zz, I_xz) # u0 is V0 in Cook # Hyp: trim condition when: ( beta = 0 and dCm/dalpha = 0) OR ( aos=0 and dcms/daoa = 0 ) if 0 not in aos_unic: log.warning( 'The aircraft can not be trimmed (requiring symetric flight condition) as beta never equal to 0 for Alt = {}, mach = {}' .format(alt, mach)) else: idx_aos = [i for i in range(len(aos_list)) if aos_list[i] == 0] find_index = get_index(idx_alt, idx_mach, idx_aos) # If there is only one data at (alt, mach, aos) then dont make stability anlysis if len(find_index) <= 1: log.warning( 'Not enough data at : Alt = {} , mach = {}, aos = 0, can not perform stability analysis' .format(alt, mach)) # If there is at leat 2 data at (alt, mach, aos) then, make stability anlysis else: # Calculate trim conditions cms = [] aoa = [] cl = [] for index in find_index: cms.append(cms_list[index]) aoa.append(aoa_list[index] * np.pi / 180) cl.append(cl_list[index]) cl_required = (m * g) / (0.5 * rho * u0**2 * s) (trim_aoa, idx_trim_before, idx_trim_after, ratio) = trim_condition( alt, mach, cl_required, cl, aoa, ) if trim_aoa: trim_aoa_deg = trim_aoa * 180 / np.pi trim_cms = interpolation(cms, idx_trim_before, idx_trim_after, ratio) pitch_moment_derivative_rad = ( cms[idx_trim_after] - cms[idx_trim_before]) / ( aoa[idx_trim_after] - aoa[idx_trim_before]) pitch_moment_derivative_deg = pitch_moment_derivative_rad / ( 180 / np.pi) # Find incremental cms if incrementalMap: for index, mach_number in enumerate(mach_unic, 0): if mach_number == mach: mach_index = index dcms_before = dcms_list[mach_index * len(aoa_unic) + idx_trim_before] dcms_after = dcms_list[mach_index * len(aoa_unic) + idx_trim_after] dcms = dcms_before + ratio * (dcms_after - dcms_before) trim_elevator = -trim_cms / dcms # Trim elevator deflection in [°] else: dcms = None trim_elevator = None else: trim_aoa_deg = None trim_cms = None pitch_moment_derivative_deg = None dcms = None trim_elevator = None # Longitudinal dynamic stability, # Stability analysis if longitudinal_analysis and trim_cms: cl = [] cd = [] dcldqstar = [] dcddqstar = [] dcmsdqstar = [] for index in find_index: cl.append(cl_list[index]) cd.append(cd_list[index]) dcldqstar.append(dcldqstar_list[index]) dcddqstar.append(dcddqstar_list[index]) dcmsdqstar.append(dcmsdqstar_list[index]) # Trimm variables cd0 = interpolation(cd, idx_trim_before, idx_trim_after, ratio) # Dragg coeff at trim cl0 = interpolation(cl, idx_trim_before, idx_trim_after, ratio) # Lift coeff at trim cl_dividedby_cd_trim = cl0 / cd0 # cl/cd ratio at trim, at trim aoa # Lift & drag coefficient derivative with respect to AOA at trimm cl_alpha0 = (cl[idx_trim_after] - cl[idx_trim_before] ) / (aoa[idx_trim_after] - aoa[idx_trim_before]) cd_alpha0 = (cd[idx_trim_after] - cd[idx_trim_before] ) / (aoa[idx_trim_after] - aoa[idx_trim_before]) print(idx_trim_before, idx_trim_after, ratio) dcddqstar0 = interpolation(dcddqstar, idx_trim_before, idx_trim_after, ratio) # x_q dcldqstar0 = interpolation(dcldqstar, idx_trim_before, idx_trim_after, ratio) # z_q dcmsdqstar0 = interpolation(dcmsdqstar, idx_trim_before, idx_trim_after, ratio) # m_q cm_alpha0 = trim_cms # Speed derivatives if there is at least 2 distinct mach values if len(mach_unic) >= 2: dcddm0 = speed_derivative_at_trim( cd_list, mach, mach_list, mach_unic, idx_alt, aoa_list, aos_list, idx_trim_before, idx_trim_after, ratio) if dcddm0 == None: dcddm0 = 0 log.warning( 'Not enough data to determine dcddm or (Cd_mach) at trim condition at Alt = {}, mach = {}, aoa = {}, aos = 0. Assumption: dcddm = 0' .format(alt, mach, round(trim_aoa_deg, 2))) dcldm0 = speed_derivative_at_trim( cl_list, mach, mach_list, mach_unic, idx_alt, aoa_list, aos_list, idx_trim_before, idx_trim_after, ratio) if dcldm0 == None: dcldm0 = 0 log.warning( 'Not enough data to determine dcldm (Cl_mach) at trim condition at Alt = {}, mach = {}, aoa = {}, aos = 0. Assumption: dcldm = 0' .format(alt, mach, round(trim_aoa_deg, 2))) else: dcddm0 = 0 dcldm0 = 0 log.warning( 'Not enough data to determine dcddm (Cd_mach) and dcldm (Cl_mach) at trim condition at Alt = {}, mach = {}, aoa = {}, aos = 0. Assumption: dcddm = dcldm = 0' .format(alt, mach, round(trim_aoa_deg, 2))) # Controls Derivatives to be found in the CPACS (To be calculated) dcddeta0 = 0 dcldeta0 = 0 dcmsdeta0 = 0 dcddtau0 = 0 dcldtau0 = 0 dcmsdtau0 = 0 # Traduction Ceasiom -> Theory Ue = u0 * np.cos( trim_aoa ) # *np.cos(aos) as aos = 0 at trim, cos(aos)=1 We = u0 * np.sin( trim_aoa ) # *np.cos(aos) as aos = 0 at trim, cos(aos)=1 # Dimentionless State Space variables, # In generalised body axes coordinates , # simplifications: Ue=V0, We=0, sin(Theta_e)=0 cos(Theta_e)=0 if thrust_available: # If power data X_u = -(2 * cd0 + mach * dcddm0) + 1 / ( 0.5 * rho * s * a ^ 2 ) * dtaudm0 # dtaudm dimensional Thrust derivative at trim conditions, P340 Michael V. Cook else: # Glider Mode X_u = -(2 * cd0 + mach * dcddm0) Z_u = -(2 * cl0 + mach * dcldm0) M_u = 0 # Negligible for subsonic conditions or better with P289 Yechout (cm_u+2cm0) X_w = (cl0 - cd_alpha0) Z_w = -(cl_alpha0 + cd0) M_w = cm_alpha0 X_q = dcddqstar0 # Normally almost = 0 Z_q = dcldqstar0 M_q = -dcmsdqstar0 X_dotw = 0 # Negligible Z_dotw = 1 / 3 * M_q / u0 / ( xh / mac ) # Thumb rule : M_alpha_dot = 1/3 Mq , ( not true for 747 :caughey P83,M_alpha_dot = 1/6Mq ) M_dotw = 1 / 3 * M_q / u0 # Thumb rule : M_alpha_dot = 1/3 Mq # Controls: X_eta = dcddeta0 # To be found from the cpacs file, and defined by the user! Z_eta = dcldeta0 # To be found from the cpacs file, and defined by the user! M_eta = dcmsdeta0 # To be found from the cpacs file, and defined by the user! X_tau = dcddtau0 # To be found from the cpacs file, and defined by the user! Z_tau = dcldtau0 # To be found from the cpacs file, and defined by the user! M_tau = dcmsdtau0 # To be found from the cpacs file, and defined by the user! # ----------------- Traduction Ceasiom -> Theory END ----------------------------------- # Sign check (Ref: Thomas Yechout Book, P304) check_sign_longi(cd_alpha0, M_w, cl_alpha0, M_dotw, Z_dotw, M_q, Z_q, M_eta, Z_eta) # Laterl-Directional if lateral_directional_analysis: cml = [] # N cmd = [] # L aos = [] aoa = [] # For Ue We cs = [] # For y_v dcsdpstar = [] # y_p dcmddpstar = [] # l_p dcmldpstar = [] # n_p dcsdrstar = [] # y_r dcmldrstar = [] # n_r dcmddrstar = [] # l_r for index in find_index: cml.append(cml_list[index]) # N , N_v cmd.append(cmd_list[index]) # L , L_v aos.append(aos_list[index] * np.pi / 180) aoa.append(aoa_list[index]) # For Ue We cs.append(cs_list[index]) dcsdpstar.append(dcsdpstar_list[index]) # y_p dcmddpstar.append(dcmddpstar_list[index]) # l_p dcmldpstar.append(dcmldpstar_list[index]) # n_p dcsdrstar.append(dcsdrstar_list[index]) # y_r dcmldrstar.append(dcmldrstar_list[index]) # n_r dcmddrstar.append(dcmddrstar_list[index]) # l_r #Trimm condition calculation # speed derivatives : y_v / l_v / n_v / Must be devided by speed given that the hyp v=Beta*U if len(aos_unic) >= 2: print('Mach : ', mach, ' and idx_mach : ', idx_mach) cs_beta0 = speed_derivative_at_trim_lat( cs_list, aos_list, aos_unic, idx_alt, idx_mach, aoa_list, idx_trim_before, idx_trim_after, ratio) # y_v if cs_beta0 == None: cs_beta0 = 0 log.warning( 'Not enough data to determine cs_beta (Y_v) at trim condition at Alt = {}, mach = {}, aoa = {}, aos = 0. Assumption: cs_beta = 0' .format(alt, mach, round(trim_aoa_deg, 2))) cmd_beta0 = speed_derivative_at_trim_lat( cmd_list, aos_list, aos_unic, idx_alt, idx_mach, aoa_list, idx_trim_before, idx_trim_after, ratio) # l_v if cmd_beta0 == None: cmd_beta0 = 0 log.warning( 'Not enough data to determine cmd_beta (L_v) at trim condition at Alt = {}, mach = {}, aoa = {}, aos = 0. Assumption: cmd_beta = 0' .format(alt, mach, round(trim_aoa_deg, 2))) cml_beta0 = speed_derivative_at_trim_lat( cml_list, aos_list, aos_unic, idx_alt, idx_mach, aoa_list, idx_trim_before, idx_trim_after, ratio) # n_v if cml_beta0 == None: cml_beta0 = 0 log.warning( 'Not enough data to determine cml_beta (N_v) at trim condition at Alt = {}, mach = {}, aoa = {}, aos = 0. Assumption: cml_beta = 0' .format(alt, mach, round(trim_aoa_deg, 2))) else: cs_beta0 = 0 cmd_beta0 = 0 cml_beta0 = 0 log.warning( 'Not enough data to determine cs_beta (Y_v), cmd_beta (L_v) and cml_beta (N_v) at trim condition at Alt = {}, mach = {}, aoa = {}, aos = 0. Assumption: cs_beta = cmd_beta = cml_beta = 0' .format(alt, mach, round(trim_aoa_deg, 2))) dcsdpstar0 = interpolation(dcsdpstar, idx_trim_before, idx_trim_after, ratio) # y_p dcmddpstar0 = interpolation(dcmddpstar, idx_trim_before, idx_trim_after, ratio) # l_p dcmldpstar0 = interpolation(dcmldpstar, idx_trim_before, idx_trim_after, ratio) # n_p dcsdrstar0 = interpolation(dcsdrstar, idx_trim_before, idx_trim_after, ratio) # y_r dcmldrstar0 = interpolation(dcmldrstar, idx_trim_before, idx_trim_after, ratio) # n_r dcmddrstar0 = interpolation(dcmddrstar, idx_trim_before, idx_trim_after, ratio) # l_r # TODO: calculate that and find in the cpacs dcsdxi0 = 0 dcmddxi0 = 0 dcmldxi0 = 0 dcsdzeta0 = 0 dcmddzeta0 = 0 dcmldzeta0 = 0 # Traduction Ceasiom -> Theory Y_v = cs_beta0 L_v = cmd_beta0 N_v = cml_beta0 Y_p = -dcsdpstar0 * mac / b L_p = -dcmddpstar0 * mac / b N_p = dcmldpstar0 * mac / b Y_r = dcsdrstar0 * mac / b N_r = -dcmldrstar0 * mac / b # mac/b :Because coefficients in ceasiom are nondimensionalised by the mac instead of the span L_r = dcmddrstar0 * mac / b # Controls: # Ailerons Y_xi = dcsdxi0 # To be found from the cpacs file, and defined by the user! L_xi = dcmddxi0 # To be found from the cpacs file, and defined by the user! N_xi = dcmldxi0 # To be found from the cpacs file, and defined by the user! # Rudder Y_zeta = dcsdzeta0 # To be found from the cpacs file, and defined by the user! L_zeta = dcmddzeta0 # To be found from the cpacs file, and defined by the user! N_zeta = dcmldzeta0 # To be found from the cpacs file, and defined by the user! Ue = u0 * np.cos( trim_aoa ) # *np.cos(aos) as aos = 0 at trim, cos(aos)=1 We = u0 * np.sin( trim_aoa ) # *np.cos(aos) as aos = 0 at trim, cos(aos)=1 # Sign check (Ref: Thomas Yechout Book, P304) check_sign_lat(Y_v, L_v, N_v, Y_p, L_p, Y_r, L_r, N_r, L_xi, Y_zeta, L_zeta, N_zeta) if trim_aoa: for angles in flight_path_angle: theta_e = angles + trim_aoa if longitudinal_analysis: (A_longi, B_longi, x_u,z_u,m_u,x_w,z_w,m_w, x_q,z_q,m_q,x_theta,z_theta,m_theta,x_eta,z_eta,m_eta, x_tau,z_tau,m_tau)\ = concise_derivative_longi(X_u,Z_u,M_u,X_w,Z_w,M_w,\ X_q,Z_q,M_q,X_dotw,Z_dotw,M_dotw,X_eta,Z_eta,M_eta,\ X_tau,Z_tau,M_tau, g, theta_e, u0,We,Ue,mac,m_adim,i_yy) C_longi = np.identity(4) D_longi = np.zeros((4, 2)) # Identify longitudinal roots if longi_root_identification( A_longi )[0] == None: # If longitudinal root not complex conjugate raise warning and plot roots eg_value_longi = longi_root_identification( A_longi)[1] log.warning( 'Longi : charcateristic equation roots are not complex conjugate : {}' .format(eg_value_longi)) legend = [ 'Root1', 'Root2', 'Root3', 'Root4' ] plot_title = 'S-plane longitudinal characteristic equation roots at (Alt = {}, Mach= {}, trimed at aoa = {}°)'.format( alt, mach, trim_aoa) plot_splane(eg_value_longi, plot_title, legend, show_plots, save_plots) else: # Longitudinal roots are complex conjugate (sp1, sp2, ph1, ph2, eg_value_longi , eg_vector_longi, eg_vector_longi_magnitude)\ = longi_root_identification(A_longi) legend = ['sp1', 'sp2', 'ph1', 'ph2'] plot_title = 'S-plane longitudinal characteristic equation roots at (Alt = {}, Mach= {}, trimed at aoa = {}°)'.format( alt, mach, trim_aoa) plot_splane(eg_value_longi, plot_title, legend, show_plots, save_plots) # Modes parameters : damping ratio, frequence, CAP, time tou double amplitude Z_w_dimensional = Z_w * ( 0.5 * rho * s * u0**2 ) # Z_w* (0.5*rho*s*u0**2) is the dimensional form of Z_w, Z_w = -(cl_alpha0 + cd0) P312 Yechout z_alpha = Z_w_dimensional * u0 / m # alpha = w/u0 hence, z_alpha = Z_w_dimensional * u0 [Newton/rad/Kg : m/s^2 /rad] load_factor = -z_alpha / g # number of g's/rad (1g/rad 2g/rad 3g/rad) (sp_freq, sp_damp, sp_cap, ph_freq, ph_damp, ph_t2)\ = longi_mode_characteristic(sp1,sp2,ph1,ph2,load_factor) # Rating sp_damp_rate = short_period_damping_rating( aircraft_class, sp_damp) sp_freq_rate = short_period_frequency_rating( flight_phase, aircraft_class, sp_freq, load_factor) # Plot SP freq vs Load factor legend = 'Alt = {}, Mach= {}, trim aoa = {}°'.format( alt, mach, trim_aoa) if flight_phase == 'A': plot_sp_level_a([load_factor], [sp_freq], legend, show_plots, save_plots) elif flight_phase == 'B': plot_sp_level_b( x_axis, y_axis, legend, show_plots, save_plots) else: plot_sp_level_c( x_axis, y_axis, legend, show_plots, save_plots) sp_cap_rate = cap_rating( flight_phase, sp_cap, sp_damp) ph_rate = phugoid_rating(ph_damp, ph_t2) # Raise warning if unstable mode in the log file if sp_damp_rate == None: log.warning( 'ShortPeriod UNstable at Alt = {}, Mach = {} , due to DampRatio = {} ' .format(alt, mach, round(sp_damp, 4))) if sp_freq_rate == None: log.warning( 'ShortPeriod UNstable at Alt = {}, Mach = {} , due to UnDampedFreq = {} rad/s ' .format(alt, mach, round(sp_freq, 4))) if sp_cap_rate == None: log.warning( 'ShortPeriod UNstable at Alt = {}, Mach = {} , with CAP evaluation, DampRatio = {} , CAP = {} ' .format(alt, mach, round(sp_damp, 4), round(sp_cap, 4))) if ph_rate == None: log.warning( 'Phugoid UNstable at Alt = {}, Mach = {} , DampRatio = {} , UnDampedFreq = {} rad/s' .format(alt, mach, round(ph_damp, 4), round(ph_freq, 4))) # TODO # Compute numerator TF for (Alt, mach, flight_path_angle, aoa_trim, aos=0 if lateral_directional_analysis: (A_direc, B_direc,y_v,l_v,n_v,y_p,y_phi,y_psi,l_p,l_phi,l_psi,n_p,y_r,l_r,n_r,n_phi,n_psi, y_xi,l_xi,n_xi, y_zeta,l_zeta,n_zeta)\ = concise_derivative_lat(Y_v,L_v,N_v,Y_p,L_p,N_p,Y_r,L_r,N_r,\ Y_xi,L_xi,N_xi, Y_zeta,L_zeta,N_zeta,\ g, b, theta_e, u0,We,Ue,m_adim,i_xx,i_zz,i_xz ) C_direc = np.identity(5) D_direc = np.zeros((5, 2)) if direc_root_identification( A_direc )[0] == None: # Lateral-directional roots are correctly identified eg_value_direc = direc_root_identification( A_direc)[1] print( 'Lat-Dir : charcateristic equation roots are not complex conjugate : {}' .format(eg_value_direc)) legend = [ 'Root1', 'Root2', 'Root3', 'Root4' ] plot_title = 'S-plane lateral characteristic equation roots at (Alt = {}, Mach= {}, trimed at aoa = {}°)'.format( alt, mach, trim_aoa) plot_splane(eg_value_direc, plot_title, legend, show_plots, save_plots) else: # Lateral-directional roots are correctly identified (roll, spiral, dr1, dr2, eg_value_direc, eg_vector_direc, eg_vector_direc_magnitude)\ = direc_root_identification(A_direc) legend = ['roll', 'spiral', 'dr1', 'dr2'] plot_title = 'S-plane lateralcharacteristic equation roots at (Alt = {}, Mach= {}, trimed at aoa = {}°)'.format( alt, mach, trim_aoa) plot_splane(eg_value_direc, plot_title, legend, show_plots, save_plots) (roll_timecst, spiral_timecst, spiral_t2, dr_freq, dr_damp, dr_damp_freq) = direc_mode_characteristic( roll, spiral, dr1, dr2) # Rating roll_rate = roll_rating( flight_phase, aircraft_class, roll_timecst) spiral_rate = spiral_rating( flight_phase, spiral_timecst, spiral_t2) dr_rate = dutch_roll_rating( flight_phase, aircraft_class, dr_damp, dr_freq, dr_damp_freq) # Raise warning in the log file if unstable mode if roll_rate == None: log.warning( 'Roll mode UNstable at Alt = {}, Mach = {} , due to roll root = {}, roll time contatant = {} s' .format(alt, mach, round(roll_root, 4), round(roll_timecst, 4))) if spiral_rate == None: log.warning( 'Spiral mode UNstable at Alt = {}, Mach = {} , spiral root = {}, time_double_ampl = {}' .format(alt, mach, round(spiral_root, 4), round(spiral_t2, 4))) if dr_rate == None: log.warning( 'Dutch Roll UNstable at Alt = {}, Mach = {} , Damping Ratio = {} , frequency = {} rad/s ' .format(alt, mach, round(dr_damp, 4), round(dr_freq, 4)))
def static_stability_analysis(cpacs_path, cpacs_out_path): """Function to analyse a full Aeromap Function 'static_stability_analysis' analyses longitudinal static static stability and directionnal static. Args: cpacs_path (str): Path to CPACS file cpacs_out_path (str):Path to CPACS output file plot (boolean): Choise to plot graph or not Returns: * Adrvertisements certifying if the aircraft is stable or Not * In case of longitudinal static UNstability or unvalid test on data: - Plot cms VS aoa for constant Alt, Mach and different aos - Plot cms VS aoa for const alt and aos=0 and different mach - plot cms VS aoa for constant mach, aos=0 and different altitudes * In case of directionnal static UNstability or unvalid test on data: - Pcot cml VS aos for constant Alt, Mach and different aoa - Plot cml VS aos for const alt and aoa and different mach - plot cml VS aos for constant mach, AOA and different altitudes * Plot one graph of cruising angles of attack for different mach and altitudes Make the following tests: (To put in the documentation) * Check the CPACS path * For longitudinal static stability analysis: - If there is more than one angle of attack for a given altitude, mach, aos - If cml values are only zeros for a given altitude, mach, aos - If there one aoa value which is repeated for a given altitude, mach, aos * For directionnal static stability analysis: - If there is more than one angle of sideslip for a given altitude, mach, aoa - If cms values are only zeros for a given altitude, mach, aoa - If there one aos value which is repeated for a given altitude, mach, aoa """ # TODO: add as CPACS option plot_for_different_mach = False # To check Mach influence plot_for_different_alt = False # To check Altitude influence tixi = cpsf.open_tixi(cpacs_path) # Get aeromap uid aeromap_uid = cpsf.get_value(tixi, STATIC_ANALYSIS_XPATH + '/aeroMapUid') log.info('The following aeroMap will be analysed: ' + aeromap_uid) show_plots = cpsf.get_value_or_default( tixi, STATIC_ANALYSIS_XPATH + '/showPlots', False) save_plots = cpsf.get_value_or_default( tixi, STATIC_ANALYSIS_XPATH + '/savePlots', False) # Aircraft mass configuration selected_mass_config_xpath = STATIC_ANALYSIS_XPATH + '/massConfiguration' mass_config = cpsf.get_value(tixi, selected_mass_config_xpath) # TODO: use get value or default instead and deal with not mass config log.info('The aircraft mass configuration used for analysis is: ' + mass_config) model_xpath = '/cpacs/vehicles/aircraft/model' masses_location_xpath = model_xpath + '/analyses/massBreakdown/designMasses' mass_config_xpath = masses_location_xpath + '/' + mass_config if tixi.checkElement(mass_config_xpath): mass_xpath = mass_config_xpath + '/mass' m = cpsf.get_value(tixi, mass_xpath) # aircraft mass [Kg] else: raise ValueError( ' !!! The mass configuration : {} is not defined in the CPACS file !!!' .format(mass_config)) # Wing plane AREA. ref_area_xpath = model_xpath + '/reference/area' s = cpsf.get_value( tixi, ref_area_xpath ) # Wing area : s for non-dimonsionalisation of aero data. Coeffs = apmf.get_aeromap(tixi, aeromap_uid) Coeffs.print_coef_list() alt_list = Coeffs.alt mach_list = Coeffs.mach aoa_list = Coeffs.aoa aos_list = Coeffs.aos cl_list = Coeffs.cl cd_list = Coeffs.cd cml_list = Coeffs.cml cms_list = Coeffs.cms cmd_list = Coeffs.cmd alt_unic = get_unic(alt_list) mach_unic = get_unic(mach_list) aos_unic = get_unic(aos_list) aoa_unic = get_unic(aoa_list) # TODO: get incremental map from CPACS # Incremental map elevator incrementalMap = False # if increment map available # aeromap_xpath = tixi.uIDGetXPath(aeromap_uid) # dcms_xpath = aeromap_xpath + '/aeroPerformanceMap/incrementMaps/incrementMap' + ' ....to complete' # TODO from incremental map # dcms_list = [0.52649,0.53744,0.54827,0.55898,0.56955,0.58001,0.59033,0.6005,0.61053,0.62043,0.63018,0.63979,0.64926,0.65859,0.66777,0.67684,0.53495,0.54603,0.55699,0.56783,0.57854,0.58912,0.59957,0.60986,0.62002,0.63004,0.63991,0.64964,0.65923,0.66867,0.67798,0.68717,0.55,0.56131,0.5725,0.58357,0.59451,0.60531,0.61598,0.62649,0.63687,0.64709,0.65718,0.66712,0.67691,0.68658,0.69609,0.70548,0.57333,0.585,0.59655,0.60796,0.61925,0.63038,0.64138,0.65224,0.66294,0.67349,0.68389,0.69415,0.70427,0.71424,0.72408,0.7338,0.60814,0.62033,0.63239,0.6443,0.65607,0.6677,0.67918,0.6905,0.70168,0.7127,0.72357,0.7343,0.74488,0.75532,0.76563,0.77581,0.66057,0.6735,0.68628,0.69891,0.71139,0.72371,0.73588,0.74789,0.75974,0.77144,0.78298,0.79438,0.80562,0.81673,0.82772,0.83858,\ # 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ # -0.61653,-0.61254,-0.60842,-0.60419,-0.59988,-0.59549,-0.59105,-0.58658,-0.5821,-0.57762,-0.57318,-0.56879,-0.56447,-0.56025,-0.55616,-0.55221,-0.62605,-0.62194,-0.6177,-0.61336,-0.60894,-0.60444,-0.59988,-0.59531,-0.59072,-0.58614,-0.58159,-0.57711,-0.5727,-0.56841,-0.56423,-0.5602,-0.64293,-0.63862,-0.63418,-0.62963,-0.62499,-0.62029,-0.61554,-0.61076,-0.60598,-0.60123,-0.5965,-0.59185,-0.58728,-0.58282,-0.5785,-0.57433,-0.66906,-0.6644,-0.65963,-0.65475,-0.64978,-0.64476,-0.63971,-0.63461,-0.62954,-0.62449,-0.61949,-0.61456,-0.60973,-0.60503,-0.60048,-0.59609,-0.70787,-0.70268,-0.69739,-0.692,-0.68653,-0.68101,-0.67546,-0.66991,-0.66437,-0.65888,-0.65344,-0.6481,-0.64289,-0.63781,-0.6329,-0.62819,-0.76596,-0.75994,-0.75382,-0.74762,-0.74135,-0.73505,-0.72874,-0.72243,-0.71617,-0.70997,-0.70387,-0.69788,-0.69205,-0.68639,-0.68094,-0.67573] # dcms are given for a relative deflection of [-1,0,1] of the # # TODO get from CPACS # elevator_deflection = 15 # Gather trim aoa conditions trim_alt_longi_list = [] trim_mach_longi_list = [] trim_aoa_longi_list = [] trim_aos_longi_list = [] trim_legend_longi_list = [] trim_derivative_longi_list = [] # Gather trim aos_list for different Alt & mach , for aoa = 0 trim_alt_lat_list = [] trim_mach_lat_list = [] trim_aoa_lat_list = [] trim_aos_lat_list = [] trim_legend_lat_list = [] trim_derivative_lat_list = [] # Gather trim aos_list for different Alt & mach , for aoa = 0 trim_alt_direc_list = [] trim_mach_direc_list = [] trim_aoa_direc_list = [] trim_aos_direc_list = [] trim_legend_direc_list = [] trim_derivative_direc_list = [] # To store in cpacs result longi_unstable_cases = [] lat_unstable_cases = [] direc_unstable_cases = [] cpacs_stability_longi = 'True' cpacs_stability_lat = 'True' cpacs_stability_direc = 'True' # Aero analyses for all given altitude, mach and aos_list, over different for alt in alt_unic: Atm = get_atmosphere(alt) g = Atm.grav a = Atm.sos rho = Atm.dens # Find index of altitude which have the same value idx_alt = [i for i in range(len(alt_list)) if alt_list[i] == alt] # Prepar trim condition lists trim_alt_longi = [] trim_mach_longi = [] trim_aoa_longi = [] trim_aos_longi = [] trim_legend_longi = [] trim_derivative_longi = [] # Prepar trim condition lists trim_alt_lat = [] trim_mach_lat = [] trim_aoa_lat = [] trim_aos_lat = [] trim_legend_lat = [] trim_derivative_lat = [] # Prepar trim condition lists trim_alt_direc = [] trim_mach_direc = [] trim_aoa_direc = [] trim_aos_direc = [] trim_legend_direc = [] trim_derivative_direc = [] for mach in mach_unic: u0 = a * mach # Find index of mach which have the same value idx_mach = [ j for j in range(len(mach_list)) if mach_list[j] == mach ] # Longitudinal stability # Analyse in function of the angle of attack for given, alt, mach and aos_list # Prepare variables for plots plot_cms = [] plot_aoa = [] plot_legend = [] plot_title = r'Pitch moment coefficeint $C_M$ vs $\alpha$ @ Atl = ' + str( alt) + 'm, and Mach = ' + str(mach) xlabel = r'$\alpha$ [°]' ylabel = r'$C_M$ [-]' # Init for determining if it's an unstable case longitudinaly_stable = True # by default, cms don't cross 0 line crossed = False # Find index at which aos= 0 idx_aos = [j for j in range(len(aos_list)) if aos_list[j] == 0] find_idx = get_index(idx_alt, idx_mach, idx_aos) # If find_idx is empty an APM function would have corrected before # If there there is only one value in find_idx for a given Alt, Mach, aos_list, no analyse can be performed if len(find_idx) == 1: log.info('Longitudinal : only one data, one aoa(' +str(aoa_list[find_idx[0]]) \ + '), for Altitude = '+ str(alt) + '[m] , Mach = ' \ + str(mach) + ' and aos = 0 , no stability analyse will be performed' ) cpacs_stability_longi = 'NotCalculated' elif len(find_idx ) > 1: # if there is at least 2 values in find_idx : # Find all cms_list values for index corresonding to an altitude, a mach, an aos_list=0, and different aoa_list cms = [] aoa = [] cl = [] cd = [] for index in find_idx: cms.append(cms_list[index]) aoa.append(aoa_list[index]) cl.append(cl_list[index]) cd.append(cd_list[index]) # Save values which will be plot plot_cms.append(cms) plot_aoa.append(aoa) curve_legend = r'$\beta$ = 0°' plot_legend.append(curve_legend) # If all aoa values are the same while the calculating the derivative, a division by zero will be prevented. aoa_good = True for jj in range(len(aoa) - 1): if aoa[jj] == aoa[jj + 1]: aoa_good = False log.error( 'Alt = {} , at least 2 aoa values are equal in aoa list: {} at Mach= {}, aos = 0' .format(alt, aoa, mach)) break if aoa_good: # Required lift for level flight cl_required = (m * g) / (0.5 * rho * u0**2 * s) (trim_aoa, idx_trim_before, idx_trim_after, ratio) = trim_condition(alt, mach, cl_required, cl, aoa) if trim_aoa: trim_cms = interpolation(cms, idx_trim_before, idx_trim_after, ratio) pitch_moment_derivative_deg = ( cms[idx_trim_after] - cms[idx_trim_before]) / ( aoa[idx_trim_after] - aoa[idx_trim_before]) # Find incremental cms if incrementalMap: for index, mach_number in enumerate(mach_unic, 0): if mach_number == mach: mach_index = index dcms_before = dcms_list[mach_index * len(aoa_unic) + idx_trim_before] dcms_after = dcms_list[mach_index * len(aoa_unic) + idx_trim_after] dcms = dcms_before + ratio * (dcms_after - dcms_before) trim_elevator_relative_deflection = -trim_cms / dcms trim_elevator = trim_elevator_relative_deflection * elevator_deflection # Trim elevator deflection in [°] else: dcms = None trim_elevator = None else: trim_cms = None pitch_moment_derivative_deg = None dcms = None trim_elevator = None fig = plt.figure(figsize=(9, 3)) plot_title___ = r'$C_L$ and $C_m$ vs $\alpha$ at Mach = {}'.format( mach) plt.title(plot_title___, fontdict=None, loc='center', pad=None) plt.plot(aoa, cl, marker='o', markersize=4, linewidth=1) plt.plot(aoa, cms, marker='+', markerfacecolor='orange', markersize=12) plt.plot([aoa[0], aoa[-1]], [cl_required, cl_required], markerfacecolor='red', markersize=12) plt.legend([r'$C_L$', r'$C_M$', r'$C_{Lrequired}$']) ax = plt.gca() ax.annotate(r'$\alpha$ [°]', xy=(1, 0), ha='right', va='top', xycoords='axes fraction', fontsize=12) # ax.annotate('Coefficient', xy=(0,1), ha='left', va='center', xycoords='axes fraction', fontsize=12) plt.grid(True) if show_plots: plt.show() # Conclusion about stability, if the cms curve has crossed the 0 line and there is not 2 repeated aoa for the same alt, mach and aos. # if idx_trim_before != idx_trim_after allow to know if the cm curve crosses the 0 line. ' if idx_trim_before != idx_trim_after and aoa_good: if pitch_moment_derivative_deg < 0: log.info('Vehicle longitudinaly staticaly stable.') trim_alt_longi.append(alt) trim_mach_longi.append(mach) trim_aoa_longi.append(trim_aoa) trim_aos_longi.append(0) trim_derivative_longi.append( pitch_moment_derivative_deg) elif pitch_moment_derivative_deg == 0: longitudinaly_stable = False log.error( 'Alt = ' + str(alt) + 'Vehicle longitudinaly staticaly neutral stable.') else: #pitch_moment_derivative_deg > 0 longitudinaly_stable = False log.error( 'Alt = ' + str(alt) + 'Vehicle *NOT* longitudinaly staticaly stable.') # If not stable store the set [alt, mach, aos] at which the aircraft is unstable. if not longitudinaly_stable: longi_unstable_cases.append([alt, mach, 0]) # To write in the output CPACS that the aircraft is not longitudinaly stable cpacs_stability_longi = 'False' #PLot cms VS aoa for constant Alt, Mach and different aos if plot_cms: plot_multicurve(plot_cms, plot_aoa, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) ## LATERAL plot_cmd = [] plot_aos = [] plot_legend = [] plot_title = r'Roll moment coefficient $C_L$ vs $\beta$ @ Atl = ' + str( alt) + 'm, and Mach = ' + str(mach) xlabel = r'$\beta$ [°]' ylabel = r'$C_L$ [-]' # Init for determinig if it is an unstability case laterally_stable = True # Find INDEX for aoa in aoa_unic: # by default, don't cross 0 line crossed = False idx_aoa = [ j for j in range(len(aoa_list)) if aoa_list[j] == aoa ] find_idx = get_index(idx_alt, idx_mach, idx_aoa) # If find_idx is empty an APM function would have corrected before # If there there is only one value in find_idx for a given Alt, Mach, aos_list, no analyse can be performed if len(find_idx) == 1: log.info('Laterral-Directional : only one data, one aos (' + str(aos_list[find_idx[0]]) \ +'), for Altitude = '+str(alt)+'[m], Mach = ' \ + str(mach) + ' and aoa = ' + str(aoa) \ + ' no stability analyse performed') cpacs_stability_lat = 'NotCalculated' elif len(find_idx ) > 1: #if there is at least 2 values in find_idx cmd = [] aos = [] for index in find_idx: cmd.append( -cmd_list[index] ) # menus sign because cmd sign convention on ceasiom is the oposite as books convention aos.append(aos_list[index]) aos, cmd = order_correctly( aos, cmd) # To roder the lists with values for growing aos # If cmd Curve crosses th 0 line more than once na stability analysis can be performed curve_legend = r'$\alpha$ = ' + str(aoa) + r' °' # Save values which will be plotted plot_cmd.append(cmd) plot_aos.append(aos) plot_legend.append(curve_legend) aos_good = True for jj in range(len(aos) - 1): if aos[jj] == aos[jj + 1]: aos_good = False log.error( 'Alt = {} , at least 2 aos values are equal in aos list: {} , Mach= {}, aos = {}' .format(alt, aoa, mach, aos)) break if aos_good: cruise_aos, roll_moment_derivative, idx_trim_before, idx_trim_after, ratio = trim_derivative( alt, mach, cmd, aos) if idx_trim_before != idx_trim_after and aos_good: if roll_moment_derivative < 0: log.info('Vehicle laterally staticaly stable.') if aoa == 0: trim_alt_lat.append(alt) trim_mach_lat.append(mach) trim_aoa_lat.append(cruise_aos) trim_aos_lat.append(aoa) trim_derivative_lat.append( roll_moment_derivative) if roll_moment_derivative == 0: laterally_stable = False log.error( 'At alt = ' + str(alt) + 'Vehicle laterally staticaly neutral stable.') if roll_moment_derivative > 0: laterally_stable = False log.error( 'Alt = ' + str(alt) + 'Vehicle *NOT* laterally staticaly stable.') # If not stable store the set [alt, mach, aos] at which the aircraft is unstable. if not laterally_stable: lat_unstable_cases.append([alt, mach, aoa]) # To write in the output CPACS that the aircraft is not longitudinaly stable cpacs_stability_lat = 'False' #PLot cmd VS aos for constant alt, mach and different aoa if not stable if plot_cmd: plot_multicurve(plot_cmd, plot_aos, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) ## Directional plot_cml = [] plot_aos = [] plot_legend = [] plot_title = r'Yaw moment coefficient $C_N$ vs $\beta$ @ Atl = ' + str( alt) + 'm, and Mach = ' + str(mach) xlabel = r'$\beta$ [°]' ylabel = r'$C_N$ [-]' # Init for determinig if it is an unstability case dirrectionaly_stable = True # Find INDEX for aoa in aoa_unic: # by default, don't cross 0 line crossed = False idx_aoa = [ j for j in range(len(aoa_list)) if aoa_list[j] == aoa ] find_idx = get_index(idx_alt, idx_mach, idx_aoa) # If find_idx is empty an APM function would have corrected before # If there there is only one value in find_idx for a given Alt, Mach, aos_list, no analyse can be performed if len(find_idx) == 1: log.info('Laterral-Directional : only one data, one aos (' + str(aos_list[find_idx[0]]) \ +'), for Altitude = '+str(alt)+'[m], Mach = ' \ + str(mach) + ' and aoa = ' + str(aoa) \ + ' no stability analyse performed') cpacs_stability_direc = 'NotCalculated' elif len(find_idx ) > 1: #if there is at least 2 values in find_idx cml = [] aos = [] for index in find_idx: cml.append( -cml_list[index] ) # menus sign because cml sign convention on ceasiom is the oposite as books convention aos.append(aos_list[index]) aos, cml = order_correctly( aos, cml) # To order values with growing aos # If cml Curve crosses th 0 line more than once na stability analysis can be performed curve_legend = r'$\alpha$ = ' + str(aoa) + r' °' # Save values which will be plot plot_cml.append(cml) plot_aos.append(aos) plot_legend.append(curve_legend) aos_good = True for jj in range(len(aos) - 1): if aos[jj] == aos[jj + 1]: aos_good = False log.error( 'Alt = {} , at least 2 aos values are equal in aos list: {} , Mach= {}, aos = {}' .format(alt, aoa, mach, aos)) break if aos_good: cruise_aos, side_moment_derivative, idx_trim_before, idx_trim_after, ratio = trim_derivative( alt, mach, cml, aos) if idx_trim_before != idx_trim_after and aos_good: if side_moment_derivative > 0: log.info('Vehicle directionnaly staticaly stable.') if aoa == 0: trim_alt_direc.append(alt) trim_mach_direc.append(mach) trim_aoa_direc.append(cruise_aos) trim_aos_direc.append(aoa) trim_derivative_direc.append( side_moment_derivative) if side_moment_derivative == 0: dirrectionaly_stable = False log.error( 'At alt = ' + str(alt) + 'Vehicle directionnaly staticaly neutral stable.' ) if side_moment_derivative < 0: dirrectionaly_stable = False log.error( 'Alt = ' + str(alt) + 'Vehicle *NOT* directionnaly staticaly stable.' ) # If not stable store the set [alt, mach, aos] at which the aircraft is unstable. if not dirrectionaly_stable: direc_unstable_cases.append([alt, mach, aoa]) # To write in the output CPACS that the aircraft is not longitudinaly stable cpacs_stability_direc = 'False' # PLot cml VS aos for constant alt, mach and different aoa if not stable if plot_cml: plot_multicurve(plot_cml, plot_aos, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) # Add trim conditions for the given altitude (longi analysis) if trim_aoa_longi: trim_aoa_longi_list.append(trim_aoa_longi) trim_mach_longi_list.append(trim_mach_longi) trim_legend_longi_list.append('Altitude = ' + str(alt) + '[m]') trim_alt_longi_list.append(trim_alt_longi) trim_aos_longi_list.append(trim_aos_longi) trim_derivative_longi_list.append(trim_derivative_longi) if trim_aos_lat: trim_aos_lat_list.append(trim_aos_lat) trim_mach_lat_list.append(trim_mach_lat) trim_legend_lat_list.append('Alt = ' + str(alt) + '[m]') trim_alt_lat_list.append(trim_alt_lat) trim_aoa_lat_list.append(trim_aoa_lat) trim_derivative_lat_list.append(trim_derivative_lat) # Add trim conditions for the given altitude (direcanalysis) if trim_aos_direc: trim_aos_direc_list.append(trim_aos_direc) trim_mach_direc_list.append(trim_mach_direc) trim_legend_direc_list.append('Alt = ' + str(alt) + '[m]') trim_alt_direc_list.append(trim_alt_direc) trim_aoa_direc_list.append(trim_aoa_direc) trim_derivative_direc_list.append(trim_derivative_direc) # MACH PLOTS if plot_for_different_mach: # To check Altitude Mach ## LONGI # Plot cms vs aoa for const alt and aos = 0 and different mach idx_aos = [k for k in range(len(aos_list)) if aos_list[k] == 0] plot_cms = [] plot_aoa = [] plot_legend = [] plot_title = r'Pitch moment coefficient $C_M$ vs $\alpha$ @ Atl = ' + str( alt) + r'm, and $\beta$ = 0 °' xlabel = r'$\alpha$ [°]' ylabel = r'$C_M$ [-]' # Init for determinig if it is an unstability case longitudinaly_stable = True for mach in mach_unic: idx_mach = [ j for j in range(len(mach_list)) if mach_list[j] == mach ] find_idx = get_index(idx_alt, idx_aos, idx_mach) # If there is only one value in Find_idx # An error message has been already printed through the first part of the code # Check if it is an unstability case detected previously for combination in longi_unstable_cases: if combination[0] == alt and combination[ 1] == mach and combination[2] == aos: longitudinaly_stable = False # If there is at list 2 values in find_idx : if len(find_idx) > 1: # Find all cms_list values for index corresonding to an altitude, a mach, an aos_list=0, and different aoa_list cms = [] aoa = [] for index in find_idx: cms.append(cms_list[index]) aoa.append(aoa_list[index]) # Save values which will be plot plot_cms.append(cms) plot_aoa.append(aoa) curve_legend = 'Mach = ' + str(mach) plot_legend.append(curve_legend) #PLot cms VS aoa for constant Alt, aoa and different mach if plot_cms: plot_multicurve(plot_cms, plot_aoa, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) ## LATERAL # Plot cmd vs aos for const alt and aoa and different mach for aoa in aoa_unic: idx_aoa = [ k for k in range(len(aoa_list)) if aoa_list[k] == aoa ] plot_cmd = [] plot_aos = [] plot_legend = [] plot_title = r'Roll moment coefficiel $C_L$ vs $\beta$ @ Atl = ' + str( alt) + r'm, and $\alpha$= ' + str(aoa) + r' °' xlabel = r'$\beta$ [°]' ylabel = r'$C_L$ [-]' # Init for determinig if it is an unstability case laterally_stable = True for mach in mach_unic: idx_mach = [ j for j in range(len(mach_list)) if mach_list[j] == mach ] find_idx = get_index(idx_alt, idx_aoa, idx_mach) #If there is only one valur in find_idx # An error message has been already printed through the first part of the code # Check if it is an unstability case detected previously for combination in lat_unstable_cases: if combination[0] == alt and combination[ 1] == mach and combination[2] == aoa: laterally_stable = False # If there is at list 2 values in find_idx : if len(find_idx) > 1: # Find all cmd_list values for index corresonding to an altitude, a mach, an aos_list=0, and different aoa_list cmd = [] aos = [] for index in find_idx: cmd.append(-cmd_list[index]) aos.append(aos_list[index]) aos, cmd = order_correctly( aos, cmd) # To order values with growing aos # Save values which will be plot plot_cmd.append(cmd) plot_aos.append(aos) curve_legend = 'Mach = ' + str(mach) plot_legend.append(curve_legend) if plot_cmd: # Plot cmd VS aos for constant Alt, aoa and different mach plot_multicurve(plot_cmd, plot_aos, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) ## Directional # Plot cml vs aos for const alt and aoa and different mach for aoa in aoa_unic: idx_aoa = [ k for k in range(len(aoa_list)) if aoa_list[k] == aoa ] plot_cml = [] plot_aos = [] plot_legend = [] plot_title = r'Yaw moment coefficiel $C_N$ vs $\beta$ @ Atl = ' + str( alt) + r'm, and $\alpha$= ' + str(aoa) + r' °' xlabel = r'$\beta$ [°]' ylabel = r'$C_N$ [-]' # Init for determinig if it is an unstability case dirrectionaly_stable = True for mach in mach_unic: idx_mach = [ j for j in range(len(mach_list)) if mach_list[j] == mach ] find_idx = get_index(idx_alt, idx_aoa, idx_mach) #If there is only one valur in find_idx # An error message has been already printed through the first part of the code # Check if it is an unstability case detected previously for combination in direc_unstable_cases: if combination[0] == alt and combination[ 1] == mach and combination[2] == aoa: dirrectionaly_stable = False # If there is at list 2 values in find_idx : if len(find_idx) > 1: # Find all cml_list values for index corresonding to an altitude, a mach, an aos_list=0, and different aoa_list cml = [] aos = [] for index in find_idx: cml.append(-cml_list[index]) aos.append(aos_list[index]) aos, cml = order_correctly( aos, cml) # To order values with growing aos # Save values which will be plot plot_cml.append(cml) plot_aos.append(aos) curve_legend = 'Mach = ' + str(mach) plot_legend.append(curve_legend) if plot_cml: # Plot cml VS aos for constant Alt, aoa and different mach plot_multicurve(plot_cml, plot_aos, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) ############ MACH PLOTS END ########## # TRIM CONDITIONS PLOTS # Plot trim_aoa VS mach for different alt # If there is at least 1 element in list of trim conditions then, plot them if trim_derivative_longi_list: log.info('graph : trim aoa vs mach genrated') plot_multicurve(trim_aoa_longi_list, trim_mach_longi_list, trim_legend_longi_list, r'$\alpha_{trim}$ vs Mach', 'Mach', r'$\alpha_{trim}$ [°]', show_plots, save_plots) log.info('graph : pitch moment derivative at trim vs mach genrated') plot_multicurve(trim_derivative_longi_list, trim_mach_longi_list, trim_legend_longi_list, r'$C_{M_{\alpha trim}}$ vs Mach', 'Mach', r'$C_{M_{\alpha trim}}$ [1/°]', show_plots, save_plots) if trim_derivative_lat_list: log.info('graph : roll moment derivative at trim vs mach genrated') plot_multicurve(trim_derivative_lat_list, trim_mach_lat_list, trim_legend_lat_list, r'$C_{L_{\beta trim}}$vs Mach', 'Mach', r'$C_{L_{\beta trim}}$ [1/°]', show_plots, save_plots) if trim_derivative_direc_list: log.info('graph : yaw moment at trim vs mach genrated') plot_multicurve(trim_derivative_direc_list, trim_mach_direc_list, trim_legend_direc_list, r'$C_{N_{\beta trim}}$ vs Mach', 'Mach', r'$C_{N_{\beta trim}}$ [1/°]', show_plots, save_plots) # ALTITUDE PLOTS if plot_for_different_alt: # To check Altitude Influence # plot cms VS aoa for constant mach, aos= 0 and different altitudes: # Find index of altitude which have the same value idx_aos = [i for i in range(len(aos_list)) if aos_list[i] == 0] for mach in mach_unic: # Find index of mach which have the same value idx_mach = [ j for j in range(len(mach_list)) if mach_list[j] == mach ] # Prepare variables for plots plot_cms = [] plot_aoa = [] plot_legend = [] plot_title = r'Pitch moment coefficient $C_M$ vs $\alpha$ @ Mach = ' + str( mach) + r' and $\beta$ = 0°' xlabel = r'$\alpha$ [°]' ylabel = r'$C_M$ [-]' longitudinaly_stable = True # Find index of slip angle which have the same value for alt in alt_unic: idx_alt = [ j for j in range(len(alt_list)) if alt_list[j] == alt ] find_idx = get_index(idx_aos, idx_mach, idx_alt) # If find_idx is empty an APM function would have corrected before # If there is only one value in find_idx for a given Alt, Mach, aos_list, no analyse can be performed # An error message has been already printed through the first part of the code # Check if it is an unstability case detected previously for combination in longi_unstable_cases: if combination[0] == alt and combination[ 1] == mach and combination[2] == aos: longitudinaly_stable = False # If there is at list 2 values in find_idx : if len(find_idx) > 1: # Find all cms_list values for index corresonding to an altitude, a mach, an aos_list=0, and different aoa_list cms = [] aoa = [] for index in find_idx: cms.append(cms_list[index]) aoa.append(aoa_list[index]) # Save values which will be plot plot_cms.append(cms) plot_aoa.append(aoa) curve_legend = 'Altitude = ' + str(alt) + ' m' plot_legend.append(curve_legend) if plot_cms: # PLot cms VS aoa for constant Mach, aos and different Alt plot_multicurve(plot_cms, plot_aoa, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) ## Lateral # plot cmd VS aos for constant mach, aoa_list and different altitudes: for aoa in aoa_unic: # Find index of altitude which have the same value idx_aoa = [i for i in range(len(aoa_list)) if aoa_list[i] == aoa] for mach in mach_unic: # Find index of mach which have the same value idx_mach = [ j for j in range(len(mach_list)) if mach_list[j] == mach ] # Prepare variables for plots plot_cmd = [] plot_aos = [] plot_legend = [] plot_title = r'Roll moment coefficient $C_L$ vs $\beta$ @ Mach = ' + str( mach) + r' and $\alpha$= ' + str(aoa) + r' °' xlabel = r'$\beta$ [°]' ylabel = r'$C_L$ [-]' laterally_stable = True # Find index of slip angle which have the same value for alt in alt_unic: idx_alt = [ j for j in range(len(alt_list)) if alt_list[j] == alt ] find_idx = get_index(idx_aoa, idx_mach, idx_alt) # If find_idx is empty an APM function would have corrected before # If there there is only one value in find_idx for a given Alt, Mach, aos_list, no analyse can be performed # An error message has been already printed through the first part of the code # Check if it is an unstability case detected previously for combination in lat_unstable_cases: if combination[0] == alt and combination[ 1] == mach and combination[2] == aoa: laterally_stable = False # If there is at list 2 values in find_idx : if len(find_idx) > 1: # Find all cmd_list values for index corresonding to an altitude, a mach, an aos_list=0, and different aoa_list cmd = [] aos = [] for index in find_idx: cmd.append(-cmd_list[index]) aos.append(aos_list[index]) # Save values which will be plot plot_cmd.append(cmd) plot_aos.append(aos) curve_legend = 'Altitude = ' + str(alt) + ' m' plot_legend.append(curve_legend) if plot_cmd: # PLot cmd VS aos for constant Mach, aoa and different alt plot_multicurve(plot_cmd, plot_aos, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) ## DIRECTIONAL # plot cml VS aos for constant mach, aoa_list and different altitudes: for aoa in aoa_unic: # Find index of altitude which have the same value idx_aoa = [i for i in range(len(aoa_list)) if aoa_list[i] == aoa] for mach in mach_unic: # Find index of mach which have the same value idx_mach = [ j for j in range(len(mach_list)) if mach_list[j] == mach ] # Prepare variables for plots plot_cml = [] plot_aos = [] plot_legend = [] plot_title = r'Yaw moment coefficient $C_N$ vs $\beta$ @ Mach = ' + str( mach) + r' and $\alpha$= ' + str(aoa) + r' °' xlabel = r'$\beta$ [°]' ylabel = r'$C_N$ [-]' dirrectionaly_stable = True # Find index of slip angle which have the same value for alt in alt_unic: idx_alt = [ j for j in range(len(alt_list)) if alt_list[j] == alt ] find_idx = get_index(idx_aoa, idx_mach, idx_alt) # Check if it is an unstability case detected previously for combination in direc_unstable_cases: if combination[0] == alt and combination[ 1] == mach and combination[2] == aoa: dirrectionaly_stable = False # If there is at list 2 values in find_idx : if len(find_idx) > 1: # Find all cml_list values for index corresonding to an altitude, a mach, an aos_list=0, and different aoa_list cml = [] aos = [] for index in find_idx: cml.append(-cml_list[index]) aos.append(aos_list[index]) # Save values which will be plot plot_cml.append(cml) plot_aos.append(aos) curve_legend = 'Altitude = ' + str(alt) + ' m' plot_legend.append(curve_legend) if plot_cml: # PLot cml VS aos for constant Mach, aoa and different alt plot_multicurve(plot_cml, plot_aos, plot_legend, plot_title, xlabel, ylabel, show_plots, save_plots) # Save in the CPACS file stability results: trim_alt_longi_list = extract_subelements(trim_alt_longi_list) trim_mach_longi_list = extract_subelements(trim_mach_longi_list) trim_aoa_longi_list = extract_subelements(trim_aoa_longi_list) trim_aos_longi_list = extract_subelements(trim_aos_longi_list) trim_derivative_longi_list = extract_subelements( trim_derivative_longi_list) trim_alt_lat_list = extract_subelements(trim_alt_lat_list) trim_mach_lat_list = extract_subelements(trim_mach_lat_list) trim_aoa_lat_list = extract_subelements(trim_aoa_lat_list) trim_aos_lat_list = extract_subelements(trim_aos_lat_list) trim_derivative_lat_list = extract_subelements(trim_derivative_lat_list) trim_alt_direc_list = extract_subelements(trim_alt_direc_list) trim_mach_direc_list = extract_subelements(trim_mach_direc_list) trim_aoa_direc_list = extract_subelements(trim_aoa_direc_list) trim_aos_direc_list = extract_subelements(trim_aos_direc_list) trim_derivative_direc_list = extract_subelements( trim_derivative_direc_list) # xpath definition # TODO: add uid of the coresponding aeropm for results longi_xpath = STATIC_ANALYSIS_XPATH + '/results/longitudinalStaticStable' lat_xpath = STATIC_ANALYSIS_XPATH + '/results/lateralStaticStable' direc_xpath = STATIC_ANALYSIS_XPATH + '/results/directionnalStaticStable' longi_trim_xpath = STATIC_ANALYSIS_XPATH + '/trimConditions/longitudinal' lat_trim_xpath = STATIC_ANALYSIS_XPATH + '/trimConditions/lateral' direc_trim_xpath = STATIC_ANALYSIS_XPATH + '/trimConditions/directional' cpsf.create_branch(tixi, longi_xpath) cpsf.create_branch(tixi, lat_xpath) cpsf.create_branch(tixi, direc_xpath) # Store in the CPACS the stability results tixi.updateTextElement(longi_xpath, str(cpacs_stability_longi)) tixi.updateTextElement(lat_xpath, str(cpacs_stability_lat)) tixi.updateTextElement(direc_xpath, str(cpacs_stability_direc)) cpsf.create_branch(tixi, longi_trim_xpath) cpsf.create_branch(tixi, lat_trim_xpath) cpsf.create_branch(tixi, direc_trim_xpath) # TODO: Normaly this "if" is not required, but the tixi function to add a vector does not support an empty vercor... if trim_alt_longi_list: cpsf.add_float_vector(tixi, longi_trim_xpath + '/altitude', trim_alt_longi_list) cpsf.add_float_vector(tixi, longi_trim_xpath + '/machNumber', trim_mach_longi_list) cpsf.add_float_vector(tixi, longi_trim_xpath + '/angleOfAttack', trim_aoa_longi_list) cpsf.add_float_vector(tixi, longi_trim_xpath + '/angleOfSideslip', trim_aos_longi_list) if trim_alt_lat_list: cpsf.add_float_vector(tixi, lat_trim_xpath + '/altitude', trim_alt_lat_list) cpsf.add_float_vector(tixi, lat_trim_xpath + '/machNumber', trim_mach_lat_list) cpsf.add_float_vector(tixi, lat_trim_xpath + '/angleOfAttack', trim_aoa_lat_list) cpsf.add_float_vector(tixi, lat_trim_xpath + '/angleOfSideslip', trim_aos_lat_list) if trim_alt_direc_list: cpsf.add_float_vector(tixi, direc_trim_xpath + '/altitude', trim_alt_direc_list) cpsf.add_float_vector(tixi, direc_trim_xpath + '/machNumber', trim_mach_direc_list) cpsf.add_float_vector(tixi, direc_trim_xpath + '/angleOfAttack', trim_aoa_direc_list) cpsf.add_float_vector(tixi, direc_trim_xpath + '/angleOfSideslip', trim_aos_direc_list) cpsf.close_tixi(tixi, cpacs_out_path)