Skip to content

Commit

Permalink
POEM 039 Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
swryan committed Jan 25, 2021
1 parent 7915d7f commit 33a0048
Show file tree
Hide file tree
Showing 19 changed files with 760 additions and 165 deletions.
339 changes: 257 additions & 82 deletions openmdao/components/exec_comp.py

Large diffs are not rendered by default.

370 changes: 365 additions & 5 deletions openmdao/components/tests/test_exec_comp.py

Large diffs are not rendered by default.

15 changes: 6 additions & 9 deletions openmdao/core/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -1278,15 +1278,12 @@ def _declare_partials(self, of, wrt, dct, quick_declare=False):
cases with large numbers of explicit components or indepvarcomps.
"""
if quick_declare:
abs_key = rel_key2abs_key(self, (of, wrt))

meta = {}
meta['rows'] = np.array(dct['rows'], dtype=INT_DTYPE, copy=False)
meta['cols'] = np.array(dct['cols'], dtype=INT_DTYPE, copy=False)
meta['shape'] = (len(dct['rows']), len(dct['cols']))
meta['value'] = dct['value']

self._subjacs_info[abs_key] = meta
self._subjacs_info[rel_key2abs_key(self, (of, wrt))] = {
'rows': np.array(dct['rows'], dtype=INT_DTYPE, copy=False),
'cols': np.array(dct['cols'], dtype=INT_DTYPE, copy=False),
'shape': (len(dct['rows']), len(dct['cols'])),
'value': dct['value'],
}
return

val = dct['value'] if 'value' in dct else None
Expand Down
51 changes: 31 additions & 20 deletions openmdao/core/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -973,8 +973,10 @@ def _compute_approx_coloring(self, recurse=False, **overrides):
self.declare_partials('*', '*', method=self._coloring_info['method'])
except AttributeError: # this system must be a group
from openmdao.core.component import Component
from openmdao.components.exec_comp import ExecComp
for s in self.system_iter(recurse=True, typ=Component):
s.declare_partials('*', '*', method=self._coloring_info['method'])
if not isinstance(s, ExecComp):
s.declare_partials('*', '*', method=self._coloring_info['method'])
self._setup_partials()

approx_scheme = self._get_approx_scheme(self._coloring_info['method'])
Expand Down Expand Up @@ -2067,15 +2069,19 @@ def _unscaled_context(self, outputs=(), residuals=()):
for vec in residuals:
vec.scale('phys')

yield
try:

if self._has_output_scaling:
for vec in outputs:
vec.scale('norm')
yield

if self._has_resid_scaling:
for vec in residuals:
vec.scale('norm')
finally:

if self._has_output_scaling:
for vec in outputs:
vec.scale('norm')

if self._has_resid_scaling:
for vec in residuals:
vec.scale('norm')

@contextmanager
def _scaled_context_all(self):
Expand All @@ -2089,14 +2095,18 @@ def _scaled_context_all(self):
for vec in self._vectors['residual'].values():
vec.scale('norm')

yield
try:

if self._has_output_scaling:
for vec in self._vectors['output'].values():
vec.scale('phys')
if self._has_resid_scaling:
for vec in self._vectors['residual'].values():
vec.scale('phys')
yield

finally:

if self._has_output_scaling:
for vec in self._vectors['output'].values():
vec.scale('phys')
if self._has_resid_scaling:
for vec in self._vectors['residual'].values():
vec.scale('phys')

@contextmanager
def _matvec_context(self, vec_name, scope_out, scope_in, mode, clear=True):
Expand Down Expand Up @@ -2153,11 +2163,12 @@ def _matvec_context(self, vec_name, scope_out, scope_in, mode, clear=True):
if scope_in is not None:
d_inputs._names = scope_in.intersection(d_inputs._abs_iter())

yield d_inputs, d_outputs, d_residuals

# reset _names so users will see full vector contents
d_inputs._names = old_ins
d_outputs._names = old_outs
try:
yield d_inputs, d_outputs, d_residuals
finally:
# reset _names so users will see full vector contents
d_inputs._names = old_ins
d_outputs._names = old_outs

@contextmanager
def _call_user_function(self, fname, protect_inputs=True,
Expand Down
11 changes: 5 additions & 6 deletions openmdao/core/tests/test_coloring.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ def setup(self):
self.add_output('g', np.ones(self.size))

# turn on dynamic partial coloring
self.declare_coloring(wrt='*', method='cs', perturb_size=1e-5, num_full_jacs=2, tol=1e-20,
orders=20)
self.declare_coloring(wrt='*', method='cs', perturb_size=1e-5, num_full_jacs=2, tol=1e-20)

def compute(self, inputs, outputs):
outputs['g'] = np.arctan(inputs['y'] / inputs['x'])
Expand Down Expand Up @@ -123,10 +122,10 @@ def run_opt(driver_class, mode, assemble_type=None, color_info=None, derivs=True
indeps.add_output('r', r_init)

if partial_coloring:
arctan_yox = DynPartialsComp(SIZE)
arctan_yox = om.ExecComp('g=arctan(y/x)', shape=SIZE)
arctan_yox.declare_coloring(wrt='*', method='cs', perturb_size=1e-5, num_full_jacs=2, tol=1e-20)
else:
arctan_yox = om.ExecComp('g=arctan(y/x)', has_diag_partials=has_diag_partials,
g=np.ones(SIZE), x=np.ones(SIZE), y=np.ones(SIZE))
arctan_yox = om.ExecComp('g=arctan(y/x)', shape=SIZE, has_diag_partials=has_diag_partials)

p.model.add_subsystem('arctan_yox', arctan_yox)

Expand Down Expand Up @@ -270,8 +269,8 @@ def test_dynamic_total_coloring_snopt_auto_dyn_partials(self):

partial_coloring = p_color.model._get_subsystem('arctan_yox')._coloring_info['coloring']
expected = [
"self.declare_partials(of='g', wrt='x', rows=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], cols=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])",
"self.declare_partials(of='g', wrt='y', rows=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], cols=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])",
"self.declare_partials(of='g', wrt='x', rows=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], cols=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])"
]
decl_partials_calls = partial_coloring.get_declare_partials_calls().strip()
for i, d in enumerate(decl_partials_calls.split('\n')):
Expand Down
3 changes: 2 additions & 1 deletion openmdao/core/tests/test_distrib_derivs.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ def setup(self):
for expr in exprs:
lhs, _ = expr.split('=', 1)
outs.update(self._parse_for_out_vars(lhs))
allvars.update(self._parse_for_vars(expr))
v, _ = self._parse_for_names(expr)
allvars.update(v)

sizes, offsets = evenly_distrib_idxs(comm.size, self.arr_size)
start = offsets[rank]
Expand Down
1 change: 0 additions & 1 deletion openmdao/core/tests/test_parallel_derivatives.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,6 @@ def test_fwd_vs_rev(self):
elapsed_rev = time.time() - elapsed_rev

# run in fwd mode and compare times for deriv calculation
p = om.Problem(model=PartialDependGroup())
p.setup(mode='fwd')
p.run_model()

Expand Down
9 changes: 5 additions & 4 deletions openmdao/devtools/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,11 @@ def profiling(outname='prof.out'):
prof = cProfile.Profile()
prof.enable()

yield prof

prof.disable()
prof.dump_stats(outname)
try:
yield prof
finally:
prof.disable()
prof.dump_stats(outname)


def compare_jacs(Jref, J, rel_trigger=1.0):
Expand Down
22 changes: 12 additions & 10 deletions openmdao/devtools/iprof_mem.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,16 +201,18 @@ def memtrace(**kwargs):

_setup(options)
start()
yield
stop()

_file_line2qualname(options.outfile)
if options.tree:
postprocess_memtrace_tree(fname=options.outfile, min_mem=options.min_mem,
show_colors=options.show_colors, stream=options.stream)
else:
postprocess_memtrace_flat(fname=options.outfile, min_mem=options.min_mem,
show_colors=options.show_colors, stream=options.stream)
try:
yield
finally:
stop()

_file_line2qualname(options.outfile)
if options.tree:
postprocess_memtrace_tree(fname=options.outfile, min_mem=options.min_mem,
show_colors=options.show_colors, stream=options.stream)
else:
postprocess_memtrace_flat(fname=options.outfile, min_mem=options.min_mem,
show_colors=options.show_colors, stream=options.stream)


def _mempost_setup_parser(parser):
Expand Down
6 changes: 4 additions & 2 deletions openmdao/devtools/itrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,10 @@ def tracing(methods=None, verbose=False, memory=False, leaks=False, show_ptrs=Fa
"""
setup(methods=methods, verbose=verbose, memory=memory, leaks=leaks, show_ptrs=show_ptrs)
start()
yield
stop()
try:
yield
finally:
stop()


