Skip to content

Commit

Permalink
Merge 98cc2e0 into dbd3806
Browse files Browse the repository at this point in the history
  • Loading branch information
hschilling committed Jan 19, 2018
2 parents dbd3806 + 98cc2e0 commit e34ec7c
Show file tree
Hide file tree
Showing 9 changed files with 467 additions and 14 deletions.
149 changes: 147 additions & 2 deletions openmdao/core/driver.py
Expand Up @@ -13,15 +13,42 @@
from openmdao.recorders.recording_iteration_stack import Recording
from openmdao.utils.record_util import create_local_meta, check_path
from openmdao.utils.mpi import MPI
from openmdao.recorders.recording_iteration_stack import get_formatted_iteration_coordinate
from openmdao.utils.options_dictionary import OptionsDictionary


def _is_debug_print_opts_valid(opts):
"""
Check validity of debug_print option for Driver.
Parameters
----------
opts : list
The value of the debug_print option set by the user.
Returns
-------
bool
True if the option is valid. Otherwise, False.
"""
if not isinstance(opts, list):
return False
_valid_opts = ['desvars', 'nl_cons', 'ln_cons', 'objs']
for opt in opts:
if opt not in _valid_opts:
return False
return True


class Driver(object):
"""
Top-level container for the systems and drivers.
Options
-------
options['debug_print'] : list of strings([])
Indicates what variables to print at each iteration. The valid options are:
'desvars','ln_cons','nl_cons',and 'objs'.
recording_options['record_metadata'] : bool(True)
Tells recorder whether to record variable attribute metadata.
recording_options['record_desvars'] : bool(True)
Expand All @@ -37,6 +64,7 @@ class Driver(object):
recording_options['excludes'] : list of strings('')
Patterns for variables to exclude in recording (processed after includes).
Attributes
----------
fail : bool
Expand All @@ -49,6 +77,8 @@ class Driver(object):
Dictionary with general pyoptsparse options.
recording_options : <OptionsDictionary>
Dictionary with driver recording options.
debug_print : <OptionsDictionary>
Dictionary with debugging printing options.
cite : str
Listing of relevant citataions that should be referenced when
publishing work that uses this class.
Expand Down Expand Up @@ -108,6 +138,13 @@ def __init__(self):
self.options = OptionsDictionary()
self.recording_options = OptionsDictionary()

###########################
self.options.declare('debug_print', types=list, is_valid=_is_debug_print_opts_valid,
desc="List of what type of Driver variables to print at each "
"iteration. Valid items in list are 'desvars','ln_cons',"
"'nl_cons','objs'",
default=[])

###########################
self.recording_options.declare('record_metadata', types=bool, desc='Record metadata',
default=True)
Expand Down Expand Up @@ -144,6 +181,19 @@ def __init__(self):
self.supports.declare('active_set', types=bool, default=False)
self.supports.declare('simultaneous_derivatives', types=bool, default=False)

# Debug printing.
self.debug_print = OptionsDictionary()
self.debug_print.declare('debug_print', types=bool, default=False,
desc='Overall option to turn on Driver debug printing')
self.debug_print.declare('debug_print_desvars', types=bool, default=False,
desc='Print design variables')
self.debug_print.declare('debug_print_nl_con', types=bool, default=False,
desc='Print nonlinear constraints')
self.debug_print.declare('debug_print_ln_con', types=bool, default=False,
desc='Print linear constraints')
self.debug_print.declare('debug_print_objective', types=bool, default=False,
desc='Print objectives')

self.iter_count = 0
self.metadata = None
self._model_viewer_data = None
Expand Down Expand Up @@ -554,7 +604,7 @@ def run(self):
boolean
Failure flag; True if failed to converge, False is successful.
"""
with Recording(self._get_name(), self.iter_count, self) as rec:
with RecordingDebugging(self._get_name(), self.iter_count, self) as rec:
failure_flag = self._problem.model._solve_nonlinear()

self.iter_count += 1
Expand Down Expand Up @@ -812,4 +862,99 @@ def _setup_simul_coloring(self, mode='fwd'):
# convert name from promoted to absolute and replace dictionary key
del dvdict[dv]
dv = prom2abs[dv][0]
dvdict[dv] = col_dict
dvdict[dv] = col_dict

def _pre_run_model_debug_print(self):
"""
Optionally print some debugging information before the model runs.
"""
if not self.options['debug_print']:
return

