Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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