FMUTIL  0.1
Fortran Miscellaneous UTILities
rootfinding.f90
Go to the documentation of this file.
1 !##############################################################################
2 ! ________ _____ ______________
3 ! / ____/ |/ / / / /_ __/ _/ /
4 ! / /_ / /|_/ / / / / / / / // /
5 ! / __/ / / / / /_/ / / / _/ // /___
6 ! /_/ /_/ /_/\____/ /_/ /___/_____/
7 !
8 ! Copyright 2020 Bharat Mahajan
9 !
10 ! Licensed under the Apache License, Version 2.0 (the "License");
11 ! you may not use this file except in compliance with the License.
12 ! You may obtain a copy of the License at
13 !
14 ! http://www.apache.org/licenses/LICENSE-2.0
15 !
16 ! Unless required by applicable law or agreed to in writing, software
17 ! distributed under the License is distributed on an "AS IS" BASIS,
18 ! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 ! See the License for the specific language governing permissions and
20 ! limitations under the License.
21 !
28 !
29 !##############################################################################
30 
31 
33 
34  use fmutilbase
35 
37  use lapack95, only: gebal, hseqr
38 
39  implicit none
40 
42  integer, parameter :: max_iterations = 50
43 
45  abstract interface
46  pure function func(x) result(y)
47  import :: wp
48  implicit none
49  real(wp), intent(in) :: x
50  real(wp) :: y
51  end function func
52  end interface
53 
54 
55  contains
56 
57 
66  subroutine polyroots(c, r, error, BalanceOn)
67 
68  implicit none
69 
72  complex(WP), dimension(1:), intent(in) :: c
73 
75  complex(WP), dimension(:), allocatable, intent(out) :: r
76 
83  integer, intent(out) :: error
84 
88  logical, intent(in), optional :: BalanceOn
89 
90  integer :: n, nnz, ctr
91  integer, dimension(1) :: nzlocl, nzloct
92  complex(WP), dimension(:), allocatable :: cnz
93  complex(WP), dimension(:,:), allocatable :: H
94  logical :: UseBalance
95  integer :: ilo, ihi, info
96  real(WP), dimension(:), allocatable, target :: scale
97 
98  error = 0 ! all ok so far!
99 
100  ! original polynomial degree
101  n = size(c) - 1
102 
104  if (n <= 0) then
105  error = -1
106  return
107  end if
108 
110  if (all(abs(c) == 0) .EQV. .true.) then
111  error = -2
112  return
113  end if
114 
115  ! By default, balance the companion matrix
116  usebalance = .true.
117  if (present(balanceon)) usebalance = balanceon
118 
119  ! find the location of the first nonzero coefficient at the front and back
120  block
121  integer, dimension(size(c)) :: c1
122 
123  c1 = merge(1,0,real(c) /= 0 .OR. aimag(c) /= 0)
124  nzlocl = findloc(c1, 1)
125  nzloct = findloc(c1, 1, back=.true.)
126  end block
127 
130  nnz = nzloct(1) - nzlocl(1)
131 
132  ! copy the nonzero coefficients
133  cnz = c(nzlocl(1):nzloct(1))
134 
135  ! Relatively small leading coefficients are removed to avoid infinite
136  ! values similar to MATLAB's "root" command, ctr will contain the
137  ! location of the first valid significant coefficient
138  ctr = 1
139  do while (any(abs(cnz(ctr+1:)/cnz(ctr)) > inf))
140  ctr = ctr + 1
141  if (ctr == ubound(cnz,1)) exit
142  end do
143  nnz = nnz - (ctr - 1)
144 
145  ! Final polynomial degree is less than 1, so it has only zero roots
146  ! corresponding to the trailing zero coefficients
147  if (nnz <= 0) then
148  r = [(0, ctr = 1,(n+1-nzloct(1)))]
149  return
150  elseif (nnz == 1) then ! Special case: 1st degree polynomial
151  r = [complex(WP):: -cnz(1+ctr)/cnz(ctr), (0, ctr = 1,(n+1-nzloct(1)))]
152  return
153  end if
154 
155  ! Form the companion matrix that is an upper Hessenberg matrix
156  allocate(h(nnz,nnz))
157  h = (0,0)
158  h(1,:) = -cnz(ctr+1:)/cnz(ctr)
159  do ctr = 2,ubound(h,1)
160  h(ctr, ctr-1) = (1,0)
161  end do
162 
163  ! Balance the companion matrix for improved accuracy
164  if (usebalance) then
165  allocate(scale(nnz))
166  call gebal(h, ilo=ilo, ihi=ihi, job='B',info=info)
167  if (info /= 0) then
168  ! balancing failed, return error
169  error = -3
170  return
171  end if
172  else
173  ilo = 1
174  ihi = nnz
175  end if
176 
177  ! We have a balanced upper Hessenberg companion matrix, therefore
178  ! we can use this LAPACK computational routine directly
179  allocate(r(nnz))
180  call hseqr(h, r, ilo=ilo, ihi=ihi, job='E', info=info)
181  !call geevx(H, r, balanc='B', info=info)
182 
183  if (info /= 0) error = -4
184 
185  ! Gather all the roots
186  r = [complex(wp) :: r, (0, ctr = 1,(n+1-nzloct(1)))]
187 
188  end subroutine polyroots
189 
190 
191 
192 
193 
194 
195 
196 
197 
198 
199 
200 
201 
202 
203 
204 
208  pure subroutine root(a, b, ya, yb, tol, f, x, fx, niter, error)
209 
210  implicit none
211 
212  real(wp), intent(in) :: a
213  real(wp), intent(in) :: b
214  real(wp), intent(in) :: ya
215  real(wp), intent(in) :: yb
216  real(wp), intent(in) :: tol
217  procedure(func) :: f
218  real(wp), intent(out) :: x
219  real(wp), intent(out) :: fx
220  integer, intent(out) :: niter
221 
227  integer, intent(out) :: error
228 
229  real(wp) :: aa, bb, cc, fa, fb, fc, xm, ss, dd, ee, pp, qq, rr
230 
231  real(wp) :: tolx
232 
233  real(wp), parameter :: nearzero = 1.0e-20_wp
234 
235  logical :: done
236  integer :: itr
237 
238  aa = a
239  bb = b
240  fa = ya
241  fb = yb
242 
243  error = 0
244  niter = 0
245 
246  ! test
247  fa = f(a)
248  fb = f(b)
249 
250  ! Trivial cases
251  if (abs(fa) <= nearzero) then
252  x = aa
253  fx = fa
254  return
255  else if (abs(fb) <= nearzero) then
256  x = bb
257  fx = fb
258  return
259  end if
260 
261  ! if root is not bracketed then return error
262  if ((fa>0 .AND. fb>0) .OR. (fa<0 .AND. fb<0)) then
263  error = 1
264  niter = 0
265  return
266  end if
267 
268  ! algorithm start here
269  fc = fb
270  done = .false.
271  itr = 0
272  do while (done .EQV. .false. .AND. itr < max_iterations)
273 
274  ! if root is NOT bracketed between C and B, then C = A
275  if ((fc>0 .AND. fb>0) .OR. (fc<0 .AND. fb<0)) then
276  cc = aa
277  fc = fa
278  dd = bb - aa
279  ee = dd
280  end if
281  ! Make sure |f(c)| is bigger than |f(b)|
282  if (abs(fc) < abs(fb)) then
283  aa = bb
284  bb = cc
285  cc = aa
286  fa = fb
287  fb = fc
288  fc = fa
289  end if
290  ! tolerance on the independent variable
291  tolx = 2.0*eps*abs(bb) + 0.5*tol
292  ! middle point
293  xm = 0.5*(cc - bb)
294 
295  if (abs(xm) <= tolx .OR. abs(fa) < nearzero) then
296  ! root has been found
297  x = bb
298  done = .true.
299  fx = f(x)
300  else
301  if (abs(ee) >= tolx .AND. abs(fa) > abs(fb)) then
302  ss = fb/fa ! make sure |ss| < 1
303  if (abs(aa - cc) < nearzero) then
304  ! linear interpolation
305  pp = 2.0*xm*ss
306  qq = 1.0 - ss
307  else
308  ! Inverse quadratic interpolation
309  qq = fa/fc
310  rr = fb/fc
311  pp = ss*(2.0*xm*qq*(qq-rr)-(bb-aa)*(rr-1.0))
312  qq = (qq - 1.0)*(rr - 1.0)*(ss - 1.0)
313  end if
314 
315  if (pp > nearzero) qq = -qq
316  pp = abs(pp)
317  if ((2.0*pp) < min(3.0*xm*qq-abs(tolx*qq), abs(ee*qq))) then
318  ! use linear or inverse quadratic interpolation
319  ee = dd
320  dd = pp/qq
321  else
322  ! use bisection
323  dd = xm
324  ee = dd
325  end if
326  else
327  ! use bisection
328  dd = xm
329  ee = dd
330  end if
331 
332  aa = bb
333  fa = fb
334  if (abs(dd) > tolx) then
335  bb = bb + dd
336  else
337  ! Correction term too small, advance by at least tolx
338  if (xm > 0) then
339  bb = bb + abs(tolx)
340  else
341  bb = bb - abs(tolx)
342  end if
343  end if
344  fb = f(bb)
345  itr = itr + 1
346  end if
347  end do
348 
349  if (itr >= max_iterations) error = 2
350  niter = itr
351 
352  end subroutine root
353 
354 
355 
356 end module rootfinding
rootfinding::root
pure subroutine root(a, b, ya, yb, tol, f, x, fx, niter, error)
Subroutine for computing the root using Brent algorithm.
Definition: rootfinding.f90:209
rootfinding::max_iterations
integer, parameter max_iterations
Max iterations for all the root-finding.
Definition: rootfinding.f90:42
fmutilbase::wp
integer, parameter, public wp
Definition: fmutil_base.F90:41
fmutilbase::inf
real(wp), parameter, public inf
Infinity definition.
Definition: fmutil_base.F90:47
rootfinding::func
Interface of the function whose root needs to be computed.
Definition: rootfinding.f90:46
fmutilbase
FMUTIL Base Module.
Definition: fmutil_base.F90:31
rootfinding::polyroots
subroutine polyroots(c, r, error, BalanceOn)
Computes the complex roots of the given polynomial.
Definition: rootfinding.f90:67
rootfinding
RootFinding Module.
Definition: rootfinding.f90:32