if not MPI or MPI.COMM_WORLD.rank == 0:
header = 'Driver debug print for iter coord: {}'.format(
get_formatted_iteration_coordinate())
print(header)
print(len(header) * '-')

if 'desvars' in self.options['debug_print']:
desvar_vals = self.get_design_var_values()
if not MPI or MPI.COMM_WORLD.rank == 0:
print("Design Vars")
if desvar_vals:
for name, value in iteritems(desvar_vals):
print("{}: {}".format(name, repr(value)))
else:
print("None")
print()

def _post_run_model_debug_print(self):
"""
Optionally print some debugging information after the model runs.
"""
if 'nl_cons' in self.options['debug_print']:
cons = self.get_constraint_values(lintype='nonlinear')
if not MPI or MPI.COMM_WORLD.rank == 0:
print("Nonlinear constraints")
if cons:
for name, value in iteritems(cons):
print("{}: {}".format(name, repr(value)))
else:
print("None")
print()

if 'ln_cons' in self.options['debug_print']:
cons = self.get_constraint_values(lintype='linear')
if not MPI or MPI.COMM_WORLD.rank == 0:
print("Linear constraints")
if cons:
for name, value in iteritems(cons):
print("{}: {}".format(name, repr(value)))
else:
print("None")
print()

if 'objs' in self.options['debug_print']:
objs = self.get_objective_values()
if not MPI or MPI.COMM_WORLD.rank == 0:
print("Objectives")
if objs:
for name, value in iteritems(objs):
print("{}: {}".format(name, repr(value)))
else:
print("None")
print()


class RecordingDebugging(Recording):
"""
A class that acts as a context manager.
Handles doing the case recording and also the Driver
debugging printing.
"""

def __enter__(self):
"""
Do things before the code inside the 'with RecordingDebugging' block.
Returns
-------
self : object
self
"""
super(RecordingDebugging, self).__enter__()
self.recording_requester._pre_run_model_debug_print()
return self

def __exit__(self, *args):
"""
Do things after the code inside the 'with RecordingDebugging' block.
Parameters
----------
*args : array
Solver recording requires extra args.
"""
self.recording_requester._post_run_model_debug_print()
super(RecordingDebugging, self).__exit__()
102 changes: 102 additions & 0 deletions openmdao/core/tests/test_distrib_driver_debug_print_options.py
@@ -0,0 +1,102 @@
import os
import sys
import unittest
from six import StringIO

from openmdao.api import Problem, Group, IndepVarComp, ExecComp, ParallelGroup
from openmdao.utils.general_utils import set_pyoptsparse_opt
from openmdao.utils.mpi import MPI

try:
from openmdao.vectors.petsc_vector import PETScVector
except ImportError:
PETScVector = None

@unittest.skipIf(PETScVector is None or os.environ.get("TRAVIS"),
"PETSc is required." if PETScVector is None
else "Unreliable on Travis CI.")
class DistributedDriverDebugPrintOptionsTest(unittest.TestCase):

N_PROCS = 2

def test_distributed_driver_debug_print_options(self):

# check that pyoptsparse is installed. if it is, try to use SLSQP.
OPT, OPTIMIZER = set_pyoptsparse_opt('SLSQP')

if OPTIMIZER:
from openmdao.drivers.pyoptsparse_driver import pyOptSparseDriver

class Mygroup(Group):

def setup(self):
self.add_subsystem('indep_var_comp', IndepVarComp('x'), promotes=['*'])
self.add_subsystem('Cy', ExecComp('y=2*x'), promotes=['*'])
self.add_subsystem('Cc', ExecComp('c=x+2'), promotes=['*'])

self.add_design_var('x')
self.add_constraint('c', lower=-3.)

prob = Problem()

prob.model.add_subsystem('par', ParallelGroup())

prob.model.par.add_subsystem('G1', Mygroup())
prob.model.par.add_subsystem('G2', Mygroup())

prob.model.add_subsystem('Obj', ExecComp('obj=y1+y2'))

prob.model.connect('par.G1.y', 'Obj.y1')
prob.model.connect('par.G2.y', 'Obj.y2')

prob.model.add_objective('Obj.obj')

prob.driver = pyOptSparseDriver()
prob.driver.options['optimizer'] = 'SLSQP'
prob.driver.options['print_results'] = False

prob.driver.options['debug_print'] = ['desvars','ln_cons','nl_cons','objs']

prob.setup(vector_class=PETScVector)

stdout = sys.stdout
strout = StringIO()
sys.stdout = strout
sys.stdout = strout
try:
prob.run_driver()
finally:
sys.stdout = stdout

