@@ -255,6 +255,74 @@ function run!(alg::EisenstatWalkerForcing, cache, f, n)
255255 return rtol
256256end
257257
258+ """
259+ KrylovMethodDebugger
260+
261+ Prints information about the Jacobian matrix `j` and the preconditioner `M` (if
262+ it is available) that are passed to a Krylov method. This is done by calling
263+ `run!(::KrylovMethodDebugger, cache, j, M)`. The `cache` can be obtained with
264+ `allocate_cache(::KrylovMethodDebugger, x_prototype)`, where `x_prototype` is
265+ `similar` to `x`.
266+ """
267+ abstract type KrylovMethodDebugger end
268+
269+ """
270+ PrintConditionNumber()
271+
272+ Prints the condition number of the Jacobian matrix `j`, and, if a preconditioner
273+ `M` is available, also prints the condition number of `inv(M) * j` (i.e., the
274+ matrix that actually gets "inverted" by the Krylov method). This requires
275+ computing dense representations of `j` and `inv(M) * j`, which is likely to be
276+ significantly slower than the Krylov method itself.
277+ """
278+ struct PrintConditionNumber <: KrylovMethodDebugger end
279+
280+ function allocate_cache (:: PrintConditionNumber , x_prototype)
281+ l = length (x_prototype)
282+ FT = eltype (x_prototype)
283+ return (;
284+ dense_vector = Array {FT} (undef, l),
285+ dense_j = Array {FT} (undef, l, l),
286+ dense_inv_M = Array {FT} (undef, l, l),
287+ dense_inv_M_j = Array {FT} (undef, l, l),
288+ )
289+ end
290+
291+ function run! (:: PrintConditionNumber , cache, j, M)
292+ (; dense_vector, dense_j, dense_inv_M, dense_inv_M_j) = cache
293+ dense_matrix_from_operator! (dense_j, dense_vector, j)
294+ if M === I
295+ @info " Condition number = $(cond (dense_j)) "
296+ else
297+ dense_inverse_matrix_from_operator! (dense_inv_M, dense_vector, M)
298+ mul! (dense_inv_M_j, dense_inv_M, dense_j)
299+ @info " Condition number = $(cond (dense_inv_M_j)) ($(cond (dense_j)) \
300+ without the preconditioner)"
301+ end
302+ end
303+
304+ # Like Matrix(op::AbstractLinearOperator) from LinearOperators.jl, but in-place.
305+ function dense_matrix_from_operator! (dense_matrix, dense_vector, op)
306+ n_columns = size (dense_matrix)[2 ]
307+ dense_vector .= 0
308+ for column in 1 : n_columns
309+ dense_vector[column] = 1
310+ mul! (view (dense_matrix, :, column), op, dense_vector)
311+ dense_vector[column] = 0
312+ end
313+ end
314+
315+ # Same as dense_matrix_from_operator!, but with ldiv! instead of mul!.
316+ function dense_inverse_matrix_from_operator! (dense_inv_matrix, dense_vector, op)
317+ n_columns = size (dense_inv_matrix)[2 ]
318+ dense_vector .= 0
319+ for column in 1 : n_columns
320+ dense_vector[column] = 1
321+ ldiv! (view (dense_inv_matrix, :, column), op, dense_vector)
322+ dense_vector[column] = 0
323+ end
324+ end
325+
258326"""
259327 KrylovMethod(;
260328 jacobian_free_jvp = nothing,
265333 solve_kwargs = (;),
266334 disable_preconditioner = false,
267335 verbose = false,
336+ debugger = nothing,
268337 )
269338
270339Finds an approximation `Δx[n] ≈ j(x[n]) \\ f(x[n])` for Newton's method such
@@ -312,6 +381,10 @@ All of the arguments and keyword arguments used to construct and run the solver
312381can be modified using `args`, `kwargs`, and `solve_kwargs`. So, the default
313382behavior of this wrapper can be easily overwritten, and any features of
314383`Krylov.jl` that are not explicitly covered by this wrapper can still be used.
384+
385+ If `verbose` is `true`, the residual `‖f(x[n]) - j(x[n]) * Δx[n]‖` is printed on
386+ each iteration of the Krylov method. If a debugger is specified, it is run
387+ before the call to `Kyrlov.solve!`.
315388"""
316389Base. @kwdef struct KrylovMethod{
317390 J <: Union{Nothing, JacobianFreeJVP} ,
@@ -320,6 +393,7 @@ Base.@kwdef struct KrylovMethod{
320393 A <: Tuple ,
321394 K <: NamedTuple ,
322395 S <: NamedTuple ,
396+ D <: Union{Nothing, KrylovMethodDebugger} ,
323397}
324398 jacobian_free_jvp:: J = nothing
325399 forcing_term:: F = ConstantForcing (0 )
@@ -329,29 +403,34 @@ Base.@kwdef struct KrylovMethod{
329403 solve_kwargs:: S = (;)
330404 disable_preconditioner:: Bool = false
331405 verbose:: Bool = false
406+ debugger:: D = nothing
332407end
333408
334409function allocate_cache (alg:: KrylovMethod , x_prototype)
335- (; jacobian_free_jvp, forcing_term, type, args, kwargs) = alg
410+ (; jacobian_free_jvp, forcing_term, type, args, kwargs, debugger ) = alg
336411 @assert alg. type isa Type{<: Krylov.KrylovSolver }
337412 l = length (x_prototype)
338413 return (;
339414 jacobian_free_jvp_cache = isnothing (jacobian_free_jvp) ? nothing :
340415 allocate_cache (jacobian_free_jvp, x_prototype),
341416 forcing_term_cache = allocate_cache (forcing_term, x_prototype),
342417 solver = type (l, l, args... , Krylov. ktypeof (x_prototype); kwargs... ),
418+ debugger_cache = isnothing (debugger) ? nothing :
419+ allocate_cache (debugger, x_prototype),
343420 )
344421end
345422
346423function run! (alg:: KrylovMethod , cache, Δx, x, f!, f, n, j = nothing )
347424 (; jacobian_free_jvp, forcing_term, type, solve_kwargs) = alg
348- (; disable_preconditioner, verbose) = alg
349- (; jacobian_free_jvp_cache, forcing_term_cache, solver) = cache
425+ (; disable_preconditioner, verbose, debugger) = alg
426+ (; jacobian_free_jvp_cache, forcing_term_cache, solver, debugger_cache) =
427+ cache
350428 jΔx! (jΔx, Δx) = isnothing (jacobian_free_jvp) ? mul! (jΔx, j, Δx) :
351429 run! (jacobian_free_jvp, jacobian_free_jvp_cache, jΔx, Δx, x, f!, f)
352430 opj = LinearOperator (eltype (x), length (x), length (x), false , false , jΔx!)
353431 M = disable_preconditioner || isnothing (j) || isnothing (jacobian_free_jvp) ?
354432 I : j
433+ run! (debugger, debugger_cache, opj, M)
355434 ldiv = true
356435 atol = zero (eltype (Δx))
357436 rtol = run! (forcing_term, forcing_term_cache, f, n)
0 commit comments