Skip to content

Commit

Permalink
Merge pull request #981 from hschilling/P166291448-var-tags
Browse files Browse the repository at this point in the history
P166291448 Add a feature that lets users tag variables
  • Loading branch information
swryan committed Jul 16, 2019
2 parents 0b916a9 + 903c12b commit 8a84901
Show file tree
Hide file tree
Showing 15 changed files with 716 additions and 22 deletions.
2 changes: 1 addition & 1 deletion openmdao/components/exec_comp.py
Expand Up @@ -18,7 +18,7 @@
# Names of metadata entries allowed for ExecComp variables.
_allowed_meta = {'value', 'shape', 'units', 'res_units', 'desc',
'ref', 'ref0', 'res_ref', 'lower', 'upper', 'src_indices',
'flat_src_indices'}
'flat_src_indices', 'tags'}

# Names that are not allowed for input or output variables (keywords for options)
_disallowed_names = {'has_diag_partials', 'vectorize', 'units', 'shape'}
Expand Down
62 changes: 62 additions & 0 deletions openmdao/components/tests/test_exec_comp.py
Expand Up @@ -801,6 +801,68 @@ def test_has_diag_partials_shape_only(self):

assert_almost_equal(J, np.eye(5)*3., decimal=6)

def test_tags(self):
prob = om.Problem(model=om.Group())
prob.model.add_subsystem('indep', om.IndepVarComp('x', 100.0, units='cm'))
C1 = prob.model.add_subsystem('C1', om.ExecComp('y=x+z+1.',
x={'value': 1.0, 'units': 'm', 'tags': 'tagx'},
y={'units': 'm', 'tags': ['tagy','tagq']},
z={'value': 2.0, 'tags': 'tagz'},
))
prob.model.connect('indep.x', 'C1.x')

prob.setup(check=False)

prob.set_solver_print(level=0)
prob.run_model()

# Inputs no tags
inputs = prob.model.list_inputs(values=False, out_stream=None)
self.assertEqual(sorted(inputs), [
('C1.x', {}),
('C1.z', {}),
])

# Inputs with tags
inputs = prob.model.list_inputs(values=False, out_stream=None, tags="tagx")
self.assertEqual(sorted(inputs), [
('C1.x', {}),
])

# Inputs with multiple tags
inputs = prob.model.list_inputs(values=False, out_stream=None, tags=["tagx", "tagz"])
self.assertEqual(sorted(inputs), [
('C1.x', {}),
('C1.z', {}),
])

# Inputs with tag that does not match
inputs = prob.model.list_inputs(values=False, out_stream=None, tags="tag_wrong")
self.assertEqual(sorted(inputs), [])

# Outputs no tags
outputs = prob.model.list_outputs(values=False, out_stream=None)
self.assertEqual(sorted(outputs), [
('C1.y', {}),
('indep.x', {}),
])

# Outputs with tags
outputs = prob.model.list_outputs(values=False, out_stream=None, tags="tagy")
self.assertEqual(sorted(outputs), [
('C1.y', {}),
])

# Outputs with multiple tags
outputs = prob.model.list_outputs(values=False, out_stream=None, tags=["tagy", "tagx"])
self.assertEqual(sorted(outputs), [
('C1.y', {}),
])

# Outputs with tag that does not match
outputs = prob.model.list_outputs(values=False, out_stream=None, tags="tag_wrong")
self.assertEqual(sorted(outputs), [])

def test_feature_has_diag_partials(self):
import numpy as np
import openmdao.api as om
Expand Down
48 changes: 40 additions & 8 deletions openmdao/core/component.py
Expand Up @@ -19,15 +19,15 @@
from openmdao.utils.name_maps import rel_key2abs_key, abs_key2rel_key, rel_name2abs_name
from openmdao.utils.mpi import MPI
from openmdao.utils.general_utils import format_as_float_or_array, ensure_compatible, \
warn_deprecation, find_matches, simple_warning
warn_deprecation, find_matches, simple_warning, convert_user_defined_tags_to_set
import openmdao.utils.coloring as coloring_mod


# the following metadata will be accessible for vars on all procs
global_meta_names = {
'input': ('units', 'shape', 'size', 'distributed'),
'input': ('units', 'shape', 'size', 'distributed', 'tags'),
'output': ('units', 'shape', 'size',
'ref', 'ref0', 'res_ref', 'distributed', 'lower', 'upper'),
'ref', 'ref0', 'res_ref', 'distributed', 'lower', 'upper', 'tags'),
}

_full_slice = slice(None)
Expand Down Expand Up @@ -415,7 +415,7 @@ def _update_subjac_sparsity(self, sparsity):
self._subjacs_info[abs_key]['sparsity'] = tup