output = strout.getvalue().split('\n')
if MPI.COMM_WORLD.rank == 0:
# Just make sure we have more than one. Not sure we will always have the same number
# of iterations
self.assertTrue(output.count("Design Vars") > 1,
"Should be more than one design vars header printed")
self.assertTrue(output.count("Nonlinear constraints") > 1,
"Should be more than one nonlinear constraint header printed")
self.assertTrue(output.count("Linear constraints") > 1,
"Should be more than one linear constraint header printed")
self.assertTrue(output.count("Objectives") > 1,
"Should be more than one objective header printed")

self.assertTrue(len([s for s in output if s.startswith('par.G1.indep_var_comp.x')]) > 1,
"Should be more than one par.G1.indep_var_comp.x printed")
self.assertTrue(len([s for s in output if s.startswith('par.G2.indep_var_comp.x')]) > 1,
"Should be more than one par.G2.indep_var_comp.x printed")
self.assertTrue(len([s for s in output if s.startswith('par.G1.Cc.c')]) > 1,
"Should be more than one par.G1.Cc.c printed")
self.assertTrue(len([s for s in output if s.startswith('par.G2.Cc.c')]) > 1,
"Should be more than one par.G2.Cc.c printed")
self.assertTrue(len([s for s in output if s.startswith('None')]) > 1,
"Should be more than one None printed")
self.assertTrue(len([s for s in output if s.startswith('Obj.obj')]) > 1,
"Should be more than one Obj.obj printed")
else:
self.assertEqual(output, [''])


if __name__ == "__main__":
from openmdao.utils.mpi import mpirun_tests
mpirun_tests()
49 changes: 49 additions & 0 deletions openmdao/core/tests/test_driver.py
Expand Up @@ -2,6 +2,8 @@

from __future__ import print_function

from six import StringIO
import sys
import unittest

import numpy as np
Expand Down Expand Up @@ -190,5 +192,52 @@ def test_vector_scaled_derivs_diff_sizes(self):
con_base = np.array([ (prob['comp.y2'][0]-1.2)/(2.0-1.2)])
assert_rel_error(self, con['comp.y2'], con_base, 1.0e-3)

def test_debug_print_option(self):

prob = Problem()
prob.model = model = SellarDerivatives()

model.add_design_var('z')
model.add_objective('obj')
model.add_constraint('con1', lower=0)
model.add_constraint('con2', lower=0, linear=True)
prob.set_solver_print(level=0)

prob.setup(check=False)

# Make sure nothing prints if debug_print is the default of empty list
stdout = sys.stdout
strout = StringIO()
sys.stdout = strout
try:
prob.run_driver()
finally:
sys.stdout = stdout
output = strout.getvalue().split('\n')
self.assertEqual(output, [''])

# Make sure everything prints when all options are on
prob.driver.options['debug_print'] = ['desvars','ln_cons','nl_cons','objs']
stdout = sys.stdout
strout = StringIO()
sys.stdout = strout
try:
prob.run_driver()
finally:
sys.stdout = stdout
output = strout.getvalue().split('\n')
self.assertEqual(output.count("Driver debug print for iter coord: rank0:Driver|1"), 1)
self.assertEqual(output.count("Design Vars"), 1)
self.assertEqual(output.count("Nonlinear constraints"), 1)
self.assertEqual(output.count("Linear constraints"), 1)
self.assertEqual(output.count("Objectives"), 1)

# Check to make sure an invalid debug_print option raises an exception
with self.assertRaises(ValueError) as context:
prob.driver.options['debug_print'] = ['bad_option']
self.assertEqual(str(context.exception),
"Function is_valid returns False for debug_print.")


if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,20 @@
.. _debugging-drivers:

*********************
Driver Debug Printing
*********************

When working with a model, it may sometimes be helpful to print out the design variables, constraints, and
objectives as the `Driver` iterates. OpenMDAO provides options on the `Driver` to let you do that.

Usage
-----

This example shows how to use the `Driver` debug printing options. The `debug_print` option is a list of strings.
The only valid strings are 'desvars','ln_cons','nl_cons',and 'objs'.


.. embed-test::
openmdao.drivers.tests.test_scipy_optimizer.TestScipyOptimizerFeatures.test_debug_print_option


Expand Up @@ -9,4 +9,5 @@ Debugging
om_command.rst
newton_solver_not_converging.rst
listing_variables.rst
debugging_drivers.rst
profiling/index.rst

0 comments on commit e34ec7c

Please sign in to comment.