Skip to content

Commit

Permalink
Merge pull request #1455 from swryan/work
Browse files Browse the repository at this point in the history
User can specify units for KScomp input and output
  • Loading branch information
swryan committed Jun 10, 2020
2 parents 8143d68 + d0137e4 commit 59f4980
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 39 deletions.
30 changes: 28 additions & 2 deletions openmdao/components/ks_comp.py
Expand Up @@ -4,6 +4,7 @@
import numpy as np

from openmdao.core.explicitcomponent import ExplicitComponent
from openmdao.utils.units import valid_units


CITATIONS = """
Expand All @@ -19,6 +20,25 @@
"""


def check_option(option, value):
"""
Check option for validity.
Parameters
----------
option : str
The name of the option
value : any
The value of the option
Raises
------
ValueError
"""
if option == 'units' and value is not None and not valid_units(value):
raise ValueError("The units '%s' are invalid." % value)


class KSfunction(object):
"""
Helper class for KSComp.
Expand Down Expand Up @@ -145,6 +165,10 @@ def initialize(self):
desc='If True, add a constraint on the resulting output of the KSComp.'
' If False, the user will be expected to add a constraint '
'explicitly.')
self.options.declare('units', types=str, allow_none=True, default=None,
desc='Units to be assigned to all variables in this component. '
'Default is None, which means variables are unitless.',
check_valid=check_option)
self.options.declare('scaler', types=(int, float), allow_none=True, default=None,
desc="Scaler for constraint, if added, default is one.")
self.options.declare('adder', types=(int, float), allow_none=True, default=None,
Expand All @@ -165,13 +189,15 @@ def setup(self):
opts = self.options
width = opts['width']
vec_size = opts['vec_size']
units = opts['units']

# Inputs
self.add_input('g', shape=(vec_size, width),
self.add_input('g', shape=(vec_size, width), units=units,
desc="Array of function values to be aggregated")

# Outputs
self.add_output('KS', shape=(vec_size, 1), desc="Value of the aggregate KS function")
self.add_output('KS', shape=(vec_size, 1), units=units,
desc="Value of the aggregate KS function")

if opts['add_constraint']:
self.add_constraint(name='KS', upper=0.0, scaler=opts['scaler'], adder=opts['adder'],
Expand Down
56 changes: 45 additions & 11 deletions openmdao/components/tests/test_ks_comp.py
Expand Up @@ -33,6 +33,11 @@ def test_basic_ks(self):

assert_near_equal(max(prob['comp.y2']), prob['ks.KS'][0])

def test_bad_units(self):
with self.assertRaises(ValueError) as ctx:
om.KSComp(units='wtfu')
self.assertEqual(str(ctx.exception), "The units 'wtfu' are invalid.")

def test_vectorized(self):
prob = om.Problem()
model = prob.model
Expand Down Expand Up @@ -208,7 +213,8 @@ def test_basic(self):
model = prob.model

model.add_subsystem('px', om.IndepVarComp('x', val=np.array([5.0, 4.0])))
model.add_subsystem('comp', om.ExecComp('y = 3.0*x', x=np.zeros((2, )),
model.add_subsystem('comp', om.ExecComp('y = 3.0*x',
x=np.zeros((2, )),
y=np.zeros((2, ))))
model.add_subsystem('ks', om.KSComp(width=2))

Expand All @@ -218,7 +224,7 @@ def test_basic(self):
prob.setup()
prob.run_model()

assert_near_equal(prob['ks.KS'][0], 15.0)
assert_near_equal(prob['ks.KS'], [[15.0]])

def test_vectorized(self):
import numpy as np
Expand All @@ -229,7 +235,8 @@ def test_vectorized(self):
model = prob.model

model.add_subsystem('px', om.IndepVarComp('x', val=np.array([[5.0, 4.0], [10.0, 8.0]])))
model.add_subsystem('comp', om.ExecComp('y = 3.0*x', x=np.zeros((2, 2)),
model.add_subsystem('comp', om.ExecComp('y = 3.0*x',
x=np.zeros((2, 2)),
y=np.zeros((2, 2))))
model.add_subsystem('ks', om.KSComp(width=2, vec_size=2))

Expand All @@ -239,8 +246,7 @@ def test_vectorized(self):
prob.setup()
prob.run_model()

assert_near_equal(prob['ks.KS'][0], 15.0)
assert_near_equal(prob['ks.KS'][1], 30.0)
assert_near_equal(prob['ks.KS'], np.array([[15], [30]]))

def test_upper(self):
import numpy as np
Expand All @@ -251,7 +257,8 @@ def test_upper(self):
model = prob.model

model.add_subsystem('px', om.IndepVarComp('x', val=np.array([5.0, 4.0])))
model.add_subsystem('comp', om.ExecComp('y = 3.0*x', x=np.zeros((2, )),
model.add_subsystem('comp', om.ExecComp('y = 3.0*x',
x=np.zeros((2, )),
y=np.zeros((2, ))))
model.add_subsystem('ks', om.KSComp(width=2))

Expand All @@ -262,7 +269,7 @@ def test_upper(self):
prob.setup()
prob.run_model()

assert_near_equal(prob['ks.KS'][0], -1.0)
assert_near_equal(prob['ks.KS'], np.array([[-1.0]]))

def test_lower_flag(self):
import numpy as np
Expand All @@ -273,7 +280,8 @@ def test_lower_flag(self):
model = prob.model

model.add_subsystem('px', om.IndepVarComp('x', val=np.array([5.0, 4.0])))
model.add_subsystem('comp', om.ExecComp('y = 3.0*x', x=np.zeros((2, )),
model.add_subsystem('comp', om.ExecComp('y = 3.0*x',
x=np.zeros((2, )),
y=np.zeros((2, ))))
model.add_subsystem('ks', om.KSComp(width=2))

Expand All @@ -284,7 +292,7 @@ def test_lower_flag(self):
prob.setup()
prob.run_model()

assert_near_equal(prob['ks.KS'][0], -12.0)
assert_near_equal(prob['ks.KS'], [[-12.0]])

def test_add_constraint(self):
import numpy as np
Expand All @@ -301,8 +309,10 @@ def test_add_constraint(self):
ivc.add_output('x', val=np.linspace(-np.pi/2, np.pi/2, n))
ivc.add_output('k', val=5.0)

model.add_subsystem('comp', om.ExecComp('y = -3.0*x**2 + k', x=np.zeros((n, )),
y=np.zeros((n, )), k=0.0))
model.add_subsystem('comp', om.ExecComp('y = -3.0*x**2 + k',
x=np.zeros((n, )),
y=np.zeros((n, )),
k=0.0))
model.add_subsystem('ks', om.KSComp(width=n, upper=4.0, add_constraint=True))

model.add_design_var('ivc.k', lower=-10, upper=10)
Expand Down Expand Up @@ -331,6 +341,30 @@ def test_add_constraint(self):

plt.show()

def test_units(self):
import openmdao.api as om
from openmdao.utils.units import convert_units

n = 10

model = om.Group()

model.add_subsystem('indep', om.IndepVarComp('x', val=range(n), units='ft'))
model.add_subsystem('ks', om.KSComp(width=n, units='m'))

model.connect('indep.x', 'ks.g')

prob = om.Problem(model=model)
prob.setup()
prob.run_model()

# KS is expressed in meters, while the independent variable 'x' is in feet
assert_near_equal(prob['ks.KS'][0], convert_units(max(prob['indep.x']), 'ft', 'm'),
tolerance=1e-8)

assert_near_equal(convert_units(prob['ks.KS'][0], 'm', 'ft'), max(prob['indep.x']),
tolerance=1e-8)


if __name__ == "__main__":
unittest.main()
9 changes: 9 additions & 0 deletions openmdao/docs/features/building_blocks/components/ks_comp.rst
Expand Up @@ -87,4 +87,13 @@ conservative.
openmdao.components.tests.test_ks_comp.TestKSFunctionFeatures.test_add_constraint
:layout: code, plot

**units**

Finally, note that you can pass a units option to the KSComp that will define units on its input and output variables.
There is only one unit, shared between both inputs and outputs.

.. embed-code::
openmdao.components.tests.test_ks_comp.TestKSFunctionFeatures.test_units
:layout: interleave

.. tags:: KSComp, Component, Constraints, Optimization
32 changes: 13 additions & 19 deletions openmdao/recorders/sqlite_reader.py
Expand Up @@ -321,16 +321,14 @@ def list_sources(self, out_stream=_DEFAULT_OUT_STREAM):
if self._format_version >= 2 and self._problem_cases.count() > 0:
sources.extend(self._problem_cases.list_sources())

if not out_stream:
return sources
else:
if out_stream:
if out_stream is _DEFAULT_OUT_STREAM:
out_stream = sys.stdout

if out_stream:
for source in sources:
out_stream.write('{}\n'.format(source))
return sources
for source in sources:
out_stream.write('{}\n'.format(source))

return sources

def list_source_vars(self, source, out_stream=_DEFAULT_OUT_STREAM):
"""
Expand Down Expand Up @@ -382,15 +380,13 @@ def list_source_vars(self, source, out_stream=_DEFAULT_OUT_STREAM):
if case.residuals:
dct['residuals'] = list(case.residuals)