def add_input(self, name, val=1.0, shape=None, src_indices=None, flat_src_indices=None,
units=None, desc=''):
units=None, desc='', tags=None):
"""
Add an input variable to the component.
Expand Down Expand Up @@ -443,6 +443,9 @@ def add_input(self, name, val=1.0, shape=None, src_indices=None, flat_src_indice
during execution. Default is None, which means it is unitless.
desc : str
description of the variable
tags : str or list of strs
User defined tags that can be used to filter what gets listed when calling
list_inputs and list_outputs.
Returns
-------
Expand Down Expand Up @@ -482,6 +485,9 @@ def add_input(self, name, val=1.0, shape=None, src_indices=None, flat_src_indice
if units is not None and not valid_units(units):
raise ValueError("%s: The units '%s' are invalid" % (self.msginfo, units))

if tags is not None and not isinstance(tags, (str, list)):
raise TypeError('The tags argument should be a str or list')

metadata = {}

# value, shape: based on args, making sure they are compatible
Expand All @@ -500,6 +506,8 @@ def add_input(self, name, val=1.0, shape=None, src_indices=None, flat_src_indice
metadata['desc'] = desc
metadata['distributed'] = self.options['distributed']

metadata['tags'] = convert_user_defined_tags_to_set(tags)

# We may not know the pathname yet, so we have to use name for now, instead of abs_name.
if self._static_mode:
var_rel2meta = self._static_var_rel2meta
Expand All @@ -517,7 +525,7 @@ def add_input(self, name, val=1.0, shape=None, src_indices=None, flat_src_indice

return metadata

def add_discrete_input(self, name, val, desc=''):
def add_discrete_input(self, name, val, desc='', tags=None):
"""
Add a discrete input variable to the component.
Expand All @@ -529,6 +537,9 @@ def add_discrete_input(self, name, val, desc=''):
The initial value of the variable being added.
desc : str
description of the variable
tags : str or list of strs
User defined tags that can be used to filter what gets listed when calling
list_inputs and list_outputs.
Returns
-------
Expand All @@ -542,11 +553,16 @@ def add_discrete_input(self, name, val, desc=''):
raise NameError('%s: The name argument should be a non-empty string.' % self.msginfo)
if not _valid_var_name(name):
raise NameError("%s: '%s' is not a valid input name." % (self.msginfo, name))
if tags is not None and not isinstance(tags, (str, list)):
raise TypeError('%s: The tags argument should be a str or list' % self.msginfo)

tags = convert_user_defined_tags_to_set(tags)

metadata = {
'value': val,
'type': type(val),
'desc': desc,
'tags': tags,
}

if self._static_mode:
Expand All @@ -563,7 +579,7 @@ def add_discrete_input(self, name, val, desc=''):
return metadata

def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc='',
lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=1.0):
lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=1.0, tags=None):
"""
Add an output variable to the component.
Expand Down Expand Up @@ -603,6 +619,9 @@ def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc
res_ref : float or ndarray
Scaling parameter. The value in the user-defined res_units of this output's residual
when the scaled value is 1. Default is 1.
tags : str or list of strs or set of strs
User defined tags that can be used to filter what gets listed when calling
list_inputs and list_outputs.
Returns
-------
Expand Down Expand Up @@ -648,6 +667,9 @@ def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc
if units is not None and not valid_units(units):
raise ValueError("%s: The units '%s' are invalid" % (self.msginfo, units))

if tags is not None and not isinstance(tags, (str, set, list)):
raise TypeError('The tags argument should be a str, set, or list')

metadata = {}

# value, shape: based on args, making sure they are compatible
Expand Down Expand Up @@ -706,6 +728,8 @@ def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc

metadata['distributed'] = self.options['distributed']

metadata['tags'] = convert_user_defined_tags_to_set(tags)

# We may not know the pathname yet, so we have to use name for now, instead of abs_name.
if self._static_mode:
var_rel2meta = self._static_var_rel2meta
Expand All @@ -723,7 +747,7 @@ def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc

return metadata

def add_discrete_output(self, name, val, desc=''):
def add_discrete_output(self, name, val, desc='', tags=None):
"""
Add an output variable to the component.
Expand All @@ -735,6 +759,9 @@ def add_discrete_output(self, name, val, desc=''):
The initial value of the variable being added.
desc : str
description of the variable.
tags : str or list of strs or set of strs
User defined tags that can be used to filter what gets listed when calling
list_inputs and list_outputs.
Returns
-------
Expand All @@ -747,11 +774,16 @@ def add_discrete_output(self, name, val, desc=''):
raise NameError('%s: The name argument should be a non-empty string.' % self.msginfo)
if not _valid_var_name(name):
raise NameError("%s: '%s' is not a valid output name." % (self.msginfo, name))
if tags is not None and not isinstance(tags, (str, set, list)):
raise TypeError('%s: The tags argument should be a str, set, or list' % self.msginfo)

