Diagnostics API

Detailed API Documentation is provided at Diagnostics API Reference.

Logging the Solve Process

All NonlinearSolve.jl native solvers allow storing and displaying the trace of the nonlinear solve process. This is controlled by 3 keyword arguments to solve:

  1. show_trace: Must be Val(true) or Val(false). This controls whether the trace is displayed to the console. (Defaults to Val(false))
  2. trace_level: Needs to be one of Trace Objects: TraceMinimal, TraceWithJacobianConditionNumber, or TraceAll. This controls the level of detail of the trace. (Defaults to TraceMinimal())
  3. store_trace: Must be Val(true) or Val(false). This controls whether the trace is stored in the solution object. (Defaults to Val(false))

Detailed Internal Timings

All the native NonlinearSolve.jl algorithms come with in-built TimerOutputs.jl support. However, this is disabled by default and can be enabled via NonlinearSolveBase.enable_timer_outputs.

Note that you will have to restart Julia to disable the timer outputs once enabled.

Example Usage

using NonlinearSolve

function nlfunc(resid, u0, p)
    resid[1] = u0[1] * u0[1] - p
    resid[2] = u0[2] * u0[2] - p
    resid[3] = u0[3] * u0[3] - p
    nothing
end

prob = NonlinearProblem(nlfunc, [1.0, 3.0, 5.0], 2.0)

solve(prob)
retcode: Success
u: 3-element Vector{Float64}:
 1.414213562373095
 1.414213562373095
 1.4142135623730951

This produced the output, but it is hard to diagnose what is going on. We can turn on the trace to see what is happening:

solve(prob; show_trace = Val(true), trace_level = TraceAll(10))

Algorithm: NewtonRaphson(
    linesearch = BackTracking(
        c_1 = 0.0001,
        ρ_hi = 0.5,
        ρ_lo = 0.1,
        order = Val{3}(),
        maxstep = Inf,
        initial_alpha = true,
        maxiters = 1000
    ),
    descent = NewtonDescent(),
    autodiff = AutoForwardDiff(),
    vjp_autodiff = AutoFiniteDiff(
        fdtype = Val{:forward}(),
        fdjtype = Val{:forward}(),
        fdhtype = Val{:hcentral}(),
        dir = true
    ),
    jvp_autodiff = AutoForwardDiff(),
    concrete_jac = Val{false}()
)

----    	-------------       	-----------         	-------
Iter    	f(u) inf-norm       	Step 2-norm         	cond(J)
----    	-------------       	-----------         	-------
0       	2.30000000e+01      	0.00000000e+00      	5.00000000e+00
1       	5.29000000e+00      	2.62699660e+00      	5.00000000e+00
Final   	4.44089210e-16
----------------------

You can also store the trace in the solution object:

sol = solve(prob; trace_level = TraceAll(), store_trace = Val(true));

sol.trace
----    	-------------       	-----------         	-------             
Iter    	f(u) inf-norm       	Step 2-norm         	cond(J)             
----    	-------------       	-----------         	-------             
0       	2.30000000e+01      	0.00000000e+00      	5.00000000e+00      
1       	5.29000000e+00      	2.62699660e+00      	5.00000000e+00      
2       	9.59674211e-01      	1.05091251e+00      	1.80000000e+00      
3       	7.77935784e-02      	2.82878317e-01      	1.21437908e+00      
4       	7.28157132e-04      	2.69957925e-02      	1.01926133e+00      
5       	6.62524795e-08      	2.57395663e-04      	1.00018202e+00      
6       	4.44089210e-16      	2.34237884e-08      	1.00000002e+00      

Now, let's try to investigate the time it took for individual internal steps. We will have to use the init and solve! API for this. The TimerOutput will be present in cache.timer. However, note that for poly-algorithms this is currently not implemented.

cache = init(prob, NewtonRaphson(); show_trace = Val(true));
solve!(cache)
cache.timer

Algorithm: NewtonRaphson(
    descent = NewtonDescent(),
    autodiff = AutoForwardDiff(),
    vjp_autodiff = AutoFiniteDiff(
        fdtype = Val{:forward}(),
        fdjtype = Val{:forward}(),
        fdhtype = Val{:hcentral}(),
        dir = true
    ),
    jvp_autodiff = AutoForwardDiff(),
    concrete_jac = Val{false}()
)

----    	-------------       	-----------
Iter    	f(u) inf-norm       	Step 2-norm
----    	-------------       	-----------
0       	2.30000000e+01      	0.00000000e+00
1       	5.29000000e+00      	2.62699660e+00
2       	9.59674211e-01      	1.05091251e+00
3       	7.77935784e-02      	2.82878317e-01
4       	7.28157132e-04      	2.69957925e-02
5       	6.62524795e-08      	2.57395663e-04
6       	4.44089210e-16      	2.34237884e-08
Final   	4.44089210e-16
----------------------

Let's try for some other solver:

cache = init(prob, DFSane(); show_trace = Val(true), trace_level = TraceMinimal(50));
solve!(cache)
cache.timer

Algorithm: DFSane(
    linesearch = RobustNonMonotoneLineSearch(
        gamma = 1//10000,
        sigma_1 = 1,
        M = 10,
        tau_min = 1//10,
        tau_max = 1//2,
        n_exp = 2,
        maxiters = 100,
        η_strategy = #2#4()
    ),
    σ_min = 1//10000000000,
    σ_max = 1.0e10
)

----    	-------------       	-----------
Iter    	f(u) inf-norm       	Step 2-norm
----    	-------------       	-----------
0       	2.30000000e+01      	0.00000000e+00
1       	1.07270233e+00      	6.23840488e+00
Final   	4.44089210e-16
----------------------
Note

For iteration == 0 only the norm(fu, Inf) is guaranteed to be meaningful. The other values being meaningful are solver dependent.