LCOV - code coverage report
Current view: top level - src - topology_wilson_unittest.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:92574dc) Lines: 97.4 % 152 148
Test Date: 2026-09-24 01:27:39 Functions: 100.0 % 8 8

            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            2 : PROGRAM topology_wilson_unittest
       9              :    USE iso_fortran_env,                 ONLY: output_unit
      10            2 :    USE kinds,                           ONLY: dp
      11              :    USE topology_wilson,                 ONLY: chern_from_wcc,&
      12              :                                               surface_resolved,&
      13              :                                               wcc_distance,&
      14              :                                               wilson_spectrum,&
      15              :                                               wilson_step,&
      16              :                                               z2_from_wcc
      17              : 
      18              :    IMPLICIT NONE
      19              : 
      20              :    REAL(KIND=dp), PARAMETER :: pi = 3.1415926535897932384626433832795_dp, tolerance = 1.e-10_dp
      21              : 
      22              :    INTEGER :: io_unit
      23              : 
      24            2 :    io_unit = output_unit
      25            2 :    CALL test_model(-1.0_dp, 1)
      26            2 :    CALL test_model(-3.0_dp, 0)
      27            2 :    CALL test_model(1.0_dp, 1)
      28            2 :    CALL test_model(3.0_dp, 0)
      29            2 :    CALL test_checks()
      30            2 :    CALL test_chern_model(-1.0_dp, 1)
      31            2 :    CALL test_chern_model(1.0_dp, -1)
      32            2 :    CALL test_chern_model(-3.0_dp, 0)
      33            2 :    CALL test_chern_checks()
      34            2 :    WRITE (io_unit, *) "Wilson-loop, Z2 and Chern unit tests passed."
      35              : 
      36              : CONTAINS
      37              : 
      38              : ! **************************************************************************************************
      39              : !> \brief Known first Chern numbers of the occupied two-band lattice Dirac model.
      40              : !> \param mass Dirac mass in h = (sin kx, sin ky, m+cos kx+cos ky).sigma
      41              : !> \param expected Chern number in the native/Z2Pack Wilson-winding convention
      42              : ! **************************************************************************************************
      43            6 :    SUBROUTINE test_chern_model(mass, expected)
      44              :       REAL(KIND=dp), INTENT(IN)                          :: mass
      45              :       INTEGER, INTENT(IN)                                :: expected
      46              : 
      47              :       INTEGER, PARAMETER                                 :: nline = 65, npoint = 41
      48              : 
      49              :       COMPLEX(KIND=dp)                                   :: h(2, 2), overlap(1, 1), PRODUCT(1, 1), &
      50              :                                                             states(2, npoint), work(8)
      51              :       INTEGER                                            :: i, invariant, j, line, status
      52              :       REAL(KIND=dp)                                      :: berry, d, doubled(2, nline), &
      53              :                                                             eigenvalues(2), kx, ky, minimum_sv, &
      54              :                                                             rwork(4), wcc(1, nline), winding
      55              : 
      56          396 :       DO line = 1, nline
      57          390 :          ky = 2.0_dp*pi*REAL(line - 1, dp)/REAL(nline - 1, dp)
      58        16380 :          DO i = 1, npoint
      59        15990 :             kx = 2.0_dp*pi*REAL(i - 1, dp)/REAL(npoint, dp)
      60        15990 :             d = mass + COS(kx) + COS(ky)
      61        15990 :             h(1, 1) = d
      62        15990 :             h(2, 2) = -d
      63        15990 :             h(1, 2) = CMPLX(SIN(kx), -SIN(ky), dp)
      64        15990 :             h(2, 1) = CONJG(h(1, 2))
      65        15990 :             CALL zheev('V', 'U', 2, h, 2, eigenvalues, work, SIZE(work), rwork, status)
      66        15990 :             IF (status /= 0) ERROR STOP "Chern model diagonalization failed"
      67        48360 :             states(:, i) = h(:, 1)
      68              :          END DO
      69         1170 :          PRODUCT(:, :) = 1.0_dp
      70        16380 :          DO i = 1, npoint
      71        15990 :             j = MOD(i, npoint) + 1
      72        47970 :             overlap(1, 1) = DOT_PRODUCT(states(:, i), states(:, j))
      73        15990 :             CALL wilson_step(product, overlap, minimum_sv, status, tolerance)
      74        16380 :             IF (status /= 0) ERROR STOP "Chern model overlap is singular"
      75              :          END DO
      76          390 :          CALL wilson_spectrum(product, wcc(:, line), berry, status)
      77          396 :          IF (status /= 0) ERROR STOP "Chern model spectrum failed"
      78              :       END DO
      79            6 :       CALL chern_from_wcc(wcc, invariant, winding, status, tolerance)
      80            6 :       IF (status /= 0 .OR. invariant /= expected) ERROR STOP "Incorrect model Chern number"
      81            6 :       IF (ABS(winding - REAL(expected, dp)) > tolerance) ERROR STOP "Incorrect Chern winding"
      82            6 :       CALL chern_from_wcc(wcc(:, nline:1:-1), invariant, winding, status, tolerance)
      83            6 :       IF (status /= 0 .OR. invariant /= -expected) ERROR STOP "Chern orientation reversal failed"
      84          396 :       doubled(1, :) = wcc(1, :)
      85          396 :       doubled(2, :) = wcc(1, :)
      86            6 :       CALL chern_from_wcc(doubled, invariant, winding, status, tolerance)
      87            6 :       IF (status /= 0 .OR. invariant /= 2*expected) ERROR STOP "Direct-sum Chern additivity failed"
      88            2 :    END SUBROUTINE test_chern_model
      89              : 
      90              : ! **************************************************************************************************
      91              : !> \brief Reject unclosed surfaces and unresolved determinant-phase steps.
      92              : ! **************************************************************************************************
      93            2 :    SUBROUTINE test_chern_checks()
      94              :       INTEGER                                            :: invariant, status
      95              :       REAL(KIND=dp)                                      :: surface(1, 5), winding
      96              : 
      97            2 :       surface(1, :) = [0.0_dp, 0.1_dp, 0.2_dp, 0.3_dp, 0.4_dp]
      98            2 :       CALL chern_from_wcc(surface, invariant, winding, status, tolerance)
      99            2 :       IF (status /= -2) ERROR STOP "Unclosed Chern surface was accepted"
     100            2 :       surface(1, :) = [0.0_dp, 0.3_dp, 0.6_dp, 0.9_dp, 1.0_dp]
     101            2 :       CALL chern_from_wcc(surface, invariant, winding, status, tolerance)
     102            2 :       IF (status /= -3) ERROR STOP "Unresolved Chern phase step was accepted"
     103           22 :       surface(:, :) = 0.1_dp
     104            2 :       CALL chern_from_wcc(surface, invariant, winding, status, tolerance)
     105            2 :       IF (status /= 0 .OR. invariant /= 0) ERROR STOP "Constant Chern surface was rejected"
     106            2 :    END SUBROUTINE test_chern_checks
     107              : 
     108              : ! **************************************************************************************************
     109              : !> \brief Known trivial/nontrivial lattice BHZ models, non-Abelian gauge changes and loop reversal.
     110              : !> \param mass Dirac mass; |mass| < 2 (excluding zero) is topological, |mass| > 2 is trivial
     111              : !> \param expected expected Z2 invariant
     112              : ! **************************************************************************************************
     113            8 :    SUBROUTINE test_model(mass, expected)
     114              :       REAL(KIND=dp), INTENT(IN)                          :: mass
     115              :       INTEGER, INTENT(IN)                                :: expected
     116              : 
     117              :       INTEGER, PARAMETER                                 :: nline = 31, npoint = 41
     118              : 
     119              :       COMPLEX(KIND=dp) :: gauged(2, 2), gauges(2, 2, npoint), links(2, 2, npoint), overlap(2, 2), &
     120              :          PRODUCT(2, 2), reversed(2, 2), states(4, 2, npoint)
     121              :       INTEGER                                            :: i, invariant, j, line, status
     122              :       REAL(KIND=dp)                                      :: berry, gauge_wcc(2, nline), kx, ky, &
     123              :                                                             minimum_sv, reverse_wcc(2, nline), &
     124              :                                                             wcc(2, nline)
     125              : 
     126          256 :       DO line = 1, nline
     127          248 :          ky = pi*REAL(line - 1, dp)/REAL(nline - 1, dp)
     128        10416 :          DO i = 1, npoint
     129        10168 :             kx = 2.0_dp*pi*REAL(i - 1, dp)/REAL(npoint, dp)
     130        10168 :             CALL occupied_states(kx, ky, mass, states(:, :, i))
     131        10416 :             CALL gauge_matrix(kx, ky, gauges(:, :, i))
     132              :          END DO
     133          248 :          PRODUCT(:, :) = 0.0_dp
     134          248 :          PRODUCT(1, 1) = 1.0_dp
     135          248 :          PRODUCT(2, 2) = 1.0_dp
     136          248 :          gauged(:, :) = product
     137          248 :          reversed(:, :) = product
     138        10416 :          DO i = 1, npoint
     139        10168 :             j = MOD(i, npoint) + 1
     140       305040 :             links(:, :, i) = MATMUL(CONJG(TRANSPOSE(states(:, :, i))), states(:, :, j))
     141        10168 :             CALL wilson_step(product, links(:, :, i), minimum_sv, status, tolerance)
     142        10168 :             IF (status /= 0) ERROR STOP "BHZ overlap is singular"
     143              :             overlap(:, :) = MATMUL(CONJG(TRANSPOSE(gauges(:, :, i))), &
     144       294872 :                                    MATMUL(links(:, :, i), gauges(:, :, j)))
     145        10168 :             CALL wilson_step(gauged, overlap, minimum_sv, status, tolerance)
     146        10416 :             IF (status /= 0) ERROR STOP "Gauge-transformed BHZ overlap is singular"
     147              :          END DO
     148        10416 :          DO i = npoint, 1, -1
     149        71176 :             overlap(:, :) = CONJG(TRANSPOSE(links(:, :, i)))
     150        10168 :             CALL wilson_step(reversed, overlap, minimum_sv, status, tolerance)
     151        10416 :             IF (status /= 0) ERROR STOP "Reversed BHZ overlap is singular"
     152              :          END DO
     153          248 :          CALL wilson_spectrum(product, wcc(:, line), berry, status)
     154          248 :          IF (status /= 0) ERROR STOP "BHZ spectrum failed"
     155          248 :          CALL wilson_spectrum(gauged, gauge_wcc(:, line), berry, status)
     156          248 :          IF (status /= 0) ERROR STOP "Gauge-transformed spectrum failed"
     157          248 :          CALL wilson_spectrum(reversed, reverse_wcc(:, line), berry, status)
     158          248 :          IF (status /= 0) ERROR STOP "Reversed spectrum failed"
     159          248 :          IF (wcc_distance(wcc(:, line), gauge_wcc(:, line)) > tolerance) THEN
     160            0 :             ERROR STOP "Wilson spectrum depends on occupied-space gauge"
     161              :          END IF
     162          752 :          IF (wcc_distance(wcc(:, line), -reverse_wcc(:, line)) > tolerance) THEN
     163            0 :             ERROR STOP "Loop reversal did not conjugate Wilson eigenvalues"
     164              :          END IF
     165              :       END DO
     166            8 :       CALL z2_from_wcc(wcc, invariant, status, tolerance)
     167            8 :       IF (status /= 0 .OR. invariant /= expected) ERROR STOP "Incorrect BHZ Z2 invariant"
     168            8 :       CALL z2_from_wcc(gauge_wcc, invariant, status, tolerance)
     169            8 :       IF (status /= 0 .OR. invariant /= expected) ERROR STOP "Gauge-dependent Z2 invariant"
     170            8 :       CALL z2_from_wcc(reverse_wcc, invariant, status, tolerance)
     171            8 :       IF (status /= 0 .OR. invariant /= expected) ERROR STOP "Reversal-dependent Z2 invariant"
     172            8 :    END SUBROUTINE test_model
     173              : 
     174              : ! **************************************************************************************************
     175              : !> \brief Occupied eigenvectors of diag(h(k), h*(-k)), with h = (sin kx, sin ky, m+cos kx+cos ky).sigma.
     176              : !> \param kx first reduced angle
     177              : !> \param ky second reduced angle
     178              : !> \param mass Dirac mass
     179              : !> \param states two occupied eigenvectors
     180              : ! **************************************************************************************************
     181        10168 :    SUBROUTINE occupied_states(kx, ky, mass, states)
     182              :       REAL(KIND=dp), INTENT(IN)                          :: kx, ky, mass
     183              :       COMPLEX(KIND=dp), INTENT(OUT)                      :: states(4, 2)
     184              : 
     185              :       COMPLEX(KIND=dp)                                   :: h(4, 4), work(16)
     186              :       INTEGER                                            :: status
     187              :       REAL(KIND=dp)                                      :: d, eigenvalues(4), rwork(10)
     188              : 
     189        10168 :       d = mass + COS(kx) + COS(ky)
     190        10168 :       h(:, :) = 0.0_dp
     191        10168 :       h(1, 1) = d
     192        10168 :       h(2, 2) = -d
     193        10168 :       h(1, 2) = CMPLX(SIN(kx), -SIN(ky), dp)
     194        10168 :       h(2, 1) = CONJG(h(1, 2))
     195        10168 :       h(3, 3) = d
     196        10168 :       h(4, 4) = -d
     197        10168 :       h(3, 4) = CMPLX(-SIN(kx), -SIN(ky), dp)
     198        10168 :       h(4, 3) = CONJG(h(3, 4))
     199        10168 :       CALL zheev('V', 'U', 4, h, 4, eigenvalues, work, SIZE(work), rwork, status)
     200        10168 :       IF (status /= 0) ERROR STOP "BHZ diagonalization failed"
     201       111848 :       states(:, :) = h(:, 1:2)
     202        10168 :    END SUBROUTINE occupied_states
     203              : 
     204              : ! **************************************************************************************************
     205              : !> \brief Deterministic k-dependent U(2) rotations, mixing the two occupied states.
     206              : !> \param kx first reduced angle
     207              : !> \param ky second reduced angle
     208              : !> \param matrix gauge matrix
     209              : ! **************************************************************************************************
     210        10168 :    SUBROUTINE gauge_matrix(kx, ky, matrix)
     211              :       REAL(KIND=dp), INTENT(IN)                          :: kx, ky
     212              :       COMPLEX(KIND=dp), INTENT(OUT)                      :: matrix(2, 2)
     213              : 
     214              :       COMPLEX(KIND=dp)                                   :: determinant_phase, phase
     215              :       REAL(KIND=dp)                                      :: angle
     216              : 
     217        10168 :       angle = 0.37_dp + 0.61_dp*SIN(3.0_dp*kx + ky)
     218        10168 :       phase = EXP(CMPLX(0.0_dp, 0.73_dp*COS(kx - 2.0_dp*ky), dp))
     219        10168 :       determinant_phase = EXP(CMPLX(0.0_dp, 0.29_dp*SIN(kx + ky), dp))
     220        10168 :       matrix(1, 1) = COS(angle)
     221        10168 :       matrix(1, 2) = SIN(angle)*phase
     222        10168 :       matrix(2, 1) = -SIN(angle)*CONJG(phase)
     223        10168 :       matrix(2, 2) = COS(angle)
     224        30504 :       matrix(:, 1) = matrix(:, 1)*determinant_phase
     225        10168 :    END SUBROUTINE gauge_matrix
     226              : 
     227              : ! **************************************************************************************************
     228              : !> \brief Analytic polar/Berry phases and singular-link, Kramers-pair and surface-resolution checks.
     229              : ! **************************************************************************************************
     230            2 :    SUBROUTINE test_checks()
     231              :       COMPLEX(KIND=dp)                                   :: overlap(2, 2), PRODUCT(2, 2)
     232              :       INTEGER                                            :: invariant, status
     233              :       REAL(KIND=dp)                                      :: berry, minimum_sv, surface(2, 3), wcc(2)
     234              : 
     235            2 :       PRODUCT(:, :) = 0.0_dp
     236            2 :       PRODUCT(1, 1) = 1.0_dp
     237            2 :       PRODUCT(2, 2) = 1.0_dp
     238            2 :       overlap(:, :) = 0.0_dp
     239            2 :       CALL wilson_step(product, overlap, minimum_sv, status, tolerance)
     240            2 :       IF (status /= -2) ERROR STOP "Singular overlap was accepted"
     241              : 
     242            2 :       overlap(1, 1) = 0.8_dp*EXP(CMPLX(0.0_dp, 0.2_dp*pi, dp))
     243            2 :       overlap(2, 2) = 0.6_dp*EXP(CMPLX(0.0_dp, 0.6_dp*pi, dp))
     244            2 :       CALL wilson_step(product, overlap, minimum_sv, status, tolerance)
     245            2 :       IF (status /= 0 .OR. ABS(minimum_sv - 0.6_dp) > tolerance) THEN
     246            0 :          ERROR STOP "Polar factor singular value is incorrect"
     247              :       END IF
     248            2 :       CALL wilson_spectrum(product, wcc, berry, status)
     249            2 :       IF (status /= 0) ERROR STOP "Analytic spectrum failed"
     250            2 :       IF (wcc_distance(wcc, [0.1_dp, 0.3_dp]) > tolerance) ERROR STOP "Incorrect analytic WCC"
     251            2 :       IF (ABS(berry - 0.8_dp*pi) > tolerance) ERROR STOP "Incorrect analytic Berry phase"
     252              : 
     253            6 :       surface(:, 1) = wcc
     254            6 :       surface(:, 2) = wcc
     255            6 :       surface(:, 3) = wcc
     256            2 :       CALL z2_from_wcc(surface, invariant, status, tolerance)
     257            2 :       IF (status /= -2 .OR. invariant /= -1) ERROR STOP "Missing Kramers pairs were accepted"
     258           20 :       surface(:, :) = 0.1_dp
     259            6 :       surface(:, 2) = 0.6_dp
     260            2 :       IF (surface_resolved(surface)) ERROR STOP "Unresolved surface was accepted"
     261           20 :       surface(:, :) = 0.1_dp
     262            2 :       IF (.NOT. surface_resolved(surface)) ERROR STOP "Constant surface was rejected"
     263            2 :       CALL z2_from_wcc(surface, invariant, status, tolerance)
     264            2 :       IF (status /= 0 .OR. invariant /= 0) ERROR STOP "Incorrect constant-surface parity"
     265            2 :       IF (wcc_distance([0.99_dp, 0.2_dp], [0.2_dp, -0.01_dp]) > tolerance) THEN
     266            0 :          ERROR STOP "Circular WCC matching failed"
     267              :       END IF
     268            2 :    END SUBROUTINE test_checks
     269              : 
     270              : END PROGRAM topology_wilson_unittest
        

Generated by: LCOV version 2.0-1