class tracedfunc(object):
Expand Down
44 changes: 42 additions & 2 deletions openmdao/docs/features/building_blocks/components/exec_comp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ExecComp
`ExecComp` is a component that provides a shortcut for building an ExplicitComponent that
represents a set of simple mathematical relationships between inputs and outputs. The ExecComp
automatically takes care of all of the component API methods, so you just need to instantiate
it with an equation.
it with an equation or a list of equations.

ExecComp Options
----------------
Expand Down Expand Up @@ -46,6 +46,8 @@ Name Description Valid T
================ ====================================================== ============================================================= ============== ========
value Initial value in user-defined units float, list, tuple, ndarray input & output 1
shape Variable shape, only needed if not an array int, tuple, list, None input & output None
shape_by_conn Determine variable shape based on its connection bool input & output False
copy_shape Determine variable shape based on named variable str input & output None
units Units of variable str, None input & output None
desc Description of variable str input & output ""
res_units Units of residuals str, None output None
Expand All @@ -66,6 +68,19 @@ For more information about these metadata, see the documentation for the argumen
- :meth:`add_output <openmdao.core.component.Component.add_output>`
Registering User Functions
--------------------------
To get your own functions added to the internal namespace of ExecComp so you can call them
from within an ExecComp expression, you can use the :code:`ExecComp.register` function.
.. automethod:: openmdao.components.exec_comp.ExecComp.register
:noindex:
Note that you're required, when registering a new function, to indicate whether that function
is complex safe or not.
ExecComp Example: Simple
------------------------
Expand All @@ -88,7 +103,8 @@ ExecComp Example: Arrays
------------------------
You can declare an ExecComp with arrays for inputs and outputs, but when you do, you must also
pass in a correctly-sized array as an argument to the ExecComp call. This can be the initial value
pass in a correctly-sized array as an argument to the ExecComp call, or set the 'shape' metadata
for that variable as described earlier. If specifying the value directly, it can be the initial value
in the case of unconnected inputs, or just an empty array with the correct size.
.. embed-code::
Expand Down Expand Up @@ -140,4 +156,28 @@ common units that are specified by setting the option.
openmdao.components.tests.test_exec_comp.TestExecComp.test_feature_options
:layout: interleave
ExecComp Example: User function registration
--------------------------------------------
If the function is complex safe, then you don't need to do anything differently than you
would for any other ExecComp.
.. embed-code::
openmdao.components.tests.test_exec_comp.TestFunctionRegistration.featuretest_register_simple
:layout: interleave
ExecComp Example: Complex unsafe user function registration
-----------------------------------------------------------
If the function isn't complex safe, then derivatives involving that function
will have to be computed using finite difference instead of complex step. The way to specify
that `fd` should be used for a given derivative is to call :code:`declare_partials`.
.. embed-code::
openmdao.components.tests.test_exec_comp.TestFunctionRegistration.featuretest_register_simple_unsafe
:layout: interleave
.. tags:: ExecComp, Component, Examples
4 changes: 2 additions & 2 deletions openmdao/drivers/tests/test_pyoptsparse_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def compute_partials(self, inputs, partials):
self.grad_iter_count += 1


