diff --git a/openmdao/core/problem.py b/openmdao/core/problem.py index f04ca9b67a..7b5368bb81 100644 --- a/openmdao/core/problem.py +++ b/openmdao/core/problem.py @@ -116,6 +116,8 @@ class Problem(object): A flag to indicate whether the system options for all the systems have been recorded _metadata : dict Problem level metadata. + _run_counter : int + The number of times run_driver or run_model has been called. """ def __init__(self, model=None, driver=None, comm=None, name=None, **options): @@ -169,6 +171,7 @@ def __init__(self, model=None, driver=None, comm=None, name=None, **options): self._initial_condition_cache = {} self._metadata = None + self._run_counter = 0 self._system_options_recorded = False self._rec_mgr = RecordingManager() @@ -597,6 +600,9 @@ def run_model(self, case_prefix=None, reset_iter_counts=True): self.driver.iter_count = 0 self.model._reset_iter_counts() + if self._setup_already_called: + self._run_counter += 1 + self.final_setup() self.model._clear_iprint() self.model.run_solve_nonlinear() @@ -633,6 +639,9 @@ def run_driver(self, case_prefix=None, reset_iter_counts=True): self.driver.iter_count = 0 self.model._reset_iter_counts() + if self._setup_already_called: + self._run_counter += 1 + self.final_setup() self.model._clear_iprint() return self.driver.run() @@ -831,6 +840,12 @@ def setup(self, check=False, logger=None, mode='auto', force_alloc_complex=False model = self.model comm = self.comm + self._setup_already_called = False + + if hasattr(self, '_metadata') and self._metadata is not None: + if self._metadata['setup_status'] == _SetupStatus.POST_FINAL_SETUP: + self._setup_already_called = True + # PETScVector is required for MPI if comm.size > 1: if PETScVector is None: diff --git a/openmdao/recorders/base_case_reader.py b/openmdao/recorders/base_case_reader.py index 64b608c9b4..1db2defd1d 100644 --- a/openmdao/recorders/base_case_reader.py +++ b/openmdao/recorders/base_case_reader.py @@ -17,7 +17,7 @@ class BaseCaseReader(object): Metadata about the problem, including the system hierachy and connections. solver_metadata : dict The solver options for each solver in the recorded model. - system_options : dict + _system_options : dict Metadata about each system in the recorded model, including options and scaling factors. """ @@ -35,7 +35,7 @@ def __init__(self, filename, pre_load=False): self._format_version = None self.problem_metadata = {} self.solver_metadata = {} - self.system_options = {} + self._system_options = {} @property def system_metadata(self): @@ -45,11 +45,11 @@ def system_metadata(self): Returns ------- dict - reference to the 'system_options' attribute. + reference to the '_system_options' attribute. """ warn_deprecation("The BaseCaseReader.system_metadata attribute is deprecated. " "Use the BaseCaseReader.system_option attribute instead.") - return self.system_options + return self._system_options def get_cases(self, source, recurse=True, flat=False): """ diff --git a/openmdao/recorders/recording_manager.py b/openmdao/recorders/recording_manager.py index 42eea34705..b58a01ddb0 100644 --- a/openmdao/recorders/recording_manager.py +++ b/openmdao/recorders/recording_manager.py @@ -270,4 +270,7 @@ def record_system_options(problem): for recorder in recorders: for sub in problem.model.system_iter(recurse=True, include_self=True): - recorder.record_metadata_system(sub) + if hasattr(problem, "_run_counter"): + recorder.record_metadata_system(sub, problem._run_counter) + else: + recorder.record_metadata_system(sub) diff --git a/openmdao/recorders/sqlite_reader.py b/openmdao/recorders/sqlite_reader.py index 5fdaf47261..822467028a 100644 --- a/openmdao/recorders/sqlite_reader.py +++ b/openmdao/recorders/sqlite_reader.py @@ -262,10 +262,10 @@ def _collect_system_metadata(self, cur): cur.execute("SELECT id, scaling_factors, component_metadata FROM system_metadata") for row in cur: id = row[0] - self.system_options[id] = {} + self._system_options[id] = {} - self.system_options[id]['scaling_factors'] = pickle.loads(row[1]) - self.system_options[id]['component_options'] = pickle.loads(row[2]) + self._system_options[id]['scaling_factors'] = pickle.loads(row[1]) + self._system_options[id]['component_options'] = pickle.loads(row[2]) def _collect_solver_metadata(self, cur): """ @@ -409,6 +409,48 @@ def list_source_vars(self, source, out_stream=_DEFAULT_OUT_STREAM): return dct + def list_model_options(self, run_counter=None, out_stream=_DEFAULT_OUT_STREAM): + """ + List of all model options. + + Parameters + ---------- + run_counter : int or None + Run_driver or run_model iteration to inspect + out_stream : file-like object + Where to send human readable output. Default is sys.stdout. + Set to None to suppress. + + Returns + ------- + dict + {'root':{key val}} + """ + if out_stream: + if out_stream is _DEFAULT_OUT_STREAM: + out_stream = sys.stdout + + dct = {} + + for i in self._system_options: + subsys, num = i.rsplit('_', 1) + + if (run_counter is not None and run_counter == int(num) and subsys == 'root') or \ + (subsys == 'root' and run_counter is None): + + out_stream.write( + 'Run Number: {}\n Subsystem: {}'.format(num, subsys)) + + for j in self._system_options[i]['component_options']: + option = "{0} : {1}".format( + j, self._system_options[i]['component_options'][j]) + out_stream.write('\n {}\n'.format(option)) + + dct[subsys] = {} + dct[subsys][j] = self._system_options[i]['component_options'][j] + + return dct + def list_cases(self, source=None, recurse=True, flat=True, out_stream=_DEFAULT_OUT_STREAM): """ Iterate over Driver, Solver and System cases in order. diff --git a/openmdao/recorders/sqlite_recorder.py b/openmdao/recorders/sqlite_recorder.py index 0482eb98b6..91e0cb8348 100644 --- a/openmdao/recorders/sqlite_recorder.py +++ b/openmdao/recorders/sqlite_recorder.py @@ -622,7 +622,7 @@ def record_viewer_data(self, model_viewer_data, key='Driver'): except sqlite3.IntegrityError: print("Model viewer data has already has already been recorded for %s." % key) - def record_metadata_system(self, recording_requester): + def record_metadata_system(self, recording_requester, run_counter=None): """ Record system metadata. @@ -630,8 +630,13 @@ def record_metadata_system(self, recording_requester): ---------- recording_requester : System The System that would like to record its metadata. + run_counter : int or None + The number of times run_driver or run_model has been called. """ if self.connection: + if run_counter is None: + run_counter = self._counter + scaling_vecs, user_options = self._get_metadata_system(recording_requester) if scaling_vecs is None: @@ -666,7 +671,8 @@ def record_metadata_system(self, recording_requester): with self.connection as c: c.execute("INSERT OR IGNORE INTO system_metadata" "(id, scaling_factors, component_metadata) " - "VALUES(?,?,?)", (path, scaling_factors, pickled_metadata)) + "VALUES(?,?,?)", ("{}_{}".format(path, str(run_counter)), scaling_factors, + pickled_metadata)) def record_metadata_solver(self, recording_requester): """ diff --git a/openmdao/recorders/tests/test_sqlite_reader.py b/openmdao/recorders/tests/test_sqlite_reader.py index 66a1774c6a..8ca31ffb31 100644 --- a/openmdao/recorders/tests/test_sqlite_reader.py +++ b/openmdao/recorders/tests/test_sqlite_reader.py @@ -2005,7 +2005,7 @@ def test_system_options_pickle_fail(self): prob.cleanup() cr = om.CaseReader(self.filename) - subs_options = cr.system_options['subs']['component_options'] + subs_options = cr._system_options['subs_0']['component_options'] # no options should have been recorded for d1 self.assertEqual(len(subs_options._dict), 0) @@ -2041,8 +2041,8 @@ def test_pre_load(self): self.assertEqual(cr._format_version, format_version) - self.assertEqual(set(cr.system_options.keys()), - set(['root'] + [sys.name for sys in prob.model._subsystems_allprocs])) + self.assertEqual(set(cr._system_options.keys()), + set(['root_0'] + [sys.name + '_0' for sys in prob.model._subsystems_allprocs])) self.assertEqual(set(cr.problem_metadata.keys()), { 'tree', 'sys_pathnames_list', 'connections_list', 'variables', 'abs2prom', @@ -2069,8 +2069,8 @@ def test_pre_load(self): self.assertEqual(cr._format_version, format_version) - self.assertEqual(set(cr.system_options.keys()), - set(['root'] + [sys.name for sys in prob.model._subsystems_allprocs])) + self.assertEqual(set(cr._system_options.keys()), + set(['root_0'] + [sys.name + '_0' for sys in prob.model._subsystems_allprocs])) self.assertEqual(set(cr.problem_metadata.keys()), { 'tree', 'sys_pathnames_list', 'connections_list', 'variables', 'abs2prom', diff --git a/openmdao/recorders/tests/test_sqlite_recorder.py b/openmdao/recorders/tests/test_sqlite_recorder.py index b46476e9a4..ecc3c508ca 100644 --- a/openmdao/recorders/tests/test_sqlite_recorder.py +++ b/openmdao/recorders/tests/test_sqlite_recorder.py @@ -2,6 +2,7 @@ import errno import os import unittest +from io import StringIO import numpy as np import sqlite3 @@ -27,7 +28,7 @@ from openmdao.recorders.tests.recorder_test_utils import run_driver from openmdao.utils.assert_utils import assert_near_equal, assert_warning, assert_equal_arrays -from openmdao.utils.general_utils import determine_adder_scaler +from openmdao.utils.general_utils import determine_adder_scaler, remove_whitespace from openmdao.utils.testing_utils import use_tempdirs # check that pyoptsparse is installed. if it is, try to use SLSQP. @@ -289,6 +290,122 @@ def test_simple_driver_recording_pyoptsparse(self): expected_data = ((coordinate, (t0, t1), expected_derivs),) assertDriverDerivDataRecorded(self, expected_data, self.eps) + def test_double_run_driver_option_overwrite(self): + prob = ParaboloidProblem() + + driver = prob.driver = om.ScipyOptimizeDriver(disp=False, tol=1e-9) + + prob.model.add_recorder(self.recorder) + + prob.setup() + prob.set_solver_print(0) + prob.run_driver() + + cr = om.CaseReader(self.filename) + + self.assertTrue(cr._system_options['root_0']['component_options']['assembled_jac_type'], 'csc') + + # New option and re-run of run_driver + prob.model.options['assembled_jac_type'] = 'dense' + prob.setup() + prob.run_driver() + + cr = om.CaseReader(self.filename) + self.assertTrue(cr._system_options['root_1']['component_options']['assembled_jac_type'], 'dense') + + stream = StringIO() + + cr.list_model_options(out_stream=stream) + + text = stream.getvalue().split('\n') + + expected = [ + "Run Number: 0", + " Subsystem: root", + " assembled_jac_type : csc", + "Run Number: 1", + " Subsystem: root", + " assembled_jac_type : dense" + ] + + for i, line in enumerate(expected): + if line and not line.startswith('-'): + self.assertEqual(remove_whitespace(text[i]), remove_whitespace(line)) + + stream = StringIO() + + cr.list_model_options(run_counter=1, out_stream=stream) + + text = stream.getvalue().split('\n') + + expected = [ + "Run Number: 1", + " Subsystem: root", + " assembled_jac_type : dense" + ] + + for i, line in enumerate(expected): + if line and not line.startswith('-'): + self.assertEqual(remove_whitespace(text[i]), remove_whitespace(line)) + + def test_double_run_model_option_overwrite(self): + prob = ParaboloidProblem() + + driver = prob.driver = om.ScipyOptimizeDriver(disp=False, tol=1e-9) + + prob.model.add_recorder(self.recorder) + + prob.setup() + prob.set_solver_print(0) + prob.run_model() + + cr = om.CaseReader(self.filename) + + self.assertTrue(cr._system_options['root_0']['component_options']['assembled_jac_type'], 'csc') + + # New option and re-run of run_driver + prob.model.options['assembled_jac_type'] = 'dense' + prob.setup() + prob.run_model() + + cr = om.CaseReader(self.filename) + self.assertTrue(cr._system_options['root_1']['component_options']['assembled_jac_type'], 'dense') + + stream = StringIO() + + cr.list_model_options(out_stream=stream) + + text = stream.getvalue().split('\n') + + expected = [ + "Run Number: 0", + " Subsystem: root", + " assembled_jac_type : csc", + "Run Number: 1", + " Subsystem: root", + " assembled_jac_type : dense" + ] + + for i, line in enumerate(expected): + if line and not line.startswith('-'): + self.assertEqual(remove_whitespace(text[i]), remove_whitespace(line)) + + stream = StringIO() + + cr.list_model_options(run_counter=1, out_stream=stream) + + text = stream.getvalue().split('\n') + + expected = [ + "Run Number: 1", + " Subsystem: root", + " assembled_jac_type : dense" + ] + + for i, line in enumerate(expected): + if line and not line.startswith('-'): + self.assertEqual(remove_whitespace(text[i]), remove_whitespace(line)) + def test_simple_driver_recording_with_prefix(self): prob = ParaboloidProblem() @@ -299,6 +416,7 @@ def test_simple_driver_recording_with_prefix(self): driver.recording_options['record_derivatives'] = True driver.recording_options['includes'] = ['*'] driver.add_recorder(self.recorder) + prob.model.add_recorder(self.recorder) prob.setup() prob.set_solver_print(0) @@ -482,18 +600,18 @@ def test_system_record_model_metadata(self): cr = om.CaseReader("cases.sql") # Quick check to see that keys and values were recorded - for key in ['root', '_auto_ivc', 'd1', 'd2', 'obj_cmp', 'con_cmp1', 'con_cmp2']: - self.assertTrue(key in cr.system_options.keys()) + for key in ['root_0', '_auto_ivc_0', 'd1_0', 'd2_0', 'obj_cmp_0', 'con_cmp1_0', 'con_cmp2_0']: + self.assertTrue(key in cr._system_options.keys()) - value = cr.system_options['root']['component_options']['assembled_jac_type'] + value = cr._system_options['root_0']['component_options']['assembled_jac_type'] self.assertEqual(value, 'csc') # quick check only. Too much to check exhaustively def test_record_system_options(self): # Regardless what object the case recorder is attached to, system options # should be recorded for all systems in the model - expected_system_options_keys = ['root', '_auto_ivc', 'd1', 'd2', 'obj_cmp', 'con_cmp1', - 'con_cmp2'] + expected_system_options_keys = ['root_0', '_auto_ivc_0', 'd1_0', 'd2_0', 'obj_cmp_0', 'con_cmp1_0', + 'con_cmp2_0'] # Recorder on Driver prob = om.Problem(model=SellarDerivatives()) @@ -506,8 +624,8 @@ def test_record_system_options(self): cr = om.CaseReader("cases_driver.sql") # Quick check to see that keys and values were recorded for key in expected_system_options_keys: - self.assertTrue(key in cr.system_options.keys()) - value = cr.system_options['root']['component_options']['assembled_jac_type'] + self.assertTrue(key in cr._system_options.keys()) + value = cr._system_options['root_0']['component_options']['assembled_jac_type'] self.assertEqual('csc', value) # quick check only. Too much to check exhaustively # Recorder on Problem @@ -521,8 +639,8 @@ def test_record_system_options(self): cr = om.CaseReader("cases_problem.sql") # Quick check to see that keys and values were recorded for key in expected_system_options_keys: - self.assertTrue(key in cr.system_options.keys()) - value = cr.system_options['root']['component_options']['assembled_jac_type'] + self.assertTrue(key in cr._system_options.keys()) + value = cr._system_options['root_0']['component_options']['assembled_jac_type'] self.assertEqual(value, 'csc') # quick check only. Too much to check exhaustively # Recorder on a subsystem @@ -536,8 +654,8 @@ def test_record_system_options(self): cr = om.CaseReader("cases_subsystem.sql") # Quick check to see that keys and values were recorded for key in expected_system_options_keys: - self.assertTrue(key in cr.system_options.keys()) - value = cr.system_options['root']['component_options']['assembled_jac_type'] + self.assertTrue(key in cr._system_options.keys()) + value = cr._system_options['root_0']['component_options']['assembled_jac_type'] self.assertEqual(value, 'csc') # quick check only. Too much to check exhaustively # Recorder on a solver @@ -551,11 +669,11 @@ def test_record_system_options(self): cr = om.CaseReader("cases_solver.sql") # Quick check to see that keys and values were recorded for key in expected_system_options_keys: - self.assertTrue(key in cr.system_options.keys()) - value = cr.system_options['root']['component_options']['assembled_jac_type'] + self.assertTrue(key in cr._system_options.keys()) + value = cr._system_options['root_0']['component_options']['assembled_jac_type'] self.assertEqual(value, 'csc') # quick check only. Too much to check exhaustively - def test_warning_system_options_overwriting(self): + def test_warning__system_options_overwriting(self): prob = ParaboloidProblem() prob.driver = om.ScipyOptimizeDriver(disp=False, tol=1e-9) @@ -1498,14 +1616,14 @@ def test_record_system_recursively(self): # Just make sure all Systems had some metadata recorded assertSystemMetadataIdsRecorded(self, [ - 'root', - '_auto_ivc', - 'mda', - 'mda.d1', - 'mda.d2', - 'obj_cmp', - 'con_cmp1', - 'con_cmp2' + 'root_0', + '_auto_ivc_0', + 'mda_0', + 'mda.d1_0', + 'mda.d2_0', + 'obj_cmp_0', + 'con_cmp1_0', + 'con_cmp2_0' ]) # Make sure all the Systems are recorded @@ -1534,14 +1652,14 @@ def test_record_system_with_prefix(self): # Just make sure all Systems had some metadata recorded assertSystemMetadataIdsRecorded(self, [ - 'root', - '_auto_ivc', - 'mda', - 'mda.d1', - 'mda.d2', - 'obj_cmp', - 'con_cmp1', - 'con_cmp2' + 'root_0', + '_auto_ivc_0', + 'mda_0', + 'mda.d1_0', + 'mda.d2_0', + 'obj_cmp_0', + 'con_cmp1_0', + 'con_cmp2_0' ]) # Make sure all the Systems are recorded at least once @@ -2502,14 +2620,15 @@ def test_feature_recording_system_options(self): cr = om.CaseReader("cases.sql") # metadata for all the systems in the model - metadata = cr.system_options + metadata = cr._system_options self.assertEqual(sorted(metadata.keys()), - sorted(['root', '_auto_ivc', 'd1', 'd2', 'obj_cmp', 'con_cmp1', 'con_cmp2'])) + sorted(['root_0', '_auto_ivc_0', 'd1_0', 'd2_0', 'obj_cmp_0', 'con_cmp1_0', + 'con_cmp2_0'])) # options for system 'd1', with second option excluded - self.assertEqual(metadata['d1']['component_options']['distributed'], False) - self.assertEqual(metadata['d1']['component_options']['options value 1'], 1) + self.assertEqual(metadata['d1_0']['component_options']['distributed'], False) + self.assertEqual(metadata['d1_0']['component_options']['options value 1'], 1) def test_feature_system_recording_options(self): import openmdao.api as om