def _data_weight_decomposition(variables: VariableIO, owe=None): """ Returns the two level weight decomposition of MTOW and optionally the decomposition of owe subcategories. :param variables: instance containing variables information :param owe: value of OWE, if provided names of owe subcategories will be provided :return: variable values, names and optionally owe subcategories names """ category_values = [] category_names = [] owe_subcategory_names = [] for variable in variables.names(): name_split = variable.split(":") if isinstance(name_split, list) and len(name_split) == 4: if name_split[0] + name_split[1] + name_split[ 3] == "dataweightmass" and not ("aircraft" in name_split[2]): category_values.append( convert_units(variables[variable].value[0], variables[variable].units, "kg")) category_names.append(name_split[2]) if owe: owe_subcategory_names.append( name_split[2] + "<br>" + str(int(variables[variable].value[0])) + " [kg] (" + str(round(variables[variable].value[0] / owe * 100, 1)) + "%)") if owe: result = category_values, category_names, owe_subcategory_names else: result = category_values, category_names, None return result
def mass_breakdown_sun_plot(aircraft_file_path: str, file_formatter=None): """ Returns a figure sunburst plot of the mass breakdown. On the left a MTOW sunburst and on the right a OWE sunburst. Different designs can be superposed by providing an existing fig. Each design can be provided a name. :param aircraft_file_path: path of data file :param file_formatter: the formatter that defines the format of data file. If not provided, default format will be assumed. :return: sunburst plot figure """ variables = VariableIO(aircraft_file_path, file_formatter).read() var_names_and_new_units = { "data:weight:aircraft:MTOW": "kg", "data:weight:aircraft:OWE": "kg", "data:weight:aircraft:payload": "kg", "data:weight:aircraft:sizing_onboard_fuel_at_takeoff": "kg", } # pylint: disable=unbalanced-tuple-unpacking # It is balanced for the parameters provided mtow, owe, payload, onboard_fuel_at_takeoff = _get_variable_values_with_new_units( variables, var_names_and_new_units) # TODO: Deal with this in a more generic manner ? if round(mtow, 6) == round(owe + payload + onboard_fuel_at_takeoff, 6): mtow = owe + payload + onboard_fuel_at_takeoff fig = make_subplots( 1, 2, specs=[[{ "type": "domain" }, { "type": "domain" }]], ) fig.add_trace( go.Sunburst( labels=[ "MTOW" + "<br>" + str(int(mtow)) + " [kg]", "payload" + "<br>" + str(int(payload)) + " [kg] (" + str(round(payload / mtow * 100, 1)) + "%)", "onboard_fuel_at_takeoff" + "<br>" + str(int(onboard_fuel_at_takeoff)) + " [kg] (" + str(round(onboard_fuel_at_takeoff / mtow * 100, 1)) + "%)", "OWE" + "<br>" + str(int(owe)) + " [kg] (" + str(round(owe / mtow * 100, 1)) + "%)", ], parents=[ "", "MTOW" + "<br>" + str(int(mtow)) + " [kg]", "MTOW" + "<br>" + str(int(mtow)) + " [kg]", "MTOW" + "<br>" + str(int(mtow)) + " [kg]", ], values=[mtow, payload, onboard_fuel_at_takeoff, owe], branchvalues="total", ), 1, 1, ) # Get data:weight 2-levels decomposition categories_values, categories_names, categories_labels = _data_weight_decomposition( variables, owe=owe) sub_categories_values = [] sub_categories_names = [] sub_categories_parent = [] for variable in variables.names(): name_split = variable.split(":") if isinstance(name_split, list) and len(name_split) >= 5: parent_name = name_split[2] if parent_name in categories_names and name_split[-1] == "mass": variable_name = "_".join(name_split[3:-1]) sub_categories_values.append( convert_units(variables[variable].value[0], variables[variable].units, "kg")) sub_categories_parent.append( categories_labels[categories_names.index(parent_name)]) sub_categories_names.append(variable_name) # Define figure data figure_labels = ["OWE" + "<br>" + str(int(owe)) + " [kg]"] figure_labels.extend(categories_labels) figure_labels.extend(sub_categories_names) figure_parents = [""] for _ in categories_names: figure_parents.append("OWE" + "<br>" + str(int(owe)) + " [kg]") figure_parents.extend(sub_categories_parent) figure_values = [owe] figure_values.extend(categories_values) figure_values.extend(sub_categories_values) # Plot figure fig.add_trace( go.Sunburst( labels=figure_labels, parents=figure_parents, values=figure_values, branchvalues="total", ), 1, 2, ) fig.update_layout(title_text="Mass Breakdown", title_x=0.5) return fig
def generate_block_analysis( system: Union[ExplicitComponent, ImplicitComponent, Group], var_inputs: List, xml_file_path: str, overwrite: bool = False, ): # Search what are the component/group outputs variables = list_variables(system) inputs_names = [var.name for var in variables if var.is_input] outputs_names = [var.name for var in variables if not var.is_input] # Check that variable inputs are in the group/component list if not(set(var_inputs) == set(inputs_names).intersection(set(var_inputs))): raise Exception('The input list contains name(s) out of component/group input list!') # Perform some tests on the .xml availability and completeness if not(os.path.exists(xml_file_path)) and not(set(var_inputs) == set(inputs_names)): # If no input file and some inputs are missing, generate it and return None if isinstance(system, Group): problem = FASTOADProblem(system) else: group = AutoUnitsDefaultGroup() group.add_subsystem('system', system, promotes=["*"]) problem = FASTOADProblem(group) problem.input_file_path = xml_file_path problem.setup() problem.write_needed_inputs(None, VariableXmlStandardFormatter()) raise Exception('Input .xml file not found, a default file has been created with default NaN values, ' 'but no function is returned!\nConsider defining proper values before second execution!') elif os.path.exists(xml_file_path): reader = VariableIO(xml_file_path, VariableXmlStandardFormatter()).read(ignore=(var_inputs + outputs_names)) xml_inputs = reader.names() if not(set(xml_inputs + var_inputs).intersection(set(inputs_names)) == set(inputs_names)): # If some inputs are missing write an error message and add them to the problem if authorized missing_inputs = list( set(inputs_names).difference(set(xml_inputs + var_inputs).intersection(set(inputs_names))) ) message = 'The following inputs are missing in .xml file:' for item in missing_inputs: message += ' [' + item + '],' message = message[:-1] + '.\n' if overwrite: reader.path_separator = ":" ivc = reader.to_ivc() group = AutoUnitsDefaultGroup() group.add_subsystem('system', system, promotes=["*"]) group.add_subsystem('ivc', ivc, promotes=["*"]) problem = FASTOADProblem(group) problem.input_file_path = xml_file_path problem.output_file_path = xml_file_path problem.setup() problem.write_outputs() message += 'Default values have been added to {} file. ' \ 'Consider modifying them for a second run!'.format(xml_file_path) raise Exception(message) else: raise Exception(message) else: # If all inputs addressed either by .xml or var_inputs, construct the function def patched_function(inputs_dict: dict) -> dict: """ The patched function perform a run of an openmdao component or group applying FASTOAD formalism. @param inputs_dict: dictionary of input (values, units) saved with their key name, as an example: inputs_dict = {'in1': (3.0, "m")}. @return: dictionary of the component/group outputs saving names as keys and (value, units) as tuple. """ # Read .xml file and construct Independent Variable Component excluding outputs reader.path_separator = ":" ivc_local = reader.to_ivc() for name, value in inputs_dict.items(): ivc_local.add_output(name, value[0], units=value[1]) group_local = AutoUnitsDefaultGroup() group_local.add_subsystem('system', system, promotes=["*"]) group_local.add_subsystem('ivc', ivc_local, promotes=["*"]) problem_local = FASTOADProblem(group_local) problem_local.setup() problem_local.run_model() if overwrite: problem_local.output_file_path = xml_file_path problem_local.write_outputs() # Get output names from component/group and construct dictionary outputs_units = [var.units for var in variables if not var.is_input] outputs_dict = {} for idx in range(len(outputs_names)): value = problem_local.get_val(outputs_names[idx], outputs_units[idx]) outputs_dict[outputs_names[idx]] = (value, outputs_units[idx]) return outputs_dict return patched_function