Skip to content

Commit

Permalink
Merge pull request #408 from fast-aircraft-design/issue-406_indep_var…
Browse files Browse the repository at this point in the history
…_comp

Issue 406 - Adding internal IndepVarComp to inputs
  • Loading branch information
ScottDelbecq committed Feb 4, 2022
2 parents 24f6fbb + 2113642 commit 759555b
Show file tree
Hide file tree
Showing 16 changed files with 328 additions and 86 deletions.
1 change: 1 addition & 0 deletions src/fastoad/cmd/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ def _run_problem(
)

problem.setup()

start_time = time()
if mode == "run_model":
problem.run_model()
Expand Down
62 changes: 16 additions & 46 deletions src/fastoad/io/configuration/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
import os.path as pth
from abc import ABC, abstractmethod
from importlib.resources import open_text
from typing import Dict, Tuple
from typing import Dict

import numpy as np
import openmdao.api as om
import tomlkit
from jsonschema import validate
Expand All @@ -30,14 +29,12 @@
from fastoad._utils.files import make_parent_dir
from fastoad.io import DataFile, IVariableIOFormatter
from fastoad.module_management.service_registry import RegisterOpenMDAOSystem, RegisterSubmodel
from fastoad.openmdao._utils import get_unconnected_input_names
from fastoad.openmdao.problem import FASTOADProblem
from fastoad.openmdao.variables import VariableList
from . import resources
from .exceptions import (
FASTConfigurationBadOpenMDAOInstructionError,
FASTConfigurationBaseKeyBuildingError,
FASTConfigurationNanInInputFile,
)

_LOGGER = logging.getLogger(__name__) # Logger for this module
Expand Down Expand Up @@ -115,17 +112,12 @@ def get_problem(self, read_inputs: bool = False, auto_scaling: bool = False) ->
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 = FASTOADProblem(self._build_model())
problem.input_file_path = self.input_file_path
problem.output_file_path = self.output_file_path
problem.additional_variables = unused_variables

if read_inputs:
problem.read_inputs()

