From 8d8284d43652112b9378dee31e0396b644715335 Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Fri, 26 Jan 2024 15:45:09 -0500 Subject: [PATCH 01/13] Fortran_to_Aviary -> fortran_to_aviary --- aviary/api.py | 2 +- aviary/interface/cmd_entry_points.py | 2 +- aviary/utils/test/test_Fortran_to_Aviary.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aviary/api.py b/aviary/api.py index d390b6789..0292649b0 100644 --- a/aviary/api.py +++ b/aviary/api.py @@ -40,7 +40,7 @@ from aviary.interface.methods_for_level2 import AviaryProblem from aviary.interface.utils.check_phase_info import check_phase_info from aviary.utils.engine_deck_conversion import EngineDeckConverter -from aviary.utils.Fortran_to_Aviary import create_aviary_deck +from aviary.utils.fortran_to_aviary import create_aviary_deck from aviary.utils.functions import set_aviary_initial_values, get_path from aviary.utils.options import list_options from aviary.constants import GRAV_METRIC_GASP, GRAV_ENGLISH_GASP, GRAV_METRIC_FLOPS, GRAV_ENGLISH_FLOPS, GRAV_ENGLISH_LBM, RHO_SEA_LEVEL_ENGLISH, RHO_SEA_LEVEL_METRIC, MU_TAKEOFF, MU_LANDING, PSLS_PSF, TSLS_DEGR, RADIUS_EARTH_METRIC diff --git a/aviary/interface/cmd_entry_points.py b/aviary/interface/cmd_entry_points.py index 56dff8119..25425863e 100644 --- a/aviary/interface/cmd_entry_points.py +++ b/aviary/interface/cmd_entry_points.py @@ -4,7 +4,7 @@ import aviary from aviary.interface.methods_for_level1 import _exec_level1, _setup_level1_parser -from aviary.utils.Fortran_to_Aviary import _exec_F2A, _setup_F2A_parser +from aviary.utils.fortran_to_aviary import _exec_F2A, _setup_F2A_parser from aviary.visualization.dashboard import _dashboard_setup_parser, _dashboard_cmd from aviary.interface.graphical_input import IntegratedPlottingApp diff --git a/aviary/utils/test/test_Fortran_to_Aviary.py b/aviary/utils/test/test_Fortran_to_Aviary.py index c136cec03..2103be035 100644 --- a/aviary/utils/test/test_Fortran_to_Aviary.py +++ b/aviary/utils/test/test_Fortran_to_Aviary.py @@ -4,7 +4,7 @@ from aviary.utils.functions import get_path from openmdao.utils.testing_utils import use_tempdirs -from aviary.utils.Fortran_to_Aviary import LegacyCode, _exec_F2A +from aviary.utils.fortran_to_aviary import LegacyCode, _exec_F2A class DummyArgs(object): From dec3506d05591374294699d58a89f274def13911 Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Thu, 1 Feb 2024 11:16:32 -0500 Subject: [PATCH 02/13] updated variable tests --- aviary/utils/test_utils/variable_test.py | 20 +++++---- .../variable_info/test/test_var_structure.py | 41 ++++++++++++++++--- aviary/variable_info/variable_meta_data.py | 23 +++++++++++ aviary/variable_info/variables.py | 4 +- 4 files changed, 74 insertions(+), 14 deletions(-) diff --git a/aviary/utils/test_utils/variable_test.py b/aviary/utils/test_utils/variable_test.py index adc3a9513..18705bc13 100644 --- a/aviary/utils/test_utils/variable_test.py +++ b/aviary/utils/test_utils/variable_test.py @@ -210,16 +210,22 @@ def assert_match_varnames(system, MetaData=None): def get_names_from_hierarchy(hierarchy): names = [] - keys = vars(hierarchy).keys() # get initial class keys + variables = vars(hierarchy).keys() # get initial class keys # filter out keys that aren't for relevant variables - keys = list(filter(filter_underscore, list(keys))) + keys = list(filter(filter_underscore, list(variables))) for key in keys: - subclass_vars = vars( - getattr(hierarchy, key) - ) # grab dictionary of variables for the subclass - # filter out keys that aren't for relevant variables - subclass_keys = list(filter(filter_underscore, list(subclass_vars.keys()))) + try: + # If there are multiple hierarchy levels, dig down one more + subclass_vars = vars( + getattr(hierarchy, key) + ) # grab dictionary of variables for the subclass + # filter out keys that aren't for relevant variables + subclass_keys = list(filter(filter_underscore, list(subclass_vars.keys()))) + except TypeError: + # Only one hierarchy level + subclass_vars = vars(hierarchy) + subclass_keys = [key] for var_name in subclass_keys: names.append( diff --git a/aviary/variable_info/test/test_var_structure.py b/aviary/variable_info/test/test_var_structure.py index c667e7c22..d5acc64df 100644 --- a/aviary/variable_info/test/test_var_structure.py +++ b/aviary/variable_info/test/test_var_structure.py @@ -1,10 +1,12 @@ import unittest +from copy import deepcopy + from aviary.utils.test_utils.variable_test import ( DuplicateHierarchy, assert_metadata_alphabetization, assert_no_duplicates, assert_structure_alphabetization, get_names_from_hierarchy) from aviary.variable_info.variable_meta_data import _MetaData -from aviary.variable_info.variables import Aircraft, Mission +from aviary.variable_info.variables import Aircraft, Mission, Dynamic, Settings class MetaDataTest(unittest.TestCase): @@ -22,16 +24,45 @@ def test_alphabetization(self): # TODO currently excluding Dynamic variables that do not have proper full # names mirroring the hierarchy - metadata_var_names = [key for key in _MetaData if ':' in key] + metadata_var_names = [catey for catey in _MetaData if ':' in catey] assert_metadata_alphabetization(metadata_var_names) + def test_missing_names(self): + """ + Test that all variables inside the metadata exist in the hierarchy, and vice-versa + """ + # NOTE: This is messy due to the fact we are dealing with attributes inside nested classes + var_names = \ + [(var_name, var) for cat_name, cat in Aircraft.__dict__.items() if not cat_name.startswith('__') + for var_name, var in cat.__dict__.items() if not var_name.startswith('__')]\ + + [(var_name, var) for cat_name, cat in Mission.__dict__.items() if not cat_name.startswith('__') + for var_name, var in cat.__dict__.items() if not var_name.startswith('__')]\ + + [(var_name, var) for cat_name, cat in Dynamic.__dict__.items() if not cat_name.startswith('__') + for var_name, var in cat.__dict__.items() if not var_name.startswith('__')]\ + + [(var_name, var) for var_name, var in Settings.__dict__.items() + if not var_name.startswith('__')] + + metadata_dict = deepcopy(_MetaData) + for var in var_names: + try: + metadata_dict.pop(var[1]) + except (TypeError, KeyError): + raise Exception(f"Variable {var[0]} ('{var[1]}') is present in variables.py but is not " + 'defined in metadata') + if metadata_dict: + # This will only happen if a variable in the metadata wasn't using the hierarchy + raise Exception(f'Variables {[*metadata_dict.keys()]} are present in metadata, but are' + ' not defined in variables.py') + class VariableStructureTest(unittest.TestCase): def test_duplicate_names_Aviary(self): - aviary_names = get_names_from_hierarchy( - Aircraft) + get_names_from_hierarchy(Mission) + aviary_names = get_names_from_hierarchy(Aircraft)\ + + get_names_from_hierarchy(Mission)\ + + get_names_from_hierarchy(Dynamic)\ + + get_names_from_hierarchy(Settings) assert_no_duplicates(aviary_names) @@ -41,7 +72,7 @@ def test_alphabetization(self): class TestTheTests(unittest.TestCase): - def test_duplication_check(self): + def test_duplication_checcat(self): with self.assertRaises(ValueError) as cm: duplicated_names = get_names_from_hierarchy(DuplicateHierarchy) diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index 508cc0f62..5a9d6491f 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -6048,6 +6048,17 @@ desc='Current Mach number of the vehicle' ) +add_meta_data( + Dynamic.Mission.MACH_RATE, + meta_data=_MetaData, + historical_name={"GASP": None, + "FLOPS": None, + "LEAPS1": None + }, + units='unitless', + desc='Current rate at which the Mach number of the vehicle is changing' +) + add_meta_data( Dynamic.Mission.MASS, meta_data=_MetaData, @@ -6093,6 +6104,18 @@ desc='Current total rate of nitrous oxide (NOx) production by the vehicle' ) +add_meta_data( + Dynamic.Mission.SPECIFIC_ENERGY, + meta_data=_MetaData, + historical_name={"GASP": None, + "FLOPS": None, + "LEAPS1": None + }, + units='m/s', + desc='Rate of change in specific energy (energy per unit weight) of the vehicle at current ' + 'flight condition' +) + add_meta_data( Dynamic.Mission.SPECIFIC_ENERGY_RATE, meta_data=_MetaData, diff --git a/aviary/variable_info/variables.py b/aviary/variable_info/variables.py index ad4c9f6f0..6c869970e 100644 --- a/aviary/variable_info/variables.py +++ b/aviary/variable_info/variables.py @@ -206,7 +206,6 @@ class Engine: MASS = 'aircraft:engine:mass' MASS_SCALER = 'aircraft:engine:mass_scaler' MASS_SPECIFIC = 'aircraft:engine:mass_specific' - MODEL_SLS_THRUST = 'aircraft:engine:model_sls_thrust' NUM_ENGINES = 'aircraft:engine:num_engines' NUM_FUSELAGE_ENGINES = 'aircraft:engine:num_fuselage_engines' NUM_WING_ENGINES = 'aircraft:engine:num_wing_engines' @@ -472,7 +471,8 @@ class Wing: AVERAGE_CHORD = 'aircraft:wing:average_chord' BENDING_FACTOR = 'aircraft:wing:bending_factor' BENDING_MASS = 'aircraft:wing:bending_mass' - BENDING_MASS_NO_INERTIA = 'aircraft:wing:bending_mass_no_inertia' + # Not defined in metadata! + # BENDING_MASS_NO_INERTIA = 'aircraft:wing:bending_mass_no_inertia' BENDING_MASS_SCALER = 'aircraft:wing:bending_mass_scaler' BWB_AFTBODY_MASS = 'aircraft:wing:bwb_aft_body_mass' BWB_AFTBODY_MASS_SCALER = 'aircraft:wing:bwb_aft_body_mass_scaler' From 1853edaf46111b85f8e548487cb6997d9366d171 Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Thu, 1 Feb 2024 13:23:42 -0500 Subject: [PATCH 03/13] fixed typos --- aviary/variable_info/test/test_var_structure.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aviary/variable_info/test/test_var_structure.py b/aviary/variable_info/test/test_var_structure.py index d5acc64df..97da512d9 100644 --- a/aviary/variable_info/test/test_var_structure.py +++ b/aviary/variable_info/test/test_var_structure.py @@ -24,7 +24,7 @@ def test_alphabetization(self): # TODO currently excluding Dynamic variables that do not have proper full # names mirroring the hierarchy - metadata_var_names = [catey for catey in _MetaData if ':' in catey] + metadata_var_names = [key for key in _MetaData if ':' in key] assert_metadata_alphabetization(metadata_var_names) @@ -72,7 +72,7 @@ def test_alphabetization(self): class TestTheTests(unittest.TestCase): - def test_duplication_checcat(self): + def test_duplication_check(self): with self.assertRaises(ValueError) as cm: duplicated_names = get_names_from_hierarchy(DuplicateHierarchy) From cccfe9d0b2d2dbc76608803b7bdf806791702c1d Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Thu, 1 Feb 2024 14:53:55 -0500 Subject: [PATCH 04/13] small variable name changes --- aviary/utils/test_utils/variable_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aviary/utils/test_utils/variable_test.py b/aviary/utils/test_utils/variable_test.py index 18705bc13..c917303f2 100644 --- a/aviary/utils/test_utils/variable_test.py +++ b/aviary/utils/test_utils/variable_test.py @@ -210,9 +210,9 @@ def assert_match_varnames(system, MetaData=None): def get_names_from_hierarchy(hierarchy): names = [] - variables = vars(hierarchy).keys() # get initial class keys + keys = vars(hierarchy).keys() # get initial class keys # filter out keys that aren't for relevant variables - keys = list(filter(filter_underscore, list(variables))) + keys = list(filter(filter_underscore, list(keys))) for key in keys: try: From 32c39c3d35d517b863691f626b4b08c57b9def28 Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Thu, 1 Feb 2024 15:10:53 -0500 Subject: [PATCH 05/13] Made file generation for some tests optional --- aviary/mission/gasp_based/ode/time_integration_base_classes.py | 2 +- aviary/utils/test/test_process_input_decks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aviary/mission/gasp_based/ode/time_integration_base_classes.py b/aviary/mission/gasp_based/ode/time_integration_base_classes.py index b3a8d0e17..57e3ab78f 100644 --- a/aviary/mission/gasp_based/ode/time_integration_base_classes.py +++ b/aviary/mission/gasp_based/ode/time_integration_base_classes.py @@ -186,7 +186,7 @@ def __init__( # TODO: add defensive checks to make sure dimensions match in both setup and # calls - if DEBUG or True: + if DEBUG: om.n2(prob, outfile="n2_simupy_problem.html", show_browser=False) with open('input_list_simupy.txt', 'w') as outfile: prob.model.list_inputs(out_stream=outfile,) diff --git a/aviary/utils/test/test_process_input_decks.py b/aviary/utils/test/test_process_input_decks.py index 8eb1baae2..0785ea0f8 100644 --- a/aviary/utils/test/test_process_input_decks.py +++ b/aviary/utils/test/test_process_input_decks.py @@ -5,7 +5,7 @@ from aviary.utils.functions import get_path -# @use_tempdirs +@use_tempdirs class TestCreateVehicle(unittest.TestCase): def test_load_aircraft_csv(self): From e46e5e0ab2172c7748c20adbc3cb748a766b2044 Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Wed, 27 Mar 2024 13:36:12 -0400 Subject: [PATCH 06/13] fixed case-sensitive changes not being applied --- aviary/interface/cmd_entry_points.py | 2 +- aviary/utils/fortran_to_aviary.py | 632 ++++++++++++++++++++ aviary/utils/test/test_fortran_to_aviary.py | 101 ++++ 3 files changed, 734 insertions(+), 1 deletion(-) create mode 100644 aviary/utils/fortran_to_aviary.py create mode 100644 aviary/utils/test/test_fortran_to_aviary.py diff --git a/aviary/interface/cmd_entry_points.py b/aviary/interface/cmd_entry_points.py index f927b895f..647a056c7 100644 --- a/aviary/interface/cmd_entry_points.py +++ b/aviary/interface/cmd_entry_points.py @@ -4,7 +4,7 @@ import aviary from aviary.interface.methods_for_level1 import _exec_level1, _setup_level1_parser -from aviary.utils.Fortran_to_Aviary import _exec_F2A, _setup_F2A_parser +from aviary.utils.fortran_to_aviary import _exec_F2A, _setup_F2A_parser from aviary.utils.engine_deck_conversion import _exec_EDC, _setup_EDC_parser, EDC_description from aviary.visualization.dashboard import _dashboard_setup_parser, _dashboard_cmd from aviary.interface.graphical_input import _exec_flight_profile, _setup_flight_profile_parser diff --git a/aviary/utils/fortran_to_aviary.py b/aviary/utils/fortran_to_aviary.py new file mode 100644 index 000000000..884e39b04 --- /dev/null +++ b/aviary/utils/fortran_to_aviary.py @@ -0,0 +1,632 @@ +""" +Fortran_to_Aviary.py is used to read in Fortran based vehicle decks and convert them to Aviary decks. + +FLOPS, GASP, or Aviary names can be used for variables (Ex WG or Mission:Design:GROSS_MASS) +When specifying variables from FORTRAN, they should be in the appropriate NAMELIST. +Aviary variable names should be specified outside any NAMELISTS. +Names are not case-sensitive. +Units can be specified using any of the openMDAO valid units. +Comments can be added using ! +Lists can be entered by separating values with commas. +Individual list elements can be specified by adding an index after the variable name. +(NOTE: 1 indexing is used inside NAMELISTS, while 0 indexing is used outside NAMELISTS) + +Example inputs: +aircraft:fuselage:pressure_differential = .5, atm !DELP in GASP, but using atmospheres instead of psi +ARNGE(1) = 3600 !target range in nautical miles +pyc_phases = taxi, groundroll, rotation, landing +""" + +import csv +import re +from enum import Enum +from pathlib import Path + +from openmdao.utils.units import valid_units + +from aviary.utils.functions import convert_strings_to_data +from aviary.utils.named_values import NamedValues, get_items +from aviary.variable_info.variable_meta_data import _MetaData +from aviary.variable_info.variables import Aircraft, Mission +from aviary.variable_info.enums import LegacyCode, Verbosity +from aviary.utils.functions import get_path +from aviary.utils.legacy_code_data.deprecated_vars import flops_deprecated_vars, gasp_deprecated_vars + + +FLOPS = LegacyCode.FLOPS +GASP = LegacyCode.GASP + + +def create_aviary_deck(fortran_deck: str, legacy_code=None, defaults_deck=None, + out_file=None, force=False, verbosity=Verbosity.BRIEF): + ''' + Create an Aviary CSV file from a Fortran input deck + Required input is the filepath to the input deck and legacy code. Optionally, a + deck of default values can be specified, this is useful if an input deck + assumes certain values for any unspecified variables + If an invalid filepath is given, pre-packaged resources will be checked for + input decks with a matching name. + ''' + # TODO generate both an Aviary input file and a phase_info file + + vehicle_data = {'input_values': NamedValues(), 'unused_values': NamedValues(), + 'initial_guesses': initial_guesses, 'verbosity': verbosity} + + fortran_deck: Path = get_path(fortran_deck, verbose=False) + + if out_file: + out_file = Path(out_file) + else: + name = fortran_deck.stem + out_file: Path = fortran_deck.parent.resolve().joinpath(name + '_converted.csv') + + if legacy_code is GASP: + default_extension = '.dat' + deprecated_vars = gasp_deprecated_vars + elif legacy_code is FLOPS: + default_extension = '.txt' + deprecated_vars = flops_deprecated_vars + + if not defaults_deck: + defaults_filename = legacy_code.value.lower() + '_default_values' + default_extension + defaults_deck = Path(__file__).parent.resolve().joinpath( + 'legacy_code_data', defaults_filename) + + # create dictionary to convert legacy code variables to Aviary variables + aviary_variable_dict = generate_aviary_names([legacy_code.value]) + + if defaults_deck: # If defaults are specified, initialize the vehicle with them + vehicle_data = input_parser(defaults_deck, vehicle_data, + aviary_variable_dict, deprecated_vars, legacy_code) + + vehicle_data = input_parser(fortran_deck, vehicle_data, + aviary_variable_dict, deprecated_vars, legacy_code) + if legacy_code is GASP: + vehicle_data = update_gasp_options(vehicle_data) + + if not out_file.is_file(): # default outputted file to be in same directory as input + out_file = fortran_deck.parent / out_file + + if out_file.is_file(): + if force: + print(f'Overwriting existing file: {out_file.name}') + else: + raise RuntimeError(f'{out_file} already exists. Choose a new name or enable ' + '--force') + else: + # create any directories defined by the new filename if they don't already exist + out_file.parent.mkdir(parents=True, exist_ok=True) + print('Writing to:', out_file) + + # open the file in write mode + with open(out_file, 'w', newline='') as f: + writer = csv.writer(f) + + # Values that have been successfully translated to Aviary variables + writer.writerow(['# Input Values']) + for var, (val, units) in sorted(vehicle_data['input_values']): + writer.writerow([var] + val + [units]) + if legacy_code is FLOPS: + EOM = 'height_energy' + mass = 'FLOPS' + if legacy_code is GASP: + EOM = '2DOF' + mass = 'GASP' + writer.writerow(['settings:equations_of_motion'] + [EOM]) + writer.writerow(['settings:mass_method'] + [mass]) + + if legacy_code is GASP: + # Values used in initial guessing of the trajectory + writer.writerow([]) + writer.writerow(['# Initial Guesses']) + for var_name in sorted(vehicle_data['initial_guesses']): + row = [var_name, vehicle_data['initial_guesses'][var_name]] + writer.writerow(row) + + # Values that were not successfully converted + writer.writerow([]) + writer.writerow(['# Unconverted Values']) + for var, (val, _) in sorted(vehicle_data['unused_values']): + writer.writerow([var] + val) + + +def input_parser(fortran_deck, vehicle_data, alternate_names, unused_vars, legacy_code): + ''' + input_parser will modify the values in the vehicle_data dictionary using the data in the + fortran_deck. + Lines are read one by one, comments are removed, and namelists are tracked. + Lines with multiple variable-data pairs are supported, but the last value per variable must + be followed by a trailing comma. + ''' + with open(fortran_deck, 'r') as f_in: + current_namelist = current_tag = '' + for line in f_in: + terminate_namelist = False + + tmp = [*line.split('!', 1), ''] + line, comment = tmp[0], tmp[1] # anything after the first ! is a comment + + # remove all white space and trailing commas + line = ''.join(line.split()).rstrip(',') + if len(line.split()) == 0: + continue # skip line if it contains only white space + + # Track when namelists are opened and closed + if (line.lstrip()[0] in ['$', '&']) and current_tag == '': + current_tag = line.lstrip()[0] + current_namelist = line.split(current_tag)[1].split()[0] + '.' + elif (line.lstrip()[0] == current_tag) or (line.rstrip()[-1] == '/'): + line = line.replace('/', '') + terminate_namelist = True + + number_of_variables = line.count('=') + if number_of_variables == 1: + # get the first element and remove white space + var_name = ''.join(line.split('=')[0].split()) + # everything after the = is the data + data = line.split('=')[1] + try: + vehicle_data = process_and_store_data( + data, var_name, legacy_code, current_namelist, alternate_names, vehicle_data, unused_vars, comment) + except Exception as err: + if current_namelist == '': + raise RuntimeError(line + ' could not be parsed successfully.' + '\nIf this was intended as a comment, ' + 'add an "!" at the beginning of the line.') from err + else: + raise err + elif number_of_variables > 1: + sub_line = line.split('=') # split the line at each = + var_name = sub_line[0] # the first element is the first name + for ii in range(number_of_variables): + # Each of the following elements contains all of the data for the current variable + # and the last element is the name of the next variable + sub_list = sub_line[ii+1].split(',') + if ii+1 < number_of_variables: + next_var_name = sub_list.pop() + if not next_var_name[0].isalpha(): + index = next((i for i, c in enumerate( + next_var_name) if c.isalpha()), len(next_var_name)) + sub_list.append(next_var_name[:index]) + next_var_name = next_var_name[index:] + + data = ','.join(sub_list) + try: + vehicle_data = process_and_store_data( + data, var_name, legacy_code, current_namelist, alternate_names, vehicle_data, unused_vars, comment) + except Exception as err: + if current_namelist == '': + raise RuntimeError(line + ' could not be parsed successfully.' + '\nIf this was intended as a comment, ' + 'add an "!" at the beginning of the line.') from err + else: + raise err + var_name = next_var_name + + if terminate_namelist: + current_namelist = current_tag = '' + + return vehicle_data + + +def process_and_store_data(data, var_name, legacy_code, current_namelist, alternate_names, vehicle_data, unused_vars, comment=''): + ''' + process_and_store_data takes in a string that contains the data, the current variable's name and + namelist, the dictionary of alternate names, and the current vehicle data. + It will convert the string of data into a list, get units, check whether the data specified is + part of a list or a single element, and update the current name to it's equivalent Aviary name. + The variables are also sorted based on whether they will set an Aviary variable or they are for initial guessing + ''' + + guess_names = list(initial_guesses.keys()) + var_ind = data_units = None + skip_variable = False + # skip any variables that shouldn't get converted + if re.search(current_namelist+var_name+'\Z', str(unused_vars), re.IGNORECASE): + return vehicle_data + # remove any elements that are empty (caused by trailing commas or extra commas) + data_list = [dat for dat in data.split(',') if dat != ''] + if len(data_list) > 0: + if valid_units(data_list[-1]): + # if the last element is a unit, remove it from the list and update the variable's units + data_units = data_list.pop() + var_values = convert_strings_to_data(data_list) + else: + skip_variable = True + var_values = [] + + if '(' in var_name: # some GASP lists are given as individual elements + # get the target index (Fortran uses 1 indexing, Python uses 0 indexing) + fortran_offset = 1 if current_namelist else 0 + var_ind = int(var_name.split('(')[1].split(')')[0])-fortran_offset + var_name = var_name.split('(')[0] # remove the index formatting + + list_of_equivalent_aviary_names = update_name( + alternate_names, current_namelist+var_name, vehicle_data['verbosity']) + for name in list_of_equivalent_aviary_names: + if not skip_variable: + if name in guess_names and legacy_code is GASP: + # all initial guesses take only a single value + vehicle_data['initial_guesses'][name] = float(var_values[0]) + continue + + elif name in _MetaData: + vehicle_data['input_values'] = set_value(name, var_values, vehicle_data['input_values'], + var_ind=var_ind, units=data_units) + continue + + vehicle_data['unused_values'] = set_value(name, var_values, vehicle_data['unused_values'], + var_ind=var_ind, units=data_units) + if vehicle_data['verbosity'].value >= 2: + print('Unused:', name, var_values, comment) + + return vehicle_data + + +def set_value(var_name, var_value, value_dict: NamedValues, var_ind=None, units=None): + ''' + set_value will update the current value of a variable in a value dictionary that contains a value + and it's associated units. + If units are specified for the new value, they will be used, otherwise the current units in the + value dictionary or the default units from _MetaData are used. + If the new variable is part of a list, the current list will be extended if needed. + ''' + + if var_name in value_dict: + current_value, units = value_dict.get_item(var_name) + else: + current_value = None + if var_name in _MetaData: + units = _MetaData[var_name]['units'] + else: + units = 'unitless' + if not units: + units = 'unitless' + + if var_ind != None: + # if an index is specified, use it, otherwise store the input as the whole value + if isinstance(current_value, list): + max_ind = len(current_value) - 1 + if var_ind > max_ind: + current_value.extend((var_ind-max_ind)*[0]) + else: + current_value = [current_value]+[0]*var_ind + current_value[var_ind] = var_value[0] + value_dict.set_val(var_name, current_value, units) + else: + value_dict.set_val(var_name, var_value, units) + return value_dict + + +def generate_aviary_names(code_bases): + ''' + Create a dictionary for each of the specified Fortran code bases to map to the Aviary + variable names. Each dictionary of Aviary names will have a list of Fortran names for + each variable + ''' + + alternate_names = {} + for code_base in code_bases: + alternate_names[code_base] = {} + for key in _MetaData.keys(): + historical_dict = _MetaData[key]['historical_name'] + if historical_dict and code_base in historical_dict: + alt_name = _MetaData[key]['historical_name'][code_base] + if isinstance(alt_name, str): + alt_name = [alt_name] + alternate_names[code_base][key] = alt_name + return alternate_names + + +def update_name(alternate_names, var_name, verbosity=Verbosity.BRIEF): + '''update_name will convert a Fortran name to a list of equivalent Aviary names.''' + + all_equivalent_names = [] + for code_base in alternate_names.keys(): + for key, list_of_names in alternate_names[code_base].items(): + if list_of_names is not None: + if any([re.search(var_name+r'\Z', altname, re.IGNORECASE) for altname in list_of_names]): + all_equivalent_names.append(key) + + # if there are no equivalent variable names, return the original name + if len(all_equivalent_names) == 0: + if verbosity.value >= 2: + print('passing: ', var_name) + all_equivalent_names = [var_name] + return all_equivalent_names + + +def update_gasp_options(vehicle_data): + """ + Handles variables that are affected by the values of others + """ + input_values: NamedValues = vehicle_data['input_values'] + + flap_types = ["plain", "split", "single_slotted", "double_slotted", + "triple_slotted", "fowler", "double_slotted_fowler"] + + ## PROBLEM TYPE ## + # if multiple values of target_range are specified, use the one that corresponds to the problem_type + design_range, distance_units = input_values.get_item(Mission.Design.RANGE) + try: + problem_type = input_values.get_val('problem_type')[0] + except KeyError: + problem_type = 'sizing' + + if isinstance(design_range, list): + # if the design range target_range value is 0, set the problem_type to fallout + if design_range[0] == 0: + problem_type = 'fallout' + input_values.set_val('problem_type', [problem_type]) + design_range = 0 + if problem_type == 'sizing': + design_range = design_range[0] + elif problem_type == 'alternate': + design_range = design_range[2] + elif problem_type == 'fallout': + design_range = 0 + else: + if design_range == 0: + input_values.set_val('problem_type', ['fallout']) + input_values.set_val(Mission.Design.RANGE, [design_range], distance_units) + + ## STRUT AND FOLD ## + strut_loc = input_values.get_val(Aircraft.Strut.ATTACHMENT_LOCATION, 'ft')[0] + folded_span = input_values.get_val(Aircraft.Wing.FOLDED_SPAN, 'ft')[0] + + if strut_loc == 0: + input_values.set_val(Aircraft.Wing.HAS_STRUT, [False], 'unitless') + else: + input_values.set_val(Aircraft.Wing.HAS_STRUT, [True], 'unitless') + if folded_span == 0: + input_values.set_val(Aircraft.Wing.HAS_FOLD, [False], 'unitless') + else: + input_values.set_val(Aircraft.Wing.HAS_FOLD, [True], 'unitless') + + if strut_loc < 0: + input_values.set_val(Aircraft.Wing.HAS_FOLD, [True], 'unitless') + input_values.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, [False], 'unitless') + strut_loc = abs(strut_loc) + + if strut_loc < 1: + input_values.set_val(Aircraft.Strut.ATTACHMENT_LOCATION_DIMENSIONLESS, + [strut_loc], 'unitless') + input_values.set_val(Aircraft.Strut.DIMENSIONAL_LOCATION_SPECIFIED, [ + False], 'unitless') + else: + input_values.set_val(Aircraft.Strut.ATTACHMENT_LOCATION, [strut_loc], 'ft') + input_values.set_val( + Aircraft.Strut.DIMENSIONAL_LOCATION_SPECIFIED, [True], 'unitless') + + if input_values.get_val(Aircraft.Wing.HAS_FOLD)[0]: + if not input_values.get_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION)[0]: + input_values.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, + [True], 'unitless') + else: + if input_values.get_val(Aircraft.Wing.FOLDED_SPAN, 'ft')[0] > 1: + input_values.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, + [True], 'unitless') + else: + input_values.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, + [False], 'unitless') + else: + input_values.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, [True], 'unitless') + input_values.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, [ + False], 'unitless') + + ## FLAPS ## + flap_type = input_values.get_val(Aircraft.Wing.FLAP_TYPE)[0] + if not isinstance(flap_type, str): + flap_type = flap_types[flap_type-1] + input_values.set_val(Aircraft.Wing.FLAP_TYPE, [flap_type]) + flap_ind = flap_types.index(flap_type) + if input_values.get_val(Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT)[0] <= 0: + input_values.set_val(Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, + [[0.62, 1.0, 0.733, 1.2, 1.32, 0.633, 0.678][flap_ind]]) + if input_values.get_val(Aircraft.Wing.OPTIMUM_FLAP_DEFLECTION, 'deg')[0] == 0: + input_values.set_val(Aircraft.Wing.OPTIMUM_FLAP_DEFLECTION, + [[60, 60, 40, 55, 55, 30, 30][flap_ind]], 'deg') + if input_values.get_val(Aircraft.Wing.FLAP_LIFT_INCREMENT_OPTIMUM)[0] == 0: + input_values.set_val(Aircraft.Wing.FLAP_LIFT_INCREMENT_OPTIMUM, + [[.9, .8, 1.18, 1.4, 1.6, 1.67, 2.25][flap_ind]]) + if input_values.get_val(Aircraft.Wing.FLAP_DRAG_INCREMENT_OPTIMUM)[0] == 0: + input_values.set_val(Aircraft.Wing.FLAP_DRAG_INCREMENT_OPTIMUM, + [[.12, .23, .13, .23, .23, .1, .15][flap_ind]]) + + res = input_values.get_val(Aircraft.Design.RESERVE_FUEL_ADDITIONAL, units='lbm')[0] + if res <= 0: + input_values.set_val(Aircraft.Design.RESERVE_FUEL_ADDITIONAL, [0], units='lbm') + input_values.set_val(Aircraft.Design.RESERVE_FUEL_FRACTION, + [-res], units='unitless') + elif res >= 10: + input_values.set_val(Aircraft.Design.RESERVE_FUEL_FRACTION, + [0], units='unitless') + else: + ValueError('"FRESF" is not valid between 0 and 10.') + + vehicle_data['input_values'] = input_values + return vehicle_data + + +def update_flops_options(vehicle_data): + """ + Handles variables that are affected by the values of others + """ + input_values: NamedValues = vehicle_data['input_values'] + + for var_name in flops_scalar_variables.items(): + update_flops_scalar_variables(var_name, input_values) + + # TWR <= 0 is not valid in Aviary (parametric variation) + if Aircraft.Design.THRUST_TO_WEIGHT_RATIO in input_values: + if input_values.get_val(Aircraft.Design.THRUST_TO_WEIGHT_RATIO) <= 0: + input_values.delete(Aircraft.Design.THRUST_TO_WEIGHT_RATIO) + + # WSR + + # Additional mass fraction scalar set to zero to not add mass twice + if Aircraft.Engine.ADDITIONAL_MASS_FRACTION in input_values: + if input_values.get_val(Aircraft.Engine.ADDITIONAL_MASS_FRACTION) >= 1: + input_values.set_val(Aircraft.Engine.ADDITIONAL_MASS, + input_values.get_val( + Aircraft.Engine.ADDITIONAL_MASS_FRACTION), + 'lbm') + input_values.set_val(Aircraft.Engine.ADDITIONAL_MASS_FRACTION, 0.0) + + # Miscellaneous propulsion mass trigger point 1 instead of 5 + if Aircraft.Propulsion.MISC_MASS_SCALER in input_values: + if input_values.get_val(Aircraft.Propulsion.MISC_MASS_SCALER) >= 1: + input_values.set_val(Aircraft.Propulsion.TOTAL_MISC_MASS, + input_values.get_val( + Aircraft.Propulsion.MISC_MASS_SCALER), + 'lbm') + input_values.set_val(Aircraft.Propulsion.MISC_MASS_SCALER, 0.0) + + vehicle_data['input_values'] = input_values + return vehicle_data + + +def update_flops_scalar_variables(var_name, input_values: NamedValues): + # The following parameters are used to modify or override + # internally computed weights for various components as follows: + # < 0., negative of starting weight which will be modified + # as appropriate during optimization or parametric + # variation, lb + # = 0., no weight for that component + # > 0. but < 5., scale factor applied to internally + # computed weight + # > 5., actual fixed weight for component, lb + # Same rules also applied to various other FLOPS scalar parameters + scalar_name = var_name + '_scaler' + if scalar_name not in input_values: + return + scalar_value = input_values[scalar_name] + if scalar_value <= 0: + input_values.delete(scalar_name) + elif scalar_value < 5: + return + elif scalar_value > 5: + input_values.set_val(var_name, scalar_value, 'lbm') + input_values.set_val(scalar_name, 1.0) + + +# list storing information on Aviary variables that are split from single +# FLOPS variables that use the same value-based branching behavior +flops_scalar_variables = [ + Aircraft.AirConditioning.MASS, + Aircraft.AntiIcing.MASS, + Aircraft.APU.MASS, + Aircraft.Avionics.MASS, + Aircraft.Canard.MASS, + Aircraft.Canard.WETTED_AREA, + Aircraft.CrewPayload.CARGO_CONTAINER_MASS, + Aircraft.CrewPayload.FLIGHT_CREW_MASS, + Aircraft.CrewPayload.NON_FLIGHT_CREW_MASS, + Aircraft.CrewPayload.PASSENGER_SERVICE_MASS, + Aircraft.Design.EMPTY_MASS_MARGIN, + Aircraft.Electrical.MASS, + Aircraft.Engine.THRUST_REVERSERS_MASS, + Aircraft.Fins.MASS, + Aircraft.Fuel.FUEL_SYSTEM_MASS, + Aircraft.Fuel.UNUSABLE_FUEL_MASS, + Aircraft.Furnishings.MASS, + Aircraft.Fuselage.MASS, + Aircraft.Fuselage.WETTED_AREA, + Aircraft.HorizontalTail.MASS, + Aircraft.HorizontalTail.WETTED_AREA, + Aircraft.Hydraulics.MASS, + Aircraft.Instruments.MASS, + Aircraft.LandingGear.MAIN_GEAR_MASS, + Aircraft.LandingGear.NOSE_GEAR_MASS, + Aircraft.Nacelle.MASS, + Aircraft.Propulsion.TOTAL_ENGINE_OIL_MASS, + Aircraft.VerticalTail.MASS_SCALER, + Aircraft.VerticalTail.WETTED_AREA_SCALER, + Aircraft.Wing.MASS, + Aircraft.Wing.SHEAR_CONTROL_MASS, + Aircraft.Wing.SURFACE_CONTROL_MASS, + Aircraft.Wing.WETTED_AREA, +] + +initial_guesses = { + # initial_guesses is a dictionary that contains values used to initialize the trajectory + 'actual_takeoff_mass': 0, + 'rotation_mass': .99, + 'fuel_burn_per_passenger_mile': 0.1, + 'cruise_mass_final': 0, + 'flight_duration': 0, + 'time_to_climb': 0, + 'climb_range': 0, + 'reserves': 0 +} + + +def _setup_F2A_parser(parser): + ''' + Set up the subparser for the Fortran_to_aviary tool. + + Parameters + ---------- + parser : argparse subparser + The parser we're adding options to. + ''' + parser.add_argument( + "input_deck", + type=str, + nargs=1, + help="Filename of vehicle input deck, including partial or complete path.", + ) + parser.add_argument( + "-o", + "--out_file", + default=None, + help="Filename for converted input deck, including partial or complete path." + ) + parser.add_argument( + "-l", + "--legacy_code", + type=LegacyCode, + help="Name of the legacy code the deck originated from", + choices=list(LegacyCode), + required=True + ) + parser.add_argument( + "-d", + "--defaults_deck", + default=None, + help="Deck of default values for unspecified variables" + ) + parser.add_argument( + "--force", + action="store_true", + help="Allow overwriting existing output files", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Enable verbose print statements", + ) + parser.add_argument( + "-vv", + "--very_verbose", + action="store_true", + help="Enable debug print statements", + ) + + +def _exec_F2A(args, user_args): + # check if args.input_deck is a list, if so, use the first element + if isinstance(args.input_deck, list): + args.input_deck = args.input_deck[0] + filepath = args.input_deck + + if args.very_verbose is True: + verbosity = Verbosity.DEBUG + elif args.verbose is True: + verbosity = Verbosity.VERBOSE + else: + verbosity = Verbosity.BRIEF + + create_aviary_deck(filepath, args.legacy_code, args.defaults_deck, + args.out_file, args.force, verbosity) diff --git a/aviary/utils/test/test_fortran_to_aviary.py b/aviary/utils/test/test_fortran_to_aviary.py new file mode 100644 index 000000000..0db23088c --- /dev/null +++ b/aviary/utils/test/test_fortran_to_aviary.py @@ -0,0 +1,101 @@ +import unittest +from pathlib import Path + +from aviary.utils.functions import get_path +from openmdao.utils.testing_utils import use_tempdirs + +from aviary.utils.fortran_to_aviary import LegacyCode, _exec_F2A + + +class DummyArgs(object): + def __init__(self): + self.input_deck = None + self.out_file = None + self.legacy_code = None + self.defaults_deck = False + self.force = False + self.verbose = False + self.very_verbose = False + + +@use_tempdirs +class TestFortranToAviary(unittest.TestCase): + def prepare_and_run(self, filepath, out_file=None, legacy_code=LegacyCode.GASP): + args = DummyArgs() + + # Specify the input file and the legacy code + args.input_deck = filepath + + # Specify the output file + filename = filepath.split('.')[0]+'.csv' + if not out_file: + args.out_file = Path.cwd() / Path('TEST_'+filename) + else: + args.out_file = Path(out_file) + args.legacy_code = legacy_code + + # Execute the conversion + _exec_F2A(args, None) + + def compare_files(self, filepath, skip_list=[]): + """ + Compares the converted file with a validation file. + + Use the `skip_list` input to specify strings that are in lines you want + to skip. This is useful for skipping data that Aviary might need but + Fortran-based tools do not. + """ + filename = filepath.split('.')[0]+'.csv' + + validation_data = get_path(filename) + + # Open the converted and validation files + with open('TEST_'+filename, 'r') as f_in, open(validation_data, 'r') as expected: + for line in f_in: + if any(s in line for s in skip_list): + break + # Remove whitespace and compare + expected_line = ''.join(expected.readline().split()) + line_no_whitespace = ''.join(line.split()) + + # Assert that the lines are equal + try: + self.assertEqual(line_no_whitespace.count(expected_line), 1) + + except Exception as error: + exc_string = f'Error: {filename}\nFound: {line_no_whitespace}\nExpected: {expected_line}' + raise Exception(exc_string) + + def test_large_single_aisle(self): + filepath = 'models/large_single_aisle_1/large_single_aisle_1_GwGm.dat' + + self.prepare_and_run(filepath) + self.compare_files(filepath) + + def test_small_single_aisle(self): + filepath = 'models/small_single_aisle/small_single_aisle_GwGm.dat' + + self.prepare_and_run(filepath) + self.compare_files(filepath) + + def test_diff_configuration(self): + filepath = 'models/test_aircraft/converter_configuration_test_data_GwGm.dat' + + self.prepare_and_run(filepath) + self.compare_files(filepath) + + def test_N3CC(self): + # Note: The csv comparison file N3CC_generic_low_speed_polars_FLOPSinp.csv was generated using the fortran-to-Aviary converter + # and was not evaluated for comparison to the original. Thus, until this file is evaluated, this test is purely a regression + # test. + + filepath = 'models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.txt' + out_file = Path.cwd() / 'TEST_models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.csv' + + self.prepare_and_run(filepath, out_file=out_file, legacy_code=LegacyCode.FLOPS) + self.compare_files( + 'models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.csv') + + +if __name__ == "__main__": + unittest.main() From 4bec5cf2f6c69f6e641f1ac80983a2fd35c0260f Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Wed, 27 Mar 2024 13:40:22 -0400 Subject: [PATCH 07/13] Revert "fixed case-sensitive changes not being applied" This reverts commit e46e5e0ab2172c7748c20adbc3cb748a766b2044. --- aviary/interface/cmd_entry_points.py | 2 +- aviary/utils/fortran_to_aviary.py | 632 -------------------- aviary/utils/test/test_fortran_to_aviary.py | 101 ---- 3 files changed, 1 insertion(+), 734 deletions(-) delete mode 100644 aviary/utils/fortran_to_aviary.py delete mode 100644 aviary/utils/test/test_fortran_to_aviary.py diff --git a/aviary/interface/cmd_entry_points.py b/aviary/interface/cmd_entry_points.py index 647a056c7..f927b895f 100644 --- a/aviary/interface/cmd_entry_points.py +++ b/aviary/interface/cmd_entry_points.py @@ -4,7 +4,7 @@ import aviary from aviary.interface.methods_for_level1 import _exec_level1, _setup_level1_parser -from aviary.utils.fortran_to_aviary import _exec_F2A, _setup_F2A_parser +from aviary.utils.Fortran_to_Aviary import _exec_F2A, _setup_F2A_parser from aviary.utils.engine_deck_conversion import _exec_EDC, _setup_EDC_parser, EDC_description from aviary.visualization.dashboard import _dashboard_setup_parser, _dashboard_cmd from aviary.interface.graphical_input import _exec_flight_profile, _setup_flight_profile_parser diff --git a/aviary/utils/fortran_to_aviary.py b/aviary/utils/fortran_to_aviary.py deleted file mode 100644 index 884e39b04..000000000 --- a/aviary/utils/fortran_to_aviary.py +++ /dev/null @@ -1,632 +0,0 @@ -""" -Fortran_to_Aviary.py is used to read in Fortran based vehicle decks and convert them to Aviary decks. - -FLOPS, GASP, or Aviary names can be used for variables (Ex WG or Mission:Design:GROSS_MASS) -When specifying variables from FORTRAN, they should be in the appropriate NAMELIST. -Aviary variable names should be specified outside any NAMELISTS. -Names are not case-sensitive. -Units can be specified using any of the openMDAO valid units. -Comments can be added using ! -Lists can be entered by separating values with commas. -Individual list elements can be specified by adding an index after the variable name. -(NOTE: 1 indexing is used inside NAMELISTS, while 0 indexing is used outside NAMELISTS) - -Example inputs: -aircraft:fuselage:pressure_differential = .5, atm !DELP in GASP, but using atmospheres instead of psi -ARNGE(1) = 3600 !target range in nautical miles -pyc_phases = taxi, groundroll, rotation, landing -""" - -import csv -import re -from enum import Enum -from pathlib import Path - -from openmdao.utils.units import valid_units - -from aviary.utils.functions import convert_strings_to_data -from aviary.utils.named_values import NamedValues, get_items -from aviary.variable_info.variable_meta_data import _MetaData -from aviary.variable_info.variables import Aircraft, Mission -from aviary.variable_info.enums import LegacyCode, Verbosity -from aviary.utils.functions import get_path -from aviary.utils.legacy_code_data.deprecated_vars import flops_deprecated_vars, gasp_deprecated_vars - - -FLOPS = LegacyCode.FLOPS -GASP = LegacyCode.GASP - - -def create_aviary_deck(fortran_deck: str, legacy_code=None, defaults_deck=None, - out_file=None, force=False, verbosity=Verbosity.BRIEF): - ''' - Create an Aviary CSV file from a Fortran input deck - Required input is the filepath to the input deck and legacy code. Optionally, a - deck of default values can be specified, this is useful if an input deck - assumes certain values for any unspecified variables - If an invalid filepath is given, pre-packaged resources will be checked for - input decks with a matching name. - ''' - # TODO generate both an Aviary input file and a phase_info file - - vehicle_data = {'input_values': NamedValues(), 'unused_values': NamedValues(), - 'initial_guesses': initial_guesses, 'verbosity': verbosity} - - fortran_deck: Path = get_path(fortran_deck, verbose=False) - - if out_file: - out_file = Path(out_file) - else: - name = fortran_deck.stem - out_file: Path = fortran_deck.parent.resolve().joinpath(name + '_converted.csv') - - if legacy_code is GASP: - default_extension = '.dat' - deprecated_vars = gasp_deprecated_vars - elif legacy_code is FLOPS: - default_extension = '.txt' - deprecated_vars = flops_deprecated_vars - - if not defaults_deck: - defaults_filename = legacy_code.value.lower() + '_default_values' + default_extension - defaults_deck = Path(__file__).parent.resolve().joinpath( - 'legacy_code_data', defaults_filename) - - # create dictionary to convert legacy code variables to Aviary variables - aviary_variable_dict = generate_aviary_names([legacy_code.value]) - - if defaults_deck: # If defaults are specified, initialize the vehicle with them - vehicle_data = input_parser(defaults_deck, vehicle_data, - aviary_variable_dict, deprecated_vars, legacy_code) - - vehicle_data = input_parser(fortran_deck, vehicle_data, - aviary_variable_dict, deprecated_vars, legacy_code) - if legacy_code is GASP: - vehicle_data = update_gasp_options(vehicle_data) - - if not out_file.is_file(): # default outputted file to be in same directory as input - out_file = fortran_deck.parent / out_file - - if out_file.is_file(): - if force: - print(f'Overwriting existing file: {out_file.name}') - else: - raise RuntimeError(f'{out_file} already exists. Choose a new name or enable ' - '--force') - else: - # create any directories defined by the new filename if they don't already exist - out_file.parent.mkdir(parents=True, exist_ok=True) - print('Writing to:', out_file) - - # open the file in write mode - with open(out_file, 'w', newline='') as f: - writer = csv.writer(f) - - # Values that have been successfully translated to Aviary variables - writer.writerow(['# Input Values']) - for var, (val, units) in sorted(vehicle_data['input_values']): - writer.writerow([var] + val + [units]) - if legacy_code is FLOPS: - EOM = 'height_energy' - mass = 'FLOPS' - if legacy_code is GASP: - EOM = '2DOF' - mass = 'GASP' - writer.writerow(['settings:equations_of_motion'] + [EOM]) - writer.writerow(['settings:mass_method'] + [mass]) - - if legacy_code is GASP: - # Values used in initial guessing of the trajectory - writer.writerow([]) - writer.writerow(['# Initial Guesses']) - for var_name in sorted(vehicle_data['initial_guesses']): - row = [var_name, vehicle_data['initial_guesses'][var_name]] - writer.writerow(row) - - # Values that were not successfully converted - writer.writerow([]) - writer.writerow(['# Unconverted Values']) - for var, (val, _) in sorted(vehicle_data['unused_values']): - writer.writerow([var] + val) - - -def input_parser(fortran_deck, vehicle_data, alternate_names, unused_vars, legacy_code): - ''' - input_parser will modify the values in the vehicle_data dictionary using the data in the - fortran_deck. - Lines are read one by one, comments are removed, and namelists are tracked. - Lines with multiple variable-data pairs are supported, but the last value per variable must - be followed by a trailing comma. - ''' - with open(fortran_deck, 'r') as f_in: - current_namelist = current_tag = '' - for line in f_in: - terminate_namelist = False - - tmp = [*line.split('!', 1), ''] - line, comment = tmp[0], tmp[1] # anything after the first ! is a comment - - # remove all white space and trailing commas - line = ''.join(line.split()).rstrip(',') - if len(line.split()) == 0: - continue # skip line if it contains only white space - - # Track when namelists are opened and closed - if (line.lstrip()[0] in ['$', '&']) and current_tag == '': - current_tag = line.lstrip()[0] - current_namelist = line.split(current_tag)[1].split()[0] + '.' - elif (line.lstrip()[0] == current_tag) or (line.rstrip()[-1] == '/'): - line = line.replace('/', '') - terminate_namelist = True - - number_of_variables = line.count('=') - if number_of_variables == 1: - # get the first element and remove white space - var_name = ''.join(line.split('=')[0].split()) - # everything after the = is the data - data = line.split('=')[1] - try: - vehicle_data = process_and_store_data( - data, var_name, legacy_code, current_namelist, alternate_names, vehicle_data, unused_vars, comment) - except Exception as err: - if current_namelist == '': - raise RuntimeError(line + ' could not be parsed successfully.' - '\nIf this was intended as a comment, ' - 'add an "!" at the beginning of the line.') from err - else: - raise err - elif number_of_variables > 1: - sub_line = line.split('=') # split the line at each = - var_name = sub_line[0] # the first element is the first name - for ii in range(number_of_variables): - # Each of the following elements contains all of the data for the current variable - # and the last element is the name of the next variable - sub_list = sub_line[ii+1].split(',') - if ii+1 < number_of_variables: - next_var_name = sub_list.pop() - if not next_var_name[0].isalpha(): - index = next((i for i, c in enumerate( - next_var_name) if c.isalpha()), len(next_var_name)) - sub_list.append(next_var_name[:index]) - next_var_name = next_var_name[index:] - - data = ','.join(sub_list) - try: - vehicle_data = process_and_store_data( - data, var_name, legacy_code, current_namelist, alternate_names, vehicle_data, unused_vars, comment) - except Exception as err: - if current_namelist == '': - raise RuntimeError(line + ' could not be parsed successfully.' - '\nIf this was intended as a comment, ' - 'add an "!" at the beginning of the line.') from err - else: - raise err - var_name = next_var_name - - if terminate_namelist: - current_namelist = current_tag = '' - - return vehicle_data - - -def process_and_store_data(data, var_name, legacy_code, current_namelist, alternate_names, vehicle_data, unused_vars, comment=''): - ''' - process_and_store_data takes in a string that contains the data, the current variable's name and - namelist, the dictionary of alternate names, and the current vehicle data. - It will convert the string of data into a list, get units, check whether the data specified is - part of a list or a single element, and update the current name to it's equivalent Aviary name. - The variables are also sorted based on whether they will set an Aviary variable or they are for initial guessing - ''' - - guess_names = list(initial_guesses.keys()) - var_ind = data_units = None - skip_variable = False - # skip any variables that shouldn't get converted - if re.search(current_namelist+var_name+'\Z', str(unused_vars), re.IGNORECASE): - return vehicle_data - # remove any elements that are empty (caused by trailing commas or extra commas) - data_list = [dat for dat in data.split(',') if dat != ''] - if len(data_list) > 0: - if valid_units(data_list[-1]): - # if the last element is a unit, remove it from the list and update the variable's units - data_units = data_list.pop() - var_values = convert_strings_to_data(data_list) - else: - skip_variable = True - var_values = [] - - if '(' in var_name: # some GASP lists are given as individual elements - # get the target index (Fortran uses 1 indexing, Python uses 0 indexing) - fortran_offset = 1 if current_namelist else 0 - var_ind = int(var_name.split('(')[1].split(')')[0])-fortran_offset - var_name = var_name.split('(')[0] # remove the index formatting - - list_of_equivalent_aviary_names = update_name( - alternate_names, current_namelist+var_name, vehicle_data['verbosity']) - for name in list_of_equivalent_aviary_names: - if not skip_variable: - if name in guess_names and legacy_code is GASP: - # all initial guesses take only a single value - vehicle_data['initial_guesses'][name] = float(var_values[0]) - continue - - elif name in _MetaData: - vehicle_data['input_values'] = set_value(name, var_values, vehicle_data['input_values'], - var_ind=var_ind, units=data_units) - continue - - vehicle_data['unused_values'] = set_value(name, var_values, vehicle_data['unused_values'], - var_ind=var_ind, units=data_units) - if vehicle_data['verbosity'].value >= 2: - print('Unused:', name, var_values, comment) - - return vehicle_data - - -def set_value(var_name, var_value, value_dict: NamedValues, var_ind=None, units=None): - ''' - set_value will update the current value of a variable in a value dictionary that contains a value - and it's associated units. - If units are specified for the new value, they will be used, otherwise the current units in the - value dictionary or the default units from _MetaData are used. - If the new variable is part of a list, the current list will be extended if needed. - ''' - - if var_name in value_dict: - current_value, units = value_dict.get_item(var_name) - else: - current_value = None - if var_name in _MetaData: - units = _MetaData[var_name]['units'] - else: - units = 'unitless' - if not units: - units = 'unitless' - - if var_ind != None: - # if an index is specified, use it, otherwise store the input as the whole value - if isinstance(current_value, list): - max_ind = len(current_value) - 1 - if var_ind > max_ind: - current_value.extend((var_ind-max_ind)*[0]) - else: - current_value = [current_value]+[0]*var_ind - current_value[var_ind] = var_value[0] - value_dict.set_val(var_name, current_value, units) - else: - value_dict.set_val(var_name, var_value, units) - return value_dict - - -def generate_aviary_names(code_bases): - ''' - Create a dictionary for each of the specified Fortran code bases to map to the Aviary - variable names. Each dictionary of Aviary names will have a list of Fortran names for - each variable - ''' - - alternate_names = {} - for code_base in code_bases: - alternate_names[code_base] = {} - for key in _MetaData.keys(): - historical_dict = _MetaData[key]['historical_name'] - if historical_dict and code_base in historical_dict: - alt_name = _MetaData[key]['historical_name'][code_base] - if isinstance(alt_name, str): - alt_name = [alt_name] - alternate_names[code_base][key] = alt_name - return alternate_names - - -def update_name(alternate_names, var_name, verbosity=Verbosity.BRIEF): - '''update_name will convert a Fortran name to a list of equivalent Aviary names.''' - - all_equivalent_names = [] - for code_base in alternate_names.keys(): - for key, list_of_names in alternate_names[code_base].items(): - if list_of_names is not None: - if any([re.search(var_name+r'\Z', altname, re.IGNORECASE) for altname in list_of_names]): - all_equivalent_names.append(key) - - # if there are no equivalent variable names, return the original name - if len(all_equivalent_names) == 0: - if verbosity.value >= 2: - print('passing: ', var_name) - all_equivalent_names = [var_name] - return all_equivalent_names - - -def update_gasp_options(vehicle_data): - """ - Handles variables that are affected by the values of others - """ - input_values: NamedValues = vehicle_data['input_values'] - - flap_types = ["plain", "split", "single_slotted", "double_slotted", - "triple_slotted", "fowler", "double_slotted_fowler"] - - ## PROBLEM TYPE ## - # if multiple values of target_range are specified, use the one that corresponds to the problem_type - design_range, distance_units = input_values.get_item(Mission.Design.RANGE) - try: - problem_type = input_values.get_val('problem_type')[0] - except KeyError: - problem_type = 'sizing' - - if isinstance(design_range, list): - # if the design range target_range value is 0, set the problem_type to fallout - if design_range[0] == 0: - problem_type = 'fallout' - input_values.set_val('problem_type', [problem_type]) - design_range = 0 - if problem_type == 'sizing': - design_range = design_range[0] - elif problem_type == 'alternate': - design_range = design_range[2] - elif problem_type == 'fallout': - design_range = 0 - else: - if design_range == 0: - input_values.set_val('problem_type', ['fallout']) - input_values.set_val(Mission.Design.RANGE, [design_range], distance_units) - - ## STRUT AND FOLD ## - strut_loc = input_values.get_val(Aircraft.Strut.ATTACHMENT_LOCATION, 'ft')[0] - folded_span = input_values.get_val(Aircraft.Wing.FOLDED_SPAN, 'ft')[0] - - if strut_loc == 0: - input_values.set_val(Aircraft.Wing.HAS_STRUT, [False], 'unitless') - else: - input_values.set_val(Aircraft.Wing.HAS_STRUT, [True], 'unitless') - if folded_span == 0: - input_values.set_val(Aircraft.Wing.HAS_FOLD, [False], 'unitless') - else: - input_values.set_val(Aircraft.Wing.HAS_FOLD, [True], 'unitless') - - if strut_loc < 0: - input_values.set_val(Aircraft.Wing.HAS_FOLD, [True], 'unitless') - input_values.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, [False], 'unitless') - strut_loc = abs(strut_loc) - - if strut_loc < 1: - input_values.set_val(Aircraft.Strut.ATTACHMENT_LOCATION_DIMENSIONLESS, - [strut_loc], 'unitless') - input_values.set_val(Aircraft.Strut.DIMENSIONAL_LOCATION_SPECIFIED, [ - False], 'unitless') - else: - input_values.set_val(Aircraft.Strut.ATTACHMENT_LOCATION, [strut_loc], 'ft') - input_values.set_val( - Aircraft.Strut.DIMENSIONAL_LOCATION_SPECIFIED, [True], 'unitless') - - if input_values.get_val(Aircraft.Wing.HAS_FOLD)[0]: - if not input_values.get_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION)[0]: - input_values.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, - [True], 'unitless') - else: - if input_values.get_val(Aircraft.Wing.FOLDED_SPAN, 'ft')[0] > 1: - input_values.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, - [True], 'unitless') - else: - input_values.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, - [False], 'unitless') - else: - input_values.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, [True], 'unitless') - input_values.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, [ - False], 'unitless') - - ## FLAPS ## - flap_type = input_values.get_val(Aircraft.Wing.FLAP_TYPE)[0] - if not isinstance(flap_type, str): - flap_type = flap_types[flap_type-1] - input_values.set_val(Aircraft.Wing.FLAP_TYPE, [flap_type]) - flap_ind = flap_types.index(flap_type) - if input_values.get_val(Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT)[0] <= 0: - input_values.set_val(Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, - [[0.62, 1.0, 0.733, 1.2, 1.32, 0.633, 0.678][flap_ind]]) - if input_values.get_val(Aircraft.Wing.OPTIMUM_FLAP_DEFLECTION, 'deg')[0] == 0: - input_values.set_val(Aircraft.Wing.OPTIMUM_FLAP_DEFLECTION, - [[60, 60, 40, 55, 55, 30, 30][flap_ind]], 'deg') - if input_values.get_val(Aircraft.Wing.FLAP_LIFT_INCREMENT_OPTIMUM)[0] == 0: - input_values.set_val(Aircraft.Wing.FLAP_LIFT_INCREMENT_OPTIMUM, - [[.9, .8, 1.18, 1.4, 1.6, 1.67, 2.25][flap_ind]]) - if input_values.get_val(Aircraft.Wing.FLAP_DRAG_INCREMENT_OPTIMUM)[0] == 0: - input_values.set_val(Aircraft.Wing.FLAP_DRAG_INCREMENT_OPTIMUM, - [[.12, .23, .13, .23, .23, .1, .15][flap_ind]]) - - res = input_values.get_val(Aircraft.Design.RESERVE_FUEL_ADDITIONAL, units='lbm')[0] - if res <= 0: - input_values.set_val(Aircraft.Design.RESERVE_FUEL_ADDITIONAL, [0], units='lbm') - input_values.set_val(Aircraft.Design.RESERVE_FUEL_FRACTION, - [-res], units='unitless') - elif res >= 10: - input_values.set_val(Aircraft.Design.RESERVE_FUEL_FRACTION, - [0], units='unitless') - else: - ValueError('"FRESF" is not valid between 0 and 10.') - - vehicle_data['input_values'] = input_values - return vehicle_data - - -def update_flops_options(vehicle_data): - """ - Handles variables that are affected by the values of others - """ - input_values: NamedValues = vehicle_data['input_values'] - - for var_name in flops_scalar_variables.items(): - update_flops_scalar_variables(var_name, input_values) - - # TWR <= 0 is not valid in Aviary (parametric variation) - if Aircraft.Design.THRUST_TO_WEIGHT_RATIO in input_values: - if input_values.get_val(Aircraft.Design.THRUST_TO_WEIGHT_RATIO) <= 0: - input_values.delete(Aircraft.Design.THRUST_TO_WEIGHT_RATIO) - - # WSR - - # Additional mass fraction scalar set to zero to not add mass twice - if Aircraft.Engine.ADDITIONAL_MASS_FRACTION in input_values: - if input_values.get_val(Aircraft.Engine.ADDITIONAL_MASS_FRACTION) >= 1: - input_values.set_val(Aircraft.Engine.ADDITIONAL_MASS, - input_values.get_val( - Aircraft.Engine.ADDITIONAL_MASS_FRACTION), - 'lbm') - input_values.set_val(Aircraft.Engine.ADDITIONAL_MASS_FRACTION, 0.0) - - # Miscellaneous propulsion mass trigger point 1 instead of 5 - if Aircraft.Propulsion.MISC_MASS_SCALER in input_values: - if input_values.get_val(Aircraft.Propulsion.MISC_MASS_SCALER) >= 1: - input_values.set_val(Aircraft.Propulsion.TOTAL_MISC_MASS, - input_values.get_val( - Aircraft.Propulsion.MISC_MASS_SCALER), - 'lbm') - input_values.set_val(Aircraft.Propulsion.MISC_MASS_SCALER, 0.0) - - vehicle_data['input_values'] = input_values - return vehicle_data - - -def update_flops_scalar_variables(var_name, input_values: NamedValues): - # The following parameters are used to modify or override - # internally computed weights for various components as follows: - # < 0., negative of starting weight which will be modified - # as appropriate during optimization or parametric - # variation, lb - # = 0., no weight for that component - # > 0. but < 5., scale factor applied to internally - # computed weight - # > 5., actual fixed weight for component, lb - # Same rules also applied to various other FLOPS scalar parameters - scalar_name = var_name + '_scaler' - if scalar_name not in input_values: - return - scalar_value = input_values[scalar_name] - if scalar_value <= 0: - input_values.delete(scalar_name) - elif scalar_value < 5: - return - elif scalar_value > 5: - input_values.set_val(var_name, scalar_value, 'lbm') - input_values.set_val(scalar_name, 1.0) - - -# list storing information on Aviary variables that are split from single -# FLOPS variables that use the same value-based branching behavior -flops_scalar_variables = [ - Aircraft.AirConditioning.MASS, - Aircraft.AntiIcing.MASS, - Aircraft.APU.MASS, - Aircraft.Avionics.MASS, - Aircraft.Canard.MASS, - Aircraft.Canard.WETTED_AREA, - Aircraft.CrewPayload.CARGO_CONTAINER_MASS, - Aircraft.CrewPayload.FLIGHT_CREW_MASS, - Aircraft.CrewPayload.NON_FLIGHT_CREW_MASS, - Aircraft.CrewPayload.PASSENGER_SERVICE_MASS, - Aircraft.Design.EMPTY_MASS_MARGIN, - Aircraft.Electrical.MASS, - Aircraft.Engine.THRUST_REVERSERS_MASS, - Aircraft.Fins.MASS, - Aircraft.Fuel.FUEL_SYSTEM_MASS, - Aircraft.Fuel.UNUSABLE_FUEL_MASS, - Aircraft.Furnishings.MASS, - Aircraft.Fuselage.MASS, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.HorizontalTail.MASS, - Aircraft.HorizontalTail.WETTED_AREA, - Aircraft.Hydraulics.MASS, - Aircraft.Instruments.MASS, - Aircraft.LandingGear.MAIN_GEAR_MASS, - Aircraft.LandingGear.NOSE_GEAR_MASS, - Aircraft.Nacelle.MASS, - Aircraft.Propulsion.TOTAL_ENGINE_OIL_MASS, - Aircraft.VerticalTail.MASS_SCALER, - Aircraft.VerticalTail.WETTED_AREA_SCALER, - Aircraft.Wing.MASS, - Aircraft.Wing.SHEAR_CONTROL_MASS, - Aircraft.Wing.SURFACE_CONTROL_MASS, - Aircraft.Wing.WETTED_AREA, -] - -initial_guesses = { - # initial_guesses is a dictionary that contains values used to initialize the trajectory - 'actual_takeoff_mass': 0, - 'rotation_mass': .99, - 'fuel_burn_per_passenger_mile': 0.1, - 'cruise_mass_final': 0, - 'flight_duration': 0, - 'time_to_climb': 0, - 'climb_range': 0, - 'reserves': 0 -} - - -def _setup_F2A_parser(parser): - ''' - Set up the subparser for the Fortran_to_aviary tool. - - Parameters - ---------- - parser : argparse subparser - The parser we're adding options to. - ''' - parser.add_argument( - "input_deck", - type=str, - nargs=1, - help="Filename of vehicle input deck, including partial or complete path.", - ) - parser.add_argument( - "-o", - "--out_file", - default=None, - help="Filename for converted input deck, including partial or complete path." - ) - parser.add_argument( - "-l", - "--legacy_code", - type=LegacyCode, - help="Name of the legacy code the deck originated from", - choices=list(LegacyCode), - required=True - ) - parser.add_argument( - "-d", - "--defaults_deck", - default=None, - help="Deck of default values for unspecified variables" - ) - parser.add_argument( - "--force", - action="store_true", - help="Allow overwriting existing output files", - ) - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Enable verbose print statements", - ) - parser.add_argument( - "-vv", - "--very_verbose", - action="store_true", - help="Enable debug print statements", - ) - - -def _exec_F2A(args, user_args): - # check if args.input_deck is a list, if so, use the first element - if isinstance(args.input_deck, list): - args.input_deck = args.input_deck[0] - filepath = args.input_deck - - if args.very_verbose is True: - verbosity = Verbosity.DEBUG - elif args.verbose is True: - verbosity = Verbosity.VERBOSE - else: - verbosity = Verbosity.BRIEF - - create_aviary_deck(filepath, args.legacy_code, args.defaults_deck, - args.out_file, args.force, verbosity) diff --git a/aviary/utils/test/test_fortran_to_aviary.py b/aviary/utils/test/test_fortran_to_aviary.py deleted file mode 100644 index 0db23088c..000000000 --- a/aviary/utils/test/test_fortran_to_aviary.py +++ /dev/null @@ -1,101 +0,0 @@ -import unittest -from pathlib import Path - -from aviary.utils.functions import get_path -from openmdao.utils.testing_utils import use_tempdirs - -from aviary.utils.fortran_to_aviary import LegacyCode, _exec_F2A - - -class DummyArgs(object): - def __init__(self): - self.input_deck = None - self.out_file = None - self.legacy_code = None - self.defaults_deck = False - self.force = False - self.verbose = False - self.very_verbose = False - - -@use_tempdirs -class TestFortranToAviary(unittest.TestCase): - def prepare_and_run(self, filepath, out_file=None, legacy_code=LegacyCode.GASP): - args = DummyArgs() - - # Specify the input file and the legacy code - args.input_deck = filepath - - # Specify the output file - filename = filepath.split('.')[0]+'.csv' - if not out_file: - args.out_file = Path.cwd() / Path('TEST_'+filename) - else: - args.out_file = Path(out_file) - args.legacy_code = legacy_code - - # Execute the conversion - _exec_F2A(args, None) - - def compare_files(self, filepath, skip_list=[]): - """ - Compares the converted file with a validation file. - - Use the `skip_list` input to specify strings that are in lines you want - to skip. This is useful for skipping data that Aviary might need but - Fortran-based tools do not. - """ - filename = filepath.split('.')[0]+'.csv' - - validation_data = get_path(filename) - - # Open the converted and validation files - with open('TEST_'+filename, 'r') as f_in, open(validation_data, 'r') as expected: - for line in f_in: - if any(s in line for s in skip_list): - break - # Remove whitespace and compare - expected_line = ''.join(expected.readline().split()) - line_no_whitespace = ''.join(line.split()) - - # Assert that the lines are equal - try: - self.assertEqual(line_no_whitespace.count(expected_line), 1) - - except Exception as error: - exc_string = f'Error: {filename}\nFound: {line_no_whitespace}\nExpected: {expected_line}' - raise Exception(exc_string) - - def test_large_single_aisle(self): - filepath = 'models/large_single_aisle_1/large_single_aisle_1_GwGm.dat' - - self.prepare_and_run(filepath) - self.compare_files(filepath) - - def test_small_single_aisle(self): - filepath = 'models/small_single_aisle/small_single_aisle_GwGm.dat' - - self.prepare_and_run(filepath) - self.compare_files(filepath) - - def test_diff_configuration(self): - filepath = 'models/test_aircraft/converter_configuration_test_data_GwGm.dat' - - self.prepare_and_run(filepath) - self.compare_files(filepath) - - def test_N3CC(self): - # Note: The csv comparison file N3CC_generic_low_speed_polars_FLOPSinp.csv was generated using the fortran-to-Aviary converter - # and was not evaluated for comparison to the original. Thus, until this file is evaluated, this test is purely a regression - # test. - - filepath = 'models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.txt' - out_file = Path.cwd() / 'TEST_models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.csv' - - self.prepare_and_run(filepath, out_file=out_file, legacy_code=LegacyCode.FLOPS) - self.compare_files( - 'models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.csv') - - -if __name__ == "__main__": - unittest.main() From 232d1667dd17f6adafa31901ddd2e45c229d107b Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Wed, 27 Mar 2024 14:15:04 -0400 Subject: [PATCH 08/13] retry case sensitive file rename --- aviary/utils/{Fortran_to_Aviary.py => fortran_to_aviary.py} | 0 .../test/{test_Fortran_to_Aviary.py => test_fortran_to_aviary.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename aviary/utils/{Fortran_to_Aviary.py => fortran_to_aviary.py} (100%) rename aviary/utils/test/{test_Fortran_to_Aviary.py => test_fortran_to_aviary.py} (100%) diff --git a/aviary/utils/Fortran_to_Aviary.py b/aviary/utils/fortran_to_aviary.py similarity index 100% rename from aviary/utils/Fortran_to_Aviary.py rename to aviary/utils/fortran_to_aviary.py diff --git a/aviary/utils/test/test_Fortran_to_Aviary.py b/aviary/utils/test/test_fortran_to_aviary.py similarity index 100% rename from aviary/utils/test/test_Fortran_to_Aviary.py rename to aviary/utils/test/test_fortran_to_aviary.py From 5c688b4bb5a84cb3d7606b0bbf0fdd09154c0118 Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Wed, 27 Mar 2024 14:17:48 -0400 Subject: [PATCH 09/13] import update --- aviary/interface/cmd_entry_points.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aviary/interface/cmd_entry_points.py b/aviary/interface/cmd_entry_points.py index f927b895f..647a056c7 100644 --- a/aviary/interface/cmd_entry_points.py +++ b/aviary/interface/cmd_entry_points.py @@ -4,7 +4,7 @@ import aviary from aviary.interface.methods_for_level1 import _exec_level1, _setup_level1_parser -from aviary.utils.Fortran_to_Aviary import _exec_F2A, _setup_F2A_parser +from aviary.utils.fortran_to_aviary import _exec_F2A, _setup_F2A_parser from aviary.utils.engine_deck_conversion import _exec_EDC, _setup_EDC_parser, EDC_description from aviary.visualization.dashboard import _dashboard_setup_parser, _dashboard_cmd from aviary.interface.graphical_input import _exec_flight_profile, _setup_flight_profile_parser From 3947a9796b043d9f3e5242881ae1d6d7c88f05e7 Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Wed, 27 Mar 2024 15:10:47 -0400 Subject: [PATCH 10/13] added header to input deck converter standardized how enums are handled by converter cmd args --- aviary/utils/engine_deck_conversion.py | 14 ++++++- aviary/utils/fortran_to_aviary.py | 46 ++++++++++++--------- aviary/utils/test/test_fortran_to_aviary.py | 3 +- aviary/variable_info/enums.py | 3 ++ aviary/variable_info/variable_meta_data.py | 2 +- 5 files changed, 44 insertions(+), 24 deletions(-) diff --git a/aviary/utils/engine_deck_conversion.py b/aviary/utils/engine_deck_conversion.py index f8f7d9e16..240b13ed3 100644 --- a/aviary/utils/engine_deck_conversion.py +++ b/aviary/utils/engine_deck_conversion.py @@ -5,7 +5,6 @@ import itertools from datetime import datetime from enum import Enum -from pathlib import Path import numpy as np import openmdao.api as om @@ -25,6 +24,9 @@ class EngineDeckType(Enum): GASP = 'GASP' GASP_TP = 'GASP_TP' + def __str__(self): + return self.value + MACH = EngineModelVariables.MACH ALTITUDE = EngineModelVariables.ALTITUDE @@ -98,8 +100,16 @@ def EngineDeckConverter(input_file, output_file, data_format: EngineDeckType): data_file = get_path(input_file) comments.append(f'# created {timestamp} by {user}') + legacy_code = data_format.value + engine_type = 'engine' + if legacy_code == 'GASP_TP': + engine_type = 'turboshaft engine' + if legacy_code == 'GASP_TP': + legacy_code = 'GASP' + comments.append( f'# {data_format.value}-derived engine deck converted from {data_file.name}') + if data_format == EngineDeckType.FLOPS: header = {key: default_units[key] for key in flops_keys} data = {key: np.array([]) for key in flops_keys} @@ -771,7 +781,7 @@ def _setup_EDC_parser(parser): help='path to engine deck file to be converted') parser.add_argument('output_file', type=str, help='path to file where new converted data will be written') - parser.add_argument('data_format', type=EngineDeckType, choices=list(EngineDeckType), + parser.add_argument('-f', '--data_format', type=EngineDeckType, choices=list(EngineDeckType), help='data format used by input_file') diff --git a/aviary/utils/fortran_to_aviary.py b/aviary/utils/fortran_to_aviary.py index 884e39b04..aa23db8c3 100644 --- a/aviary/utils/fortran_to_aviary.py +++ b/aviary/utils/fortran_to_aviary.py @@ -19,9 +19,10 @@ import csv import re -from enum import Enum -from pathlib import Path +import getpass +from datetime import datetime +from pathlib import Path from openmdao.utils.units import valid_units from aviary.utils.functions import convert_strings_to_data @@ -54,6 +55,14 @@ def create_aviary_deck(fortran_deck: str, legacy_code=None, defaults_deck=None, fortran_deck: Path = get_path(fortran_deck, verbose=False) + timestamp = datetime.now().strftime('%m/%d/%y at %H:%M') + user = getpass.getuser() + comments = [] + + comments.append(f'# created {timestamp} by {user}') + comments.append( + f'# {legacy_code.value}-derived aircraft input deck converted from {fortran_deck.name}') + if out_file: out_file = Path(out_file) else: @@ -88,7 +97,7 @@ def create_aviary_deck(fortran_deck: str, legacy_code=None, defaults_deck=None, out_file = fortran_deck.parent / out_file if out_file.is_file(): - if force: + if force and verbosity.value >= 1: print(f'Overwriting existing file: {out_file.name}') else: raise RuntimeError(f'{out_file} already exists. Choose a new name or enable ' @@ -96,7 +105,8 @@ def create_aviary_deck(fortran_deck: str, legacy_code=None, defaults_deck=None, else: # create any directories defined by the new filename if they don't already exist out_file.parent.mkdir(parents=True, exist_ok=True) - print('Writing to:', out_file) + if verbosity.value >= 2: + print('Writing to:', out_file) # open the file in write mode with open(out_file, 'w', newline='') as f: @@ -603,16 +613,18 @@ def _setup_F2A_parser(parser): ) parser.add_argument( "-v", - "--verbose", - action="store_true", - help="Enable verbose print statements", - ) - parser.add_argument( - "-vv", - "--very_verbose", - action="store_true", - help="Enable debug print statements", + "--verbosity", + type=Verbosity, + choices=list(Verbosity), + default=1, + help="Set level of print statements", ) + # parser.add_argument( + # "-vv", + # "--very_verbose", + # action="store_true", + # help="Enable debug print statements", + # ) def _exec_F2A(args, user_args): @@ -621,12 +633,8 @@ def _exec_F2A(args, user_args): args.input_deck = args.input_deck[0] filepath = args.input_deck - if args.very_verbose is True: - verbosity = Verbosity.DEBUG - elif args.verbose is True: - verbosity = Verbosity.VERBOSE - else: - verbosity = Verbosity.BRIEF + # convert verbosity from number to enum + verbosity = Verbosity(args.verbosity) create_aviary_deck(filepath, args.legacy_code, args.defaults_deck, args.out_file, args.force, verbosity) diff --git a/aviary/utils/test/test_fortran_to_aviary.py b/aviary/utils/test/test_fortran_to_aviary.py index 0db23088c..ecbb016cc 100644 --- a/aviary/utils/test/test_fortran_to_aviary.py +++ b/aviary/utils/test/test_fortran_to_aviary.py @@ -14,8 +14,7 @@ def __init__(self): self.legacy_code = None self.defaults_deck = False self.force = False - self.verbose = False - self.very_verbose = False + self.verbosity = 1 @use_tempdirs diff --git a/aviary/variable_info/enums.py b/aviary/variable_info/enums.py index e0a27834e..eca400520 100644 --- a/aviary/variable_info/enums.py +++ b/aviary/variable_info/enums.py @@ -179,3 +179,6 @@ class Verbosity(Enum): BRIEF = 1 VERBOSE = 2 DEBUG = 3 + + def __str__(self): + return str(self.value) diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index 9d2c6f15c..5ea532f5f 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -7499,7 +7499,7 @@ desc='Sets how much information Aviary outputs when run. Options include:' '0. QUIET: All output except errors are suppressed' '1. BRIEF: Only important information is output, in human-readable format' - '2. VERBOSE: All avaliable informating is output, in human-readable format' + '2. VERBOSE: All avaliable information is output, in human-readable format' '3. DEBUG: Intermediate status and calculation outputs, no formatting requirement', option=True, types=Verbosity, From 299af9c13c13806bdf682e49ab730fd9a5afdb96 Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Wed, 27 Mar 2024 15:13:25 -0400 Subject: [PATCH 11/13] added missing variable in metadata --- aviary/variable_info/variable_meta_data.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index 5ea532f5f..874c273b2 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -6446,6 +6446,19 @@ 'tolerance)', ) +add_meta_data( + Mission.Constraints.RANGE_RESIDUAL_RESERVE, + meta_data=_MetaData, + historical_name={"GASP": None, + "FLOPS": None, + "LEAPS1": None + }, + units='NM', + desc='residual to make sure aircraft reserve mission range is equal to the targeted ' + 'range, value should be zero at convergence (within acceptable ' + 'tolerance)', +) + # _____ _ # | __ \ (_) # | | | | ___ ___ _ __ _ _ __ From 9f1a198a6e77842bd5d3d99604ae900f9f9c651d Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Wed, 27 Mar 2024 16:00:15 -0400 Subject: [PATCH 12/13] fixed bug with engine conversion header --- aviary/utils/engine_deck_conversion.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aviary/utils/engine_deck_conversion.py b/aviary/utils/engine_deck_conversion.py index 240b13ed3..38c65ec62 100644 --- a/aviary/utils/engine_deck_conversion.py +++ b/aviary/utils/engine_deck_conversion.py @@ -104,11 +104,10 @@ def EngineDeckConverter(input_file, output_file, data_format: EngineDeckType): engine_type = 'engine' if legacy_code == 'GASP_TP': engine_type = 'turboshaft engine' - if legacy_code == 'GASP_TP': legacy_code = 'GASP' comments.append( - f'# {data_format.value}-derived engine deck converted from {data_file.name}') + f'# {data_format.value}-derived {engine_type} deck converted from {data_file.name}') if data_format == EngineDeckType.FLOPS: header = {key: default_units[key] for key in flops_keys} From cc13808e49aa708cbda5c9cbacea2c47c9b68d6e Mon Sep 17 00:00:00 2001 From: jkirk5 Date: Wed, 27 Mar 2024 16:01:04 -0400 Subject: [PATCH 13/13] fixed another header bug --- aviary/utils/engine_deck_conversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aviary/utils/engine_deck_conversion.py b/aviary/utils/engine_deck_conversion.py index 38c65ec62..0c76545eb 100644 --- a/aviary/utils/engine_deck_conversion.py +++ b/aviary/utils/engine_deck_conversion.py @@ -107,7 +107,7 @@ def EngineDeckConverter(input_file, output_file, data_format: EngineDeckType): legacy_code = 'GASP' comments.append( - f'# {data_format.value}-derived {engine_type} deck converted from {data_file.name}') + f'# {legacy_code}-derived {engine_type} deck converted from {data_file.name}') if data_format == EngineDeckType.FLOPS: header = {key: default_units[key] for key in flops_keys}