Skip to content

Commit

Permalink
Merge branch 'master' into latest
Browse files Browse the repository at this point in the history
  • Loading branch information
swryan committed Oct 13, 2023
2 parents 94ac252 + 69958b8 commit 30e44a3
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 24 deletions.
43 changes: 33 additions & 10 deletions openmdao/core/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from openmdao.recorders.recording_manager import RecordingManager
from openmdao.recorders.recording_iteration_stack import Recording
from openmdao.utils.hooks import _setup_hooks
from openmdao.utils.record_util import create_local_meta, check_path
from openmdao.utils.record_util import create_local_meta, check_path, has_match
from openmdao.utils.general_utils import _src_name_iter, _src_or_alias_name
from openmdao.utils.mpi import MPI
from openmdao.utils.options_dictionary import OptionsDictionary
Expand Down Expand Up @@ -468,28 +468,39 @@ def _check_for_invalid_desvar_values(self):
else:
issue_warning(s, category=DriverWarning)

def _get_vars_to_record(self, recording_options):
def _get_vars_to_record(self, obj=None):
"""
Get variables to record based on recording options.
Parameters
----------
recording_options : <OptionsDictionary>
Dictionary with recording options.
obj : Problem or Driver
Parent object which has recording options.
Returns
-------
dict
Dictionary containing lists of variables to record.
"""
if obj is None:
obj = self

recording_options = obj.recording_options

problem = self._problem()
model = problem.model

incl = recording_options['includes']
excl = recording_options['excludes']

# includes and excludes for outputs are specified using promoted names
abs2prom = model._var_allprocs_abs2prom['output']
# includes and excludes for inputs are specified using _absolute_ names
abs2prom_output = model._var_allprocs_abs2prom['output']
abs2prom_inputs = model._var_allprocs_abs2prom['input']

# set of promoted output names and absolute input and residual names
# used for matching includes/excludes
match_names = set()

# 1. If record_outputs is True, get the set of outputs
# 2. Filter those using includes and excludes to get the baseline set of variables to record
Expand All @@ -501,7 +512,8 @@ def _get_vars_to_record(self, recording_options):
myinputs = myoutputs = myresiduals = []

if recording_options['record_outputs']:
myoutputs = [n for n, prom in abs2prom.items() if check_path(prom, incl, excl)]
match_names = match_names | set(abs2prom_output.values())
myoutputs = [n for n, prom in abs2prom_output.items() if check_path(prom, incl, excl)]

model_outs = model._outputs

Expand All @@ -513,8 +525,9 @@ def _get_vars_to_record(self, recording_options):
myresiduals = myoutputs

elif recording_options['record_residuals']:
match_names = match_names | set(model._residuals.keys())
myresiduals = [n for n in model._residuals._abs_iter()
if check_path(abs2prom[n], incl, excl)]
if check_path(abs2prom_output[n], incl, excl)]

myoutputs = set(myoutputs)
if recording_options['record_desvars']:
Expand All @@ -527,8 +540,18 @@ def _get_vars_to_record(self, recording_options):
# inputs (if in options). inputs use _absolute_ names for includes/excludes
if 'record_inputs' in recording_options:
if recording_options['record_inputs']:
myinputs = [n for n in model._var_allprocs_abs2prom['input']
if check_path(n, incl, excl)]
match_names = match_names | set(abs2prom_inputs.keys())
myinputs = [n for n in abs2prom_inputs if check_path(n, incl, excl)]

# check that all exclude/include globs have at least one matching output or input name
for pattern in excl:
if not has_match(pattern, match_names):
issue_warning(f"{obj.msginfo}: No matches for pattern '{pattern}' in "
"recording_options['excludes'].")
for pattern in incl:
if not has_match(pattern, match_names):
issue_warning(f"{obj.msginfo}: No matches for pattern '{pattern}' in "
"recording_options['includes'].")

