Skip to content

Commit

Permalink
Merge pull request #482 from naylor-b/unconn_input
Browse files Browse the repository at this point in the history
added more info to error message for access to non-unique inputs
  • Loading branch information
naylor-b committed Jan 9, 2018
2 parents f7b0619 + 9487f6c commit 5ad331b
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 93 deletions.
11 changes: 6 additions & 5 deletions openmdao/core/tests/test_connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ def test_diff_conn_input_units_w_src(self):
class TestConnectionsPromoted(unittest.TestCase):

def test_inp_inp_promoted_no_src(self):
raise unittest.SkipTest("connected inputs w/o src not supported yet")

p = Problem(model=Group())
root = p.model
Expand All @@ -293,12 +292,14 @@ def test_inp_inp_promoted_no_src(self):
C3 = G4.add_subsystem("C3", ExecComp('y=x*2.0'), promotes=['x'])
C4 = G4.add_subsystem("C4", ExecComp('y=x*2.0'), promotes=['x'])

p.setup(check=False)
p.setup()
p.final_setup()

# setting promoted name should set both inputs mapped to that name
p['G3.x'] = 999.
self.assertEqual(C3._inputs['x'], 999.)
self.assertEqual(C4._inputs['x'], 999.)
with self.assertRaises(Exception) as context:
p['G3.x'] = 999.
self.assertEqual(str(context.exception),
"The promoted name G3.x is invalid because it refers to multiple inputs: [G3.G4.C3.x, G3.G4.C4.x] that are not connected to an output variable.")

def test_inp_inp_promoted_w_prom_src(self):
p = Problem(model=Group())
Expand Down
85 changes: 52 additions & 33 deletions openmdao/core/tests/test_getset_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,18 @@ def test_with_promotion_errors(self):
# -------------------------------------------------------------------

msg1 = 'Variable name "{}" not found.'
msg2 = ('The promoted name "{}" is invalid because it is non-unique. '
'Access the value from the connected output variable "{}" instead.')
msg2 = "The promoted name x is invalid because it refers to multiple inputs: [g.c2.x ,g.c3.x]. Access the value from the connected output variable x instead."

inputs, outputs, residuals = g.get_nonlinear_vectors()

# inputs
with assertRaisesRegex(self, KeyError, msg2.format('x', 'x')):
with self.assertRaises(Exception) as context:
inputs['x'] = 5.0
with assertRaisesRegex(self, KeyError, msg2.format('x', 'x')):
self.assertEqual(str(context.exception), msg2)
with self.assertRaises(Exception) as context:
self.assertEqual(inputs['x'], 5.0)
self.assertEqual(str(context.exception), msg2)

with assertRaisesRegex(self, KeyError, msg1.format('g.c2.x')):
inputs['g.c2.x'] = 5.0
with assertRaisesRegex(self, KeyError, msg1.format('g.c2.x')):
Expand All @@ -231,10 +234,14 @@ def test_with_promotion_errors(self):
with g.jacobian_context() as jac:

# d(outputs)/d(inputs)
with assertRaisesRegex(self, KeyError, msg2.format('x', 'x')):
with self.assertRaises(Exception) as context:
jac['y', 'x'] = 5.0
with assertRaisesRegex(self, KeyError, msg2.format('x', 'x')):
self.assertEqual(str(context.exception), msg2)

with self.assertRaises(Exception) as context:
self.assertEqual(jac['y', 'x'], 5.0)
self.assertEqual(str(context.exception), msg2)

with assertRaisesRegex(self, KeyError, msg1.format('g.c2.y', 'g.c2.x')):
jac['g.c2.y', 'g.c2.x'] = 5.0
with assertRaisesRegex(self, KeyError, msg1.format('g.c2.y', 'g.c2.x')):
Expand Down Expand Up @@ -268,44 +275,49 @@ def test_nested_promotion_errors(self):

# -------------------------------------------------------------------

msg1 = ('The promoted name "{}" is invalid because it is non-unique. '
'Access the value from the connected output variable instead.')
msg1 = "The promoted name g.x is invalid because it refers to multiple inputs: [g.c2.x, g.c3.x] that are not connected to an output variable."

# inputs (g.x is not connected)
with assertRaisesRegex(self, KeyError, msg1.format('g.x')):
#with assertRaisesRegex(self, RuntimeError, msg1.format('g.x')):
with self.assertRaises(Exception) as context:
p['g.x'] = 5.0
p.final_setup()

