Solver Debug Printing

Solver Debug Printing#

When working with a model and you have a situation where a nonlinear solver is not converging, it may be helpful to know the complete set of input and output values from the initialization of the failing case so that it can be recreated for debugging purposes. NonlinearSolver provides the debug_print option for this purpose:

NonlinearSolver Options#

OptionDefaultAcceptable ValuesAcceptable TypesDescription
atol1e-10N/AN/Aabsolute error tolerance
debug_printFalse[True, False]['bool']If true, the values of input and output variables at the start of iteration are printed and written to a file after a failure to converge.
err_on_non_convergeFalse[True, False]['bool']When True, AnalysisError will be raised if we don't converge.
iprint1N/A['int']whether to print output
maxiter10N/A['int']maximum number of iterations
restart_from_successfulFalse[True, False]['bool']If True, the states are cached after a successful solve and used to restart the solver in the case of a failed solve.
rtol1e-10N/AN/Arelative error tolerance
stall_limit0N/AN/ANumber of iterations after which, if the residual norms are identical within the stall_tol, then terminate as if max iterations were reached. Default is 0, which disables this feature.
stall_tol1e-12N/AN/AWhen stall checking is enabled, the threshold below which the residual norm is considered unchanged.
stall_tol_typerel['abs', 'rel']N/ASpecifies whether the absolute or relative norm of the residual is used for stall detection.

Usage#

This example shows how to use the debug_print option for a NonlinearSolver. When this option is set to True, the values of the input and output variables will be displayed and written to a file if the solver fails to converge.

from packaging.version import Version
import numpy as np

import openmdao.api as om
from openmdao.test_suite.scripts.circuit_analysis import Circuit
from openmdao.utils.general_utils import printoptions

p = om.Problem()
model = p.model

model.add_subsystem('circuit', Circuit())

p.setup()

nl = model.circuit.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)

nl.options['iprint'] = 2
nl.options['debug_print'] = True
nl.options['err_on_non_converge'] = True

# set some poor initial guesses so that we don't converge
p.set_val('circuit.I_in', 0.1, units='A')
p.set_val('circuit.Vg', 0.0, units='V')
p.set_val('circuit.n1.V', 10.)
p.set_val('circuit.n2.V', 1e-3)

opts = {}
# formatting has changed in numpy 1.14 and beyond.
if Version(np.__version__) >= Version("1.14"):
    opts["legacy"] = '1.13'

with printoptions(**opts):
    # run the model
    try:
        p.run_model()
    except om.AnalysisError:
        pass

with open('solver_errors.0.out', 'r') as f:
    print(f.read())
=======
circuit
=======
NL: Newton 0 ; 2.53337743 1
NL: Newton 1 ; 6.9707252e+152 2.75155416e+152
NL: Newton 2 ; 2.56438649e+152 1.01224021e+152
NL: Newton 3 ; 9.43385069e+151 3.72382361e+151
NL: Newton 4 ; 3.47051972e+151 1.36991815e+151
NL: Newton 5 ; 1.27673286e+151 5.03964723e+150
NL: Newton 6 ; 4.69683769e+150 1.85398261e+150
NL: Newton 7 ; 1.72787003e+150 6.82042086e+149
NL: Newton 8 ; 6.35647859e+149 2.50909261e+149
NL: Newton 9 ; 2.33841779e+149 9.23043588e+148
NL: Newton 10 ; 8.60255831e+148 3.39568759e+148
NL: NewtonSolver 'NL: Newton' on system 'circuit' failed to converge in 10 iterations.

# Inputs and outputs at start of iteration 'rank0:root._solve_nonlinear|0|NLRunOnce|0|circuit._solve_nonlinear|0':

# nonlinear inputs
{'circuit.D1.V_in': array([ 1.+0.j]),
 'circuit.D1.V_out': array([ 0.+0.j]),
 'circuit.R1.V_in': array([ 1.+0.j]),
 'circuit.R1.V_out': array([ 0.+0.j]),
 'circuit.R2.V_in': array([ 1.+0.j]),
 'circuit.R2.V_out': array([ 1.+0.j]),
 'circuit.n1.I_in:0': array([ 0.1+0.j]),
 'circuit.n1.I_out:0': array([ 1.+0.j]),
 'circuit.n1.I_out:1': array([ 1.+0.j]),
 'circuit.n2.I_in:0': array([ 1.+0.j]),
 'circuit.n2.I_out:0': array([ 1.+0.j])}

# nonlinear outputs
{'circuit.D1.I': array([ 1.+0.j]),
 'circuit.R1.I': array([ 1.+0.j]),
 'circuit.R2.I': array([ 1.+0.j]),
 'circuit.n1.V': array([ 10.+0.j]),
 'circuit.n2.V': array([ 0.001+0.j])}

Inputs and outputs at start of iteration have been saved to 'solver_errors.0.out'.
# Inputs and outputs at start of iteration 'rank0:root._solve_nonlinear|0|NLRunOnce|0|circuit._solve_nonlinear|0':

# nonlinear inputs
{'circuit.D1.V_in': array([ 1.+0.j]),
 'circuit.D1.V_out': array([ 0.+0.j]),
 'circuit.R1.V_in': array([ 1.+0.j]),
 'circuit.R1.V_out': array([ 0.+0.j]),
 'circuit.R2.V_in': array([ 1.+0.j]),
 'circuit.R2.V_out': array([ 1.+0.j]),
 'circuit.n1.I_in:0': array([ 0.1+0.j]),
 'circuit.n1.I_out:0': array([ 1.+0.j]),
 'circuit.n1.I_out:1': array([ 1.+0.j]),
 'circuit.n2.I_in:0': array([ 1.+0.j]),
 'circuit.n2.I_out:0': array([ 1.+0.j])}

# nonlinear outputs
{'circuit.D1.I': array([ 1.+0.j]),
 'circuit.R1.I': array([ 1.+0.j]),
 'circuit.R2.I': array([ 1.+0.j]),
 'circuit.n1.V': array([ 10.+0.j]),
 'circuit.n2.V': array([ 0.001+0.j])}