if not out_stream:
return dct
else:
if out_stream:
if out_stream is _DEFAULT_OUT_STREAM:
out_stream = sys.stdout

if out_stream:
write_source_table(dct, out_stream)
return dct
write_source_table(dct, out_stream)

return dct

def list_cases(self, source=None, recurse=True, flat=True, out_stream=_DEFAULT_OUT_STREAM):
"""
Expand Down Expand Up @@ -548,15 +544,13 @@ def _list_cases_recurse_flat(self, coord=None, out_stream=_DEFAULT_OUT_STREAM):
self.source_cases_table[table].append(case_coord)
cases.append(case_coord)

if not out_stream:
return cases
else:
if out_stream:
if out_stream is _DEFAULT_OUT_STREAM:
out_stream = sys.stdout

if out_stream:
write_source_table(self.source_cases_table, out_stream)
return cases
write_source_table(self.source_cases_table, out_stream)

return cases

def _list_cases_recurse_nested(self, coord=None):
"""
Expand Down
@@ -1,7 +1,7 @@
"""
Stress calculation component.
Simple calculation of beam bending stress assuming small angular displacments.
Simple calculation of beam bending stress assuming small angular displacements.
Vectorized for multiple load cases.
"""

Expand Down
Expand Up @@ -124,9 +124,8 @@ def setup(self):
comp = MultiStressComp(num_elements=num_elements, E=E, num_rhs=num_rhs)
sub.add_subsystem('stress_comp', comp)

self.connect(
'local_stiffness_matrix_comp.K_local',
'parallel.%s.states_comp.K_local' % name)
self.connect('local_stiffness_matrix_comp.K_local',
'parallel.%s.states_comp.K_local' % name)

for k in range(num_rhs):
sub.connect('states_comp.d_%d' % k,
Expand All @@ -144,9 +143,8 @@ def setup(self):

sub.add_subsystem('KS_%d' % k, comp)

sub.connect(
'stress_comp.stress_%d' % k,
'KS_%d.g' % k)
sub.connect('stress_comp.stress_%d' % k,
'KS_%d.g' % k)

if not self.options['ks_add_constraint']:
sub.add_constraint('KS_%d.KS' % k, upper=0.0,
Expand Down

0 comments on commit 59f4980

Please sign in to comment.