# sort lists to ensure that vars are iterated over in the same order on all procs
vars2record = {
Expand All @@ -543,7 +566,7 @@ def _setup_recording(self):
"""
Set up case recording.
"""
self._filtered_vars_to_record = self._get_vars_to_record(self.recording_options)
self._filtered_vars_to_record = self._get_vars_to_record()
self._rec_mgr.startup(self, self._problem().comm)

def _get_voi_val(self, name, meta, remote_vois, driver_scaling=True,
Expand Down
5 changes: 3 additions & 2 deletions openmdao/core/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,8 +812,9 @@ def _setup_recording(self):
"""
Set up case recording.
"""
self._filtered_vars_to_record = self.driver._get_vars_to_record(self.recording_options)
self._rec_mgr.startup(self, self.comm)
if self._rec_mgr.has_recorders():
self._filtered_vars_to_record = self.driver._get_vars_to_record(self)
self._rec_mgr.startup(self, self.comm)

def add_recorder(self, recorder):
"""
Expand Down
31 changes: 26 additions & 5 deletions openmdao/core/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from openmdao.vectors.vector import _full_slice
from openmdao.utils.mpi import MPI, multi_proc_exception_check
from openmdao.utils.options_dictionary import OptionsDictionary
from openmdao.utils.record_util import create_local_meta, check_path
from openmdao.utils.record_util import create_local_meta, check_path, has_match
from openmdao.utils.units import is_compatible, unit_conversion, simplify_unit
from openmdao.utils.variable_table import write_var_table
from openmdao.utils.array_utils import evenly_distrib_idxs, shape_to_len
Expand Down Expand Up @@ -1948,16 +1948,27 @@ def _setup_recording(self):
incl = options['includes']
excl = options['excludes']

# includes and excludes for outputs are specified using promoted names
# includes and excludes for inputs are specified using _absolute_ names
abs2prom_output = self._var_allprocs_abs2prom['output']
abs2prom_inputs = self._var_allprocs_abs2prom['input']

# set of promoted output names and absolute input and residual names
# used for matching includes/excludes
match_names = set()

# includes and excludes for inputs are specified using _absolute_ names
# vectors are keyed on absolute name, discretes on relative/promoted name
if options['record_inputs']:
myinputs = sorted([n for n in self._var_abs2prom['input']
match_names.update(abs2prom_inputs.keys())
myinputs = sorted([n for n in abs2prom_inputs
if check_path(n, incl, excl)])

# includes and excludes for outputs are specified using _promoted_ names
# vectors are keyed on absolute name, discretes on relative/promoted name
if options['record_outputs']:
myoutputs = sorted([n for n, prom in self._var_abs2prom['output'].items()
match_names.update(abs2prom_output.values())
myoutputs = sorted([n for n, prom in abs2prom_output.items()
if check_path(prom, incl, excl)])

if self._var_discrete['output']:
Expand All @@ -1969,9 +1980,19 @@ def _setup_recording(self):
myresiduals = myoutputs

elif options['record_residuals']:
abs2prom = self._var_abs2prom['output']
match_names.update(self._residuals.keys())
myresiduals = [n for n in self._residuals._abs_iter()
if check_path(abs2prom[n], incl, excl)]
if check_path(abs2prom_output[n], incl, excl)]

# check that all exclude/include globs have at least one matching output or input name
for pattern in excl:
if not has_match(pattern, match_names):
issue_warning(f"{self.msginfo}: No matches for pattern '{pattern}' in "
"recording_options['excludes'].")
for pattern in incl:
if not has_match(pattern, match_names):
issue_warning(f"{self.msginfo}: No matches for pattern '{pattern}' in "
"recording_options['includes'].")

self._filtered_vars_to_record = {
'input': myinputs,
Expand Down
70 changes: 66 additions & 4 deletions openmdao/core/tests/test_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
import openmdao.api as om
from openmdao.core.driver import Driver
from openmdao.utils.units import convert_units
from openmdao.utils.assert_utils import assert_near_equal, assert_warning
from openmdao.utils.assert_utils import assert_near_equal, assert_warnings
from openmdao.utils.general_utils import printoptions
from openmdao.utils.testing_utils import use_tempdirs
from openmdao.test_suite.components.paraboloid import Paraboloid
from openmdao.test_suite.components.sellar import SellarDerivatives
from openmdao.test_suite.components.simple_comps import DoubleArrayComp, NonSquareArrayComp
from openmdao.utils.om_warnings import OMDeprecationWarning
from openmdao.utils.om_warnings import OpenMDAOWarning

from openmdao.utils.mpi import MPI

Expand Down Expand Up @@ -650,11 +650,11 @@ def test_units_with_scaling(self):

totals = prob.check_totals(out_stream=None, driver_scaling=True)

for key, val in totals.items():
for val in totals.values():
assert_near_equal(val['rel error'][0], 0.0, 1e-6)

cr = om.CaseReader("cases.sql")
cases = cr.list_cases('driver')
cases = cr.list_cases('driver', out_stream=None)
case = cr.get_case(cases[0])

dv = case.get_design_vars()
Expand Down Expand Up @@ -814,6 +814,68 @@ def test_get_desvar_subsystem(self):
assert_near_equal(totals['sub.comp.f_xy', 'sub.x']['J_fd'], [[1.44e2]], 1e-5)
assert_near_equal(totals['sub.comp.f_xy', 'sub.y']['J_fd'], [[1.58e2]], 1e-5)

def test_get_vars_to_record(self):
recorder = om.SqliteRecorder("cases.sql")

prob = om.Problem(name='ABCD')
prob.add_recorder(recorder)

prob.model.add_subsystem('mag', om.ExecComp('y=x**2'),
promotes_inputs=['*'], promotes_outputs=['*'])

prob.model.add_subsystem('sum', om.ExecComp('z=sum(y)'),
promotes_inputs=['*'], promotes_outputs=['*'])


prob.driver = om.ScipyOptimizeDriver()
prob.driver.add_recorder(recorder)


prob.recording_options['record_inputs'] = True
prob.recording_options['excludes'] = ['*x*', '*aa*']
prob.recording_options['includes'] = ['*z*', '*bb*']

prob.driver.recording_options['record_inputs'] = True
prob.driver.recording_options['excludes'] = ['*y*', '*cc*']
prob.driver.recording_options['includes'] = ['*x*', '*dd*']

prob.setup()

expected_warnings = (
(OpenMDAOWarning, "Problem ABCD: No matches for pattern '*aa*' in recording_options['excludes']."),
(OpenMDAOWarning, "Problem ABCD: No matches for pattern '*bb*' in recording_options['includes']."),
(OpenMDAOWarning, "ScipyOptimizeDriver: No matches for pattern '*cc*' in recording_options['excludes']."),
(OpenMDAOWarning, "ScipyOptimizeDriver: No matches for pattern '*dd*' in recording_options['includes'].")
)

with assert_warnings(expected_warnings):
prob.final_setup()

def test_record_residuals_includes_excludes(self):
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarProblem

prob = SellarProblem()

recorder = om.SqliteRecorder("rec_resids.sql")
prob.driver.add_recorder(recorder)

# just want record residuals
prob.driver.recording_options['record_inputs'] = False
prob.driver.recording_options['record_outputs'] = False
prob.driver.recording_options['record_residuals'] = True
prob.driver.recording_options['excludes'] = ['*y1*', '*x'] # x is an input, which we are not recording
prob.driver.recording_options['includes'] = ['*con*', '*z'] # z is an input, which we are not recording

prob.setup()

expected_warnings = (
(om.OpenMDAOWarning, "Driver: No matches for pattern '*x' in recording_options['excludes']."),
(om.OpenMDAOWarning, "Driver: No matches for pattern '*z' in recording_options['includes']."),
)

with assert_warnings(expected_warnings):
prob.final_setup()

@use_tempdirs
class TestCheckRelevance(unittest.TestCase):
Expand Down
72 changes: 69 additions & 3 deletions openmdao/core/tests/test_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import numpy as np

from openmdao.api import Problem, Group, IndepVarComp, ExecComp, ExplicitComponent
from openmdao.utils.assert_utils import assert_near_equal, assert_warning
from openmdao.utils.assert_utils import assert_near_equal, assert_warning, assert_warnings
from openmdao.utils.testing_utils import use_tempdirs


class TestSystem(unittest.TestCase):
Expand Down Expand Up @@ -220,7 +221,6 @@ def test_list_inputs_outputs_invalid_return_format(self):

self.assertEqual(str(cm.exception), msg)


def test_list_inputs_output_with_includes_excludes(self):
from openmdao.test_suite.scripts.circuit_analysis import Circuit

Expand All @@ -235,6 +235,7 @@ def test_list_inputs_output_with_includes_excludes(self):
model.connect('ground.V', 'circuit.Vg')

p.setup()
p.set_solver_print(-1)
p.run_model()

# Inputs with no includes or excludes
Expand Down Expand Up @@ -409,7 +410,7 @@ def compute(self, inputs, outputs):
"any `set_val` calls.")

with assert_warning(UserWarning, msg):
prob.model.list_inputs(units=True, prom_name=True)
prob.model.list_inputs(units=True, prom_name=True, out_stream=None)

def test_get_io_metadata(self):
from openmdao.test_suite.components.sellar_feature import SellarMDA
Expand Down Expand Up @@ -602,6 +603,71 @@ def setup(self):
expected = ((4 * 3 + 5) * 3 + 5) * 3 + 5.
assert_near_equal(expected, c3y)

@use_tempdirs
def test_recording_options_includes_excludes(self):
import openmdao.api as om

prob = om.Problem()

mag = prob.model.add_subsystem('mag', om.ExecComp('y=x**2'),
promotes_inputs=['*'], promotes_outputs=['*'])

sum = prob.model.add_subsystem('sum', om.ExecComp('z=sum(y)'),
promotes_inputs=['*'], promotes_outputs=['*'])

recorder = om.SqliteRecorder("rec_options.sql")
mag.add_recorder(recorder)
sum.add_recorder(recorder)

mag.recording_options['record_inputs'] = True
mag.recording_options['excludes'] = ['*x*', '*aa*']
mag.recording_options['includes'] = ['*y*', '*bb*']

sum.recording_options['record_inputs'] = True
sum.recording_options['excludes'] = ['*y*', '*cc*']
sum.recording_options['includes'] = ['*z*', '*dd*']

prob.setup()

expected_warnings = (
(om.OpenMDAOWarning, "'mag' <class ExecComp>: No matches for pattern '*aa*' in recording_options['excludes']."),
(om.OpenMDAOWarning, "'mag' <class ExecComp>: No matches for pattern '*bb*' in recording_options['includes']."),
(om.OpenMDAOWarning, "'sum' <class ExecComp>: No matches for pattern '*cc*' in recording_options['excludes']."),
(om.OpenMDAOWarning, "'sum' <class ExecComp>: No matches for pattern '*dd*' in recording_options['includes'].")
)

with assert_warnings(expected_warnings):
prob.final_setup()

@use_tempdirs
def test_record_residuals_includes_excludes(self):
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarProblem

prob = SellarProblem()

model = prob.model

recorder = om.SqliteRecorder("rec_resids.sql")
model.add_recorder(recorder)

# just want record residuals
model.recording_options['record_inputs'] = False
model.recording_options['record_outputs'] = False
model.recording_options['record_residuals'] = True
model.recording_options['excludes'] = ['*y1*', '*x'] # x is an input, which we are not recording
model.recording_options['includes'] = ['*con*', '*z'] # z is an input, which we are not recording

prob.setup()

expected_warnings = (
(om.OpenMDAOWarning, "<model> <class SellarDerivatives>: No matches for pattern '*x' in recording_options['excludes']."),
(om.OpenMDAOWarning, "<model> <class SellarDerivatives>: No matches for pattern '*z' in recording_options['includes']."),
)

with assert_warnings(expected_warnings):
prob.final_setup()


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

0 comments on commit 30e44a3

Please sign in to comment.