Octopus
isdf.F90
Go to the documentation of this file.
1!! Copyright (C) 2024 - 2025 A. Buccheri
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#include "global.h"
19
21module isdf_oct_m
22 use, intrinsic :: iso_fortran_env, only: real64
27 use comm_oct_m
28 use debug_oct_m
31 use global_oct_m
32 use io_oct_m
38 use math_oct_m
39 use mesh_oct_m
42 use mpi_oct_m, only: mpi_world, mpi_double_precision, mpi_sum
46 use space_oct_m
49 use xc_cam_oct_m, only: xc_cam_t
50
51 implicit none
52 private
53 public :: &
57
58 ! TODO(Alex) Issue #1195 Extend ISDF to spin-polarised systems
60 integer, private, parameter :: ik = 1
61
62contains
63
67 subroutine isdf_ace_compute_potentials(exxop, namespace, space, mesh, st, Vx_on_st, kpoints)
68 type(exchange_operator_t), intent(in ) :: exxop
69 ! ISDF interpolation points, and cam parameters.
70 type(namespace_t), intent(in ) :: namespace
71 class(space_t), intent(in ) :: space
72 class(mesh_t), intent(in ) :: mesh
73 type(states_elec_t), intent(in ) :: st
74 type(kpoints_t), intent(in ) :: kpoints
75
76 type(states_elec_t), intent(out) :: Vx_on_st
77
78 real(real64), allocatable :: psi_mu(:, :), P_r_mu(:, :), isdf_vectors(:, :)
79 integer :: nstates
80 type(distributed_t) :: isdf_dist
81
83
84 ! TODO(Alex) Issue #1195 Extend ISDF to spin-polarised and periodic systems
85 assert(kpoints%gamma_only())
86 assert(.not. space%is_periodic())
87 nstates = exxop%isdf%n_ks_states
88
89 call isdf_interpolation_vectors(exxop%isdf, namespace, mesh, st, exxop%isdf%centroids, psi_mu, &
90 p_r_mu, isdf_vectors)
91
92 !Distribute ISDF vectors along states_kpt (spin) communicator
93 call distributed_init(isdf_dist, size(isdf_vectors, 2), st%st_kpt_mpi_grp%comm)
94
95 call disdf_ace_apply_exchange_op(exxop, namespace, mesh, st, psi_mu, p_r_mu, isdf_vectors, isdf_dist, vx_on_st)
96 safe_deallocate_a(psi_mu)
97 safe_deallocate_a(p_r_mu)
98 safe_deallocate_a(isdf_vectors)
99
101
102 end subroutine isdf_ace_compute_potentials
103
104
106 subroutine isdf_interpolation_vectors(isdf, namespace, mesh, st, centroids, psi_mu, P_r_mu, isdf_vectors)
107 type(isdf_options_t), intent(in ) :: isdf
108 type(namespace_t), intent(in ) :: namespace
109 class(mesh_t), intent(in ) :: mesh
110 type(states_elec_t), intent(in ) :: st
111 type(centroids_t), intent(in ) :: centroids
112
113 real(real64), allocatable, intent(out) :: psi_mu(:, :)
114 ! defined at interpolation points: \f$ \psi_i(\mathbf{r}_\mu) \f$
115 real(real64), allocatable, intent(out) :: P_r_mu(:, :)
116 ! \f$P_{\mathbf{r},\mu}\f$, with size (np, n_int)
117 real(real64), allocatable, intent(out) :: isdf_vectors(:, :)
118
119 character(len=6) :: np_char
120 integer :: nocc, n_int_g, rank, i, j
121 real(real64), allocatable :: zct(:, :)
122 real(real64), allocatable :: p_mu_nu(:, :)
123 ! with both variables defined at interpolation points.
124 real(real64), allocatable :: cct(:, :)
125 ! Gets overwritten with its inverse.
126
127 push_sub_with_profile(isdf_interpolation_vectors)
128
129 ! TODO(Alex) Issue #1195 Extend ISDF to spin-polarised systems
130 if (st%d%nspin > 1) then
131 call messages_not_implemented("ISDF for SPIN_POLARIZED and SPINOR calculations", namespace)
132 endif
133
134 ! TODO(Alex) Issue #1196 Template ISDF handle both real and complex states
135 if (.not. states_are_real(st)) then
136 call messages_not_implemented("ISDF handling of complex states", namespace)
137 endif
138
139 ! TODO(Alex) Issue #1276 Implement ISDF on GPU
140 if (accel_is_enabled()) then
141 call messages_not_implemented("ISDF not supported on GPU", namespace)
142 end if
143
144 ! For debug file naming
145 write(np_char, '(I6)') mpi_world%size
146
147 ! Total number of interpolation points
148 n_int_g = centroids%npoints_global()
149
150 ! Max number of states used in ISDF expansion
151 nocc = isdf%n_ks_states
152
153 if (st%st_start <= nocc .and. nocc <= st%st_end) then
154 write(message(1),'(a, 1x, I3, 1x, a, 1x, I3)') "ISDF: Computing ISDF vectors up to state", &
155 & nocc, " on process", st%mpi_grp%rank
156 call messages_info(1, namespace=namespace, debug_only=.true.)
157 endif
158
159 ! psi_mu allocated within the routine as shape varies w.r.t. PACKED or UNPACKED
160 call dphi_at_interpolation_points(mesh, st, centroids, nocc, psi_mu)
161 if (debug%info) call output_psi_mu_for_all_states(namespace, st, nocc, psi_mu)
163 safe_allocate(p_r_mu(1:mesh%np, 1:n_int_g))
164 call dquasi_density_matrix_at_mesh_centroid_points(st, nocc, psi_mu, p_r_mu)
165 if (debug%info) call output_matrix(namespace, "p_r_mu_np"//trim(adjustl(np_char))//".txt", p_r_mu)
166
167 safe_allocate(zct(1:mesh%np, 1:n_int_g))
168 call pair_product_coefficient_matrix(p_r_mu, zct)
169 if (debug%info) call output_matrix(namespace, "zct_np"//trim(adjustl(np_char))//".txt", zct)
170
171 ! Note(Alex) Might be more efficient to mask p_r_mu -> p_mu_nu than perform GEMM and allreduce
172 safe_allocate(p_mu_nu(1:n_int_g, 1:n_int_g))
173 ! Contract over the state index, ist: P_mu_nu = [psi_ist_mu]^T @ psi_ist_nu
174 if (local_number_of_states(st, nocc) > 0) then
175 call lalg_gemm(psi_mu, psi_mu, p_mu_nu, transa='T')
176 else
177 ! Process st%mpi_grp%rank contains no states that contribute to ISDF expansion
178 ! so avoid GEMM, and initialise to zero for allreduce
179 do j = 1, n_int_g
180 do i = 1, n_int_g
181 p_mu_nu(i, j) = 0.0_real64
182 enddo
183 enddo
184 endif
185
186 ! States may be distributed so all elements of p_mu_nu only contain a partial contribution from {ist}
187 call comm_allreduce(st%mpi_grp, p_mu_nu)
188 if (debug%info) call output_matrix(namespace, "p_mu_nu_np"//trim(adjustl(np_char))//".txt", p_mu_nu)
189
190 safe_allocate(cct(1:n_int_g, 1:n_int_g))
191 call coefficient_product_matrix(p_mu_nu, cct)
192 if (debug%info) then
193 call output_matrix(namespace, "cct_np"//trim(adjustl(np_char))//".txt", cct)
194 assert(is_symmetric(cct))
195 endif
196 safe_deallocate_a(p_mu_nu)
197
198 ! [CC^T]^{-1}, mutating cct in-place
199 ! NOTE, CC^T is extremely ill-conditioned once a critical number of interpolation points
200 ! is exceeded. Using the pseudo-inverse (SVD) circumvents this problem
201 write(message(1),'(a)') "ISDF: Inverting [CC^T]"
202 call messages_info(1, namespace=namespace, debug_only=.true.)
203 call lalg_svd_inverse(n_int_g, n_int_g, cct)
204 call symmetrize_matrix(n_int_g, cct)
205
206 if (isdf%check_n_interp) then
207 rank = lalg_matrix_rank_svd(cct, preserve_mat=.true.)
208 write(message(1),'(a, I7)') "ISDF: Rank of CC^T is ", rank
209 if (rank < n_int_g) then
210 write(message(2),'(a)') " - This rank is the optimal ISDFNpoints to run the calculation with"
211 else
212 write(message(2),'(a)') " - This suggests that ISDFNpoints is either optimal, or could be larger"
213 endif
214 call messages_info(2, namespace=namespace)
215 endif
216
217 ! zeta = [ZC^T][CC^T]^-1
218 safe_allocate(isdf_vectors(1:mesh%np, 1:n_int_g))
219 call lalg_gemm(mesh%np, n_int_g, n_int_g, 1.0_real64, zct, cct, 0.0_real64, isdf_vectors)
220
221 ! ISDF vectors are distributed on the mesh, so do not output in that case
222 if (debug%info .and. .not. mesh%parallel_in_domains) then
223 call output_matrix(namespace, "isdf_np"//trim(adjustl(np_char))//".txt", isdf_vectors)
224 endif
225
226 safe_deallocate_a(zct)
227 safe_deallocate_a(cct)
228
229 pop_sub_with_profile(isdf_interpolation_vectors)
230
231 end subroutine isdf_interpolation_vectors
232
233
244 subroutine pair_product_coefficient_matrix(p_phi, zct, p_psi)
245 real(real64), target, contiguous, intent(in ) :: p_phi(:, :)
246 ! \f$P^{\varphi}(\mathbf{r}, \mathbf{r}_\mu)\f$
247 real(real64), target, optional, contiguous, intent(in ) :: p_psi(:, :)
248 ! \f$P^{\psi}(\mathbf{r}, \mathbf{r}_\mu)\f$
249
250 real(real64), contiguous, intent(out) :: zct(:, :)
251
252 integer :: np, n_int_g, ip, i_mu
253 real(real64), pointer, contiguous :: p_2(:, :)
254
255 push_sub_with_profile(pair_product_coefficient_matrix)
256
257 write(message(1),'(a)') "ISDF: Constructing Z C^T"
258 call messages_info(1, debug_only=.true.)
259
260 if (present(p_psi)) then
261 p_2 => p_psi
262 else
263 p_2 => p_phi
264 endif
265
266 ! Quasi-density matrices require the same shape for element-wise multiplication
267 assert(all(shape(p_phi) == shape(p_2)))
268 ! zct should be allocated, and its shape should be consistent with the quasi-density matrices
269 assert(all(shape(p_phi) == shape(zct)))
270
271 np = size(p_phi, 1)
272 n_int_g = size(p_phi, 2)
273
274 ! Construct ZC^T
275 !$omp parallel
276 do i_mu = 1, n_int_g
277 !$omp do simd
278 do ip = 1, np
279 zct(ip, i_mu) = p_phi(ip, i_mu) * p_2(ip, i_mu)
280 enddo
281 !$omp end do simd nowait
282 enddo
283 !$omp end parallel
284 nullify(p_2)
285
286 pop_sub_with_profile(pair_product_coefficient_matrix)
287
289
290
302 subroutine coefficient_product_matrix(p_phi, cct, p_psi)
303 real(real64), target, contiguous, intent(in ) :: p_phi(:, :)
304 ! \f$P^{\varphi}(\mathbf{r}_\mu, \mathbf{r}_\nu)\f$
305 real(real64), target, contiguous, optional, intent(in ) :: p_psi(:, :)
306 ! \f$P^{\psi}(\mathbf{r}_\mu, \mathbf{r}_\nu)\f$
307
308 real(real64), contiguous, intent(out) :: cct(:, :)
309 ! Array should be allocated by the caller
310
311 integer :: n_int_g, i_mu, i_nu
312 real(real64), contiguous, pointer :: p_2(:, :)
313
314 push_sub_with_profile(coefficient_product_matrix)
315
316 write(message(1),'(a)') "ISDF: Construct CC^T"
317 call messages_info(1, debug_only=.true.)
318
319 if (present(p_psi)) then
320 p_2 => p_psi
321 else
322 p_2 => p_phi
323 endif
324
325 ! Quasi-density matrices require the same shape for element-wise multiplication
326 assert(all(shape(p_phi) == shape(p_2)))
327 ! cct should be allocated, and its shape should be consistent with the quasi-density matrices
328 assert(all(shape(p_phi) == shape(cct)))
329 n_int_g = size(p_phi, 1)
330
331 ! Construct CC^T
332 !$omp parallel do collapse(2) default(shared)
333 do i_nu = 1, n_int_g
334 do i_mu = 1, n_int_g
335 cct(i_mu, i_nu) = p_phi(i_mu, i_nu) * p_2(i_mu, i_nu)
336 enddo
337 enddo
338 !$omp end parallel do
339 nullify(p_2)
340
341 pop_sub_with_profile(coefficient_product_matrix)
342
343 end subroutine coefficient_product_matrix
344
345
351 subroutine isdf_gram_matrix(mesh, isdf_vectors, gram_matrix)
352 class(mesh_t), intent(in) :: mesh
353 real(real64), contiguous, intent(in ) :: isdf_vectors(:, :)
354 real(real64), contiguous, intent(out) :: gram_matrix(:, :)
355
356 integer :: n_int, i, j
357
358 push_sub(isdf_gram_matrix)
359
360 assert(mesh%np == size(isdf_vectors, 1))
361 n_int = size(isdf_vectors, 2)
362 assert(all(shape(gram_matrix) == [n_int, n_int]))
363
364 ! It would be more efficient to use DGEMM, but dmf_dotp ensures the correct volume element
365
366 ! Diagonal elements
367 do i = 1, n_int
368 gram_matrix(i, i) = dmf_dotp(mesh, isdf_vectors(:, i), isdf_vectors(:, i), reduce=.false.)
369 enddo
370
371 ! Upper triangle elements
372 do j = 2, n_int
373 do i = 1, j - 1
374 gram_matrix(i, j) = dmf_dotp(mesh, isdf_vectors(:, i), isdf_vectors(:, j), reduce=.false.)
375 ! Lower triangle from symmetry
376 gram_matrix(j, i) = gram_matrix(i, j)
377 enddo
378 enddo
379
380 call mesh%allreduce(gram_matrix)
381
382 pop_sub(isdf_gram_matrix)
383
384 end subroutine isdf_gram_matrix
385
386#include "real.F90"
387#include "isdf_inc.F90"
388#include "undef.F90"
389
390end module isdf_oct_m
391
392!! Local Variables:
393!! mode: f90
394!! coding: utf-8
395!! End:
There are several ways how to call batch_set_state and batch_get_state:
Definition: batch_ops.F90:203
Matrix-matrix multiplication plus matrix.
Definition: lalg_basic.F90:229
pure logical function, public accel_is_enabled()
Definition: accel.F90:395
This module implements batches of mesh functions.
Definition: batch.F90:135
integer, parameter, public batch_not_packed
functions are stored in CPU memory, unpacked order
Definition: batch.F90:286
integer, parameter, public batch_device_packed
functions are stored in device memory in packed order
Definition: batch.F90:286
integer, parameter, public batch_packed
functions are stored in CPU memory, in transposed (packed) order
Definition: batch.F90:286
This module implements common operations on batches of mesh functions.
Definition: batch_ops.F90:118
type(debug_t), save, public debug
Definition: debug.F90:158
subroutine, public distributed_init(this, total, comm, tag, scalapack_compat)
Distribute N instances across M processes of communicator comm
Definition: io.F90:116
Interoperable Separable Density Fitting (ISDF) molecular implementation.
Definition: isdf.F90:116
subroutine, public isdf_interpolation_vectors(isdf, namespace, mesh, st, centroids, psi_mu, P_r_mu, isdf_vectors)
Top-level routine for computing ISDF vectors.
Definition: isdf.F90:202
subroutine, public isdf_ace_compute_potentials(exxop, namespace, space, mesh, st, Vx_on_st, kpoints)
ISDF wrapper computing interpolation points and vectors, which are used to build the potential used ...
Definition: isdf.F90:163
subroutine dquasi_density_matrix_at_mesh_centroid_points(st, max_state, psi_mu, p_r_mu)
Compute the quasi-density matrix where one spatial coordinate is defined at grid points and the is de...
Definition: isdf.F90:624
subroutine dphi_at_interpolation_points(mesh, st, centroids, max_state, psi_mu)
Construct a 2D array of states, defined only at a specific subset of grid points.
Definition: isdf.F90:539
subroutine disdf_ace_apply_exchange_op(exxop, namespace, mesh, st, psi_mu, P_r_mu, isdf_vectors, isdf_dist, Vx_on_st)
Compute the action of the exchange potential on KS states for adaptively-compressed exchange.
Definition: isdf.F90:710
subroutine coefficient_product_matrix(p_phi, cct, p_psi)
Construct the coefficient product matrix .
Definition: isdf.F90:398
subroutine, public isdf_gram_matrix(mesh, isdf_vectors, gram_matrix)
Compute the Gram matrix for the ISDF interpolation vectors.
Definition: isdf.F90:447
subroutine pair_product_coefficient_matrix(p_phi, zct, p_psi)
Construct the matrix-matrix product .
Definition: isdf.F90:340
subroutine, public output_matrix(namespace, fname, matrix)
Helper routine to output a 2D matrix.
Definition: isdf_utils.F90:151
integer function, public local_number_of_states(st, max_state)
Number of states contributing to the expansion, local to current process.
Definition: isdf_utils.F90:230
subroutine, public output_psi_mu_for_all_states(namespace, st, max_state, psi_mu)
Output the gathered psi_mu for all states, such that the matrix is the same irregardless of state par...
Definition: isdf_utils.F90:309
This module is intended to contain "only mathematical" functions and procedures.
Definition: math.F90:117
logical function, public is_symmetric(a, tol)
Check if a 2D array is symmetric.
Definition: math.F90:1492
This module defines various routines, operating on mesh functions.
This module defines the meshes, which are used in Octopus.
Definition: mesh.F90:120
subroutine, public messages_not_implemented(feature, namespace)
Definition: messages.F90:1091
character(len=256), dimension(max_lines), public message
to be output by fatal, warning
Definition: messages.F90:162
subroutine, public messages_info(no_lines, iunit, debug_only, stress, all_nodes, namespace)
Definition: messages.F90:594
type(mpi_grp_t), public mpi_world
Definition: mpi.F90:272
pure logical function, public states_are_real(st)
Distribution of N instances over mpi_grpsize processes, for the local rank mpi_grprank....
Describes mesh distribution to nodes.
Definition: mesh.F90:187
Coulomb-attenuating method parameters, used in the partitioning of the Coulomb potential into a short...
Definition: xc_cam.F90:141
int true(void)