def save(self): """ Save the optimization to the files. Possible files modified are: - the .yml configuration file - the input file (initial values) - the output file (values) """ conf = self.problem_configuration input_variables = DataFile(self.problem_configuration.input_file_path, None) output_variables = DataFile( self.problem_configuration.output_file_path, None) opt_def = conf.get_optimization_definition() variables = self.get_variables() for variable in variables: name = variable.name if name in input_variables.names(): input_variables[name].value = variable.metadata[ "initial_value"] if name in output_variables.names(): output_variables[name].value = variable.metadata["value"] self._update_optim_variable(variable, opt_def) # Saving modifications # Initial values input_variables.save() # Values output_variables.save() # Optimization definition conf.set_optimization_definition(opt_def) conf.save()
def _get_problem_inputs( self, problem: FASTOADProblem) -> Tuple[om.IndepVarComp, VariableList]: """ Reads input file for the configure problem. Needed variables are returned as an IndepVarComp instance while unused variables are returned as a VariableList instance. :param problem: problem with missing inputs. setup() must have been run. :return: IVC of needed input variables, VariableList with unused variables. """ mandatory, optional = get_unconnected_input_names(problem, promoted_names=True) needed_variable_names = mandatory + optional input_variables = DataFile(self.input_file_path) unused_variables = VariableList([ var for var in input_variables if var.name not in needed_variable_names ]) for name in unused_variables.names(): del input_variables[name] nan_variable_names = [ var.name for var in input_variables if np.all(np.isnan(var.value)) ] if nan_variable_names: raise FASTConfigurationNanInInputFile(self.input_file_path, nan_variable_names) input_ivc = input_variables.to_ivc() return input_ivc, unused_variables
def test_problem_definition_with_xml_ref_with_indep(cleanup): """Tests what happens when writing inputs of a problem with indeps using data from existing XML file""" for extension in ["toml", "yml"]: clear_openmdao_registry() conf = FASTOADProblemConfigurator( pth.join(DATA_FOLDER_PATH, "valid_sellar_with_indep.%s" % extension) ) result_folder_path = pth.join( RESULTS_FOLDER_PATH, "problem_definition_with_xml_ref_with_indep" ) conf.input_file_path = pth.join(result_folder_path, "inputs.xml") conf.output_file_path = pth.join(result_folder_path, "outputs.xml") ref_input_data_path = pth.join(DATA_FOLDER_PATH, "ref_inputs.xml") conf.write_needed_inputs(ref_input_data_path) input_data = DataFile(conf.input_file_path) assert len(input_data) == 2 assert "system:x" in input_data.names() assert "z" in input_data.names() problem = conf.get_problem(read_inputs=True, auto_scaling=True) # runs evaluation without optimization loop to check that inputs are taken into account problem.setup() # system:x is not in ref_inputs.xml problem["system:x"] = 1.0 problem.run_model() assert problem["f"] == pytest.approx(28.58830817, abs=1e-6) problem.write_outputs()
def write_needed_inputs(self, source_file_path: str = None, source_formatter: IVariableIOFormatter = None): """ Writes the input file of the problem with unconnected inputs of the configured problem. Written value of each variable will be taken: 1. from input_data if it contains the variable 2. from defined default values in component definitions :param source_file_path: if provided, variable values will be read from it :param source_formatter: the class that defines format of input file. if not provided, expected format will be the default one. """ problem = self.get_problem(read_inputs=False) problem.setup() variables = DataFile(self.input_file_path, load_data=False) variables.update( VariableList.from_unconnected_inputs(problem, with_optional_inputs=True), add_variables=True, ) if source_file_path: ref_vars = DataFile(source_file_path, formatter=source_formatter) variables.update(ref_vars, add_variables=False) for var in variables: var.is_input = True variables.save()
def test_mission_group_without_loop(cleanup, with_dummy_plugin_2): input_file_path = pth.join(DATA_FOLDER_PATH, "test_mission.xml") ivc = DataFile(input_file_path).to_ivc() with pytest.raises(FastMissionFileMissingMissionNameError): run_system( Mission( propulsion_id="test.wrapper.propulsion.dummy_engine", out_file=pth.join(RESULTS_FOLDER_PATH, "unlooped_mission_group.csv"), use_initializer_iteration=False, mission_file_path=pth.join(DATA_FOLDER_PATH, "test_mission.yml"), adjust_fuel=False, reference_area_variable="data:geometry:aircraft:reference_area", ), ivc, ) problem = run_system( Mission( propulsion_id="test.wrapper.propulsion.dummy_engine", out_file=pth.join(RESULTS_FOLDER_PATH, "unlooped_mission_group.csv"), use_initializer_iteration=False, mission_file_path=pth.join(DATA_FOLDER_PATH, "test_mission.yml"), mission_name="operational", adjust_fuel=False, reference_area_variable="data:geometry:aircraft:reference_area", ), ivc, ) assert_allclose(problem["data:mission:operational:needed_block_fuel"], 6589.0, atol=1.0) assert_allclose(problem["data:mission:operational:block_fuel"], 15195.0, atol=1.0)
def _get_problem_inputs(self) -> Tuple[VariableList, VariableList]: """ Reads input file for the configured problem. Needed variables and unused variables are returned as a VariableList instance. :return: VariableList of needed input variables, VariableList with unused variables. """ problem_variables = VariableList().from_problem(self) problem_inputs_names = [var.name for var in problem_variables if var.is_input] input_variables = DataFile(self.input_file_path) unused_variables = VariableList( [var for var in input_variables if var.name not in problem_inputs_names] ) for name in unused_variables.names(): del input_variables[name] nan_variable_names = [var.name for var in input_variables if np.all(np.isnan(var.value))] if nan_variable_names: raise FASTOpenMDAONanInInputFile(self.input_file_path, nan_variable_names) return input_variables, unused_variables
def test_mission_group_with_loop(cleanup): input_file_path = pth.join(DATA_FOLDER_PATH, "test_mission.xml") vars = DataFile(input_file_path) del vars["data:mission:operational:TOW"] ivc = vars.to_ivc() problem = run_system( Mission( propulsion_id="test.wrapper.propulsion.dummy_engine", out_file=pth.join(RESULTS_FOLDER_PATH, "test_looped_mission_group.csv"), use_initializer_iteration=True, mission_file_path=pth.join(DATA_FOLDER_PATH, "test_mission.yml"), mission_name="operational", add_solver=True, ), ivc, ) # check loop assert_allclose( problem["data:mission:operational:TOW"], problem["data:mission:operational:OWE"] + problem["data:mission:operational:payload"] + problem["data:mission:operational:onboard_fuel_at_takeoff"], atol=1.0, ) assert_allclose( problem["data:mission:operational:needed_onboard_fuel_at_takeoff"], problem["data:mission:operational:onboard_fuel_at_takeoff"], atol=1.0, ) assert_allclose( problem["data:mission:operational:needed_block_fuel"], problem["data:mission:operational:needed_onboard_fuel_at_takeoff"] + problem["data:mission:operational:taxi_out:fuel"] + problem["data:mission:operational:takeoff:fuel"], atol=1.0, ) assert_allclose( problem["data:mission:operational:needed_block_fuel"], 5682.0, atol=1.0, )
def test_mission_group_breguet_with_loop(cleanup, with_dummy_plugin_2): input_file_path = pth.join(DATA_FOLDER_PATH, "test_mission.xml") vars = DataFile(input_file_path) del vars["data:mission:operational:TOW"] ivc = vars.to_ivc() problem = run_system( Mission( propulsion_id="test.wrapper.propulsion.dummy_engine", out_file=pth.join(RESULTS_FOLDER_PATH, "looped_breguet_mission_group.csv"), use_initializer_iteration=True, mission_file_path=pth.join(DATA_FOLDER_PATH, "test_breguet.yml"), add_solver=True, reference_area_variable="data:geometry:aircraft:reference_area", ), ivc, ) # check loop assert_allclose( problem["data:mission:operational:TOW"], problem["data:mission:operational:OWE"] + problem["data:mission:operational:payload"] + problem["data:mission:operational:onboard_fuel_at_takeoff"], atol=1.0, ) assert_allclose( problem["data:mission:operational:needed_onboard_fuel_at_takeoff"], problem["data:mission:operational:onboard_fuel_at_takeoff"], atol=1.0, ) assert_allclose( problem["data:mission:operational:needed_block_fuel"], problem["data:mission:operational:needed_onboard_fuel_at_takeoff"] + problem["data:mission:operational:taxi_out:fuel"] + problem["data:mission:operational:takeoff:fuel"], atol=1.0, ) assert_allclose(problem["data:mission:operational:needed_block_fuel"], 5626.0, atol=1.0) assert_allclose( problem["data:mission:operational:needed_onboard_fuel_at_takeoff"], 5430.0, atol=1.0 )
def test_problem_definition_with_xml_ref(cleanup): """Tests what happens when writing inputs using data from existing XML file""" for extension in ["toml", "yml"]: clear_openmdao_registry() conf = FASTOADProblemConfigurator(pth.join(DATA_FOLDER_PATH, "valid_sellar.%s" % extension)) result_folder_path = pth.join(RESULTS_FOLDER_PATH, "problem_definition_with_xml_ref") conf.input_file_path = pth.join(result_folder_path, "inputs.xml") conf.output_file_path = pth.join(result_folder_path, "outputs.xml") ref_input_data_path = pth.join(DATA_FOLDER_PATH, "ref_inputs.xml") conf.write_needed_inputs(ref_input_data_path) input_data = DataFile(conf.input_file_path) assert len(input_data) == 2 assert "x" in input_data.names() assert "z" in input_data.names() problem = conf.get_problem(read_inputs=True, auto_scaling=True) # runs evaluation without optimization loop to check that inputs are taken into account problem.setup() problem.run_model() assert problem["f"] == pytest.approx(28.58830817, abs=1e-6) problem.write_outputs() # Test with alternate submodel ######################################### alt_conf = FASTOADProblemConfigurator( pth.join(DATA_FOLDER_PATH, "valid_sellar_alternate.%s" % extension) ) alt_conf.input_file_path = pth.join(result_folder_path, "inputs.xml") alt_conf.output_file_path = pth.join(result_folder_path, "outputs_alt.xml") alt_problem = alt_conf.get_problem(read_inputs=True, auto_scaling=True) # runs evaluation without optimization loop to check that inputs are taken into account alt_problem.setup() alt_problem.run_model() alt_problem.write_outputs() assert alt_problem["f"] == pytest.approx(0.58830817, abs=1e-6) assert alt_problem["g2"] == pytest.approx(-11.94151185, abs=1e-6) with pytest.raises(KeyError): alt_problem["g1"] # submodel for g1 computation has been deactivated.
def test_mission_component(cleanup, with_dummy_plugin_2): input_file_path = pth.join(DATA_FOLDER_PATH, "test_mission.xml") ivc = DataFile(input_file_path).to_ivc() problem = run_system( MissionComponent( propulsion_id="test.wrapper.propulsion.dummy_engine", out_file=pth.join(RESULTS_FOLDER_PATH, "mission.csv"), use_initializer_iteration=False, mission_wrapper=MissionWrapper(pth.join(DATA_FOLDER_PATH, "test_mission.yml")), mission_name="operational", reference_area_variable="data:geometry:aircraft:reference_area", ), ivc, ) # plot_flight(problem.model.component.flight_points, "test_mission.png") assert_allclose(problem["data:mission:operational:needed_block_fuel"], 6590.0, atol=1.0) assert_allclose( problem["data:mission:operational:main_route:initial_climb:duration"], 34.0, atol=1.0 ) assert_allclose( problem["data:mission:operational:main_route:initial_climb:fuel"], 121.0, atol=1.0 ) assert_allclose( problem["data:mission:operational:main_route:initial_climb:distance"], 3600.0, atol=1.0 ) assert_allclose(problem["data:mission:operational:main_route:climb:duration"], 236.0, atol=1.0) assert_allclose(problem["data:mission:operational:main_route:climb:fuel"], 727.0, atol=1.0) assert_allclose( problem["data:mission:operational:main_route:climb:distance"], 43065.0, atol=1.0 ) assert_allclose( problem["data:mission:operational:main_route:cruise:duration"], 14734.0, atol=1.0 ) assert_allclose(problem["data:mission:operational:main_route:cruise:fuel"], 5167.0, atol=1.0) assert_allclose( problem["data:mission:operational:main_route:cruise:distance"], 3392590.0, atol=1.0 ) assert_allclose( problem["data:mission:operational:main_route:descent:duration"], 1424.0, atol=1.0 ) assert_allclose(problem["data:mission:operational:main_route:descent:fuel"], 192.0, atol=1.0) assert_allclose( problem["data:mission:operational:main_route:descent:distance"], 264451.0, atol=1.0 )
def test_mission_component_breguet(cleanup): input_file_path = pth.join(DATA_FOLDER_PATH, "test_mission.xml") ivc = DataFile(input_file_path).to_ivc() problem = run_system( MissionComponent( propulsion_id="test.wrapper.propulsion.dummy_engine", out_file=pth.join(RESULTS_FOLDER_PATH, "test_breguet.csv"), use_initializer_iteration=False, mission_wrapper=MissionWrapper( pth.join(DATA_FOLDER_PATH, "test_breguet.yml")), mission_name="operational", ), ivc, ) # plot_flight(problem.model.component.flight_points, "test_mission.png") assert_allclose(problem["data:mission:operational:needed_block_fuel"], 6353.0, atol=1.0)
def test_mission_group_breguet_without_loop(cleanup): input_file_path = pth.join(DATA_FOLDER_PATH, "test_mission.xml") ivc = DataFile(input_file_path).to_ivc() problem = run_system( Mission( propulsion_id="test.wrapper.propulsion.dummy_engine", out_file=pth.join(RESULTS_FOLDER_PATH, "test_unlooped_mission_group.csv"), use_initializer_iteration=False, mission_file_path=pth.join(DATA_FOLDER_PATH, "test_breguet.yml"), adjust_fuel=False, ), ivc, ) assert_allclose(problem["data:mission:operational:needed_block_fuel"], 6353.0, atol=1.0) assert_allclose(problem["data:mission:operational:block_fuel"], 15195.0, atol=1.0)
def load( self, problem_configuration: FASTOADProblemConfigurator, ): """ Loads the FAST-OAD problem and stores its data. :param problem_configuration: the FASTOADProblem instance. """ self.problem_configuration = problem_configuration if pth.isfile(self.problem_configuration.input_file_path): input_variables = DataFile( self.problem_configuration.input_file_path) else: # TODO: generate the input file by default ? raise FastMissingFile( "Please generate input file before using the optimization viewer" ) if pth.isfile(self.problem_configuration.output_file_path): output_variables = DataFile( self.problem_configuration.output_file_path) else: problem = self.problem_configuration.get_problem() problem.setup() output_variables = VariableList.from_problem(problem) optimization_variables = VariableList() opt_def = problem_configuration.get_optimization_definition() # Design Variables if KEY_DESIGN_VARIABLES in opt_def: for name, design_var in opt_def[KEY_DESIGN_VARIABLES].items(): metadata = { "type": "design_var", "initial_value": input_variables[name].value, "lower": design_var.get("lower"), "value": output_variables[name].value, "upper": design_var.get("upper"), "units": input_variables[name].units, "desc": input_variables[name].description, } optimization_variables[name] = metadata # Constraints if KEY_CONSTRAINTS in opt_def: for name, constr in opt_def[KEY_CONSTRAINTS].items(): metadata = { "type": "constraint", "initial_value": None, "lower": constr.get("lower"), "value": output_variables[name].value, "upper": constr.get("upper"), "units": output_variables[name].units, "desc": output_variables[name].description, } optimization_variables[name] = metadata # Objectives if KEY_OBJECTIVE in opt_def: for name in opt_def[KEY_OBJECTIVE]: metadata = { "type": "objective", "initial_value": None, "lower": None, "value": output_variables[name].value, "upper": None, "units": output_variables[name].units, "desc": output_variables[name].description, } optimization_variables[name] = metadata self.load_variables(optimization_variables)
def test_generate_inputs(cleanup): input_file_path = api.generate_inputs(CONFIGURATION_FILE_PATH, overwrite=False) assert input_file_path == pth.join(RESULTS_FOLDER_PATH, "inputs.xml") assert pth.exists(input_file_path) data = DataFile(input_file_path) assert len(data) == 2 assert "x" in data.names() and "z" in data.names() # Let's add another variable to ensure overwrite is correctly done (issue #328) data["dummy_var"] = {"value": 0.0} data.save() # Generating again without forcing overwrite will make it fail with pytest.raises(FastPathExistsError): api.generate_inputs(CONFIGURATION_FILE_PATH, overwrite=False) input_file_path = api.generate_inputs(CONFIGURATION_FILE_PATH, pth.join(DATA_FOLDER_PATH, "inputs.xml"), overwrite=True) assert input_file_path == pth.join(RESULTS_FOLDER_PATH, "inputs.xml") assert pth.exists(input_file_path) data = DataFile(input_file_path) assert len(data) == 2 assert "x" in data.names() and "z" in data.names() # We test without source file to see if variable description in "desc" kwargs # is captured (issue #319) input_file_path = api.generate_inputs(CONFIGURATION_FILE_PATH, overwrite=True) assert input_file_path == pth.join(RESULTS_FOLDER_PATH, "inputs.xml") assert pth.exists(input_file_path) data = DataFile(input_file_path) assert len(data) == 2 assert "x" in data.names() and "z" in data.names()
def run_non_regression_test( conf_file, legacy_result_file, result_dir, use_xfoil=False, global_tolerance=1e-2, vars_to_check=None, specific_tolerance=5.0e-3, check_weight_perfo_loop=True, ): """ Convenience function for non regression tests :param conf_file: FAST-OAD configuration file :param legacy_result_file: reference data for inputs and outputs :param result_dir: relative name, folder will be in RESULTS_FOLDER_PATH :param use_xfoil: if True, XFOIL computation will be activated :param vars_to_check: variables that will be concerned by specific_tolerance :param specific_tolerance: test will fail if absolute relative error between computed and reference values is beyond this value for variables in vars_to_check :param global_tolerance: test will fail if absolute relative error between computed and reference values is beyond this value for ANY variable :param check_weight_perfo_loop: if True, consistency of weights will be checked """ results_folder_path = pth.join(RESULTS_FOLDER_PATH, result_dir) configuration_file_path = pth.join(results_folder_path, conf_file) # Copy of configuration file and generation of problem instance ------------------ api.generate_configuration_file(configuration_file_path) # just ensure folders are created... shutil.copy(pth.join(DATA_FOLDER_PATH, conf_file), configuration_file_path) configurator = FASTOADProblemConfigurator(configuration_file_path) configurator._set_configuration_modifier(XFOILConfigurator(use_xfoil)) # Generation of inputs ---------------------------------------- ref_inputs = pth.join(DATA_FOLDER_PATH, legacy_result_file) configurator.write_needed_inputs(ref_inputs) # Get problem with inputs ------------------------------------- problem = configurator.get_problem(read_inputs=True) problem.setup() # Run model --------------------------------------------------------------- problem.run_model() problem.write_outputs() om.view_connections( problem, outfile=pth.join(results_folder_path, "connections.html"), show_browser=False ) if check_weight_perfo_loop: _check_weight_performance_loop(problem) ref_data = DataFile(pth.join(DATA_FOLDER_PATH, legacy_result_file)) row_list = [] for ref_var in ref_data: try: value = problem.get_val(ref_var.name, units=ref_var.units)[0] except KeyError: continue row_list.append( { "name": ref_var.name, "units": ref_var.units, "ref_value": ref_var.value[0], "value": value, } ) df = pd.DataFrame(row_list) df["rel_delta"] = (df.value - df.ref_value) / df.ref_value df["rel_delta"][(df.ref_value == 0) & (abs(df.value) <= 1e-10)] = 0.0 df["abs_rel_delta"] = np.abs(df.rel_delta) pd.set_option("display.max_rows", None) pd.set_option("display.max_columns", None) pd.set_option("display.width", 1000) pd.set_option("display.max_colwidth", 120) print(df.sort_values(by=["abs_rel_delta"])) if vars_to_check is not None: for name in vars_to_check: assert_allclose(df.ref_value, df.value, rtol=global_tolerance) row = df.loc[df.name == name] assert_allclose(row.ref_value, row.value, rtol=specific_tolerance) else: assert np.all(df.abs_rel_delta < specific_tolerance)