FrontISTR  5.9.0
Large-scale structural analysis program with finit element method
hecmw_precond_SAAMG_aggregate.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 !-------------------------------------------------------------------------------
17  implicit none
18 
19  private
20  public :: hecmwst_saamg_nodegraph
23  public :: hecmw_saamg_aggregate
24  public :: hecmw_saamg_write_vtk
25 
27  type hecmwst_saamg_nodegraph
28  integer(kind=kint) :: n = 0
29  integer(kind=kint), allocatable :: index(:)
30  integer(kind=kint), allocatable :: item(:)
31  end type hecmwst_saamg_nodegraph
32 
33 contains
34 
42  subroutine hecmw_saamg_build_nodegraph(A, g, theta)
43  implicit none
44  type(hecmwst_saamg_bcsr), intent(in) :: a
45  type(hecmwst_saamg_nodegraph), intent(out) :: g
46  real(kind=kreal), optional, intent(in) :: theta
47  integer(kind=kint) :: nb, nnode, inode, ii, k, jnode, deg, pos, t, nt
48  integer(kind=kint), allocatable :: mark(:), touched(:)
49  real(kind=kreal), allocatable :: dnorm(:), acc(:)
50  real(kind=kreal) :: th2
51  logical :: filt
52 
53  nb = a%nb
54  nnode = a%nbrow
56  g%n = nnode
57  filt = present(theta)
58  if (filt) filt = (theta > 0.0d0)
59  th2 = 0.0d0
60  if (filt) th2 = theta*theta
61 
62  allocate(g%index(nnode+1), mark(nnode), touched(nnode), acc(nnode), dnorm(nnode))
63  mark = 0; acc = 0.0d0; dnorm = 0.0d0
64 
65  ! diagonal block Frobenius norms (only needed for the strength filter):
66  ! sum of squares of the diagonal block's entries, read directly from bval
67  if (filt) then
68  do inode = 1, nnode
69  do k = a%browptr(inode), a%browptr(inode+1)-1
70  if (a%bcol(k) == inode) then
71  do ii = 1, nb*a%mb
72  dnorm(inode) = dnorm(inode) + a%bval((k-1)*nb*a%mb+ii)**2
73  end do
74  exit
75  end if
76  end do
77  dnorm(inode) = sqrt(dnorm(inode))
78  end do
79  end if
80 
81  ! pass 1: degree count (off-diagonal blocks surviving the strength filter)
82  g%index = 0
83  do inode = 1, nnode
84  call gather_blocks(a, nb, inode, mark, touched, acc, nt)
85  deg = 0
86  do t = 1, nt
87  jnode = touched(t)
88  if (.not. filt) then
89  deg = deg + 1
90  else if (acc(jnode) >= th2 * dnorm(inode) * dnorm(jnode)) then
91  deg = deg + 1
92  end if
93  end do
94  g%index(inode+1) = deg
95  end do
96  do inode = 1, nnode
97  g%index(inode+1) = g%index(inode+1) + g%index(inode)
98  end do
99  allocate(g%item(g%index(nnode+1)))
100 
101  ! pass 2: fill the surviving neighbor ids (same gather order as pass 1)
102  mark = 0
103  do inode = 1, nnode
104  call gather_blocks(a, nb, inode, mark, touched, acc, nt)
105  pos = g%index(inode)
106  do t = 1, nt
107  jnode = touched(t)
108  if (.not. filt) then
109  pos = pos + 1; g%item(pos) = jnode
110  else if (acc(jnode) >= th2 * dnorm(inode) * dnorm(jnode)) then
111  pos = pos + 1; g%item(pos) = jnode
112  end if
113  end do
114  end do
115  deallocate(mark, touched, acc, dnorm)
116  end subroutine hecmw_saamg_build_nodegraph
117 
122  subroutine gather_blocks(A, nb, inode, mark, touched, acc, nt)
123  type(hecmwst_saamg_bcsr), intent(in) :: a
124  integer(kind=kint), intent(in) :: nb, inode
125  integer(kind=kint), intent(inout) :: mark(:), touched(:)
126  real(kind=kreal), intent(inout) :: acc(:)
127  integer(kind=kint), intent(out) :: nt
128  integer(kind=kint) :: t, jnode, nrow, e, b0
129  real(kind=kreal) :: bn
130  ! Only internal node columns form graph edges: nrow = A%nbrow is the number of
131  ! (internal) row-nodes; halo columns (jnode > nrow, present when A is the
132  ! rectangular distributed operator) are dropped -> uncoupled aggregation.
133  ! For a square (sequential) A this skip never triggers.
134  nrow = a%nbrow
135  nt = 0
136  do t = a%browptr(inode), a%browptr(inode+1)-1
137  jnode = a%bcol(t)
138  if (jnode == inode .or. jnode > nrow) cycle
139  b0 = (t-1)*nb*a%mb; bn = 0.0d0
140  do e = 1, nb*a%mb
141  bn = bn + a%bval(b0+e)*a%bval(b0+e)
142  end do
143  mark(jnode) = inode; nt = nt + 1; touched(nt) = jnode; acc(jnode) = bn
144  end do
145  end subroutine gather_blocks
146 
148  implicit none
149  type(hecmwst_saamg_nodegraph), intent(inout) :: g
150  if (allocated(g%index)) deallocate(g%index)
151  if (allocated(g%item)) deallocate(g%item)
152  g%n = 0
153  end subroutine hecmw_saamg_nodegraph_free
154 
157  subroutine hecmw_saamg_write_vtk(fname, coord, nnode, aggr)
158  implicit none
159  character(len=*), intent(in) :: fname
160  real(kind=kreal), intent(in) :: coord(:)
161  integer(kind=kint), intent(in) :: nnode
162  integer(kind=kint), intent(in) :: aggr(:)
163  integer(kind=kint) :: iu, i
164 
165  open(newunit=iu, file=fname, status='replace', action='write')
166  write(iu,'(a)') '# vtk DataFile Version 3.0'
167  write(iu,'(a)') 'SA-AMG aggregates'
168  write(iu,'(a)') 'ASCII'
169  write(iu,'(a)') 'DATASET UNSTRUCTURED_GRID'
170  write(iu,'(a,i0,a)') 'POINTS ', nnode, ' double'
171  do i = 1, nnode
172  write(iu,'(es16.8,1x,es16.8,1x,es16.8)') &
173  coord(3*i-2), coord(3*i-1), coord(3*i)
174  end do
175  write(iu,'(a,i0,1x,i0)') 'CELLS ', nnode, 2*nnode
176  do i = 1, nnode
177  write(iu,'(a,i0)') '1 ', i-1
178  end do
179  write(iu,'(a,i0)') 'CELL_TYPES ', nnode
180  do i = 1, nnode
181  write(iu,'(a)') '1'
182  end do
183  write(iu,'(a,i0)') 'POINT_DATA ', nnode
184  write(iu,'(a)') 'SCALARS agg_id int 1'
185  write(iu,'(a)') 'LOOKUP_TABLE default'
186  do i = 1, nnode
187  write(iu,'(i0)') aggr(i)
188  end do
189  close(iu)
190  end subroutine hecmw_saamg_write_vtk
191 
193  subroutine build_scan_order(g, omode, gid, order)
194  type(hecmwst_saamg_nodegraph), intent(in) :: g
195  integer(kind=kint), intent(in) :: omode
196  integer(kind=kint), optional, intent(in) :: gid(:)
197  integer(kind=kint), intent(out) :: order(:)
198  integer(kind=kint), allocatable :: key(:), visited(:), queue(:)
199  integer(kind=kint) :: n, i, k, pos, qh, qt, s
200  integer(kind=8) :: h
201  n = g%n
202  select case (omode)
203  case (1) ! BFS (graph) order: layer-by-layer tiling from the first node
204  allocate(visited(n), queue(n)); visited = 0
205  pos = 0
206  do s = 1, n
207  if (visited(s) /= 0) cycle
208  qh = 1; qt = 1; queue(1) = s; visited(s) = 1
209  do while (qh <= qt)
210  i = queue(qh); qh = qh + 1
211  pos = pos + 1; order(pos) = i
212  do k = g%index(i)+1, g%index(i+1)
213  if (visited(g%item(k)) == 0) then
214  visited(g%item(k)) = 1; qt = qt + 1; queue(qt) = g%item(k)
215  end if
216  end do
217  end do
218  end do
219  deallocate(visited, queue)
220  case (2) ! deterministic hash order of node ids (partition-independent
221  ! priority when gid carries GLOBAL ids)
222  allocate(key(n))
223  do i = 1, n
224  if (present(gid)) then; h = int(gid(i), 8); else; h = int(i, 8); end if
225  h = ieor(h, ishft(h, 21)); h = ieor(h, ishft(h, -35)); h = ieor(h, ishft(h, 4))
226  h = h * 2685821657736338717_8
227  key(i) = int(iand(h, 2147483647_8), kind=kint)
228  end do
229  call sort_by_key(key, order, n)
230  deallocate(key)
231  case (3, 4) ! degree order (3 = min first, 4 = max first)
232  allocate(key(n))
233  do i = 1, n
234  key(i) = g%index(i+1) - g%index(i)
235  if (omode == 4) key(i) = -key(i)
236  end do
237  call sort_by_key(key, order, n)
238  deallocate(key)
239  case default ! natural (legacy)
240  do i = 1, n
241  order(i) = i
242  end do
243  end select
244  end subroutine build_scan_order
245 
247  subroutine sort_by_key(key, order, n)
248  integer(kind=kint), intent(in) :: key(:)
249  integer(kind=kint), intent(out) :: order(:)
250  integer(kind=kint), intent(in) :: n
251  integer(kind=kint), allocatable :: work(:), kwork(:), karr(:)
252  integer(kind=kint) :: i
253  allocate(work(n), kwork(n), karr(n))
254  do i = 1, n
255  order(i) = i
256  end do
257  karr(1:n) = key(1:n)
258  call msort(karr, order, kwork, work, 1, n)
259  deallocate(work, kwork, karr)
260  contains
261  recursive subroutine msort(k, idx, kw, iw, lo, hi)
262  integer(kind=kint), intent(inout) :: k(:), idx(:), kw(:), iw(:)
263  integer(kind=kint), intent(in) :: lo, hi
264  integer(kind=kint) :: mid, a, b, c
265  if (hi - lo < 1) return
266  mid = (lo + hi) / 2
267  call msort(k, idx, kw, iw, lo, mid)
268  call msort(k, idx, kw, iw, mid+1, hi)
269  a = lo; b = mid + 1; c = lo
270  do while (a <= mid .and. b <= hi)
271  if (k(a) <= k(b)) then
272  kw(c) = k(a); iw(c) = idx(a); a = a + 1
273  else
274  kw(c) = k(b); iw(c) = idx(b); b = b + 1
275  end if
276  c = c + 1
277  end do
278  do while (a <= mid); kw(c) = k(a); iw(c) = idx(a); a = a + 1; c = c + 1; end do
279  do while (b <= hi); kw(c) = k(b); iw(c) = idx(b); b = b + 1; c = c + 1; end do
280  k(lo:hi) = kw(lo:hi); idx(lo:hi) = iw(lo:hi)
281  end subroutine msort
282  end subroutine sort_by_key
283 
295  subroutine hecmw_saamg_aggregate(g, min_size, max_size, aggr, naggr, order_mode, gid)
296  implicit none
297  type(hecmwst_saamg_nodegraph), intent(in) :: g
298  integer(kind=kint), intent(in) :: min_size, max_size
299  integer(kind=kint), allocatable, intent(out) :: aggr(:)
300  integer(kind=kint), intent(out) :: naggr
301  integer(kind=kint), optional, intent(in) :: order_mode
302  integer(kind=kint), optional, intent(in) :: gid(:)
303  integer(kind=kint), allocatable :: state(:), state1(:), sz(:), order(:)
304  integer(kind=kint) :: n, i, k, csz, ii, omode
305 
306  n = g%n
307  allocate(state(n), state1(n), sz(n))
308  state = 0
309 
310  ! excluded: no neighbors
311  do i = 1, n
312  if (g%index(i+1) == g%index(i)) state(i) = -1
313  end do
314 
315  omode = 0
316  if (present(order_mode)) omode = order_mode
317  allocate(order(n))
318  call build_scan_order(g, omode, gid, order)
319 
320  ! --- phase 1: cores of diameter <= 2, capped at max_size ---
321  ! Seed at a node whose whole neighborhood is still free, then absorb its
322  ! neighbors up to max_size nodes. On fine-level FE graphs the node degree
323  ! (~27) is far below the default max_size (96), so the cap is inert there and
324  ! the moderately-large cores coarsen aggressively (low operator complexity).
325  ! The cap matters on the Galerkin-densified DEEP-level graphs, where one
326  ! uncapped core can swallow a whole component: the coarsening taper (see
327  ! build_coarse_level) shrinks max_size at deep levels, and phase 1 must
328  ! honor it or the taper has no effect (phases 2/3 alone cannot undo an
329  ! oversized phase-1 core).
330  naggr = 0
331  do ii = 1, n
332  i = order(ii)
333  if (state(i) /= 0) cycle
334  if (.not. all_neighbors_free(g, state, i)) cycle
335  naggr = naggr + 1
336  state(i) = naggr
337  csz = 1
338  do k = g%index(i)+1, g%index(i+1)
339  if (csz >= max_size) exit
340  state(g%item(k)) = naggr
341  csz = csz + 1
342  end do
343  end do
344  deallocate(order)
345  state1 = state ! snapshot of phase-1 membership
346 
347  sz = 0
348  do i = 1, n
349  if (state(i) > 0) sz(state(i)) = sz(state(i)) + 1
350  end do
351 
352  ! --- phase 2: attach leftovers to strongest phase-1 aggregate ---
353  call phase2_attach(g, state, state1, sz, max_size)
354 
355  ! --- phase 3: remaining unassigned -> bounded BFS aggregates (<= max_size) ---
356  call phase3_components(g, state, sz, naggr, max_size)
357 
358  ! --- forced merge of aggregates smaller than min_size ---
359  call merge_small(g, state, sz, naggr, min_size)
360 
361  ! --- compact aggregate ids; excluded -> 0 ---
362  call compact_ids(state, n, naggr)
363 
364  allocate(aggr(n))
365  do i = 1, n
366  if (state(i) < 0) then
367  aggr(i) = 0
368  else
369  aggr(i) = state(i)
370  end if
371  end do
372 
373  deallocate(state, state1, sz)
374  end subroutine hecmw_saamg_aggregate
375 
376  !-----------------------------------------------------------------------------
377  ! helpers (private)
378  !-----------------------------------------------------------------------------
379 
380  logical function all_neighbors_free(g, state, i) result(ok)
381  type(hecmwst_saamg_nodegraph), intent(in) :: g
382  integer(kind=kint), intent(in) :: state(:), i
383  integer(kind=kint) :: k
384  ok = .true.
385  do k = g%index(i)+1, g%index(i+1)
386  if (state(g%item(k)) /= 0) then
387  ok = .false.; return
388  end if
389  end do
390  end function all_neighbors_free
391 
394  subroutine phase2_attach(g, state, state1, sz, max_size)
395  type(hecmwst_saamg_nodegraph), intent(in) :: g
396  integer(kind=kint), intent(inout) :: state(:), sz(:)
397  integer(kind=kint), intent(in) :: state1(:), max_size
398  integer(kind=kint), allocatable :: str(:), touched(:)
399  integer(kind=kint) :: n, i, k, a, nt, t, best, beststr
400  n = g%n
401  allocate(str(n), touched(n)); str = 0
402  do i = 1, n
403  if (state(i) /= 0) cycle
404  nt = 0
405  do k = g%index(i)+1, g%index(i+1)
406  a = state1(g%item(k))
407  if (a > 0) then
408  if (str(a) == 0) then; nt = nt + 1; touched(nt) = a; end if
409  str(a) = str(a) + 1
410  end if
411  end do
412  best = 0; beststr = 0
413  do t = 1, nt
414  a = touched(t)
415  if (sz(a) < max_size .and. str(a) > beststr) then
416  beststr = str(a); best = a
417  end if
418  end do
419  if (best > 0) then
420  state(i) = best; sz(best) = sz(best) + 1
421  end if
422  do t = 1, nt
423  str(touched(t)) = 0
424  end do
425  end do
426  deallocate(str, touched)
427  end subroutine phase2_attach
428 
433  subroutine phase3_components(g, state, sz, naggr, max_size)
434  type(hecmwst_saamg_nodegraph), intent(in) :: g
435  integer(kind=kint), intent(inout) :: state(:), sz(:), naggr
436  integer(kind=kint), intent(in) :: max_size
437  integer(kind=kint), allocatable :: queue(:)
438  integer(kind=kint) :: n, i, head, tail, u, k, v, cnt
439  n = g%n
440  allocate(queue(n))
441  do i = 1, n
442  if (state(i) /= 0) cycle
443  naggr = naggr + 1
444  head = 1; tail = 1; queue(1) = i; state(i) = naggr; cnt = 1
445  do while (head <= tail .and. cnt < max_size)
446  u = queue(head); head = head + 1
447  do k = g%index(u)+1, g%index(u+1)
448  v = g%item(k)
449  if (state(v) == 0) then
450  state(v) = naggr; cnt = cnt + 1
451  tail = tail + 1; queue(tail) = v
452  if (cnt >= max_size) exit
453  end if
454  end do
455  end do
456  sz(naggr) = cnt
457  end do
458  deallocate(queue)
459  end subroutine phase3_components
460 
464  subroutine merge_small(g, state, sz, naggr, min_size)
465  type(hecmwst_saamg_nodegraph), intent(in) :: g
466  integer(kind=kint), intent(inout) :: state(:), sz(:)
467  integer(kind=kint), intent(in) :: naggr, min_size
468  integer(kind=kint), allocatable :: str(:), touched(:), stuck(:)
469  integer(kind=kint) :: n, i, k, a, b, small, nt, t, best, beststr
470  n = g%n
471  allocate(str(naggr), touched(naggr), stuck(naggr))
472  str = 0; stuck = 0
473 
474  do
475  ! pick a non-stuck aggregate below min_size
476  small = 0
477  do a = 1, naggr
478  if (sz(a) > 0 .and. sz(a) < min_size .and. stuck(a) == 0) then
479  small = a; exit
480  end if
481  end do
482  if (small == 0) exit
483 
484  ! tally connection strength to other aggregates
485  nt = 0
486  do i = 1, n
487  if (state(i) /= small) cycle
488  do k = g%index(i)+1, g%index(i+1)
489  b = state(g%item(k))
490  if (b > 0 .and. b /= small) then
491  if (str(b) == 0) then; nt = nt + 1; touched(nt) = b; end if
492  str(b) = str(b) + 1
493  end if
494  end do
495  end do
496  best = 0; beststr = 0
497  do t = 1, nt
498  b = touched(t)
499  if (str(b) > beststr) then; beststr = str(b); best = b; end if
500  str(b) = 0
501  end do
502 
503  if (best == 0) then
504  stuck(small) = 1 ! isolated small cluster: cannot merge
505  write(*,'(a,i0,a)') 'hecmw_saamg merge_small: aggregate ', small, &
506  ' has no neighbor aggregate; left below min_size'
507  cycle
508  end if
509  ! merge small -> best
510  do i = 1, n
511  if (state(i) == small) state(i) = best
512  end do
513  sz(best) = sz(best) + sz(small)
514  sz(small) = 0
515  end do
516 
517  deallocate(str, touched, stuck)
518  end subroutine merge_small
519 
521  subroutine compact_ids(state, n, naggr)
522  integer(kind=kint), intent(inout) :: state(:), naggr
523  integer(kind=kint), intent(in) :: n
524  integer(kind=kint), allocatable :: map(:)
525  integer(kind=kint) :: i, a, cnt
526  allocate(map(naggr)); map = 0
527  cnt = 0
528  do i = 1, n
529  a = state(i)
530  if (a > 0) then
531  if (map(a) == 0) then; cnt = cnt + 1; map(a) = cnt; end if
532  state(i) = map(a)
533  end if
534  end do
535  naggr = cnt
536  deallocate(map)
537  end subroutine compact_ids
538 
Smoothed Aggregation AMG preconditioner : node graph + aggregation.
subroutine, public hecmw_saamg_aggregate(g, min_size, max_size, aggr, naggr, order_mode, gid)
Vanek 3-phase aggregation + min-size forced merge. aggr(1:n): 1..naggr for aggregated nodes,...
subroutine, public hecmw_saamg_build_nodegraph(A, g, theta)
Build the node graph from the block-CSR operator A. With theta (optional) > 0, apply a strength-of-co...
subroutine, public hecmw_saamg_write_vtk(fname, coord, nnode, aggr)
Write a legacy-VTK point cloud colored by aggregate id (ParaView diagnostic). coord is (x,...
Smoothed Aggregation AMG preconditioner : internal block-CSR matrix.
Smoothed Aggregation AMG preconditioner : kind parameters.
integer(kind=4), parameter kreal
integer(kind=4), parameter kint