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
def get_problem(self, read_inputs: bool = False, auto_scaling: bool = False) -> FASTOADProblem: """ Builds the OpenMDAO problem from current configuration. :param read_inputs: if True, the created problem will already be fed with variables from the input file :param auto_scaling: if True, automatic scaling is performed for design variables and constraints :return: the problem instance """ if not self._conf_dict: raise RuntimeError("read configuration file first") problem = FASTOADProblem(self._build_model()) problem.input_file_path = self.input_file_path problem.output_file_path = self.output_file_path driver = self._conf_dict.get(KEY_DRIVER, "") if driver: problem.driver = _om_eval(driver) if self.get_optimization_definition(): self._add_constraints(problem.model, auto_scaling) self._add_objectives(problem.model) if read_inputs: problem.read_inputs() self._add_design_vars(problem.model, auto_scaling) return problem
def test_problem_read_inputs_with_nan_inputs(cleanup): """Tests that when reading inputs using existing XML with some nan values an exception is raised""" problem = FASTOADProblem() problem.model.add_subsystem("sellar", Sellar(), promotes=["*"]) input_data_path = pth.join(DATA_FOLDER_PATH, "nan_inputs.xml") problem.input_file_path = pth.join(DATA_FOLDER_PATH, "nan_inputs.xml") with pytest.raises(FASTOpenMDAONanInInputFile) as exc_info: problem.read_inputs() assert exc_info.value.input_file_path == input_data_path assert exc_info.value.nan_variable_names == ["x"] problem.setup() with pytest.raises(FASTOpenMDAONanInInputFile) as exc_info: problem.read_inputs() assert exc_info.value.input_file_path == input_data_path assert exc_info.value.nan_variable_names == ["x"]
def get_problem(self, read_inputs: bool = False, auto_scaling: bool = False) -> FASTOADProblem: """ Builds the OpenMDAO problem from current configuration. :param read_inputs: if True, the created problem will already be fed with variables from the input file :param auto_scaling: if True, automatic scaling is performed for design variables and constraints :return: the problem instance """ if self._serializer.data is None: raise RuntimeError("read configuration file first") if read_inputs: problem_with_no_inputs = self.get_problem( auto_scaling=auto_scaling) problem_with_no_inputs.setup() input_ivc, unused_variables = self._get_problem_inputs( problem_with_no_inputs) else: input_ivc = unused_variables = None problem = FASTOADProblem(self._build_model(input_ivc)) problem.input_file_path = self.input_file_path problem.output_file_path = self.output_file_path problem.additional_variables = unused_variables driver = self._serializer.data.get(KEY_DRIVER, "") if driver: problem.driver = _om_eval(driver) if self.get_optimization_definition(): self._add_constraints(problem.model, auto_scaling) self._add_objectives(problem.model) if read_inputs: self._add_design_vars(problem.model, auto_scaling) if self._configuration_modifier: self._configuration_modifier.modify(problem) return problem
def test_write_outputs(): problem = FASTOADProblem() problem.model.add_subsystem("sellar", Sellar(), promotes=["*"]) problem.output_file_path = pth.join(RESULTS_FOLDER_PATH, "output.xml") problem.setup() problem.write_outputs() variables = VariableIO(problem.output_file_path).read() assert variables == [ Variable(name="f", value=1.0), Variable(name="g1", value=1.0), Variable(name="g2", value=1.0), Variable(name="x", value=2), Variable(name="y2", value=1.0), Variable(name="z", value=[5.0, 2.0], units="m**2"), ] problem.run_model() problem.write_outputs() variables = VariableIO(problem.output_file_path).read() assert variables == [ Variable(name="f", value=32.569100892077444), Variable(name="g1", value=-23.409095627564167), Variable(name="g2", value=-11.845478137832359), Variable(name="x", value=2), Variable(name="y2", value=12.154521862167641), Variable(name="z", value=[5.0, 2.0], units="m**2"), ]
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
def test_problem_read_inputs_before_setup(cleanup): """Tests what happens when reading inputs using existing XML with correct var""" problem = FASTOADProblem() problem.model.add_subsystem("sellar", Sellar(), promotes=["*"]) problem.input_file_path = pth.join(DATA_FOLDER_PATH, "ref_inputs.xml") problem.read_inputs() problem.setup() problem.run_model() assert_allclose(problem.get_val(name="x"), 1.0) assert_allclose(problem.get_val(name="z", units="m**2"), [4.0, 3.0]) assert_allclose(problem["f"], 21.7572, atol=1.0e-4)
def test_problem_read_inputs_after_setup(cleanup): """Tests what happens when reading inputs using existing XML with correct var""" problem = FASTOADProblem() problem.model.add_subsystem("sellar", Sellar(), promotes=["*"]) problem.input_file_path = pth.join(DATA_FOLDER_PATH, "ref_inputs.xml") problem.setup() assert problem.get_val(name="x") == [2.0] with pytest.raises(RuntimeError): # Several default values are defined for "z", thus OpenMDAO raises an error that # will be solved only after run_model() has been used. _ = problem.get_val(name="z", units="m**2") problem.read_inputs() problem.run_model() assert_allclose(problem.get_val(name="x"), 1.0) assert_allclose(problem.get_val(name="z", units="m**2"), [4.0, 3.0]) assert_allclose(problem["f"], 21.7572, atol=1.0e-4)
def test_write_outputs(): problem = FASTOADProblem() problem.model.add_subsystem("sellar", Sellar(), promotes=["*"]) problem.output_file_path = pth.join(RESULTS_FOLDER_PATH, "output.xml") problem.setup() problem.write_outputs() variables = VariableIO(problem.output_file_path).read() assert variables == [ Variable(name="f", val=1.0), Variable(name="g1", val=1.0), Variable(name="g2", val=1.0), Variable(name="x", val=2.0), Variable(name="y2", val=1.0), Variable(name="z", val=[np.nan, np.nan], units="m**2"), ] problem["x"] = 2.0 problem["z"] = [ 5.0, 2.0, ] # Since version 3.17 of OpenMDAO, the np.nan input definition of z is chosen. problem.run_model() problem.write_outputs() variables = VariableIO(problem.output_file_path).read() assert variables == [ Variable(name="f", val=32.569100892077444), Variable(name="g1", val=-23.409095627564167), Variable(name="g2", val=-11.845478137832359), Variable(name="x", val=2.0), Variable(name="y2", val=12.154521862167641), Variable(name="z", val=[5.0, 2.0], units="m**2"), ]
def test_problem_with_dynamically_shaped_inputs(cleanup): class MyComp1(om.ExplicitComponent): def setup(self): self.add_input("x", shape_by_conn=True, copy_shape="y") self.add_output("y", shape_by_conn=True, copy_shape="x") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): outputs["y"] = 10 * inputs["x"] class MyComp2(om.ExplicitComponent): def setup(self): self.add_input("y", shape_by_conn=True, copy_shape="z") self.add_output("z", shape_by_conn=True, copy_shape="y") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): outputs["z"] = 0.1 * inputs["y"] # -------------------------------------------------------------------------- # With these 2 components, an OpenMDAO problem won't pass the setup due to # the non-determined shapes vanilla_problem = om.Problem() vanilla_problem.model.add_subsystem("comp1", MyComp1(), promotes=["*"]) vanilla_problem.model.add_subsystem("comp2", MyComp2(), promotes=["*"]) with pytest.raises(RuntimeError): vanilla_problem.setup() # -------------------------------------------------------------------------- # ... But fastoad problem will do the setup and provide dummy shapes # when needed fastoad_problem = FASTOADProblem() fastoad_problem.model.add_subsystem("comp1", MyComp1(), promotes=["*"]) fastoad_problem.model.add_subsystem("comp2", MyComp2(), promotes=["*"]) fastoad_problem.setup() assert (fastoad_problem["x"].shape == fastoad_problem["y"].shape == fastoad_problem["z"].shape == (2, )) # In such case, reading inputs after the setup will make run_model fail, because dummy shapes # have already been provided, and will probably not match the ones in input file. fastoad_problem.input_file_path = pth.join(DATA_FOLDER_PATH, "dynamic_shape_inputs_1.xml") fastoad_problem.read_inputs() with pytest.raises(ValueError): fastoad_problem.run_model() # -------------------------------------------------------------------------- # If input reading is done before setup, all is fine. fastoad_problem = FASTOADProblem() fastoad_problem.model.add_subsystem("comp1", MyComp1(), promotes=["*"]) fastoad_problem.model.add_subsystem("comp2", MyComp2(), promotes=["*"]) fastoad_problem.input_file_path = pth.join(DATA_FOLDER_PATH, "dynamic_shape_inputs_1.xml") fastoad_problem.read_inputs() fastoad_problem.setup() inputs = VariableList.from_problem(fastoad_problem, io_status="inputs") assert inputs.names() == ["x"] outputs = VariableList.from_problem(fastoad_problem, io_status="outputs") assert outputs.names() == ["y", "z"] variables = VariableList.from_problem(fastoad_problem) assert variables.names() == ["x", "y", "z"] fastoad_problem.run_model() assert_allclose(fastoad_problem["x"], [1.0, 2.0, 5.0]) assert_allclose(fastoad_problem["y"], [10.0, 20.0, 50.0]) assert_allclose(fastoad_problem["z"], [1.0, 2.0, 5.0]) # -------------------------------------------------------------------------- # In the case variables are shaped from "downstream", OpenMDAO works OK. class MyComp3(om.ExplicitComponent): def setup(self): self.add_input("z", shape=(3, )) self.add_output("a") def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): outputs["a"] = np.sum(inputs["z"]) fastoad_problem = FASTOADProblem() fastoad_problem.model.add_subsystem("comp1", MyComp1(), promotes=["*"]) fastoad_problem.model.add_subsystem("comp2", MyComp2(), promotes=["*"]) fastoad_problem.model.add_subsystem("comp3", MyComp3(), promotes=["*"]) inputs = VariableList.from_problem(fastoad_problem, io_status="inputs") assert inputs.names() == ["x"] outputs = VariableList.from_problem(fastoad_problem, io_status="outputs") assert outputs.names() == ["y", "z", "a"] variables = VariableList.from_problem(fastoad_problem) assert variables.names() == ["x", "y", "z", "a"] fastoad_problem.setup() fastoad_problem.run_model()
def test_problem_with_case_recorder(cleanup): """Tests what happens when using a case recorder""" # Adding a case recorder may cause a crash in case of deepcopy. problem = FASTOADProblem() sellar = Sellar() sellar.nonlinear_solver = om.NonlinearBlockGS( ) # Solver that is compatible with deepcopy sellar.add_recorder( om.SqliteRecorder(pth.join(RESULTS_FOLDER_PATH, "cases.sql"))) problem.model.add_subsystem("sellar", sellar, promotes=["*"]) problem.input_file_path = pth.join(DATA_FOLDER_PATH, "ref_inputs.xml") problem.setup() problem.read_inputs() problem.run_model() assert_allclose(problem.get_val(name="x"), 1.0) assert_allclose(problem.get_val(name="z", units="m**2"), [4.0, 3.0]) assert_allclose(problem["f"], 21.7572, atol=1.0e-4)