nonlin 1.5.2
A library that provides routines to compute the solutions to systems of nonlinear equations.
Loading...
Searching...
No Matches
nonlin_optimize_nelder_mead.f90
1! nonlin_optimize_nelder_mead.f90
2
3submodule(nonlin_optimize) nonlin_optimize_nelder_mead
4 implicit none
5contains
6! ------------------------------------------------------------------------------
7 module subroutine nm_solve(this, fcn, x, fout, ib, err)
8 ! Arguments
9 class(nelder_mead), intent(inout) :: this
10 class(fcnnvar_helper), intent(in) :: fcn
11 real(real64), intent(inout), dimension(:) :: x
12 real(real64), intent(out), optional :: fout
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 :: negone = -1.0d0
19 real(real64), parameter :: half = 0.5d0
20 real(real64), parameter :: two = 2.0d0
21
22 ! Local Variables
23 logical :: buildSimplex, fcnvrg
24 integer(int32) :: i, ihi, ilo, ihi2, ndim, npts, flag, neval, iter, &
25 maxeval
26 real(real64) :: ftol, rtol, ftry, fsave, fval, swp
27 real(real64), allocatable, dimension(:) :: f, pcent, pmin, work
28 class(errors), pointer :: errmgr
29 type(errors), target :: deferr
30 character(len = 256) :: errmsg
31
32 ! Initialization
33 ndim = fcn%get_variable_count()
34 npts = ndim + 1
35 buildsimplex = .true.
36 maxeval = this%get_max_fcn_evals()
37 ftol = this%get_tolerance()
38 iter = 0
39 neval = 0
40 fcnvrg = .false.
41 if (present(ib)) then
42 ib%iter_count = iter
43 ib%fcn_count = neval
44 ib%jacobian_count = 0
45 ib%gradient_count = 0
46 ib%converge_on_fcn = fcnvrg
47 ib%converge_on_chng = .false.
48 ib%converge_on_zero_diff = .false.
49 end if
50 if (present(err)) then
51 errmgr => err
52 else
53 errmgr => deferr
54 end if
55
56 ! Input Check
57 if (.not.fcn%is_fcn_defined()) then
58 ! ERROR: No function is defined
59 call errmgr%report_error("nm_solve", &
60 "No function has been defined.", &
61 nl_invalid_operation_error)
62 return
63 end if
64 if (size(x) /= ndim) then
65 write(errmsg, 100) &
66 "It was expected to receive a coordinate vector of length ", &
67 ndim, " , but a vector of length ", size(x), " was received."
68 call errmgr%report_error("nm_solve", trim(errmsg), &
69 nl_invalid_input_error)
70 return
71 end if
72
73 ! Ensure that if an initial simplex was defined, that it is
74 ! appropriately sized. If not, simply create a new simplex of the
75 ! appropriate size.
76 if (allocated(this%m_simplex)) then
77 ! This matrix must be NDIM-by-NPTS
78 if (size(this%m_simplex, 1) /= ndim .or. &
79 size(this%m_simplex, 2) /= npts) then
80 deallocate(this%m_simplex)
81 buildsimplex = .true.
82 else
83 ! The simplex is appropriately sized
84 buildsimplex = .false.
85 end if
86 end if
87
88 ! Local Memory Allocation
89 allocate(f(npts), stat = flag)
90 if (flag == 0) allocate(pcent(ndim), stat = flag)
91 if (flag == 0) allocate(pmin(ndim), stat = flag)
92 if (flag == 0) allocate(work(ndim), stat = flag)
93 if (buildsimplex .and. flag == 0) allocate(this%m_simplex(ndim, npts))
94 if (flag /= 0) then
95 ! ERROR: Out of memory
96 call errmgr%report_error("nm_solve", &
97 "Insufficient memory available.", nl_out_of_memory_error)
98 return
99 end if
100
101 ! Define the initial simplex, if needed
102 if (buildsimplex) then
103 this%m_simplex(:,1) = x
104 do i = 2, npts
105 this%m_simplex(:,i) = x
106 end do
107 do i = 1, ndim
108 this%m_simplex(i,i+1) = this%m_simplex(i,i+1) + this%m_initSize
109 end do
110 end if
111
112 ! Evaluate the function at each vertex of the simplex
113 do i = 1, npts
114 f(i) = fcn%fcn(this%m_simplex(:,i))
115 end do
116 neval = npts
117 fval = f(1)
118
119 do i = 1, ndim
120 pcent(i) = sum(this%m_simplex(i,:))
121 end do
122
123 ! Main Loop
124 flag = 0 ! Used to check for convergence errors
125 do
126 ! Update the iteration counter
127 iter = iter + 1
128
129 ! Determine the characteristics of each vertex
130 ilo = 1
131 if (f(1) > f(2)) then
132 ihi = 1
133 ihi2 = 2
134 else
135 ihi = 2
136 ihi2 = 1
137 end if
138 do i = 1, npts
139 if (f(i) <= f(ilo)) ilo = i
140 if (f(i) > f(ihi)) then
141 ihi2 = ihi
142 ihi = i
143 else if (f(i) > f(ihi2)) then
144 if (i /= ihi) ihi2 = i
145 end if
146 end do
147
148 ! Check for convergence. Nelder and Mead recommend using the
149 ! following convergence test: sqrt(sum(f - favg)**2 / n); however,
150 ! it seems that a sufficient check may be made using only the
151 ! extreme function values of the simplex (highest and lowest valued
152 ! points).
153 rtol = abs(f(ihi) - f(ilo))
154 if (rtol < ftol) then
155 swp = f(1)
156 f(1) = f(ilo)
157 f(ilo) = swp
158 do i = 1, ndim
159 swp = this%m_simplex(i,1)
160 this%m_simplex(i,1) = this%m_simplex(i,ilo)
161 this%m_simplex(i,ilo) = swp
162 x(i) = this%m_simplex(i,1)
163 end do
164 fval = f(1)
165 fcnvrg = .true.
166 exit
167 end if
168
169 ! Start of a new iteration by reflecting the simplex at its largest
170 ! point.
171 ftry = this%extrapolate(fcn, f, pcent, ihi, negone, neval, work)
172 if (ftry <= f(ilo)) then
173 ! The result of the reflection is better than the current
174 ! best point. As a result, try a factor of 2 in the reflected
175 ! direction. Again, the highest point is of interest.
176 ftry = this%extrapolate(fcn, f, pcent, ihi, two, neval, work)
177 else if (ftry >= f(ihi2)) then
178 ! The reflected point is worse than the second highest, so look
179 ! for an intermediate lower point (contract the simplex)
180 fsave = f(ihi)
181 ftry = this%extrapolate(fcn, f, pcent, ihi, half, neval, work)
182 if (ftry >= fsave) then
183 ! Cannot improve on the high point. Try to contract around
184 ! the low point.
185 do i = 1, npts
186 if (i /= ilo) then
187 pcent = half * (this%m_simplex(:,i) + &
188 this%m_simplex(:,ilo))
189 this%m_simplex(:,i) = pcent
190 f(i) = fcn%fcn(pcent)
191 end if
192 end do
193 neval = neval + npts
194 do i = 1, ndim
195 pcent(i) = sum(this%m_simplex(i,:))
196 end do
197 end if
198 end if
199
200 ! Print iteration status
201 if (this%get_print_status()) then
202 print *, ""
203 print 101, "Iteration: ", iter
204 print 101, "Function Evaluations: ", neval
205 print 102, "Function Value: ", fval
206 print 102, "Convergence Parameter: ", rtol
207 end if
208
209 ! Ensure we haven't made too many function evaluations
210 if (neval >= maxeval) then
211 flag = 1
212 exit
213 end if
214 end do
215
216 ! Report out iteration statistics
217 if (present(ib)) then
218 ib%iter_count = iter
219 ib%fcn_count = neval
220 ib%jacobian_count = 0
221 ib%gradient_count = 0
222 ib%converge_on_fcn = fcnvrg
223 ib%converge_on_chng = .false.
224 ib%converge_on_zero_diff = .false.
225 end if
226
227 ! Get the function value at the computed minimum
228 if (present(fout)) fout = fval
229
230 ! Check for convergence issues
231 if (flag /= 0) then
232 write(errmsg, 103) &
233 "The algorithm failed to converge." // new_line('c') // &
234 "Function evaluations performed: ", neval, new_line('c') // &
235 "Convergence Parameter: ", rtol, new_line('c') // &
236 "Convergence Criteria: ", ftol
237 call errmgr%report_error("nm_solve", trim(errmsg), &
238 nl_convergence_error)
239 end if
240
241 ! Formatting
242100 format(a, i0, a, i0, a)
243101 format(a, i0)
244102 format(a, e10.3)
245103 format(a, i0, a, e10.3, a, e10.3)
246 end subroutine
247
248! ------------------------------------------------------------------------------
249 module function nm_extrapolate(this, fcn, y, pcent, ihi, fac, neval, &
250 work) result(ytry)
251 ! Arguments
252 class(nelder_mead), intent(inout) :: this
253 class(fcnnvar_helper), intent(in) :: fcn
254 real(real64), intent(inout), dimension(:) :: y, pcent
255 integer(int32), intent(in) :: ihi
256 real(real64), intent(in) :: fac
257 integer(int32), intent(inout) :: neval
258 real(real64), intent(out), dimension(:) :: work
259 real(real64) :: ytry
260
261 ! Parameters
262 real(real64), parameter :: one = 1.0d0
263
264 ! Local Variables
265 integer(int32) :: i, ndim
266 real(real64) :: fac1, fac2
267
268 ! Initialization
269 ndim = size(this%m_simplex, 1)
270
271 ! Define a trial point
272 fac1 = (one - fac) / ndim
273 fac2 = fac1 - fac
274 do i = 1, ndim
275 work(i) = pcent(i) * fac1 - this%m_simplex(i,ihi) * fac2
276 end do
277
278 ! Evaluate the function at the trial point, and then replace if the
279 ! trial provides an improvement
280 ytry = fcn%fcn(work)
281 neval = neval + 1
282 if (ytry < y(ihi)) then
283 y(ihi) = ytry
284 do i = 1, ndim
285 pcent(i) = pcent(i) + work(i) - this%m_simplex(i,ihi)
286 this%m_simplex(i,ihi) = work(i)
287 end do
288 end if
289 end function
290
291! ------------------------------------------------------------------------------
292 pure module function nm_get_simplex(this) result(p)
293 class(nelder_mead), intent(in) :: this
294 real(real64), allocatable, dimension(:,:) :: p
295 integer(int32) :: m, n
296 if (allocated(this%m_simplex)) then
297 m = size(this%m_simplex, 1)
298 n = size(this%m_simplex, 2)
299 allocate(p(m,n))
300 p = this%m_simplex
301 end if
302 end function
303
304! --------------------
305 module subroutine nm_set_simplex(this, x)
306 class(nelder_mead), intent(inout) :: this
307 real(real64), dimension(:,:) :: x
308 integer(int32) :: m, n
309 m = size(x, 1)
310 n = size(x, 2)
311 if (allocated(this%m_simplex)) then
312 if (size(this%m_simplex, 1) /= m .or. &
313 size(this%m_simplex, 2) /= n) then
314 deallocate(this%m_simplex)
315 allocate(this%m_simplex(m, n))
316 end if
317 else
318 allocate(this%m_simplex(m, n))
319 end if
320 this%m_simplex = x
321 end subroutine
322
323! ------------------------------------------------------------------------------
324 pure module function nm_get_size(this) result(x)
325 class(nelder_mead), intent(in) :: this
326 real(real64) :: x
327 x = this%m_initSize
328 end function
329
330! --------------------
331 module subroutine nm_set_size(this, x)
332 class(nelder_mead), intent(inout) :: this
333 real(real64), intent(in) :: x
334 this%m_initSize = x
335 end subroutine
336end submodule
nonlin_optimize