diff --git a/openmdao/core/constants.py b/openmdao/core/constants.py index 142b34c828..5693bacb93 100644 --- a/openmdao/core/constants.py +++ b/openmdao/core/constants.py @@ -128,3 +128,11 @@ def __deepcopy__(self, memo): # Use this as a special value to be able to tell if the caller set a value for the optional # out_stream argument. We run into problems running testflo if we use a default of sys.stdout. _DEFAULT_OUT_STREAM = _ReprClass("DEFAULT_OUT_STREAM") + +# Used in pyOptSparseDriver and Problem for coloring dir. The default is the reports directory +# which includes the directory +# named after the Problem name. But when the declare method in that class is called, the driver +# does not have a reference to the Problem so can't get the name. This serves as a flag that +# the default directory in the reports directory is what is wanted. Then in the run method, +# the actual default directory is used +_DEFAULT_REPORTS_DIR = _ReprClass("DEFAULT_REPORTS_DIR") diff --git a/openmdao/core/problem.py b/openmdao/core/problem.py index 9904f49ebc..206aa1ad24 100644 --- a/openmdao/core/problem.py +++ b/openmdao/core/problem.py @@ -1,6 +1,7 @@ """Define the Problem class and a FakeComm class for non-MPI users.""" import __main__ +import shutil import sys import pprint @@ -348,6 +349,13 @@ def __init__(self, model=None, driver=None, comm=None, name=None, reports=_UNDEF desc='Patterns for vars to exclude in recording ' '(processed post-includes). Uses fnmatch wildcards') + # Start a run by deleting any existing reports so that the files + # that are in that directory are all from this run and not a previous run + reports_dirpath = pathlib.Path(get_reports_dir()).joinpath(f'{self._name}') + if self.comm.rank == 0: + if os.path.isdir(reports_dirpath): + shutil.rmtree(reports_dirpath) + # register hooks for any reports activate_reports(self._reports, self) diff --git a/openmdao/core/tests/test_partial_color.py b/openmdao/core/tests/test_partial_color.py index 9782be3404..41cf13f7f0 100644 --- a/openmdao/core/tests/test_partial_color.py +++ b/openmdao/core/tests/test_partial_color.py @@ -1867,4 +1867,4 @@ def test_simple_partials_explicit(self, method): if __name__ == '__main__': - unitest.main() + unittest.main() diff --git a/openmdao/docs/openmdao_book/advanced_user_guide/analysis_errors/analysis_error.ipynb b/openmdao/docs/openmdao_book/advanced_user_guide/analysis_errors/analysis_error.ipynb index 9ab5c23f1d..d143b1b1ab 100644 --- a/openmdao/docs/openmdao_book/advanced_user_guide/analysis_errors/analysis_error.ipynb +++ b/openmdao/docs/openmdao_book/advanced_user_guide/analysis_errors/analysis_error.ipynb @@ -102,6 +102,7 @@ " if optimizer == 'IPOPT':\n", " driver.opt_settings['file_print_level'] = 5\n", " driver.options['print_results'] = False\n", + " driver.options['output_dir'] = None # will put the optimizer output file in the current directory\n", "\n", " # setup problem & initialize values\n", " prob = om.Problem(model, driver)\n", @@ -424,7 +425,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.11.6" } }, "nbformat": 4, diff --git a/openmdao/docs/openmdao_book/features/core_features/adding_desvars_cons_objs/adding_constraint.ipynb b/openmdao/docs/openmdao_book/features/core_features/adding_desvars_cons_objs/adding_constraint.ipynb index f2d57e7011..1d7fa5c19e 100644 --- a/openmdao/docs/openmdao_book/features/core_features/adding_desvars_cons_objs/adding_constraint.ipynb +++ b/openmdao/docs/openmdao_book/features/core_features/adding_desvars_cons_objs/adding_constraint.ipynb @@ -260,7 +260,6 @@ }, "outputs": [], "source": [ - "%%px\n", "import openmdao.api as om\n", "om.display_source(\"openmdao.test_suite.components.paraboloid_distributed.DistParabFeature\")" ] diff --git a/openmdao/drivers/pyoptsparse_driver.py b/openmdao/drivers/pyoptsparse_driver.py index c983a97111..0f87c1f978 100644 --- a/openmdao/drivers/pyoptsparse_driver.py +++ b/openmdao/drivers/pyoptsparse_driver.py @@ -5,7 +5,7 @@ formulating and solving nonlinear constrained optimization problems, with additional MPI capability. """ - +import pathlib import sys import json import signal @@ -22,12 +22,13 @@ except Exception as err: pyoptsparse = err -from openmdao.core.constants import INT_DTYPE +from openmdao.core.constants import INT_DTYPE, _DEFAULT_REPORTS_DIR, _ReprClass from openmdao.core.analysis_error import AnalysisError from openmdao.core.driver import Driver, RecordingDebugging from openmdao.utils.class_util import WeakMethodWrapper from openmdao.utils.mpi import FakeComm, MPI from openmdao.utils.om_warnings import issue_warning, warn_deprecation +from openmdao.utils.reports_system import get_reports_dir # what version of pyoptspare are we working with if pyoptsparse and hasattr(pyoptsparse, '__version__'): @@ -268,6 +269,10 @@ def _declare_options(self): self.options.declare('hotstart_file', types=str, default=None, allow_none=True, desc='File location of a pyopt_sparse optimization history to use ' 'to hot start the optimization. Default is None.') + self.options.declare('output_dir', types=(str, _ReprClass), default=_DEFAULT_REPORTS_DIR, + allow_none=True, + desc='Directory location of pyopt_sparse output files.' + 'Default is ./reports_directory/problem_name.') @property def hist_file(self): @@ -374,6 +379,7 @@ def run(self): self._quantities = [] optimizer = self.options['optimizer'] + self._fill_NANs = not respects_fail_flag[self.options['optimizer']] self._check_for_missing_objective() @@ -564,6 +570,32 @@ def run(self): msg = "Optimizer %s is not available in this installation." % optimizer raise ImportError(msg) + # Need to tell optimizer where to put its .out files + if self.options['output_dir'] is None: + output_dir = "." + elif self.options['output_dir'] == _DEFAULT_REPORTS_DIR: + problem = self._problem() + default_output_dir = pathlib.Path(get_reports_dir()).joinpath(problem._name) + pathlib.Path(default_output_dir).mkdir(parents=True, exist_ok=True) + output_dir = str(default_output_dir) + else: + output_dir = self.options['output_dir'] + + optimizers_and_output_files = { + # ALPSO uses a single option `filename` to determine name of both output files + 'ALPSO': [('filename', 'ALPSO.out')], + 'CONMIN': [('IFILE', 'CONMIN.out')], + 'IPOPT': [('output_file', 'IPOPT.out')], + 'PSQP': [('IFILE', 'PSQP.out')], + 'SLSQP': [('IFILE', 'SLSQP.out')], + 'SNOPT': [('Print file', 'SNOPT_print.out'), ('Summary file', 'SNOPT_summary.out')] + } + + if optimizer in optimizers_and_output_files: + for opt_setting_name, output_file_name in optimizers_and_output_files[optimizer]: + if self.opt_settings.get(opt_setting_name) is None: + self.opt_settings[opt_setting_name] = f'{output_dir}/{output_file_name}' + # Process any default optimizer-specific settings. if optimizer in DEFAULT_OPT_SETTINGS: for name, value in DEFAULT_OPT_SETTINGS[optimizer].items(): diff --git a/openmdao/drivers/tests/test_analysis_errors.py b/openmdao/drivers/tests/test_analysis_errors.py index f782eda4c9..569dcf83c6 100644 --- a/openmdao/drivers/tests/test_analysis_errors.py +++ b/openmdao/drivers/tests/test_analysis_errors.py @@ -111,6 +111,8 @@ def setup_problem(self, optimizer, func=None): if optimizer in self.opt_settings: driver.opt_settings = self.opt_settings[optimizer] driver.options['print_results'] = False + driver.options['output_dir'] = None # So output goes in current working directory + # that was the location when this test was written # setup problem & initialize values prob = om.Problem(model, driver) diff --git a/openmdao/drivers/tests/test_pyoptsparse_driver.py b/openmdao/drivers/tests/test_pyoptsparse_driver.py index 17e8cfdc13..89c3f6dfb8 100644 --- a/openmdao/drivers/tests/test_pyoptsparse_driver.py +++ b/openmdao/drivers/tests/test_pyoptsparse_driver.py @@ -1,6 +1,7 @@ """ Unit tests for the Pyoptsparse Driver.""" import copy +import pathlib import unittest import os.path @@ -16,6 +17,7 @@ from openmdao.test_suite.components.sellar import SellarDerivativesGrouped from openmdao.utils.assert_utils import assert_near_equal, assert_warning, assert_check_totals from openmdao.utils.general_utils import set_pyoptsparse_opt, run_driver +from openmdao.utils.reports_system import get_reports_dir from openmdao.utils.testing_utils import use_tempdirs, require_pyoptsparse from openmdao.utils.om_warnings import OMDeprecationWarning from openmdao.utils.mpi import MPI @@ -3288,6 +3290,110 @@ def test_resize(self): p.run_driver() p.compute_totals() +@unittest.skipIf(OPT is None or OPTIMIZER is None, "only run if pyoptsparse is installed.") +@use_tempdirs +class TestPyoptSparseOutputFiles(unittest.TestCase): + # dict of optimizers with the values being a list of tuples of ( option_name, output_file_name ) + optimizers_and_output_files = { + # ALPSO uses a single option `filename` to determine name of both output files + 'ALPSO': [('filename', 'ALPSO_print.out'), ('filename', 'ALPSO_summary.out')], + 'CONMIN': [('IFILE', 'CONMIN.out')], + 'IPOPT': [('output_file', 'IPOPT.out')], + 'PSQP': [('IFILE', 'PSQP.out')], + 'SLSQP': [('IFILE', 'SLSQP.out')], + 'SNOPT': [('Print file', 'SNOPT_print.out'), ('Summary file', 'SNOPT_summary.out')] + } + + def createParaboloidProblem(self): + prob = om.Problem() + model = prob.model + model.add_subsystem('p1', om.IndepVarComp('x', 50.0), promotes=['*']) + model.add_subsystem('p2', om.IndepVarComp('y', 50.0), promotes=['*']) + model.add_subsystem('comp', Paraboloid(), promotes=['*']) + model.add_subsystem('con', om.ExecComp('c = - x + y'), promotes=['*']) + prob.set_solver_print(level=0) + model.add_design_var('x', lower=-50.0, upper=50.0) + model.add_design_var('y', lower=-50.0, upper=50.0) + model.add_objective('f_xy') + model.add_constraint('c', upper=-15.0) + prob.setup() + return prob + + def run_and_test_default_output_dir(self, optimizer, output_file_names): + # default is to put the files in the reports directory under the problem name folder + # output_file_names is a list of tuples of setting name and output file name + prob = self.createParaboloidProblem() + prob.driver = pyOptSparseDriver(optimizer=optimizer, print_results=False) + + if optimizer == 'ALPSO': + prob.driver.opt_settings['fileout'] = 3 # need this to be 3 to get the output files + + prob.run_driver() + default_output_dir = pathlib.Path(get_reports_dir()).joinpath(prob._name) + for opt_setting_name, output_file_name in output_file_names: + output_file = default_output_dir.joinpath(output_file_name) + self.assertTrue(output_file.is_file(), + f"{output_file_name} output file not found at {str(output_file)}") + + def run_and_test_previous_behavior_output_dir(self, optimizer, output_file_names): + # Previous behavior is to put files in current working directory. Indicated with None + # output_file_names is a list of tuples of setting name and output file name + prob = self.createParaboloidProblem() + prob.driver = pyOptSparseDriver(optimizer=optimizer, print_results=False) + + if optimizer == 'ALPSO': + prob.driver.opt_settings['fileout'] = 3 + + prob.driver.options['output_dir'] = None + + prob.run_driver() + for opt_setting_name, output_file_name in output_file_names: + output_file = pathlib.Path(output_file_name) + self.assertTrue(output_file.is_file(), + f"{output_file_name} output file not found at {str(output_file)}") + + def run_and_test_user_set_output_dir(self, optimizer, output_file_names): + # output_file_names is a list of tuples of setting name and output file name + + user_directory_name = 'user_reports_dir' + pathlib.Path(user_directory_name).mkdir(exist_ok=True) + + prob = self.createParaboloidProblem() + prob.driver = pyOptSparseDriver(optimizer=optimizer, print_results=False) + + if optimizer == 'ALPSO': + prob.driver.opt_settings['fileout'] = 3 + + + prob.driver.options['output_dir'] = user_directory_name + + prob.run_driver() + + for opt_setting_name, output_file_name in output_file_names: + output_file_path = pathlib.Path(user_directory_name).joinpath(output_file_name) + self.assertTrue(output_file_path.is_file(), + f"{str(output_file_path)} output file not found at {str(output_file_path)}") + + def test_default_output_dir(self): + for optimizer, output_files in self.optimizers_and_output_files.items(): + _, loc_opt = set_pyoptsparse_opt(optimizer) + if loc_opt == optimizer: # Only do optimizers that are installed + self.run_and_test_default_output_dir(optimizer, output_files) + + def test_previous_behavior_output_dir(self): + for optimizer, output_files in self.optimizers_and_output_files.items(): + _, loc_opt = set_pyoptsparse_opt(optimizer) + if loc_opt == optimizer: # Only do optimizers that are installed + self.run_and_test_previous_behavior_output_dir(optimizer, output_files) + + def test_user_set_output_dir(self): + for optimizer, output_files in self.optimizers_and_output_files.items(): + _, loc_opt = set_pyoptsparse_opt(optimizer) + if loc_opt == optimizer: # Only do optimizers that are installed + self.run_and_test_user_set_output_dir(optimizer, output_files) + + + if __name__ == "__main__": unittest.main()