driver = self._serializer.data.get(KEY_DRIVER, "")
if driver:
Expand Down Expand Up @@ -220,8 +212,17 @@ def write_needed_inputs(
problem = self.get_problem(read_inputs=False)
problem.setup()
variables = DataFile(self.input_file_path, load_data=False)

unconnected_inputs = VariableList.from_problem(
problem,
use_initial_values=True,
get_promoted_names=True,
promoted_only=True,
io_status="inputs",
)

variables.update(
VariableList.from_unconnected_inputs(problem, with_optional_inputs=True),
unconnected_inputs,
add_variables=True,
)
if source_file_path:
Expand Down Expand Up @@ -265,7 +266,7 @@ def set_optimization_definition(self, optimization_definition: Dict):
subpart = {"optimization": subpart}
self._serializer.data.update(subpart)

def _build_model(self, input_ivc: om.IndepVarComp = None) -> om.Group:
def _build_model(self) -> om.Group:
"""
Builds the model as defined in the configuration file.
Expand All @@ -278,9 +279,6 @@ def _build_model(self, input_ivc: om.IndepVarComp = None) -> om.Group:
model = FASTOADModel()
model.active_submodels = self._serializer.data.get(KEY_SUBMODELS, {})

if input_ivc:
model.add_subsystem("fastoad_inputs", input_ivc, promotes=["*"])

model_definition = self._serializer.data.get(KEY_MODEL)

try:
Expand Down Expand Up @@ -414,34 +412,6 @@ def _add_design_vars(self, model, auto_scaling):
design_var_table["ref"] = design_var_table["upper"]
model.add_design_var(**design_var_table)

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 _set_configuration_modifier(self, modifier: "_IConfigurationModifier"):
self._configuration_modifier = modifier

Expand Down
16 changes: 0 additions & 16 deletions src/fastoad/io/configuration/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from typing import List

from fastoad.exceptions import FastError

Expand Down Expand Up @@ -76,18 +75,3 @@ def __init__(self, original_exception: Exception, key: str, value=None):

class FASTConfigurationBadOpenMDAOInstructionError(FASTConfigurationBaseKeyBuildingError):
"""Class for managing errors that result from trying to set an attribute by eval."""


class FASTConfigurationNanInInputFile(FastError):
"""Raised if NaN values are read in input data file."""

def __init__(self, input_file_path: str, nan_variable_names: List[str]):
self.input_file_path = input_file_path
self.nan_variable_names = nan_variable_names

msg = "NaN values found in input file (%s). Please check following variables: %s" % (
input_file_path,
nan_variable_names,
)

super().__init__(self, msg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Sellar indeps"""
# This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design
# Copyright (C) 2021 ONERA & ISAE-SUPAERO
# FAST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import openmdao.api as om

from fastoad.module_management.service_registry import RegisterOpenMDAOSystem


@RegisterOpenMDAOSystem("configuration_test.sellar.indeps")
class Indeps(om.Group):
def setup(self):
# System variables
comp = om.IndepVarComp()
comp.add_output("system:x", val=2)

self.add_subsystem("indeps", comp, promotes=["*"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
title: Sellar

module_folders:
- conf_sellar_example
- d:/path/does/not/exist # will only trigger a warning

input_file: ../results/problem_definition_with_indep/inputs.xml
output_file: ../results/problem_definition_with_indep/outputs.xml

driver: om.ScipyOptimizeDriver(optimizer='SLSQP')
model:
indep:
id: configuration_test.sellar.indeps
connections:
- source: system:x
target: x
- source: y1
target: yy1
- source: y2
target: yy2
cycle:
nonlinear_solver: om.NonlinearBlockGS(iprint=1)
linear_solver: om.ScipyKrylov()
disc1:
id: configuration_test.sellar.disc1
disc2:
id: configuration_test.sellar.disc2
functions:
id: configuration_test.sellar.functions


submodels:
service.function.f: function.f.default
service.function.g1: function.g1.default
service.function.g2: function.g2.default

optimization:
design_variables:
- name: x
lower: 0
upper: 10
- name: z
lower: 0
upper: 10
constraints:
- name: g1
lower: -100
upper: 0
- name: g2
upper: 0
objective:
- name: f
scaler: 1e-1

unknown_section:
foo: bar
17 changes: 1 addition & 16 deletions src/fastoad/io/configuration/tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from fastoad.module_management.exceptions import FastBundleLoaderUnknownFactoryNameError
from ..exceptions import (
FASTConfigurationBadOpenMDAOInstructionError,
FASTConfigurationNanInInputFile,
)

DATA_FOLDER_PATH = pth.join(pth.dirname(__file__), "data")
Expand Down Expand Up @@ -136,7 +135,6 @@ def test_problem_definition_with_xml_ref(cleanup):
# 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()

Expand All @@ -158,6 +156,7 @@ def test_problem_definition_with_xml_ref(cleanup):
alt_problem["g1"] # submodel for g1 computation has been deactivated.


# FIXME: this test should be reworked and moved to test_problem
def test_problem_definition_with_custom_xml(cleanup):
"""Tests what happens when writing inputs using existing XML with some unwanted var"""
conf = FASTOADProblemConfigurator(pth.join(DATA_FOLDER_PATH, "valid_sellar.toml"))
Expand All @@ -178,20 +177,6 @@ def test_problem_definition_with_custom_xml(cleanup):
problem.write_outputs()


def test_problem_definition_with_nan_inputs(cleanup):
"""Tests what happens when writing inputs using existing XML with some unwanted var"""
conf = FASTOADProblemConfigurator(pth.join(DATA_FOLDER_PATH, "valid_sellar.toml"))

input_data_path = pth.join(DATA_FOLDER_PATH, "nan_inputs.xml")
os.makedirs(RESULTS_FOLDER_PATH, exist_ok=True)
shutil.copy(input_data_path, conf.input_file_path)

with pytest.raises(FASTConfigurationNanInInputFile) as exc:
problem = conf.get_problem(read_inputs=True, auto_scaling=True)
assert exc.input_file_path == input_data_path
assert exc.nan_variable_names == ["x"]


def test_problem_definition_with_xml_ref_run_optim(cleanup):
"""
Tests what happens when writing inputs using data from existing XML file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@
<taxi_out>
<duration units="s" is_input="True">
540.0<!--duration of taxi-out phase in sizing mission--></duration>
<thrust_rate units="s" is_input="True">
<thrust_rate is_input="True">
0.25<!--thrust rate (between 0.0 and 1.0) during taxi-out phase in sizing mission--></thrust_rate>
<fuel units="kg" is_input="True">
276.0<!--mass of consumed fuel during taxi-out phase in sizing mission--></fuel>
Expand Down Expand Up @@ -469,7 +469,7 @@
<taxi_out>
<duration units="s" is_input="True">
540.0<!--duration of taxi-out phase in sizing mission--></duration>
<thrust_rate units="s" is_input="True">
<thrust_rate is_input="True">
0.25<!--thrust rate (between 0.0 and 1.0) during taxi-out phase in sizing mission--></thrust_rate>
<fuel units="kg" is_input="True">
276.0<!--mass of consumed fuel during taxi-out phase in sizing mission--></fuel>
Expand Down Expand Up @@ -517,7 +517,7 @@
<taxi_out>
<duration units="s" is_input="True">
540.0<!--duration of taxi-out phase in sizing mission--></duration>
<thrust_rate units="s" is_input="True">
<thrust_rate is_input="True">
0.25<!--thrust rate (between 0.0 and 1.0) during taxi-out phase in sizing mission--></thrust_rate>
<fuel units="kg" is_input="True">
276.0<!--mass of consumed fuel during taxi-out phase in sizing mission--></fuel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@
<taxi_out>
<duration units="s" is_input="True">540.0</duration>
<fuel units="kg" is_input="False">279.5481830622739<!--burned fuel during taxi-out of mission "MTOW_mission"--></fuel>
<thrust_rate units="s" is_input="True">0.25</thrust_rate>
<thrust_rate is_input="True">0.25</thrust_rate>
</taxi_out>
</MTOW_mission>
<SPP_design>
Expand Down Expand Up @@ -367,7 +367,7 @@
</taxi_in>
<taxi_out>
<duration units="s" is_input="True">540.0</duration>
<thrust_rate units="s" is_input="True">0.25</thrust_rate>
<thrust_rate is_input="True">0.25</thrust_rate>
</taxi_out>
</SPP_design>
<SPP_study>
Expand Down Expand Up @@ -411,7 +411,7 @@
</taxi_in>
<taxi_out>
<duration units="s" is_input="True">540.0</duration>
<thrust_rate units="s" is_input="True">0.25</thrust_rate>
<thrust_rate is_input="True">0.25</thrust_rate>
</taxi_out>
</SPP_study>
<sizing>
Expand Down
5 changes: 5 additions & 0 deletions src/fastoad/openmdao/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from typing import List, Tuple
from deprecated import deprecated

import numpy as np
import openmdao.api as om


@deprecated(
version="1.3.0",
reason="Will be removed in version 2.0. Please use VariableList.from_problem() instead",
)
def get_unconnected_input_names(
problem: om.Problem, promoted_names=False
) -> Tuple[List[str], List[str]]:
Expand Down
37 changes: 37 additions & 0 deletions src/fastoad/openmdao/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Module for custom Exception classes linked to OpenMDAO
"""


# This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design
# Copyright (C) 2022 ONERA & ISAE-SUPAERO
# FAST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.


from typing import List

from fastoad.exceptions import FastError


class FASTOpenMDAONanInInputFile(FastError):
"""Raised if NaN values are read in input data file."""

def __init__(self, input_file_path: str, nan_variable_names: List[str]):
self.input_file_path = input_file_path
self.nan_variable_names = nan_variable_names

msg = "NaN values found in input file (%s). Please check following variables: %s" % (
input_file_path,
nan_variable_names,
)

super().__init__(self, msg)

0 comments on commit 759555b

Please sign in to comment.