Skip to content

Commit

Permalink
Merge branch 'master' into 3018_warning
Browse files Browse the repository at this point in the history
  • Loading branch information
swryan committed Oct 5, 2023
2 parents a606020 + 58a5ff9 commit 89167e9
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 26 deletions.
29 changes: 28 additions & 1 deletion openmdao/components/tests/test_submodel_comp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import openmdao.api as om
from openmdao.utils.mpi import MPI
from openmdao.utils.assert_utils import assert_near_equal, assert_check_partials
from openmdao.utils.assert_utils import assert_near_equal, assert_check_partials, \
assert_check_totals
from openmdao.test_suite.groups.parallel_groups import FanIn, FanOut

try:
Expand Down Expand Up @@ -464,6 +465,28 @@ def test_subprob_solver_print(self):

self.assertTrue((3, 20, 'NL') in p.model.submodel._subprob.model._solver_print_cache)

def test_complex_step_across_submodel(self):
p = om.Problem()
subprob = om.Problem()
subprob.model.add_subsystem('comp', om.ExecComp('x = r*cos(theta)'), promotes=['*'])
submodel = om.SubmodelComp(problem=subprob)

submodel.add_input('r', name='new_r', val=20)
submodel.add_input('theta', name='new_theta', val=0.5)
submodel.add_output('x', name='new_x', val=100)

model = p.model
model.add_subsystem('submodel', submodel, promotes=['*'])
model.add_design_var('new_r')
model.add_design_var('new_theta')
model.add_objective('new_x')

p.setup(force_alloc_complex=True)
p.run_model()

totals = p.check_totals(method='cs')
assert_check_totals(totals, atol=1e-11, rtol=1e-11)


@unittest.skipUnless(MPI and PETScVector, "MPI and PETSc are required.")
class TestSubmodelCompMPI(unittest.TestCase):
Expand All @@ -485,3 +508,7 @@ def test_submodel_comp(self):
p.run_model()
cpd = p.check_partials(method='cs', out_stream=None)
assert_check_partials(cpd)


