LCOV - code coverage report
Current view: top level - src - kpoint_lattice_fft_unittest.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:31b3539) Lines: 90.0 % 319 287
Test Date: 2026-10-25 01:03:06 Functions: 100.0 % 5 5

            Line data    Source code
       1              : !--------------------------------------------------------------------------------------------------!
       2              : !   CP2K: A general program to perform molecular dynamics simulations                              !
       3              : !   Copyright 2000-2026 CP2K developers group <https://cp2k.org>                                   !
       4              : !                                                                                                  !
       5              : !   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
       6              : !--------------------------------------------------------------------------------------------------!
       7            2 : PROGRAM kpoint_lattice_fft_unittest
       8            2 :    USE fft_tools,                       ONLY: finalize_fft,&
       9              :                                               init_fft
      10              :    USE kinds,                           ONLY: dp
      11              :    USE kpoint_lattice_fft,              ONLY: cell_to_k_grid_fft,&
      12              :                                               k_grid_to_cell_execute,&
      13              :                                               k_grid_to_cell_fft,&
      14              :                                               k_grid_to_cell_prepare,&
      15              :                                               k_grid_to_cell_release,&
      16              :                                               k_grid_to_cell_work_type,&
      17              :                                               regular_kpoint_grid
      18              :    USE lattice_preconditioner_operator, ONLY: &
      19              :         apply_lattice_inverse_dense, apply_lattice_state_inverse_dense, build_lattice_inverse, &
      20              :         build_lattice_local_correction, lattice_fft_auto_select, lattice_fft_cost_ratio, &
      21              :         lattice_fft_storage_ratio, lattice_grid, lattice_local_cost_ratio, &
      22              :         lattice_local_storage_ratio
      23              :    USE mathconstants,                   ONLY: gaussi,&
      24              :                                               twopi,&
      25              :                                               z_zero
      26              :    USE message_passing,                 ONLY: mp_comm_type,&
      27              :                                               mp_world_finalize,&
      28              :                                               mp_world_init
      29              : 
      30              :    IMPLICIT NONE
      31              : 
      32              :    INTEGER, PARAMETER                                  :: base_block = 2, base_cell = 8, base_n = 16, &
      33              :                                                           base_ncol = 3, ncell = 31, nkp = 64, &
      34              :                                                           nround = 16, nselected = 3
      35              :    INTEGER, PARAMETER                                  :: large_block = 1000, large_ncol = 1000, &
      36              :                                                           large_num_pe = 16
      37            2 :    COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)   :: inverse_k
      38            2 :    COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :, :) :: state_inverse_k
      39              :    COMPLEX(KIND=dp), DIMENSION(2, 3, nkp)              :: direct, transformed
      40              :    COMPLEX(KIND=dp), DIMENSION(2, 3, nround)           :: reconstructed, round_k
      41              :    COMPLEX(KIND=dp), DIMENSION(2, 3, nselected)         :: selected_transformed
      42              :    INTEGER                                             :: d, i, icell, info, j, jcell, ik, ix, iy, iz
      43            2 :    INTEGER, ALLOCATABLE, DIMENSION(:)                  :: correction_indices
      44              :    INTEGER, DIMENSION(3)                               :: base_dims, delta, large_dims
      45              :    INTEGER, DIMENSION(3, base_cell)                    :: base_index
      46              :    INTEGER, DIMENSION(3)                               :: nkp_grid
      47              :    INTEGER, DIMENSION(3, ncell)                        :: index_to_cell
      48              :    INTEGER, DIMENSION(3, nround)                       :: round_index
      49              :    INTEGER, DIMENSION(nselected)                       :: selected_kpoints
      50              :    LOGICAL                                             :: used_fft
      51              :    REAL(KIND=dp), DIMENSION(3, 3)                      :: hmat
      52              :    REAL(KIND=dp)                                       :: base_residual_norm, corrected_projection_error, &
      53              :                                                           corrected_residual_norm, cost_ratio, &
      54              :                                                           expected_cost_ratio, imaginary_residual, &
      55              :                                                           large_block_real, large_ncell_real, &
      56              :                                                           large_ncol_real, projection_error
      57              :    REAL(KIND=dp), DIMENSION(3, base_cell)              :: base_xkp
      58              :    REAL(KIND=dp), DIMENSION(base_n, base_n)            :: base_overlap
      59              :    REAL(KIND=dp), DIMENSION(base_n, base_n)            :: defect_operator
      60              :    REAL(KIND=dp), DIMENSION(base_n, base_ncol)          :: base_input, base_input_rotated, &
      61              :                                                            base_output, base_output_rotated, &
      62              :                                                            base_state_output, defect_residual
      63            2 :    REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)         :: coarse_inverse, defect_base_output, &
      64            2 :                                                           defect_corrected_output, local_operator, &
      65            2 :                                                           selected_output, selected_rhs
      66              :    REAL(KIND=dp), DIMENSION(base_ncol, base_ncol)       :: column_rotation
      67              :    REAL(KIND=dp), DIMENSION(3, nkp)                    :: xkp, xkp_bad
      68              :    REAL(KIND=dp), DIMENSION(2, 3, ncell)               :: values_rs
      69              :    REAL(KIND=dp), DIMENSION(2, 3, nround)              :: round_rs
      70              :    TYPE(mp_comm_type)                                  :: mp_comm
      71            8 :    TYPE(k_grid_to_cell_work_type)                      :: inverse_work
      72              :    REAL(KIND=dp), DIMENSION(9)                         :: subset_weights
      73              :    COMPLEX(KIND=dp), DIMENSION(2, 3, ncell)            :: weighted_reference, weighted_result
      74              : 
      75            2 :    CALL mp_world_init(mp_comm)
      76            2 :    CALL check_image_reconstruction(mp_comm)
      77              :    CALL init_fft("FFTSG", alltoall=.FALSE., pool_limit=4, &
      78            2 :                  wisdom_file="", plan_style=1)
      79              : 
      80            2 :    nkp_grid = [4, 4, 4]
      81              :    hmat = RESHAPE([4.0_dp, 0.1_dp, 0.2_dp, &
      82              :                    0.0_dp, 5.0_dp, 0.3_dp, &
      83            2 :                    0.0_dp, 0.0_dp, 6.0_dp], [3, 3])
      84              : 
      85            2 :    ik = 0
      86           10 :    DO iz = 0, nkp_grid(3) - 1
      87           42 :       DO iy = 0, nkp_grid(2) - 1
      88          168 :          DO ix = 0, nkp_grid(1) - 1
      89          128 :             ik = ik + 1
      90              :             xkp(:, ik) = [(REAL(ix, dp) + 0.5_dp)/REAL(nkp_grid(1), dp), &
      91              :                           REAL(iy, dp)/REAL(nkp_grid(2), dp), &
      92          544 :                           (REAL(iz, dp) + 0.25_dp)/REAL(nkp_grid(3), dp)]
      93              :          END DO
      94              :       END DO
      95              :    END DO
      96            2 :    IF (.NOT. regular_kpoint_grid(xkp, nkp_grid)) ERROR STOP "Regular k-point grid not recognized"
      97              : 
      98           64 :    DO icell = 1, ncell
      99              :       index_to_cell(:, icell) = [MODULO(3*icell + 1, 11) - 5, &
     100              :                                  MODULO(5*icell + 2, 9) - 4, &
     101          248 :                                  MODULO(7*icell + 3, 7) - 3]
     102          250 :       DO d = 1, 3
     103          186 :          values_rs(1, d, icell) = SIN(0.13_dp*REAL(icell + 2*d, dp))
     104          248 :          values_rs(2, d, icell) = COS(0.17_dp*REAL(2*icell - d, dp))
     105              :       END DO
     106              :    END DO
     107              : 
     108            2 :    CALL cell_to_k_grid_fft(values_rs, index_to_cell, xkp, nkp_grid, transformed, used_fft)
     109            2 :    IF (.NOT. used_fft) ERROR STOP "Complete regular grid did not use lattice FFT"
     110            2 :    CALL direct_reference(values_rs, index_to_cell, xkp, direct)
     111         1282 :    IF (MAXVAL(ABS(transformed - direct)) > 2.0E-12_dp) THEN
     112            0 :       ERROR STOP "Shifted lattice FFT differs from direct transform"
     113              :    END IF
     114              : 
     115            2 :    selected_kpoints = [2, 31, 64]
     116              :    CALL cell_to_k_grid_fft(values_rs, index_to_cell, xkp, nkp_grid, selected_transformed, &
     117            2 :                            used_fft, selected_kpoints=selected_kpoints)
     118            2 :    IF (.NOT. used_fft) ERROR STOP "Selected distributed output did not use lattice FFT"
     119           62 :    IF (MAXVAL(ABS(selected_transformed - direct(:, :, selected_kpoints))) > 2.0E-12_dp) THEN
     120            0 :       ERROR STOP "Selected lattice-FFT output differs from the full transform"
     121              :    END IF
     122              : 
     123            2 :    CALL cell_to_k_grid_fft(values_rs, index_to_cell, xkp, nkp_grid, transformed, used_fft, 2, hmat)
     124            2 :    IF (.NOT. used_fft) ERROR STOP "Derivative did not use lattice FFT"
     125            2 :    CALL direct_reference(values_rs, index_to_cell, xkp, direct, 2, hmat)
     126         1282 :    IF (MAXVAL(ABS(transformed - direct)) > 5.0E-12_dp) THEN
     127            0 :       ERROR STOP "Lattice-FFT derivative differs from direct transform"
     128              :    END IF
     129              : 
     130            2 :    nkp_grid = [4, 4, 1]
     131            2 :    ik = 0
     132           10 :    DO iy = 0, nkp_grid(2) - 1
     133           42 :       DO ix = 0, nkp_grid(1) - 1
     134           32 :          ik = ik + 1
     135              :          xkp(:, ik) = [(REAL(ix, dp) + 0.5_dp)/REAL(nkp_grid(1), dp), &
     136          136 :                        REAL(iy, dp)/REAL(nkp_grid(2), dp), 0.0_dp]
     137              :       END DO
     138              :    END DO
     139              :    CALL cell_to_k_grid_fft(values_rs, index_to_cell, xkp(:, 1:ik), nkp_grid, &
     140            2 :                            transformed(:, :, 1:ik), used_fft)
     141            2 :    IF (.NOT. used_fft) ERROR STOP "Singleton grid direction did not use padded lattice FFT"
     142            2 :    CALL direct_reference(values_rs, index_to_cell, xkp(:, 1:ik), direct(:, :, 1:ik))
     143          322 :    IF (MAXVAL(ABS(transformed(:, :, 1:ik) - direct(:, :, 1:ik))) > 2.0E-12_dp) THEN
     144            0 :       ERROR STOP "Padded lattice FFT differs from direct transform"
     145              :    END IF
     146              : 
     147              :    icell = 0
     148           10 :    DO iy = 0, nkp_grid(2) - 1
     149           42 :       DO ix = 0, nkp_grid(1) - 1
     150           32 :          icell = icell + 1
     151          128 :          round_index(:, icell) = [ix, iy, 0]
     152          328 :          round_rs(:, :, icell) = values_rs(:, :, icell)
     153              :       END DO
     154              :    END DO
     155            2 :    CALL cell_to_k_grid_fft(round_rs, round_index, xkp(:, 1:ik), nkp_grid, round_k, used_fft)
     156            2 :    IF (.NOT. used_fft) ERROR STOP "Round-trip forward transform did not use lattice FFT"
     157            2 :    CALL k_grid_to_cell_fft(round_k, xkp(:, 1:ik), nkp_grid, round_index, reconstructed, used_fft)
     158            2 :    IF (.NOT. used_fft) ERROR STOP "Round-trip inverse transform did not use lattice FFT"
     159          322 :    IF (MAXVAL(ABS(reconstructed - CMPLX(round_rs, KIND=dp))) > 3.0E-12_dp) THEN
     160            0 :       ERROR STOP "Forward/inverse lattice-FFT round trip is inconsistent"
     161              :    END IF
     162              : 
     163              :    ! An incomplete shifted grid uses the supplied weights exactly, including
     164              :    ! negative translations. Reuse the preparation for separate matrix batches.
     165            2 :    subset_weights = [(REAL(ik, dp)/45.0_dp, ik=1, 9)]
     166            2 :    weighted_reference = z_zero
     167           64 :    DO icell = 1, ncell
     168          622 :       DO ik = 1, 9
     169              :          weighted_reference(:, :, icell) = weighted_reference(:, :, icell) + subset_weights(ik)* &
     170         7316 :                                            EXP(-gaussi*twopi*SUM(xkp(:, ik)*REAL(index_to_cell(:, icell), dp)))*round_k(:, :, ik)
     171              :       END DO
     172              :    END DO
     173              :    CALL k_grid_to_cell_prepare(inverse_work, xkp(:, 1:9), nkp_grid, index_to_cell, &
     174            2 :                                weights=subset_weights, allow_incomplete=.TRUE.)
     175            6 :    DO i = 1, 2
     176            4 :       CALL k_grid_to_cell_execute(inverse_work, round_k(i:i, :, 1:9), weighted_result(i:i, :, :), used_fft)
     177            6 :       IF (.NOT. used_fft) ERROR STOP "Prepared incomplete grid did not use FFT"
     178              :    END DO
     179          622 :    IF (MAXVAL(ABS(weighted_result - weighted_reference)) > 3.0E-12_dp) THEN
     180            0 :       ERROR STOP "Prepared weighted inverse differs from explicit phase sum"
     181              :    END IF
     182              :    CALL k_grid_to_cell_fft(round_k(:, :, 1:9), xkp(:, 1:9), nkp_grid, index_to_cell, weighted_result, &
     183            2 :                            used_fft, weights=subset_weights, allow_incomplete=.TRUE.)
     184          622 :    IF (MAXVAL(ABS(weighted_result - weighted_reference)) > 3.0E-12_dp) THEN
     185            0 :       ERROR STOP "One-shot and prepared inverse transforms disagree"
     186              :    END IF
     187              :    CALL k_grid_to_cell_prepare(inverse_work, xkp(:, 1:9), nkp_grid, index_to_cell, &
     188           20 :                                weights=2.0_dp*subset_weights, allow_incomplete=.TRUE.)
     189            2 :    CALL k_grid_to_cell_execute(inverse_work, round_k(:, :, 1:9), weighted_result, used_fft)
     190          622 :    IF (MAXVAL(ABS(weighted_result - 2.0_dp*weighted_reference)) > 6.0E-12_dp) THEN
     191            0 :       ERROR STOP "Repreparing inverse transform retained stale weights"
     192              :    END IF
     193            2 :    CALL k_grid_to_cell_release(inverse_work)
     194            2 :    CALL k_grid_to_cell_release(inverse_work)
     195              : 
     196            2 :    nkp_grid = [4, 4, 4]
     197              : 
     198            2 :    xkp_bad = xkp
     199            2 :    xkp_bad(1, nkp) = xkp_bad(1, nkp) + 0.03125_dp
     200            2 :    CALL cell_to_k_grid_fft(values_rs, index_to_cell, xkp_bad, nkp_grid, transformed, used_fft)
     201            2 :    IF (used_fft) ERROR STOP "Irregular k-point list incorrectly used lattice FFT"
     202            2 :    CALL direct_reference(values_rs, index_to_cell, xkp_bad, direct)
     203         1282 :    IF (MAXVAL(ABS(transformed - direct)) > 1.0E-14_dp) THEN
     204            0 :       ERROR STOP "Direct lattice-transform fallback is inconsistent"
     205              :    END IF
     206              : 
     207              :    ! Verify the block-circulant inverse against direct matrix application and right covariance.
     208            2 :    base_dims = [4, 2, 1]
     209            2 :    CALL lattice_grid(base_dims, base_index, base_xkp)
     210            2 :    base_overlap = 0.0_dp
     211           18 :    DO jcell = 1, base_cell
     212          146 :       DO icell = 1, base_cell
     213          512 :          delta = MODULO(base_index(:, icell) - base_index(:, jcell), base_dims)
     214          384 :          DO i = 1, base_block
     215          512 :             IF (ALL(delta == 0)) THEN
     216              :                base_overlap((icell - 1)*base_block + i, (jcell - 1)*base_block + i) = &
     217           32 :                   2.8_dp + 0.2_dp*REAL(i, KIND=dp)
     218          224 :             ELSE IF ((delta(1) == 1 .OR. delta(1) == base_dims(1) - 1) .AND. &
     219              :                      delta(2) == 0) THEN
     220           64 :                base_overlap((icell - 1)*base_block + i, (jcell - 1)*base_block + i) = -0.12_dp
     221          160 :             ELSE IF (delta(1) == 0 .AND. delta(2) == 1) THEN
     222           32 :                base_overlap((icell - 1)*base_block + i, (jcell - 1)*base_block + i) = -0.08_dp
     223              :             END IF
     224              :          END DO
     225          208 :          IF (ALL(delta == 0)) THEN
     226           16 :             base_overlap((icell - 1)*base_block + 1, (jcell - 1)*base_block + 2) = 0.15_dp
     227           16 :             base_overlap((icell - 1)*base_block + 2, (jcell - 1)*base_block + 1) = 0.15_dp
     228              :          END IF
     229              :       END DO
     230              :    END DO
     231            8 :    DO j = 1, base_ncol
     232          104 :       DO i = 1, base_n
     233          102 :          base_input(i, j) = SIN(0.11_dp*REAL(2*i + 3*j, KIND=dp))
     234              :       END DO
     235              :    END DO
     236            2 :    CALL build_lattice_inverse(base_overlap, base_dims, inverse_k, projection_error, used_fft, info)
     237            2 :    IF (info /= 0) ERROR STOP "Positive lattice overlap failed block Cholesky inversion"
     238            2 :    IF (.NOT. used_fft) ERROR STOP "Block-circulant overlap construction did not use lattice FFT"
     239            2 :    IF (projection_error > 2.0E-15_dp) ERROR STOP "Exact block-circulant overlap was changed by projection"
     240            2 :    IF (lattice_fft_auto_select(base_dims, base_block, base_ncol, 1, 1, &
     241              :                                projection_error, used_fft, info)) THEN
     242            0 :       ERROR STOP "Cost-aware lattice-FFT AUTO accepted a tiny eight-cell operator"
     243              :    END IF
     244            2 :    IF (.NOT. lattice_fft_auto_select(base_dims, 6, 8, 1, 2, &
     245              :                                      projection_error, used_fft, info)) THEN
     246            0 :       ERROR STOP "Cost-aware lattice-FFT AUTO rejected an economical eight-cell operator"
     247              :    END IF
     248            2 :    IF (lattice_fft_auto_select([4, 1, 1], 6, 8, 1, 2, &
     249              :                                projection_error, used_fft, info)) THEN
     250            0 :       ERROR STOP "Conservative lattice-FFT AUTO accepted fewer than eight cells"
     251              :    END IF
     252            2 :    IF (lattice_fft_auto_select(base_dims, 6, 8, 1, 2, 2.0E-3_dp, used_fft, info)) THEN
     253            0 :       ERROR STOP "Conservative lattice-FFT AUTO accepted a large projection error"
     254              :    END IF
     255            2 :    IF (lattice_fft_auto_select(base_dims, 6, 8, 17, 2, &
     256              :                                projection_error, used_fft, info)) THEN
     257            0 :       ERROR STOP "Storage-aware lattice-FFT AUTO accepted too many state operators"
     258              :    END IF
     259            2 :    IF (lattice_fft_cost_ratio(base_dims, base_block, base_ncol, 1) <= 0.8_dp) THEN
     260            0 :       ERROR STOP "Lattice-FFT cost estimate missed the tiny-batch overhead"
     261              :    END IF
     262            2 :    large_dims = [100, 100, 1]
     263            8 :    large_ncell_real = REAL(PRODUCT(large_dims), KIND=dp)
     264            2 :    large_block_real = REAL(large_block, KIND=dp)
     265            2 :    large_ncol_real = REAL(large_ncol, KIND=dp)
     266              :    expected_cost_ratio = ( &
     267              :                          large_ncell_real*large_block_real**2*large_ncol_real + &
     268              :                          4.0_dp*large_ncell_real*large_block_real*large_ncol_real* &
     269              :                          LOG(large_ncell_real)/LOG(2.0_dp) + &
     270              :                          8.0_dp*large_ncell_real*large_block_real + &
     271              :                          2.0_dp*large_ncell_real*large_block_real*large_ncol_real* &
     272              :                          LOG(REAL(large_num_pe + 1, KIND=dp))/LOG(2.0_dp))/ &
     273            2 :                          ((large_ncell_real*large_block_real)**2*large_ncol_real)
     274            2 :    cost_ratio = lattice_fft_cost_ratio(large_dims, large_block, large_ncol, large_num_pe)
     275            2 :    IF (ABS(cost_ratio - expected_cost_ratio) > 1.0E-14_dp*expected_cost_ratio) THEN
     276            0 :       ERROR STOP "Lattice-FFT cost estimate overflowed for a large problem"
     277              :    END IF
     278            2 :    IF (ABS(lattice_fft_storage_ratio(base_dims, 1) - 0.25_dp) > 1.0E-14_dp) THEN
     279            0 :       ERROR STOP "Lattice-FFT storage estimate is inconsistent"
     280              :    END IF
     281            2 :    IF (ABS(lattice_local_cost_ratio(base_dims, 1) - 0.28125_dp) > 1.0E-14_dp) THEN
     282            0 :       ERROR STOP "Lattice local-correction cost estimate is inconsistent"
     283              :    END IF
     284            2 :    IF (ABS(lattice_local_storage_ratio(base_dims, 3, 1) - 0.421875_dp) > 1.0E-14_dp) THEN
     285            0 :       ERROR STOP "Lattice local-correction storage estimate is inconsistent"
     286              :    END IF
     287              :    CALL apply_lattice_inverse_dense(inverse_k, base_dims, base_input, base_output, &
     288            2 :                                     used_fft, imaginary_residual)
     289            2 :    IF (.NOT. used_fft) ERROR STOP "Block-circulant inverse application did not use lattice FFT"
     290            2 :    IF (imaginary_residual > 3.0E-13_dp) ERROR STOP "Lattice inverse did not reconstruct a real matrix"
     291         1742 :    IF (MAXVAL(ABS(MATMUL(base_overlap, base_output) - base_input)) > 2.0E-11_dp) THEN
     292            0 :       ERROR STOP "FFT block-circulant inverse differs from the dense operator inverse"
     293              :    END IF
     294              : 
     295            2 :    ALLOCATE (state_inverse_k(base_block, base_block, base_cell, base_ncol))
     296            8 :    DO j = 1, base_ncol
     297          344 :       state_inverse_k(:, :, :, j) = (0.5_dp + 0.25_dp*REAL(j, KIND=dp))*inverse_k
     298              :    END DO
     299              :    CALL apply_lattice_state_inverse_dense(state_inverse_k, base_dims, base_input, &
     300            2 :                                           base_state_output, used_fft, imaginary_residual)
     301            2 :    IF (.NOT. used_fft) ERROR STOP "State-dependent inverse application did not use lattice FFT"
     302            8 :    DO j = 1, base_ncol
     303          102 :       IF (MAXVAL(ABS(base_state_output(:, j) - &
     304            2 :                      (0.5_dp + 0.25_dp*REAL(j, KIND=dp))*base_output(:, j))) > 3.0E-12_dp) THEN
     305            0 :          ERROR STOP "State-dependent lattice inverse used the wrong column operator"
     306              :       END IF
     307              :    END DO
     308            2 :    DEALLOCATE (state_inverse_k)
     309              : 
     310            2 :    column_rotation = 0.0_dp
     311            2 :    column_rotation(1, 1) = COS(0.31_dp)
     312            2 :    column_rotation(1, 2) = -SIN(0.31_dp)
     313            2 :    column_rotation(2, 1) = SIN(0.31_dp)
     314            2 :    column_rotation(2, 2) = COS(0.31_dp)
     315            2 :    column_rotation(3, 3) = 1.0_dp
     316          314 :    base_input_rotated = MATMUL(base_input, column_rotation)
     317              :    CALL apply_lattice_inverse_dense(inverse_k, base_dims, base_input_rotated, &
     318            2 :                                     base_output_rotated, used_fft)
     319          416 :    IF (MAXVAL(ABS(base_output_rotated - MATMUL(base_output, column_rotation))) > 3.0E-12_dp) THEN
     320            0 :       ERROR STOP "FFT lattice base is not right-covariant under orbital rotations"
     321              :    END IF
     322              : 
     323              :    ! Add one localized positive defect and verify the balanced cell correction.
     324            2 :    defect_operator = base_overlap
     325            2 :    defect_operator(5, 5) = defect_operator(5, 5) + 0.90_dp
     326            2 :    defect_operator(6, 6) = defect_operator(6, 6) + 0.60_dp
     327            2 :    CALL build_lattice_inverse(defect_operator, base_dims, inverse_k, projection_error, used_fft, info)
     328            2 :    IF (info /= 0 .OR. .NOT. used_fft) ERROR STOP "Defective lattice operator failed construction"
     329            2 :    IF (projection_error <= 1.0E-3_dp) ERROR STOP "Localized defect did not break lattice translations"
     330            2 :    IF (lattice_fft_auto_select(base_dims, 6, 8, 1, 2, projection_error, used_fft, info)) THEN
     331            0 :       ERROR STOP "Lattice-FFT AUTO accepted a localized defect"
     332              :    END IF
     333              :    CALL build_lattice_local_correction( &
     334              :       defect_operator, base_dims, 1, correction_indices, local_operator, coarse_inverse, &
     335            2 :       corrected_projection_error, info)
     336            2 :    IF (info /= 0) ERROR STOP "Local correction coarse inverse failed"
     337            6 :    IF (ANY(correction_indices /= [5, 6])) ERROR STOP "Local correction selected the wrong defect cell"
     338            2 :    IF (corrected_projection_error >= projection_error) THEN
     339            0 :       ERROR STOP "Local correction did not reduce the non-circulant residual"
     340              :    END IF
     341              : 
     342            2 :    ALLOCATE (defect_base_output(base_n, base_ncol), defect_corrected_output(base_n, base_ncol))
     343            2 :    CALL apply_lattice_inverse_dense(inverse_k, base_dims, base_input, defect_base_output, used_fft)
     344              :    CALL apply_balanced_local_inverse(inverse_k, base_dims, correction_indices, local_operator, &
     345            2 :                                      coarse_inverse, base_input, defect_corrected_output)
     346         3280 :    defect_residual = MATMUL(defect_operator, defect_base_output) - base_input
     347          104 :    base_residual_norm = NORM2(defect_residual)
     348         1742 :    defect_residual = MATMUL(defect_operator, defect_corrected_output) - base_input
     349          104 :    corrected_residual_norm = NORM2(defect_residual)
     350            2 :    IF (corrected_residual_norm >= base_residual_norm) THEN
     351            0 :       ERROR STOP "Balanced local correction did not improve inverse application"
     352              :    END IF
     353          104 :    IF (SUM(base_input*defect_corrected_output) <= 0.0_dp) THEN
     354            0 :       ERROR STOP "Balanced local correction lost positive definiteness"
     355              :    END IF
     356              : 
     357            0 :    ALLOCATE (selected_rhs(base_n, SIZE(correction_indices)), &
     358            8 :              selected_output(base_n, SIZE(correction_indices)))
     359           70 :    selected_rhs(:, :) = local_operator
     360              :    CALL apply_balanced_local_inverse(inverse_k, base_dims, correction_indices, local_operator, &
     361            2 :                                      coarse_inverse, selected_rhs, selected_output)
     362            2 :    selected_rhs = 0.0_dp
     363            6 :    DO i = 1, SIZE(correction_indices)
     364            6 :       selected_rhs(correction_indices(i), i) = 1.0_dp
     365              :    END DO
     366           70 :    IF (MAXVAL(ABS(selected_output - selected_rhs)) > 3.0E-11_dp) THEN
     367            0 :       ERROR STOP "Balanced local correction is not exact in its selected subspace"
     368              :    END IF
     369            0 :    DEALLOCATE (coarse_inverse, correction_indices, defect_base_output, defect_corrected_output, &
     370            2 :                local_operator, selected_output, selected_rhs)
     371            2 :    DEALLOCATE (inverse_k)
     372              : 
     373            2 :    CALL finalize_fft(mp_comm, "")
     374            2 :    CALL mp_world_finalize()
     375              : 
     376              : CONTAINS
     377              : 
     378              : ! **************************************************************************************************
     379              : !> \brief Check triangular image storage against rskp_transform for both neighbor orientations.
     380              : !> \param comm MPI communicator
     381              : ! **************************************************************************************************
     382            2 :    SUBROUTINE check_image_reconstruction(comm)
     383              :       USE cp_dbcsr_api, ONLY: &
     384              :          dbcsr_create, dbcsr_distribution_new, dbcsr_distribution_release, dbcsr_distribution_type, &
     385              :          dbcsr_finalize, dbcsr_finalize_lib, dbcsr_get_block_p, dbcsr_init_lib, dbcsr_p_type, &
     386            2 :          dbcsr_put_block, dbcsr_release, dbcsr_type, dbcsr_type_antisymmetric, dbcsr_type_symmetric
     387              :       USE cp_log_handling, ONLY: cp_add_default_logger, cp_logger_create, cp_logger_release, &
     388              :                                  cp_logger_type, cp_rm_default_logger
     389              :       USE kpoint_k_r_trafo_simple, ONLY: replicate_rs_matrices, rs_to_kp
     390              :       USE kpoint_methods, ONLY: rskp_transform
     391              :       USE kpoint_types, ONLY: kpoint_type
     392              :       USE machine, ONLY: default_output_unit
     393              :       USE message_passing, ONLY: mp_para_env_release, mp_para_env_type
     394              :       USE qs_neighbor_list_types, ONLY: add_neighbor_list, add_neighbor_node, &
     395              :                                         allocate_neighbor_list_set, deallocate_neighbor_list_set, neighbor_list_p_type
     396              :       TYPE(mp_comm_type), INTENT(IN)                     :: comm
     397              : 
     398              :       CHARACTER                                          :: matrix_type, mirror_type
     399              :       COMPLEX(dp)                                        :: expected(5, 5), phase, RESULT(5, 5)
     400              :       INTEGER                                            :: atom, ik, image(3), img, io, mode, &
     401              :                                                             neighbor, orientation, owner
     402            2 :       INTEGER, POINTER                                   :: col_dist(:), images(:, :), &
     403            2 :                                                             process_grid(:, :), row_dist(:), &
     404            2 :                                                             sizes(:)
     405              :       LOGICAL                                            :: found
     406              :       REAL(dp)                                           :: expanded(1, 5, 5, 3), point(3), sign, &
     407              :                                                             values(2, 3)
     408            2 :       REAL(dp), POINTER                                  :: cosine(:, :), sine(:, :)
     409              :       TYPE(cp_logger_type), POINTER                      :: logger
     410              :       TYPE(dbcsr_distribution_type)                      :: distribution
     411            2 :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
     412              :       TYPE(dbcsr_type)                                   :: cosine_matrix, sine_matrix
     413              :       TYPE(kpoint_type), POINTER                         :: points
     414              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     415              :       TYPE(neighbor_list_p_type)                         :: neighbors
     416              : 
     417            0 :       ALLOCATE (para_env)
     418            2 :       CALL para_env%from_dup(comm)
     419            2 :       io = -1
     420            2 :       IF (comm%mepos == 0) io = default_output_unit
     421            2 :       NULLIFY (logger)
     422              :       CALL cp_logger_create(logger, para_env=para_env, default_global_unit_nr=io, &
     423            2 :                             close_global_unit_on_dealloc=.FALSE.)
     424            2 :       CALL cp_add_default_logger(logger)
     425            2 :       CALL dbcsr_init_lib(comm%get_handle(), io)
     426            6 :       ALLOCATE (process_grid(0:comm%num_pe - 1, 0:0), row_dist(2), col_dist(2), sizes(2))
     427           10 :       process_grid(:, 0) = [(img, img=0, comm%num_pe - 1)]
     428            6 :       col_dist = 0
     429            6 :       sizes = [2, 3]
     430           30 :       ALLOCATE (points, source(1, 3), images(3, 3))
     431            4 :       ALLOCATE (points%cell_to_index(-1:1, 0:0, 0:0), points%sab_nl(1))
     432            8 :       DO img = 1, 3
     433            6 :          points%cell_to_index(img - 2, 0, 0) = img
     434           24 :          images(:, img) = [img - 2, 0, 0]
     435            8 :          ALLOCATE (source(1, img)%matrix)
     436              :       END DO
     437            2 :       values = RESHAPE([0.3_dp, -0.2_dp, 0.7_dp, 0.1_dp, -0.4_dp, 0.6_dp], [2, 3])
     438            6 :       DO mode = 1, 2
     439            4 :          matrix_type = MERGE(dbcsr_type_symmetric, dbcsr_type_antisymmetric, mode == 1)
     440            4 :          mirror_type = MERGE(dbcsr_type_antisymmetric, dbcsr_type_symmetric, mode == 1)
     441            4 :          sign = MERGE(1.0_dp, -1.0_dp, mode == 1)
     442           14 :          DO orientation = 1, 2
     443            8 :             owner = MODULO(orientation - 1, comm%num_pe)
     444           24 :             row_dist = [owner, MODULO(owner + 1, comm%num_pe)]
     445              :             CALL dbcsr_distribution_new(distribution, group=comm%get_handle(), pgrid=process_grid, &
     446            8 :                                         row_dist=row_dist, col_dist=col_dist)
     447            8 :             atom = orientation
     448            8 :             neighbor = 3 - orientation
     449           32 :             image = [3 - 2*orientation, 0, 0]
     450            8 :             CALL allocate_neighbor_list_set(points%sab_nl(1)%neighbor_list_set, symmetric=.TRUE.)
     451            8 :             CALL add_neighbor_list(points%sab_nl(1)%neighbor_list_set, atom, neighbors%neighbor_list)
     452           32 :             CALL add_neighbor_node(neighbors%neighbor_list, neighbor, image, REAL(image, dp))
     453           32 :             DO img = 1, 3
     454              :                CALL dbcsr_create(source(1, img)%matrix, name="Image reconstruction", dist=distribution, &
     455           24 :                                  matrix_type=matrix_type, row_blk_size=sizes, col_blk_size=sizes)
     456           24 :                IF (comm%mepos == owner .AND. img == image(1) + 2) THEN
     457            4 :                   CALL dbcsr_put_block(source(1, img)%matrix, 1, 2, values)
     458              :                END IF
     459           32 :                CALL dbcsr_finalize(source(1, img)%matrix)
     460              :             END DO
     461              :             CALL dbcsr_create(cosine_matrix, name="Image cosine", dist=distribution, matrix_type=matrix_type, &
     462            8 :                               row_blk_size=sizes, col_blk_size=sizes)
     463              :             CALL dbcsr_create(sine_matrix, name="Image sine", dist=distribution, matrix_type=mirror_type, &
     464            8 :                               row_blk_size=sizes, col_blk_size=sizes)
     465            8 :             IF (comm%mepos == owner) THEN
     466           40 :                CALL dbcsr_put_block(cosine_matrix, 1, 2, 0.0_dp*values)
     467           40 :                CALL dbcsr_put_block(sine_matrix, 1, 2, 0.0_dp*values)
     468              :             END IF
     469            8 :             CALL dbcsr_finalize(cosine_matrix)
     470            8 :             CALL dbcsr_finalize(sine_matrix)
     471            8 :             expanded = 0.0_dp
     472            8 :             CALL replicate_rs_matrices(source, points, expanded, points%cell_to_index)
     473           24 :             DO ik = 1, 2
     474           64 :                point = [0.137_dp, 0.217_dp, 0.089_dp]*REAL(ik - 1, dp)
     475           16 :                CALL rs_to_kp(expanded(1, :, :, :), RESULT, images, point)
     476           16 :                phase = EXP(gaussi*twopi*point(1))
     477           16 :                expected = z_zero
     478          160 :                expected(1:2, 3:5) = phase*values
     479          272 :                expected(3:5, 1:2) = sign*CONJG(TRANSPOSE(expected(1:2, 3:5)))
     480          496 :                IF (MAXVAL(ABS(RESULT - expected)) > 1.e-12_dp) THEN
     481            0 :                   ERROR STOP "Image reconstruction depends on neighbor orientation"
     482              :                END IF
     483              :                CALL rskp_transform(cosine_matrix, sine_matrix, source, 1, point, &
     484           16 :                                    points%cell_to_index, points%sab_nl)
     485           16 :                CALL dbcsr_get_block_p(cosine_matrix, 1, 2, cosine, found)
     486           16 :                IF (comm%mepos == owner .AND. .NOT. found) ERROR STOP "Missing image cosine block"
     487           24 :                IF (found) THEN
     488            8 :                   CALL dbcsr_get_block_p(sine_matrix, 1, 2, sine, found)
     489            8 :                   IF (.NOT. found) ERROR STOP "Missing image sine block"
     490           80 :                   IF (MAXVAL(ABS(RESULT(1:2, 3:5) - CMPLX(cosine, sine, KIND=dp))) > 1.e-12_dp) THEN
     491            0 :                      ERROR STOP "Image reconstruction differs from rskp_transform"
     492              :                   END IF
     493           80 :                   cosine = 0.0_dp
     494           80 :                   sine = 0.0_dp
     495              :                END IF
     496              :             END DO
     497            8 :             CALL dbcsr_release(cosine_matrix)
     498            8 :             CALL dbcsr_release(sine_matrix)
     499           32 :             DO img = 1, 3
     500           32 :                CALL dbcsr_release(source(1, img)%matrix)
     501              :             END DO
     502            8 :             CALL dbcsr_distribution_release(distribution)
     503           12 :             CALL deallocate_neighbor_list_set(points%sab_nl(1)%neighbor_list_set)
     504              :          END DO
     505              :       END DO
     506            8 :       DO img = 1, 3
     507            8 :          DEALLOCATE (source(1, img)%matrix)
     508              :       END DO
     509            2 :       DEALLOCATE (points%cell_to_index, points%sab_nl)
     510            2 :       DEALLOCATE (points, source, images)
     511            2 :       DEALLOCATE (process_grid, row_dist, col_dist, sizes)
     512            2 :       CALL dbcsr_finalize_lib()
     513            2 :       CALL cp_rm_default_logger()
     514            2 :       CALL cp_logger_release(logger)
     515            2 :       CALL mp_para_env_release(para_env)
     516            4 :    END SUBROUTINE check_image_reconstruction
     517              : 
     518              : ! **************************************************************************************************
     519              : !> \brief Apply the balanced two-level inverse used by the production local correction.
     520              : !> \param inverse_k block-circulant base inverse
     521              : !> \param lattice_dims replicated-cell dimensions
     522              : !> \param correction_indices selected coordinate basis
     523              : !> \param operator_columns columns A U
     524              : !> \param coarse_inverse inverse of U^T A U
     525              : !> \param input right-hand sides
     526              : !> \param output corrected inverse action
     527              : ! **************************************************************************************************
     528           20 :    SUBROUTINE apply_balanced_local_inverse(inverse_k, lattice_dims, correction_indices, &
     529            4 :                                            operator_columns, coarse_inverse, input, output)
     530              : 
     531              :       COMPLEX(KIND=dp), DIMENSION(:, :, :), INTENT(IN)   :: inverse_k
     532              :       INTEGER, DIMENSION(3), INTENT(IN)                  :: lattice_dims
     533              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: correction_indices
     534              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: operator_columns, coarse_inverse, input
     535              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: output
     536              : 
     537              :       INTEGER                                            :: i
     538              :       LOGICAL                                            :: used_fft
     539            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: base_input, coarse_input, &
     540            4 :                                                             coarse_projected, coarse_residual, &
     541              :                                                             coarse_rhs
     542              : 
     543              :       ALLOCATE (base_input(SIZE(input, 1), SIZE(input, 2)), &
     544              :                 coarse_input(SIZE(correction_indices), SIZE(input, 2)), &
     545              :                 coarse_projected(SIZE(correction_indices), SIZE(input, 2)), &
     546              :                 coarse_residual(SIZE(correction_indices), SIZE(input, 2)), &
     547           52 :                 coarse_rhs(SIZE(correction_indices), SIZE(input, 2)))
     548           12 :       DO i = 1, SIZE(correction_indices)
     549           32 :          coarse_input(i, :) = input(correction_indices(i), :)
     550              :       END DO
     551           74 :       coarse_rhs(:, :) = MATMUL(coarse_inverse, coarse_input)
     552          528 :       base_input(:, :) = input - MATMUL(operator_columns, coarse_rhs)
     553            4 :       CALL apply_lattice_inverse_dense(inverse_k, lattice_dims, base_input, output, used_fft)
     554            4 :       IF (.NOT. used_fft) ERROR STOP "Balanced correction base did not use lattice FFT"
     555          352 :       coarse_projected(:, :) = MATMUL(TRANSPOSE(operator_columns), output)
     556           74 :       coarse_residual(:, :) = MATMUL(coarse_inverse, coarse_projected)
     557           34 :       output(correction_indices, :) = output(correction_indices, :) + coarse_rhs - coarse_residual
     558            4 :       DEALLOCATE (base_input, coarse_input, coarse_projected, coarse_residual, coarse_rhs)
     559              : 
     560            2 :    END SUBROUTINE apply_balanced_local_inverse
     561              : 
     562              : ! **************************************************************************************************
     563              : !> \brief ...
     564              : !> \param values_rs ...
     565              : !> \param index_to_cell ...
     566              : !> \param xkp ...
     567              : !> \param values_k ...
     568              : !> \param deriv_direction ...
     569              : !> \param hmat ...
     570              : ! **************************************************************************************************
     571            8 :    SUBROUTINE direct_reference(values_rs, index_to_cell, xkp, values_k, deriv_direction, hmat)
     572              : 
     573              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(IN)      :: values_rs
     574              :       INTEGER, DIMENSION(:, :), INTENT(IN)               :: index_to_cell
     575              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: xkp
     576              :       COMPLEX(KIND=dp), DIMENSION(:, :, :), INTENT(OUT)  :: values_k
     577              :       INTEGER, INTENT(IN), OPTIONAL                      :: deriv_direction
     578              :       REAL(KIND=dp), DIMENSION(3, 3), INTENT(IN), &
     579              :          OPTIONAL                                        :: hmat
     580              : 
     581              :       COMPLEX(KIND=dp)                                   :: factor
     582              :       INTEGER                                            :: icell, ik
     583              :       REAL(KIND=dp), DIMENSION(3)                        :: cell_index_real, cell_vector
     584              : 
     585         4168 :       values_k = z_zero
     586          424 :       DO ik = 1, SIZE(xkp, 2)
     587        13320 :          DO icell = 1, SIZE(values_rs, 3)
     588        51584 :             factor = EXP(gaussi*twopi*SUM(xkp(:, ik)*REAL(index_to_cell(:, icell), KIND=dp)))
     589        12896 :             IF (PRESENT(deriv_direction)) THEN
     590        15872 :                cell_index_real = REAL(index_to_cell(:, icell), KIND=dp)
     591        51584 :                cell_vector = MATMUL(hmat, cell_index_real)
     592         3968 :                factor = factor*gaussi*cell_vector(deriv_direction)
     593              :             END IF
     594       129376 :             values_k(:, :, ik) = values_k(:, :, ik) + factor*values_rs(:, :, icell)
     595              :          END DO
     596              :       END DO
     597              : 
     598            8 :    END SUBROUTINE direct_reference
     599              : 
     600              : END PROGRAM kpoint_lattice_fft_unittest
        

Generated by: LCOV version 2.0-1