self.assertEqual(str(context.exception), msg1)

# Repeat test for post final_setup when vectors are allocated.
p = Problem(model)
p.setup(check=False)
p.final_setup()

# -------------------------------------------------------------------

msg1 = ('The promoted name "{}" is invalid because it is non-unique. '
'Access the value from the connected output variable instead.')

# inputs (g.x is not connected)
with assertRaisesRegex(self, KeyError, msg1.format('g.x')):
with self.assertRaises(Exception) as context:
p['g.x'] = 5.0
p.final_setup()
self.assertEqual(str(context.exception), msg1)

# Start from a clean state again
p = Problem(model)
p.setup(check=False)

with assertRaisesRegex(self, KeyError, msg1.format('g.x')):
with self.assertRaises(Exception) as context:
self.assertEqual(p['g.x'], 5.0)
self.assertEqual(str(context.exception), msg1)

msg2 = "The promoted name x is invalid because it refers to multiple inputs: [g.c2.x, g.c3.x] that are not connected to an output variable."

with g.jacobian_context() as jac:

# d(outputs)/d(inputs)
with assertRaisesRegex(self, KeyError, msg1.format('x')):
with self.assertRaises(Exception) as context:
jac['y', 'x'] = 5.0
self.assertEqual(str(context.exception), msg2)

with assertRaisesRegex(self, KeyError, msg1.format('x')):
with self.assertRaises(Exception) as context:
self.assertEqual(jac['y', 'x'], 5.0)
self.assertEqual(str(context.exception), msg2)

# -------------------------------------------------------------------

Expand All @@ -314,78 +326,85 @@ def test_nested_promotion_errors(self):
p.setup(check=False)
p.final_setup()

with assertRaisesRegex(self, KeyError, msg1.format('g.x')):
with self.assertRaises(Exception) as context:
self.assertEqual(p['g.x'], 5.0)
self.assertEqual(str(context.exception), msg1)

with g.jacobian_context() as jac:

# d(outputs)/d(inputs)
with assertRaisesRegex(self, KeyError, msg1.format('x')):
with self.assertRaises(Exception) as context:
jac['y', 'x'] = 5.0
self.assertEqual(str(context.exception), msg2)

with assertRaisesRegex(self, KeyError, msg1.format('x')):
with self.assertRaises(Exception) as context:
self.assertEqual(jac['y', 'x'], 5.0)
self.assertEqual(str(context.exception), msg2)

# -------------------------------------------------------------------

msg1 = "The promoted name g.x is invalid because it refers to multiple inputs: [g.c2.x ,g.c3.x]. Access the value from the connected output variable x instead."

# From here, 'g.x' has a valid source.
model.connect('x', 'g.x')

p = Problem(model)
p.setup(check=False)

msg2 = ('The promoted name "{}" is invalid because it is non-unique. '
'Access the value from the connected output variable "{}" instead.')

# inputs (g.x is connected to x)
p['g.x'] = 5.0
with assertRaisesRegex(self, KeyError, msg2.format('g.x', 'x')):
with self.assertRaises(Exception) as context:
p.final_setup()
self.assertEqual(str(context.exception), msg1)

# Repeat test for post final_setup when vectors are allocated.
p = Problem(model)
p.setup(check=False)
p.final_setup()

msg2 = ('The promoted name "{}" is invalid because it is non-unique. '
'Access the value from the connected output variable "{}" instead.')

# inputs (g.x is connected to x)
with assertRaisesRegex(self, KeyError, msg2.format('g.x', 'x')):
with self.assertRaises(Exception) as context:
p['g.x'] = 5.0
self.assertEqual(str(context.exception), msg1)

# Final test, the getitem
p = Problem(model)
p.setup(check=False)

with assertRaisesRegex(self, KeyError, msg2.format('g.x', 'x')):
with self.assertRaises(Exception) as context:
self.assertEqual(p['g.x'], 5.0)
self.assertEqual(str(context.exception), msg1)

with g.jacobian_context() as jac:

# d(outputs)/d(inputs)
with assertRaisesRegex(self, KeyError, msg1.format('x')):
with self.assertRaises(Exception) as context:
jac['y', 'x'] = 5.0
self.assertEqual(str(context.exception), msg2)

with assertRaisesRegex(self, KeyError, msg1.format('x')):
with self.assertRaises(Exception) as context:
self.assertEqual(jac['y', 'x'], 5.0) # Start from a clean state again
self.assertEqual(str(context.exception), msg2)

