@@ -227,6 +227,14 @@ function Base.collect(JOp::Union{Adjoint{<:Any, <:AbstractJacobianOperator}, Tra
227227 return J
228228end
229229
230+ # #
231+ # LineSearches
232+ # #
233+
234+ include (" linesearches.jl" )
235+ import . LineSearches: AbstractLineSearch, NoLineSearch, BacktrackingLineSearch
236+ export NoLineSearch, BacktrackingLineSearch
237+
230238# #
231239# Newton-Krylov
232240# #
@@ -270,15 +278,15 @@ end
270278"""
271279Compute the Eisenstat-Walker forcing term for n > 0
272280"""
273- function (F:: EisenstatWalker )(η, tol, n_res, n_res_prior )
274- η_res = F. γ * n_res ^ 2 / n_res_prior ^ 2
281+ function (F:: EisenstatWalker )(η, tol, norm_res, norm_res_prior )
282+ η_res = F. γ * norm_res ^ 2 / norm_res_prior ^ 2
275283 # Eq 3.6
276284 if F. γ * η^ 2 <= 1 // 10
277285 η_safe = min (F. η_max, η_res)
278286 else
279287 η_safe = min (F. η_max, max (η_res, F. γ * η^ 2 ))
280288 end
281- return min (F. η_max, max (η_safe, 1 // 2 * tol / n_res )) # Eq 3.5
289+ return min (F. η_max, max (η_safe, 1 // 2 * tol / norm_res )) # Eq 3.5
282290end
283291initial (F:: EisenstatWalker ) = F. η_max
284292
@@ -331,13 +339,13 @@ end
331339struct Stats
332340 outer_iterations:: Int
333341 inner_iterations:: Int
334- n_res :: Float64
342+ norm_res :: Float64
335343end
336- function update (stats:: Stats , inner_iterations, n_res :: Float64 )
344+ function update (stats:: Stats , inner_iterations, norm_res :: Float64 )
337345 return Stats (
338346 stats. outer_iterations + 1 ,
339347 stats. inner_iterations + inner_iterations,
340- n_res
348+ norm_res
341349 )
342350end
343351
@@ -357,6 +365,7 @@ function newton_krylov!(
357365 tol_abs = 1.0e-12 , # Scipy uses 6e-6
358366 max_niter = 50 ,
359367 forcing:: Union{Forcing, Nothing} = EisenstatWalker (),
368+ linesearch!:: AbstractLineSearch = NoLineSearch (),
360369 verbose = 0 ,
361370 algo = :gmres ,
362371 M = nothing ,
@@ -366,25 +375,25 @@ function newton_krylov!(
366375 )
367376 t₀ = time_ns ()
368377 F! (res, u, p) # res = F(u)
369- n_res = norm (res)
370- callback (u, res, n_res )
378+ norm_res = norm (res)
379+ callback (u, res, norm_res )
371380
372- tol = tol_rel * n_res + tol_abs
381+ tol = tol_rel * norm_res + tol_abs
373382
374383 if forcing != = nothing
375384 η = initial (forcing)
376385 end
377386
378- verbose > 0 && @info " Jacobian-Free Newton-Krylov" algo res₀ = n_res tol tol_rel tol_abs η
387+ verbose > 0 && @info " Jacobian-Free Newton-Krylov" algo res₀ = norm_res tol tol_rel tol_abs η
379388
380389 J = JacobianOperator (F!, res, u, p)
381390
382391 # TODO : Refactor to provide method that re-uses the cache here.
383392 kc = KrylovConstructor (res)
384393 workspace = krylov_workspace (algo, kc)
385394
386- stats = Stats (0 , 0 , n_res )
387- while n_res > tol && stats. outer_iterations <= max_niter
395+ stats = Stats (0 , 0 , norm_res )
396+ while norm_res > tol && stats. outer_iterations <= max_niter
388397 # Handle kwargs for Preconditioners
389398 kwargs = krylov_kwargs
390399 if N != = nothing
@@ -405,47 +414,41 @@ function newton_krylov!(
405414 kwargs = (; atol = zero (η), rtol = η, kwargs... )
406415 end
407416
408- # Solve: J d = res = F(u)
409- # Typically, the Newton method is formulated as J d = -F(u)
410- # with update u = u + d.
411- # To simplify the implementation, we solve J d = F(u)
412- # and update u = u - d instead.
413- # `res` is modified by J, so we create a copy `res`
414- # TODO : provide a temporary storage for `res`
415- krylov_solve! (workspace, J, copy (res); kwargs... )
416-
417- d = workspace. x # (negative) Newton direction
418- s = 1 # Scaling of the Newton step TODO : LineSearch
417+ # Solve: J d = -res = -F(u)
418+ # The Newton method is formulated as J d = -F(u)
419+ # `res` is modified by J, so we create a `neg_res` copy here.
420+ # TODO : provide cache for `neg_res` to avoid this allocation.
421+ neg_res = similar (res)
422+ @. neg_res = - res
423+ krylov_solve! (workspace, J, neg_res; kwargs... )
419424
420- # Update u
421- u .= muladd .(- s, d, u) # u = u - s * d
425+ d₀ = workspace. x # (negative) Newton direction
422426
423- # Update residual and norm
424- n_res_prior = n_res
427+ # Perform line search to find an appropriate step size and update `u` and `res` in-place
428+ norm_res_prior = norm_res
429+ norm_res = linesearch! (J, F!, res, norm_res_prior, u, p, d₀)
425430
426- F! (res, u, p) # res = F(u)
427- n_res = norm (res)
428- callback (u, res, n_res)
431+ callback (u, res, norm_res)
429432
430- if isinf (n_res ) || isnan (n_res )
433+ if isinf (norm_res ) || isnan (norm_res )
431434 @error " Inner solver blew up" stats
432435 break
433436 end
434437
435438 if forcing != = nothing
436- η = forcing (η, tol, n_res, n_res_prior )
439+ η = forcing (η, tol, norm_res, norm_res_prior )
437440 end
438441
439442 # This is almost to be expected for implicit time-stepping
440443 if verbose > 0 && workspace. stats. niter == 0 && forcing != = nothing
441444 @info " Inexact Newton thinks our step is good enough " η stats
442445 end
443446
444- stats = update (stats, workspace. stats. niter, n_res )
445- verbose > 0 && @info " Newton" iter = n_res η stats
447+ stats = update (stats, workspace. stats. niter, norm_res )
448+ verbose > 0 && @info " Newton" iter = norm_res η stats
446449 end
447450 t = (time_ns () - t₀) / 1.0e9
448- return u, (; solved = n_res <= tol, stats, t)
451+ return u, (; solved = norm_res <= tol, stats, t)
449452end
450453
451454end # module Ariadne
0 commit comments