class DummyComp(om.ExecComp):
class DummyComp(om.ExplicitComponent):
"""
Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.
"""
Expand All @@ -96,7 +96,7 @@ def setup(self):

self.add_output('c', val=0.0)

self.declare_partials('*', '*')
self.declare_partials('*', '*', method='cs')

def compute(self, inputs, outputs):
"""
Expand Down
4 changes: 2 additions & 2 deletions openmdao/drivers/tests/test_scipy_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def rastrigin(x):
return np.sum(np.square(x) - a * np.cos(2 * np.pi * x)) + a * np.size(x)


class DummyComp(om.ExecComp):
class DummyComp(om.ExplicitComponent):
"""
Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.
"""
Expand All @@ -52,7 +52,7 @@ def setup(self):

self.add_output('c', val=0.0)

self.declare_partials('*', '*')
self.declare_partials('*', '*', method='cs')

def compute(self, inputs, outputs):
"""
Expand Down
7 changes: 4 additions & 3 deletions openmdao/recorders/tests/sqlite_recorder_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ def database_cursor(filename):
con = sqlite3.connect(filename)
cur = con.cursor()

yield cur

con.close()
try:
yield cur
finally:
con.close()


def get_format_version_abs2meta(db_cur):
Expand Down
2 changes: 2 additions & 0 deletions openmdao/recorders/tests/test_sqlite_recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ def test_double_run_driver_option_overwrite(self):
" has_diag_partials : False",
" units : None",
" shape : None",
" shape_by_conn : False",
""
]

Expand Down Expand Up @@ -451,6 +452,7 @@ def test_double_run_model_option_overwrite(self):
" has_diag_partials : False",
" units : None",
" shape : None",
" shape_by_conn : False",
""
]

Expand Down
17 changes: 9 additions & 8 deletions openmdao/utils/coloring.py
Original file line number Diff line number Diff line change
Expand Up @@ -1373,14 +1373,15 @@ def _compute_total_coloring_context(top):
if jac is not None:
jac._randomize = True

yield

for system in top.system_iter(recurse=True, include_self=True):
jac = system._assembled_jac
if jac is None:
jac = system._jacobian
if jac is not None:
jac._randomize = False
try:
yield
finally:
for system in top.system_iter(recurse=True, include_self=True):
jac = system._assembled_jac
if jac is None:
jac = system._jacobian
if jac is not None:
jac._randomize = False


def _get_bool_total_jac(prob, num_full_jacs=_DEF_COMP_SPARSITY_ARGS['num_full_jacs'],
Expand Down
6 changes: 4 additions & 2 deletions openmdao/utils/general_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ def ignore_errors_context(flag=True):
"""
save = ignore_errors()
ignore_errors(flag)
yield
ignore_errors(save)
try:
yield
finally:
ignore_errors(save)


def warn_deprecation(msg):
Expand Down

0 comments on commit 33a0048

Please sign in to comment.