Octopus
nonlocal_overlap_matrix.F90
Go to the documentation of this file.
1!! Copyright (C) 2026 N. Tancogne-Dejean
2!!
3!! This program is free software; you can redistribute it and/or modify
4!! it under the terms of the GNU General Public License as published by
5!! the Free Software Foundation; either version 2, or (at your option)
6!! any later version.
7!!
8!! This program is distributed in the hope that it will be useful,
9!! but WITHOUT ANY WARRANTY; without even the implied warranty of
10!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11!! GNU General Public License for more details.
12!!
13!! You should have received a copy of the GNU General Public License
14!! along with this program; if not, write to the Free Software
15!! Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16!! 02110-1301, USA.
17!!
18
19#include "global.h"
20
33 use accel_oct_m
34 use debug_oct_m
35 use epot_oct_m
36 use global_oct_m
38 use mesh_oct_m
43 use space_oct_m
45
46 implicit none
47
48 private
49 public :: &
52
59 integer :: nmat = 0
60 integer, allocatable :: imats(:)
61 integer, allocatable :: coff(:)
62 integer :: nc = 0
63 logical :: is_cmplx = .false.
66 real(real64), allocatable :: dt(:, :, :)
67 complex(real64), allocatable :: zt(:, :, :)
68 end type overlap_cluster_t
69
74 logical, public :: use_global_coupling = .false.
75 logical, public :: coupling_built = .false.
76 integer :: ncluster = 0
77 integer, allocatable :: proj_offset(:)
78 type(overlap_cluster_t), allocatable :: clusters(:)
79 contains
80 procedure :: detect => overlap_matrix_detect
81 procedure :: build => overlap_matrix_build
82 procedure :: end => overlap_matrix_end
83 procedure :: dapply => doverlap_matrix_apply
84 procedure :: zapply => zoverlap_matrix_apply
85 end type overlap_matrix_t
86
88 logical :: uspp_gpu_approx_warned = .false.
89
90contains
91
92 !--------------------------------------------------------
97 subroutine overlap_matrix_detect(this, nmat, projector_matrices, projector_to_atom, epot, space, self_overlap)
98 class(overlap_matrix_t), intent(inout) :: this
99 integer, intent(in) :: nmat
100 type(projector_matrix_t), intent(in) :: projector_matrices(:)
101 integer, intent(in) :: projector_to_atom(:)
102 type(epot_t), intent(in) :: epot
103 class(space_t), intent(in) :: space
104 logical, intent(in) :: self_overlap
105
106 integer :: im, jm, ia, ja, ic, root, nroots, kk
107 integer, allocatable :: parent(:), root_of(:), csize(:), cluster_of(:), fill(:)
108 logical :: any_overlap
109
110 push_sub(overlap_matrix_detect)
111
112 call overlap_matrix_end(this)
113
114 ! projection-vector offset of each matrix` slice (matches ind(imat) in the apply path)
115 safe_allocate(this%proj_offset(1:max(nmat, 1)))
116 kk = 0
117 do im = 1, nmat
118 this%proj_offset(im) = kk
119 kk = kk + projector_matrices(im)%nprojs
120 end do
121
122 if (nmat == 0) then
123 pop_sub(overlap_matrix_detect)
124 return
125 end if
126
127 ! union-find over projector matrices whose spheres share grid points (distinct atoms only:
128 ! a single matrix per atom, so same-atom pairs never arise here)
129 safe_allocate(parent(1:nmat))
130 do im = 1, nmat
131 parent(im) = im
132 end do
133 any_overlap = .false.
134 do im = 1, nmat
135 ia = projector_to_atom(im)
136 do jm = im + 1, nmat
137 ja = projector_to_atom(jm)
138 if (ia == ja) cycle
139 if (submesh_overlap(epot%proj(ia)%sphere, epot%proj(ja)%sphere, space)) then
140 call uf_union(parent, im, jm)
141 any_overlap = .true.
142 end if
143 end do
144 end do
145
146 ! the per-atom Gram ignores the image cross terms of a self-overlapping sphere
147 this%use_global_coupling = any_overlap .or. self_overlap
148 if (.not. this%use_global_coupling) then
149 safe_deallocate_a(parent)
150 pop_sub(overlap_matrix_detect)
151 return
152 end if
154 ! materialize the connected components as clusters
155 safe_allocate(cluster_of(1:nmat))
156 safe_allocate(root_of(1:nmat))
157 safe_allocate(csize(1:nmat))
158 nroots = 0
159 csize = 0
160 do im = 1, nmat
161 root = uf_find(parent, im)
162 ic = 0
163 do kk = 1, nroots
164 if (root_of(kk) == root) then
165 ic = kk
166 exit
167 end if
168 end do
169 if (ic == 0) then
170 nroots = nroots + 1
171 root_of(nroots) = root
172 ic = nroots
173 end if
174 cluster_of(im) = ic
175 csize(ic) = csize(ic) + 1
176 end do
178 this%ncluster = nroots
179 safe_allocate(this%clusters(1:nroots))
180 safe_allocate(fill(1:nroots))
181 fill = 0
182 do ic = 1, nroots
183 this%clusters(ic)%nmat = csize(ic)
184 safe_allocate(this%clusters(ic)%imats(1:csize(ic)))
185 safe_allocate(this%clusters(ic)%coff(1:csize(ic)))
186 end do
187 ! matrices are visited in ascending order, so imats(:) comes out sorted
188 do im = 1, nmat
189 ic = cluster_of(im)
190 fill(ic) = fill(ic) + 1
191 this%clusters(ic)%imats(fill(ic)) = im
192 end do
193 do ic = 1, nroots
194 kk = 0
195 do im = 1, this%clusters(ic)%nmat
196 this%clusters(ic)%coff(im) = kk
197 kk = kk + projector_matrices(this%clusters(ic)%imats(im))%nprojs
198 end do
199 this%clusters(ic)%nc = kk
200 end do
201
202 safe_deallocate_a(parent)
203 safe_deallocate_a(cluster_of)
204 safe_deallocate_a(root_of)
205 safe_deallocate_a(csize)
206 safe_deallocate_a(fill)
207
208 pop_sub(overlap_matrix_detect)
209 end subroutine overlap_matrix_detect
210
211 !--------------------------------------------------------
213 recursive integer function uf_find(parent, ii) result(rr)
214 integer, intent(inout) :: parent(:)
215 integer, intent(in) :: ii
216
217 if (parent(ii) /= ii) parent(ii) = uf_find(parent, parent(ii))
218 rr = parent(ii)
219 end function uf_find
220
221 !--------------------------------------------------------
223 subroutine uf_union(parent, ii, jj)
224 integer, intent(inout) :: parent(:)
225 integer, intent(in) :: ii, jj
226
227 integer :: ri, rj
228
229 ri = uf_find(parent, ii)
230 rj = uf_find(parent, jj)
231 if (ri /= rj) parent(rj) = ri
232 end subroutine uf_union
233
234 ! ----------------------------------------------------------------------------------
250 subroutine overlap_matrix_build(this, projector_matrices, projector_phases, mesh, kstart, kend)
251 class(overlap_matrix_t), target, intent(inout) :: this
252 type(projector_matrix_t), intent(in) :: projector_matrices(:)
253 complex(real64), allocatable, intent(in) :: projector_phases(:, :, :, :)
254 class(mesh_t), intent(in) :: mesh
255 integer, intent(in) :: kstart
256 integer, intent(in) :: kend
257
258 integer :: c, ik, nc, a, b, oa, ob, ii, jj, nI
259 logical :: phased
260 type(overlap_cluster_t), pointer :: cl
261 complex(real64), allocatable :: zgI(:, :), zgJ(:, :), mmat(:, :), qmat(:, :), amat(:, :), tmat(:, :)
262
263 push_sub(overlap_matrix_build)
264
265 if (.not. this%use_global_coupling) then
266 pop_sub(overlap_matrix_build)
267 return
268 end if
269
270 ! The exact cross-block inverse is applied on the CPU only. On the GPU the coupling still uses
271 ! the per-atom (block-diagonal) buff_mix, i.e. the previous approximation; warn once. (The
272 ! device dense coupling is a follow-up that needs GPU hardware to implement and validate.)
273 if (accel_is_enabled() .and. .not. uspp_gpu_approx_warned) then
274 uspp_gpu_approx_warned = .true.
275 message(1) = "Ultrasoft projector spheres of different atoms overlap on the grid."
276 message(2) = "The exact inverse overlap S^{-1} is applied on the CPU only; the GPU path"
277 message(3) = "still uses the per-atom approximation. Convergence on GPU may be degraded."
278 call messages_warning(3)
279 end if
280
281 phased = allocated(projector_phases)
282
283 do c = 1, this%ncluster
284 cl => this%clusters(c)
285 nc = cl%nc
286 ! T is complex if the projectors are phased (k /= 0 / vector potential); otherwise a real
287 ! T is stored.
288 cl%is_cmplx = phased .or. projector_matrices(cl%imats(1))%is_cmplx
289
290 if (cl%is_cmplx) then
291 if (.not. allocated(cl%zt)) then
292 safe_allocate(cl%zt(1:nc, 1:nc, kstart:kend))
293 end if
294 else
295 if (.not. allocated(cl%dt)) then
296 safe_allocate(cl%dt(1:nc, 1:nc, kstart:kend))
297 end if
298 end if
299
300 safe_allocate(mmat(1:nc, 1:nc))
301 safe_allocate(qmat(1:nc, 1:nc))
302 safe_allocate(amat(1:nc, 1:nc))
303 safe_allocate(tmat(1:nc, 1:nc))
304
305 do ik = kstart, kend
307 ! Cluster Gram M(k): local contribution over shared grid points, upper blocks only
308 mmat = m_z0
309 do a = 1, cl%nmat
310 call fill_phased_ket(cl%imats(a), ik, zgi)
311 oa = cl%coff(a)
312 do b = a, cl%nmat
313 call fill_phased_ket(cl%imats(b), ik, zgj)
314 ob = cl%coff(b)
315 call accumulate_gram(projector_matrices(cl%imats(a)), &
316 projector_matrices(cl%imats(b)), zgi, zgj, mmat, oa, ob)
317 safe_deallocate_a(zgj)
318 end do
319 safe_deallocate_a(zgi)
320 end do
321 ! fill the strict-lower entries by Hermiticity (diagonal blocks are already full)
322 do jj = 1, nc
323 do ii = jj + 1, nc
324 mmat(ii, jj) = conjg(mmat(jj, ii))
325 end do
326 end do
327 ! the sphere may be split across domains
328 if (mesh%parallel_in_domains) call mesh%allreduce(mmat, dim = (/nc, nc/))
329 mmat = mmat * mesh%volume_element
330
331 ! Block-diagonal augmentation charges Q (per atom; no cross-atom q terms)
332 qmat = m_z0
333 do a = 1, cl%nmat
334 ni = projector_matrices(cl%imats(a))%nprojs
335 oa = cl%coff(a)
336 qmat(oa+1:oa+ni, oa+1:oa+ni) = &
337 cmplx(projector_matrices(cl%imats(a))%qmix(1:ni, 1:ni), m_zero, real64)
338 end do
339
340 ! A = 1 + M Q ; T = Q A^{-1}
341 amat = matmul(mmat, qmat)
342 do ii = 1, nc
343 amat(ii, ii) = amat(ii, ii) + m_z1
344 end do
345 call lalg_inverse(nc, amat, 'svd')
346 tmat = matmul(qmat, amat)
347
348 if (cl%is_cmplx) then
349 cl%zt(1:nc, 1:nc, ik) = -tmat(1:nc, 1:nc)
350 else
351 cl%dt(1:nc, 1:nc, ik) = -real(tmat(1:nc, 1:nc), real64)
352 end if
353
354 end do
355
356 safe_deallocate_a(mmat)
357 safe_deallocate_a(qmat)
358 safe_deallocate_a(amat)
359 safe_deallocate_a(tmat)
360 end do
361
362 this%coupling_built = .true.
363
364 pop_sub(overlap_matrix_build)
365
366 contains
367
368 ! Build the phased ket weight g(p, i) = beta_i(p) * conjg(phase(p)) for projector matrix im.
369 subroutine fill_phased_ket(im, ik, zg)
370 integer, intent(in) :: im
371 integer, intent(in) :: ik
372 complex(real64), allocatable, intent(out) :: zg(:, :)
373
374 integer :: np, npr, p, i
375
376 np = projector_matrices(im)%npoints
377 npr = projector_matrices(im)%nprojs
378 safe_allocate(zg(1:max(np, 1), 1:npr))
379 if (np == 0) return
380
381 if (allocated(projector_matrices(im)%zprojectors)) then
382 do i = 1, npr
383 do p = 1, np
384 zg(p, i) = projector_matrices(im)%zprojectors(p, i)
385 end do
386 end do
387 else
388 do i = 1, npr
389 do p = 1, np
390 zg(p, i) = cmplx(projector_matrices(im)%dprojectors(p, i), m_zero, real64)
391 end do
392 end do
393 end if
394
395 if (phased) then
396 do i = 1, npr
397 do p = 1, np
398 zg(p, i) = zg(p, i) * conjg(projector_phases(p, 1, im, ik))
399 end do
400 end do
401 end if
402 end subroutine fill_phased_ket
403
404 ! Accumulate M[oa+i, ob+j] += sum_{shared points} conjg(gI) gJ.
405 ! A self-overlapping sphere stores its map as injective regions,
406 ! so each region pair is merged; this covers every pair of
407 ! sphere points sharing a grid point, periodic images included.
408 subroutine accumulate_gram(pmI, pmJ, zgI, zgJ, mmat, oa, ob)
409 type(projector_matrix_t), intent(in) :: pmI
410 type(projector_matrix_t), intent(in) :: pmJ
411 complex(real64), intent(in) :: zgI(:, :)
412 complex(real64), intent(in) :: zgJ(:, :)
413 complex(real64), intent(inout) :: mmat(:, :)
414 integer, intent(in) :: oa
415 integer, intent(in) :: ob
416
417 integer :: ri, rj, ip, jp, ipe, jpe, ni, nj, i, j, dd
418 complex(real64) :: bra
419
420 ni = size(zgi, 2)
421 nj = size(zgj, 2)
422 do ri = 1, pmi%nregions
423 ipe = pmi%regions(ri + 1) - 1
424 do rj = 1, pmj%nregions
425 jpe = pmj%regions(rj + 1) - 1
426 ip = pmi%regions(ri)
427 jp = pmj%regions(rj)
428 do while (ip <= ipe .and. jp <= jpe)
429 dd = pmi%map(ip) - pmj%map(jp)
430 if (dd < 0) then
431 ip = ip + 1
432 else if (dd > 0) then
433 jp = jp + 1
434 else
435 do i = 1, ni
436 bra = conjg(zgi(ip, i))
437 do j = 1, nj
438 mmat(oa+i, ob+j) = mmat(oa+i, ob+j) + bra*zgj(jp, j)
439 end do
440 end do
441 ip = ip + 1
442 jp = jp + 1
443 end if
444 end do
445 end do
446 end do
447 end subroutine accumulate_gram
448
449 end subroutine overlap_matrix_build
450
451 !-----------------------------------------------------------------
453 subroutine overlap_matrix_end(this)
454 class(overlap_matrix_t), intent(inout) :: this
455
456 integer :: ic
457
458 push_sub(overlap_matrix_end)
459
460 if (allocated(this%clusters)) then
461 do ic = 1, size(this%clusters)
462 safe_deallocate_a(this%clusters(ic)%imats)
463 safe_deallocate_a(this%clusters(ic)%coff)
464 safe_deallocate_a(this%clusters(ic)%dt)
465 safe_deallocate_a(this%clusters(ic)%zt)
466 end do
467 safe_deallocate_a(this%clusters)
468 end if
469 safe_deallocate_a(this%proj_offset)
470 this%ncluster = 0
471 this%use_global_coupling = .false.
472 this%coupling_built = .false.
473
474 pop_sub(overlap_matrix_end)
475 end subroutine overlap_matrix_end
476
477#include "undef.F90"
478#include "real.F90"
479#include "nonlocal_overlap_matrix_inc.F90"
480
481#include "undef.F90"
482#include "complex.F90"
483#include "nonlocal_overlap_matrix_inc.F90"
484
486
487!! Local Variables:
488!! mode: f90
489!! coding: utf-8
490!! End:
pure logical function, public accel_is_enabled()
Definition: accel.F90:412
real(real64), parameter, public m_zero
Definition: global.F90:198
complex(real64), parameter, public m_z0
Definition: global.F90:208
complex(real64), parameter, public m_z1
Definition: global.F90:209
This module defines the meshes, which are used in Octopus.
Definition: mesh.F90:118
subroutine, public messages_warning(no_lines, all_nodes, namespace)
Definition: messages.F90:523
character(len=256), dimension(max_lines), public message
to be output by fatal, warning
Definition: messages.F90:160
Exact inverse of the ultrasoft/PAW generalized overlap operator for overlapping spheres.
subroutine overlap_matrix_build(this, projector_matrices, projector_phases, mesh, kstart, kend)
Assemble the exact inverse-overlap coupling -T for each overlap cluster.
subroutine doverlap_matrix_apply(this, projector_matrices, proj, nst_linear, ik)
Apply the exact inverse-overlap coupling p` = -T p (mix = -T stored per cluster), dense within each o...
subroutine uf_union(parent, ii, jj)
Union-find "union" of two projector matrices into one cluster.
subroutine overlap_matrix_detect(this, nmat, projector_matrices, projector_to_atom, epot, space, self_overlap)
Detect distinct-atom projector overlap and group the projector matrices into the connected components...
subroutine zoverlap_matrix_apply(this, projector_matrices, proj, nst_linear, ik)
Apply the exact inverse-overlap coupling p` = -T p (mix = -T stored per cluster), dense within each o...
subroutine overlap_matrix_end(this)
Release the exact inverse-overlap cluster data.
recursive integer function uf_find(parent, ii)
Union-find "find" with path compression (roots of the projector-overlap graph).
/* To use the profiling module, call profiling_in and profiling_out with the label of the region as a...
Definition: profiling.F90:143
logical function, public submesh_overlap(sm1, sm2, space)
Definition: submesh.F90:740
subroutine fill_phased_ket(im, ik, zg)
subroutine accumulate_gram(pmI, pmJ, zgI, zgJ, mmat, oa, ob)
Describes mesh distribution to nodes.
Definition: mesh.F90:186
One connected component of the projector-overlap graph.
The exact inverse-overlap operator, grouped into overlap clusters.
A set of projectors defined on a submesh.
int true(void)