From 5bd8a827dab2c4cedaf553bf00d6db7173b5078a Mon Sep 17 00:00:00 2001 From: timeeapacala <“t.pacala@tu-berlin.de”> Date: Wed, 20 Nov 2024 16:17:44 +0100 Subject: [PATCH] linting --- weight_and_balance_analysis/main.py | 25 +- .../src/datapostprocessing.py | 74 +- .../src/datapreprocessing.py | 33 +- .../standard/basic/acommodationIO.py | 24 +- .../standard/basic/componentMassIO.py | 66 +- .../standard/basic/massPropertiesIO.py | 69 +- .../standard/basic/methodbasic.py | 981 ++++++++++++------ .../tube_and_wing/standard/basic/tankIO.py | 7 +- .../standard/basic/transportTaskIO.py | 3 +- .../standard/general/methodhtmlreport.py | 51 +- .../standard/general/methodplot.py | 80 +- .../standard/general/methodxmlexport.py | 23 +- .../standard/usermethoddatapreparation.py | 77 +- .../weight_and_balance_analysis_conf.xml | 5 + 14 files changed, 985 insertions(+), 533 deletions(-) diff --git a/weight_and_balance_analysis/main.py b/weight_and_balance_analysis/main.py index 037b56b2..6e0a4dad 100644 --- a/weight_and_balance_analysis/main.py +++ b/weight_and_balance_analysis/main.py @@ -43,25 +43,27 @@ def main(): module_config_file = "weight_and_balance_analysis_conf.xml" # Initialize exception string and runtime output logger. - exception_string = str() runtime_output = logging.getLogger('module_logger') - tool_name = "weight and balance analysis" + tool_name = "Weight and Balance" try: """Preprocessing: Acquire necessary data and paths.""" # Run 'data_preprocessing' function from 'datapreprocessing.py'. - paths_and_names, routing_dict, runtime_output = data_preprocessing(module_config_file, argv) + paths_and_names, routing_dict, runtime_output = data_preprocessing( + module_config_file, argv) runtime_output.print(f'{module_name} started...') """Run: Execute code depending on method layers.""" # Execute 'run_module' function from 'methodexecutionpackage' library. This function is responsible for the # main logic of the program. - run_output_dict = run_module(paths_and_names, routing_dict, runtime_output) + run_output_dict = run_module( + paths_and_names, routing_dict, runtime_output) """Postprocessing: Write data to aircraft exchange file and generate plots and reports.""" # Run 'data_postprocessing' function from 'datapostprocessing.py' to handle postprocessing tasks. Receives data # from preprocessing and main processing step. - data_postprocessing(paths_and_names, routing_dict, run_output_dict, runtime_output) + data_postprocessing(paths_and_names, routing_dict, + run_output_dict, runtime_output) runtime_output.print(f'{module_name} finished.') except Exception as e: # pylint: disable=broad-exception-caught @@ -70,26 +72,25 @@ def main(): # Check if exception raised. runtime_output.critical(exception_string_msg(e, tool_name)) sys.exit(1) - - + + def exception_string_msg(error, tool_name: str) -> str: error_type = str(type(error).__name__) error_trace = traceback.extract_tb(error.__traceback__) error_file, error_line, error_func, error_code = error_trace[-1] error_file = error_file.split('/')[-1] - - + exception_string = f"{error_type} : \n" exception_string += f"File : {error_file} \n" exception_string += f"Function / Method: {error_func} \n" exception_string += f"Line : {error_line} \n" exception_string += f"Code : {error_code} \n" exception_string += f"Error message : {str(error)} \n" - + return colored(exception_string + "\n" + " " + f"Main execution of {tool_name} module failed! \n" - + " " + "Program aborted.",'red') - + + " " + "Program aborted.", 'red') + if __name__ == '__main__': main() diff --git a/weight_and_balance_analysis/src/datapostprocessing.py b/weight_and_balance_analysis/src/datapostprocessing.py index 1ac12163..af4e5501 100644 --- a/weight_and_balance_analysis/src/datapostprocessing.py +++ b/weight_and_balance_analysis/src/datapostprocessing.py @@ -28,28 +28,52 @@ def data_postprocessing(paths_and_names, routing_dict, data_dict, runtime_output """Data preparation.""" # Changes to this list are the sole responsibility of the module manager! paths_to_key_parameters_list = [] - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("manufacturer_mass_empty")) - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("operating_mass_empty")) - #paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("maximum_takeoff_mass")) - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("maximum_zero_fuel_mass")) - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("maximum_payload_mass")) - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("maximum_landing_mass")) - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("maximum_fuel_mass")) - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("most_forward_mass")) - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("most_afterward_mass")) - paths_to_key_parameters_list.extend(MassPropertiesIO().key_parameters_paths("design_mass")) - + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("manufacturer_mass_empty")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("operating_mass_empty")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("maximum_takeoff_mass")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("maximum_zero_fuel_mass")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("maximum_payload_mass")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("maximum_landing_mass")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("maximum_fuel_mass")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("most_forward_mass")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("most_afterward_mass")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("design_mass")) + paths_to_key_parameters_list.extend( + MassPropertiesIO().key_parameters_paths("design_fuel_mass")) + child_key_parameters_dict = {} - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("manufacturer_mass_empty","MME")) - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("operating_mass_empty","OME")) - #child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("maximum_takeoff_mass","MTOM")) - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("maximum_zero_fuel_mass","MZFM")) - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("maximum_payload_mass","")) - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("maximum_landing_mass","MLM")) - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("maximum_fuel_mass","")) - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("most_forward_mass","")) - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("most_afterward_mass","")) - child_key_parameters_dict.update(MassPropertiesIO().key_parameters_dicts("design_mass","")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("manufacturer_mass_empty", "MME")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("operating_mass_empty", "OME")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("maximum_takeoff_mass", "MTOM")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("maximum_zero_fuel_mass", "MZFM")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("maximum_payload_mass", "")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("maximum_landing_mass", "MLM")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("maximum_fuel_mass", "")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("most_forward_mass", "")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("most_afterward_mass", "")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("design_mass", "")) + child_key_parameters_dict.update( + MassPropertiesIO().key_parameters_dicts("design_fuel_mass", "")) module_key_parameters_dict = { "analysis": { @@ -59,11 +83,13 @@ def data_postprocessing(paths_and_names, routing_dict, data_dict, runtime_output **child_key_parameters_dict} } } - - paths_and_names = prepare_element_tree_for_module_key_parameter(paths_and_names, module_key_parameters_dict) + + paths_and_names = prepare_element_tree_for_module_key_parameter( + paths_and_names, module_key_parameters_dict) # Run 'user_method_data_output_preparation' from 'usermethoddatapreparation.py'. - key_output_dict, method_specific_output_dict = routing_dict['func_user_method_data_output_preparation'](data_dict) + key_output_dict, method_specific_output_dict = routing_dict['func_user_method_data_output_preparation']( + data_dict) # Extract tool level from routing dictionary. tool_level = routing_dict['tool_level'] diff --git a/weight_and_balance_analysis/src/datapreprocessing.py b/weight_and_balance_analysis/src/datapreprocessing.py index 8ca59c90..87234f31 100644 --- a/weight_and_balance_analysis/src/datapreprocessing.py +++ b/weight_and_balance_analysis/src/datapreprocessing.py @@ -5,7 +5,6 @@ import sys # Import own modules. from datapreprocessingmodule import get_paths_and_names, read_routing_values_from_xml -from src.readlayertext import read_energy_carrier def data_preprocessing(module_configuration_file, argv): @@ -34,7 +33,8 @@ def data_preprocessing(module_configuration_file, argv): """Get paths, names, and xml trees for module configuration and aircraft exchange file.""" # Call 'get_paths_and_names' function to obtain various paths and names. - paths_and_names, runtime_output = get_paths_and_names(module_configuration_file, argv) + paths_and_names, runtime_output = get_paths_and_names( + module_configuration_file, argv) # Note: It is the exclusive responsibility of the module manager to modify the following information! # Create layer description dictionary according to the number of individual layers. # The dictionary associates layers with their respective XML paths and expected data types according to the @@ -44,9 +44,11 @@ def data_preprocessing(module_configuration_file, argv): './requirements_and_specifications/design_specification/configuration/configuration_type/value').text category = paths_and_names['root_of_module_config_tree'].find( f"./program_settings/{configuration_type}/category/value").text + design_specification_path = \ + 'aircraft_exchange_file/requirements_and_specifications/design_specification/' layer_description_dict = { - 'layer_1': ['aircraft_exchange_file/requirements_and_specifications/design_specification/configuration/configuration_type/value', str], + 'layer_1': [f"{design_specification_path}/configuration/configuration_type/value", str], 'layer_2': [f"module_configuration_file/program_settings/{configuration_type}/category/value", str], 'user_layer': [f"module_configuration_file/program_settings/{configuration_type}/{category}/method/value", str] } @@ -55,7 +57,7 @@ def data_preprocessing(module_configuration_file, argv): # Extract root and path to aircraft exchange file and write key data to aircraft exchange file. root_of_aircraft_exchange_tree = paths_and_names['root_of_aircraft_exchange_tree'] root_of_module_configuration_file = paths_and_names['root_of_module_config_tree'] - + # Extract data from *.xml files based on the provided layer description. The result is stored in the # 'preprocessing_dict' dictionary. It has the following output format (all values are strings): # dict_out = {'layer_1': value, 'layer_2': value, 'layer_3': value, 'user_layer': value, 'tool_level': value} @@ -73,9 +75,11 @@ def data_preprocessing(module_configuration_file, argv): # * 'usermethoddatapreparation.py'. # E.g., the import command for the module import from the 'runmodule.py' file is as follows: # 'src.[value of layer_1].[value of layer_2].[value of layer_3].usermethoddatapreparation' - import_command_user_method_data_preparation = module_import_name + '.usermethoddatapreparation' + import_command_user_method_data_preparation = module_import_name + \ + '.usermethoddatapreparation' import_command_user_method_plot = module_import_name + '.general.methodplot' - import_command_user_method_html_report = module_import_name + '.general.methodhtmlreport' + import_command_user_method_html_report = module_import_name + \ + '.general.methodhtmlreport' import_command_user_method_xml_export = module_import_name + '.general.methodxmlexport' import_command_user_method_tex_output = module_import_name + '.general.methodtexoutput' @@ -86,11 +90,16 @@ def data_preprocessing(module_configuration_file, argv): # Dynamically import modules and functions based on the generated import commands. try: # Import functions from the specified modules. - import_user_method_data_preparation = importlib.import_module(import_command_user_method_data_preparation) - import_user_method_plot = importlib.import_module(import_command_user_method_plot) - import_user_method_html_report = importlib.import_module(import_command_user_method_html_report) - import_user_method_xml_export = importlib.import_module(import_command_user_method_xml_export) - import_user_method_tex_output = importlib.import_module(import_command_user_method_tex_output) + import_user_method_data_preparation = importlib.import_module( + import_command_user_method_data_preparation) + import_user_method_plot = importlib.import_module( + import_command_user_method_plot) + import_user_method_html_report = importlib.import_module( + import_command_user_method_html_report) + import_user_method_xml_export = importlib.import_module( + import_command_user_method_xml_export) + import_user_method_tex_output = importlib.import_module( + import_command_user_method_tex_output) # Save the imported functions as variables in the 'preprocessing_dict' dictionary. preprocessing_dict['func_user_method_data_input_preparation'] \ = import_user_method_data_preparation.user_method_data_input_preparation @@ -104,7 +113,7 @@ def data_preprocessing(module_configuration_file, argv): except ModuleNotFoundError as module_import_error: runtime_output_string = ('Error: ' + str(module_import_error) + ' found in ' + preprocessing_dict['module_name'] + '.\n' - + ' Program aborted.') + + ' Program aborted.') runtime_output.critical(runtime_output_string) sys.exit(1) diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/acommodationIO.py b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/acommodationIO.py index 0f45372a..2b3bcb3d 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/acommodationIO.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/acommodationIO.py @@ -17,23 +17,25 @@ class AcommodationIO: self.excel_sheets = pd.read_excel(excel_file, sheet_name=None) - self.rows = int(xml_node.find(base_path + "passenger_definition/total_number_passengers/value").text)/6 + self.rows = int(xml_node.find( + base_path + "passenger_definition/total_number_passengers/value").text) / 6 # fixed nr of seats = 6 per row self.mass_per_passenger = float(xml_node.find( base_path + "passenger_definition/mass_per_passenger/value").text) - + seats_x_coordinate = [6.2232, 6.9598, 7.6964, 8.433, 9.1696, 9.9062, 10.6428, 11.3794, 12.116, - 12.8526, 13.5892, 14.3258, 15.3302, 16.3302, 17.0414, 17.7526, 18.4638, - 19.175, 19.8862, 20.5974, 21.3086, 22.0198, 22.731, 23.4422, 24.128, 24.8138, - 25.4996, 26.1854, 26.8712, 27.557] + 12.8526, 13.5892, 14.3258, 15.3302, 16.3302, 17.0414, 17.7526, 18.4638, + 19.175, 19.8862, 20.5974, 21.3086, 22.0198, 22.731, 23.4422, 24.128, 24.8138, + 25.4996, 26.1854, 26.8712, 27.557] max_number_of_passengers_per_row = [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6] #max 180 PAX for 30 rows + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6] # max 180 PAX for 30 rows + + self.seating_spacing_x_coordinate = seats_x_coordinate[:int(self.rows)] + self.number_of_passengers_per_row = max_number_of_passengers_per_row[:int( + self.rows)] - self.seating_spacing_x_coordinate = seats_x_coordinate[:int(self.rows)] - self.number_of_passengers_per_row = max_number_of_passengers_per_row[:int(self.rows)] - self.cargo_spacing_x_coordinate = range(5, 30) # self.fuselage_type = { @@ -42,4 +44,4 @@ class AcommodationIO: # "decks": 1 # } - return self \ No newline at end of file + return self diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/componentMassIO.py b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/componentMassIO.py index 5ddaf7aa..2dfc4473 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/componentMassIO.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/componentMassIO.py @@ -69,13 +69,15 @@ class ComponentMassIO: self.mass = float(xml_access.find( f"{self.path_to_element}/mass/value").text) self.name = str(xml_access.find( - f"{self.path_to_element}/name/value").text) + f"{self.path_to_element}/name/value").text) for key in self.center_of_gravity: try: # Attempt to extract the value and convert it to a float - value = xml_access.find(f"{self.path_to_element}/center_of_gravity/{key}/value").text - self.center_of_gravity[key] = float(value) if value is not None else 0.0 + value = xml_access.find( + f"{self.path_to_element}/center_of_gravity/{key}/value").text + self.center_of_gravity[key] = float( + value) if value is not None else 0.0 except (AttributeError, ValueError, TypeError): # If the value is None, invalid, or can't be converted to float, default to 0.0 self.center_of_gravity[key] = 0.0 @@ -84,7 +86,6 @@ class ComponentMassIO: raise ValueError return self - def print(self, name=None): if name is not None: @@ -96,23 +97,26 @@ class ComponentMassIO: def user_method_data_generation(self): element_name = self.path_to_element.split("/")[-1] to_write = { - f"{element_name}_mass": [f"{self.path_to_element}/mass_properties/mass", self.mass], - f"{element_name}_cog_x": [f"{self.path_to_element}/mass_properties/center_of_gravity/x", self.center_of_gravity["x"]], - f"{element_name}_cog_y": [f"{self.path_to_element}/mass_properties/center_of_gravity/y", self.center_of_gravity["y"]], - f"{element_name}_cog_z": [f"{self.path_to_element}/mass_properties/center_of_gravity/z", self.center_of_gravity["z"]], - f"{element_name}_j_xx": [f"{self.path_to_element}/mass_properties/inertia/j_xx", self.inertia["j_xx"]], - f"{element_name}_j_yy": [f"{self.path_to_element}/mass_properties/inertia/j_yy", self.inertia["j_yy"]], - f"{element_name}_j_zz": [f"{self.path_to_element}/mass_properties/inertia/j_zz", self.inertia["j_zz"]], - f"{element_name}_j_xy": [f"{self.path_to_element}/mass_properties/inertia/j_xy", self.inertia["j_xy"]], - f"{element_name}_j_xz": [f"{self.path_to_element}/mass_properties/inertia/j_xz", self.inertia["j_xz"]], - f"{element_name}_j_yx": [f"{self.path_to_element}/mass_properties/inertia/j_yx", self.inertia["j_yx"]], - f"{element_name}_j_yz": [f"{self.path_to_element}/mass_properties/inertia/j_yz", self.inertia["j_yz"]], - f"{element_name}_j_zx": [f"{self.path_to_element}/mass_properties/inertia/j_zx", self.inertia["j_zx"]], - f"{element_name}_j_zy": [f"{self.path_to_element}/mass_properties/inertia/j_zy", self.inertia["j_zy"]] + f"{element_name}_mass": [f"{self.path_to_element}/mass_properties/mass", self.mass], + f"{element_name}_cog_x": [f"{self.path_to_element}/mass_properties/center_of_gravity/x", + self.center_of_gravity["x"]], + f"{element_name}_cog_y": [f"{self.path_to_element}/mass_properties/center_of_gravity/y", + self.center_of_gravity["y"]], + f"{element_name}_cog_z": [f"{self.path_to_element}/mass_properties/center_of_gravity/z", + self.center_of_gravity["z"]], + f"{element_name}_j_xx": [f"{self.path_to_element}/mass_properties/inertia/j_xx", self.inertia["j_xx"]], + f"{element_name}_j_yy": [f"{self.path_to_element}/mass_properties/inertia/j_yy", self.inertia["j_yy"]], + f"{element_name}_j_zz": [f"{self.path_to_element}/mass_properties/inertia/j_zz", self.inertia["j_zz"]], + f"{element_name}_j_xy": [f"{self.path_to_element}/mass_properties/inertia/j_xy", self.inertia["j_xy"]], + f"{element_name}_j_xz": [f"{self.path_to_element}/mass_properties/inertia/j_xz", self.inertia["j_xz"]], + f"{element_name}_j_yx": [f"{self.path_to_element}/mass_properties/inertia/j_yx", self.inertia["j_yx"]], + f"{element_name}_j_yz": [f"{self.path_to_element}/mass_properties/inertia/j_yz", self.inertia["j_yz"]], + f"{element_name}_j_zx": [f"{self.path_to_element}/mass_properties/inertia/j_zx", self.inertia["j_zx"]], + f"{element_name}_j_zy": [f"{self.path_to_element}/mass_properties/inertia/j_zy", self.inertia["j_zy"]] } return to_write - def key_parameters_paths(self,mass_type_w_underscore: str = "mass"): + def key_parameters_paths(self, mass_type_w_underscore: str = "mass"): basic_path = f"./analysis/masses_cg_inertia/{mass_type_w_underscore}/mass_properties" mass_item_path = f"{basic_path}/mass" @@ -128,22 +132,21 @@ class ComponentMassIO: cog_path_x = f"{basic_path}/center_of_gravity/x" cog_path_y = f"{basic_path}/center_of_gravity/y" cog_path_z = f"{basic_path}/center_of_gravity/z" - - - return [mass_item_path, + + return [mass_item_path, inertia_path_j_xx, inertia_path_j_yy, inertia_path_j_zz, inertia_path_j_xy, inertia_path_j_xz, inertia_path_j_yx, inertia_path_j_yz, inertia_path_j_zx, inertia_path_j_zy, cog_path_x, cog_path_y, cog_path_z] - - def key_parameters_dicts(self, mass_type_w_underscore: str="mass", short: str="M"): - mass_type_wo_underscore = mass_type_w_underscore.replace("_"," ") + + def key_parameters_dicts(self, mass_type_w_underscore: str = "mass", short: str = "M"): + mass_type_wo_underscore = mass_type_w_underscore.replace("_", " ") parameters_dict = { f"{mass_type_w_underscore}": { "attributes": {"description": f"{short}"}, - f"mass_properties": { + "mass_properties": { "attributes": {"description": f"{mass_type_wo_underscore} properties"}, 'mass': { 'attributes': {'description': 'Mass'}, @@ -223,21 +226,24 @@ class ComponentMassIO: 'description': 'Center of gravity w.r.t global coordinate system.'}, 'x': { 'attributes': { - 'description': 'Center of gravity in x-direction with regard to the global reference point.'}, + 'description': + 'Center of gravity in x-direction with regard to the global reference point.'}, 'value': '0', 'unit': 'm', 'lower_boundary': '-100', 'upper_boundary': '100'}, 'y': { 'attributes': { - 'description': 'Center of gravity in y-direction with regard to the global reference point.'}, + 'description': + 'Center of gravity in y-direction with regard to the global reference point.'}, 'value': '0', 'unit': 'm', 'lower_boundary': '-50', 'upper_boundary': '50'}, 'z': { 'attributes': { - 'description': 'Center of gravity in z-direction with regard to the global reference point.'}, + 'description': + 'Center of gravity in z-direction with regard to the global reference point.'}, 'value': '0', 'unit': 'm', 'lower_boundary': '-10', @@ -246,5 +252,5 @@ class ComponentMassIO: } } } - - return parameters_dict \ No newline at end of file + + return parameters_dict diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/massPropertiesIO.py b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/massPropertiesIO.py index d22843b7..d703e179 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/massPropertiesIO.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/massPropertiesIO.py @@ -91,40 +91,45 @@ class MassPropertiesIO: raise ValueError return self - def print(self, name=None): + import logging + runtime_output = logging.getLogger('module_logger') + if name is not None: - print(f"== {name} == ") - print(f"mass ... {self.mass}") + runtime_output.print(f"== {name} == ") + runtime_output.print(f"mass ... {self.mass}") for ax, value in self.center_of_gravity.items(): - print(f"{ax} ... {value}") + runtime_output.print(f"{ax} ... {value}") for component, value in self.inertia.items(): - print(f"{component} ... {value}") + runtime_output.print(f"{component} ... {value}") return self def user_method_data_generation(self): element_name = self.path_to_element.split("/")[-1] to_write = { - f"{element_name}_mass": [f"{self.path_to_element}/mass_properties/mass", self.mass], - f"{element_name}_cog_x": [f"{self.path_to_element}/mass_properties/center_of_gravity/x", self.center_of_gravity["x"]], - f"{element_name}_cog_y": [f"{self.path_to_element}/mass_properties/center_of_gravity/y", self.center_of_gravity["y"]], - f"{element_name}_cog_z": [f"{self.path_to_element}/mass_properties/center_of_gravity/z", self.center_of_gravity["z"]], - f"{element_name}_j_xx": [f"{self.path_to_element}/mass_properties/inertia/j_xx", self.inertia["j_xx"]], - f"{element_name}_j_yy": [f"{self.path_to_element}/mass_properties/inertia/j_yy", self.inertia["j_yy"]], - f"{element_name}_j_zz": [f"{self.path_to_element}/mass_properties/inertia/j_zz", self.inertia["j_zz"]], - f"{element_name}_j_xy": [f"{self.path_to_element}/mass_properties/inertia/j_xy", self.inertia["j_xy"]], - f"{element_name}_j_xz": [f"{self.path_to_element}/mass_properties/inertia/j_xz", self.inertia["j_xz"]], - f"{element_name}_j_yx": [f"{self.path_to_element}/mass_properties/inertia/j_yx", self.inertia["j_yx"]], - f"{element_name}_j_yz": [f"{self.path_to_element}/mass_properties/inertia/j_yz", self.inertia["j_yz"]], - f"{element_name}_j_zx": [f"{self.path_to_element}/mass_properties/inertia/j_zx", self.inertia["j_zx"]], - f"{element_name}_j_zy": [f"{self.path_to_element}/mass_properties/inertia/j_zy", self.inertia["j_zy"]] + f"{element_name}_mass": [f"{self.path_to_element}/mass_properties/mass", self.mass], + f"{element_name}_cog_x": [f"{self.path_to_element}/mass_properties/center_of_gravity/x", + self.center_of_gravity["x"]], + f"{element_name}_cog_y": [f"{self.path_to_element}/mass_properties/center_of_gravity/y", + self.center_of_gravity["y"]], + f"{element_name}_cog_z": [f"{self.path_to_element}/mass_properties/center_of_gravity/z", + self.center_of_gravity["z"]], + f"{element_name}_j_xx": [f"{self.path_to_element}/mass_properties/inertia/j_xx", self.inertia["j_xx"]], + f"{element_name}_j_yy": [f"{self.path_to_element}/mass_properties/inertia/j_yy", self.inertia["j_yy"]], + f"{element_name}_j_zz": [f"{self.path_to_element}/mass_properties/inertia/j_zz", self.inertia["j_zz"]], + f"{element_name}_j_xy": [f"{self.path_to_element}/mass_properties/inertia/j_xy", self.inertia["j_xy"]], + f"{element_name}_j_xz": [f"{self.path_to_element}/mass_properties/inertia/j_xz", self.inertia["j_xz"]], + f"{element_name}_j_yx": [f"{self.path_to_element}/mass_properties/inertia/j_yx", self.inertia["j_yx"]], + f"{element_name}_j_yz": [f"{self.path_to_element}/mass_properties/inertia/j_yz", self.inertia["j_yz"]], + f"{element_name}_j_zx": [f"{self.path_to_element}/mass_properties/inertia/j_zx", self.inertia["j_zx"]], + f"{element_name}_j_zy": [f"{self.path_to_element}/mass_properties/inertia/j_zy", self.inertia["j_zy"]] } return to_write - def key_parameters_paths(self,mass_type_w_underscore: str = "mass"): + def key_parameters_paths(self, mass_type_w_underscore: str = "mass"): basic_path = f"./analysis/masses_cg_inertia/{mass_type_w_underscore}/mass_properties" mass_item_path = f"{basic_path}/mass" @@ -140,22 +145,21 @@ class MassPropertiesIO: cog_path_x = f"{basic_path}/center_of_gravity/x" cog_path_y = f"{basic_path}/center_of_gravity/y" cog_path_z = f"{basic_path}/center_of_gravity/z" - - - return [mass_item_path, + + return [mass_item_path, inertia_path_j_xx, inertia_path_j_yy, inertia_path_j_zz, inertia_path_j_xy, inertia_path_j_xz, inertia_path_j_yx, inertia_path_j_yz, inertia_path_j_zx, inertia_path_j_zy, cog_path_x, cog_path_y, cog_path_z] - - def key_parameters_dicts(self, mass_type_w_underscore: str="mass", short: str="M"): - mass_type_wo_underscore = mass_type_w_underscore.replace("_"," ") + + def key_parameters_dicts(self, mass_type_w_underscore: str = "mass", short: str = "M"): + mass_type_wo_underscore = mass_type_w_underscore.replace("_", " ") parameters_dict = { f"{mass_type_w_underscore}": { "attributes": {"description": f"{short}"}, - f"mass_properties": { + "mass_properties": { "attributes": {"description": f"{mass_type_wo_underscore} properties"}, 'mass': { 'attributes': {'description': 'Mass'}, @@ -235,21 +239,24 @@ class MassPropertiesIO: 'description': 'Center of gravity w.r.t global coordinate system.'}, 'x': { 'attributes': { - 'description': 'Center of gravity in x-direction with regard to the global reference point.'}, + 'description': + 'Center of gravity in x-direction with regard to the global reference point.'}, 'value': '0', 'unit': 'm', 'lower_boundary': '-100', 'upper_boundary': '100'}, 'y': { 'attributes': { - 'description': 'Center of gravity in y-direction with regard to the global reference point.'}, + 'description': + 'Center of gravity in y-direction with regard to the global reference point.'}, 'value': '0', 'unit': 'm', 'lower_boundary': '-50', 'upper_boundary': '50'}, 'z': { 'attributes': { - 'description': 'Center of gravity in z-direction with regard to the global reference point.'}, + 'description': + 'Center of gravity in z-direction with regard to the global reference point.'}, 'value': '0', 'unit': 'm', 'lower_boundary': '-10', @@ -258,5 +265,5 @@ class MassPropertiesIO: } } } - - return parameters_dict \ No newline at end of file + + return parameters_dict diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/methodbasic.py b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/methodbasic.py index 5f359dad..3e4d1d3c 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/methodbasic.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/methodbasic.py @@ -18,120 +18,193 @@ import copy import pyaircraftgeometry2 as geom2 import pyaixml as aixml -TO_M = 1852 #conversion nautical miles to m from constants +TO_M = 1852 # conversion nautical miles to m from constants def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_config, runtime_output): - """Weight and balance function and stability analysis. + """Weight and balance function. - This function performs ... + This function performs the calculation of the aircraft weights and the weight and balance loading diagramm. [Add more information here...] - The output dictionary 'kerosene_output_dict' contains the results of the [insert calculation module name here] and + The output dictionary 'basic_output_dict' contains the results of the methodbasic.py and is structured according to the following scheme: - kerosene_output_dict = {'parameter_name_1': value, ...} + basic_output_dict = {'parameter_name_1': value, ...} :param dict paths_and_names: Dictionary containing system paths and ElementTrees :param dict routing_dict: Dictionary containing routing parameter :param dict dict_ac_exchange: Dict containing parameter and according values from aircraft exchange file :param dict dict_mod_config: Dict containing parameter and according values from module configuration file - :return dict kerosene_output_dict: Dictionary containing results from calculation for kerosene-powered aircraft + :return dict basic_output_dict: Dictionary containing the aircraft weights, components masses and CG-positions + for kerosene-powered aircraft """ - tool_name = paths_and_names['tool_name'] - user_layer = routing_dict['user_layer'] - ac_exchange_file = paths_and_names["root_of_aircraft_exchange_tree"] - module_config_path = paths_and_names["root_of_module_config_tree"] + ################################################################################################################## - excel_file_path = paths_and_names['project_directory'] + '/' + "Seats_positions.xlsx" - DEBUG(msg="ToDo: Update this when acommodation data are available.") + # Data preparation and reading: + ac_exchange_file = paths_and_names["root_of_aircraft_exchange_tree"] + aircraft_class = dict_mod_config["aircraft_class"] + excel_file_path = paths_and_names['project_directory'] + \ + '/' + "Seats_positions.xlsx" + airplane_seatings = pd.read_excel(excel_file_path) + DEBUG(msg="ToDo: Update this when acommodation and neutral point position are available.") - runtime_output.debug = "Reading wing and fuselage" + # Read fuselage and wing wing = read_wing(paths_and_names) fuselage = read_fuselage(paths_and_names) fuselage_length = geom2.measure.length(fuselage) wing_span = geom2.measure.span(wing) - mac = geom2.measure.mean_aerodynamic_chord(wing) - x_wing_global_position = dict_ac_exchange["x_wing_globlal_position"] - x_mac= geom2.measure.centroid(wing).x()-1/2*mac - x_leading_edge_mac = x_wing_global_position + x_mac - - neutral_point_mac = (dict_ac_exchange["x_aircraft_neutral_point"] - x_leading_edge_mac) / mac * 100. - # print("Neutral Point global position:", -neutral_point_mac*mac + x_leading_edge_mac) - + # Method selection area: + # Select inertia method + inertia_methods_available = { + "mode_0": {"method": calculate_inertia_by_lth_method, "additional_parameters": [wing_span, fuselage_length]}, + "mode_1": {"method": calculate_inertia_by_components, "additional_parameters": None}, + "mode_2": {"method": calculate_inertia_by_Raymer, + "additional_parameters": [wing_span, fuselage_length, aircraft_class]}, + } + # Select maximum landing mass method here + maximum_landing_mass_methods_available = { + "mode_0": {"method": calculate_maximum_landing_mass_by_default_method, + "additional_parameters": [ac_exchange_file]}, # noPep8 e501 + "mode_1": {"method": calculate_maximum_landing_mass_by_regression_rwth, + "additional_parameters": None} + } + inertia_method = inertia_methods_available[dict_mod_config["mode_inertia_calculation"]] + maximum_landing_mass_method = maximum_landing_mass_methods_available[ + dict_mod_config["mode_maximum_landing_mass_calculation"]] # Select loading and active modes - refueling_mode = dict_mod_config["refueling_mode"] # ferry range or design mission + # ferry range or design mission + refueling_mode = dict_mod_config["refueling_mode"] defueling_mode = dict_mod_config["defueling_mode"] # active or not active - passengers_boarding_mode = dict_mod_config["passengers_boarding_mode"] # row-wise or window-aisle + # row-wise or window-aisle + passengers_boarding_mode = dict_mod_config["passengers_boarding_mode"] - # Before method data + # Read further components and mission data mission_information = { - "trip_energy": dict_ac_exchange["trip_energy"], - "mission_energy": dict_ac_exchange["mission_energy"], - # "mission_range": dict_ac_exchange["mission_range"] # Add design "mission" range here + "trip_fuel_mass": determine_trip_fuel(dict_ac_exchange), + "mission_fuel_mass": determine_mission_fuel(dict_ac_exchange), + "taxi_out_fuel_mass": determine_taxi_out_fuel(dict_ac_exchange), + "taxi_in_fuel_mass": determine_taxi_in_fuel(dict_ac_exchange), + "takeoff_fuel_mass": determine_takeoff_fuel(dict_ac_exchange, runtime_output), + "landing_fuel_mass": determine_landing_fuel(dict_ac_exchange, runtime_output) } - - # Read data area transport_task_data = read_transport_task(ac_exchange_file) main_components_mass_properties = read_main_components( - ac_exchange_file, dict_ac_exchange) + ac_exchange_file, dict_ac_exchange, runtime_output) tanks, sorted_tanks_defueling = read_tank_properties( - ac_exchange_file, defueling_mode) + ac_exchange_file, defueling_mode, runtime_output) nacelles, pylons, engines = read_propulsion_properties(ac_exchange_file) maximum_takeoff_mass = read_maximum_takeoff_mass(ac_exchange_file) - operator_items, fuselage_operator_items_mass, systems_operator_items_mass = read_operator_items(ac_exchange_file,fuselage_length) + operator_items, fuselage_operator_items_mass, systems_operator_items_mass = read_operator_items( + ac_exchange_file, fuselage_length) furnishings_mass, furnishings = read_furnishings(ac_exchange_file) systems = read_systems(ac_exchange_file) acommodation_data = read_acommodation(ac_exchange_file, excel_file_path) # x-coordinates of seats and cargo, nr of rows and nr of passengers per row are manually fixed in AcommodationIO! DEBUG(msg="ToDo: Modify the AcommodationIO when the data is available") - airplane_seatings = pd.read_excel(excel_file_path) - aircraft_class = dict_mod_config["aircraft_class"] - - # Method selection area - - # Select inertia method - inertia_methods_available = { - "mode_0": {"method": calculate_inertia_by_lth_method, "additional_parameters": [wing_span, fuselage_length]}, - "mode_1": {"method": calculate_inertia_by_components, "additional_parameters": None}, - "mode_2": {"method": calculate_inertia_by_Raymer, "additional_parameters": [wing_span, fuselage_length, aircraft_class]}, - } - # Select maximum landing mass method here - maximum_landing_mass_methods_available = { - "mode_0": {"method": calculate_maximum_landing_mass_by_default_method, "additional_parameters": [ac_exchange_file]}, # noPep8 e501 - "mode_1": {"method": calculate_maximum_landing_mass_by_regression_rwth, "additional_parameters": None} - } + # Determine wing geometry data and NP position + mac = geom2.measure.mean_aerodynamic_chord(wing) + x_wing_global_position = dict_ac_exchange["x_wing_globlal_position"] + x_mac = geom2.measure.centroid(wing).x() - 1 / 2 * mac + x_leading_edge_mac = x_wing_global_position + x_mac + neutral_point_mac = ( + dict_ac_exchange["x_aircraft_neutral_point"] - x_leading_edge_mac) / mac * 100. - inertia_method = inertia_methods_available[dict_mod_config["mode_inertia_calculation"]] - maximum_landing_mass_method = maximum_landing_mass_methods_available[ - dict_mod_config["mode_maximum_landing_mass_calculation"]] + ################################################################################################################## + ### Calculate masses ### - # Calculate masses # Operating mass empty - OME # Maximum fuel mass # Design fuel mass # Design payload mass + # Maximum takeoff mass = design mass + # Maximum landing mass # Manufacturer mass empty - manufacturer_mass_empty = calculate_manufacturer_mass_empty(operator_items, - main_components_mass_properties, inertia_method).print("Manufacturer mass empty") + manufacturer_mass_empty = calculate_manufacturer_mass_empty( + operator_items, main_components_mass_properties, inertia_method).print("Manufacturer mass empty") # Operating mass empty = MME + operator items - operating_mass_empty = calculate_operating_mass_empty(main_components_mass_properties, - inertia_method, x_leading_edge_mac, mac).print("Operating mass empty") + operating_mass_empty = calculate_operating_mass_empty( + main_components_mass_properties, inertia_method, x_leading_edge_mac, mac).print("Operating mass empty") # Maximum fuel mass + maximum fuel mass per tank maximum_fuel_mass, maximum_fuel_mass_per_tank = calculate_maximum_fuel_mass( tanks, operating_mass_empty, x_leading_edge_mac, mac) - + maximum_fuel_mass.print("Maximum fuel mass") + + # Design payload mass + design_payload_mass = calculate_design_payload_mass( + transport_task_data, main_components_mass_properties["fuselage"]).print("Design payload mass") + # Design fuel mass design_fuel_mass_takeoff, design_fuel_mass_midflight, design_fuel_mass_minimum_landing, \ - design_fuel_mass_takeoff_per_tank, consumed_fuel = \ + design_fuel_mass_takeoff_per_tank, consumed_fuel, design_fuel_mass = \ calculate_design_fuel_mass( - None, maximum_takeoff_mass, operating_mass_empty, transport_task_data, tanks) + mission_information, maximum_takeoff_mass, operating_mass_empty, design_payload_mass, tanks) + design_fuel_mass.path_to_element = "./analysis/masses_cg_inertia/design_fuel_mass" + design_fuel_mass.print("Design fuel mass") - print("== Calculate loading diagramm cg change ==") - print("REFUELING...") + # Maximum PL mass + maximum_payload_mass = calculate_maximum_payload_mass( + dict_ac_exchange["maximum_structural_payload_mass"], + main_components_mass_properties["fuselage"]).print("Maximimum payload mass") + + # Maximum zero fuel mass + maximum_zero_fuel_mass = calculate_maximum_zero_fuel_mass( + [operating_mass_empty, maximum_payload_mass], inertia_method).print("Maximum zero fuel mass") + maximum_zero_fuel_mass.cg_mac = ( + maximum_zero_fuel_mass.center_of_gravity['x'] - x_leading_edge_mac) / mac * 100 + + # Design mass + design_mass_takeoff = calculate_design_mass( + [operating_mass_empty, design_payload_mass, + design_fuel_mass_takeoff], inertia_method + ).print("Design mass takeoff") + design_mass = copy.copy(design_mass_takeoff) + design_mass.path_to_element = "./analysis/masses_cg_inertia/design_mass" + design_mass.cg_mac = ( + design_mass.center_of_gravity['x'] - x_leading_edge_mac) / mac * 100 + + # Maximum takeoff mass + maximum_takeoff_mass = copy.copy(design_mass) + maximum_takeoff_mass.path_to_element = "./analysis/masses_cg_inertia/maximum_takeoff_mass" + maximum_takeoff_mass.cg_mac = ( + design_mass.center_of_gravity['x'] - x_leading_edge_mac) / mac * 100 + + # Maximum payload + fuel up to MTOW + mtow_max_payload_mass = calculate_mtow_max_payload( + operating_mass_empty, maximum_payload_mass, main_components_mass_properties["wing"], + maximum_takeoff_mass, x_leading_edge_mac, mac) + + # Maximum fuel + payload up to MTOW + mtow_max_fuel_mass = calculate_mtow_max_fuel( + operating_mass_empty, maximum_fuel_mass_per_tank, + main_components_mass_properties["wing"], main_components_mass_properties["fuselage"], + maximum_takeoff_mass, x_leading_edge_mac, mac) + + # Midflight + design_mass_midflight = calculate_design_mass( + [operating_mass_empty, design_payload_mass, design_fuel_mass_midflight], + inertia_method).print("Design mass midflight") + + # Landing mass + if dict_mod_config["mode_maximum_landing_mass_calculation"] == "mode_0": + maximum_landing_mass = calculate_maximum_landing_mass_by_default_method( + mission_information, inertia_method, [ + operating_mass_empty, design_payload_mass, design_fuel_mass_minimum_landing], + maximum_landing_mass_method["additional_parameters"]).print("Maximum landing mass") + else: + runtime_output.print("Calculate MLM by RWTH regression ...") + maximum_landing_mass = calculate_maximum_landing_mass_by_regression_rwth( + maximum_takeoff_mass, inertia_method, main_components_mass_properties).print("Maximum landing mass") + maximum_landing_mass.path_to_element = "./analysis/masses_cg_inertia/maximum_landing_mass" + + ################################################################################################################## + ### Determine W&B diagram and CG curtailment ### + + runtime_output.print("== Calculate loading diagramm cg change ==") + runtime_output.print("REFUELING...") if refueling_mode == "mode_1": # Calculate the change in CG after refueling - ferry range mission case: @@ -146,31 +219,34 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi operating_mass_empty.mass, design_fuel_mass_takeoff_per_tank, mac, x_leading_edge_mac) else: + runtime_output.critical("Invalid refueling mode specified") raise ValueError("Invalid refueling mode specified") # Prepare passengers boarding starting_mass = total_mass_refueling[-1] starting_cg = cg_positions_over_mac_refueling[-1] * \ - mac/100 + x_leading_edge_mac + mac / 100 + x_leading_edge_mac x_coord_front_back = acommodation_data.seating_spacing_x_coordinate passenger_front_back = acommodation_data.number_of_passengers_per_row x_coord_back_front = x_coord_front_back[::-1] passenger_back_front = passenger_front_back[::-1] - print("PASSENGERS BOARDING...") + runtime_output.print("PASSENGERS BOARDING...") if passengers_boarding_mode == "mode_0": # Calculate the change in CG after the boarding of the passengers from the front to the back - row-wise case: - print("ROW-WISE FRONT TO BACK") + runtime_output.print("ROW-WISE FRONT TO BACK") cg_positions_over_mac_passengers_front_back, total_mass_passengers_front_back = \ cg_change_passengers_boarding_front_back( - starting_cg, starting_mass, x_coord_front_back, passenger_front_back, acommodation_data, mac, x_leading_edge_mac) + starting_cg, starting_mass, x_coord_front_back, passenger_front_back, + acommodation_data, mac, x_leading_edge_mac) # Calculate the change in CG after the boarding of the passengers from the back to the front - row-wise case: - print("ROW-WISE BACK TO FRONT") + runtime_output.print("ROW-WISE BACK TO FRONT") cg_positions_over_mac_passengers_back_front, total_mass_passengers_back_front = \ cg_change_passengers_boarding_front_back( - starting_cg, starting_mass, x_coord_back_front, passenger_back_front, acommodation_data, mac, x_leading_edge_mac) + starting_cg, starting_mass, x_coord_back_front, passenger_back_front, + acommodation_data, mac, x_leading_edge_mac) DEBUG(msg="If the data is to be read from excel and not from acommodationIO modify/adapt this") # cg_positions_over_mac_passengers_front_back, total_mass_passengers_front_back = \ @@ -178,26 +254,29 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi # acommodation_data, mac, x_leading_edge_mac) elif passengers_boarding_mode == "mode_1": - print("WINDOW AISLE FRONT TO BACK") + runtime_output.print("WINDOW AISLE FRONT TO BACK") cg_positions_over_mac_passengers_front_back, total_mass_passengers_front_back = \ cg_change_passengers_boarding_window_aisle( - starting_cg, starting_mass, x_coord_front_back,passenger_front_back, acommodation_data, mac, x_leading_edge_mac) + starting_cg, starting_mass, x_coord_front_back, passenger_front_back, + acommodation_data, mac, x_leading_edge_mac) - print("WINDOW AISLE BACK TO FRONT") + runtime_output.print("WINDOW AISLE BACK TO FRONT") cg_positions_over_mac_passengers_back_front, total_mass_passengers_back_front = \ cg_change_passengers_boarding_window_aisle( - starting_cg, starting_mass, x_coord_back_front,passenger_back_front, acommodation_data, mac, x_leading_edge_mac) + starting_cg, starting_mass, x_coord_back_front, passenger_back_front, + acommodation_data, mac, x_leading_edge_mac) else: + runtime_output.critical("Invalid passengers boarding mode specified") raise ValueError("Invalid passengers boarding mode specified") # Prepare cargo starting_mass = total_mass_passengers_front_back[-1] starting_cg = cg_positions_over_mac_passengers_front_back[-1] * \ - mac/100 + x_leading_edge_mac + mac / 100 + x_leading_edge_mac x_coord_front_back_cargo = acommodation_data.cargo_spacing_x_coordinate x_coord_back_front_cargo = x_coord_front_back_cargo[::-1] - print("CARGO...") + runtime_output.print("CARGO...") DEBUG(msg="cargo = luggage mass per passenger, modify/extend later with palets / add additional cargo") cg_positions_over_mac_cargo_front_back, total_mass_cargo_front_back = \ @@ -209,17 +288,14 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi starting_cg, starting_mass, x_coord_back_front_cargo, transport_task_data, mac, x_leading_edge_mac) # Prepare defueling - # Design fuel mass - # _, _, _, _, _, design_fuel_mass_landing_min_per_tank = calculate_design_fuel_mass( - # None, maximum_takeoff_mass, operating_mass_empty, transport_task_data, sorted_tanks_defueling) - - defueling_mass_per_tank = calculate_fuel_properties_defueling(sorted_tanks_defueling, consumed_fuel, design_fuel_mass_takeoff_per_tank ) + defueling_mass_per_tank = calculate_fuel_properties_defueling( + sorted_tanks_defueling, consumed_fuel, design_fuel_mass_takeoff_per_tank, runtime_output) starting_mass = total_mass_cargo_back_front[-1] starting_cg = cg_positions_over_mac_cargo_front_back[-1] * \ - mac/100 + x_leading_edge_mac + mac / 100 + x_leading_edge_mac - print("DEFUELING...") + runtime_output.print("DEFUELING...") # Calculate the change in CG after refueling - design mission case: cg_positions_over_mac_defueling, total_mass_defueling = calculate_cg_position_over_mac_defueling( starting_cg, starting_mass, defueling_mass_per_tank, mac, x_leading_edge_mac) @@ -231,11 +307,12 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi most_aft_CG_over_mac = max([max(cg_positions_over_mac_refueling), max(cg_positions_over_mac_passengers_back_front), max(cg_positions_over_mac_cargo_back_front)]) - most_fwd_CG = most_fwd_CG_over_mac*mac/100 + x_leading_edge_mac - most_aft_CG = most_aft_CG_over_mac*mac/100 + x_leading_edge_mac + most_fwd_CG = most_fwd_CG_over_mac * mac / 100 + x_leading_edge_mac + most_aft_CG = most_aft_CG_over_mac * mac / 100 + x_leading_edge_mac - print("Most forward CG global position", most_fwd_CG) - print("Most aft CG global position", most_aft_CG) + runtime_output.print( + f"Most forward CG global position is {most_fwd_CG} m.") + runtime_output.print(f"Most aft CG global position is {most_aft_CG} m.") # Determine the corresponding mass to the most fwd CG if most_fwd_CG_over_mac in cg_positions_over_mac_refueling: @@ -251,7 +328,8 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi most_fwd_CG_over_mac) corresponding_mass_most_fwd_CG = total_mass_cargo_front_back[corresponding_index] - print("Corresponding mass most fwd CG:", corresponding_mass_most_fwd_CG) + runtime_output.print( + f"Corresponding mass most fwd CG is of {corresponding_mass_most_fwd_CG} kg.") # Determine the corresponding mass to the most aft CG if most_aft_CG_over_mac in cg_positions_over_mac_refueling: @@ -267,10 +345,12 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi most_aft_CG_over_mac) corresponding_mass_most_aft_CG = total_mass_cargo_back_front[corresponding_index] - print("Corresponding mass most aft CG:", corresponding_mass_most_aft_CG) + runtime_output.print( + f"Corresponding mass most aft CG is of {corresponding_mass_most_aft_CG} kg.") # Prepare the data to be written as nodes for axml - most_forward_mass = MassPropertiesIO("./analysis/masses_cg_inertia/most_forward_mass") + most_forward_mass = MassPropertiesIO( + "./analysis/masses_cg_inertia/most_forward_mass") most_forward_mass.center_of_gravity["x"] = most_fwd_CG most_forward_mass.center_of_gravity["y"] = 0.0 most_forward_mass.center_of_gravity["z"] = 0.0 @@ -280,7 +360,8 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi MassPropertiesIO().initialize_zero()], inertia_method["additional_parameters"]) - most_afterward_mass = MassPropertiesIO("./analysis/masses_cg_inertia/most_afterward_mass") + most_afterward_mass = MassPropertiesIO( + "./analysis/masses_cg_inertia/most_afterward_mass") most_afterward_mass.center_of_gravity["x"] = most_aft_CG most_afterward_mass.center_of_gravity["y"] = 0.0 most_afterward_mass.center_of_gravity["z"] = 0.0 @@ -290,68 +371,20 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi MassPropertiesIO().initialize_zero()], inertia_method["additional_parameters"]) - # Design payload mass - design_payload_mass = calculate_design_payload_mass( - transport_task_data, main_components_mass_properties["fuselage"]).print("Design payload mass") - - maximum_payload_mass = calculate_maximum_payload_mass( - dict_ac_exchange["maximum_structural_payload_mass"], - main_components_mass_properties["fuselage"]).print("Maximimum payload mass") - - # Maximum zero fuel mass - maximum_zero_fuel_mass = calculate_maximum_zero_fuel_mass( - [operating_mass_empty, maximum_payload_mass], inertia_method).print("Maximum zero fuel mass") - maximum_zero_fuel_mass.cg_mac = (maximum_zero_fuel_mass.center_of_gravity['x']-x_leading_edge_mac)/mac *100 - - # Design mass - design_mass_takeoff = calculate_design_mass( - [operating_mass_empty, design_payload_mass, - design_fuel_mass_takeoff], inertia_method - ).print("Design mass takeoff") - - design_mass = copy.copy(design_mass_takeoff) - design_mass.path_to_element = "./analysis/masses_cg_inertia/design_mass" - design_mass.cg_mac = (design_mass.center_of_gravity['x']-x_leading_edge_mac)/mac *100 - - # Maximum payload + fuel up to MTOW - - mtow_max_payload_mass = calculate_mtow_max_payload(operating_mass_empty, maximum_payload_mass, main_components_mass_properties["wing"], - maximum_takeoff_mass,x_leading_edge_mac,mac) - - # Maximum fuel + payload up to MTOW - - mtow_max_fuel_mass = calculate_mtow_max_fuel(operating_mass_empty, maximum_fuel_mass_per_tank, - main_components_mass_properties["wing"],main_components_mass_properties["fuselage"], - maximum_takeoff_mass,x_leading_edge_mac,mac) - - design_mass_midflight = calculate_design_mass( - [operating_mass_empty, design_payload_mass, - design_fuel_mass_midflight], inertia_method - ).print("Design mass midflight") - - design_mass_landing_minimum = calculate_design_mass( - [operating_mass_empty, design_payload_mass, - design_fuel_mass_minimum_landing], inertia_method - ).print("Design mass landing minimum") - - # Landing mass - DEBUG(msg="ToDo: Replace the energy density with data from axml for default method") - if dict_mod_config["mode_maximum_landing_mass_calculation"] == "mode_0": - maximum_landing_mass = calculate_maximum_landing_mass_by_default_method(operating_mass_empty, transport_task_data, mission_information, - inertia_method,main_components_mass_properties, maximum_landing_mass_method["additional_parameters"]) - else: - maximum_landing_mass = calculate_maximum_landing_mass_by_regression_rwth(maximum_takeoff_mass, inertia_method,main_components_mass_properties) - maximum_landing_mass.path_to_element = "./analysis/masses_cg_inertia/maximum_landing_mass" + ########################################################################################################### + # Prepare output # # Define Group masses group_mass_structure = main_components_mass_properties["wing"].mass + \ - main_components_mass_properties["fuselage"].mass - fuselage_operator_items_mass - furnishings_mass + main_components_mass_properties["empennage"].mass + \ + main_components_mass_properties["fuselage"].mass - fuselage_operator_items_mass - furnishings_mass + \ + main_components_mass_properties["empennage"].mass + \ main_components_mass_properties["landing_gear"].mass +\ sum([pylon.mass for pylon in pylons if pylon is not None]) group_mass_power_unit = sum([sum([nacelle.mass for nacelle in nacelles if nacelle is not None]), sum( [engine.mass for engine in engines if engine is not None])]) - group_mass_systems = main_components_mass_properties["systems"].mass - systems_operator_items_mass - group_mass_furnishings = furnishings_mass #sum([furnishing.mass for furnishing in furnishings if furnishing is not None]) + group_mass_systems = main_components_mass_properties["systems"].mass - \ + systems_operator_items_mass + group_mass_furnishings = furnishings_mass group_mass_operator_items = sum(item.mass for item in operator_items) group_masses = { "Structure": group_mass_structure, @@ -363,7 +396,6 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi structure_masses = { "Wing": main_components_mass_properties["wing"].mass, "Fuselage": main_components_mass_properties["fuselage"].mass - fuselage_operator_items_mass - furnishings_mass, - # "Tanks": main_components_mass_properties["tank"].mass, "Empennage": main_components_mass_properties["empennage"].mass, "Landing Gear": main_components_mass_properties["landing_gear"].mass, "Pylons": sum([pylon.mass for pylon in pylons if pylon is not None]) @@ -380,8 +412,10 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi "maximum_landing_mass": maximum_landing_mass, "mtow_with_max_payload": mtow_max_payload_mass, "mtow_with_max_fuel": mtow_max_fuel_mass, - "design_mass": design_mass, + "design_mass": design_mass, "design_payload_mass": design_payload_mass, + "design_mass_midflight": design_mass_midflight, + "design_fuel_mass": design_fuel_mass, "design_fuel_mass_takeoff": design_fuel_mass_takeoff, "design_fuel_mass_midflight": design_fuel_mass_midflight, "design_fuel_mass_minimum_landing": design_fuel_mass_minimum_landing, @@ -404,11 +438,11 @@ def method_basic(paths_and_names, routing_dict, dict_ac_exchange, dict_mod_confi "most_afterward_mass": most_afterward_mass, "Most forward CG mac position": most_fwd_CG_over_mac, "Most aft CG mac position": most_aft_CG_over_mac, - "Furnishings":furnishings, + "Furnishings": furnishings, "Operator_items": operator_items, "Nacelles": nacelles, - "Engines":engines, - "Systems":systems + "Engines": engines, + "Systems": systems } return basic_output_dict @@ -439,7 +473,7 @@ def calculate_inertia_by_components(mass_properties=[], additional_parameters=No for component, value in inertia.items(): for element in mass_properties: - inertia[component] += element.inertia[component] + element.mass*calculate_geometric_distance_by_axis( + inertia[component] += element.inertia[component] + element.mass * calculate_geometric_distance_by_axis( component, reference_center_of_gravity, element.center_of_gravity) return inertia @@ -453,9 +487,11 @@ def calculate_geometric_distance_by_axis(axis, reference_axis, current_axis): reference_axis (dict): center_of_gravity to apply inertia current_axis (dict): center_of_gravity of current component """ - def main_axis(p, q): return (p**2 + q**2) + def main_axis(p, q): + return (p**2 + q**2) - def deviation_axis(p, q): return -(p*q) + def deviation_axis(p, q): + return -(p * q) to_get = { "j_xx": {"first": "y", "second": "z", "calculation": main_axis}, @@ -476,8 +512,8 @@ def calculate_geometric_distance_by_axis(axis, reference_axis, current_axis): calculation = selected["calculation"] # Calculate delta values between reference and current axis for first and second component - delta_ref_curr_first = reference_axis[first]-current_axis[first] - delta_ref_curr_second = reference_axis[second]-current_axis[second] + delta_ref_curr_first = reference_axis[first] - current_axis[first] + delta_ref_curr_second = reference_axis[second] - current_axis[second] # Calculate inertia distance for axis return calculation(delta_ref_curr_first, delta_ref_curr_second) @@ -488,7 +524,8 @@ def calculate_inertia_by_lth_method(mass_properties=[], additional_parameters=[] Only valid for tube and wing aircrafts Args: - mass_operation_empty (MassPropertiesIO Object list): operation mass empty, payload mass expected, fuel mass expected # noPep8 e501 + mass_operation_empty (MassPropertiesIO Object list): + operation mass empty, payload mass expected, fuel mass expected additional_parameters (list): wing span, fuselage length Returns: @@ -507,11 +544,11 @@ def calculate_inertia_by_lth_method(mass_properties=[], additional_parameters=[] expected_fuel_over_ome = mass_fuel_expected.mass / mass_operation_empty.mass # Transferred table chart page 2 of 4 from LTH - MA 402 42-01 Ausgabe A to formula below - factor_kx = (1.0/12.0 * ((-2.0/3.0 * (expected_mass_over_ome - + factor_kx = (1.0 / 12.0 * ((-2.0 / 3.0 * (expected_mass_over_ome - 1.0) + expected_fuel_over_ome) + 1.0) + 0.065) # Transferred table chart page 3 of 4 from LTH - MA 402 42-01 Ausgabe A to formula below - factor_ky = (-0.065/1.72 * (0.2 * (expected_mass_over_ome - + factor_ky = (-0.065 / 1.72 * (0.2 * (expected_mass_over_ome - 1.0) + expected_fuel_over_ome) + 0.2025) factor_tech_j_xx = 0.8 # self estimated tech factor for Jxx @@ -533,12 +570,14 @@ def calculate_inertia_by_lth_method(mass_properties=[], additional_parameters=[] } return inertia + def calculate_inertia_by_Raymer(mass_properties=[], additional_parameters=[]): """Calculate inertia by 'Raymer p.623, table 16.1' (Empirical table) Args: - mass_operation_empty (MassPropertiesIO Object list): operation mass empty, payload mass expected, fuel mass expected # noPep8 e501 + mass_operation_empty (MassPropertiesIO Object list): + operation mass empty, payload mass expected, fuel mass expected additional_parameters (list): wing span, fuselage length, aircraft_type Returns: @@ -603,6 +642,8 @@ def calculate_inertia_by_Raymer(mass_properties=[], additional_parameters=[]): def calculate_center_of_gravity(mass_properties): + import logging + runtime_output = logging.getLogger('module_logger') center_of_gravity = {"x": None, "y": None, @@ -613,6 +654,8 @@ def calculate_center_of_gravity(mass_properties): elif isinstance(mass_properties, list): components = mass_properties else: + runtime_output.critical( + "Wrong type of mass_properties (no dict or list)... abort program!") raise ValueError( "Wrong type of mass_properties (no dict or list)... abort program!") @@ -638,15 +681,19 @@ def calculate_manufacturer_mass_empty(operator_items, mass_properties, inertia_m mass_op_items = sum(item.mass for item in operator_items) # mass properties contains the total mass of components (ex. fuselage = structure + op items + furnishings) - mass_tot = sum([mass_properties[element].mass for element in mass_properties]) # = OME - cg_tot = calculate_center_of_gravity(mass_properties) # = CG OME + mass_tot = sum( + [mass_properties[element].mass for element in mass_properties]) # = OME + cg_tot = calculate_center_of_gravity(mass_properties) # = CG OME # removing the operator items to determine MME - manufacturer_mass_empty.mass = mass_tot - mass_op_items + manufacturer_mass_empty.mass = mass_tot - mass_op_items - manufacturer_mass_empty.center_of_gravity['x'] = (mass_tot*cg_tot['x'] - mass_op_items * cg_items['x'])/(mass_tot - mass_op_items) - manufacturer_mass_empty.center_of_gravity['y'] = (mass_tot*cg_tot['y'] - mass_op_items * cg_items['y'])/(mass_tot - mass_op_items) - manufacturer_mass_empty.center_of_gravity['z'] = (mass_tot*cg_tot['z'] - mass_op_items * cg_items['z'])/(mass_tot - mass_op_items) + manufacturer_mass_empty.center_of_gravity['x'] = ( + mass_tot * cg_tot['x'] - mass_op_items * cg_items['x']) / (mass_tot - mass_op_items) + manufacturer_mass_empty.center_of_gravity['y'] = ( + mass_tot * cg_tot['y'] - mass_op_items * cg_items['y']) / (mass_tot - mass_op_items) + manufacturer_mass_empty.center_of_gravity['z'] = ( + mass_tot * cg_tot['z'] - mass_op_items * cg_items['z']) / (mass_tot - mass_op_items) manufacturer_mass_empty.inertia = inertia_method["method"]([manufacturer_mass_empty, MassPropertiesIO( ).initialize_zero(), MassPropertiesIO().initialize_zero()], inertia_method["additional_parameters"]) @@ -654,7 +701,7 @@ def calculate_manufacturer_mass_empty(operator_items, mass_properties, inertia_m return manufacturer_mass_empty -def calculate_operating_mass_empty(mass_properties, inertia_method,LE, mac): +def calculate_operating_mass_empty(mass_properties, inertia_method, LE, mac): """Calculate opertaing mass empty Args: @@ -666,7 +713,8 @@ def calculate_operating_mass_empty(mass_properties, inertia_method,LE, mac): "./analysis/masses_cg_inertia/operating_mass_empty") operating_mass_empty.mass = sum( - [mass_properties[element].mass for element in mass_properties]) # operator_items_mass already considered in the total mass of components + [mass_properties[element].mass for element in mass_properties]) + # operator_items_mass already considered in the total mass of components # not needed extra, already considered under systems and fuselage operating_mass_empty.center_of_gravity = calculate_center_of_gravity( @@ -674,12 +722,13 @@ def calculate_operating_mass_empty(mass_properties, inertia_method,LE, mac): operating_mass_empty.mass += 0.1 # ToDo avoid division by zero operating_mass_empty.inertia = inertia_method["method"]([operating_mass_empty, MassPropertiesIO( ).initialize_zero(), MassPropertiesIO().initialize_zero()], inertia_method["additional_parameters"]) - operating_mass_empty.cg_mac = (operating_mass_empty.center_of_gravity['x'] - LE) /mac * 100 + operating_mass_empty.cg_mac = ( + operating_mass_empty.center_of_gravity['x'] - LE) / mac * 100 return operating_mass_empty -def calculate_maximum_landing_mass_by_default_method(operating_mass_empty=None, transport_task_data=None, - mission_information=None, inertia_method=None, mass_properties=None, additional_parameters=[]): +def calculate_maximum_landing_mass_by_default_method( + mission_information=None, inertia_method=None, mass_properties=None, additional_parameters=[]): """Calculate maximum landing mass by default method - OME - Design Payload - Reserve fuel Args: @@ -690,41 +739,35 @@ def calculate_maximum_landing_mass_by_default_method(operating_mass_empty=None, Returns: float: maximum landing mass """ - print("Calculate MLM by default method (OME - Payload - Reserve fuel)") + maximum_landing_mass = MassPropertiesIO( "./analysis/masses_cg_inertia/maximum_landing_mass/mass_properties") - # ToDo -> get information from mission for trip and mission fuel - - energy_density = 4.31 * 10**7 # KEROSENE_GRAVIMETRIC_ENERGY_DENSITY - - DEBUG(msg="ToDo: Update when mission data available") - - design_fuel_mass_trip = mission_information["trip_energy"]/energy_density - design_fuel_mass_mission = mission_information["mission_energy"]/energy_density + design_fuel_mass_trip = mission_information["trip_fuel_mass"] + design_fuel_mass_mission = mission_information["mission_fuel_mass"] # If mission information not existent - work with current landing mass if design_fuel_mass_trip is None or design_fuel_mass_mission is None: maximum_landing_mass.read(additional_parameters[0]) - maximum_landing_mass.center_of_gravity = calculate_center_of_gravity(mass_properties) - maximum_landing_mass.inertia = inertia_method["method"]([maximum_landing_mass, - MassPropertiesIO().initialize_zero(), MassPropertiesIO().initialize_zero()], inertia_method["additional_parameters"]) + maximum_landing_mass.center_of_gravity = calculate_center_of_gravity( + mass_properties) + maximum_landing_mass.inertia = inertia_method["method"]( + [maximum_landing_mass, MassPropertiesIO().initialize_zero(), + MassPropertiesIO().initialize_zero()], + inertia_method["additional_parameters"]) return maximum_landing_mass - design_fuel_mass_landing = design_fuel_mass_mission - design_fuel_mass_trip if mission_information is not None: - print("Use mission information here ...") - - maximum_landing_mass.mass = operating_mass_empty.mass + \ - transport_task_data.combined_passenger_and_luggage_mass + design_fuel_mass_landing + transport_task_data.additional_cargo_mass - print(f"MLM - {maximum_landing_mass.mass} [kg]") - maximum_landing_mass.center_of_gravity = calculate_center_of_gravity(mass_properties) - maximum_landing_mass.inertia = inertia_method["method"]([maximum_landing_mass, MassPropertiesIO( - ).initialize_zero(), MassPropertiesIO().initialize_zero()], inertia_method["additional_parameters"]) + maximum_landing_mass.mass = sum( + [element.mass for element in mass_properties]) + maximum_landing_mass.center_of_gravity = calculate_center_of_gravity( + mass_properties) + maximum_landing_mass.inertia = inertia_method["method"]([maximum_landing_mass, MassPropertiesIO( + ).initialize_zero(), MassPropertiesIO().initialize_zero()], inertia_method["additional_parameters"]) return maximum_landing_mass -def calculate_maximum_landing_mass_by_regression_rwth(maximum_takeoff_mass,inertia_method,mass_properties): +def calculate_maximum_landing_mass_by_regression_rwth(maximum_takeoff_mass, inertia_method, mass_properties): """Calculate maximum landing mass by RWTH regression formular Args: @@ -733,15 +776,14 @@ def calculate_maximum_landing_mass_by_regression_rwth(maximum_takeoff_mass,inert Returns: float: maximum landing mass """ - print("Calculate MLM by RWTH regression ...") maximum_landing_mass = MassPropertiesIO( "./analysis/masses_cg_inertia/maximum_landing_mass/mass_properties") if maximum_takeoff_mass.mass > 15000.0: maximum_landing_mass.mass = 1.9689 * maximum_takeoff_mass.mass**0.9248 else: maximum_landing_mass.mass = 0.9009 * maximum_takeoff_mass.mass + 410.85 - print(f"MLM - {maximum_landing_mass.mass} [kg]") - maximum_landing_mass.center_of_gravity = calculate_center_of_gravity(mass_properties) + maximum_landing_mass.center_of_gravity = calculate_center_of_gravity( + mass_properties) maximum_landing_mass.inertia = inertia_method["method"]([maximum_landing_mass, MassPropertiesIO( ).initialize_zero(), MassPropertiesIO().initialize_zero()], inertia_method["additional_parameters"]) @@ -759,10 +801,10 @@ def calculate_maximum_fuel_mass(tanks, ome, LE, mac): _type_: _description_ """ max_fuel_mass_per_tank = [] - mass_prop =[] + mass_prop = [] max_fuel_mass = MassPropertiesIO( "./analysis/masses_cg_inertia/maximum_fuel_mass") - + # the order to be filled is already sorted in the tanks for empty_tank in tanks: filled_tank_wo_structure = MassPropertiesIO().initialize_zero() @@ -774,8 +816,8 @@ def calculate_maximum_fuel_mass(tanks, ome, LE, mac): # No adaption of Center of gravity or Inertia here - filled_tank_wo_structure.moment_change = (filled_tank_wo_structure.mass * - filled_tank_wo_structure.center_of_gravity["x"]) + filled_tank_wo_structure.moment_change = ( + filled_tank_wo_structure.mass * filled_tank_wo_structure.center_of_gravity["x"]) max_fuel_mass_per_tank.append(filled_tank_wo_structure) mass_prop.append(filled_tank_wo_structure) @@ -788,7 +830,8 @@ def calculate_maximum_fuel_mass(tanks, ome, LE, mac): max_fuel_mass.mass = 0.1 max_fuel_mass.center_of_gravity = calculate_center_of_gravity(mass_prop) max_fuel_mass.initialize_zero_inertia() - max_fuel_mass.cg_mac = (max_fuel_mass.center_of_gravity['x']-LE) / mac * 100 + max_fuel_mass.cg_mac = ( + max_fuel_mass.center_of_gravity['x'] - LE) / mac * 100 return max_fuel_mass, max_fuel_mass_per_tank @@ -847,11 +890,10 @@ def calculate_cg_position_over_mac_defueling(initial_cg, initial_weight, fuel_ma return cg_positions, total_weight -def calculate_fuel_mass_properties_evenly_distributed(tanks, fuel_mass): +def calculate_fuel_mass_properties_evenly_distributed(tanks, fuel_mass, runtime_output): # Divide fuel mass evenly on tanks - might be adaptable to inner and outer tanks overall_fuel_mass_per_tank = fuel_mass / len(tanks) - DEBUG(msg="ToDo: Add percentage, tanks can not be equally filled") fuel_mass_properties = MassPropertiesIO().initialize_zero() fuel_mass_per_tank = [] for empty_tank in tanks: @@ -859,9 +901,11 @@ def calculate_fuel_mass_properties_evenly_distributed(tanks, fuel_mass): filled_tank_wo_structure.center_of_gravity = empty_tank.mass_properties.center_of_gravity if overall_fuel_mass_per_tank <= empty_tank.energy / empty_tank.gravimetric_density: - # if overall_fuel_mass_per_tank <= empty_tank.volume["value"] * empty_tank.fuel_density: + # if overall_fuel_mass_per_tank <= empty_tank.volume["value"] * empty_tank.fuel_density: filled_tank_wo_structure.mass = overall_fuel_mass_per_tank else: + runtime_output.critical( + "Fuel mass calculation failed ... tank to small") raise ValueError("Fuel mass calculation failed ... tank to small") # No adaption of Center of gravity or Inertia here @@ -882,17 +926,18 @@ def calculate_fuel_mass_properties_evenly_distributed(tanks, fuel_mass): def calculate_fuel_mass_properties(tanks, fuel_mass): """ Refuel tanks in the following priority order: - 1. Left and Right wing tanks (equally). These are also sorted based on the order defined before according to the wing mounting + 1. Left and Right wing tanks (equally). These are also sorted based on the order defined before according + to the wing mounting 2. Center tank. 3. Remaining tanks. - + Parameters: tanks (list): List of tank objects with properties like capacity, location, and mass. fuel_mass (float): Total fuel mass to be distributed among the tanks. Returns: list: Fuel distribution across the tanks. - + Raises: ValueError: If the fuel mass exceeds the total tank capacity. """ @@ -910,16 +955,22 @@ def calculate_fuel_mass_properties(tanks, fuel_mass): return "other" # Sort tanks into categories based on location - left_wing_tanks = [tank for tank in tanks if categorize_tank_location(tank) == "left"] - right_wing_tanks = [tank for tank in tanks if categorize_tank_location(tank) == "right"] - center_tank = [tank for tank in tanks if categorize_tank_location(tank) == "center"] - other_tanks = [tank for tank in tanks if categorize_tank_location(tank) == "other"] - + left_wing_tanks = [ + tank for tank in tanks if categorize_tank_location(tank) == "left"] + right_wing_tanks = [ + tank for tank in tanks if categorize_tank_location(tank) == "right"] + center_tank = [ + tank for tank in tanks if categorize_tank_location(tank) == "center"] + other_tanks = [ + tank for tank in tanks if categorize_tank_location(tank) == "other"] + # Calculate the total available capacity - total_capacity = sum(tank.energy / tank.gravimetric_density for tank in tanks) - + total_capacity = sum( + tank.energy / tank.gravimetric_density for tank in tanks) + if fuel_mass > total_capacity: - raise ValueError("Fuel mass to be filled up exceeds the total capacity of all tanks!") + raise ValueError( + "Fuel mass to be filled up exceeds the total capacity of all tanks!") # Distribute fuel across left and right wing tanks first remaining_fuel = fuel_mass @@ -935,7 +986,8 @@ def calculate_fuel_mass_properties(tanks, fuel_mass): filled_tank_wo_structure_right = MassPropertiesIO().initialize_zero() # Amount to fill equally - fuel_to_fill = min(left_capacity, right_capacity, remaining_fuel / 2) + fuel_to_fill = min( + left_capacity, right_capacity, remaining_fuel / 2) # Fill both left and right tanks if fuel_to_fill > 0: @@ -943,12 +995,14 @@ def calculate_fuel_mass_properties(tanks, fuel_mass): filled_tank_wo_structure_left.mass = fuel_to_fill filled_tank_wo_structure_left.center_of_gravity = left_tank.mass_properties.center_of_gravity - filled_tank_wo_structure_left.moment_change = (fuel_to_fill * filled_tank_wo_structure_left.center_of_gravity["x"]) + filled_tank_wo_structure_left.moment_change = ( + fuel_to_fill * filled_tank_wo_structure_left.center_of_gravity["x"]) filled_tank_wo_structure_left.tank_location = left_tank.tank_location filled_tank_wo_structure_right.mass = fuel_to_fill filled_tank_wo_structure_right.center_of_gravity = right_tank.mass_properties.center_of_gravity - filled_tank_wo_structure_right.moment_change = (fuel_to_fill * filled_tank_wo_structure_right.center_of_gravity["x"]) + filled_tank_wo_structure_right.moment_change = ( + fuel_to_fill * filled_tank_wo_structure_right.center_of_gravity["x"]) filled_tank_wo_structure_right.tank_location = right_tank.tank_location fuel_mass_per_tank.append(filled_tank_wo_structure_left) @@ -973,7 +1027,8 @@ def calculate_fuel_mass_properties(tanks, fuel_mass): else: filled_tank_wo_structure = MassPropertiesIO().initialize_zero() filled_tank_wo_structure.mass = 0 - filled_tank_wo_structure.center_of_gravity = center_tank[0].mass_properties.center_of_gravity + filled_tank_wo_structure.center_of_gravity = center_tank[ + 0].mass_properties.center_of_gravity filled_tank_wo_structure.moment_change = 0 filled_tank_wo_structure.tank_location = center_tank[0].tank_location fuel_mass_per_tank.append(filled_tank_wo_structure) @@ -983,63 +1038,75 @@ def calculate_fuel_mass_properties(tanks, fuel_mass): if remaining_fuel <= 0: filled_tank_wo_structure = MassPropertiesIO().initialize_zero() filled_tank_wo_structure.mass = 0 - filled_tank_wo_structure.center_of_gravity = other_tank[0].mass_properties.center_of_gravity + filled_tank_wo_structure.center_of_gravity = other_tank[ + 0].mass_properties.center_of_gravity filled_tank_wo_structure.moment_change = 0 filled_tank_wo_structure.tank_location = other_tank[0].tank_location fuel_mass_per_tank.append(filled_tank_wo_structure) - else: + else: other_capacity = other_tank.energy / other_tank.gravimetric_density fuel_to_fill = min(other_capacity, remaining_fuel) if fuel_to_fill > 0: filled_tank_wo_structure = MassPropertiesIO().initialize_zero() filled_tank_wo_structure.mass = fuel_to_fill filled_tank_wo_structure.center_of_gravity = other_tank.mass_properties.center_of_gravity - filled_tank_wo_structure.moment_change = (fuel_to_fill * filled_tank_wo_structure.center_of_gravity["x"]) + filled_tank_wo_structure.moment_change = ( + fuel_to_fill * filled_tank_wo_structure.center_of_gravity["x"]) filled_tank_wo_structure.tank_location = other_tank.tank_location fuel_mass_per_tank.append(filled_tank_wo_structure) remaining_fuel -= fuel_to_fill # Check if there is still remaining fuel (should not happen unless there is a capacity error) if remaining_fuel > 0: - raise ValueError("Fuel mass calculation failed ... Not enough capacity in tanks!") - + raise ValueError( + "Fuel mass calculation failed ... Not enough capacity in tanks!") + fuel_mass_properties = MassPropertiesIO().initialize_zero() fuel_mass_properties.mass = sum( [filled_tank.mass for filled_tank in fuel_mass_per_tank]) fuel_mass_properties.center_of_gravity = calculate_center_of_gravity( fuel_mass_per_tank) - fuel_mass_properties.initialize_zero_inertia() + fuel_mass_properties.initialize_zero_inertia() # Return the fuel mass properties and distribution per tank return fuel_mass_properties, fuel_mass_per_tank -def calculate_fuel_properties_defueling(tanks, fuel_to_remove, fuel_mass_per_tank): +def calculate_fuel_properties_defueling(tanks, fuel_to_remove, fuel_mass_per_tank, runtime_output): + + def map_fuel_mass_to_tanks(tanks, fuel_mass_per_tank, runtime_output): - def map_fuel_mass_to_tanks(tanks, fuel_mass_per_tank): - # Helper function to categorize tanks and fuel mass objects by their location def get_tank_location_key(tank): - return tank.tank_location.strip().lower() # Create a normalized key from tank location + # Create a normalized key from tank location + return tank.tank_location.strip().lower() # Create a mapping of locations to fuel_mass_per_tank objects - fuel_mass_mapping = {get_tank_location_key(fuel_tank): fuel_tank for fuel_tank in fuel_mass_per_tank} + fuel_mass_mapping = {get_tank_location_key( + fuel_tank): fuel_tank for fuel_tank in fuel_mass_per_tank} # Iterate over tanks and update their filled_fuel_mass based on the corresponding fuel_mass_per_tank for tank in tanks: tank_location_key = get_tank_location_key(tank) - + if tank_location_key in fuel_mass_mapping: corresponding_fuel = fuel_mass_mapping[tank_location_key] - tank.filled_fuel_mass = corresponding_fuel.mass # Update tank with the filled mass value + # Update tank with the filled mass value + tank.filled_fuel_mass = corresponding_fuel.mass else: - raise ValueError(f"No matching fuel mass found for tank at location: {tank.tank_location}") + runtime_output.critical( + f"No matching fuel mass found for tank at location: {tank.tank_location}. \ + No fuel was loaded in this tank.") + raise ValueError( + f"No matching fuel mass found for tank at location: {tank.tank_location}. \ + No fuel was loaded in this tank.") # This function modifies the tanks in place, so no need to return anything - map_fuel_mass_to_tanks(tanks, fuel_mass_per_tank) # the tanks are already sorted in the order for defueling, \ + map_fuel_mass_to_tanks(tanks, fuel_mass_per_tank, + runtime_output) # the tanks are already sorted in the order for defueling, \ # this is why here they are only mapped here to get the existing filled up fuel mass - + # Helper function to categorize tanks based on location string def categorize_tank_location(tank): location = tank.tank_location.split() @@ -1053,16 +1120,23 @@ def calculate_fuel_properties_defueling(tanks, fuel_to_remove, fuel_mass_per_tan return "other" # Sort tanks into categories based on location - left_wing_tanks = [tank for tank in tanks if categorize_tank_location(tank) == "left"] - right_wing_tanks = [tank for tank in tanks if categorize_tank_location(tank) == "right"] - center_tank = [tank for tank in tanks if categorize_tank_location(tank) == "center"] - other_tanks = [tank for tank in tanks if categorize_tank_location(tank) == "other"] - + left_wing_tanks = [ + tank for tank in tanks if categorize_tank_location(tank) == "left"] + right_wing_tanks = [ + tank for tank in tanks if categorize_tank_location(tank) == "right"] + center_tank = [ + tank for tank in tanks if categorize_tank_location(tank) == "center"] + other_tanks = [ + tank for tank in tanks if categorize_tank_location(tank) == "other"] + # Calculate the total available fuel in the tanks total_fuel_available = sum(tank.filled_fuel_mass for tank in tanks) - + if fuel_to_remove > total_fuel_available: - raise ValueError("Fuel to remove exceeds the available fuel in all tanks!") + runtime_output.critical( + "Fuel to remove exceeds the available fuel in all tanks!") + raise ValueError( + "Fuel to remove exceeds the available fuel in all tanks!") remaining_fuel_to_remove = fuel_to_remove defueled_mass_per_tank = [] @@ -1073,13 +1147,14 @@ def calculate_fuel_properties_defueling(tanks, fuel_to_remove, fuel_mass_per_tan break fuel_available = other_tank.filled_fuel_mass fuel_to_defuel = min(fuel_available, remaining_fuel_to_remove) - + if fuel_to_defuel > 0: remaining_fuel_to_remove -= fuel_to_defuel defueled_tank = MassPropertiesIO().initialize_zero() defueled_tank.mass = -fuel_to_defuel # Negative for defueling defueled_tank.center_of_gravity = other_tank.mass_properties.center_of_gravity - defueled_tank.moment_change = (-fuel_to_defuel * defueled_tank.center_of_gravity["x"]) + defueled_tank.moment_change = (-fuel_to_defuel * + defueled_tank.center_of_gravity["x"]) defueled_tank.name = other_tank.tank_location defueled_mass_per_tank.append(defueled_tank) other_tank.filled_fuel_mass -= fuel_to_defuel @@ -1092,7 +1167,8 @@ def calculate_fuel_properties_defueling(tanks, fuel_to_remove, fuel_mass_per_tan defueled_tank = MassPropertiesIO().initialize_zero() defueled_tank.mass = -fuel_to_defuel # Negative for defueling defueled_tank.center_of_gravity = center_tank[0].mass_properties.center_of_gravity - defueled_tank.moment_change = (-fuel_to_defuel * defueled_tank.center_of_gravity["x"]) + defueled_tank.moment_change = (-fuel_to_defuel * + defueled_tank.center_of_gravity["x"]) defueled_tank.name = center_tank[0].tank_location defueled_mass_per_tank.append(defueled_tank) center_tank[0].filled_fuel_mass -= fuel_to_defuel @@ -1107,7 +1183,8 @@ def calculate_fuel_properties_defueling(tanks, fuel_to_remove, fuel_mass_per_tan left_fuel_available = left_tank.filled_fuel_mass right_fuel_available = right_tank.filled_fuel_mass - fuel_to_defuel = min(left_fuel_available, right_fuel_available, remaining_fuel_to_remove / 2) + fuel_to_defuel = min( + left_fuel_available, right_fuel_available, remaining_fuel_to_remove / 2) if fuel_to_defuel > 0: remaining_fuel_to_remove -= 2 * fuel_to_defuel @@ -1115,13 +1192,15 @@ def calculate_fuel_properties_defueling(tanks, fuel_to_remove, fuel_mass_per_tan defueled_tank_left = MassPropertiesIO().initialize_zero() defueled_tank_left.mass = -fuel_to_defuel # Negative for defueling defueled_tank_left.center_of_gravity = left_tank.mass_properties.center_of_gravity - defueled_tank_left.moment_change = (-fuel_to_defuel * defueled_tank_left.center_of_gravity["x"]) + defueled_tank_left.moment_change = ( + -fuel_to_defuel * defueled_tank_left.center_of_gravity["x"]) defueled_tank_left.name = left_tank.tank_location defueled_tank_right = MassPropertiesIO().initialize_zero() defueled_tank_right.mass = -fuel_to_defuel # Negative for defueling defueled_tank_right.center_of_gravity = right_tank.mass_properties.center_of_gravity - defueled_tank_right.moment_change = (-fuel_to_defuel * defueled_tank_right.center_of_gravity["x"]) + defueled_tank_right.moment_change = ( + -fuel_to_defuel * defueled_tank_right.center_of_gravity["x"]) defueled_tank_right.name = right_tank.tank_location defueled_mass_per_tank.append(defueled_tank_left) @@ -1132,49 +1211,69 @@ def calculate_fuel_properties_defueling(tanks, fuel_to_remove, fuel_mass_per_tan # Check if we have defueled the correct amount if remaining_fuel_to_remove > 0: + runtime_output.critical( + "Not enough fuel in the tanks to complete defueling!") raise ValueError("Not enough fuel in the tanks to complete defueling!") return defueled_mass_per_tank def calculate_design_fuel_mass(mission_information=None, maximum_takeoff_mass=None, - operating_mass_empty=None, transport_task_data=None, tanks=None): + operating_mass_empty=None, design_payload_mass=None, tanks=None): """Calculation of design fuel mass Args: - mission_information (object, optional): design mission information from ac_exchange_file. Defaults to None. - maximum_takeoff_mass (float, optional): maximum takeoff mass. Defaults to None. - operating_mass_empty (float, optional): operating mass empty. Defaults to None. - transport_task_data (float, optional): transport task data (passengers, mass p.P, luggage p.P). Defaults to None. # noPep8 e501 + mission_information (object, optional): design mission information from ac_exchange_file. + maximum_takeoff_mass (float, optional): maximum takeoff mass. + operating_mass_empty (float, optional): operating mass empty. + transport_task_data (float, optional): transport task data (passengers, mass p.P, luggage p.P). + Defaults to None. Returns: float: design fuel @ takeoff, midflight and landing (minimum) """ - design_fuel_mass_takeoff = None - design_fuel_mass_midflight = None - design_fuel_mass_landing_minimum = None - - contingency_fuel_factor = 0.05 - alternate_fuel_factor = 0.1 - final_reserve_fuel_factor = 0.02 if mission_information is not None: - print("Not implemented here due to missing information in mission analysis -> TOC and TOD") + # print("Calculating design fuel mass based on mission information") + + cruise_fuel = mission_information["trip_fuel_mass"] - \ + mission_information["takeoff_fuel_mass"] - \ + mission_information["landing_fuel_mass"] + design_fuel_mass = mission_information["mission_fuel_mass"] + design_fuel_mass_at_takeoff = mission_information["mission_fuel_mass"] - \ + mission_information["taxi_out_fuel_mass"] + design_fuel_mass_midflight = design_fuel_mass_at_takeoff - \ + mission_information["takeoff_fuel_mass"] - cruise_fuel / 2 + design_fuel_mass_landing_minimum = mission_information["mission_fuel_mass"] - \ + mission_information["trip_fuel_mass"] - \ + mission_information["taxi_out_fuel_mass"] + consumed_fuel_during_flight = mission_information["trip_fuel_mass"] + + # distribute the fuel along the tanks and save the informations per tank + # the refueling order is already saved in the sorted tanks + # = (preparation for the calculation of the refueling-cg-shift) + design_fuel_mass, design_fuel_mass_takeoff_per_tank = calculate_fuel_mass_properties( + tanks, design_fuel_mass) + design_fuel_mass_takeoff, design_fuel_mass_takeoff_per_tank = calculate_fuel_mass_properties( + tanks, design_fuel_mass_at_takeoff) + design_fuel_mass_midflight, _ = calculate_fuel_mass_properties( + tanks, design_fuel_mass_midflight) + design_fuel_mass_landing_minimum, _ = calculate_fuel_mass_properties( + tanks, design_fuel_mass_landing_minimum) else: - print("Calculate design fuel masses ...") + contingency_fuel_factor = 0.05 + alternate_fuel_factor = 0.1 + final_reserve_fuel_factor = 0.02 - design_fuel_mass_takeoff = (maximum_takeoff_mass.mass-operating_mass_empty.mass - - transport_task_data.combined_passenger_and_luggage_mass-transport_task_data.additional_cargo_mass) - DEBUG(msg="ToDo: Check if payload = transport_task_data.combined_passenger_and_luggage_mass") + design_fuel_mass_takeoff = ( + maximum_takeoff_mass.mass - operating_mass_empty.mass - design_payload_mass.mass) design_fuel_mass_midflight = design_fuel_mass_takeoff / 2.0 - # ToDo: Check percentage value for rest fuel design_fuel_mass_landing_minimum = design_fuel_mass_takeoff * \ (contingency_fuel_factor + alternate_fuel_factor + final_reserve_fuel_factor) - consumed_fuel_during_flight = design_fuel_mass_takeoff - \ design_fuel_mass_landing_minimum - # distribute the fuel along the tanks and save the informations per tank + # distribute the fuel along the tanks and save the informations per tank # the refueling order is already saved in the sorted tanks # = (preparation for the calculation of the refueling-cg-shift) design_fuel_mass_takeoff, design_fuel_mass_takeoff_per_tank = calculate_fuel_mass_properties( @@ -1183,8 +1282,10 @@ def calculate_design_fuel_mass(mission_information=None, maximum_takeoff_mass=No tanks, design_fuel_mass_midflight) design_fuel_mass_landing_minimum, _ = calculate_fuel_mass_properties( tanks, design_fuel_mass_landing_minimum) + design_fuel_mass = design_fuel_mass_at_takeoff - return design_fuel_mass_takeoff, design_fuel_mass_midflight, design_fuel_mass_landing_minimum, design_fuel_mass_takeoff_per_tank, consumed_fuel_during_flight # noPep8 e501 + return (design_fuel_mass_takeoff, design_fuel_mass_midflight, design_fuel_mass_landing_minimum, + design_fuel_mass_takeoff_per_tank, consumed_fuel_during_flight, design_fuel_mass) def calculate_design_payload_mass(transport_task_data, fuselage_mass_properties): @@ -1199,7 +1300,8 @@ def calculate_design_payload_mass(transport_task_data, fuselage_mass_properties) """ design_payload_mass = MassPropertiesIO( "./analysis/masses_cg_inertia/design_payload_mass") - design_payload_mass.mass = transport_task_data.combined_passenger_and_luggage_mass + transport_task_data.additional_cargo_mass + design_payload_mass.mass = transport_task_data.combined_passenger_and_luggage_mass + \ + transport_task_data.additional_cargo_mass # cargodeck + passenger_deck + additional cargo design_payload_mass.center_of_gravity = fuselage_mass_properties.center_of_gravity design_payload_mass.initialize_zero_inertia() @@ -1207,7 +1309,8 @@ def calculate_design_payload_mass(transport_task_data, fuselage_mass_properties) return design_payload_mass -def cg_change_passengers_boarding_front_back(initial_cg, initial_weight, x_coordinate_row, number_of_passengers_per_row, acommodation_data, mac, LE): +def cg_change_passengers_boarding_front_back( + initial_cg, initial_weight, x_coordinate_row, number_of_passengers_per_row, acommodation_data, mac, LE): cg_positions = [] total_weight = [] @@ -1215,7 +1318,7 @@ def cg_change_passengers_boarding_front_back(initial_cg, initial_weight, x_coord total_weight.append(initial_weight) total_weight_new = initial_weight total_moment_change = initial_cg * initial_weight - initial_mac_position = (initial_cg-LE) / mac * 100.0 + initial_mac_position = (initial_cg - LE) / mac * 100.0 cg_positions.append(initial_mac_position) for row in range(len(x_coordinate_row)): @@ -1241,7 +1344,7 @@ def cg_change_cargo_loading_front_back(initial_cg, initial_weight, x_coordinate_ total_weight.append(initial_weight) total_weight_new = initial_weight total_moment_change = initial_cg * initial_weight - initial_mac_position = (initial_cg-LE) / mac * 100.0 + initial_mac_position = (initial_cg - LE) / mac * 100.0 cg_positions.append(initial_mac_position) for row in range(len(x_coordinate_row)): @@ -1268,7 +1371,7 @@ def cg_change_passengers_boarding_front_back_excel(initial_cg, initial_weight, s total_weight.append(initial_weight) total_weight_new = initial_weight total_moment_change = initial_cg * initial_weight - initial_mac_position = (initial_cg-LE) / mac * 100.0 + initial_mac_position = (initial_cg - LE) / mac * 100.0 cg_positions.append(initial_mac_position) for index, row in seating.iterrows(): @@ -1286,12 +1389,13 @@ def cg_change_passengers_boarding_front_back_excel(initial_cg, initial_weight, s return cg_positions, total_weight -def cg_change_passengers_boarding_window_aisle(initial_cg, initial_weight, x_coordinate_row,number_of_passengers_per_row, acommodation_data, mac, LE): # noPep8 e501 +def cg_change_passengers_boarding_window_aisle( + initial_cg, initial_weight, x_coordinate_row, number_of_passengers_per_row, acommodation_data, mac, LE): # total nr of seats per row nr_of_seats = number_of_passengers_per_row.copy() - max_seats = math.ceil(max(nr_of_seats)/2) + max_seats = math.ceil(max(nr_of_seats) / 2) cg_positions = [] total_weight = [] @@ -1299,7 +1403,7 @@ def cg_change_passengers_boarding_window_aisle(initial_cg, initial_weight, x_coo total_weight.append(initial_weight) total_weight_new = initial_weight total_moment_change = initial_cg * initial_weight - initial_mac_position = (initial_cg-LE) / mac * 100.0 + initial_mac_position = (initial_cg - LE) / mac * 100.0 cg_positions.append(initial_mac_position) for y in range(max_seats): @@ -1307,7 +1411,7 @@ def cg_change_passengers_boarding_window_aisle(initial_cg, initial_weight, x_coo for row in range(len(x_coordinate_row)): left_seats = nr_of_seats[row] - nr_of_seats[row] = nr_of_seats[row]-2 + nr_of_seats[row] = nr_of_seats[row] - 2 if left_seats >= 2: added_mass = acommodation_data.mass_per_passenger * 2 @@ -1388,16 +1492,18 @@ def calculate_design_mass(mass_properties, inertia_method): return design_mass -def calculate_mtow_max_payload(ome, maximum_payload_mass,wing,mtom, LE, mac): +def calculate_mtow_max_payload(ome, maximum_payload_mass, wing, mtom, LE, mac): mtow_max_payload = MassPropertiesIO() fuel = mtom.mass - maximum_payload_mass.mass - ome.mass - + mtow_max_payload.mass = ome.mass + fuel + maximum_payload_mass.mass - mtow_max_payload.center_of_gravity = (ome.mass*ome.center_of_gravity['x']+fuel * wing.center_of_gravity['x'] + - maximum_payload_mass.mass*maximum_payload_mass.center_of_gravity['x']) / mtow_max_payload.mass - # DEBUG(msg="Estimated CG (fuel CG set in wing CG)") - mtow_max_payload.cg_mac = (mtow_max_payload.center_of_gravity- LE) / mac * 100.0 + mtow_max_payload.center_of_gravity = ( + ome.mass * ome.center_of_gravity['x'] + fuel * wing.center_of_gravity['x'] + + maximum_payload_mass.mass * maximum_payload_mass.center_of_gravity['x']) / mtow_max_payload.mass + # DEBUG(msg="Estimated CG (fuel CG set in wing CG)") + mtow_max_payload.cg_mac = ( + mtow_max_payload.center_of_gravity - LE) / mac * 100.0 return mtow_max_payload @@ -1410,11 +1516,11 @@ def calculate_mtow_max_fuel(ome, fuel_mass, wing, fuselage, mtom, LE, mac): payload_mass = mtom.mass - max_fuel - ome.mass mtow_max_fuel.mass = ome.mass + max_fuel + payload_mass - mtow_max_fuel.center_of_gravity = (ome.mass*ome.center_of_gravity['x']+max_fuel * wing.center_of_gravity['x'] + - payload_mass*fuselage.center_of_gravity['x']) / mtow_max_fuel.mass - # DEBUG(msg="Estimated CG (fuel CG set in wing CG)") + mtow_max_fuel.center_of_gravity = (ome.mass * ome.center_of_gravity['x'] + max_fuel * wing.center_of_gravity['x'] + + payload_mass * fuselage.center_of_gravity['x']) / mtow_max_fuel.mass + # DEBUG(msg="Estimated CG (fuel CG set in wing CG)") mtow_max_fuel.cg_mac = (mtow_max_fuel.center_of_gravity - LE) / mac * 100.0 - + return mtow_max_fuel @@ -1484,7 +1590,7 @@ def custom_sort_defueling(obj): return 6 # If none of the criteria are met, maintain the order -def read_tank_properties(ac_exchange_file, defueling): +def read_tank_properties(ac_exchange_file, defueling, runtime_output): """Read tank properties - mass specific Args: @@ -1506,27 +1612,39 @@ def read_tank_properties(ac_exchange_file, defueling): energy_carrier = [] tank_requirements_nodes = ac_exchange_file.findall( "./requirements_and_specifications/design_specification/configuration/tank_definition/tank[@ID]") - i=0 + i = 0 for tank_definition_node in tank_requirements_nodes: - tank_energy_carrier_ID = int(tank_definition_node.find("./energy_carrier_ID/value").text) + tank_energy_carrier_ID = int( + tank_definition_node.find("./energy_carrier_ID/value").text) energy_carrier_nodes = ac_exchange_file.findall( - "./requirements_and_specifications/design_specification/energy_carriers/energy_carrier[@ID]") + "./requirements_and_specifications/design_specification/energy_carriers/energy_carrier[@ID]") - energy_carrier = energy_carrier_nodes[tank_energy_carrier_ID].find("./type/value").text - energy_carrier_density = float(energy_carrier_nodes[tank_energy_carrier_ID].find("./density/value").text) - energy_carrier_gravimetric_density = float(energy_carrier_nodes[tank_energy_carrier_ID].find("./gravimetric_density/value").text) + energy_carrier = energy_carrier_nodes[tank_energy_carrier_ID].find( + "./type/value").text + energy_carrier_density = float( + energy_carrier_nodes[tank_energy_carrier_ID].find("./density/value").text) + energy_carrier_gravimetric_density = float( + energy_carrier_nodes[tank_energy_carrier_ID].find("./gravimetric_density/value").text) if len(tank_requirements_nodes) == len(tanks): tanks[i].energy_carrier = energy_carrier - tanks[i].fuel_density = energy_carrier_density #783 # kg/m^3 Jet A1 Kerosene - tanks[i].gravimetric_density = energy_carrier_gravimetric_density # 43 MJ/kg for kerosene, 1.1993E8 for liqiud hydrogen - tanks[i].max_fuel_mass_capacity = tanks[i].energy / tanks[i].gravimetric_density - tanks[i].mass_properties.center_of_gravity["x"] = tanks_ref_point_x + tanks[i].position["x"]["value"] + tanks[i].centroid["x"]["value"] + # 783 # kg/m^3 Jet A1 Kerosene + tanks[i].fuel_density = energy_carrier_density + # 43 MJ/kg for kerosene, 1.1993E8 for liqiud hydrogen + tanks[i].gravimetric_density = energy_carrier_gravimetric_density + tanks[i].max_fuel_mass_capacity = tanks[i].energy / \ + tanks[i].gravimetric_density + tanks[i].mass_properties.center_of_gravity["x"] = tanks_ref_point_x + \ + tanks[i].position["x"]["value"] + \ + tanks[i].centroid["x"]["value"] else: - raise ValueError("No consistent tanks definitions with the number of designed tanks ") - i+=1 + runtime_output.critical( + "No consistent tanks definitions with the number of designed tanks.") + raise ValueError( + "No consistent tanks definitions with the number of designed tanks.") + i += 1 # Sort the tanks according to the wing mounting using the custom sorting function wing_mounting = str(ac_exchange_file.find( @@ -1543,7 +1661,8 @@ def read_tank_properties(ac_exchange_file, defueling): sorted_tanks = tanks # adapt / change for bwb, h2? if defueling == "mode_1": - sorted_tanks_defueling = sorted_tanks[::-1] # inverse order of refueling + # inverse order of refueling + sorted_tanks_defueling = sorted_tanks[::-1] else: sorted_tanks_defueling = tanks # adapt / change for bwb, h2? @@ -1564,25 +1683,28 @@ def read_propulsion_properties(ac_exchange_file): for nacelle_node in nacelle_nodes: nacelles.append(NacelleIO().read(nacelle_node)) - #nacelles.append(NacelleIO().read(propulsor_node)) + # nacelles.append(NacelleIO().read(propulsor_node)) pylons.append(PylonIO().read(propulsor_node)) engines.append(EngineIO().read(propulsor_node)) return nacelles, pylons, engines -def read_main_components(ac_exchange_file, dict_ac_exchange): +def read_main_components(ac_exchange_file, dict_ac_exchange, runtime_output): + components_available = [ key.replace('_exist', '') for key in dict_ac_exchange if dict_ac_exchange[key] is True and '_exist' in key] mass_properties = {} # Get overall mass properties for component in components_available: - print(f"Mass properties of component {component} read ...") + runtime_output.print( + f"Mass properties of component {component} read ...") mass_properties[component] = MassPropertiesIO( f"./component_design/{component}/mass_properties") mass_properties[component].read(ac_exchange_file) - print(f"{component} - mass: {mass_properties[component].mass}") + runtime_output.print( + f"{component} - mass: {mass_properties[component].mass}") return mass_properties @@ -1602,34 +1724,37 @@ def read_maximum_takeoff_mass(ac_exchange_file): def read_operator_items(ac_exchange_file, fuselage_length): # the operator items are written by the fuselage node and the systems node operator_items = [] - i=0 - total_mass= 0 + i = 0 + total_mass = 0 fuselage_nodes = ac_exchange_file.findall( "./component_design/fuselage/specific/geometry/fuselage[@ID]" ) for fuselage_node in fuselage_nodes: - fuselage_operator_nodes = fuselage_node.findall("./mass_breakdown/fuselage_operator_items/component_mass[@ID]") + fuselage_operator_nodes = fuselage_node.findall( + "./mass_breakdown/fuselage_operator_items/component_mass[@ID]") for operator in fuselage_operator_nodes: operator_items.append(ComponentMassIO(".").read(operator)) - operator_items[i].center_of_gravity['x']= 0.45 * fuselage_length # + fuselage_reference_point + operator_items[i].center_of_gravity['x'] = 0.45 * \ + fuselage_length # + fuselage_reference_point total_mass += operator_items[i].mass fuselage_operator_items_mass = total_mass - i=i+1 + i = i + 1 # DEBUG(msg="CGs of fuselage operator items missing in axml -> estimated here at 45% fuselage length") systems_operator_nodes = ac_exchange_file.findall( "./component_design/systems/operator_items/operator_item[@ID]" ) - + for operator in systems_operator_nodes: - operator_items.append(MassPropertiesIO("./mass_properties").read(operator)) - operator_items[i].name = str(operator.find(f"./name/value").text) + operator_items.append(MassPropertiesIO( + "./mass_properties").read(operator)) + operator_items[i].name = str(operator.find("./name/value").text) total_mass += operator_items[i].mass - i=i+1 + i = i + 1 systems_operator_items_mass = total_mass - fuselage_operator_items_mass - + return operator_items, fuselage_operator_items_mass, systems_operator_items_mass @@ -1639,18 +1764,20 @@ def read_furnishings(ac_exchange_file): "./component_design/fuselage/specific/geometry/fuselage[@ID]" ) furnishings = [] - i=0 - furnishings_total_mass= 0 + i = 0 + furnishings_total_mass = 0 for fuselage_node in fuselage_nodes: - furnishing_nodes = fuselage_node.findall("./mass_breakdown/fuselage_furnishing/component_mass[@ID]") + furnishing_nodes = fuselage_node.findall( + "./mass_breakdown/fuselage_furnishing/component_mass[@ID]") for furnishing in furnishing_nodes: furnishings.append(ComponentMassIO(".").read(furnishing)) furnishings_total_mass += furnishings[i].mass - i=i+1 + i = i + 1 return furnishings_total_mass, furnishings + def read_systems(ac_exchange_file): systems_nodes = ac_exchange_file.findall( @@ -1662,7 +1789,7 @@ def read_systems(ac_exchange_file): item = {} item['name'] = node.tag item['mass'] = float(node.find("./value").text) - + systems.append(item) return systems @@ -1682,5 +1809,221 @@ def read_fuselage(paths): return geom2.factory.FuselageFactory(acxml, geometry_data_dir).create("fuselage/specific/geometry/fuselage@0") +def determine_mission_fuel(dict_ac_exchange): + + # Initialize an empty dictionary to store fuel mass for each entry + fuel_mass_results = {} + + # Iterate over the energy entries + for energy_id, consumed_energy in dict_ac_exchange["loaded_mission_energy"].items(): + # Generate the corresponding carrier key by replacing "energy" with "energy_carrier" + carrier_key = energy_id.replace("energy", "energy_carrier") + + # Retrieve the integer carrier ID from "loaded_mission_energy_carrier" + carrier_id_int = dict_ac_exchange["loaded_mission_energy_carrier"].get( + carrier_key) + + # Construct the gravimetric density ID using the integer carrier ID + density_id = f"energy_carrier_gravimetric_density_ID{int(carrier_id_int)}" + + # Retrieve the gravimetric density for this specific carrier + gravimetric_density = dict_ac_exchange["energy_carrier_gravimetric_density"].get( + density_id) + + # Calculate fuel mass if gravimetric density is available + if gravimetric_density is not None: + fuel_mass = consumed_energy / gravimetric_density + fuel_mass_results[energy_id] = fuel_mass + else: + print( + f"{energy_id} - Gravimetric density not found for carrier ID {carrier_id_int}") + + mission_fuel_mass = sum(fuel_mass_results.values()) + + return mission_fuel_mass + + +def determine_taxi_in_fuel(dict_ac_exchange): + + # Initialize an empty dictionary to store fuel mass for each entry + fuel_mass_results = {} + + # Iterate over the energy entries + for energy_id, consumed_energy in dict_ac_exchange["taxi_energy_in"].items(): + # Generate the corresponding carrier key by replacing "energy" with "energy_carrier" + carrier_key = energy_id.replace("in", "in_carrier") + + # Retrieve the integer carrier ID from "loaded_mission_energy_carrier" + carrier_id_int = dict_ac_exchange["taxi_energy_in_carrier"].get( + carrier_key) + + # Construct the gravimetric density ID using the integer carrier ID + density_id = f"energy_carrier_gravimetric_density_ID{int(carrier_id_int)}" + + # Retrieve the gravimetric density for this specific carrier + gravimetric_density = dict_ac_exchange["energy_carrier_gravimetric_density"].get( + density_id) + + # Calculate fuel mass if gravimetric density is available + if gravimetric_density is not None: + fuel_mass = consumed_energy / gravimetric_density + fuel_mass_results[energy_id] = fuel_mass + else: + print( + f"{energy_id} - Gravimetric density not found for carrier ID {carrier_id_int}") + + taxi_in_fuel_mass = sum(fuel_mass_results.values()) + + return taxi_in_fuel_mass + + +def determine_taxi_out_fuel(dict_ac_exchange): + + # Initialize an empty dictionary to store fuel mass for each entry + fuel_mass_results = {} + + # Iterate over the energy entries + for energy_id, consumed_energy in dict_ac_exchange["taxi_energy_out"].items(): + # Generate the corresponding carrier key by replacing "energy" with "energy_carrier" + carrier_key = energy_id.replace("out", "out_carrier") + + # Retrieve the integer carrier ID from "loaded_mission_energy_carrier" + carrier_id_int = dict_ac_exchange["taxi_energy_out_carrier"].get( + carrier_key) + + # Construct the gravimetric density ID using the integer carrier ID + density_id = f"energy_carrier_gravimetric_density_ID{int(carrier_id_int)}" + + # Retrieve the gravimetric density for this specific carrier + gravimetric_density = dict_ac_exchange["energy_carrier_gravimetric_density"].get( + density_id) + + # Calculate fuel mass if gravimetric density is available + if gravimetric_density is not None: + fuel_mass = consumed_energy / gravimetric_density + fuel_mass_results[energy_id] = fuel_mass + else: + print( + f"{energy_id} - Gravimetric density not found for carrier ID {carrier_id_int}") + + taxi_out_fuel_mass = sum(fuel_mass_results.values()) + + return taxi_out_fuel_mass + + +def determine_takeoff_fuel(dict_ac_exchange, runtime_output): + + # Initialize an empty dictionary to store fuel mass for each entry + fuel_mass_results = {} + + # Iterate over the energy entries + for energy_id, consumed_energy in dict_ac_exchange["take_off_energy"].items(): + if consumed_energy is not None: + # Generate the corresponding carrier key by replacing "energy" with "energy_carrier" + carrier_key = energy_id.replace("energy", "energy_carrier") + + # Retrieve the integer carrier ID from "loaded_mission_energy_carrier" + carrier_id_int = dict_ac_exchange["take_off_energy_carrier"].get( + carrier_key) + + # Construct the gravimetric density ID using the integer carrier ID + density_id = f"energy_carrier_gravimetric_density_ID{int(carrier_id_int)}" + + # Retrieve the gravimetric density for this specific carrier + gravimetric_density = dict_ac_exchange["energy_carrier_gravimetric_density"].get( + density_id) + + # Calculate fuel mass if gravimetric density is available + if gravimetric_density is not None: + fuel_mass = consumed_energy / gravimetric_density + fuel_mass_results[energy_id] = fuel_mass + else: + print( + f"{energy_id} - Gravimetric density not found for carrier ID {carrier_id_int}") + else: + runtime_output.critical( + "The take off energy node not found in the mission analysis block. \ + Fuel mass needed for the takeoff segment set to 0.") + + take_off_fuel_mass = sum(fuel_mass_results.values()) + + return take_off_fuel_mass + + +def determine_landing_fuel(dict_ac_exchange, runtime_output): + + # Initialize an empty dictionary to store fuel mass for each entry + fuel_mass_results = {} + + # Iterate over the energy entries + for energy_id, consumed_energy in dict_ac_exchange["landing_energy"].items(): + if consumed_energy is not None: + # Generate the corresponding carrier key by replacing "energy" with "energy_carrier" + carrier_key = energy_id.replace("energy", "energy_carrier") + + # Retrieve the integer carrier ID from "loaded_mission_energy_carrier" + carrier_id_int = dict_ac_exchange["landing_energy_carrier"].get( + carrier_key) + + # Construct the gravimetric density ID using the integer carrier ID + density_id = f"energy_carrier_gravimetric_density_ID{int(carrier_id_int)}" + + # Retrieve the gravimetric density for this specific carrier + gravimetric_density = dict_ac_exchange["energy_carrier_gravimetric_density"].get( + density_id) + + # Calculate fuel mass if gravimetric density is available + if gravimetric_density is not None: + fuel_mass = consumed_energy / gravimetric_density + fuel_mass_results[energy_id] = fuel_mass + else: + print( + f"{energy_id} - Gravimetric density not found for carrier ID {carrier_id_int}") + else: + runtime_output.critical( + "The landing energy node not found in the mission analysis block. \ + Fuel mass needed for the landing segment set to 0.") + + landing_fuel_mass = sum(fuel_mass_results.values()) + + return landing_fuel_mass + + +def determine_trip_fuel(dict_ac_exchange): + + # Initialize an empty dictionary to store fuel mass for each entry + fuel_mass_results = {} + + # Iterate over the energy entries + for energy_id, consumed_energy in dict_ac_exchange["trip_energy"].items(): + # Generate the corresponding carrier key by replacing "energy" with "energy_carrier" + carrier_key = energy_id.replace("energy", "energy_carrier") + + # Retrieve the integer carrier ID from "loaded_mission_energy_carrier" + carrier_id_int = dict_ac_exchange["trip_energy_carrier"].get( + carrier_key) + + # Construct the gravimetric density ID using the integer carrier ID + density_id = f"energy_carrier_gravimetric_density_ID{int(carrier_id_int)}" + + # Retrieve the gravimetric density for this specific carrier + gravimetric_density = dict_ac_exchange["energy_carrier_gravimetric_density"].get( + density_id) + + # Calculate fuel mass if gravimetric density is available + if gravimetric_density is not None: + fuel_mass = consumed_energy / gravimetric_density + fuel_mass_results[energy_id] = fuel_mass + else: + print( + f"{energy_id} - Gravimetric density not found for carrier ID {carrier_id_int}") + + trip_fuel_mass = sum(fuel_mass_results.values()) + + return trip_fuel_mass + + def DEBUG(msg=""): - print(f"Debug {sys._getframe().f_back.f_lineno}: {msg}") + import logging + runtime_output = logging.getLogger('module_logger') + runtime_output.warning(f"Debug {sys._getframe().f_back.f_lineno}: {msg}") diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/tankIO.py b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/tankIO.py index b4afa5e8..f473a0e7 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/tankIO.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/tankIO.py @@ -10,8 +10,8 @@ class TankIO: self.position = None self.mass_properties = None self.volume = None - self.fuel_density = None #810.0 # kg/m^3 Jet A1 Kerosene - self.gravimetric_density = None # 4.31E7 # 43 MJ/kg for kerosene + self.fuel_density = None # 810.0 # kg/m^3 Jet A1 Kerosene + self.gravimetric_density = None # 4.31E7 # 43 MJ/kg for kerosene self.energy_carrier = None self.max_fuel_mass_capacity = None self.centroid = None @@ -62,7 +62,8 @@ class TankIO: self.mass_properties = MassPropertiesIO( "./mass_properties").read(xml_node) - self.energy = float(xml_node.find("./maximum_energy_capacity/value").text) + self.energy = float(xml_node.find( + "./maximum_energy_capacity/value").text) # self.volume = { # "value": float(xml_node.find("./volume/value").text), diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/transportTaskIO.py b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/transportTaskIO.py index e392ac6d..e740580c 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/basic/transportTaskIO.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/basic/transportTaskIO.py @@ -16,7 +16,8 @@ class TransportTaskIO: def read(self, xml_node): base_path = "./requirements_and_specifications/design_specification/transport_task/" - self.number_of_passengers = int(xml_node.find(base_path + "passenger_definition/total_number_passengers/value").text) + self.number_of_passengers = int(xml_node.find( + base_path + "passenger_definition/total_number_passengers/value").text) self.mass_per_passenger = float(xml_node.find( base_path + "passenger_definition/mass_per_passenger/value").text) self.luggage_mass_per_passenger = float(xml_node.find( diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodhtmlreport.py b/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodhtmlreport.py index 05212417..912d559d 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodhtmlreport.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodhtmlreport.py @@ -33,7 +33,7 @@ def method_html_report(paths_and_names, routing_dict, data_dict, method_specific ("Operating Mass Empty", "OME", data_dict["operating_mass_empty"].mass), ("Maximum Takeoff Mass", "MTOM", - data_dict["maximum_takeoff_mass"].mass), + data_dict["maximum_takeoff_mass"].mass), ("Maximum Zero Fuel Mass", "MZFM", data_dict["maximum_zero_fuel_mass"].mass), ("Maximum Payload Mass", "-", data_dict["maximum_payload_mass"].mass), @@ -46,7 +46,7 @@ def method_html_report(paths_and_names, routing_dict, data_dict, method_specific ("Maximum Takeoff Mass", "MTOM", data_dict["maximum_takeoff_mass"].mass), ("Maximum Landing Mass", "MLM", - data_dict["maximum_landing_mass"].mass), + data_dict["maximum_landing_mass"].mass), ("Design Payload Mass", "-", data_dict["design_payload_mass"].mass), ("Design Fuel Mass Takeoff", "-", data_dict["design_fuel_mass_takeoff"].mass), @@ -62,27 +62,27 @@ def method_html_report(paths_and_names, routing_dict, data_dict, method_specific for key, mass in data_dict["group_masses"].items(): name = key.replace("_", " ").title() aircraft_group_masses.append( - (name, mass, mass/ome * 100.0, mass/mtom * 100.0)) - + (name, mass, mass / ome * 100.0, mass / mtom * 100.0)) + aircraft_structure_masses = [] for key, mass in data_dict["structure_components"].items(): name = key.replace("_", " ").title() aircraft_structure_masses.append( - (name, mass, mass/ome * 100.0, mass/mtom * 100.0)) - - aircraft_propulsion_masses = [] + (name, mass, mass / ome * 100.0, mass / mtom * 100.0)) + + aircraft_propulsion_masses = [] nacelles = data_dict["Nacelles"] - name= "Nacelles" + name = "Nacelles" mass = sum([nacelle.mass for nacelle in nacelles if nacelle is not None]) aircraft_propulsion_masses.append( - (name, mass, mass/ome * 100.0, mass/mtom * 100.0)) + (name, mass, mass / ome * 100.0, mass / mtom * 100.0)) engines = data_dict["Engines"] mass = sum([engine.mass for engine in engines if engine is not None]) name = "Engines" aircraft_propulsion_masses.append( - (name, mass, mass/ome * 100.0, mass/mtom * 100.0)) - + (name, mass, mass / ome * 100.0, mass / mtom * 100.0)) + def format_variable_name(var_name): # Replace underscores with spaces formatted_name = var_name.replace('_', ' ') @@ -96,19 +96,19 @@ def method_html_report(paths_and_names, routing_dict, data_dict, method_specific name = format_variable_name(item['name']) mass = item['mass'] aircraft_systems_masses.append( - (name, mass, mass/ome * 100.0, mass/mtom * 100.0)) + (name, mass, mass / ome * 100.0, mass / mtom * 100.0)) aircraft_furnishing_masses = [] for item in data_dict["Furnishings"]: name = format_variable_name(item.name) aircraft_furnishing_masses.append( - (name, item.mass, item.mass/ome * 100.0, item.mass/mtom * 100.0)) - + (name, item.mass, item.mass / ome * 100.0, item.mass / mtom * 100.0)) + aircraft_operator_masses = [] for item in data_dict["Operator_items"]: name = format_variable_name(item.name) aircraft_operator_masses.append( - (name, item.mass, item.mass/ome * 100.0, item.mass/mtom * 100.0)) + (name, item.mass, item.mass / ome * 100.0, item.mass / mtom * 100.0)) # Start generation with tag('body'): @@ -265,7 +265,7 @@ def method_html_report(paths_and_names, routing_dict, data_dict, method_specific else: with tag('td'): if isinstance(col, str): - text(col) + text(col) with tag('table', klass="content-table"): with tag('caption'): @@ -288,7 +288,7 @@ def method_html_report(paths_and_names, routing_dict, data_dict, method_specific else: with tag('td'): if isinstance(col, str): - text(col) + text(col) with tag('table', klass="content-table"): with tag('caption'): @@ -311,22 +311,27 @@ def method_html_report(paths_and_names, routing_dict, data_dict, method_specific else: with tag('td'): if isinstance(col, str): - text(col) + text(col) with tag('div', klass="box plot"): with tag('h2'): text("Plot") - with tag('img', src='aircraft_group_masses.svg', klass="image-plot"): + with tag('img', src="../plots/aircraft_group_masses.svg", klass="image-plot"): pass - with tag('img', src='weight_and_balance_diagramm.svg', klass="image-plot"): + with tag('img', src="../plots/weight_and_balance_diagramm.svg", klass="image-plot"): + pass + with tag('img', src="../plots/cg_cases.svg", klass="image-plot"): pass - with tag('img', src='cg_cases.svg', klass="image-plot"): - pass soup = BeautifulSoup(doc.getvalue(), 'html.parser') html_output = soup.prettify() - with open(f"{tool_name}_report.html", "w", encoding="utf-8") as report: + # Define the path where the report will be saved + output_path = paths_and_names["project_directory"] + \ + '/reporting/reportHTML/' + f"{tool_name}_report.html" + + # Save the HTML content to the specified path + with open(output_path, "w", encoding="utf-8") as report: report.write(html_output) diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodplot.py b/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodplot.py index d4e68820..d2ef9570 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodplot.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodplot.py @@ -19,6 +19,7 @@ def method_plot(paths_and_names, routing_dict, data_dict, method_specific_output # This is just a dummy code snippet. Insert your code here. tool_name = paths_and_names['tool_name'] + output_path = paths_and_names["project_directory"] + '/reporting/plots/' aircraft_group_masses = [] ome = data_dict["operating_mass_empty"].mass @@ -26,18 +27,20 @@ def method_plot(paths_and_names, routing_dict, data_dict, method_specific_output for key, mass in data_dict["group_masses"].items(): name = key.replace("_", " ").title() aircraft_group_masses.append( - (name, mass, mass/ome * 100.0, mass/mtom * 100.0)) + (name, mass, mass / ome * 100.0, mass / mtom * 100.0)) # Extract components and their values from data_dict components = list(data_dict["group_masses"].keys()) - component_values = list(data_dict["group_masses"].values()) # Mass values in kg + component_values = list( + data_dict["group_masses"].values()) # Mass values in kg structure_components = list(data_dict["structure_components"].keys()) - structure_component_values = list(data_dict["structure_components"].values()) # Mass values in kg + structure_component_values = list( + data_dict["structure_components"].values()) # Mass values in kg # Colors for each component colors = ['red', 'yellow', 'blue', 'orange', 'green'] - #colors_structure = ['darkgreen', 'chartreuse', 'teal', 'aquamarine', 'olive'] + # colors_structure = ['darkgreen', 'chartreuse', 'teal', 'aquamarine', 'olive'] colors_structure = ['0.35', '0.45', '0.57', '0.80', '0.9'] # Setting color characteristics @@ -55,18 +58,19 @@ def method_plot(paths_and_names, routing_dict, data_dict, method_specific_output bottom = 0 for i in range(len(components)): ax.bar(0, component_values[i], bottom=bottom, - width=0.5, color=colors[i], label=components[i]) + width=0.5, color=colors[i], label=components[i]) bottom += component_values[i] - + # Create bars for each individual component (not stacked) for i in range(len(components)): - ax.bar(ind[i + 1], component_values[i], width=0.5, color=colors[i], label=f'{components[i]}') + ax.bar(ind[i + 1], component_values[i], width=0.5, + color=colors[i], label=f'{components[i]}') # Create stacked bar for the structure components bottom = 0 for i in range(len(structure_components)): ax.bar(1, structure_component_values[i], bottom=bottom, - width=0.5, color=colors_structure[i], label=structure_components[i]) + width=0.5, color=colors_structure[i], label=structure_components[i]) bottom += structure_component_values[i] # Configure the plot @@ -78,8 +82,10 @@ def method_plot(paths_and_names, routing_dict, data_dict, method_specific_output # Manually add legend entries for structure components and others handles, labels = ax.get_legend_handles_labels() legend_labels = components + structure_components - handles = [plt.Rectangle((0, 0), 1, 1, color=color) for color in (colors + colors_structure)] - legend = ax.legend(handles, legend_labels, loc='upper right', fontsize='small') + handles = [plt.Rectangle((0, 0), 1, 1, color=color) + for color in (colors + colors_structure)] + legend = ax.legend(handles, legend_labels, + loc='upper right', fontsize='small') # Set the text in the legend to black for text in legend.get_texts(): @@ -90,7 +96,7 @@ def method_plot(paths_and_names, routing_dict, data_dict, method_specific_output spine.set_edgecolor('#ecf0f1') # Save the figure - plt.savefig("./aircraft_group_masses.svg", format="svg") + plt.savefig(output_path + "aircraft_group_masses.svg", format="svg") # Loading chart @@ -114,87 +120,81 @@ def method_plot(paths_and_names, routing_dict, data_dict, method_specific_output data_dict["Defueling mass change"], 'm') # Defueling legend_labels = ['Refueling', 'Pax from front', 'Pax from back', 'Cargo from front', 'Cargo from back', 'Defueling', 'OME', 'MTOM', 'Neutral point Ma=0,83', - 'Most fwd CG', 'Most aft CG', 'NP-5%'] + 'Most fwd CG', 'Most aft CG', 'NP-5%'] else: legend_labels = ['Refueling', 'Pax from front', 'Pax from back', 'Cargo from front', 'Cargo from back', 'OME', 'MTOM', 'Neutral point Ma=0,83', - 'Most fwd CG', 'Most aft CG', 'NP-5%'] + 'Most fwd CG', 'Most aft CG', 'NP-5%'] - N = np.arange(data_dict["Most forward CG mac position"]-5, max( - [data_dict["Most aft CG mac position"], -data_dict["Neutral point mac position"]*100])+5) + N = np.arange(data_dict["Most forward CG mac position"] - 5, max( + [data_dict["Most aft CG mac position"], data_dict["Neutral point mac position"]]) + 5) plt.hlines(data_dict["operating_mass_empty"].mass, N.min(), N.max(), colors='gray', linestyles='--') plt.hlines(data_dict["maximum_takeoff_mass"].mass, N.min(), N.max(), colors='brown', linestyles='--') - plt.xlim(data_dict["Most forward CG mac position"]-5, max( - [data_dict["Most aft CG mac position"], -data_dict["Neutral point mac position"]*100])+5) + plt.xlim(data_dict["Most forward CG mac position"] - 5, max( + [data_dict["Most aft CG mac position"], data_dict["Neutral point mac position"]]) + 5) - M = np.arange(ome-100, mtom+100) - plt.plot(-np.ones(len(M)) * - data_dict["Neutral point mac position"]*100, M, 'r') + M = np.arange(ome - 100, mtom + 100) + plt.plot(np.ones(len(M)) * data_dict["Neutral point mac position"], M, 'r') plt.plot(np.ones(len(M)) * data_dict["Most forward CG mac position"], M, 'g') plt.plot(np.ones(len(M)) * data_dict["Most aft CG mac position"], M, 'limegreen') - plt.plot(-np.ones(len(M)) * - data_dict["Neutral point mac position"]*100 - 5, M, 'r-.') + plt.plot(np.ones(len(M)) * + data_dict["Neutral point mac position"] - 5, M, 'r-.') # Define legend labels and colors - legend = plt.legend(legend_labels, loc='upper left', fontsize='small') + legend = plt.legend(legend_labels, loc='upper right', fontsize='small') for text in legend.get_texts(): text.set_color('black') - plt.grid(visible=False, color='#ecf0f1') + plt.grid(color='#ecf0f1') plt.title("Loading Diagramm") plt.xlabel("CG x-position over MAC in %", color='#ecf0f1') plt.ylabel("Mass [kg]", color='#ecf0f1') - plt.savefig("./weight_and_balance_diagramm.svg", format="svg") + plt.savefig(output_path + "weight_and_balance_diagramm.svg", format="svg") # Plot different mass configuration cases: plt.figure(figsize=(8, 5), facecolor='#494f4f') plt.plot(data_dict["operating_mass_empty"].cg_mac, - data_dict["operating_mass_empty"].mass, '+') + data_dict["operating_mass_empty"].mass, '+') plt.plot(data_dict["maximum_zero_fuel_mass"].cg_mac, data_dict["maximum_zero_fuel_mass"].mass, 'x') - + plt.plot(data_dict["mtow_with_max_payload"].cg_mac, data_dict["mtow_with_max_payload"].mass, '*') plt.plot(data_dict["mtow_with_max_fuel"].cg_mac, data_dict["mtow_with_max_fuel"].mass, 'D') - + plt.plot(data_dict["maximum_fuel_mass"].cg_mac, - data_dict["maximum_fuel_mass"].mass,'o') - - plt.plot(data_dict["design_mass"].cg_mac, - data_dict["design_mass"].mass, 's') + data_dict["maximum_fuel_mass"].mass, 'o') - # plt.xlim(data_dict["Most forward CG mac position"]-5, max( - # [data_dict["Most aft CG mac position"], -data_dict["Neutral point mac position"]*100])+5) + plt.plot(data_dict["design_mass"].cg_mac, + data_dict["design_mass"].mass, 's') # Define legend labels and colors - legend_labels2 = ['OME', 'MZFM', 'max Payload + Fuel', 'max Fuel + Payload', 'Maximum Fuel Mass', - 'Design Fuel + Design Payload'] + legend_labels2 = ['OME', 'MZFM (max Payload)', 'max Payload + Fuel', 'max Fuel + Payload', 'max Fuel', + 'Design Fuel + Design Payload'] legend = plt.legend(legend_labels2, loc='upper left', fontsize='small') for text in legend.get_texts(): text.set_color('black') - plt.grid(visible=False, color='#ecf0f1') + plt.grid(color='#ecf0f1') plt.title("Loading Cases") plt.xlabel("CG x-position over MAC in %", color='#ecf0f1') plt.ylabel("Mass [kg]", color='#ecf0f1') - #plt.show - plt.savefig("./cg_cases.svg", format="svg") - + plt.savefig(output_path + "cg_cases.svg", format="svg") runtime_output.print("plotted") diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodxmlexport.py b/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodxmlexport.py index 1dd3e43e..c618c2ef 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodxmlexport.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/general/methodxmlexport.py @@ -29,18 +29,19 @@ def method_xml_export(paths_and_names, routing_dict, data_dict, method_specific_ :raises OSError: Raised if writing to aircraft exchange file failed :return: None """ - runtime_output.print("Method-specific data are written to '" + routing_dict['module_name'] + "_results.xml'...") + runtime_output.print("Method-specific data are written to '" + + routing_dict['module_name'] + "_results.xml'...") # Function to write data to 'cost_estimation_results.xml' root_of_results_file = xml_export_tree.getroot() parent = root_of_results_file.find('calculation_results') # Add method node. root_of_module_config_tree = paths_and_names['root_of_module_config_tree'] - - + # Prepare ElementTree for export to module-specific XML. - prepare_element_tree_for_module_specific_export(root_of_results_file, method_specific_output_dict) - + prepare_element_tree_for_module_specific_export( + root_of_results_file, method_specific_output_dict) + # Write all parameters to export file. try: # Ensure proper indentation. @@ -49,8 +50,10 @@ def method_xml_export(paths_and_names, routing_dict, data_dict, method_specific_ xml_export_tree.write(path_to_results_file) # Exception handling for operating system error. except OSError: - runtime_output.error('Error: Writing to aircraft exchange file failed. Program aborted!') - + runtime_output.error( + 'Error: Writing to aircraft exchange file failed. Program aborted!') + + def prepare_element_tree_for_module_specific_export(root_of_results_file, specific_output_dict): """ Prepare ElementTree for module-specific results export. @@ -92,7 +95,9 @@ def prepare_element_tree_for_module_specific_export(root_of_results_file, specif new_node.set(key, value) # Add further sub-elements if defined. if 'parameters' in specific_output_dict[part]: - current_path = root_of_results_file.find(path_to_check) + current_path = root_of_results_file.find( + path_to_check) for key, value in specific_output_dict[part]['parameters'].items(): - parameter_node = ET.SubElement(current_path, key) + parameter_node = ET.SubElement( + current_path, key) parameter_node.text = str(value) diff --git a/weight_and_balance_analysis/src/tube_and_wing/standard/usermethoddatapreparation.py b/weight_and_balance_analysis/src/tube_and_wing/standard/usermethoddatapreparation.py index a8c21ce7..17254796 100644 --- a/weight_and_balance_analysis/src/tube_and_wing/standard/usermethoddatapreparation.py +++ b/weight_and_balance_analysis/src/tube_and_wing/standard/usermethoddatapreparation.py @@ -29,21 +29,50 @@ def user_method_data_input_preparation(routing_dict): design_specification_path = \ './requirements_and_specifications/design_specification/' data_to_extract_from_aircraft_exchange_dict = { - "mission_energy": [mission_analysis_path + "mission_energy/consumed_energy", float], - "trip_energy": [mission_analysis_path + "trip_energy/consumed_energy", float], - "maximum_structural_payload_mass": [top_level_aircraft_requirements_path + 'maximum_structrual_payload_mass', - float], + "maximum_structural_payload_mass": [ + top_level_aircraft_requirements_path + 'maximum_structrual_payload_mass', float], "wing_exist": [component_design_path + "wing", None], "fuselage_exist": [component_design_path + "fuselage", None], - # "tank_exist": [component_design_path + "tank", None], "empennage_exist": [component_design_path + "empennage", None], "landing_gear_exist": [component_design_path + "landing_gear", None], "propulsion_exist": [component_design_path + "propulsion", None], "systems_exist": [component_design_path + "systems", None], "wing_mounting": [requirements_wing_path + 'mounting', str], + "x_global_reference_point": [component_design_path + 'global_reference_point/x', float], "x_wing_globlal_position": [component_design_path + 'wing/position/x', float], - "x_aircraft_neutral_point" : [aero_analysis_path + "neutral_point/x", float], - "aircraft_type": [design_specification_path + "configuration/configuration_type",str] + "x_aircraft_neutral_point": [aero_analysis_path + 'neutral_point/x', float], + "aircraft_type": [design_specification_path + "configuration/configuration_type", str], + "loaded_mission_energy": [ + mission_analysis_path + 'loaded_mission_energy/mission_energy[@ID="0"]/consumed_energy', float], + "loaded_mission_energy_carrier": [ + mission_analysis_path + 'loaded_mission_energy/mission_energy[@ID="0"]/energy_carrier_ID', float], + "taxi_energy_in": [ + mission_analysis_path + 'taxi_energy/taxi_in_energy[@ID="0"]/consumed_energy', float], + "taxi_energy_in_carrier": [ + mission_analysis_path + 'taxi_energy/taxi_in_energy[@ID="0"]/energy_carrier_ID', float], + "taxi_energy_out": [ + mission_analysis_path + 'taxi_energy/taxi_out_energy[@ID="0"]/consumed_energy', float], + "taxi_energy_out_carrier": [ + mission_analysis_path + 'taxi_energy/taxi_out_energy[@ID="0"]/energy_carrier_ID', float], + "take_off_energy": [ + mission_analysis_path + 'in_flight_energy/takoff_energy[@ID="0"]/consumed_energy', float], + "take_off_energy_carrier": [ + mission_analysis_path + 'in_flight_energy/takoff_energy[@ID="0"]/energy_carrier_ID', float], + "landing_energy": [ + mission_analysis_path + 'in_flight_energy/landing_energy[@ID="0"]/consumed_energy', float], + "landing_energy_carrier": [ + mission_analysis_path + 'in_flight_energy/landing_energy[@ID="0"]/energy_carrier_ID', float], + "trip_energy": [ + mission_analysis_path + 'in_flight_energy/trip_energy[@ID="0"]/consumed_energy', float], + "trip_energy_carrier": [ + mission_analysis_path + 'in_flight_energy/trip_energy[@ID="0"]/energy_carrier_ID', float], + "energy_carrier_gravimetric_density": [design_specification_path + + 'energy_carriers/energy_carrier[@ID="0"]/gravimetric_density', float], + "energy_carrier_volumetric_density": [design_specification_path + + 'energy_carriers/energy_carrier[@ID="0"]/volumetric_density', float], + "energy_carrier_density": [design_specification_path + 'energy_carriers/energy_carrier[@ID="0"]/density', + float], + "energy_carrier_type": [design_specification_path + 'energy_carriers/energy_carrier[@ID="0"]/type', str], } """Module configuration file.""" @@ -53,7 +82,7 @@ def user_method_data_input_preparation(routing_dict): module_settings_path = f"./program_settings/tube_and_wing/standard/{routing_dict['user_layer']}/" general_data_to_extract_from_module_configuration_dict = { "mode_inertia_calculation": [module_settings_path + "calculation_methods/inertia/method", str], - "aircraft_class": [module_settings_path + "calculation_methods/aircraft_type",str], + "aircraft_class": [module_settings_path + "calculation_methods/aircraft_type", str], "mode_maximum_landing_mass_calculation": [module_settings_path + "calculation_methods/maximum_landing_mass/method", str], "refueling_mode": [module_settings_path + "calculation_methods/refueling_mode/method", str], @@ -98,16 +127,28 @@ def user_method_data_output_preparation(data_dict): """ key_output_dict = {} - key_output_dict.update(data_dict["manufacturer_mass_empty"].user_method_data_generation()) - key_output_dict.update(data_dict["operating_mass_empty"].user_method_data_generation()) - key_output_dict.update(data_dict["maximum_takeoff_mass"].user_method_data_generation()) - key_output_dict.update(data_dict["maximum_zero_fuel_mass"].user_method_data_generation()) - key_output_dict.update(data_dict["maximum_payload_mass"].user_method_data_generation()) - key_output_dict.update(data_dict["maximum_landing_mass"].user_method_data_generation()) - key_output_dict.update(data_dict["maximum_fuel_mass"].user_method_data_generation()) - key_output_dict.update(data_dict["most_forward_mass"].user_method_data_generation()) - key_output_dict.update(data_dict["most_afterward_mass"].user_method_data_generation()) - key_output_dict.update(data_dict["design_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["manufacturer_mass_empty"].user_method_data_generation()) + key_output_dict.update( + data_dict["operating_mass_empty"].user_method_data_generation()) + key_output_dict.update( + data_dict["maximum_takeoff_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["maximum_zero_fuel_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["maximum_payload_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["maximum_landing_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["maximum_fuel_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["most_forward_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["most_afterward_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["design_mass"].user_method_data_generation()) + key_output_dict.update( + data_dict["design_fuel_mass"].user_method_data_generation()) method_specific_output_dict = { # List of paths diff --git a/weight_and_balance_analysis/weight_and_balance_analysis_conf.xml b/weight_and_balance_analysis/weight_and_balance_analysis_conf.xml index 5e3c4f51..731f5bac 100644 --- a/weight_and_balance_analysis/weight_and_balance_analysis_conf.xml +++ b/weight_and_balance_analysis/weight_and_balance_analysis_conf.xml @@ -45,6 +45,11 @@ <gnuplot_path description="Path to the gnuplot application (DEFAULT: Use gnuplot from the UNICADO repo structure)"> <value>DEFAULT</value> </gnuplot_path> + <program_specific_control_settings description="Program specific control settings for this tool"> + <xml_output description="Switch to export module specific data to XML. Switch: true (On) / false (Off)"> + <value>false</value> + </xml_output> + </program_specific_control_settings> </control_settings> <program_settings description="program settings"> <tube_and_wing description="Weight and balance analysis"> -- GitLab