nonlin 1.5.2
A library that provides routines to compute the solutions to systems of nonlinear equations.
Loading...
Searching...
No Matches
nonlin_solve_quasi_newton.f90
1! nonlin_solve_quasi_newton.f90
2
3submodule(nonlin_solve) nonlin_solve_quasi_newton
4 implicit none
5contains
6! ------------------------------------------------------------------------------
7 module subroutine qns_solve(this, fcn, x, fvec, ib, err)
8 ! Arguments
9 class(quasi_newton_solver), intent(inout) :: this
10 class(vecfcn_helper), intent(in) :: fcn
11 real(real64), intent(inout), dimension(:) :: x
12 real(real64), intent(out), dimension(:) :: fvec
13 type(iteration_behavior), optional :: ib
14 class(errors), intent(inout), optional, target :: err
15
16 ! Parameters
17 real(real64), parameter :: zero = 0.0d0
18 real(real64), parameter :: half = 0.5d0
19 real(real64), parameter :: one = 1.0d0
20 real(real64), parameter :: factor = 1.0d2
21
22 ! Local Variables
23 logical :: restart, xcnvrg, fcnvrg, gcnvrg, check
24 integer(int32) :: i, neqn, nvar, flag, lw1, lw2, lw3, neval, iter, &
25 maxeval, jcount, njac
26 real(real64), allocatable, dimension(:) :: work, tau, dx, df, fvold, &
27 xold, s
28 real(real64), allocatable, dimension(:,:) :: q, r, b
29 real(real64) :: test, f, fold, temp, ftol, xtol, gtol, &
30 stpmax, x2, xnorm, fnorm
31 type(iteration_behavior) :: lib
32 class(errors), pointer :: errmgr
33 type(errors), target :: deferr
34 character(len = 256) :: errmsg
35 class(line_search), allocatable :: ls
36
37 ! Initialization
38 restart = .true.
39 xcnvrg = .false.
40 fcnvrg = .false.
41 gcnvrg = .false.
42 neqn = fcn%get_equation_count()
43 nvar = fcn%get_variable_count()
44 neval = 0
45 iter = 0
46 njac = 0
47 ftol = this%get_fcn_tolerance()
48 xtol = this%get_var_tolerance()
49 gtol = this%get_gradient_tolerance()
50 maxeval = this%get_max_fcn_evals()
51 if (present(ib)) then
52 ib%iter_count = iter
53 ib%fcn_count = neval
54 ib%jacobian_count = njac
55 ib%gradient_count = 0
56 ib%converge_on_fcn = fcnvrg
57 ib%converge_on_chng = xcnvrg
58 ib%converge_on_zero_diff = gcnvrg
59 end if
60 if (present(err)) then
61 errmgr => err
62 else
63 errmgr => deferr
64 end if
65 if (this%get_use_line_search()) then
66 if (.not.this%is_line_search_defined()) &
67 call this%set_default_line_search()
68 call this%get_line_search(ls)
69 end if
70
71 ! Input Check
72 if (.not.fcn%is_fcn_defined()) then
73 ! ERROR: No function is defined
74 call errmgr%report_error("qns_solve", &
75 "No function has been defined.", &
76 nl_invalid_operation_error)
77 return
78 end if
79 if (nvar /= neqn) then
80 ! ERROR: # of equations doesn't match # of variables
81 write(errmsg, 100) "The number of equations (", neqn, &
82 ") does not match the number of unknowns (", nvar, ")."
83 call errmgr%report_error("qns_solve", trim(errmsg), &
84 nl_invalid_input_error)
85 return
86 end if
87 flag = 0
88 if (size(x) /= nvar) then
89 flag = 3
90 else if (size(fvec) /= neqn) then
91 flag = 4
92 end if
93 if (flag /= 0) then
94 ! One of the input arrays is not sized correctly
95 write(errmsg, 101) "Input number ", flag, &
96 " is not sized correctly."
97 call errmgr%report_error("qns_solve", trim(errmsg), &
98 nl_array_size_error)
99 return
100 end if
101
102 ! Local Memory Allocation
103 allocate(q(neqn, neqn), stat = flag)
104 if (flag == 0) allocate(r(neqn, nvar), stat = flag)
105 if (flag == 0) allocate(tau(min(neqn, nvar)), stat = flag)
106 if (flag == 0) allocate(b(neqn, nvar), stat = flag)
107 if (flag == 0) allocate(df(neqn), stat = flag)
108 if (flag == 0) allocate(fvold(neqn), stat = flag)
109 if (flag == 0) allocate(xold(nvar), stat = flag)
110 if (flag == 0) allocate(dx(nvar), stat = flag)
111 if (flag == 0) allocate(s(neqn), stat = flag)
112 if (flag /= 0) then
113 ! ERROR: Out of memory
114 call errmgr%report_error("qns_solve", &
115 "Insufficient memory available.", nl_out_of_memory_error)
116 return
117 end if
118 call qr_factor(r, tau, work, lw1)
119 call form_qr(r, tau, q, work, lw2)
120 call fcn%jacobian(x, b, fv = fvec, olwork = lw3)
121 allocate(work(max(lw1, lw2, lw3)), stat = flag)
122 if (flag /= 0) then
123 ! ERROR: Out of memory
124 call errmgr%report_error("qns_solve", &
125 "Insufficient memory available.", nl_out_of_memory_error)
126 return
127 end if
128
129 ! Test to see if the initial guess is a root
130 call fcn%fcn(x, fvec)
131 f = half * dot_product(fvec, fvec)
132 neval = neval + 1
133 test = zero
134 do i = 1, neqn
135 test = max(abs(fvec(i)), test)
136 end do
137 if (test < ftol) then
138 fcnvrg = .true.
139 end if
140
141 ! Process
142 flag = 0 ! Used to check for convergence errors
143 if (.not.fcnvrg) then
144 ! Determine the maximum line search step
145 stpmax = factor * max(norm2(x), real(nvar, real64))
146
147 ! Main Iteration Loop
148 do
149 ! Update the iteration counter
150 iter = iter + 1
151
152 ! Compute or update the Jacobian
153 if (restart) then
154 ! Compute the Jacobian
155 call fcn%jacobian(x, b, fvec, work)
156 njac = njac + 1
157
158 ! Compute the QR factorization, and form Q & R
159 r = b ! Copy the Jacobian - we'll need it later
160 call qr_factor(r, tau, work)
161 call form_qr(r, tau, q, work)
162
163 ! Reset the Jacobian iteration counter
164 jcount = 0
165 else
166 ! Apply the rank 1 update to Q and R
167 df = fvec - fvold
168 dx = x - xold
169 x2 = dot_product(dx, dx)
170
171 ! Compute S = ALPHA * (DF - B * DX)
172 s = (df - matmul(b, dx))
173 call recip_mult_array(x2, s)
174
175 ! Compute the new Q and R matrices for the rank1 update:
176 ! B' = B + ALPHA * S * DX**T
177 call rank1_update(one, s, dx, b)
178 call qr_rank1_update(q, r, s, dx, work) ! S & DX overwritten
179
180 ! Increment the counter tracking how many iterations have
181 ! passed since the last Jacobian recalculation
182 jcount = jcount + 1
183 end if
184
185 ! Compute GRAD = B**T * F, store in DX
186 call mtx_mult(.true., one, b, fvec, zero, dx)
187
188 ! Store FVEC and X
189 xold = x
190 fvold = fvec
191 fold = f
192
193 ! Solve the linear system: B * DX = -F for DX noting that
194 ! B = Q * R. As such, form -Q**T * F, and store in DF
195 call mtx_mult(.true., -one, q, fvec, zero, df)
196
197 ! Now we have R * DX = -Q**T * F, and since R is upper
198 ! triangular, the solution is readily computed. The solution
199 ! will be stored in the first NVAR elements of DF
200 call solve_triangular_system(.true., .false., .true., r, &
201 df(1:nvar))
202
203 ! Ensure the new solution estimate is heading in a sensible
204 ! direction. If not, it is likely time to update the Jacobian
205 temp = dot_product(dx, df(1:nvar))
206 if (temp >= zero) then
207 restart = .true.
208 if (this%get_print_status()) then
209 call print_status(iter, neval, njac, xnorm, fnorm)
210 end if
211 cycle
212 end if
213
214 ! Apply the line search if needed
215 if (this%get_use_line_search()) then
216 ! Define the step length for the line search
217 temp = dot_product(df(1:nvar), df(1:nvar))
218 if (temp > stpmax) df(1:nvar) = df(1:nvar) * (stpmax / temp)
219
220 ! Apply the line search
221 call limit_search_vector(df(1:nvar), stpmax)
222 call ls%search(fcn, xold, dx, df(1:nvar), x, fvec, fold, &
223 f, lib, errmgr)
224 neval = neval + lib%fcn_count
225 else
226 ! No line search - just update the solution estimate
227 x = x + df(1:nvar)
228 call fcn%fcn(x, fvec)
229 f = half * dot_product(fvec, fvec)
230 neval = neval + 1
231 end if
232
233 ! Test for convergence
234 if (lib%converge_on_zero_diff .and. &
235 this%get_use_line_search()) then
236 call test_convergence(x, xold, fvec, dx, .true., xtol, &
237 ftol, gtol, check, xcnvrg, fcnvrg, gcnvrg, xnorm, fnorm)
238 else
239 call test_convergence(x, xold, fvec, dx, .false., xtol, &
240 ftol, gtol, check, xcnvrg, fcnvrg, gcnvrg, xnorm, fnorm)
241 end if
242 if (.not.check) then
243 ! The solution did not converge, figure out why
244 if (gcnvrg) then
245 ! The slope of the gradient is sufficiently close to
246 ! zero to cause issue.
247 if (restart) then
248 ! We've already tried recalculating a new Jacobian,
249 ! issue a warning
250 write(errmsg, 102) &
251 "It appears the solution has settled to " // &
252 "a point where the slope of the gradient " // &
253 "is effectively zero. " // new_line('c') // &
254 "Function evaluations performed: ", neval, &
255 "." // new_line('c') // &
256 "Change in Variable: ", xnorm, &
257 new_line('c') // "Residual: ", fnorm
258 call errmgr%report_warning("nqs_solve", &
259 trim(errmsg), nl_spurious_convergence_error)
260 exit
261 else
262 ! Try computing a new Jacobian
263 restart = .true.
264 end if
265 else
266 ! We have not converged, but we're not stuck with a
267 ! zero slope gradient vector either. Go ahead and
268 ! continue the iteration process without recomputing
269 ! the Jacobian - unless the user dictates a
270 ! recaclulation.
271 if (jcount >= this%m_jDelta) then
272 restart = .true.
273 else
274 restart = .false.
275 end if
276 end if
277 else
278 ! The solution has converged. It's OK to exit
279 exit
280 end if
281
282 ! Print status
283 if (this%get_print_status()) then
284 call print_status(iter, neval, njac, xnorm, fnorm)
285 end if
286
287 ! Ensure we haven't made too many function evaluations
288 if (neval >= maxeval) then
289 flag = 1
290 exit
291 end if
292 end do
293 end if
294
295 ! Report out iteration statistics
296 if (present(ib)) then
297 ib%iter_count = iter
298 ib%fcn_count = neval
299 ib%jacobian_count = njac
300 ib%gradient_count = 0
301 ib%converge_on_fcn = fcnvrg
302 ib%converge_on_chng = xcnvrg
303 ib%converge_on_zero_diff = gcnvrg
304 end if
305
306 ! Check for convergence issues
307 if (flag /= 0) then
308 write(errmsg, 102) "The algorithm failed to " // &
309 "converge. Function evaluations performed: ", neval, &
310 "." // new_line('c') // "Change in Variable: ", xnorm, &
311 new_line('c') // "Residual: ", fnorm
312 call errmgr%report_error("qns_solve", trim(errmsg), &
313 nl_convergence_error)
314 end if
315
316 ! Format
317100 format(a, i0, a, i0, a)
318101 format(a, i0, a)
319102 format(a, i0, a, e10.3, a, e10.3)
320 end subroutine
321
322! ------------------------------------------------------------------------------
323 pure module function qns_get_jac_interval(this) result(n)
324 class(quasi_newton_solver), intent(in) :: this
325 integer(int32) :: n
326 n = this%m_jDelta
327 end function
328
329! --------------------
330 module subroutine qns_set_jac_interval(this, n)
331 class(quasi_newton_solver), intent(inout) :: this
332 integer(int32), intent(in) :: n
333 this%m_jDelta = n
334 end subroutine
335end submodule
nonlin_solve