tags = convert_user_defined_tags_to_set(tags)

metadata = {
'value': val,
'type': type(val),
'desc': desc
'desc': desc,
'tags': tags
}

if self._static_mode:
Expand Down
8 changes: 6 additions & 2 deletions openmdao/core/explicitcomponent.py
Expand Up @@ -157,7 +157,7 @@ def _setup_jacobians(self, recurse=True):
self._set_approx_partials_meta()

def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc='',
lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=None):
lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=None, tags=None):
"""
Add an output variable to the component.
Expand Down Expand Up @@ -200,6 +200,9 @@ def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc
Scaling parameter. The value in the user-defined res_units of this output's residual
when the scaled value is 1. Default is None, which means residual scaling matches
output scaling.
tags : str or list of strs
User defined tags that can be used to filter what gets listed when calling
list_inputs and list_outputs and also when listing results from case recorders.
Returns
-------
Expand All @@ -213,7 +216,8 @@ def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc
val=val, shape=shape, units=units,
res_units=res_units, desc=desc,
lower=lower, upper=upper,
ref=ref, ref0=ref0, res_ref=res_ref)
ref=ref, ref0=ref0, res_ref=res_ref,
tags=tags)

def _approx_subjac_keys_iter(self):
for abs_key, meta in iteritems(self._subjacs_info):
Expand Down
37 changes: 32 additions & 5 deletions openmdao/core/indepvarcomp.py
Expand Up @@ -7,7 +7,7 @@
from six import string_types

from openmdao.core.explicitcomponent import ExplicitComponent
from openmdao.utils.general_utils import warn_deprecation
from openmdao.utils.general_utils import warn_deprecation, convert_user_defined_tags_to_set


class IndepVarComp(ExplicitComponent):
Expand Down Expand Up @@ -48,6 +48,15 @@ def __init__(self, name=None, val=1.0, **kwargs):
self._indep_external = []
self._indep_external_discrete = []

if 'tags' not in kwargs:
kwargs['tags'] = set()
else:
if not isinstance(kwargs['tags'], (str, set, list)):
raise TypeError('The tags argument should be a str, set, or list')

kwargs['tags'] = convert_user_defined_tags_to_set(kwargs['tags'])
kwargs['tags'].add('indep_var') # Tag all indep var comps this way

# A single variable is declared during instantiation
if isinstance(name, string_types):
self._indep.append((name, val, kwargs))
Expand Down Expand Up @@ -101,7 +110,7 @@ def setup(self):
"afterwards.".format(self.msginfo))

def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc='',
lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=None):
lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=None, tags=None):
"""
Add an independent variable to this component.
Expand Down Expand Up @@ -142,16 +151,26 @@ def add_output(self, name, val=1.0, shape=None, units=None, res_units=None, desc
Scaling parameter. The value in the user-defined res_units of this output's residual
when the scaled value is 1. Default is None, which means residual scaling matches
output scaling.
tags : str or list of strs
User defined tags that can be used to filter what gets listed when calling
list_outputs.
"""
if res_ref is None:
res_ref = ref

if tags is not None and not isinstance(tags, (str, list)):
raise TypeError('The tags argument should be a str or list')

tags = convert_user_defined_tags_to_set(tags)
tags.add('indep_var')

kwargs = {'shape': shape, 'units': units, 'res_units': res_units, 'desc': desc,
'lower': lower, 'upper': upper, 'ref': ref, 'ref0': ref0,
'res_ref': res_ref}
'res_ref': res_ref, 'tags': tags
}
self._indep_external.append((name, val, kwargs))

def add_discrete_output(self, name, val, desc=''):
def add_discrete_output(self, name, val, desc='', tags=None):
"""
Add an output variable to the component.
Expand All @@ -163,8 +182,16 @@ def add_discrete_output(self, name, val, desc=''):
The initial value of the variable being added in user-defined units. Default is 1.0.
desc : str
description of the variable.
tags : str or list of strs
User defined tags that can be used to filter what gets listed when calling
list_outputs.
"""
kwargs = {'desc': desc}
if tags is not None and not isinstance(tags, (str, list)):
raise TypeError('The tags argument should be a str or list')

tags = convert_user_defined_tags_to_set(tags)

kwargs = {'desc': desc, 'tags': tags}
self._indep_external_discrete.append((name, val, kwargs))

def _linearize(self, jac=None, sub_do_ln=False):
Expand Down

0 comments on commit 8a84901

Please sign in to comment.