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#
Option | Default | Acceptable Values | Acceptable Types | Description |
---|---|---|---|---|
atol | 1e-10 | N/A | N/A | absolute error tolerance |
debug_print | False | [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_converge | False | [True, False] | ['bool'] | When True, AnalysisError will be raised if we don't converge. |
iprint | 1 | N/A | ['int'] | whether to print output |
maxiter | 10 | N/A | ['int'] | maximum number of iterations |
restart_from_successful | False | [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. |
rtol | 1e-10 | N/A | N/A | relative error tolerance |
stall_limit | 0 | N/A | N/A | Number 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_tol | 1e-12 | N/A | N/A | When stall checking is enabled, the threshold below which the residual norm is considered unchanged. |
stall_tol_type | rel | ['abs', 'rel'] | N/A | Specifies 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.
Circuit
class definition
class Circuit(om.Group):
def setup(self):
self.add_subsystem('n1', Node(n_in=1, n_out=2), promotes_inputs=[('I_in:0', 'I_in')])
self.add_subsystem('n2', Node()) # leaving defaults
self.add_subsystem('R1', Resistor(R=100.), promotes_inputs=[('V_out', 'Vg')])
self.add_subsystem('R2', Resistor(R=10000.))
self.add_subsystem('D1', Diode(), promotes_inputs=[('V_out', 'Vg')])
self.connect('n1.V', ['R1.V_in', 'R2.V_in'])
self.connect('R1.I', 'n1.I_out:0')
self.connect('R2.I', 'n1.I_out:1')
self.connect('n2.V', ['R2.V_out', 'D1.V_in'])
self.connect('R2.I', 'n2.I_in:0')
self.connect('D1.I', 'n2.I_out:0')
self.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)
self.nonlinear_solver.options['iprint'] = 2
self.nonlinear_solver.options['maxiter'] = 20
self.linear_solver = om.DirectSolver()
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])}