FrontISTR  5.9.0
Large-scale structural analysis program with finit element method
hecmw_precond_SAAMG_smoother.F90
Go to the documentation of this file.
1 !-------------------------------------------------------------------------------
2 ! Copyright (c) 2026 FrontISTR Commons
3 ! This software is released under the MIT License, see License.txt
4 !-------------------------------------------------------------------------------
24  implicit none
25 
26  private
27  public :: hecmwst_saamg_blockdiag
36  public :: hecmwst_saamg_chebyshev
37  public :: hecmw_saamg_cheb_setup
39 
41  type hecmwst_saamg_blockdiag
42  integer(kind=kint) :: nnode = 0
43  integer(kind=kint) :: nb = 0
44  real(kind=kreal), allocatable :: dblk(:)
45  real(kind=kreal), allocatable :: inv(:)
46  end type hecmwst_saamg_blockdiag
47 
49  type hecmwst_saamg_chebyshev
50  integer(kind=kint) :: deg = 2
51  real(kind=kreal) :: theta = 0.0d0
52  real(kind=kreal) :: delta = 0.0d0
53  real(kind=kreal) :: sigma = 0.0d0
54  end type hecmwst_saamg_chebyshev
55 
56 contains
57 
58  !-----------------------------------------------------------------------------
59  ! Node-block diagonal D
60  !-----------------------------------------------------------------------------
61 
70  subroutine hecmw_saamg_blockdiag_setup(A, D)
71  implicit none
72  type(hecmwst_saamg_bcsr), intent(in) :: a
73  type(hecmwst_saamg_blockdiag), intent(out) :: d
74  integer(kind=kint) :: nb, nnode, inode, off, t, info, astat
75 
76  nb = a%nb
77  if (nb <= 0 .or. a%mb /= nb .or. mod(a%n, nb) /= 0) then
78  write(*,*) 'hecmw_saamg_blockdiag_setup: A not square-blocked (nb,mb)=', nb, a%mb
79  call hecmw_saamg_abort('blockdiag_setup: operator not square-blocked')
80  end if
81  nnode = a%nbrow
82 
84  d%nb = nb; d%nnode = nnode
85  allocate(d%dblk(nb*nb*nnode), d%inv(nb*nb*nnode), stat=astat)
86  call hecmw_saamg_check_alloc(astat, 'blockdiag_setup (dblk/inv)')
87  d%dblk = 0.0d0
88 
89  ! each node's block is inverted independently with the BLAS-free block_inverse
90  ! (no per-thread workspace, no LAPACK -> no nested BLAS threading, OpenACC-ready)
91  !$omp parallel do default(none) shared(A, D, nb, nnode) private(inode, off, t, info)
92  do inode = 1, nnode
93  off = (inode-1)*nb*nb
94  do t = a%browptr(inode), a%browptr(inode+1)-1
95  if (a%bcol(t) == inode) then ! the diagonal block
96  d%dblk(off+1:off+nb*nb) = a%bval((t-1)*nb*nb+1:t*nb*nb)
97  exit
98  end if
99  end do
100  call block_inverse(nb, d%dblk(off+1:off+nb*nb), d%inv(off+1:off+nb*nb), info)
101  if (info /= 0) then
102  write(*,'(a,i0)') 'hecmw_saamg_blockdiag_setup: singular diagonal block at node ', inode
103  call hecmw_saamg_abort('blockdiag_setup: singular diagonal block')
104  end if
105  end do
106  !$omp end parallel do
107  end subroutine hecmw_saamg_blockdiag_setup
108 
117  subroutine block_inverse(nb, a, ainv, info)
118  implicit none
119  integer(kind=kint), intent(in) :: nb
120  real(kind=kreal), intent(in) :: a(nb*nb) ! col-major a(i,j) = a((j-1)*nb+i)
121  real(kind=kreal), intent(out) :: ainv(nb*nb) ! col-major
122  integer(kind=kint), intent(out) :: info
123  real(kind=kreal) :: det, idet, piv, f, aug(nb, 2*nb)
124  integer(kind=kint) :: i, j, k, p
125  info = 0
126  select case (nb)
127  case (1)
128  if (a(1) == 0.0d0) then; info = 1; return; end if
129  ainv(1) = 1.0d0 / a(1)
130  case (2)
131  det = a(1)*a(4) - a(2)*a(3)
132  if (det == 0.0d0) then; info = 1; return; end if
133  idet = 1.0d0 / det
134  ainv(1) = a(4)*idet; ainv(2) = -a(2)*idet
135  ainv(3) = -a(3)*idet; ainv(4) = a(1)*idet
136  case (3)
137  det = a(1)*(a(5)*a(9)-a(8)*a(6)) - a(4)*(a(2)*a(9)-a(8)*a(3)) + a(7)*(a(2)*a(6)-a(5)*a(3))
138  if (det == 0.0d0) then; info = 1; return; end if
139  idet = 1.0d0 / det
140  ainv(1) = (a(5)*a(9)-a(8)*a(6))*idet
141  ainv(2) = (a(8)*a(3)-a(2)*a(9))*idet
142  ainv(3) = (a(2)*a(6)-a(5)*a(3))*idet
143  ainv(4) = (a(7)*a(6)-a(4)*a(9))*idet
144  ainv(5) = (a(1)*a(9)-a(7)*a(3))*idet
145  ainv(6) = (a(4)*a(3)-a(1)*a(6))*idet
146  ainv(7) = (a(4)*a(8)-a(7)*a(5))*idet
147  ainv(8) = (a(7)*a(2)-a(1)*a(8))*idet
148  ainv(9) = (a(1)*a(5)-a(4)*a(2))*idet
149  case default
150  ! partial-pivoted Gauss-Jordan on the augmented [a | I] -> [I | a^{-1}]
151  do j = 1, nb
152  do i = 1, nb
153  aug(i, j) = a((j-1)*nb + i)
154  aug(i, nb+j) = 0.0d0
155  end do
156  aug(j, nb+j) = 1.0d0
157  end do
158  do k = 1, nb
159  p = k ! partial pivot
160  do i = k+1, nb
161  if (abs(aug(i,k)) > abs(aug(p,k))) p = i
162  end do
163  if (aug(p,k) == 0.0d0) then; info = 1; return; end if
164  if (p /= k) then
165  do j = 1, 2*nb
166  f = aug(k,j); aug(k,j) = aug(p,j); aug(p,j) = f
167  end do
168  end if
169  piv = aug(k,k)
170  do j = 1, 2*nb
171  aug(k,j) = aug(k,j) / piv
172  end do
173  do i = 1, nb
174  if (i == k) cycle
175  f = aug(i,k)
176  if (f == 0.0d0) cycle
177  do j = 1, 2*nb
178  aug(i,j) = aug(i,j) - f*aug(k,j)
179  end do
180  end do
181  end do
182  do j = 1, nb
183  do i = 1, nb
184  ainv((j-1)*nb + i) = aug(i, nb+j)
185  end do
186  end do
187  end select
188  end subroutine block_inverse
189 
191  subroutine hecmw_saamg_blockdiag_apply(D, x, y)
192  implicit none
193  type(hecmwst_saamg_blockdiag), intent(in) :: d
194  real(kind=kreal), intent(in) :: x(:)
195  real(kind=kreal), intent(out) :: y(:)
196  integer(kind=kint) :: inode, base, ii, jj, off, nb
197  real(kind=kreal) :: s
198 
199  nb = d%nb
200  ! GPU (OpenACC) vs CPU (OpenMP) via #ifdef _OPENACC (mutually exclusive; safe with
201  ! both -acc and -mp). Managed memory => no data clauses; inner nb x nb seq.
202 #ifdef _OPENACC
203  !$acc parallel loop gang vector private(base, off, ii, jj, s)
204 #else
205  !$omp parallel do default(none) shared(D, x, y, nb) private(inode, base, off, ii, jj, s)
206 #endif
207  do inode = 1, d%nnode
208  base = (inode-1)*nb
209  off = (inode-1)*nb*nb
210  !$acc loop seq
211  do ii = 1, nb ! y_i = sum_j Dinv(i,j) x_j (col-major)
212  s = 0.0d0
213  !$acc loop seq
214  do jj = 1, nb
215  s = s + d%inv(off + (jj-1)*nb + ii) * x(base+jj)
216  end do
217  y(base+ii) = s
218  end do
219  end do
220 #ifndef _OPENACC
221  !$omp end parallel do
222 #endif
223  end subroutine hecmw_saamg_blockdiag_apply
224 
230  subroutine hecmw_saamg_blockdiag_apply_bcsr(D, C, DC)
231  implicit none
232  type(hecmwst_saamg_blockdiag), intent(in) :: d
233  type(hecmwst_saamg_bcsr), intent(in) :: c
234  type(hecmwst_saamg_bcsr), intent(out) :: dc
235  integer(kind=kint) :: nb, mb, inode, off, t, b0, ii, jj, cc, astat
236  real(kind=kreal) :: s
237 
238  nb = d%nb; mb = c%mb
239  if (c%nb /= nb .or. c%nbrow /= d%nnode) then
240  write(*,*) 'hecmw_saamg_blockdiag_apply_bcsr: block-row mismatch', c%nb, nb, &
241  c%nbrow, d%nnode
242  call hecmw_saamg_abort('blockdiag_apply_bcsr: block-row mismatch')
243  end if
244 
245  ! DC = D^{-1} C : same block structure as C, each block DC_IK = D_I^{-1} C_IK
246  ! (nb x nb inverse times the nb x mb block, small dense matmul)
247  call hecmw_saamg_bcsr_free(dc)
248  dc%n = c%n; dc%ncol = c%ncol; dc%nb = nb; dc%mb = mb
249  dc%nbrow = c%nbrow; dc%nbcol = c%nbcol; dc%nnzb = c%nnzb
250  allocate(dc%browptr(c%nbrow+1), dc%bcol(c%nnzb), dc%bval(nb*mb*c%nnzb), stat=astat)
251  call hecmw_saamg_check_alloc(astat, 'blockdiag_apply_bcsr (DC)')
252  dc%browptr = c%browptr; dc%bcol = c%bcol
253 
254  !$omp parallel do default(none) shared(C, DC, D, nb, mb) private(inode, off, t, b0, cc, ii, jj, s)
255  do inode = 1, c%nbrow
256  off = (inode-1)*nb*nb
257  do t = c%browptr(inode), c%browptr(inode+1)-1
258  b0 = (t-1)*nb*mb
259  do cc = 1, mb ! DC(i,cc) = sum_j Dinv(i,j) C(j,cc)
260  do ii = 1, nb
261  s = 0.0d0
262  do jj = 1, nb
263  s = s + d%inv(off + (jj-1)*nb + ii) * c%bval(b0 + (cc-1)*nb + jj)
264  end do
265  dc%bval(b0 + (cc-1)*nb + ii) = s
266  end do
267  end do
268  end do
269  end do
270  !$omp end parallel do
271  ! block-only result: the sole consumer (smoothed prolongator) reads bval
272  end subroutine hecmw_saamg_blockdiag_apply_bcsr
273 
278  implicit none
279  type(hecmwst_saamg_blockdiag), intent(in) :: d
280  type(hecmwst_saamg_bcsr), intent(inout) :: c
281  integer(kind=kint) :: nb, mb, inode, off, t, b0, ii, jj, cc
282  real(kind=kreal) :: tmp(d%nb), s
283  nb = d%nb; mb = c%mb
284  if (c%nb /= nb .or. c%nbrow /= d%nnode) then
285  write(*,*) 'hecmw_saamg_blockdiag_apply_bcsr_inplace: block-row mismatch', &
286  c%nb, nb, c%nbrow, d%nnode
287  call hecmw_saamg_abort('blockdiag_apply_bcsr_inplace: block-row mismatch')
288  end if
289  !$omp parallel do default(none) shared(C, D, nb, mb) private(inode, off, t, b0, cc, ii, jj, s, tmp)
290  do inode = 1, c%nbrow
291  off = (inode-1)*nb*nb
292  do t = c%browptr(inode), c%browptr(inode+1)-1
293  b0 = (t-1)*nb*mb
294  do cc = 1, mb ! col cc <- Dinv * col cc (temp: read before overwrite)
295  do jj = 1, nb
296  tmp(jj) = c%bval(b0 + (cc-1)*nb + jj)
297  end do
298  do ii = 1, nb
299  s = 0.0d0
300  do jj = 1, nb
301  s = s + d%inv(off + (jj-1)*nb + ii) * tmp(jj)
302  end do
303  c%bval(b0 + (cc-1)*nb + ii) = s
304  end do
305  end do
306  end do
307  end do
308  !$omp end parallel do
310 
312  subroutine hecmw_saamg_blockdiag_matvec(D, x, y)
313  implicit none
314  type(hecmwst_saamg_blockdiag), intent(in) :: d
315  real(kind=kreal), intent(in) :: x(:)
316  real(kind=kreal), intent(out) :: y(:)
317  integer(kind=kint) :: inode, base, ii, jj, off
318  real(kind=kreal) :: s
319 
320  !$omp parallel do default(none) shared(D, x, y) private(inode, base, off, ii, jj, s)
321  do inode = 1, d%nnode
322  base = (inode-1)*d%nb
323  off = (inode-1)*d%nb*d%nb
324  do ii = 1, d%nb
325  s = 0.0d0
326  do jj = 1, d%nb
327  s = s + d%dblk(off + (jj-1)*d%nb + ii) * x(base+jj)
328  end do
329  y(base+ii) = s
330  end do
331  end do
332  !$omp end parallel do
333  end subroutine hecmw_saamg_blockdiag_matvec
334 
336  implicit none
337  type(hecmwst_saamg_blockdiag), intent(inout) :: d
338  if (allocated(d%dblk)) deallocate(d%dblk)
339  if (allocated(d%inv)) deallocate(d%inv)
340  d%nnode = 0; d%nb = 0
341  end subroutine hecmw_saamg_blockdiag_free
342 
343  !-----------------------------------------------------------------------------
344  ! Lanczos estimate of lambda_max(D^{-1} A)
345  !-----------------------------------------------------------------------------
346 
351  function hecmw_saamg_tridiag_max_eig(diag, offd, m) result(lmax)
352  implicit none
353  real(kind=kreal), intent(in) :: diag(:), offd(:)
354  integer(kind=kint), intent(in) :: m
355  real(kind=kreal) :: lmax
356  real(kind=kreal), allocatable :: d(:), e(:)
357  integer(kind=kint) :: info
358 #ifdef HECMW_WITH_LAPACK
359  external :: dsterf
360  if (m <= 1) then; lmax = diag(1); return; end if
361  allocate(d(m), e(m-1))
362  d(1:m) = diag(1:m); e(1:m-1) = offd(1:m-1)
363  call dsterf(m, d, e, info) ! eigenvalues ascending in d
364  if (info == 0) then; lmax = d(m); else; lmax = maxval(diag(1:m)); end if
365  deallocate(d, e)
366 #else
367  ! LAPACK-free fallback (largest diagonal = Rayleigh lower bound). Unreached in
368  ! practice: the backend aborts before setup when LAPACK is absent.
369  lmax = maxval(diag(1:m))
370 #endif
371  end function hecmw_saamg_tridiag_max_eig
372 
380  function hecmw_saamg_lanczos_lambda_max(A, D, niter) result(lambda)
381  implicit none
382  type(hecmwst_saamg_bcsr), intent(in) :: a
383  type(hecmwst_saamg_blockdiag), intent(in) :: d
384  integer(kind=kint), intent(in) :: niter
385  real(kind=kreal) :: lambda
386  real(kind=kreal), allocatable :: q(:), qp(:), w(:), aq(:), dv(:), alpha(:), beta(:)
387  real(kind=kreal) :: qdq, wdw, bprev
388  integer(kind=kint) :: j, i, m
389  integer(kind=8) :: seed
390 
391  allocate(q(a%n), qp(a%n), w(a%n), aq(a%n), dv(a%n), alpha(niter), beta(niter))
392 
393  ! fixed-seed deterministic start (tiny LCG) mapped to (-1,1)
394  seed = 88172645463325252_8
395  do i = 1, a%n
396  seed = seed*6364136223846793005_8 + 1442695040888963407_8
397  q(i) = real(seed, kreal)/9.223372036854776d18
398  end do
399  ! D-normalize the start vector: <q,q>_D = q^T D q
400  call hecmw_saamg_blockdiag_matvec(d, q, dv); qdq = dot_product(q, dv)
401  if (qdq <= 0.0d0) then; lambda = 0.0d0; deallocate(q,qp,w,aq,dv,alpha,beta); return; end if
402  q = q / sqrt(qdq); qp = 0.0d0; bprev = 0.0d0; m = 0
403 
404  do j = 1, niter
405  call hecmw_saamg_matvec(a, q, aq) ! aq = A q
406  call hecmw_saamg_blockdiag_apply(d, aq, w) ! w = D^{-1} A q = M q
407  alpha(j) = dot_product(aq, q) ! <M q, q>_D = (A q)^T q
408  w = w - alpha(j)*q - bprev*qp ! orthogonalize (3-term recurrence)
409  m = j
410  call hecmw_saamg_blockdiag_matvec(d, w, dv); wdw = dot_product(w, dv)
411  if (wdw <= 0.0d0) exit ! invariant subspace -> exact
412  beta(j) = sqrt(wdw)
413  if (j == niter) exit
414  qp = q; bprev = beta(j); q = w / beta(j)
415  end do
416 
417  lambda = hecmw_saamg_tridiag_max_eig(alpha, beta, m)
418  deallocate(q, qp, w, aq, dv, alpha, beta)
419  end function hecmw_saamg_lanczos_lambda_max
420 
421  !-----------------------------------------------------------------------------
422  ! Chebyshev smoother
423  !-----------------------------------------------------------------------------
424 
427  subroutine hecmw_saamg_cheb_setup(lambda_hat, deg, alpha, cheb)
428  implicit none
429  real(kind=kreal), intent(in) :: lambda_hat
430  integer(kind=kint), intent(in) :: deg
431  real(kind=kreal), intent(in) :: alpha
432  type(hecmwst_saamg_chebyshev), intent(out) :: cheb
433  real(kind=kreal) :: lmax, lmin
434  lmax = 1.1d0 * lambda_hat
435  lmin = lmax / alpha
436  cheb%deg = deg
437  cheb%theta = 0.5d0*(lmax + lmin)
438  cheb%delta = 0.5d0*(lmax - lmin)
439  cheb%sigma = cheb%theta / cheb%delta
440  end subroutine hecmw_saamg_cheb_setup
441 
444  subroutine hecmw_saamg_cheb_apply_local(A, D, cheb, b, x, zero_init)
445  implicit none
446  type(hecmwst_saamg_bcsr), intent(in) :: a
447  type(hecmwst_saamg_blockdiag), intent(in) :: d
448  type(hecmwst_saamg_chebyshev), intent(in) :: cheb
449  real(kind=kreal), intent(in) :: b(:)
450  real(kind=kreal), intent(inout) :: x(:)
451  logical, intent(in) :: zero_init
452  real(kind=kreal), allocatable :: r(:), p(:), dr(:), ap(:)
453  real(kind=kreal) :: rho, rho2, c1, c2
454  integer(kind=kint) :: it, n
455 
456  n = a%n
457  allocate(r(n), p(n), dr(n), ap(n))
458 
459  if (zero_init) then
460  x = 0.0d0
461  r = b
462  else
463  call hecmw_saamg_matvec(a, x, ap)
464  r = b - ap
465  end if
466 
467  call hecmw_saamg_blockdiag_apply(d, r, dr)
468  p = dr / cheb%theta
469  rho = 1.0d0 / cheb%sigma
470 
471  do it = 1, cheb%deg
472  x = x + p
473  call hecmw_saamg_matvec(a, p, ap)
474  r = r - ap
475  rho2 = 1.0d0 / (2.0d0*cheb%sigma - rho)
476  call hecmw_saamg_blockdiag_apply(d, r, dr)
477  c1 = rho2 * rho
478  c2 = 2.0d0 * rho2 / cheb%delta
479  p = c1 * p + c2 * dr
480  rho = rho2
481  end do
482 
483  deallocate(r, p, dr, ap)
484  end subroutine hecmw_saamg_cheb_apply_local
485 
Smoothed Aggregation AMG preconditioner : lightweight comm table.
subroutine, public hecmw_saamg_check_alloc(ier, what)
Report a failed allocation (stat /= 0) with a clear message and a collective abort,...
subroutine, public hecmw_saamg_abort(msg)
Fatal-error termination with a clear message. Under MPI this is a COLLECTIVE abort (hecmw_abort -> MP...
Smoothed Aggregation AMG preconditioner : internal block-CSR matrix.
subroutine, public hecmw_saamg_bcsr_free(A)
Release the storage held by a hecmwST_saamg_bcsr.
subroutine, public hecmw_saamg_matvec(A, x, y)
Sparse matrix-vector product : y = A x.
Smoothed Aggregation AMG preconditioner : smoother building blocks.
subroutine, public hecmw_saamg_cheb_setup(lambda_hat, deg, alpha, cheb)
Set Chebyshev interval [lmax/alpha, lmax] with lmax = 1.1 * lambda_hat. lambda_hat is the (already sa...
subroutine, public hecmw_saamg_blockdiag_apply_bcsr_inplace(D, C)
Apply C <- D^{-1} C in place (each nb x mb block solved against the node's LU). Same as blockdiag_app...
real(kind=kreal) function, public hecmw_saamg_lanczos_lambda_max(A, D, niter)
Estimate lambda_max(D^{-1}A) by D-inner-product Lanczos. M = D^{-1}A is self-adjoint in <u,...
subroutine, public hecmw_saamg_blockdiag_apply_bcsr(D, C, DC)
Apply DC = D^{-1} C block-row-wise to a block-CSR matrix C (reusing the stored block LU)....
subroutine, public hecmw_saamg_cheb_apply_local(A, D, cheb, b, x, zero_init)
Apply the Chebyshev smoother: x <- x + p_k(D^{-1}A)(b - A x). zero_init = .true. assumes x=0 on entry...
subroutine, public hecmw_saamg_blockdiag_matvec(D, x, y)
Apply y = D x (block-wise dense matvec using the original blocks).
subroutine, public hecmw_saamg_blockdiag_apply(D, x, y)
Apply y = D^{-1} x (block-wise small dense matmul against the stored inverse).
subroutine, public hecmw_saamg_blockdiag_setup(A, D)
Extract the nb x nb diagonal blocks of A, keep them, and store the explicit inverse D_i^{-1} of each ...
real(kind=kreal) function, public hecmw_saamg_tridiag_max_eig(diag, offd, m)
Largest eigenvalue of a symmetric tridiagonal matrix (diagonal diag(1:m), sub/super-diagonal offd(1:m...
Smoothed Aggregation AMG preconditioner : kind parameters.
integer(kind=4), parameter kreal