FrontISTR  6.0.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  logical :: square_blocked
76 
77  nb = a%nb
78  square_blocked = nb > 0 .and. a%mb == nb
79  if (square_blocked) square_blocked = mod(a%n, nb) == 0
80  if (.not. square_blocked) then
81  write(*,*) 'hecmw_saamg_blockdiag_setup: A not square-blocked (nb,mb)=', nb, a%mb
82  call hecmw_saamg_abort('blockdiag_setup: operator not square-blocked')
83  end if
84  nnode = a%nbrow
85 
87  d%nb = nb; d%nnode = nnode
88  allocate(d%dblk(nb*nb*nnode), d%inv(nb*nb*nnode), stat=astat)
89  call hecmw_saamg_check_alloc(astat, 'blockdiag_setup (dblk/inv)')
90  d%dblk = 0.0d0
91 
92  ! each node's block is inverted independently with the BLAS-free block_inverse
93  ! (no per-thread workspace, no LAPACK -> no nested BLAS threading, OpenACC-ready)
94  !$omp parallel do default(none) shared(A, D, nb, nnode) private(inode, off, t, info)
95  do inode = 1, nnode
96  off = (inode-1)*nb*nb
97  do t = a%browptr(inode), a%browptr(inode+1)-1
98  if (a%bcol(t) == inode) then ! the diagonal block
99  d%dblk(off+1:off+nb*nb) = a%bval((t-1)*nb*nb+1:t*nb*nb)
100  exit
101  end if
102  end do
103  call block_inverse(nb, d%dblk(off+1:off+nb*nb), d%inv(off+1:off+nb*nb), info)
104  if (info /= 0) then
105  !$omp critical
106  write(*,'(a,i0)') 'hecmw_saamg_blockdiag_setup: singular diagonal block at node ', inode
107  !$omp end critical
108  call hecmw_saamg_abort('blockdiag_setup: singular diagonal block')
109  end if
110  end do
111  !$omp end parallel do
112  end subroutine hecmw_saamg_blockdiag_setup
113 
122  subroutine block_inverse(nb, a, ainv, info)
123  implicit none
124  integer(kind=kint), intent(in) :: nb
125  real(kind=kreal), intent(in) :: a(nb*nb) ! col-major a(i,j) = a((j-1)*nb+i)
126  real(kind=kreal), intent(out) :: ainv(nb*nb) ! col-major
127  integer(kind=kint), intent(out) :: info
128  real(kind=kreal) :: det, idet, piv, f, aug(nb, 2*nb)
129  integer(kind=kint) :: i, j, k, p
130  info = 0
131  select case (nb)
132  case (1)
133  if (a(1) == 0.0d0) then; info = 1; return; end if
134  ainv(1) = 1.0d0 / a(1)
135  case (2)
136  det = a(1)*a(4) - a(2)*a(3)
137  if (det == 0.0d0) then; info = 1; return; end if
138  idet = 1.0d0 / det
139  ainv(1) = a(4)*idet; ainv(2) = -a(2)*idet
140  ainv(3) = -a(3)*idet; ainv(4) = a(1)*idet
141  case (3)
142  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))
143  if (det == 0.0d0) then; info = 1; return; end if
144  idet = 1.0d0 / det
145  ainv(1) = (a(5)*a(9)-a(8)*a(6))*idet
146  ainv(2) = (a(8)*a(3)-a(2)*a(9))*idet
147  ainv(3) = (a(2)*a(6)-a(5)*a(3))*idet
148  ainv(4) = (a(7)*a(6)-a(4)*a(9))*idet
149  ainv(5) = (a(1)*a(9)-a(7)*a(3))*idet
150  ainv(6) = (a(4)*a(3)-a(1)*a(6))*idet
151  ainv(7) = (a(4)*a(8)-a(7)*a(5))*idet
152  ainv(8) = (a(7)*a(2)-a(1)*a(8))*idet
153  ainv(9) = (a(1)*a(5)-a(4)*a(2))*idet
154  case default
155  ! partial-pivoted Gauss-Jordan on the augmented [a | I] -> [I | a^{-1}]
156  do j = 1, nb
157  do i = 1, nb
158  aug(i, j) = a((j-1)*nb + i)
159  aug(i, nb+j) = 0.0d0
160  end do
161  aug(j, nb+j) = 1.0d0
162  end do
163  do k = 1, nb
164  p = k ! partial pivot
165  do i = k+1, nb
166  if (abs(aug(i,k)) > abs(aug(p,k))) p = i
167  end do
168  if (aug(p,k) == 0.0d0) then; info = 1; return; end if
169  if (p /= k) then
170  do j = 1, 2*nb
171  f = aug(k,j); aug(k,j) = aug(p,j); aug(p,j) = f
172  end do
173  end if
174  piv = aug(k,k)
175  do j = 1, 2*nb
176  aug(k,j) = aug(k,j) / piv
177  end do
178  do i = 1, nb
179  if (i == k) cycle
180  f = aug(i,k)
181  if (f == 0.0d0) cycle
182  do j = 1, 2*nb
183  aug(i,j) = aug(i,j) - f*aug(k,j)
184  end do
185  end do
186  end do
187  do j = 1, nb
188  do i = 1, nb
189  ainv((j-1)*nb + i) = aug(i, nb+j)
190  end do
191  end do
192  end select
193  end subroutine block_inverse
194 
196  subroutine hecmw_saamg_blockdiag_apply(D, x, y)
197  implicit none
198  type(hecmwst_saamg_blockdiag), intent(in) :: d
199  real(kind=kreal), intent(in) :: x(:)
200  real(kind=kreal), intent(out) :: y(:)
201  integer(kind=kint) :: inode, base, ii, jj, off, nb
202  real(kind=kreal) :: s
203 
204  nb = d%nb
205  ! GPU (OpenACC) vs CPU (OpenMP) via #ifdef _OPENACC (mutually exclusive; safe with
206  ! both -acc and -mp). Managed memory => no data clauses; inner nb x nb seq.
207 #ifdef _OPENACC
208  !$acc parallel loop gang vector private(base, off, ii, jj, s)
209 #else
210  !$omp parallel do default(none) shared(D, x, y, nb) private(inode, base, off, ii, jj, s)
211 #endif
212  do inode = 1, d%nnode
213  base = (inode-1)*nb
214  off = (inode-1)*nb*nb
215  !$acc loop seq
216  do ii = 1, nb ! y_i = sum_j Dinv(i,j) x_j (col-major)
217  s = 0.0d0
218  !$acc loop seq
219  do jj = 1, nb
220  s = s + d%inv(off + (jj-1)*nb + ii) * x(base+jj)
221  end do
222  y(base+ii) = s
223  end do
224  end do
225 #ifndef _OPENACC
226  !$omp end parallel do
227 #endif
228  end subroutine hecmw_saamg_blockdiag_apply
229 
235  subroutine hecmw_saamg_blockdiag_apply_bcsr(D, C, DC)
236  implicit none
237  type(hecmwst_saamg_blockdiag), intent(in) :: d
238  type(hecmwst_saamg_bcsr), intent(in) :: c
239  type(hecmwst_saamg_bcsr), intent(out) :: dc
240  integer(kind=kint) :: nb, mb, inode, off, t, b0, ii, jj, cc, astat
241  real(kind=kreal) :: s
242 
243  nb = d%nb; mb = c%mb
244  if (c%nb /= nb .or. c%nbrow /= d%nnode) then
245  write(*,*) 'hecmw_saamg_blockdiag_apply_bcsr: block-row mismatch', c%nb, nb, &
246  c%nbrow, d%nnode
247  call hecmw_saamg_abort('blockdiag_apply_bcsr: block-row mismatch')
248  end if
249 
250  ! DC = D^{-1} C : same block structure as C, each block DC_IK = D_I^{-1} C_IK
251  ! (nb x nb inverse times the nb x mb block, small dense matmul)
252  call hecmw_saamg_bcsr_free(dc)
253  dc%n = c%n; dc%ncol = c%ncol; dc%nb = nb; dc%mb = mb
254  dc%nbrow = c%nbrow; dc%nbcol = c%nbcol; dc%nnzb = c%nnzb
255  allocate(dc%browptr(c%nbrow+1), dc%bcol(c%nnzb), dc%bval(nb*mb*c%nnzb), stat=astat)
256  call hecmw_saamg_check_alloc(astat, 'blockdiag_apply_bcsr (DC)')
257  dc%browptr = c%browptr; dc%bcol = c%bcol
258 
259  !$omp parallel do default(none) shared(C, DC, D, nb, mb) private(inode, off, t, b0, cc, ii, jj, s)
260  do inode = 1, c%nbrow
261  off = (inode-1)*nb*nb
262  do t = c%browptr(inode), c%browptr(inode+1)-1
263  b0 = (t-1)*nb*mb
264  do cc = 1, mb ! DC(i,cc) = sum_j Dinv(i,j) C(j,cc)
265  do ii = 1, nb
266  s = 0.0d0
267  do jj = 1, nb
268  s = s + d%inv(off + (jj-1)*nb + ii) * c%bval(b0 + (cc-1)*nb + jj)
269  end do
270  dc%bval(b0 + (cc-1)*nb + ii) = s
271  end do
272  end do
273  end do
274  end do
275  !$omp end parallel do
276  ! block-only result: the sole consumer (smoothed prolongator) reads bval
277  end subroutine hecmw_saamg_blockdiag_apply_bcsr
278 
283  implicit none
284  type(hecmwst_saamg_blockdiag), intent(in) :: d
285  type(hecmwst_saamg_bcsr), intent(inout) :: c
286  integer(kind=kint) :: nb, mb, inode, off, t, b0, ii, jj, cc
287  real(kind=kreal) :: tmp(d%nb), s
288  nb = d%nb; mb = c%mb
289  if (c%nb /= nb .or. c%nbrow /= d%nnode) then
290  write(*,*) 'hecmw_saamg_blockdiag_apply_bcsr_inplace: block-row mismatch', &
291  c%nb, nb, c%nbrow, d%nnode
292  call hecmw_saamg_abort('blockdiag_apply_bcsr_inplace: block-row mismatch')
293  end if
294  !$omp parallel do default(none) shared(C, D, nb, mb) private(inode, off, t, b0, cc, ii, jj, s, tmp)
295  do inode = 1, c%nbrow
296  off = (inode-1)*nb*nb
297  do t = c%browptr(inode), c%browptr(inode+1)-1
298  b0 = (t-1)*nb*mb
299  do cc = 1, mb ! col cc <- Dinv * col cc (temp: read before overwrite)
300  do jj = 1, nb
301  tmp(jj) = c%bval(b0 + (cc-1)*nb + jj)
302  end do
303  do ii = 1, nb
304  s = 0.0d0
305  do jj = 1, nb
306  s = s + d%inv(off + (jj-1)*nb + ii) * tmp(jj)
307  end do
308  c%bval(b0 + (cc-1)*nb + ii) = s
309  end do
310  end do
311  end do
312  end do
313  !$omp end parallel do
315 
317  subroutine hecmw_saamg_blockdiag_matvec(D, x, y)
318  implicit none
319  type(hecmwst_saamg_blockdiag), intent(in) :: d
320  real(kind=kreal), intent(in) :: x(:)
321  real(kind=kreal), intent(out) :: y(:)
322  integer(kind=kint) :: inode, base, ii, jj, off
323  real(kind=kreal) :: s
324 
325  !$omp parallel do default(none) shared(D, x, y) private(inode, base, off, ii, jj, s)
326  do inode = 1, d%nnode
327  base = (inode-1)*d%nb
328  off = (inode-1)*d%nb*d%nb
329  do ii = 1, d%nb
330  s = 0.0d0
331  do jj = 1, d%nb
332  s = s + d%dblk(off + (jj-1)*d%nb + ii) * x(base+jj)
333  end do
334  y(base+ii) = s
335  end do
336  end do
337  !$omp end parallel do
338  end subroutine hecmw_saamg_blockdiag_matvec
339 
341  implicit none
342  type(hecmwst_saamg_blockdiag), intent(inout) :: d
343  if (allocated(d%dblk)) deallocate(d%dblk)
344  if (allocated(d%inv)) deallocate(d%inv)
345  d%nnode = 0; d%nb = 0
346  end subroutine hecmw_saamg_blockdiag_free
347 
348  !-----------------------------------------------------------------------------
349  ! Lanczos estimate of lambda_max(D^{-1} A)
350  !-----------------------------------------------------------------------------
351 
356  function hecmw_saamg_tridiag_max_eig(diag, offd, m) result(lmax)
357  implicit none
358  real(kind=kreal), intent(in) :: diag(:), offd(:)
359  integer(kind=kint), intent(in) :: m
360  real(kind=kreal) :: lmax
361  real(kind=kreal), allocatable :: d(:), e(:)
362  integer(kind=kint) :: info
363 #ifdef HECMW_WITH_LAPACK
364  external :: dsterf
365  if (m <= 1) then; lmax = diag(1); return; end if
366  allocate(d(m), e(m-1))
367  d(1:m) = diag(1:m); e(1:m-1) = offd(1:m-1)
368  call dsterf(m, d, e, info) ! eigenvalues ascending in d
369  if (info == 0) then; lmax = d(m); else; lmax = maxval(diag(1:m)); end if
370  deallocate(d, e)
371 #else
372  ! LAPACK-free fallback (largest diagonal = Rayleigh lower bound). Unreached in
373  ! practice: the backend aborts before setup when LAPACK is absent.
374  lmax = maxval(diag(1:m))
375 #endif
376  end function hecmw_saamg_tridiag_max_eig
377 
385  function hecmw_saamg_lanczos_lambda_max(A, D, niter) result(lambda)
386  implicit none
387  type(hecmwst_saamg_bcsr), intent(in) :: a
388  type(hecmwst_saamg_blockdiag), intent(in) :: d
389  integer(kind=kint), intent(in) :: niter
390  real(kind=kreal) :: lambda
391  real(kind=kreal), allocatable :: q(:), qp(:), w(:), aq(:), dv(:), alpha(:), beta(:)
392  real(kind=kreal) :: qdq, wdw, bprev
393  integer(kind=kint) :: j, i, m
394  integer(kind=8) :: seed
395 
396  allocate(q(a%n), qp(a%n), w(a%n), aq(a%n), dv(a%n), alpha(niter), beta(niter))
397 
398  ! fixed-seed deterministic start (tiny LCG) mapped to (-1,1)
399  seed = 88172645463325252_8
400  do i = 1, a%n
401  seed = seed*6364136223846793005_8 + 1442695040888963407_8
402  q(i) = real(seed, kreal)/9.223372036854776d18
403  end do
404  ! D-normalize the start vector: <q,q>_D = q^T D q
405  call hecmw_saamg_blockdiag_matvec(d, q, dv); qdq = dot_product(q, dv)
406  if (qdq <= 0.0d0) then; lambda = 0.0d0; deallocate(q,qp,w,aq,dv,alpha,beta); return; end if
407  q = q / sqrt(qdq); qp = 0.0d0; bprev = 0.0d0; m = 0
408 
409  do j = 1, niter
410  call hecmw_saamg_matvec(a, q, aq) ! aq = A q
411  call hecmw_saamg_blockdiag_apply(d, aq, w) ! w = D^{-1} A q = M q
412  alpha(j) = dot_product(aq, q) ! <M q, q>_D = (A q)^T q
413  w = w - alpha(j)*q - bprev*qp ! orthogonalize (3-term recurrence)
414  m = j
415  call hecmw_saamg_blockdiag_matvec(d, w, dv); wdw = dot_product(w, dv)
416  if (wdw <= 0.0d0) exit ! invariant subspace -> exact
417  beta(j) = sqrt(wdw)
418  if (j == niter) exit
419  qp = q; bprev = beta(j); q = w / beta(j)
420  end do
421 
422  lambda = hecmw_saamg_tridiag_max_eig(alpha, beta, m)
423  deallocate(q, qp, w, aq, dv, alpha, beta)
424  end function hecmw_saamg_lanczos_lambda_max
425 
426  !-----------------------------------------------------------------------------
427  ! Chebyshev smoother
428  !-----------------------------------------------------------------------------
429 
432  subroutine hecmw_saamg_cheb_setup(lambda_hat, deg, alpha, cheb)
433  implicit none
434  real(kind=kreal), intent(in) :: lambda_hat
435  integer(kind=kint), intent(in) :: deg
436  real(kind=kreal), intent(in) :: alpha
437  type(hecmwst_saamg_chebyshev), intent(out) :: cheb
438  real(kind=kreal) :: lmax, lmin
439  lmax = 1.1d0 * lambda_hat
440  lmin = lmax / alpha
441  cheb%deg = deg
442  cheb%theta = 0.5d0*(lmax + lmin)
443  cheb%delta = 0.5d0*(lmax - lmin)
444  cheb%sigma = cheb%theta / cheb%delta
445  end subroutine hecmw_saamg_cheb_setup
446 
449  subroutine hecmw_saamg_cheb_apply_local(A, D, cheb, b, x, zero_init)
450  implicit none
451  type(hecmwst_saamg_bcsr), intent(in) :: a
452  type(hecmwst_saamg_blockdiag), intent(in) :: d
453  type(hecmwst_saamg_chebyshev), intent(in) :: cheb
454  real(kind=kreal), intent(in) :: b(:)
455  real(kind=kreal), intent(inout) :: x(:)
456  logical, intent(in) :: zero_init
457  real(kind=kreal), allocatable :: r(:), p(:), dr(:), ap(:)
458  real(kind=kreal) :: rho, rho2, c1, c2
459  integer(kind=kint) :: it, n
460 
461  n = a%n
462  allocate(r(n), p(n), dr(n), ap(n))
463 
464  if (zero_init) then
465  x = 0.0d0
466  r = b
467  else
468  call hecmw_saamg_matvec(a, x, ap)
469  r = b - ap
470  end if
471 
472  call hecmw_saamg_blockdiag_apply(d, r, dr)
473  p = dr / cheb%theta
474  rho = 1.0d0 / cheb%sigma
475 
476  do it = 1, cheb%deg
477  x = x + p
478  call hecmw_saamg_matvec(a, p, ap)
479  r = r - ap
480  rho2 = 1.0d0 / (2.0d0*cheb%sigma - rho)
481  call hecmw_saamg_blockdiag_apply(d, r, dr)
482  c1 = rho2 * rho
483  c2 = 2.0d0 * rho2 / cheb%delta
484  p = c1 * p + c2 * dr
485  rho = rho2
486  end do
487 
488  deallocate(r, p, dr, ap)
489  end subroutine hecmw_saamg_cheb_apply_local
490 
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