# Repeat test for post final_setup when vectors are allocated.
p = Problem(model)
p.setup(check=False)
p.final_setup()

with assertRaisesRegex(self, KeyError, msg2.format('g.x', 'x')):
with self.assertRaises(Exception) as context:
self.assertEqual(p['g.x'], 5.0)
self.assertEqual(str(context.exception), msg1)

with g.jacobian_context() as jac:

# d(outputs)/d(inputs)
with assertRaisesRegex(self, KeyError, msg1.format('x')):
with self.assertRaises(Exception) as context:
jac['y', 'x'] = 5.0
self.assertEqual(str(context.exception), msg2)

with assertRaisesRegex(self, KeyError, msg1.format('x')):
with self.assertRaises(Exception) as context:
self.assertEqual(jac['y', 'x'], 5.0)
self.assertEqual(str(context.exception), msg2)


if __name__ == '__main__':
Expand Down
72 changes: 38 additions & 34 deletions openmdao/error_checking/check_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,6 @@
from openmdao.utils.logger_utils import get_logger


def check_config(problem, logger=None):
"""
Perform optional error checks on a Problem.
Parameters
----------
problem : Problem
The Problem being checked.
logger : object
Logging object.
"""
logger = logger if logger else get_logger('check_config', use_format=True)

_check_hanging_inputs(problem, logger)

for system in problem.model.system_iter(include_self=True, recurse=True):
# system specific check
system.check_config(logger)
# check dataflow within Group
if isinstance(system, Group):
_check_dataflow(system, logger)


def _check_dataflow(group, logger):
"""
Report any cycles and out of order Systems to the logger.
Expand All @@ -57,8 +34,7 @@ def _check_dataflow(group, logger):
cycle_idxs = {}

if cycles:
logger.warning("Group '%s' has the following cycles: %s" %
(group.pathname, cycles))
logger.info("Group '%s' has the following cycles: %s" % (group.pathname, cycles))
for i, cycle in enumerate(cycles):
# keep track of cycles so we can detect when a system in
# one cycle is out of order with a system in a different cycle.
Expand Down Expand Up @@ -141,6 +117,8 @@ def _check_hanging_inputs(problem, logger):
"""
Issue a logger warning if any inputs are not connected.
Promoted inputs are shown alongside their corresponding absolute names.
Parameters
----------
problem : <Problem>
Expand All @@ -150,12 +128,21 @@ def _check_hanging_inputs(problem, logger):
"""
input_srcs = problem.model._conn_global_abs_in2out

hanging = sorted([name for name in problem.model._var_allprocs_abs_names['input']
if name not in input_srcs])
prom_ins = problem.model._var_allprocs_prom2abs_list['input']
unconns = []
for prom, abslist in iteritems(prom_ins):
unconn = [a for a in abslist if a not in input_srcs or len(input_srcs[a]) == 0]
if unconn:
unconns.append(prom)

if hanging:
if unconns:
msg = ["The following inputs are not connected:\n"]
msg.extend(" %s\n" % h for h in hanging)
for prom in sorted(unconns):
absnames = prom_ins[prom]
if len(absnames) == 1 and prom == absnames[0]: # not really promoted
msg.append(" %s\n" % prom)
else: # promoted
msg.append(" %s: %s\n" % (prom, prom_ins[prom]))
logger.warning(''.join(msg))


Expand All @@ -182,6 +169,23 @@ def _check_system_configs(problem, logger):
'system': _check_system_configs,
}


def check_config(problem, logger=None):
"""
Perform optional error checks on a Problem.
Parameters
----------
problem : Problem
The Problem being checked.
logger : object
Logging object.
"""
logger = logger if logger else get_logger('check_config', use_format=True)

for c in sorted(_checks.keys()):
_checks[c](problem, logger)

#
# Command line interface functions
#
Expand Down Expand Up @@ -219,17 +223,17 @@ def _check_config_cmd(options):
"""
def _check_config(prob):
if options.outfile is None:
outfile = sys.stdout
logger = get_logger('check_config', use_format=True)
else:
outfile = open(options.outfile, 'w')
logger = get_logger('check_config', out_stream=outfile, use_format=True)

if not options.checks:
check_config(prob, logger)
else:
for c in options.checks:
_checks[c](prob, logger)
options.checks = sorted(_checks.keys())

for c in options.checks:
_checks[c](prob, logger)

exit()

return _check_config
Loading

0 comments on commit 5ad331b

Please sign in to comment.