Skip to content

Commit

Permalink
Merge pull request #184 from fast-aircraft-design/input_output_selector
Browse files Browse the repository at this point in the history
Input output selector
  • Loading branch information
christophe-david committed Jun 18, 2020
2 parents 92cd94f + cbde52f commit 493a5cd
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 155 deletions.
10 changes: 5 additions & 5 deletions src/fastoad/cmd/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ def list_variables(
conf = FASTOADProblemConfigurator(configuration_file_path)
problem = conf.get_problem()

input_variables = VariableList.from_unconnected_inputs(problem, with_optional_inputs=True)
output_variables = VariableList.from_problem(problem, use_inputs=False)

input_variables.sort(key=lambda var: var.name)
output_variables.sort(key=lambda var: var.name)
# Extracting inputs and outputs
variables = VariableList.from_problem(problem, promoted_only=False)
variables.sort(key=lambda var: var.name)
input_variables = VariableList([var for var in variables if var.is_input])
output_variables = VariableList([var for var in variables if not var.is_input])

if isinstance(out, str):
if not overwrite and pth.exists(out):
Expand Down
3 changes: 3 additions & 0 deletions src/fastoad/io/xml/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
DEFAULT_UNIT_ATTRIBUTE = "units"
"""label of tag attribute for providing units as a string"""

DEFAULT_IO_ATTRIBUTE = "is_input"
"""label of tag attribute for providing io variable type as boolean"""

ROOT_TAG = "FASTOAD_model"
"""name of root element for XML files"""
13 changes: 10 additions & 3 deletions src/fastoad/io/xml/variable_io_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from lxml.etree import _Element # pylint: disable=protected-access # Useful for type hinting
from openmdao.vectors.vector import Vector

from .constants import DEFAULT_UNIT_ATTRIBUTE, ROOT_TAG
from .constants import DEFAULT_UNIT_ATTRIBUTE, DEFAULT_IO_ATTRIBUTE, ROOT_TAG

_LOGGER = logging.getLogger(__name__) # Logger for this module

Expand Down Expand Up @@ -68,6 +68,7 @@ class VariableXmlBaseFormatter(IVariableIOFormatter):
def __init__(self, translator: VarXpathTranslator):
self._translator = translator
self.xml_unit_attribute = DEFAULT_UNIT_ATTRIBUTE
self.xml_io_attribute = DEFAULT_IO_ATTRIBUTE
self.unit_translation = {
"²": "**2",
"³": "**3",
Expand Down Expand Up @@ -101,6 +102,7 @@ def read_variables(self, data_source: Union[str, IO]) -> VariableList:
root = tree.getroot()
for elem in root.iter():
units = elem.attrib.get(self.xml_unit_attribute, None)
is_input = elem.attrib.get(self.xml_io_attribute, None)
if units:
# Ensures compatibility with OpenMDAO units
for legacy_chars, om_chars in self.unit_translation.items():
Expand All @@ -126,7 +128,10 @@ def read_variables(self, data_source: Union[str, IO]) -> VariableList:

if name not in variables.names():
# Add Variable
variables[name] = {"value": value, "units": units}
if is_input is not None:
is_input = is_input == "True"

variables[name] = {"value": value, "units": units, "is_input": is_input}
else:
raise FastXmlFormatterDuplicateVariableError(
"Variable %s is defined in more than one place in file %s"
Expand All @@ -148,9 +153,11 @@ def write_variables(self, data_source: Union[str, IO], variables: VariableList):
continue
element = self._create_xpath(root, xpath)

# Set value and units
# Set value, units and io
if variable.units:
element.attrib[self.xml_unit_attribute] = variable.units
if variable.is_input is not None:
element.attrib[self.xml_io_attribute] = str(variable.is_input)

# Filling value for already created element
element.text = str(variable.value)
Expand Down
2 changes: 2 additions & 0 deletions src/fastoad/openmdao/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def write_needed_inputs(
if source_file_path:
ref_vars = VariableIO(source_file_path, source_formatter).read()
variables.update(ref_vars)
for var in variables:
var.is_input = True
writer = VariableIO(self.input_file_path)
writer.write(variables)

Expand Down
173 changes: 75 additions & 98 deletions src/fastoad/openmdao/tests/test_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ def test_df_from_to_variables():

def test_get_variables_from_system():
def _test_and_check(
component, use_inputs: bool, use_outputs: bool, expected_vars: List[Variable],
component, expected_vars: List[Variable],
):
vars = VariableList.from_system(component, use_inputs=use_inputs, use_outputs=use_outputs)
vars = VariableList.from_system(component)

# A comparison of sets will not work due to values not being strictly equal
# (not enough decimals in expected values), so we do not use this:
Expand All @@ -161,47 +161,34 @@ def _test_and_check(

# test Component -----------------------------------------------------------
comp = Disc1()
expected_input_vars = [
expected_vars = [
Variable(name="x", value=np.array([np.nan]), units=None),
Variable(name="y2", value=np.array([1.0]), units=None),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2"),
Variable(name="y1", value=np.array([1.0]), units=None),
]
expected_output_vars = [Variable(name="y1", value=np.array([1.0]), units=None)]
_test_and_check(comp, True, False, expected_input_vars)
_test_and_check(comp, False, True, expected_output_vars)
_test_and_check(comp, True, True, expected_input_vars + expected_output_vars)
_test_and_check(comp, expected_vars)

# test Group ---------------------------------------------------------------
group = om.Group()
group.add_subsystem("disc1", Disc1(), promotes=["*"])
group.add_subsystem("disc2", Disc2(), promotes=["*"])

expected_input_vars = [
expected_vars = [
Variable(name="x", value=np.array([np.nan]), units=None),
Variable(name="y1", value=np.array([1.0]), units=None),
Variable(name="y2", value=np.array([1.0]), units=None),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2"),
]
expected_output_vars = [
Variable(name="y1", value=np.array([1.0]), units=None),
Variable(name="y2", value=np.array([1.0]), units=None),
]
_test_and_check(group, True, False, expected_input_vars)
_test_and_check(group, False, True, expected_output_vars)
_test_and_check(group, True, True, expected_input_vars)

_test_and_check(group, expected_vars)


def test_get_variables_from_problem():
def _test_and_check(
problem: om.Problem,
use_initial_values: bool,
use_inputs: bool,
use_outputs: bool,
expected_vars: List[Variable],
problem: om.Problem, use_initial_values: bool, expected_vars: List[Variable],
):
vars = VariableList.from_problem(
problem, use_initial_values, use_inputs=use_inputs, use_outputs=use_outputs
)
vars = VariableList.from_problem(problem, use_initial_values)

# A comparison of sets will not work due to values not being strictly equal
# (not enough decimals in expected values), so we do not use this:
Expand All @@ -214,39 +201,32 @@ def _test_and_check(
assert var.name == expected_var.name
assert_allclose(var.value, expected_var.value)
assert var.units == expected_var.units
assert var.is_input == expected_var.is_input

# Check with an ExplicitComponent ------------------------------------------
problem = om.Problem(Disc1())
expected_input_vars = [
Variable(name="x", value=np.array([np.nan]), units=None),
Variable(name="y2", value=np.array([1.0]), units=None),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2"),
expected_vars = [
Variable(name="x", value=np.array([np.nan]), units=None, is_input=True),
Variable(name="y2", value=np.array([1.0]), units=None, is_input=True),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2", is_input=True),
Variable(name="y1", value=np.array([1.0]), units=None, is_input=False),
]
expected_output_vars = [Variable(name="y1", value=np.array([1.0]), units=None)]
_test_and_check(problem, False, True, False, expected_input_vars)
_test_and_check(problem, False, False, True, expected_output_vars)
_test_and_check(problem, False, True, True, expected_input_vars + expected_output_vars)
_test_and_check(problem, False, expected_vars)

# Check with a Group -------------------------------------------------------
group = om.Group()
group.add_subsystem("disc1", Disc1(), promotes=["*"])
group.add_subsystem("disc2", Disc2(), promotes=["*"])
problem = om.Problem(group)

# All variables are inputs somewhere
expected_input_vars = [
Variable(name="x", value=np.array([np.nan]), units=None),
Variable(name="y1", value=np.array([1.0]), units=None),
Variable(name="y2", value=np.array([1.0]), units=None),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2"),
]
expected_output_vars = [
Variable(name="y1", value=np.array([1.0]), units=None),
Variable(name="y2", value=np.array([1.0]), units=None),
expected_vars = [
Variable(name="x", value=np.array([np.nan]), units=None, is_input=True),
Variable(name="y1", value=np.array([1.0]), units=None, is_input=False),
Variable(name="y2", value=np.array([1.0]), units=None, is_input=False),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2", is_input=True),
]
_test_and_check(problem, False, True, False, expected_input_vars)
_test_and_check(problem, False, False, True, expected_output_vars)
_test_and_check(problem, False, True, True, expected_input_vars)

_test_and_check(problem, False, expected_vars)

# Check with the whole Sellar problem --------------------------------------
# WITH promotions, WITHOUT computation
Expand All @@ -260,40 +240,31 @@ def _test_and_check(
group.nonlinear_solver = om.NonlinearBlockGS()
problem = om.Problem(group)

expected_input_vars = [
Variable(name="x", value=np.array([np.nan]), units=None),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2"),
Variable(name="y1", value=np.array([1.0]), units=None),
Variable(name="y2", value=np.array([1.0]), units=None),
]
expected_output_vars = [
Variable(name="x", value=np.array([1.0]), units="Pa"),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2"),
Variable(name="y1", value=np.array([1.0]), units=None),
Variable(name="y2", value=np.array([1.0]), units=None),
Variable(name="g1", value=np.array([1.0]), units=None),
Variable(name="g2", value=np.array([1.0]), units=None),
Variable(name="f", value=np.array([1.0]), units=None),
expected_vars = [
Variable(name="x", value=np.array([1.0]), units="Pa", is_input=True),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2", is_input=True),
Variable(name="y1", value=np.array([1.0]), units=None, is_input=False),
Variable(name="y2", value=np.array([1.0]), units=None, is_input=False),
Variable(name="g1", value=np.array([1.0]), units=None, is_input=False),
Variable(name="g2", value=np.array([1.0]), units=None, is_input=False),
Variable(name="f", value=np.array([1.0]), units=None, is_input=False),
]
_test_and_check(problem, True, True, False, expected_input_vars)
_test_and_check(problem, True, False, True, expected_output_vars)
_test_and_check(problem, True, expected_vars)

# Check with the whole Sellar problem --------------------------------------
# WITH promotions, WITH computation
expected_computed_output_vars = [
Variable(name="x", value=np.array([1.0]), units="Pa"),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2"),
Variable(name="y1", value=np.array([25.58830237]), units=None),
Variable(name="y2", value=np.array([12.05848815]), units=None),
Variable(name="f", value=np.array([28.58830817]), units=None),
Variable(name="g1", value=np.array([-22.42830237]), units=None),
Variable(name="g2", value=np.array([-11.94151185]), units=None),
expected_vars = [
Variable(name="x", value=np.array([1.0]), units="Pa", is_input=True),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2", is_input=True),
Variable(name="y1", value=np.array([25.58830237]), units=None, is_input=False),
Variable(name="y2", value=np.array([12.05848815]), units=None, is_input=False),
Variable(name="f", value=np.array([28.58830817]), units=None, is_input=False),
Variable(name="g1", value=np.array([-22.42830237]), units=None, is_input=False),
Variable(name="g2", value=np.array([-11.94151185]), units=None, is_input=False),
]
problem.setup()
problem.run_model()
_test_and_check(problem, True, True, False, expected_input_vars)
_test_and_check(problem, True, False, True, expected_output_vars)
_test_and_check(problem, False, False, True, expected_computed_output_vars)
_test_and_check(problem, False, expected_vars)

# Check with the whole Sellar problem --------------------------------------
# WITHOUT promotions, WITHOUT computation
Expand All @@ -318,24 +289,24 @@ def _test_and_check(
problem = om.Problem(group)

expected_vars = [
Variable(name="indeps.x", value=np.array([1.0]), units="Pa"),
Variable(name="indeps.z", value=np.array([5.0, 2.0]), units="m**2"),
Variable(name="disc1.x", value=np.array([np.nan]), units=None),
Variable(name="disc1.z", value=np.array([5.0, 2.0]), units="m**2"),
Variable(name="disc1.y1", value=np.array([1.0]), units=None),
Variable(name="disc1.y2", value=np.array([1.0]), units=None),
Variable(name="disc2.z", value=np.array([5.0, 2.0]), units="m**2"),
Variable(name="disc2.y1", value=np.array([1.0]), units=None),
Variable(name="disc2.y2", value=np.array([1.0]), units=None),
Variable(name="functions.x", value=np.array([2]), units=None),
Variable(name="functions.z", value=np.array([np.nan, np.nan]), units="m**2"),
Variable(name="functions.y1", value=np.array([1.0]), units=None),
Variable(name="functions.y2", value=np.array([1.0]), units=None),
Variable(name="functions.g1", value=np.array([1.0]), units=None),
Variable(name="functions.g2", value=np.array([1.0]), units=None),
Variable(name="functions.f", value=np.array([1.0]), units=None),
Variable(name="indeps.x", value=np.array([1.0]), units="Pa", is_input=True),
Variable(name="indeps.z", value=np.array([5.0, 2.0]), units="m**2", is_input=True),
Variable(name="disc1.x", value=np.array([np.nan]), units=None, is_input=True),
Variable(name="disc1.z", value=np.array([5.0, 2.0]), units="m**2", is_input=True),
Variable(name="disc1.y1", value=np.array([1.0]), units=None, is_input=False),
Variable(name="disc1.y2", value=np.array([1.0]), units=None, is_input=False),
Variable(name="disc2.z", value=np.array([5.0, 2.0]), units="m**2", is_input=False),
Variable(name="disc2.y1", value=np.array([1.0]), units=None, is_input=False),
Variable(name="disc2.y2", value=np.array([1.0]), units=None, is_input=False),
Variable(name="functions.x", value=np.array([2]), units=None, is_input=True),
Variable(name="functions.z", value=np.array([np.nan, np.nan]), units="m**2", is_input=True),
Variable(name="functions.y1", value=np.array([1.0]), units=None, is_input=False),
Variable(name="functions.y2", value=np.array([1.0]), units=None, is_input=False),
Variable(name="functions.g1", value=np.array([1.0]), units=None, is_input=False),
Variable(name="functions.g2", value=np.array([1.0]), units=None, is_input=False),
Variable(name="functions.f", value=np.array([1.0]), units=None, is_input=False),
]
_test_and_check(problem, True, True, True, expected_vars)
_test_and_check(problem, True, expected_vars)
expected_computed_vars = [ # Here links are done, even without computations
Variable(name="indeps.x", value=np.array([1.0]), units="Pa"),
Variable(name="indeps.z", value=np.array([5.0, 2.0]), units="m**2"),
Expand All @@ -354,7 +325,7 @@ def _test_and_check(
Variable(name="functions.g2", value=np.array([1.0]), units=None),
Variable(name="functions.f", value=np.array([1.0]), units=None),
]
_test_and_check(problem, False, True, True, expected_computed_vars)
_test_and_check(problem, False, expected_computed_vars)

# Check with the whole Sellar problem --------------------------------------
# WITHOUT promotions, WITH computation
Expand All @@ -378,8 +349,8 @@ def _test_and_check(
]
problem.setup()
problem.run_model()
_test_and_check(problem, True, True, True, expected_vars)
_test_and_check(problem, False, True, True, expected_computed_vars)
_test_and_check(problem, True, expected_vars)
_test_and_check(problem, False, expected_computed_vars)


def test_variables_from_unconnected_inputs():
Expand All @@ -397,10 +368,12 @@ def _test_and_check(

# Check with an ExplicitComponent ------------------------------------------
problem = om.Problem(Disc1())
expected_mandatory_vars = [Variable(name="x", value=np.array([np.nan]), units=None)]
expected_mandatory_vars = [
Variable(name="x", value=np.array([np.nan]), units=None, is_input=True)
]
expected_optional_vars = [
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2"),
Variable(name="y2", value=np.array([1.0]), units=None),
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2", is_input=True),
Variable(name="y2", value=np.array([1.0]), units=None, is_input=True),
]
_test_and_check(problem, expected_mandatory_vars, expected_optional_vars)

Expand All @@ -410,8 +383,12 @@ def _test_and_check(
group.add_subsystem("disc2", Disc2(), promotes=["*"])
problem = om.Problem(group)

expected_mandatory_vars = [Variable(name="x", value=np.array([np.nan]), units=None)]
expected_optional_vars = [Variable(name="z", value=np.array([5.0, 2.0]), units="m**2")]
expected_mandatory_vars = [
Variable(name="x", value=np.array([np.nan]), units=None, is_input=True)
]
expected_optional_vars = [
Variable(name="z", value=np.array([5.0, 2.0]), units="m**2", is_input=True)
]
_test_and_check(problem, expected_mandatory_vars, expected_optional_vars)

# Check with the whole Sellar problem --------------------------------------
Expand All @@ -423,8 +400,8 @@ def _test_and_check(
problem = om.Problem(group)

expected_mandatory_vars = [
Variable(name="x", value=np.array([np.nan]), units=None),
Variable(name="z", value=np.array([np.nan, np.nan]), units="m**2"),
Variable(name="x", value=np.array([np.nan]), units=None, is_input=True),
Variable(name="z", value=np.array([np.nan, np.nan]), units="m**2", is_input=True),
]
expected_optional_vars = []
_test_and_check(problem, expected_mandatory_vars, expected_optional_vars)

0 comments on commit 493a5cd

Please sign in to comment.