if __name__ == '__main__':
unittest.main()
44 changes: 23 additions & 21 deletions openmdao/core/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -4853,27 +4853,29 @@ def _set_complex_step_mode(self, active):
active : bool
Complex mode flag; set to True prior to commencing complex step.
"""
for sub in self.system_iter(include_self=True, recurse=True):
sub.under_complex_step = active
sub._inputs.set_complex_step_mode(active)
sub._outputs.set_complex_step_mode(active)
sub._residuals.set_complex_step_mode(active)

if sub._doutputs._alloc_complex:
sub._doutputs.set_complex_step_mode(active)
sub._dinputs.set_complex_step_mode(active)
sub._dresiduals.set_complex_step_mode(active)
if sub.nonlinear_solver:
sub.nonlinear_solver._set_complex_step_mode(active)

if sub.linear_solver:
sub.linear_solver._set_complex_step_mode(active)

if sub._owns_approx_jac:
sub._jacobian.set_complex_step_mode(active)

if sub._assembled_jac:
sub._assembled_jac.set_complex_step_mode(active)
self.under_complex_step = active
self._inputs.set_complex_step_mode(active)
self._outputs.set_complex_step_mode(active)
self._residuals.set_complex_step_mode(active)

if self._doutputs._alloc_complex:
self._doutputs.set_complex_step_mode(active)
self._dinputs.set_complex_step_mode(active)
self._dresiduals.set_complex_step_mode(active)
if self.nonlinear_solver:
self.nonlinear_solver._set_complex_step_mode(active)

if self.linear_solver:
self.linear_solver._set_complex_step_mode(active)

if self._owns_approx_jac:
self._jacobian.set_complex_step_mode(active)

if self._assembled_jac:
self._assembled_jac.set_complex_step_mode(active)

for sub in self.system_iter(include_self=False, recurse=True):
sub._set_complex_step_mode(active)

def cleanup(self):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.1"
"version": "3.8.10"
},
"orphan": true
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -767,12 +767,111 @@
"expected = ((4 * 3 + 5) * 3 + 5) * 3 + 5.\n",
"assert_near_equal(expected, c3y)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Adding a function to pre-process setting an option\n",
"\n",
"The `OptionsDictionary` has support for custom pre-processing the value before it is set. One potential use for this is to provide a way to convert units while setting an option. The following example shows how to do this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import unittest\n",
"\n",
"import openmdao.api as om\n",
"from openmdao.utils.units import convert_units\n",
"\n",
"\n",
"# TODO: Turn this into a test.\n",
"\n",
"def units_setter(opt_meta, value):\n",
" \"\"\"\n",
" Check and convert new units tuple into\n",
"\n",
" Parameters\n",
" ----------\n",
" opt_meta : dict\n",
" Dictionary of entries for the option.\n",
" value : any\n",
" New value for the option.\n",
"\n",
" Returns\n",
" -------\n",
" any\n",
" Post processed value to set into the option.\n",
" \"\"\"\n",
" new_val, new_units = value\n",
" old_val, units = opt_meta['val']\n",
"\n",
" converted_val = convert_units(new_val, new_units, units)\n",
" return (converted_val, units)\n",
"\n",
"\n",
"class MyComp(om.ExplicitComponent):\n",
"\n",
" def setup(self):\n",
"\n",
" self.add_input('x', 3.0)\n",
" self.add_output('y', 3.0)\n",
"\n",
" def initialize(self):\n",
" self.options.declare('length', default=(12.0, 'inch'),\n",
" set_function=units_setter)\n",
"\n",
" def compute(self, inputs, outputs):\n",
" length = self.options['length'][0]\n",
"\n",
" x = inputs['x']\n",
" outputs['y'] = length * x\n",
"\n",
"\n",
"class MySubgroup(om.Group):\n",
"\n",
" def setup(self):\n",
" self.add_subsystem('mass', MyComp())\n",
"\n",
"\n",
"prob = om.Problem()\n",
"model = prob.model\n",
"\n",
"model.add_subsystem('statics', MySubgroup())\n",
"\n",
"prob.model_options['*'] = {'length': (2.0, 'ft')}\n",
"prob.setup()\n",
"\n",
"prob.run_model()\n",
"\n",
"print('The following should be 72 if the units convert correctly.')\n",
"print(prob.get_val('statics.mass.y'))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"remove-output",
"remove-input"
]
},
"outputs": [],
"source": [
"from openmdao.utils.assert_utils import assert_near_equal\n",
"assert_near_equal(prob.get_val('statics.mass.y'), 72, 1e-6)\n"
]
}
],
"metadata": {
"celltoolbar": "Tags",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
Expand All @@ -786,7 +885,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.0"
"version": "3.8.10"
},
"orphan": true
},
Expand Down
10 changes: 9 additions & 1 deletion openmdao/utils/options_dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def temporary(self, **kwargs):

def declare(self, name, default=_UNDEFINED, values=None, types=None, desc='',
upper=None, lower=None, check_valid=None, allow_none=False, recordable=True,
deprecation=None):
set_function=None, deprecation=None):
r"""
Declare an option.
Expand Down Expand Up @@ -379,6 +379,9 @@ def declare(self, name, default=_UNDEFINED, values=None, types=None, desc='',
If True, allow None as a value regardless of values or types.
recordable : bool
If True, add to recorder.
set_function : None or function
User-supplied function with arguments (Options metadata, value) that pre-processes
value and returns a new value.
deprecation : str or tuple or None
If None, it is not deprecated. If a str, use as a DeprecationWarning
during __setitem__ and __getitem__. If a tuple of the form (msg, new_name),
Expand Down Expand Up @@ -437,6 +440,7 @@ def declare(self, name, default=_UNDEFINED, values=None, types=None, desc='',
'has_been_set': default_provided,
'allow_none': allow_none,
'recordable': recordable,
'set_function': set_function,
'deprecation': deprecation,
}

Expand Down Expand Up @@ -521,6 +525,10 @@ def __setitem__(self, name, value):

self._assert_valid(name, value)

# General function test
if meta['set_function'] is not None:
value = meta['set_function'](meta, value)

meta['val'] = value
meta['has_been_set'] = True

Expand Down
73 changes: 73 additions & 0 deletions openmdao/utils/tests/test_options_dictionary_units.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import unittest

import openmdao.api as om
from openmdao.utils.assert_utils import assert_near_equal
from openmdao.utils.units import convert_units


def units_setter(opt_meta, value):
"""
Check and convert new units tuple into
Parameters
----------
opt_meta : dict
Dictionary of entries for the option.
value : any
New value for the option.
Returns
-------
any
Post processed value to set into the option.
"""
new_val, new_units = value
old_val, units = opt_meta['val']

converted_val = convert_units(new_val, new_units, units)
return (converted_val, units)


class AviaryComp(om.ExplicitComponent):

def setup(self):

self.add_input('x', 3.0)
self.add_output('y', 3.0)

def initialize(self):
self.options.declare('length', default=(12.0, 'inch'),
set_function=units_setter)

def compute(self, inputs, outputs):
length = self.options['length'][0]

x = inputs['x']
outputs['y'] = length * x


class Fakeviary(om.Group):

def setup(self):
self.add_subsystem('mass', AviaryComp())


class TestOptionsDictionaryUnits(unittest.TestCase):

def test_simple(self):
prob = om.Problem()
model = prob.model

model.add_subsystem('statics', Fakeviary())

prob.model_options['*'] = {'length': (2.0, 'ft')}
prob.setup()

prob.run_model()

y = prob.get_val('statics.mass.y')
assert_near_equal(y, 72, 1e-6)


if __name__ == "__main__":
unittest.main()

0 comments on commit 89167e9

Please sign in to comment.