nonlin 1.5.2
A library that provides routines to compute the solutions to systems of nonlinear equations.
Loading...
Searching...
No Matches
nonlin_linesearch.f90
1! nonlin_linesearch.f90
2
3! REFERENCES
4! - https://scicomp.stackexchange.com/questions/26330/backtracking-armijo-line-search-algorithm
5! - https://ctk.math.ncsu.edu/
6
14 use, intrinsic :: iso_fortran_env, only : int32, real64
16 use nonlin_core
17 use ferror, only : errors
18 implicit none
19 private
20 public :: line_search
21 public :: limit_search_vector
22
23! ******************************************************************************
24! TYPES
25! ------------------------------------------------------------------------------
41 private
44 integer(int32) :: m_maxeval = 100
51 real(real64) :: m_alpha = 1.0d-4
59 real(real64) :: m_factor = 0.1d0
60 contains
63 procedure, public :: get_max_fcn_evals => ls_get_max_eval
66 procedure, public :: set_max_fcn_evals => ls_set_max_eval
69 procedure, public :: get_scaling_factor => ls_get_scale
72 procedure, public :: set_scaling_factor => ls_set_scale
75 procedure, public :: get_distance_factor => ls_get_dist
78 procedure, public :: set_distance_factor => ls_set_dist
83 generic, public :: search => ls_search_mimo, ls_search_miso
84
85 procedure :: ls_search_mimo
86 procedure :: ls_search_miso
87 end type
88
89contains
90! ******************************************************************************
91! LINE SEARCH MEMBERS
92! ------------------------------------------------------------------------------
98 pure function ls_get_max_eval(this) result(n)
99 class(line_search), intent(in) :: this
100 integer(int32) :: n
101 n = this%m_maxEval
102 end function
103
104! --------------------
110 subroutine ls_set_max_eval(this, x)
111 class(line_search), intent(inout) :: this
112 integer(int32), intent(in) :: x
113 this%m_maxEval = x
114 end subroutine
115
116! ------------------------------------------------------------------------------
124 pure function ls_get_scale(this) result(x)
125 class(line_search), intent(in) :: this
126 real(real64) :: x
127 x = this%m_alpha
128 end function
129
130! --------------------
138 subroutine ls_set_scale(this, x)
139 class(line_search), intent(inout) :: this
140 real(real64), intent(in) :: x
141 this%m_alpha = x
142 end subroutine
143
144! ------------------------------------------------------------------------------
151 pure function ls_get_dist(this) result(x)
152 class(line_search), intent(in) :: this
153 real(real64) :: x
154 x = this%m_factor
155 end function
156
157! --------------------
165 subroutine ls_set_dist(this, x)
166 class(line_search), intent(inout) :: this
167 real(real64), intent(in) :: x
168 if (x <= 0.0d0) then
169 this%m_factor = 0.1d0
170 else if (x >= 1.0d0) then
171 this%m_factor = 0.99d0
172 else
173 this%m_factor = x
174 end if
175 end subroutine
176
177! ------------------------------------------------------------------------------
213 subroutine ls_search_mimo(this, fcn, xold, grad, dir, x, fvec, fold, fx, &
214 ib, err)
215 ! Arguments
216 class(line_search), intent(in) :: this
217 class(vecfcn_helper), intent(in) :: fcn
218 real(real64), intent(in), dimension(:) :: xold, dir, grad
219 real(real64), intent(out), dimension(:) :: x, fvec
220 real(real64), intent(in), optional :: fold
221 real(real64), intent(out), optional :: fx
222 type(iteration_behavior), optional :: ib
223 class(errors), intent(inout), optional, target :: err
224
225 ! Parameters
226 real(real64), parameter :: zero = 0.0d0
227 real(real64), parameter :: p5 = 0.5d0
228 real(real64), parameter :: one = 1.0d0
229 real(real64), parameter :: two = 2.0d0
230 real(real64), parameter :: three = 3.0d0
231
232 ! Local Variables
233 logical :: xcnvrg, fcnvrg
234 integer(int32) :: i, m, n, neval, niter, flag, maxeval
235 real(real64) :: alam, alam1, alamin, f1, slope, temp, test, tmplam, &
236 alpha, tolx, lambdamin, f, fo
237 class(errors), pointer :: errmgr
238 type(errors), target :: deferr
239 character(len = 128) :: errmsg
240
241 ! Initialization
242 xcnvrg = .false.
243 fcnvrg = .false.
244 neval = 0
245 niter = 0
246 m = fcn%get_equation_count()
247 n = fcn%get_variable_count()
248 tolx = two * epsilon(tolx)
249 alpha = this%m_alpha
250 lambdamin = this%m_factor
251 maxeval = this%m_maxEval
252 if (present(fx)) fx = zero
253 if (present(ib)) then
254 ib%iter_count = niter
255 ib%fcn_count = neval
256 ib%converge_on_fcn = fcnvrg
257 ib%converge_on_chng = xcnvrg
258 ib%converge_on_zero_diff = .false.
259 end if
260 if (present(err)) then
261 errmgr => err
262 else
263 errmgr => deferr
264 end if
265
266 ! Input Checking
267 if (.not.fcn%is_fcn_defined()) then
268 ! ERROR: No function is defined
269 call errmgr%report_error("ls_search_mimo", &
270 "No function has been defined.", &
271 nl_invalid_operation_error)
272 return
273 end if
274 flag = 0
275 if (size(xold) /= n) then
276 flag = 3
277 else if (size(grad) /= n) then
278 flag = 4
279 else if (size(dir) /= n) then
280 flag = 5
281 else if (size(x) /= n) then
282 flag = 6
283 else if (size(fvec) /= m) then
284 flag = 7
285 end if
286 if (flag /= 0) then
287 ! One of the input arrays is not sized correctly
288 write(errmsg, 100) "Input number ", flag, &
289 " is not sized correctly."
290 call errmgr%report_error("ls_search_mimo", trim(errmsg), &
291 nl_array_size_error)
292 return
293 end if
294
295 ! Compute 1/2 F * F (* = dot product) if not provided
296 if (present(fold)) then
297 fo = fold
298 else
299 ! Evaluate the function, and compute the dot product
300 call fcn%fcn(xold, fvec)
301 fo = p5 * dot_product(fvec, fvec)
302 neval = neval + 1
303 end if
304
305 ! Compute the slope parameter
306 slope = dot_product(grad, dir)
307 if (slope >= zero) then
308 ! ERROR: The slope should not be pointing uphill - invalid direction
309 call errmgr%report_error("ls_search_mimo", &
310 "The search direction vector appears to be pointing in " // &
311 "an uphill direction - away from a minimum.", &
312 nl_divergent_behavior_error)
313 return
314 end if
315
316 ! Compute the minimum lambda value (length along the search direction)
317 test = zero
318 do i = 1, n
319 temp = abs(dir(i)) / max(abs(xold(i)), one)
320 if (temp > test) test = temp
321 end do
322 alamin = tolx / test
323 alam = one
324
325 ! Iteration Loop
326 flag = 0 ! Used to check for convergence errors
327 do
328 ! Step along the specified direction by the amount ALAM
329 x = xold + alam * dir
330 call fcn%fcn(x, fvec)
331 f = p5 * dot_product(fvec, fvec)
332 neval = neval + 1
333 niter = niter + 1
334
335 ! Check the step
336 if (alam < alamin) then
337 ! Either the solution has converged due to negligible change in
338 ! the root values, or the line search may have fully
339 ! backtracked. In the event the solution fully backtracked,
340 ! we'll issue a warning to inform the user of the potential
341 ! issue.
342 if (norm2(x - xold) == zero) then
343 ! The line search fully backtracked
344 call errmgr%report_warning("ls_search_mimo", &
345 "The line search appears to have fully " // &
346 "backtracked. As such, check results carefully, " // &
347 "and/or consider attempting the solve without " // &
348 "the line search.", &
349 nl_convergence_error)
350 end if
351 x = xold
352 xcnvrg = .true.
353 exit
354 else if (f <= fo + alpha * alam * slope) then
355 ! The function has converged
356 fcnvrg = .true.
357 exit
358 else
359 ! Convergence has not yet occurred, continue backtracking
360 tmplam = min_backtrack_search(niter, fo, f, f1, alam, &
361 alam1, slope)
362 end if
363
364 ! Set up parameters for the cubic model as we've already been
365 ! through once with the quadratic model without success.
366 alam1 = alam
367 f1 = f
368 alam = max(tmplam, lambdamin * alam)
369
370 ! Ensure we haven't performed too many function evaluations
371 if (neval >= maxeval) then
372 ! ERROR: Too many function evaluations
373 flag = 1
374 exit
375 end if
376 end do
377 if (present(fx)) fx = f
378
379 ! Report out iteration statistics
380 if (present(ib)) then
381 ib%iter_count = niter
382 ib%fcn_count = neval
383 ib%converge_on_fcn = fcnvrg
384 ib%converge_on_chng = xcnvrg
385 ib%converge_on_zero_diff = .false.
386 end if
387
388 ! Check for convergence issues
389 if (flag /= 0) then
390 write(errmsg, 100) "The line search failed to " // &
391 "converge. Function evaluations performed: ", neval, "."
392 call errmgr%report_error("ls_search_mimo", trim(errmsg), &
393 nl_convergence_error)
394 end if
395
396 ! Formatting
397100 format(a, i0, a)
398 end subroutine
399
400! ------------------------------------------------------------------------------
431 subroutine ls_search_miso(this, fcn, xold, grad, dir, x, fold, fx, &
432 ib, err)
433 ! Arguments
434 class(line_search), intent(in) :: this
435 class(fcnnvar_helper), intent(in) :: fcn
436 real(real64), intent(in), dimension(:) :: xold, dir, grad
437 real(real64), intent(out), dimension(:) :: x
438 real(real64), intent(in), optional :: fold
439 real(real64), intent(out), optional :: fx
440 type(iteration_behavior), optional :: ib
441 class(errors), intent(inout), optional, target :: err
442
443 ! Parameters
444 real(real64), parameter :: zero = 0.0d0
445 real(real64), parameter :: p5 = 0.5d0
446 real(real64), parameter :: one = 1.0d0
447 real(real64), parameter :: two = 2.0d0
448 real(real64), parameter :: three = 3.0d0
449
450 ! Local Variables
451 logical :: xcnvrg, fcnvrg
452 integer(int32) :: i, n, neval, niter, flag, maxeval
453 real(real64) :: alam, alam1, alamin, f1, slope, temp, test, tmplam, &
454 alpha, tolx, lambdamin, f, fo
455 class(errors), pointer :: errmgr
456 type(errors), target :: deferr
457 character(len = 128) :: errmsg
458
459 ! Initialization
460 xcnvrg = .false.
461 fcnvrg = .false.
462 neval = 0
463 niter = 0
464 n = fcn%get_variable_count()
465 tolx = two * epsilon(tolx)
466 alpha = this%m_alpha
467 lambdamin = this%m_factor
468 maxeval = this%m_maxEval
469 if (present(fx)) fx = zero
470 if (present(ib)) then
471 ib%iter_count = niter
472 ib%fcn_count = neval
473 ib%converge_on_fcn = fcnvrg
474 ib%converge_on_chng = xcnvrg
475 ib%converge_on_zero_diff = .false.
476 end if
477 if (present(err)) then
478 errmgr => err
479 else
480 errmgr => deferr
481 end if
482
483 ! Input Checking
484 if (.not.fcn%is_fcn_defined()) then
485 ! ERROR: No function is defined
486 call errmgr%report_error("ls_search_miso", &
487 "No function has been defined.", &
488 nl_invalid_operation_error)
489 return
490 end if
491 flag = 0
492 if (size(xold) /= n) then
493 flag = 3
494 else if (size(grad) /= n) then
495 flag = 4
496 else if (size(dir) /= n) then
497 flag = 5
498 else if (size(x) /= n) then
499 flag = 6
500 end if
501 if (flag /= 0) then
502 ! One of the input arrays is not sized correctly
503 write(errmsg, 100) "Input number ", flag, &
504 " is not sized correctly."
505 call errmgr%report_error("ls_search_miso", trim(errmsg), &
506 nl_array_size_error)
507 return
508 end if
509
510 ! Establish the "old" function value
511 if (present(fold)) then
512 fo = fold
513 else
514 ! Evaluate the function
515 fo = fcn%fcn(xold)
516 neval = neval + 1
517 end if
518
519 ! Compute the slope parameter
520 slope = dot_product(grad, dir)
521 if (slope >= zero) then
522 ! ERROR: The slope should not be pointing uphill - invalid direction
523 call errmgr%report_error("ls_search_miso", &
524 "The search direction vector appears to be pointing in " // &
525 "an uphill direction - away from a minimum.", &
526 nl_divergent_behavior_error)
527 return
528 end if
529
530 ! Compute the minimum lambda value (length along the search direction)
531 test = zero
532 do i = 1, n
533 temp = abs(dir(i)) / max(abs(xold(i)), one)
534 if (temp > test) test = temp
535 end do
536 alamin = tolx / test
537 alam = one
538
539 ! Iteration Loop
540 flag = 0 ! Used to check for convergence errors
541 do
542 ! Step along the specified direction by the amount ALAM
543 x = xold + alam * dir
544 f = fcn%fcn(x)
545 neval = neval + 1
546 niter = niter + 1
547
548 ! Check the step
549 if (alam < alamin) then
550 ! Either the solution has converged due to negligible change in
551 ! the root values, or the line search may have fully
552 ! backtracked. In the event the solution fully backtracked,
553 ! we'll issue a warning to inform the user of the potential
554 ! issue.
555 if (norm2(x - xold) == zero) then
556 ! The line search fully backtracked
557 call errmgr%report_warning("ls_search_miso", &
558 "The line search appears to have fully " // &
559 "backtracked. As such, check results carefully, " // &
560 "and/or consider attempting the solve without " // &
561 "the line search.", &
562 nl_convergence_error)
563 end if
564 x = xold
565 xcnvrg = .true.
566 exit
567 else if (f <= fo + alpha * alam * slope) then
568 ! The function has converged
569 fcnvrg = .true.
570 exit
571 else
572 ! Convergence has not yet occurred, continue backtracking
573 tmplam = min_backtrack_search(niter, fo, f, f1, alam, &
574 alam1, slope)
575 end if
576
577 ! Set up parameters for the cubic model as we've already been
578 ! through once with the quadratic model without success.
579 alam1 = alam
580 f1 = f
581 alam = max(tmplam, lambdamin * alam)
582
583 ! Ensure we haven't performed too many function evaluations
584 if (neval >= maxeval) then
585 ! ERROR: Too many function evaluations
586 flag = 1
587 exit
588 end if
589 end do
590 if (present(fx)) fx = f
591
592 ! Report out iteration statistics
593 if (present(ib)) then
594 ib%iter_count = niter
595 ib%fcn_count = neval
596 ib%converge_on_fcn = fcnvrg
597 ib%converge_on_chng = xcnvrg
598 ib%converge_on_zero_diff = .false.
599 end if
600
601 ! Check for convergence issues
602 if (flag /= 0) then
603 write(errmsg, 100) "The line search failed to " // &
604 "converge. Function evaluations performed: ", neval, "."
605 call errmgr%report_error("ls_search_miso", trim(errmsg), &
606 nl_convergence_error)
607 end if
608
609 ! Formatting
610100 format(a, i0, a)
611 end subroutine
612
613! ------------------------------------------------------------------------------
626 pure function min_backtrack_search(mode, f0, f, f1, alam, alam1, &
627 slope) result(lam)
628 ! Arguments
629 integer(int32), intent(in) :: mode
630 real(real64), intent(in) :: f0, f, f1, alam, alam1, slope
631 real(real64) :: lam
632
633 ! Parameters
634 real(real64), parameter :: zero = 0.0d0
635 real(real64), parameter :: p5 = 0.5d0
636 real(real64), parameter :: two = 2.0d0
637 real(real64), parameter :: three = 3.0d0
638
639 ! Local Variables
640 real(real64) :: rhs1, rhs2, a, b, disc
641
642 ! Process
643 if (mode == 1) then
644 ! Use a quadratic function approximation
645 lam = -slope / (two * (f - f0 - slope))
646 else
647 ! Use a cubic function
648 rhs1 = f - f0 - alam * slope
649 rhs2 = f1 - f0 - alam1 * slope
650 a = (rhs1 / alam**2 - rhs2 / alam1**2) / (alam - alam1)
651 b = (-alam1 * rhs1 / alam**2 + alam * rhs2 / alam1**2) / &
652 (alam - alam1)
653 if (a == zero) then
654 lam = -slope / (two * b)
655 else
656 disc = b**2 - three * a * slope
657 if (disc < zero) then
658 lam = p5 * alam
659 else if (b <= zero) then
660 lam = (-b + sqrt(disc)) / (three * a)
661 else
662 lam = -slope / (b + sqrt(disc))
663 end if
664 end if
665 if (lam > p5 * alam) lam = p5 * alam
666 end if
667 end function
668
669! ------------------------------------------------------------------------------
678 subroutine limit_search_vector(x, lim)
679 ! Arguments
680 real(real64), intent(inout), dimension(:) :: x
681 real(real64), intent(in) :: lim
682
683 ! Local Variables
684 real(real64) :: mag
685
686 ! Process
687 mag = norm2(x)
688 if (mag == 0.0d0) return
689 if (mag > lim) x = (lim / mag) * x
690 end subroutine
691
692! ------------------------------------------------------------------------------
693end module
nonlin_constants
nonlin_core
nonlin_linesearch
Defines a type capable of encapsulating an equation of N variables.
Defines a set of parameters that describe the behavior of the iteration process.
Defines a type capable of encapsulating a system of nonlinear equations of the form: F(X) = 0....
Defines a type capable of performing an inexact, backtracking line search to find a point as far alon...