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
:
show_trace
: Must beVal(true)
orVal(false)
. This controls whether the trace is displayed to the console. (Defaults toVal(false)
)trace_level
: Needs to be one of Trace Objects:TraceMinimal
,TraceWithJacobianConditionNumber
, orTraceAll
. This controls the level of detail of the trace. (Defaults toTraceMinimal()
)store_trace
: Must beVal(true)
orVal(false)
. This controls whether the trace is stored in the solution object. (Defaults toVal(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 NonlinearSolve.enable_timer_outputs
.
Note that you will have to restart Julia to disable the timer outputs once enabled.
Example Usage
using ModelingToolkit, NonlinearSolve
@variables x y z
@parameters σ ρ β
# Define a nonlinear system
eqs = [0 ~ σ * (y - x),
0 ~ x * (ρ - z) - y,
0 ~ x * y - β * z]
@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β])
u0 = [x => 1.0, y => 0.0, z => 0.0]
ps = [σ => 10.0 ρ => 26.0 β => 8 / 3]
prob = NonlinearProblem(ns, u0, ps)
solve(prob)
retcode: Success
u: 3-element Vector{Float64}:
3.793345350492673e-20
2.9557012405196054e-18
-4.2310897604246004e-16
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: Broyden(
descent = NewtonDescent(),
update_rule = GoodBroydenUpdateRule(),
reinit_rule = NoChangeInStateReset(),
max_resets = 100,
initialization = IdentityInitialization(structure = FullStructure()),
inverse_jacobian = true
)
---- ------------- ----------- -------
Iter f(u) inf-norm Step 2-norm cond(J)
---- ------------- ----------- -------
0 2.60000000e+01 1.19656636e-309 Inf
1 3.11334008e+01 5.00000000e-01 1.00000000e+00
11 3.56364608e-09 1.87220799e-08 9.23959279e+01
Final 1.12829060e-15
----------------------
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.60000000e+01 9.76987084e-310 Inf
1 3.11334008e+01 5.00000000e-01 1.00000000e+00
2 1.70049757e+01 2.47663402e+00 4.01371531e+00
3 1.25070298e+01 4.55718519e-01 3.44264376e+00
4 4.08844590e-02 1.26766212e+00 1.04551194e+01
5 4.28684266e-02 1.01939613e-03 1.04494280e+01
6 4.69551736e-02 1.54013596e-03 1.23850377e+01
7 2.18953882e-02 9.39808481e-03 4.41202867e+01
8 3.44645828e-03 6.91851172e-03 7.57097566e+01
9 1.70717053e-06 1.29181254e-03 8.96736841e+01
10 5.34816923e-08 6.20165360e-07 8.97163144e+01
11 3.56364608e-09 1.87220799e-08 9.23959279e+01
12 5.75065840e-10 1.55202581e-09 9.99847641e+01
13 1.99849681e-11 2.08160415e-10 8.66802131e+01
14 1.12829060e-15 7.49496905e-12 8.97701883e+01
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
───────────────────────────────────────────────────────────────────────────────
Time Allocations
─────────────────────── ────────────────────────
Tot / % measured: 1.20s / 22.6% 202MiB / 21.7%
Section ncalls time %tot avg alloc %tot avg
───────────────────────────────────────────────────────────────────────────────
cache construction 1 272ms 99.9% 272ms 43.7MiB 99.8% 43.7MiB
solve 1 145μs 0.1% 145μs 70.0KiB 0.2% 70.0KiB
descent 1 21.3μs 0.0% 21.3μs 1.00KiB 0.0% 1.00KiB
linear solve 1 19.4μs 0.0% 19.4μs 224B 0.0% 224B
jacobian 1 3.21μs 0.0% 3.21μs 48.0B 0.0% 48.0B
step 1 1.12μs 0.0% 1.12μs 80.0B 0.0% 80.0B
linesearch 1 405ns 0.0% 405ns 16.0B 0.0% 16.0B
───────────────────────────────────────────────────────────────────────────────
Let's try for some other solver:
cache = init(prob, DFSane(); show_trace = Val(true), trace_level = TraceMinimal(50));
solve!(cache)
cache.timer
────────────────────────────────────────────────────────────────────────────────
Time Allocations
─────────────────────── ────────────────────────
Tot / % measured: 259ms / 30.8% 28.6MiB / 44.0%
Section ncalls time %tot avg alloc %tot avg
────────────────────────────────────────────────────────────────────────────────
cache construction 1 71.3ms 89.3% 71.3ms 12.2MiB 96.6% 12.2MiB
solve 139 8.51ms 10.7% 61.2μs 442KiB 3.4% 3.18KiB
linesearch 139 8.05ms 10.1% 57.9μs 144KiB 1.1% 1.03KiB
update spectral ... 139 30.0μs 0.0% 216ns 2.17KiB 0.0% 16.0B
step 139 25.7μs 0.0% 185ns 10.9KiB 0.1% 80.0B
descent 139 17.3μs 0.0% 125ns 2.17KiB 0.0% 16.0B
────────────────────────────────────────────────────────────────────────────────
For iteration == 0
only the norm(fu, Inf)
is guaranteed to be meaningful. The other values being meaningful are solver dependent.