LCOV - code coverage report
Current view: top level - src - spectral_localizer_unittest.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:9ccafda) Lines: 96.9 % 938 909
Test Date: 2026-10-15 00:55:44 Functions: 100.0 % 20 20

            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              : 
       8              : MODULE spectral_localizer_test_cases
       9              :    USE cp_log_handling,                 ONLY: cp_logger_get_default_io_unit
      10              : 
      11              :    IMPLICIT NONE
      12              :    PRIVATE
      13              :    PUBLIC :: check_localizer_2d, check_localizer_3d, check_localizer_flattening
      14              : CONTAINS
      15              : 
      16              : ! **************************************************************************************************
      17              : !> \brief Independent finite-model, metric, orientation and Pfaffian tests.
      18              : !> \author Thomas D. Kuehne, tkuehne@cp2k.org
      19              : ! **************************************************************************************************
      20            2 :    SUBROUTINE check_localizer_2d()
      21              : #if defined(__HAS_IEEE_EXCEPTIONS)
      22              :       USE ieee_exceptions, ONLY: IEEE_ALL, ieee_get_halting_mode
      23              : #endif
      24              :       USE kinds, ONLY: dp
      25              :       USE spectral_localizer, ONLY: localizer_aii_skew, &
      26              :                                     localizer_evaluate, &
      27              :                                     localizer_inertia, &
      28              :                                     localizer_periodic_evaluate, &
      29              :                                     localizer_pfaffian_sign
      30              : 
      31              :       IMPLICIT NONE
      32              : #if defined(__HAS_IEEE_EXCEPTIONS)
      33              :       LOGICAL :: original_halt(SIZE(IEEE_ALL)), current_halt(SIZE(IEEE_ALL))
      34              : #endif
      35            2 :       COMPLEX(KIND=dp), ALLOCATABLE :: h(:, :), s(:, :), r(:, :, :), transform(:, :), &
      36            2 :                                        hc(:, :), sc(:, :), rc(:, :, :), hs(:, :), ss(:, :), rs(:, :, :)
      37              :       INTEGER :: axis, i, index, reference, status, n, pf, permutation(4), io
      38              :       REAL(KIND=dp) :: gap, gap_reference, ratio, skew(4, 4), exact
      39              : 
      40            2 :       io = cp_logger_get_default_io_unit()
      41              : #if defined(__HAS_IEEE_EXCEPTIONS)
      42              :       CALL ieee_get_halting_mode(IEEE_ALL, original_halt)
      43              : #endif
      44            2 :       CALL inertia_tests()
      45            2 :       CALL real_inertia_tests()
      46            2 :       CALL gap_tests()
      47            2 :       CALL pfaffian_orientation_tests()
      48            2 :       CALL aii_transform_tests()
      49            2 :       CALL aii_roundoff_tests()
      50            2 :       CALL periodic_tests()
      51              :       ! Independent Pfaffian polynomial for size four, including odd permutations.
      52            2 :       skew = 0.0_dp
      53            2 :       skew(1, 2) = 0.3_dp
      54            2 :       skew(1, 3) = 2.0_dp
      55            2 :       skew(1, 4) = -1.0_dp
      56            2 :       skew(2, 3) = 0.7_dp
      57            2 :       skew(2, 4) = 3.0_dp
      58            2 :       skew(3, 4) = 0.8_dp
      59           82 :       skew = skew - TRANSPOSE(skew)
      60            2 :       exact = skew(1, 2)*skew(3, 4) - skew(1, 3)*skew(2, 4) + skew(1, 4)*skew(2, 3)
      61            2 :       CALL localizer_pfaffian_sign(skew, pf, status)
      62            2 :       IF (status /= 0 .OR. pf /= INT(SIGN(1.0_dp, exact))) ERROR STOP 'Pfaffian polynomial failed'
      63            2 :       permutation = [2, 1, 3, 4]
      64           42 :       CALL localizer_pfaffian_sign(skew(permutation, permutation), pf, status)
      65            2 :       IF (status /= 0 .OR. pf /= -INT(SIGN(1.0_dp, exact))) ERROR STOP 'Pfaffian orientation failed'
      66            2 :       skew = 0.0_dp
      67            2 :       CALL localizer_pfaffian_sign(skew, pf, status)
      68            2 :       IF (status == 0 .OR. pf /= 0) ERROR STOP 'Singular Pfaffian accepted'
      69              : 
      70            2 :       ALLOCATE (h(1, 1), s(1, 1), r(1, 1, 2))
      71            6 :       h = 4.0_dp
      72            6 :       s = 2.0_dp
      73            6 :       r(1, 1, :) = [6.0_dp, 8.0_dp]
      74            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp], 1.0_dp, 0.2_dp, .FALSE., index, gap, status)
      75            2 :       IF (status /= 0 .OR. index /= 0 .OR. ABS(gap - SQRT(2.0_dp)) > 1.e-12_dp) THEN
      76            0 :          ERROR STOP 'Analytic generalized localizer failed'
      77              :       END IF
      78            2 :       CALL evaluate(h, s, r, [3.0_dp, 4.0_dp], 2.0_dp, 0.2_dp, .FALSE., index, gap, status)
      79            2 :       IF (status /= 1) ERROR STOP 'Zero gap was assigned an index'
      80            2 :       DEALLOCATE (h, s, r)
      81              : 
      82            2 :       ALLOCATE (h(2, 2), s(2, 2), r(2, 2, 2))
      83            2 :       h = 0.0_dp
      84            2 :       s = 0.0_dp
      85            2 :       r = 0.0_dp
      86            2 :       s(1, 1) = 1.e-9_dp
      87            2 :       s(2, 2) = 1.0_dp
      88            2 :       h(1, 1) = 1.e-16_dp
      89            2 :       h(2, 2) = 1.0_dp
      90            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp], 0.0_dp, 0.2_dp, .FALSE., index, gap, status)
      91            2 :       IF (status /= 1) ERROR STOP 'Metric-amplified roundoff ignored'
      92            2 :       DEALLOCATE (h, s, r)
      93              : 
      94            2 :       CALL qwz_model(7, -1.0_dp, h, s, r)
      95            2 :       n = SIZE(h, 1)
      96            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., reference, gap_reference, status)
      97            2 :       IF (status /= 0 .OR. reference /= -1 .OR. ABS(gap_reference - 0.740327333453389_dp) > 1.e-11_dp) THEN
      98            0 :          ERROR STOP 'Nontrivial open QWZ model failed'
      99              :       END IF
     100        38814 :       CALL evaluate(h, s, r(:, :, [2, 1]), [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., index, gap, status)
     101            2 :       IF (status /= 0 .OR. index /= -reference .OR. ABS(gap - gap_reference) > 1.e-11_dp) THEN
     102            0 :          ERROR STOP 'Oriented-plane reversal failed'
     103              :       END IF
     104        19406 :       CALL evaluate(CONJG(h), s, r, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., index, gap, status)
     105            2 :       IF (status /= 0 .OR. index /= -reference) ERROR STOP 'Time reversed Chern sign failed'
     106            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp], 0.0_dp, 0.15_dp, .FALSE., index, gap, status)
     107            2 :       IF (status /= 0 .OR. index /= reference) ERROR STOP 'Kappa plateau failed'
     108            2 :       CALL evaluate(h, s, r, [20.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., index, gap, status)
     109            2 :       IF (status /= 0 .OR. index /= 0) ERROR STOP 'Outside-vacuum limit failed'
     110              : 
     111           24 :       ALLOCATE (transform(n, n), hc(n, n), sc(n, n), rc(n, n, 2))
     112            2 :       transform = 0.0_dp
     113          198 :       DO i = 1, n
     114          196 :          transform(i, i) = (1.0_dp + REAL(MOD(i, 5), dp)/5.0_dp)*EXP(CMPLX(0.0_dp, 0.17_dp*i, dp))
     115          198 :          IF (i < n) transform(i, i + 1) = CMPLX(0.05_dp, 0.03_dp, dp)
     116              :       END DO
     117        19406 :       hc(:, :) = MATMUL(CONJG(TRANSPOSE(transform)), MATMUL(h, transform))
     118        19406 :       sc(:, :) = MATMUL(CONJG(TRANSPOSE(transform)), MATMUL(s, transform))
     119            6 :       DO axis = 1, 2
     120        38814 :          rc(:, :, axis) = MATMUL(CONJG(TRANSPOSE(transform)), MATMUL(r(:, :, axis), transform))
     121              :       END DO
     122            2 :       CALL evaluate(hc, sc, rc, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., index, gap, status)
     123            2 :       IF (status /= 0 .OR. index /= reference .OR. ABS(gap - gap_reference) > 1.e-11_dp) THEN
     124            0 :          ERROR STOP 'Complex nonorthogonal basis invariance failed'
     125              :       END IF
     126        19406 :       rc(:, :, 1) = rc(:, :, 1) + 13.0_dp*sc
     127        19406 :       rc(:, :, 2) = rc(:, :, 2) - 7.0_dp*sc
     128        19406 :       hc(:, :) = hc + 11.0_dp*sc
     129            2 :       CALL evaluate(hc, sc, rc, [13.0_dp, -7.0_dp], 11.0_dp, 0.25_dp, .FALSE., index, gap, status)
     130            2 :       IF (status /= 0 .OR. index /= reference .OR. ABS(gap - gap_reference) > 1.e-11_dp) THEN
     131            0 :          ERROR STOP 'Origin/energy shift covariance failed'
     132              :       END IF
     133            2 :       sc = 0.0_dp
     134          198 :       DO i = 1, n
     135          198 :          sc(i, i) = 1.0_dp
     136              :       END DO
     137            2 :       sc(1, 1) = 1.e-14_dp
     138            2 :       CALL evaluate(hc, sc, rc, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., index, gap, status)
     139            2 :       IF (status /= -2) ERROR STOP 'Ill-conditioned metric accepted'
     140            2 :       sc(1, 1) = -1.0_dp
     141            2 :       CALL evaluate(hc, sc, rc, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., index, gap, status)
     142            2 :       IF (status /= -2) ERROR STOP 'Indefinite metric accepted'
     143            2 :       hc(1, 2) = hc(1, 2) + 1.0_dp
     144            2 :       CALL evaluate(hc, s, r, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., index, gap, status)
     145            2 :       IF (status /= -1) ERROR STOP 'Non-Hermitian Hamiltonian accepted'
     146              : 
     147              :       ! Two time-reversed Chern copies, with TR-preserving, spin-nonconserving mixing.
     148           20 :       ALLOCATE (hs(2*n, 2*n), ss(2*n, 2*n), rs(2*n, 2*n, 2))
     149            2 :       hs = 0.0_dp
     150            2 :       ss = 0.0_dp
     151            2 :       rs = 0.0_dp
     152        19406 :       hs(:n, :n) = h
     153        19406 :       hs(n + 1:, n + 1:) = CONJG(h)
     154        19406 :       ss(:n, :n) = s
     155        19406 :       ss(n + 1:, n + 1:) = s
     156        38814 :       rs(:n, :n, :) = r
     157        38814 :       rs(n + 1:, n + 1:, :) = r
     158            2 :       DO i = 1, n, 2
     159           98 :          hs(i, n + i + 1) = 0.1_dp
     160           98 :          hs(i + 1, n + i) = -0.1_dp
     161              :       END DO
     162        38810 :       hs(n + 1:, :n) = CONJG(TRANSPOSE(hs(:n, n + 1:)))
     163            2 :       CALL evaluate(hs, ss, rs, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .TRUE., index, gap, status)
     164            2 :       IF (status /= 0 .OR. index /= 1) ERROR STOP 'Spin-mixed nontrivial AII model failed'
     165            2 :       gap_reference = gap
     166              :       ! Real, nonorthogonal AO change applied to both spin components preserves fixed Theta.
     167        19406 :       transform = REAL(transform, dp)
     168          198 :       DO i = 1, n
     169          198 :          transform(i, i) = 1.0_dp + REAL(MOD(i, 5), dp)/5.0_dp
     170              :       END DO
     171            6 :       DO axis = 1, 2
     172           14 :          DO i = 1, 2
     173              :             hs((i - 1)*n + 1:i*n, (axis - 1)*n + 1:axis*n) = &
     174        77628 :                MATMUL(TRANSPOSE(transform), MATMUL(hs((i - 1)*n + 1:i*n, (axis - 1)*n + 1:axis*n), transform))
     175              :          END DO
     176              :       END DO
     177            4 :       sc(:, :) = MATMUL(TRANSPOSE(transform), MATMUL(s, transform))
     178        19406 :       ss(:n, :n) = sc
     179        19406 :       ss(n + 1:, n + 1:) = sc
     180            6 :       DO axis = 1, 2
     181            8 :          rc(:, :, axis) = MATMUL(TRANSPOSE(transform), MATMUL(r(:, :, axis), transform))
     182        38812 :          rs(:n, :n, axis) = rc(:, :, axis)
     183        38814 :          rs(n + 1:, n + 1:, axis) = rc(:, :, axis)
     184              :       END DO
     185            2 :       CALL evaluate(hs, ss, rs, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .TRUE., index, gap, status)
     186            2 :       IF (status /= 0 .OR. index /= 1 .OR. ABS(gap - gap_reference) > 1.e-11_dp) THEN
     187            0 :          ERROR STOP 'AII metric covariance failed'
     188              :       END IF
     189            2 :       hs(1, 1) = hs(1, 1) + 1.e-12_dp
     190            2 :       CALL evaluate(hs, ss, rs, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .TRUE., index, gap, status)
     191            2 :       IF (status /= 0 .OR. index /= 1) ERROR STOP 'Roundoff-level AII residual failed'
     192            2 :       hs(1, 1) = hs(1, 1) + 1.0_dp
     193            2 :       CALL evaluate(hs, ss, rs, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .TRUE., index, gap, status)
     194            2 :       IF (status /= -3) ERROR STOP 'Broken physical time reversal accepted'
     195        77226 :       hs(:, :) = ss
     196            2 :       CALL evaluate(hs, ss, rs, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .TRUE., index, gap, status)
     197            2 :       IF (status /= 0 .OR. index /= 0) ERROR STOP 'Atomic AII calibration failed'
     198              : 
     199            2 :       DEALLOCATE (h, s, r)
     200            2 :       CALL qwz_model(7, -3.0_dp, h, s, r)
     201            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp], 0.0_dp, 0.25_dp, .FALSE., index, gap, status)
     202            2 :       IF (status /= 0 .OR. index /= 0) ERROR STOP 'Trivial QWZ model failed'
     203              : #if defined(__HAS_IEEE_EXCEPTIONS)
     204              :       CALL ieee_get_halting_mode(IEEE_ALL, current_halt)
     205              :       IF (ANY(current_halt .NEQV. original_halt)) ERROR STOP 'Caller floating-point traps changed'
     206              : #endif
     207              : 
     208              :    CONTAINS
     209              : 
     210              : ! **************************************************************************************************
     211              : !> \brief Signed-permutation AII conversion against the explicit dense unitary transform.
     212              : ! **************************************************************************************************
     213            2 :       SUBROUTINE aii_transform_tests()
     214            2 :       COMPLEX(dp), ALLOCATABLE                           :: matrix(:, :), q(:, :), reference(:, :)
     215              :       INTEGER                                            :: i, j, m, n, status
     216              :       REAL(dp)                                           :: error, largest, residual
     217            2 :       REAL(dp), ALLOCATABLE                              :: actual(:, :), expected(:, :)
     218              : 
     219            2 :          largest = 0.0_dp
     220           28 :          DO n = 1, 13
     221           26 :             m = 4*n
     222          338 :             ALLOCATE (q(m, m), matrix(m, m), reference(m, m), expected(m, m), actual(m, m))
     223           26 :             q = 0.0_dp
     224          754 :             DO i = 1, m
     225          728 :                q(i, i) = 1.0_dp
     226        26962 :                DO j = 1, m
     227        26936 :                   expected(i, j) = SIN(REAL(i + 2*j, dp)) + COS(REAL(3*i - j, dp))
     228              :                END DO
     229              :             END DO
     230        53898 :             expected(:, :) = expected - TRANSPOSE(expected)
     231          208 :             DO i = 1, n
     232          182 :                q(i, 3*n + i) = CMPLX(0.0_dp, -1.0_dp, dp)
     233          182 :                q(n + i, 2*n + i) = CMPLX(0.0_dp, 1.0_dp, dp)
     234          182 :                q(2*n + i, n + i) = CMPLX(0.0_dp, 1.0_dp, dp)
     235          208 :                q(3*n + i, i) = CMPLX(0.0_dp, -1.0_dp, dp)
     236              :             END DO
     237        26962 :             q = q/SQRT(2.0_dp)
     238       687730 :             matrix(:, :) = CMPLX(0.0_dp, -1.0_dp, dp)*MATMUL(q, MATMUL(expected, CONJG(TRANSPOSE(q))))
     239       433116 :             reference(:, :) = MATMUL(CONJG(TRANSPOSE(q)), MATMUL(matrix, q))
     240           26 :             CALL localizer_aii_skew(matrix, actual, 1.e-10_dp, residual, status)
     241        53924 :             error = MAX(MAXVAL(ABS(actual - expected)), MAXVAL(ABS(actual + AIMAG(reference))))
     242           26 :             IF (status /= 0 .OR. error > 1.e-13_dp) ERROR STOP 'AII sparse-unitary transform failed'
     243           26 :             largest = MAX(largest, error)
     244          754 :             DO i = 1, m
     245          754 :                matrix(i, i) = matrix(i, i) - CMPLX(0.0_dp, 5.e-12_dp, dp)
     246              :             END DO
     247           26 :             CALL localizer_aii_skew(matrix, actual, 1.e-10_dp, residual, status)
     248        26962 :             IF (status /= 0 .OR. MAXVAL(ABS(actual - expected)) > 1.e-13_dp .OR. residual < 4.9e-12_dp) THEN
     249            0 :                ERROR STOP 'AII skew projection or discarded-error bound failed'
     250              :             END IF
     251        26962 :             IF (MAXVAL(ABS(actual + TRANSPOSE(actual))) /= 0.0_dp) ERROR STOP 'AII projection is not exactly skew'
     252          754 :             DO i = 1, m
     253          754 :                matrix(i, i) = matrix(i, i) + CMPLX(0.0_dp, 5.e-12_dp, dp)
     254              :             END DO
     255           26 :             matrix(1, 1) = matrix(1, 1) + 0.1_dp
     256           26 :             CALL localizer_aii_skew(matrix, actual, 1.e-10_dp, residual, status)
     257           26 :             IF (status == 0) ERROR STOP 'Broken AII transform accepted'
     258           26 :             CALL localizer_aii_skew(matrix(:m - 1, :m - 1), actual, 1.e-10_dp, residual, status)
     259           26 :             IF (status == 0) ERROR STOP 'Invalid AII dimensions accepted'
     260           28 :             DEALLOCATE (q, matrix, reference, expected, actual)
     261              :          END DO
     262            2 :          IF (io > 0) WRITE (io, '(A,ES14.5)') 'AII signed-permutation maximum error: ', largest
     263            4 :       END SUBROUTINE aii_transform_tests
     264              : 
     265              : ! **************************************************************************************************
     266              : !> \brief Accepted Hermiticity roundoff must not fail the stricter Pfaffian skew check.
     267              : ! **************************************************************************************************
     268            2 :       SUBROUTINE aii_roundoff_tests()
     269              :       COMPLEX(dp)                                        :: h0(4, 4), r0(4, 4, 2), s0(4, 4)
     270              :       INTEGER                                            :: i, index, status
     271              :       REAL(dp)                                           :: gap
     272              : 
     273            2 :          h0 = 0.0_dp
     274            2 :          s0 = 0.0_dp
     275            2 :          r0 = 0.0_dp
     276           10 :          DO i = 1, 4
     277            8 :             s0(i, i) = 1.0_dp
     278           10 :             h0(i, i) = REAL(3*MOD(i + 1, 2) - 1, dp)
     279              :          END DO
     280            2 :          h0(1, 2) = 5.e-12_dp
     281            2 :          h0(3, 4) = 5.e-12_dp
     282            2 :          CALL evaluate(h0, s0, r0, [0.0_dp, 0.0_dp], 0.0_dp, 0.2_dp, .TRUE., index, gap, status)
     283            2 :          IF (status /= 0 .OR. index /= 0 .OR. ABS(gap - 1.0_dp) > 1.e-12_dp) ERROR STOP 'AII roundoff rejected'
     284            2 :          h0(1, 1) = -1.e-10_dp
     285            2 :          h0(3, 3) = -1.e-10_dp
     286            2 :          CALL evaluate(h0, s0, r0, [0.0_dp, 0.0_dp], 0.0_dp, 0.2_dp, .TRUE., index, gap, status)
     287            2 :          IF (status /= 1) ERROR STOP 'AII unresolved gap assigned an index'
     288            2 :          h0(1, 2) = 1.e-5_dp
     289            2 :          CALL evaluate(h0, s0, r0, [0.0_dp, 0.0_dp], 0.0_dp, 0.2_dp, .TRUE., index, gap, status)
     290            2 :          IF (status /= -1) ERROR STOP 'AII non-Hermitian input projected away'
     291            2 :       END SUBROUTINE aii_roundoff_tests
     292              : 
     293              : ! **************************************************************************************************
     294              : !> \brief Torus models with known Chern/Z2 phases, origin periodicity and metric covariance.
     295              : ! **************************************************************************************************
     296            2 :       SUBROUTINE periodic_tests()
     297              :       INTEGER, PARAMETER                                 :: length = 8
     298              : 
     299            2 :       COMPLEX(dp), ALLOCATABLE :: co(:, :, :), ct(:, :, :), h0(:, :), ht(:, :), pos(:, :, :), &
     300            2 :          s0(:, :), si(:, :, :), sn(:, :, :), spin_c(:, :, :), spin_h(:, :), spin_s(:, :), &
     301            2 :          spin_sin(:, :, :), st(:, :), t(:, :)
     302              :       INTEGER                                            :: col, expected, i, idx, info, j, kx, ky, &
     303              :                                                             n, orbital, phase, row, x, y
     304              :       REAL(dp)                                           :: cond, g, g0, mass, two_pi, wave(2)
     305              : 
     306            2 :          two_pi = 8.0_dp*ATAN(1.0_dp)
     307            8 :          DO phase = 1, 3
     308            6 :             mass = -1.0_dp
     309            6 :             expected = -1
     310            6 :             IF (phase == 2) THEN
     311            2 :                mass = 1.0_dp
     312            2 :                expected = 1
     313            4 :             ELSE IF (phase == 3) THEN
     314            2 :                mass = -3.0_dp
     315            2 :                expected = 0
     316              :             END IF
     317            6 :             CALL qwz_model(length, mass, h0, s0, pos, torus=.TRUE.)
     318            6 :             n = SIZE(h0, 1)
     319           42 :             ALLOCATE (co(n, n, 2), si(n, n, 2))
     320            6 :             co = 0.0_dp
     321            6 :             si = 0.0_dp
     322           18 :             DO j = 1, 2
     323         1554 :                DO i = 1, n
     324         1536 :                   co(i, i, j) = COS(two_pi*REAL(pos(i, i, j), dp)/length)
     325         1548 :                   si(i, i, j) = SIN(two_pi*REAL(pos(i, i, j), dp)/length)
     326              :                END DO
     327              :             END DO
     328              :             CALL localizer_periodic_evaluate(h0, s0, co, si, [0.0_dp, 0.0_dp], 0.0_dp, 1.0_dp, .FALSE., &
     329            6 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g0, cond, info)
     330            6 :             IF (info /= 0 .OR. idx /= expected) ERROR STOP 'Periodic QWZ Chern phase failed'
     331              :             CALL localizer_periodic_evaluate(h0, s0, co, si, [two_pi, -2*two_pi], 0.0_dp, 1.0_dp, .FALSE., &
     332           18 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     333            6 :             IF (info /= 0 .OR. idx /= expected .OR. ABS(g - g0) > 1.e-11_dp) THEN
     334            0 :                ERROR STOP 'Torus origin periodicity failed'
     335              :             END IF
     336              :             CALL localizer_periodic_evaluate(h0, s0, co, si, two_pi*[1.0_dp, 2.0_dp]/length, &
     337              :                                              0.0_dp, 1.0_dp, .FALSE., &
     338           18 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     339            6 :             IF (info /= 0 .OR. idx /= expected .OR. ABS(g - g0) > 1.e-11_dp) THEN
     340            0 :                ERROR STOP 'Periodic lattice translation covariance failed'
     341              :             END IF
     342              :             CALL localizer_periodic_evaluate(h0, s0, co(:, :, [2, 1]), si(:, :, [2, 1]), &
     343              :                                              [0.0_dp, 0.0_dp], 0.0_dp, 1.0_dp, .FALSE., &
     344       396318 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     345            6 :             IF (info /= 0 .OR. idx /= -expected .OR. ABS(g - g0) > 1.e-11_dp) THEN
     346            0 :                ERROR STOP 'Periodic orientation reversal failed'
     347              :             END IF
     348              : 
     349           72 :             ALLOCATE (spin_h(2*n, 2*n), spin_s(2*n, 2*n), spin_c(2*n, 2*n, 2), spin_sin(2*n, 2*n, 2))
     350            6 :             spin_h = 0.0_dp
     351            6 :             spin_s = 0.0_dp
     352            6 :             spin_c = 0.0_dp
     353            6 :             spin_sin = 0.0_dp
     354        99078 :             spin_h(:n, :n) = h0
     355        99078 :             spin_h(n + 1:, n + 1:) = CONJG(h0)
     356            6 :             DO i = 1, n, 2
     357          384 :                spin_h(i, n + i + 1) = 0.1_dp
     358          384 :                spin_h(i + 1, n + i) = -0.1_dp
     359              :             END DO
     360       198150 :             spin_h(n + 1:, :n) = CONJG(TRANSPOSE(spin_h(:n, n + 1:)))
     361        99078 :             spin_s(:n, :n) = s0
     362        99078 :             spin_s(n + 1:, n + 1:) = s0
     363       198162 :             spin_c(:n, :n, :) = co
     364       198162 :             spin_c(n + 1:, n + 1:, :) = co
     365       198162 :             spin_sin(:n, :n, :) = si
     366       198162 :             spin_sin(n + 1:, n + 1:, :) = si
     367              :             CALL localizer_periodic_evaluate(spin_h, spin_s, spin_c, spin_sin, [0.0_dp, 0.0_dp], &
     368              :                                              0.0_dp, 1.0_dp, .TRUE., &
     369            6 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     370            6 :             IF (info /= 0 .OR. idx /= ABS(expected)) ERROR STOP 'Periodic spin-mixed Z2 phase failed'
     371       394758 :             spin_h(:, :) = spin_s
     372              :             CALL localizer_periodic_evaluate(spin_h, spin_s, spin_c, spin_sin, [0.0_dp, 0.0_dp], &
     373              :                                              0.0_dp, 1.0_dp, .TRUE., &
     374            6 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     375            6 :             IF (info /= 0 .OR. idx /= 0) ERROR STOP 'Periodic atomic Pfaffian calibration failed'
     376            6 :             DEALLOCATE (spin_h, spin_s, spin_c, spin_sin)
     377              : 
     378            6 :             IF (phase == 1) THEN
     379           24 :                ALLOCATE (t(n, n), ht(n, n), st(n, n), ct(n, n, 2), sn(n, n, 2))
     380              :                ! Full Fourier transform: H becomes block diagonal, but torus coordinates do not.
     381            2 :                t = 0.0_dp
     382           18 :                DO ky = 0, length - 1
     383          146 :                   DO kx = 0, length - 1
     384          128 :                      col = 2*(ky*length + kx) + 1
     385         1168 :                      DO y = 0, length - 1
     386         9344 :                         DO x = 0, length - 1
     387         8192 :                            row = 2*(y*length + x) + 1
     388        25600 :                            DO orbital = 0, 1
     389              :                               t(row + orbital, col + orbital) = &
     390        24576 :                                  EXP(CMPLX(0.0_dp, two_pi*(x*kx + y*ky)/length, dp))/length
     391              :                            END DO
     392              :                         END DO
     393              :                      END DO
     394              :                   END DO
     395              :                END DO
     396        33026 :                ht(:, :) = MATMUL(CONJG(TRANSPOSE(t)), MATMUL(h0, t))
     397        33026 :                st(:, :) = s0
     398           18 :                DO ky = 0, length - 1
     399          146 :                   DO kx = 0, length - 1
     400          128 :                      col = 2*(ky*length + kx) + 1
     401          384 :                      wave = two_pi*[REAL(kx, dp), REAL(ky, dp)]/length
     402          384 :                      IF (ABS(ht(col, col) - (mass + SUM(COS(wave)))) > 1.e-12_dp) THEN
     403            0 :                         ERROR STOP 'Periodic real-space/k-space Hamiltonian mismatch'
     404              :                      END IF
     405          144 :                      IF (ABS(ht(col, col + 1) - CMPLX(SIN(wave(1)), -SIN(wave(2)), dp)) > 1.e-12_dp) THEN
     406            0 :                         ERROR STOP 'Periodic Bloch Hamiltonian offdiagonal mismatch'
     407              :                      END IF
     408              :                   END DO
     409              :                END DO
     410            6 :                DO j = 1, 2
     411        66052 :                   ct(:, :, j) = MATMUL(CONJG(TRANSPOSE(t)), MATMUL(co(:, :, j), t))
     412        66054 :                   sn(:, :, j) = MATMUL(CONJG(TRANSPOSE(t)), MATMUL(si(:, :, j), t))
     413              :                END DO
     414              :                CALL localizer_periodic_evaluate(ht, st, ct, sn, [0.0_dp, 0.0_dp], &
     415              :                                                 0.0_dp, 1.0_dp, .FALSE., &
     416            2 :                                                 1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     417            2 :                IF (info /= 0 .OR. idx /= expected .OR. ABS(g - g0) > 1.e-11_dp) THEN
     418            0 :                   ERROR STOP 'Full k-mesh/real-space periodic localizer mismatch'
     419              :                END IF
     420            2 :                t = 0.0_dp
     421          258 :                DO i = 1, n
     422          256 :                   t(i, i) = (1.0_dp + MOD(i, 5)/5.0_dp)*EXP(CMPLX(0.0_dp, 0.17_dp*i, dp))
     423          258 :                   IF (i < n) t(i, i + 1) = CMPLX(0.07_dp, -0.04_dp, dp)
     424              :                END DO
     425        33026 :                ht(:, :) = MATMUL(CONJG(TRANSPOSE(t)), MATMUL(h0, t))
     426        33026 :                st(:, :) = MATMUL(CONJG(TRANSPOSE(t)), t)
     427            6 :                DO j = 1, 2
     428        66052 :                   ct(:, :, j) = MATMUL(CONJG(TRANSPOSE(t)), MATMUL(co(:, :, j), t))
     429        66054 :                   sn(:, :, j) = MATMUL(CONJG(TRANSPOSE(t)), MATMUL(si(:, :, j), t))
     430              :                END DO
     431              :                CALL localizer_periodic_evaluate(ht + 7.0_dp*st, st, ct, sn, [0.0_dp, 0.0_dp], &
     432              :                                                 7.0_dp, 1.0_dp, .FALSE., &
     433        33026 :                                                 1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     434            2 :                IF (info /= 0 .OR. idx /= expected .OR. ABS(g - g0) > 1.e-11_dp) THEN
     435            0 :                   ERROR STOP 'Periodic metric/energy covariance failed'
     436              :                END IF
     437              :                CALL localizer_periodic_evaluate(2*ht, st, ct, sn, [0.0_dp, 0.0_dp], &
     438              :                                                 0.0_dp, 2.0_dp, .FALSE., &
     439        33026 :                                                 1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     440            2 :                IF (info /= 0 .OR. idx /= expected .OR. ABS(g - 2*g0) > 1.e-11_dp) THEN
     441            0 :                   ERROR STOP 'Periodic energy-scale covariance failed'
     442              :                END IF
     443            2 :                DEALLOCATE (t, ht, st, ct, sn)
     444              :             END IF
     445              :             CALL localizer_periodic_evaluate(h0, s0, co, si, [0.0_dp, 0.0_dp], 0.0_dp, 0.0_dp, .FALSE., &
     446            6 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     447            6 :             IF (info /= -1) ERROR STOP 'Zero torus energy scale accepted'
     448            6 :             co(1, 2, 1) = co(1, 2, 1) + 0.1_dp
     449              :             CALL localizer_periodic_evaluate(h0, s0, co, si, [0.0_dp, 0.0_dp], 0.0_dp, 1.0_dp, .FALSE., &
     450            6 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     451            6 :             IF (info /= -1) ERROR STOP 'Non-Hermitian torus cosine accepted'
     452            6 :             h0 = 0.0_dp
     453            6 :             si = 0.0_dp
     454        99078 :             co(:, :, 1) = s0
     455        99078 :             co(:, :, 2) = s0
     456              :             CALL localizer_periodic_evaluate(h0, s0, co, si, [0.0_dp, 0.0_dp], 0.0_dp, 1.0_dp, .FALSE., &
     457            6 :                                              1.e-10_dp, 1.e-10_dp, 1.e-9_dp, idx, g, cond, info)
     458            6 :             IF (info /= 1) ERROR STOP 'Closed periodic localizer assigned an index'
     459            8 :             DEALLOCATE (h0, s0, pos, co, si)
     460              :          END DO
     461            2 :       END SUBROUTINE periodic_tests
     462              : 
     463              : ! **************************************************************************************************
     464              : !> \brief Exercise 2x2 pivots, complex congruences, scale changes and singularity.
     465              : ! **************************************************************************************************
     466            2 :       SUBROUTINE inertia_tests()
     467              :       COMPLEX(KIND=dp)                                   :: change(4, 4), matrix(4, 4), &
     468              :                                                             transformed(4, 4)
     469              :       INTEGER                                            :: exponent, i, j, m, p, RESULT
     470              : 
     471            2 :          matrix = 0.0_dp
     472            2 :          matrix(1, 2) = CMPLX(0.0_dp, 2.0_dp, dp)
     473            2 :          matrix(2, 1) = CONJG(matrix(1, 2))
     474            2 :          matrix(3, 3) = 1.e-12_dp
     475            2 :          matrix(4, 4) = 3.0_dp
     476            2 :          change = 0.0_dp
     477           10 :          DO i = 1, 4
     478            8 :             change(i, i) = CMPLX(1.0_dp + 0.2_dp*i, 0.1_dp*i, dp)
     479           22 :             DO j = i + 1, 4
     480           20 :                change(i, j) = CMPLX(0.03_dp*j, -0.07_dp*i, dp)
     481              :             END DO
     482              :          END DO
     483          338 :          transformed = MATMUL(CONJG(TRANSPOSE(change)), MATMUL(matrix, change))
     484            6 :          DO exponent = -150, 150, 150
     485          126 :             CALL localizer_inertia((10.0_dp**exponent)*transformed, p, m, RESULT)
     486            8 :             IF (RESULT /= 0 .OR. p /= 3 .OR. m /= 1) ERROR STOP 'LDL block inertia or congruence failed'
     487              :          END DO
     488            2 :          matrix(3, 3) = -1.e-12_dp
     489            2 :          CALL localizer_inertia(matrix, p, m, RESULT)
     490            2 :          IF (RESULT /= 0 .OR. p /= 2 .OR. m /= 2) ERROR STOP 'LDL gap crossing failed'
     491            2 :          matrix(3, 3) = 0.0_dp
     492            2 :          CALL localizer_inertia(matrix, p, m, RESULT)
     493            2 :          IF (RESULT /= 1) ERROR STOP 'Singular LDL accepted'
     494            2 :          matrix(2, 1) = matrix(1, 2)
     495            2 :          CALL localizer_inertia(matrix, p, m, RESULT)
     496            2 :          IF (RESULT /= -1) ERROR STOP 'Complex symmetric matrix accepted as Hermitian'
     497            2 :          matrix = 0.0_dp
     498            2 :          matrix(1, 2) = CMPLX(0.0_dp, 1.e-12_dp, dp)
     499            2 :          matrix(2, 1) = CONJG(matrix(1, 2))
     500            2 :          matrix(3, 3) = 1.0_dp
     501            2 :          matrix(4, 4) = -1.0_dp
     502            2 :          CALL localizer_inertia(matrix, p, m, RESULT)
     503            2 :          IF (RESULT /= 0 .OR. p /= 2 .OR. m /= 2) ERROR STOP 'Small imaginary coupling discarded'
     504            2 :       END SUBROUTINE inertia_tests
     505              : 
     506              : ! **************************************************************************************************
     507              : !> \brief Cross-check real and complex LDL under congruences, all signatures and permutations.
     508              : ! **************************************************************************************************
     509            2 :       SUBROUTINE real_inertia_tests()
     510            2 :       COMPLEX(dp), ALLOCATABLE                           :: cmatrix(:, :), phases(:)
     511              :       INTEGER                                            :: exponent, i, info, j, m, n, negative, p
     512              :       REAL(dp)                                           :: pair(2, 2), scale
     513            2 :       REAL(dp), ALLOCATABLE                              :: change(:, :), diagonal(:, :), &
     514            2 :                                                             matrix(:, :)
     515              : 
     516           26 :          DO n = 1, 12
     517          312 :             ALLOCATE (diagonal(n, n), change(n, n), matrix(n, n), phases(n), cmatrix(n, n))
     518           24 :             change = 0.0_dp
     519          180 :             DO i = 1, n
     520          156 :                change(i, i) = 0.5_dp + REAL(i, dp)/n
     521          156 :                phases(i) = EXP(CMPLX(0.0_dp, 0.37_dp*i, dp))
     522          752 :                DO j = i + 1, n
     523          728 :                   change(i, j) = 0.1_dp*SIN(REAL(i + 3*j, dp))
     524              :                END DO
     525              :             END DO
     526          204 :             DO negative = 0, n
     527          180 :                diagonal = 0.0_dp
     528         1636 :                DO i = 1, n
     529         1456 :                   diagonal(i, i) = 1.0_dp + REAL(i, dp)/n
     530         1636 :                   IF (i <= negative) diagonal(i, i) = -diagonal(i, i)
     531              :                END DO
     532       564920 :                matrix(:, :) = MATMUL(TRANSPOSE(change), MATMUL(diagonal, change))
     533         1636 :                DO i = 1, n
     534        15104 :                   DO j = 1, n
     535        14924 :                      cmatrix(i, j) = CONJG(phases(i))*matrix(i, j)*phases(j)
     536              :                   END DO
     537              :                END DO
     538          564 :                DO exponent = -150, 150, 150
     539          540 :                   scale = 10.0_dp**exponent
     540        45312 :                   CALL localizer_inertia(scale*matrix, p, m, info)
     541          540 :                   IF (info /= 0 .OR. p /= n - negative .OR. m /= negative) ERROR STOP 'Real LDL signature failed'
     542        45312 :                   CALL localizer_inertia(scale*cmatrix, p, m, info)
     543          540 :                   IF (info /= 0 .OR. p /= n - negative .OR. m /= negative) ERROR STOP 'Complex LDL signature failed'
     544        45312 :                   CALL localizer_inertia(CMPLX(scale*matrix, 0.0_dp, dp), p, m, info)
     545          720 :                   IF (info /= 0 .OR. p /= n - negative .OR. m /= negative) ERROR STOP 'Real LDL dispatch failed'
     546              :                END DO
     547              :             END DO
     548           26 :             DEALLOCATE (diagonal, change, matrix, phases, cmatrix)
     549              :          END DO
     550            2 :          pair = RESHAPE([0.0_dp, 2.0_dp, 2.0_dp, 0.0_dp], [2, 2])
     551            2 :          CALL localizer_inertia(pair, p, m, info)
     552            2 :          IF (info /= 0 .OR. p /= 1 .OR. m /= 1) ERROR STOP 'Zero-diagonal real 2x2 pivot failed'
     553            2 :          pair(1, 2) = 0.0_dp
     554            2 :          CALL localizer_inertia(pair, p, m, info)
     555            2 :          IF (info /= -1) ERROR STOP 'Nonsymmetric real matrix accepted'
     556            2 :          pair = 0.0_dp
     557            2 :          CALL localizer_inertia(pair, p, m, info)
     558            2 :          IF (info /= 1) ERROR STOP 'Singular real matrix accepted'
     559            2 :       END SUBROUTINE real_inertia_tests
     560              : 
     561              : ! **************************************************************************************************
     562              : !> \brief Generalized gaps have an analytic reference independent of LDL pivot magnitudes.
     563              : ! **************************************************************************************************
     564            2 :       SUBROUTINE gap_tests()
     565              :       COMPLEX(dp)                                        :: change(2, 2), h(2, 2), hc(2, 2), &
     566              :                                                             r(2, 2, 2), s(2, 2), sc(2, 2)
     567              :       INTEGER                                            :: i, index, status
     568              :       REAL(dp)                                           :: crossing(5), expected, gap, ratio, trace
     569              : 
     570            2 :          h = RESHAPE([1.0_dp, 20.0_dp, 20.0_dp, 399.0_dp], [2, 2])
     571            2 :          s = 0.0_dp
     572            2 :          s(1, 1) = 2.0_dp
     573            2 :          s(2, 2) = 2.0_dp
     574            2 :          r = 0.0_dp
     575            2 :          trace = REAL(h(1, 1) + h(2, 2), dp)
     576            2 :          expected = 1.0_dp/(trace + SQRT(trace**2 + 4.0_dp))
     577              :          CALL localizer_evaluate(h, s, r, [0.0_dp, 0.0_dp], 0.0_dp, 1.0_dp, .FALSE., &
     578            2 :                                  1.e-10_dp, 1.e-10_dp, 1.e-9_dp, index, gap, ratio, status)
     579            2 :          IF (status /= 0 .OR. index /= 0 .OR. ABS(gap - expected) > 1.e-12_dp) ERROR STOP 'Analytic LDL/gap separation failed'
     580            2 :          change = RESHAPE([0.5_dp, 0.0_dp, 0.2_dp, 3.0_dp], [2, 2])
     581           58 :          hc = MATMUL(TRANSPOSE(change), MATMUL(h, change))
     582           58 :          sc = MATMUL(TRANSPOSE(change), MATMUL(s, change))
     583              :          CALL localizer_evaluate(hc, sc, r, [0.0_dp, 0.0_dp], 0.0_dp, 1.0_dp, .FALSE., &
     584            2 :                                  1.e-10_dp, 1.e-10_dp, 1.e-9_dp, index, gap, ratio, status)
     585            2 :          IF (status /= 0 .OR. ABS(gap - expected) > 1.e-12_dp) ERROR STOP 'Metric LDL/gap separation failed'
     586            2 :          crossing = [-1.e-6_dp, -1.e-12_dp, 0.0_dp, 1.e-12_dp, 1.e-6_dp]
     587            2 :          h = 0.0_dp
     588            2 :          h(2, 2) = 5.0_dp
     589           12 :          DO i = 1, SIZE(crossing)
     590           10 :             h(1, 1) = crossing(i)
     591              :             CALL localizer_evaluate(h, s, r, [0.0_dp, 0.0_dp], 0.0_dp, 1.0_dp, .FALSE., &
     592           10 :                                     1.e-10_dp, 1.e-10_dp, 1.e-9_dp, index, gap, ratio, status)
     593           12 :             IF (i == 1 .OR. i == 5) THEN
     594            4 :                IF (status /= 0 .OR. ABS(gap - 5.e-7_dp) > 1.e-14_dp) ERROR STOP 'Resolved gap crossing failed'
     595              :             ELSE
     596            6 :                IF (status /= 1) ERROR STOP 'Unresolved gap assigned an index'
     597              :             END IF
     598              :          END DO
     599            2 :       END SUBROUTINE gap_tests
     600              : 
     601              : ! **************************************************************************************************
     602              : !> \brief Independent Pfaffian polynomials distinguish signs despite identical determinants.
     603              : ! **************************************************************************************************
     604            2 :       SUBROUTINE pfaffian_orientation_tests()
     605              :       INTEGER                                            :: exponent, i, j, n, pf_sign, sample, &
     606              :                                                             status
     607            2 :       INTEGER, ALLOCATABLE                               :: permutation(:)
     608              :       REAL(dp)                                           :: exact, scale
     609            2 :       REAL(dp), ALLOCATABLE                              :: a(:, :), change(:, :), transformed(:, :)
     610              : 
     611            2 :          DO n = 2, 8, 2
     612           80 :             ALLOCATE (a(n, n), change(n, n), transformed(n, n), permutation(n))
     613           72 :             DO sample = 1, 8
     614           64 :                a = 0.0_dp
     615          320 :                DO j = 2, n
     616         1120 :                   DO i = 1, j - 1
     617         1056 :                      a(i, j) = SIN(REAL(11*i + 17*j + sample, dp))
     618              :                   END DO
     619              :                END DO
     620           64 :                DO i = 1, n, 2
     621          160 :                   a(i, i + 1) = a(i, i + 1) + 3.0_dp
     622              :                END DO
     623         4544 :                a(:, :) = a - TRANSPOSE(a)
     624           64 :                exact = pfaffian_polynomial(a)
     625           64 :                IF (ABS(exact) < 1.e-6_dp) ERROR STOP 'Ill-conditioned Pfaffian fixture'
     626          704 :                permutation(:) = [(i, i=1, n)]
     627          192 :                permutation(:2) = [2, 1]
     628          192 :                DO exponent = -150, 150, 150
     629          192 :                   scale = 10.0_dp**exponent
     630         6912 :                   CALL localizer_pfaffian_sign(scale*a, pf_sign, status)
     631          192 :                   IF (status /= 0 .OR. pf_sign /= INT(SIGN(1.0_dp, exact))) ERROR STOP 'Pfaffian polynomial sign failed'
     632         6912 :                   CALL localizer_pfaffian_sign(-scale*a, pf_sign, status)
     633          192 :                   IF (status /= 0 .OR. pf_sign /= (-1)**(n/2)*INT(SIGN(1.0_dp, exact))) ERROR STOP 'Negative Pfaffian scale failed'
     634         6912 :                   CALL localizer_pfaffian_sign(scale*a(permutation, permutation), pf_sign, status)
     635          256 :                   IF (status /= 0 .OR. pf_sign /= -INT(SIGN(1.0_dp, exact))) ERROR STOP 'Equal determinant/opposite Pfaffian failed'
     636              :                END DO
     637           64 :                change = 0.0_dp
     638          384 :                DO i = 1, n
     639          320 :                   change(i, i) = 0.8_dp + 0.1_dp*i
     640          384 :                   IF (i < n) change(i, i + 1) = 0.1_dp
     641              :                END DO
     642          384 :                change(1, :) = -change(1, :)
     643        55936 :                transformed(:, :) = MATMUL(change, MATMUL(a, TRANSPOSE(change)))
     644           64 :                CALL localizer_pfaffian_sign(transformed, pf_sign, status)
     645           72 :                IF (status /= 0 .OR. pf_sign /= -INT(SIGN(1.0_dp, exact))) ERROR STOP 'Pfaffian congruence orientation failed'
     646              :             END DO
     647            8 :             DEALLOCATE (a, change, transformed, permutation)
     648              :          END DO
     649            2 :          ALLOCATE (a(4, 4))
     650            2 :          a = 0.0_dp
     651            2 :          a(1, 2) = 1.0_dp
     652            2 :          a(1, 3) = 1.0_dp
     653            2 :          a(2, 4) = 1.0_dp
     654            2 :          a(3, 4) = 1.0_dp
     655           82 :          a(:, :) = a - TRANSPOSE(a)
     656            2 :          CALL localizer_pfaffian_sign(a, pf_sign, status)
     657            2 :          IF (status /= -2 .OR. pf_sign /= 0) ERROR STOP 'Pfaffian polynomial cancellation ignored'
     658            4 :       END SUBROUTINE pfaffian_orientation_tests
     659              : 
     660              : ! **************************************************************************************************
     661              : !> \brief Small independent recursive Pfaffian oracle, without pivoting or elimination.
     662              : !> \param a Even-order skew matrix, at most eight rows in these tests
     663              : !> \return Pfaffian from the expansion along the first row
     664              : ! **************************************************************************************************
     665         2784 :       RECURSIVE FUNCTION pfaffian_polynomial(a) RESULT(value)
     666              :       REAL(dp), INTENT(IN)                               :: a(:, :)
     667              :       REAL(dp)                                           :: value
     668              : 
     669         5568 :       INTEGER                                            :: i, j, n, keep(SIZE(a, 1) - 2)
     670              : 
     671         2784 :          n = SIZE(a, 1)
     672         2784 :          IF (n == 2) THEN
     673         1984 :             value = a(1, 2)
     674         1984 :             RETURN
     675              :          END IF
     676              :          value = 0.0_dp
     677         3520 :          DO j = 2, n
     678        42272 :             keep = PACK([(i, i=2, n)], [(i /= j, i=2, n)])
     679        32832 :             value = value + REAL((-1)**j, dp)*a(1, j)*pfaffian_polynomial(a(keep, keep))
     680              :          END DO
     681              :       END FUNCTION pfaffian_polynomial
     682              : 
     683              : ! **************************************************************************************************
     684              : !> \brief Apply uniform numerical tolerances in tests.
     685              : !> \param h ...
     686              : !> \param s ...
     687              : !> \param r ...
     688              : !> \param point ...
     689              : !> \param energy ...
     690              : !> \param kappa ...
     691              : !> \param aii ...
     692              : !> \param invariant ...
     693              : !> \param gap ...
     694              : !> \param status ...
     695              : ! **************************************************************************************************
     696           44 :       SUBROUTINE evaluate(h, s, r, point, energy, kappa, aii, invariant, gap, status)
     697              :       COMPLEX(KIND=dp), INTENT(IN)                       :: h(:, :), s(:, :), r(:, :, :)
     698              :       REAL(KIND=dp), INTENT(IN)                          :: point(2), energy, kappa
     699              :       LOGICAL, INTENT(IN)                                :: aii
     700              :       INTEGER, INTENT(OUT)                               :: invariant
     701              :       REAL(KIND=dp), INTENT(OUT)                         :: gap
     702              :       INTEGER, INTENT(OUT)                               :: status
     703              : 
     704              :          CALL localizer_evaluate(h, s, r, point, energy, kappa, aii, 1.e-10_dp, 1.e-10_dp, &
     705           44 :                                  1.e-9_dp, invariant, gap, ratio, status)
     706           44 :       END SUBROUTINE evaluate
     707              : 
     708              : ! **************************************************************************************************
     709              : !> \brief Open square Qi-Wu-Zhang model: sin(kx)sx+sin(ky)sy+(m+cos(kx)+cos(ky))sz.
     710              : !> \param length Number of sites along each direction
     711              : !> \param mass Onsite mass; -1 nontrivial, -3 trivial in the infinite model
     712              : !> \param h Hamiltonian in site-major, two-orbital order
     713              : !> \param s Identity metric
     714              : !> \param r Commuting diagonal positions centered on the flake
     715              : !> \param torus Optional periodic boundary hoppings
     716              : ! **************************************************************************************************
     717           10 :       SUBROUTINE qwz_model(length, mass, h, s, r, torus)
     718              :       INTEGER, INTENT(IN)                                :: length
     719              :       REAL(KIND=dp), INTENT(IN)                          :: mass
     720              :       COMPLEX(KIND=dp), ALLOCATABLE, INTENT(OUT)         :: h(:, :), s(:, :), r(:, :, :)
     721              :       LOGICAL, INTENT(IN), OPTIONAL                      :: torus
     722              : 
     723              :       COMPLEX(KIND=dp)                                   :: tx(2, 2), ty(2, 2)
     724              :       INTEGER                                            :: i, j, n, orbital, x, y
     725              : 
     726           10 :          n = 2*length**2
     727          100 :          ALLOCATE (h(n, n), s(n, n), r(n, n, 2))
     728           10 :          h = 0.0_dp
     729           10 :          s = 0.0_dp
     730           10 :          r = 0.0_dp
     731              :          tx = 0.0_dp
     732           10 :          tx(1, 1) = 0.5_dp
     733           10 :          tx(2, 2) = -0.5_dp
     734           10 :          tx(1, 2) = CMPLX(0.0_dp, -0.5_dp, dp)
     735           10 :          tx(2, 1) = tx(1, 2)
     736              :          ty = 0.0_dp
     737           10 :          ty(1, 1) = 0.5_dp
     738           10 :          ty(2, 2) = -0.5_dp
     739           10 :          ty(1, 2) = -0.5_dp
     740           10 :          ty(2, 1) = 0.5_dp
     741           86 :          DO y = 1, length
     742          666 :             DO x = 1, length
     743          580 :                i = 2*((y - 1)*length + x) - 1
     744          580 :                h(i, i) = mass
     745          580 :                h(i + 1, i + 1) = -mass
     746         1740 :                DO orbital = 0, 1
     747         1160 :                   s(i + orbital, i + orbital) = 1.0_dp
     748         1160 :                   r(i + orbital, i + orbital, 1) = REAL(x, dp) - REAL(length + 1, dp)/2.0_dp
     749         1740 :                   r(i + orbital, i + orbital, 2) = REAL(y, dp) - REAL(length + 1, dp)/2.0_dp
     750              :                END DO
     751          580 :                IF (x < length) THEN
     752          504 :                   j = i + 2
     753         3528 :                   h(i:i + 1, j:j + 1) = tx
     754         3528 :                   h(j:j + 1, i:i + 1) = CONJG(TRANSPOSE(tx))
     755              :                END IF
     756          580 :                IF (y < length) THEN
     757          504 :                   j = i + 2*length
     758         3528 :                   h(i:i + 1, j:j + 1) = ty
     759         3528 :                   h(j:j + 1, i:i + 1) = CONJG(TRANSPOSE(ty))
     760              :                END IF
     761          656 :                IF (PRESENT(torus)) THEN
     762          384 :                   IF (torus .AND. x == length) THEN
     763           48 :                      j = 2*(y - 1)*length + 1
     764          336 :                      h(i:i + 1, j:j + 1) = tx
     765          336 :                      h(j:j + 1, i:i + 1) = CONJG(TRANSPOSE(tx))
     766              :                   END IF
     767          384 :                   IF (torus .AND. y == length) THEN
     768           48 :                      j = 2*x - 1
     769          336 :                      h(i:i + 1, j:j + 1) = ty
     770          336 :                      h(j:j + 1, i:i + 1) = CONJG(TRANSPOSE(ty))
     771              :                   END IF
     772              :                END IF
     773              :             END DO
     774              :          END DO
     775           10 :       END SUBROUTINE qwz_model
     776              : 
     777              :    END SUBROUTINE check_localizer_2d
     778              : 
     779              : ! **************************************************************************************************
     780              : !> \brief Independent chiral singular values, strong/weak/trivial Wilson-Dirac models and AO covariance.
     781              : !> \author Thomas D. Kuehne, tkuehne@cp2k.org
     782              : ! **************************************************************************************************
     783            2 :    SUBROUTINE check_localizer_3d()
     784              :       USE ieee_arithmetic, ONLY: ieee_value, ieee_quiet_nan
     785              : #if defined(__HAS_IEEE_EXCEPTIONS)
     786              :       USE ieee_exceptions, ONLY: IEEE_ALL, ieee_get_halting_mode, ieee_set_halting_mode
     787              : #endif
     788              :       USE kinds, ONLY: dp
     789              :       USE spectral_localizer, ONLY: localizer_3d_evaluate, localizer_3d_matrix_evaluate
     790              :       IMPLICIT NONE
     791              :       COMPLEX(dp), PARAMETER :: ii = CMPLX(0.0_dp, 1.0_dp, dp)
     792              :       INTEGER, PARAMETER :: side = 3, sites = side**3, n = 4*sites
     793              :       REAL(dp), PARAMETER :: masses(3) = [0.0_dp, 4.0_dp, 2.0_dp]
     794              :       COMPLEX(dp) :: h(n, n), s(n, n), r(n, n, 3), u(n, n), hc(n, n), sc(n, n), rc(n, n, 3)
     795              :       COMPLEX(dp) :: pauli(2, 2, 3), a(2*n, 2*n), dummy(1, 1), work(10*n)
     796              :       REAL(dp) :: singular(2*n), rwork(10*n), gap, reference, ratio, mass, shift(3), defect, lower, upper, midpoint
     797              :       INTEGER :: i, j, axis, model, index, expected, status, sign_product, kx, ky, kz, io
     798              : #if defined(__HAS_IEEE_EXCEPTIONS)
     799              :       LOGICAL :: halt(SIZE(IEEE_ALL)), current_halt(SIZE(IEEE_ALL))
     800              :       CALL ieee_get_halting_mode(IEEE_ALL, halt)
     801              : #endif
     802              : 
     803            2 :       io = cp_logger_get_default_io_unit()
     804            2 :       CALL determinant_controls()
     805            2 :       pauli = 0.0_dp
     806            2 :       pauli(1, 2, 1) = 1.0_dp
     807            2 :       pauli(2, 1, 1) = 1.0_dp
     808            2 :       pauli(1, 2, 2) = -ii
     809            2 :       pauli(2, 1, 2) = ii
     810            2 :       pauli(1, 1, 3) = 1.0_dp
     811            2 :       pauli(2, 2, 3) = -1.0_dp
     812            8 :       DO model = 1, 3
     813            6 :          mass = masses(model)
     814            6 :          CALL dirac_model(mass)
     815              :          ! Bulk inversion eigenvalues at the eight TRIM: strong, not weak, parity.
     816            6 :          sign_product = 1
     817           18 :          DO kx = -1, 1, 2
     818           42 :             DO ky = -1, 1, 2
     819           36 :                DO kz = -1, 1, 2
     820           48 :                   sign_product = sign_product*INT(SIGN(1.0_dp, mass + kx + ky + kz))
     821              :                END DO
     822              :             END DO
     823              :          END DO
     824            6 :          expected = (1 - sign_product)/2
     825            6 :          CALL evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp)
     826            6 :          IF (status /= 0 .OR. index /= expected) ERROR STOP '3D strong/trivial parity mismatch'
     827            6 :          reference = gap
     828            6 :          a = 0.0_dp
     829        70638 :          a(:n, :n) = h
     830        70638 :          a(n + 1:, n + 1:) = h
     831           24 :          DO axis = 1, 3
     832           60 :             DO j = 1, 2
     833          126 :                DO i = 1, 2
     834              :                   a((i - 1)*n + 1:i*n, (j - 1)*n + 1:j*n) = &
     835       847692 :                      a((i - 1)*n + 1:i*n, (j - 1)*n + 1:j*n) - 0.5_dp*ii*pauli(i, j, axis)*r(:, :, axis)
     836              :                END DO
     837              :             END DO
     838              :          END DO
     839              : #if defined(__HAS_IEEE_EXCEPTIONS)
     840              :          CALL ieee_set_halting_mode(IEEE_ALL, .FALSE.)
     841              : #endif
     842            6 :          CALL zgesvd('N', 'N', 2*n, 2*n, a, 2*n, singular, dummy, 1, dummy, 1, work, SIZE(work), rwork, status)
     843              : #if defined(__HAS_IEEE_EXCEPTIONS)
     844              :          CALL ieee_set_halting_mode(IEEE_ALL, halt)
     845              : #endif
     846         1302 :          IF (status /= 0 .OR. ABS(reference - MINVAL(singular)) > 2.e-12_dp) ERROR STOP '3D singular gap mismatch'
     847            9 :          IF (io > 0) WRITE (io, '(A,F4.1,I3,2ES18.9)') '3D Dirac mass, index, gap, SVD error: ', &
     848          656 :             mass, expected, reference, ABS(reference - MINVAL(singular))
     849              :       END DO
     850              : 
     851              :       ! Reuse the validated mass-2 model for the covariance and origin checks.
     852            2 :       u = 0.0_dp
     853          110 :       DO i = 1, n/2
     854          108 :          u(i, i) = CMPLX(0.8_dp + 0.004_dp*i, 0.07_dp*SIN(REAL(i, dp)), dp)
     855          108 :          u(i + n/2, i + n/2) = CONJG(u(i, i))
     856          108 :          u(i, i + n/2) = CMPLX(0.08_dp, 0.03_dp, dp)
     857          108 :          u(i + n/2, i) = -CONJG(u(i, i + n/2))
     858          110 :          IF (i < n/2) THEN
     859          106 :             u(i, i + 1) = CMPLX(0.02_dp, 0.01_dp, dp)
     860          106 :             u(i + n/2, i + n/2 + 1) = CONJG(u(i, i + 1))
     861              :          END IF
     862              :       END DO
     863        23546 :       hc = MATMUL(CONJG(TRANSPOSE(u)), MATMUL(h, u))
     864        23546 :       sc = MATMUL(CONJG(TRANSPOSE(u)), MATMUL(s, u))
     865            8 :       DO axis = 1, 3
     866        70640 :          rc(:, :, axis) = MATMUL(CONJG(TRANSPOSE(u)), MATMUL(r(:, :, axis), u))
     867              :       END DO
     868            2 :       CALL evaluate(hc, sc, rc, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp)
     869            2 :       defect = ABS(gap - reference)
     870            2 :       IF (status /= 0 .OR. index /= 1 .OR. defect > 3.e-12_dp) ERROR STOP '3D nonorthogonal spin-mixing basis'
     871            2 :       IF (io > 0) WRITE (io, '(A,ES18.9)') '3D nonorthogonal spin-mixing gap residual: ', defect
     872            2 :       shift = [1.2_dp, -0.8_dp, 0.4_dp]
     873            8 :       DO axis = 1, 3
     874        70640 :          rc(:, :, axis) = r(:, :, axis) + shift(axis)*s
     875              :       END DO
     876        23546 :       CALL evaluate(h + 3.0_dp*s, s, rc, shift, 3.0_dp)
     877            2 :       IF (status /= 0 .OR. index /= 1 .OR. ABS(gap - reference) > 3.e-12_dp) ERROR STOP '3D origin shifts'
     878        23546 :       rc(:, :, 1) = 0.6_dp*r(:, :, 1) + 0.8_dp*r(:, :, 2)
     879        23546 :       rc(:, :, 2) = -0.8_dp*r(:, :, 1) + 0.6_dp*r(:, :, 2)
     880        23546 :       rc(:, :, 3) = -r(:, :, 3)
     881            2 :       CALL evaluate(h, s, rc, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp)
     882            2 :       IF (status /= 0 .OR. index /= 1 .OR. ABS(gap - reference) > 3.e-12_dp) ERROR STOP '3D reflection/rotation'
     883              :       ! A too-small finite box/scale is not automatically classified by the bulk parity.
     884              :       CALL localizer_3d_evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp, 0.3_dp, &
     885            2 :                                  1.e-10_dp, 1.e-10_dp, 1.e-10_dp, index, gap, ratio, status)
     886            2 :       IF (status /= 0 .OR. index /= 0) ERROR STOP '3D finite-size counterexample lost'
     887              :       lower = 0.3_dp
     888              :       upper = 0.5_dp
     889           62 :       DO i = 1, 32
     890           62 :          midpoint = 0.5_dp*(lower + upper)
     891              :          CALL localizer_3d_evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp, midpoint, &
     892           62 :                                     1.e-10_dp, 1.e-10_dp, 0.0_dp, index, gap, ratio, status)
     893           62 :          IF (status == 1) EXIT
     894           60 :          IF (status /= 0) ERROR STOP '3D transition factorization failed'
     895           62 :          IF (index == 0) THEN
     896              :             lower = midpoint
     897              :          ELSE
     898           40 :             upper = midpoint
     899              :          END IF
     900              :       END DO
     901              :       CALL localizer_3d_evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp, midpoint, &
     902            2 :                                  1.e-10_dp, 1.e-10_dp, 1.e-8_dp, index, gap, ratio, status)
     903            2 :       IF (status /= 1) ERROR STOP '3D closing gap assigned an index'
     904            2 :       IF (io > 0) WRITE (io, '(A,2ES18.9)') '3D localizer transition scale and unresolved gap: ', midpoint, gap
     905              :       CALL localizer_3d_evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp, 0.5_dp, &
     906            2 :                                  1.e-10_dp, -1.0_dp, 1.e-10_dp, index, gap, ratio, status)
     907            2 :       IF (status /= -1) ERROR STOP '3D invalid metric tolerance accepted'
     908              :       CALL localizer_3d_evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp, 0.5_dp, &
     909            2 :                                  1.e-10_dp, 1.e-10_dp, ieee_value(0.0_dp, ieee_quiet_nan), index, gap, ratio, status)
     910            2 :       IF (status /= -1) ERROR STOP '3D nonfinite gap tolerance accepted'
     911            2 :       h(1, 1) = h(1, 1) + 0.1_dp
     912            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp)
     913            2 :       IF (status /= -3) ERROR STOP '3D broken time reversal accepted'
     914            2 :       h = 0.0_dp
     915            2 :       r = 0.0_dp
     916            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp)
     917            2 :       IF (status /= 1 .OR. index /= 0) ERROR STOP '3D zero gap accepted'
     918            2 :       s(1, 1) = -1.0_dp
     919            2 :       s(n/2 + 1, n/2 + 1) = -1.0_dp
     920            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp)
     921            2 :       IF (status /= -2) ERROR STOP '3D indefinite metric accepted'
     922            2 :       h(1, 1) = ieee_value(0.0_dp, ieee_quiet_nan)
     923            2 :       CALL evaluate(h, s, r, [0.0_dp, 0.0_dp, 0.0_dp], 0.0_dp)
     924            2 :       IF (status /= -1) ERROR STOP '3D nonfinite operator accepted'
     925              : #if defined(__HAS_IEEE_EXCEPTIONS)
     926              :       CALL ieee_get_halting_mode(IEEE_ALL, current_halt)
     927              :       IF (ANY(halt .NEQV. current_halt)) ERROR STOP '3D localizer changed IEEE exception modes'
     928              : #endif
     929            3 :       IF (io > 0) WRITE (io, *) '3D AII determinant, generalized gap, basis, origin, TR and finite-size tests passed.'
     930              :    CONTAINS
     931              : ! **************************************************************************************************
     932              : !> \brief Chiral-block determinant controls independent of the Wilson-Dirac model.
     933              : !> \note Wilkinson's matrix has exponential LU pivot growth despite a large singular gap.
     934              : ! **************************************************************************************************
     935            2 :       SUBROUTINE determinant_controls()
     936              :       INTEGER, PARAMETER                                 :: m = 64, quarter = m/4
     937              : 
     938              :       COMPLEX(dp)                                        :: block(m, m), metric(m/2, m/2), &
     939              :                                                             unused(1, 1), workspace(5*m)
     940              :       INTEGER                                            :: col, col_sign, info, orientation, &
     941              :                                                             paired_col, paired_row, RESULT, row, &
     942              :                                                             row_sign
     943              :       REAL(dp)                                           :: matrix(m, m), measured_gap, &
     944              :                                                             metric_condition, real_work(5*m), &
     945              :                                                             svd_gap, values(m)
     946              : 
     947            2 :          metric = 0.0_dp
     948           66 :          DO row = 1, m/2
     949           66 :             metric(row, row) = 1.0_dp
     950              :          END DO
     951            2 :          matrix = 0.0_dp
     952          130 :          DO row = 1, m
     953          128 :             matrix(row, row) = 1.0_dp
     954         4160 :             matrix(row, :row - 1) = -1.0_dp
     955          130 :             matrix(row, m) = 1.0_dp + 0.25_dp*MODULO(row, 3)
     956              :          END DO
     957            6 :          DO orientation = 0, 1
     958              :             ! Positive last-column entries give a positive determinant. One row reversal changes its sign.
     959          134 :             IF (orientation == 1) matrix(1, :) = -matrix(1, :)
     960              :             ! Exact dyadic entries in Q W Q^dagger retain the pivot-growth fixture after conversion.
     961          260 :             DO col = 1, m
     962          256 :                paired_col = (3 - (col - 1)/quarter)*quarter + MODULO(col - 1, quarter) + 1
     963          256 :                col_sign = MERGE(1, -1, col <= quarter .OR. col > 3*quarter)
     964        16644 :                DO row = 1, m
     965        16384 :                   paired_row = (3 - (row - 1)/quarter)*quarter + MODULO(row - 1, quarter) + 1
     966        16384 :                   row_sign = MERGE(1, -1, row <= quarter .OR. row > 3*quarter)
     967              :                   block(row, col) = 0.5_dp*(matrix(row, col) + row_sign*col_sign*matrix(paired_row, paired_col) + &
     968        16640 :                                             ii*(col_sign*matrix(row, paired_col) - row_sign*matrix(paired_row, col)))
     969              :                END DO
     970              :             END DO
     971              :             CALL localizer_3d_matrix_evaluate(block, metric, 1.e-10_dp, 1.e-10_dp, 1.e-10_dp, &
     972            4 :                                               RESULT, measured_gap, metric_condition, info)
     973            4 :             IF (info /= 0 .OR. RESULT /= orientation) ERROR STOP '3D high-growth determinant sign'
     974              : #if defined(__HAS_IEEE_EXCEPTIONS)
     975              :             CALL ieee_set_halting_mode(IEEE_ALL, .FALSE.)
     976              : #endif
     977              :             CALL zgesvd('N', 'N', m, m, block, m, values, unused, 1, unused, 1, workspace, &
     978            4 :                         SIZE(workspace), real_work, info)
     979              : #if defined(__HAS_IEEE_EXCEPTIONS)
     980              :             CALL ieee_set_halting_mode(IEEE_ALL, halt)
     981              : #endif
     982          260 :             svd_gap = MINVAL(values)
     983            4 :             IF (info /= 0 .OR. ABS(measured_gap - svd_gap) > 1.e-12_dp) ERROR STOP '3D high-growth singular gap'
     984            6 :             IF (io > 0) WRITE (io, '(A,I2,2ES18.9)') '3D high-growth determinant, gap, SVD error: ', &
     985            6 :                RESULT, measured_gap, ABS(measured_gap - svd_gap)
     986              :          END DO
     987              :          ! Independent atomic limit for both signs of a spin-degenerate energy.
     988            4 :          DO col = -1, 1, 2
     989            4 :             block = 0.0_dp
     990          260 :             DO row = 1, m
     991          260 :                block(row, row) = REAL(col, dp)
     992              :             END DO
     993              :             CALL localizer_3d_matrix_evaluate(block, metric, 1.e-10_dp, 1.e-10_dp, 1.e-10_dp, &
     994            4 :                                               RESULT, measured_gap, metric_condition, info)
     995            6 :             IF (info /= 0 .OR. RESULT /= 0 .OR. ABS(measured_gap - 1.0_dp) > 1.e-12_dp) THEN
     996            0 :                ERROR STOP '3D atomic reference orientation'
     997              :             END IF
     998              :          END DO
     999            2 :       END SUBROUTINE determinant_controls
    1000              : 
    1001              : ! **************************************************************************************************
    1002              : !> \brief Fixed tolerances and localizer scale for the small Wilson-Dirac control.
    1003              : !> \param hh Physical Hamiltonian
    1004              : !> \param ss Physical overlap
    1005              : !> \param rr Three Cartesian moments
    1006              : !> \param point Query position
    1007              : !> \param energy Query energy
    1008              : ! **************************************************************************************************
    1009           20 :       SUBROUTINE evaluate(hh, ss, rr, point, energy)
    1010              :       COMPLEX(dp), INTENT(IN)                            :: hh(:, :), ss(:, :), rr(:, :, :)
    1011              :       REAL(dp), INTENT(IN)                               :: point(3), energy
    1012              : 
    1013              :          CALL localizer_3d_evaluate(hh, ss, rr, point, energy, 0.5_dp, 1.e-10_dp, 1.e-10_dp, 1.e-10_dp, &
    1014           20 :                                     index, gap, ratio, status)
    1015           20 :       END SUBROUTINE evaluate
    1016              : ! **************************************************************************************************
    1017              : !> \brief Open 3x3x3 Wilson-Dirac cube in fixed physical up/down ordering.
    1018              : !> \param m Onsite mass; periodic Bloch mass is m+cos(kx)+cos(ky)+cos(kz)
    1019              : ! **************************************************************************************************
    1020            6 :       SUBROUTINE dirac_model(m)
    1021              :       REAL(dp), INTENT(IN)                               :: m
    1022              : 
    1023              :       INTEGER                                            :: col, direction, neighbor, orbital, &
    1024              :                                                             other, pos(3), row, site, spin, x, y, z
    1025              : 
    1026            6 :          h = 0.0_dp
    1027            6 :          s = 0.0_dp
    1028            6 :          r = 0.0_dp
    1029           24 :          DO z = 0, side - 1
    1030           78 :             DO y = 0, side - 1
    1031          234 :                DO x = 0, side - 1
    1032          648 :                   pos = [x, y, z]
    1033          162 :                   site = 1 + x + side*y + side**2*z
    1034          540 :                   DO spin = 1, 2
    1035         1134 :                      DO orbital = 1, 2
    1036          648 :                         row = site + sites*(orbital - 1) + 2*sites*(spin - 1)
    1037          648 :                         s(row, row) = 1.0_dp
    1038          648 :                         h(row, row) = m*REAL(3 - 2*orbital, dp)
    1039         2592 :                         r(row, row, :) = REAL(pos - 1, dp)
    1040         2916 :                         DO direction = 1, 3
    1041         1944 :                            IF (pos(direction) == side - 1) CYCLE
    1042         1296 :                            neighbor = site + side**(direction - 1)
    1043         1296 :                            col = neighbor + sites*(orbital - 1) + 2*sites*(spin - 1)
    1044         1296 :                            h(row, col) = 0.5_dp*REAL(3 - 2*orbital, dp)
    1045         1296 :                            h(col, row) = CONJG(h(row, col))
    1046         4536 :                            DO other = 1, 2
    1047         2592 :                               col = neighbor + sites*(2 - orbital) + 2*sites*(other - 1)
    1048         2592 :                               h(row, col) = -0.5_dp*ii*pauli(spin, other, direction)
    1049         4536 :                               h(col, row) = CONJG(h(row, col))
    1050              :                            END DO
    1051              :                         END DO
    1052              :                      END DO
    1053              :                   END DO
    1054              :                END DO
    1055              :             END DO
    1056              :          END DO
    1057            6 :       END SUBROUTINE dirac_model
    1058              :    END SUBROUTINE check_localizer_3d
    1059              : 
    1060              : ! **************************************************************************************************
    1061              : !> \brief Complex metric-covariant matrix sign against exact spectral constructions.
    1062              : !> \param para_env Parallel environment initialized by the unit-test driver.
    1063              : !> \author Thomas D. Kuehne, tkuehne@cp2k.org
    1064              : ! **************************************************************************************************
    1065            2 :    SUBROUTINE check_localizer_flattening(para_env)
    1066              :       USE cp_dbcsr_api, ONLY: &
    1067              :          dbcsr_create, dbcsr_distribution_new, dbcsr_distribution_release, dbcsr_distribution_type, &
    1068              :          dbcsr_finalize, dbcsr_finalize_lib, dbcsr_init_lib, dbcsr_put_block, dbcsr_type_no_symmetry
    1069              :       USE fft_tools, ONLY: finalize_fft, &
    1070              :                            init_fft
    1071              :       USE ieee_arithmetic, ONLY: ieee_quiet_nan, &
    1072              :                                  ieee_value
    1073              :       USE kinds, ONLY: dp
    1074              :       USE mathconstants, ONLY: twopi
    1075              :       USE message_passing, ONLY: mp_para_env_type
    1076              :       USE spectral_localizer_bloch, ONLY: localizer_flatten_bloch
    1077              :       USE spectral_localizer_dbcsr, ONLY: localizer_dbcsr_dense, &
    1078              :                                           localizer_dbcsr_release, &
    1079              :                                           localizer_matrix_type
    1080              :       USE spectral_localizer_flatten, ONLY: localizer_flatten_checked, &
    1081              :                                             localizer_flatten_dbcsr
    1082              : #include "./base/base_uses.f90"
    1083              : 
    1084              :       IMPLICIT NONE
    1085              :       INTEGER, PARAMETER :: n = 12, nb = 6, bs = 2
    1086              :       TYPE(mp_para_env_type), POINTER :: para_env
    1087              :       TYPE(dbcsr_distribution_type) :: distribution
    1088              :       TYPE(localizer_matrix_type) :: h, s, flat
    1089            2 :       INTEGER, POINTER :: process_grid(:, :), row_dist(:), col_dist(:), sizes(:)
    1090              :       COMPLEX(dp) :: transform(n, n), u(n, n), rotation(n, n), scaled(n, n), hdense(n, n), sdense(n, n), exact(n, n)
    1091            2 :       COMPLEX(dp), ALLOCATABLE :: result(:, :)
    1092              :       REAL(dp) :: energies(n), residuals(4), difference, maximum_error, energy, scale, gap(2), metric_ratio, exact_gap
    1093              :       INTEGER :: i, j, mode, nr, nc, io, status
    1094              :       CHARACTER(LEN=32) :: argument
    1095              : 
    1096            4 :       io = cp_logger_get_default_io_unit()
    1097            2 :       CALL dbcsr_init_lib(para_env%get_handle(), io)
    1098            2 :       CALL init_fft('FFTSG', alltoall=.FALSE., pool_limit=4, wisdom_file='', plan_style=1)
    1099            2 :       nr = para_env%num_pe
    1100            2 :       nc = 1
    1101            4 :       DO i = 1, command_argument_count()
    1102            2 :          CALL get_command_argument(i, argument)
    1103            4 :          IF (TRIM(argument) == '--column-grid') THEN
    1104            0 :             nr = 1
    1105            0 :             nc = para_env%num_pe
    1106              :          END IF
    1107              :       END DO
    1108            8 :       ALLOCATE (process_grid(0:nr - 1, 0:nc - 1), row_dist(nb), col_dist(nb), sizes(nb))
    1109           14 :       process_grid = RESHAPE([(i, i=0, para_env%num_pe - 1)], [nr, nc])
    1110           26 :       row_dist = [(MODULO(i - 1, nr), i=1, nb)]
    1111           26 :       col_dist = [(MODULO(i - 1, nc), i=1, nb)]
    1112           14 :       sizes = bs
    1113              :       CALL dbcsr_distribution_new(distribution, group=para_env%get_handle(), pgrid=process_grid, &
    1114            2 :                                   row_dist=row_dist, col_dist=col_dist)
    1115            2 :       energy = 0.002_dp
    1116            2 :       scale = 1.7_dp
    1117            2 :       maximum_error = 0.0_dp
    1118           14 :       DO mode = 0, 5
    1119           12 :          u = 0.0_dp
    1120           12 :          transform = 0.0_dp
    1121          156 :          DO i = 1, n
    1122          144 :             energies(i) = 0.01_dp*(i - n/2) - 0.005_dp
    1123          144 :             transform(i, i) = 1.0_dp
    1124         1872 :             DO j = 1, n
    1125         1872 :                u(i, j) = EXP(CMPLX(0.0_dp, twopi*REAL((i - 1)*(j - 1), dp)/n, dp))/SQRT(REAL(n, dp))
    1126              :             END DO
    1127          144 :             IF (mode == 0) CYCLE
    1128          120 :             transform(i, i) = 0.5_dp + 0.1_dp*i
    1129          132 :             IF (i < n) transform(i, i + 1) = CMPLX(0.13_dp, -0.08_dp, dp)
    1130              :          END DO
    1131           12 :          IF (mode == 2) transform(n, n) = 0.1_dp
    1132           36 :          IF (mode == 4) energies = energies + 0.5_dp
    1133           36 :          IF (mode == 5) energies = energies - 0.5_dp
    1134           12 :          IF (mode == 3) THEN
    1135            2 :             u = 0.0_dp
    1136            2 :             rotation = 0.0_dp
    1137           86 :             transform(:n/2, n/2 + 1:) = 0.0_dp
    1138           86 :             transform(n/2 + 1:, :n/2) = 0.0_dp
    1139           86 :             transform(n/2 + 1:, n/2 + 1:) = CONJG(transform(:n/2, :n/2))
    1140           14 :             DO i = 1, n/2
    1141           12 :                energies(i) = 0.01_dp*(i - n/4) - 0.005_dp
    1142           12 :                energies(n/2 + i) = energies(i)
    1143           12 :                rotation(i, i) = COS(0.37_dp)
    1144           12 :                rotation(n/2 + i, n/2 + i) = COS(0.37_dp)
    1145           12 :                rotation(i, n/2 + i) = SIN(0.37_dp)
    1146           12 :                rotation(n/2 + i, i) = -SIN(0.37_dp)
    1147           86 :                DO j = 1, n/2
    1148           84 :                   u(i, j) = EXP(CMPLX(0.0_dp, twopi*REAL((i - 1)*(j - 1), dp)/(n/2), dp))/SQRT(REAL(n/2, dp))
    1149              :                END DO
    1150              :             END DO
    1151           86 :             u(n/2 + 1:, n/2 + 1:) = CONJG(u(:n/2, :n/2))
    1152         3770 :             u = MATMUL(rotation, u)
    1153              :          END IF
    1154         1884 :          scaled = u*SPREAD(energies, 1, n)
    1155        67836 :          hdense = MATMUL(CONJG(TRANSPOSE(transform)), MATMUL(MATMUL(scaled, CONJG(TRANSPOSE(u))), transform))
    1156        22620 :          sdense = MATMUL(CONJG(TRANSPOSE(transform)), transform)
    1157         2028 :          scaled = u*SPREAD(SIGN(scale, energies - energy), 1, n)
    1158        67836 :          exact = MATMUL(CONJG(TRANSPOSE(transform)), MATMUL(MATMUL(scaled, CONJG(TRANSPOSE(u))), transform))
    1159           12 :          CALL make_operator(hdense, h)
    1160           12 :          CALL make_operator(sdense, s)
    1161           12 :          CALL localizer_flatten_dbcsr(h, s, energy, scale, 1.e-9_dp, para_env, flat, residuals, status)
    1162           12 :          IF (status /= 0) THEN
    1163            0 :             IF (io > 0) WRITE (io, *) 'Flatten failure: ', mode, status, residuals
    1164            0 :             CPABORT('Distributed spectral flattening failed')
    1165              :          END IF
    1166           12 :          CALL localizer_dbcsr_dense(flat, para_env, RESULT)
    1167         1884 :          difference = MAXVAL(ABS(RESULT - exact))
    1168           12 :          maximum_error = MAX(maximum_error, difference)
    1169           12 :          IF (difference > 1.e-9_dp) THEN
    1170            0 :             CPABORT('Covariant sign differs from exact eigensystem')
    1171              :          END IF
    1172           12 :          IF (mode == 3) THEN
    1173           86 :             IF (MAXVAL(ABS(RESULT(:n/2, :n/2) - CONJG(RESULT(n/2 + 1:, n/2 + 1:)))) > 1.e-9_dp) THEN
    1174            0 :                CPABORT('Flattening broke physical TR diagonal blocks')
    1175              :             END IF
    1176           86 :             IF (MAXVAL(ABS(RESULT(:n/2, n/2 + 1:) + CONJG(RESULT(n/2 + 1:, :n/2)))) > 1.e-9_dp) THEN
    1177            0 :                CPABORT('Flattening broke physical TR spin mixing')
    1178              :             END IF
    1179              :          END IF
    1180           12 :          DEALLOCATE (RESULT)
    1181           12 :          CALL localizer_dbcsr_dense(h, para_env, RESULT)
    1182         1884 :          IF (MAXVAL(ABS(RESULT - hdense)) > 1.e-14_dp) THEN
    1183            0 :             CPABORT('Flattening modified input Hamiltonian')
    1184              :          END IF
    1185           12 :          DEALLOCATE (RESULT)
    1186           12 :          CALL localizer_dbcsr_dense(s, para_env, RESULT)
    1187         1884 :          IF (MAXVAL(ABS(RESULT - sdense)) > 1.e-14_dp) THEN
    1188            0 :             CPABORT('Flattening modified input metric')
    1189              :          END IF
    1190           12 :          DEALLOCATE (RESULT)
    1191           12 :          CALL localizer_dbcsr_release(flat)
    1192              :          CALL localizer_flatten_checked(h, s, energy, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1193           12 :                                         para_env, flat, gap, metric_ratio, residuals, status)
    1194           12 :          IF (status /= 0) CPABORT('Checked flattening failed')
    1195          156 :          exact_gap = MINVAL(ABS(energies - energy))
    1196           12 :          IF (gap(1) > exact_gap + 1.e-10_dp .OR. gap(2) < exact_gap - 1.e-10_dp) THEN
    1197            0 :             CPABORT('Input electronic gap differs from reference')
    1198              :          END IF
    1199           12 :          IF (metric_ratio <= 1.e-10_dp) CPABORT('Invalid positive metric bound')
    1200           12 :          CALL localizer_dbcsr_dense(flat, para_env, RESULT)
    1201         1884 :          difference = MAXVAL(ABS(RESULT - exact))
    1202           12 :          maximum_error = MAX(maximum_error, difference)
    1203           12 :          IF (difference > 1.e-9_dp) THEN
    1204            0 :             CPABORT('Checked flattening changed the exact result')
    1205              :          END IF
    1206           12 :          DEALLOCATE (RESULT)
    1207           12 :          CALL localizer_dbcsr_release(flat)
    1208           12 :          CALL localizer_dbcsr_release(h)
    1209           14 :          CALL localizer_dbcsr_release(s)
    1210              :       END DO
    1211              : 
    1212            2 :       hdense = 0.0_dp
    1213            2 :       sdense = 0.0_dp
    1214           26 :       DO i = 1, n
    1215           24 :          sdense(i, i) = 1.0_dp
    1216           26 :          hdense(i, i) = 1.0_dp
    1217              :       END DO
    1218            2 :       hdense(1, 1) = 0.0_dp
    1219            2 :       CALL make_operator(hdense, h)
    1220            2 :       CALL make_operator(sdense, s)
    1221            2 :       CALL localizer_flatten_dbcsr(h, s, 0.0_dp, scale, 1.e-9_dp, para_env, flat, residuals, status)
    1222            2 :       IF (status /= 1) CPABORT('Singular spectral sign was accepted')
    1223              :       CALL localizer_flatten_checked(h, s, 0.0_dp, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1224            2 :                                      para_env, flat, gap, metric_ratio, residuals, status)
    1225            2 :       IF (status /= 1) CPABORT('Checked flattening accepted a zero spectral mode')
    1226            2 :       CALL localizer_flatten_dbcsr(h, s, energy, -scale, 1.e-9_dp, para_env, flat, residuals, status)
    1227            2 :       IF (status /= -1) CPABORT('Negative flattening scale was accepted')
    1228              :       CALL localizer_flatten_dbcsr(h, s, ieee_value(energy, ieee_quiet_nan), scale, 1.e-9_dp, &
    1229            2 :                                    para_env, flat, residuals, status)
    1230            2 :       IF (status /= -1) CPABORT('Nonfinite energy was accepted')
    1231            2 :       CALL localizer_dbcsr_release(h)
    1232            2 :       hdense = 0.0_dp
    1233            2 :       CALL make_operator(hdense, h)
    1234            2 :       CALL localizer_flatten_dbcsr(h, s, 0.0_dp, scale, 1.e-9_dp, para_env, flat, residuals, status)
    1235            2 :       IF (status /= 1) CPABORT('Zero Hamiltonian sign was accepted')
    1236            2 :       CALL localizer_dbcsr_release(h)
    1237            2 :       hdense(1, 2) = 1.0_dp
    1238            2 :       CALL make_operator(hdense, h)
    1239            2 :       CALL localizer_flatten_dbcsr(h, s, energy, scale, 1.e-9_dp, para_env, flat, residuals, status)
    1240            2 :       IF (status /= -1) CPABORT('Non-Hermitian Hamiltonian was accepted')
    1241            2 :       CALL localizer_dbcsr_release(h)
    1242            2 :       CALL localizer_dbcsr_release(s)
    1243            2 :       hdense = sdense
    1244            2 :       hdense(1, 1) = 1.e-11_dp
    1245            2 :       CALL make_operator(hdense, h)
    1246            2 :       CALL make_operator(sdense, s)
    1247              :       CALL localizer_flatten_checked(h, s, 0.0_dp, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1248            2 :                                      para_env, flat, gap, metric_ratio, residuals, status)
    1249            2 :       IF (status /= 1) CPABORT('Unresolved near-zero spectral mode was accepted')
    1250            2 :       CALL localizer_dbcsr_release(s)
    1251            2 :       sdense(1, 1) = -1.0_dp
    1252            2 :       CALL make_operator(sdense, s)
    1253              :       CALL localizer_flatten_checked(h, s, 0.0_dp, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1254            2 :                                      para_env, flat, gap, metric_ratio, residuals, status)
    1255            2 :       IF (status /= -2) CPABORT('Indefinite metric was accepted')
    1256            2 :       CALL localizer_dbcsr_release(s)
    1257            2 :       sdense(1, 1) = 1.e-12_dp
    1258            2 :       CALL make_operator(sdense, s)
    1259              :       CALL localizer_flatten_checked(h, s, 0.0_dp, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1260            2 :                                      para_env, flat, gap, metric_ratio, residuals, status)
    1261            2 :       IF (status /= -2) CPABORT('Ill-conditioned metric was accepted')
    1262            2 :       CALL localizer_dbcsr_release(h)
    1263            2 :       CALL localizer_dbcsr_release(s)
    1264            2 :       CALL check_bloch()
    1265            2 :       IF (io > 0) WRITE (io, '(A,ES14.5)') 'Spectral flattening exact-matrix maximum error: ', maximum_error
    1266            2 :       CALL dbcsr_distribution_release(distribution)
    1267            2 :       DEALLOCATE (process_grid, row_dist, col_dist, sizes)
    1268            2 :       CALL dbcsr_finalize_lib()
    1269            4 :       CALL finalize_fft(para_env, '')
    1270              : 
    1271              :    CONTAINS
    1272              : 
    1273              : ! **************************************************************************************************
    1274              : !> \brief Complex nonorthogonal full-band Fourier references in spin/cell/atom order.
    1275              : ! **************************************************************************************************
    1276            2 :       SUBROUTINE check_bloch()
    1277              :       INTEGER                                            :: band, grid(3), k, kr(3), m, ncell, &
    1278              :                                                             nscalar, nsmall, nspin, p, pnext, &
    1279              :                                                             r(3), rnext, row, t, tnext
    1280              :       REAL(dp)                                           :: bloch_error, bloch_gap, norm_error, &
    1281              :                                                             phase, ref_gap
    1282              : 
    1283            2 :          bloch_error = 0.0_dp
    1284           16 :          DO m = 1, 7
    1285           98 :             sizes = bs
    1286           14 :             grid = [3, 1, 1]
    1287           14 :             nspin = 2
    1288           14 :             IF (m == 2) grid = [1, 3, 1]
    1289           14 :             IF (m == 3) grid = [1, 1, 3]
    1290           14 :             IF (m == 4) THEN
    1291            2 :                grid = [2, 3, 1]
    1292            2 :                nspin = 1
    1293              :             END IF
    1294           14 :             IF (m == 5) THEN
    1295            2 :                nspin = 1
    1296           14 :                sizes = [1, 3, 1, 3, 1, 3]
    1297              :             END IF
    1298           20 :             IF (m == 6) grid = 1
    1299           56 :             ncell = PRODUCT(grid)
    1300           14 :             nscalar = n/(nspin*ncell)
    1301           14 :             nsmall = nscalar*nspin
    1302           14 :             u = 0.0_dp
    1303           14 :             transform = 0.0_dp
    1304          182 :             DO row = 1, n
    1305          168 :                t = MODULO((row - 1)/nscalar, ncell)
    1306          168 :                p = ((row - 1)/(ncell*nscalar))*nscalar + MODULO(row - 1, nscalar)
    1307          672 :                r = [MODULO(t, grid(1)), MODULO(t/grid(1), grid(2)), t/(grid(1)*grid(2))]
    1308          168 :                transform(row, row) = 1.0_dp + 0.03_dp*p
    1309          168 :                pnext = MODULO(p + 1, nsmall)
    1310          168 :                rnext = (pnext/nscalar)*ncell*nscalar + t*nscalar + MODULO(pnext, nscalar) + 1
    1311          168 :                transform(row, rnext) = transform(row, rnext) + CMPLX(0.07_dp, -0.04_dp, dp)
    1312          168 :                tnext = MODULO(t + 1, ncell)
    1313          168 :                rnext = (p/nscalar)*ncell*nscalar + tnext*nscalar + MODULO(p, nscalar) + 1
    1314              :                ! A 1D cyclic displacement is a lattice translation for these first-axis-fast grids.
    1315          168 :                IF (m /= 4) transform(row, rnext) = transform(row, rnext) + CMPLX(0.05_dp, 0.02_dp, dp)
    1316          710 :                DO k = 0, ncell - 1
    1317         2112 :                   kr = [MODULO(k, grid(1)), MODULO(k/grid(1), grid(2)), k/(grid(1)*grid(2))]
    1318         2712 :                   DO band = 0, nsmall - 1
    1319         8064 :                      phase = twopi*(SUM(REAL(kr*r, dp)/REAL(grid, dp)) + REAL(p*band, dp)/REAL(nsmall, dp))
    1320         2016 :                      u(row, k*nsmall + band + 1) = EXP(CMPLX(0.0_dp, phase, dp))/SQRT(REAL(n, dp))
    1321         2016 :                      energies(k*nsmall + band + 1) = 0.2_dp + 0.03_dp*band + 0.01_dp*COS(REAL(k, dp))
    1322         2016 :                      IF (m == 7) energies(k*nsmall + band + 1) = 0.25_dp + 0.01_dp*COS(REAL(k, dp))
    1323         2544 :                      IF (band < nsmall/2) energies(k*nsmall + band + 1) = -energies(k*nsmall + band + 1)
    1324              :                   END DO
    1325              :                END DO
    1326              :             END DO
    1327         2198 :             scaled = u*SPREAD(energies, 1, n)
    1328        79142 :             hdense = MATMUL(CONJG(TRANSPOSE(transform)), MATMUL(MATMUL(scaled, CONJG(TRANSPOSE(u))), transform))
    1329        26390 :             sdense = MATMUL(CONJG(TRANSPOSE(transform)), transform)
    1330         2366 :             scaled = u*SPREAD(SIGN(scale, energies - energy), 1, n)
    1331        79142 :             exact = MATMUL(CONJG(TRANSPOSE(transform)), MATMUL(MATMUL(scaled, CONJG(TRANSPOSE(u))), transform))
    1332           14 :             CALL make_operator(hdense, h)
    1333           14 :             CALL make_operator(sdense, s)
    1334              :             CALL localizer_flatten_bloch(h, s, grid, nspin, energy, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1335           14 :                                          para_env, flat, bloch_gap, metric_ratio, residuals, status)
    1336           14 :             IF (status /= 0) CPABORT('Bloch flattening failed')
    1337           14 :             CALL localizer_dbcsr_dense(flat, para_env, RESULT)
    1338         2198 :             norm_error = MAXVAL(ABS(RESULT - exact))
    1339           14 :             bloch_error = MAX(bloch_error, norm_error)
    1340           14 :             maximum_error = MAX(maximum_error, norm_error)
    1341          182 :             ref_gap = MINVAL(ABS(energies - energy))
    1342           14 :             IF (norm_error > 1.e-11_dp .OR. ABS(bloch_gap - ref_gap) > 1.e-12_dp) THEN
    1343            0 :                CPABORT('Bloch flattening differs from exact full-torus reference')
    1344              :             END IF
    1345           14 :             CALL localizer_dbcsr_release(flat)
    1346              :             CALL localizer_flatten_checked(h, s, energy, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1347           14 :                                            para_env, flat, gap, metric_ratio, residuals, status)
    1348           14 :             IF (status /= 0) CPABORT('Full-torus reference flattening failed')
    1349           14 :             CALL localizer_dbcsr_dense(flat, para_env, RESULT)
    1350         2198 :             IF (MAXVAL(ABS(RESULT - exact)) > 1.e-10_dp) THEN
    1351            0 :                CPABORT('Full-torus/Bloch sign mismatch')
    1352              :             END IF
    1353           14 :             CALL localizer_dbcsr_release(flat)
    1354           14 :             CALL localizer_dbcsr_release(h)
    1355           14 :             IF (m /= 6) THEN
    1356           12 :                hdense(1, 1) = hdense(1, 1) + 0.01_dp
    1357           12 :                CALL make_operator(hdense, h)
    1358              :                CALL localizer_flatten_bloch(h, s, grid, nspin, energy, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1359           12 :                                             para_env, flat, bloch_gap, metric_ratio, residuals, status)
    1360           12 :                IF (status /= -7) CPABORT('Broken translations accepted')
    1361           12 :                CALL localizer_dbcsr_release(h)
    1362              :             END IF
    1363         2198 :             CALL make_operator(energy*sdense, h)
    1364              :             CALL localizer_flatten_bloch(h, s, grid, nspin, energy, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1365           14 :                                          para_env, flat, bloch_gap, metric_ratio, residuals, status)
    1366           14 :             IF (status /= 1) CPABORT('Closed Bloch gap accepted')
    1367           14 :             CALL localizer_dbcsr_release(s)
    1368         2198 :             CALL make_operator(-sdense, s)
    1369              :             CALL localizer_flatten_bloch(h, s, grid, nspin, energy, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1370           14 :                                          para_env, flat, bloch_gap, metric_ratio, residuals, status)
    1371           14 :             IF (status /= -2) CPABORT('Bloch flattening accepted an indefinite metric')
    1372           14 :             CALL localizer_dbcsr_release(h)
    1373           16 :             CALL localizer_dbcsr_release(s)
    1374              :          END DO
    1375           14 :          sizes = bs
    1376              :          ! Each Bloch metric is well conditioned, but their union has an unacceptable ratio.
    1377            2 :          hdense = 0.0_dp
    1378            2 :          sdense = 0.0_dp
    1379           26 :          DO row = 1, n
    1380           24 :             hdense(row, row) = 1.0_dp
    1381           24 :             sdense(row, row) = 0.5_dp*(1.0_dp + 1.e-12_dp)
    1382           24 :             rnext = 1 + MODULO(row - 1 + n/2, n)
    1383           26 :             sdense(row, rnext) = 0.5_dp*(1.0_dp - 1.e-12_dp)
    1384              :          END DO
    1385            2 :          CALL make_operator(hdense, h)
    1386            2 :          CALL make_operator(sdense, s)
    1387              :          CALL localizer_flatten_bloch(h, s, [2, 1, 1], 1, 0.0_dp, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1388            2 :                                       para_env, flat, bloch_gap, metric_ratio, residuals, status)
    1389            2 :          IF (status /= -2) CPABORT('Global Bloch metric bound failed')
    1390              :          CALL localizer_flatten_bloch(h, s, [0, 1, 1], 1, 0.0_dp, scale, 1.e-9_dp, 1.e-10_dp, 1.e-8_dp, &
    1391            2 :                                       para_env, flat, bloch_gap, metric_ratio, residuals, status)
    1392            2 :          IF (status /= -1) CPABORT('Invalid Bloch grid accepted')
    1393            2 :          CALL localizer_dbcsr_release(h)
    1394            2 :          CALL localizer_dbcsr_release(s)
    1395            2 :          IF (io > 0) WRITE (io, '(A,ES14.5)') 'Seven Bloch flattening pencils; maximum error: ', bloch_error
    1396            2 :       END SUBROUTINE check_bloch
    1397              : 
    1398              : ! **************************************************************************************************
    1399              : !> \brief Insert only locally owned dense-reference blocks.
    1400              : !> \param dense Reference matrix
    1401              : !> \param matrix New complex DBCSR operator
    1402              : ! **************************************************************************************************
    1403          112 :       SUBROUTINE make_operator(dense, matrix)
    1404              :       COMPLEX(dp), INTENT(IN)                            :: dense(:, :)
    1405              :       TYPE(localizer_matrix_type), INTENT(OUT)           :: matrix
    1406              : 
    1407              :       INTEGER                                            :: col, first, last, left, right, row
    1408              : 
    1409          112 :          CALL dbcsr_create(matrix%real_part, 'FLATTEN REAL', distribution, dbcsr_type_no_symmetry, sizes, sizes)
    1410          112 :          CALL dbcsr_create(matrix%imaginary_part, template=matrix%real_part)
    1411          784 :          DO row = 1, nb
    1412         2352 :             first = SUM(sizes(:row - 1)) + 1
    1413         3024 :             last = SUM(sizes(:row))
    1414         4816 :             DO col = 1, nb
    1415         4032 :                IF (process_grid(row_dist(row), col_dist(col)) /= para_env%mepos) CYCLE
    1416         7056 :                left = SUM(sizes(:col - 1)) + 1
    1417         9072 :                right = SUM(sizes(:col))
    1418        14112 :                CALL dbcsr_put_block(matrix%real_part, row, col, REAL(dense(first:last, left:right), dp))
    1419        16800 :                CALL dbcsr_put_block(matrix%imaginary_part, row, col, AIMAG(dense(first:last, left:right)))
    1420              :             END DO
    1421              :          END DO
    1422          112 :          CALL dbcsr_finalize(matrix%real_part)
    1423          112 :          CALL dbcsr_finalize(matrix%imaginary_part)
    1424          112 :       END SUBROUTINE make_operator
    1425              : 
    1426              :    END SUBROUTINE check_localizer_flattening
    1427              : 
    1428          338 : END MODULE spectral_localizer_test_cases
    1429              : 
    1430            2 : PROGRAM spectral_localizer_unittest
    1431              :    USE cp_log_handling, ONLY: cp_add_default_logger, &
    1432              :                               cp_logger_create, &
    1433              :                               cp_logger_release, &
    1434            2 :                               cp_logger_type, &
    1435              :                               cp_rm_default_logger
    1436              :    USE machine, ONLY: default_output_unit
    1437              :    USE message_passing, ONLY: mp_para_env_type, mp_world_finalize, mp_world_init
    1438              :    USE spectral_localizer_test_cases, ONLY: check_localizer_2d, check_localizer_3d, check_localizer_flattening
    1439              :    IMPLICIT NONE
    1440              :    TYPE(mp_para_env_type), POINTER :: para_env
    1441              :    TYPE(cp_logger_type), POINTER :: logger
    1442              :    INTEGER :: io
    1443              : 
    1444            2 :    ALLOCATE (para_env)
    1445            2 :    CALL mp_world_init(para_env)
    1446            2 :    io = -1
    1447            2 :    IF (para_env%is_source()) io = default_output_unit
    1448            2 :    CALL cp_logger_create(logger, para_env=para_env, default_global_unit_nr=io, close_global_unit_on_dealloc=.FALSE.)
    1449            2 :    CALL cp_add_default_logger(logger)
    1450            2 :    CALL check_localizer_2d()
    1451            2 :    CALL check_localizer_3d()
    1452            2 :    CALL check_localizer_flattening(para_env)
    1453            2 :    CALL cp_rm_default_logger()
    1454            2 :    CALL cp_logger_release(logger)
    1455            2 :    DEALLOCATE (para_env)
    1456            2 :    CALL mp_world_finalize()
    1457            2 : END PROGRAM spectral_localizer_unittest
        

Generated by: LCOV version 2.0-1