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

            Line data    Source code
       1              : !--------------------------------------------------------------------------------------------------!
       2              : !   CP2K: A general program to perform molecular dynamics simulations                              !
       3              : !   Copyright 2000-2026 CP2K developers group <https://cp2k.org>                                   !
       4              : !                                                                                                  !
       5              : !   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
       6              : !--------------------------------------------------------------------------------------------------!
       7            2 : PROGRAM topology_symmetry_unittest
       8            2 :    USE ieee_arithmetic,                 ONLY: ieee_quiet_nan,&
       9              :                                               ieee_value
      10              :    USE iso_fortran_env,                 ONLY: output_unit
      11              :    USE kinds,                           ONLY: dp
      12              :    USE topology_symmetry,               ONLY: character_multiplicities,&
      13              :                                               inversion_representation
      14              :    USE topology_tqc,                    ONLY: inversion_ebr_signature,&
      15              :                                               inversion_indicators
      16              : 
      17              :    IMPLICIT NONE
      18              :    COMPLEX(KIND=dp) :: table(6, 3), group_chars(6), metric(2, 2), coeff(4, 4), phase(2), tmp(4)
      19              :    REAL(KIND=dp) :: error, energy_error, energies(4)
      20              :    INTEGER :: multiplicities(3), status, counts(2), odd(8), ebr(2, 8), a, k, sign, strong, weak(3), z4, io_unit
      21              :    LOGICAL :: signed_atomic, nonnegative_atomic
      22              : 
      23            2 :    io_unit = output_unit
      24              :    ! Complete C3v/S3 character table per element, including a two-dimensional irrep.
      25           14 :    table(:, 1) = CMPLX([1, 1, 1, 1, 1, 1], 0, dp)
      26           14 :    table(:, 2) = CMPLX([1, 1, 1, -1, -1, -1], 0, dp)
      27           14 :    table(:, 3) = CMPLX([2, -1, -1, 0, 0, 0], 0, dp)
      28           14 :    group_chars = table(:, 1) + 2*table(:, 3)
      29            2 :    CALL character_multiplicities(group_chars, table, multiplicities, 1.e-10_dp, status)
      30            8 :    IF (status /= 0 .OR. ANY(multiplicities /= [1, 0, 2])) ERROR STOP 'C3v decomposition failed'
      31            2 :    group_chars(2) = group_chars(2) + 0.1_dp
      32            2 :    CALL character_multiplicities(group_chars, table, multiplicities, 1.e-10_dp, status)
      33            2 :    IF (status == 0) ERROR STOP 'Invalid character accepted'
      34            2 :    metric = CMPLX(0, 0, dp)
      35            2 :    metric(1, 1) = 2.0_dp
      36            2 :    metric(2, 2) = 0.5_dp
      37            2 :    coeff = CMPLX(0, 0, dp)
      38            2 :    coeff(1, 1) = SQRT(0.5_dp)
      39            2 :    coeff(2, 2) = SQRT(2.0_dp)
      40            2 :    coeff(3, 3) = SQRT(0.5_dp)
      41            2 :    coeff(4, 4) = SQRT(2.0_dp)
      42            2 :    phase = CMPLX([1, -1], 0, dp)
      43            2 :    energies = 0.0_dp
      44              :    CALL inversion_representation(metric, coeff, [1, 2], phase, energies, .TRUE., &
      45            2 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      46            6 :    IF (status /= 0 .OR. ANY(counts /= [2, 2])) ERROR STOP 'Metric/spinor parity failed'
      47           10 :    tmp = coeff(:, 1)
      48           10 :    coeff(:, 1) = (tmp + CMPLX(0, 1, dp)*coeff(:, 2))/SQRT(2.0_dp)
      49           10 :    coeff(:, 2) = (CMPLX(0, 1, dp)*tmp + coeff(:, 2))/SQRT(2.0_dp)
      50              :    CALL inversion_representation(metric, coeff, [1, 2], phase, energies, .TRUE., &
      51            2 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      52            6 :    IF (status /= 0 .OR. ANY(counts /= [2, 2])) ERROR STOP 'Degenerate-gauge parity failed'
      53              :    CALL inversion_representation(metric, coeff(:, :1), [1, 2], phase, energies(:1), .FALSE., &
      54            2 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      55            2 :    IF (status == 0) ERROR STOP 'Nonclosed parity subspace accepted'
      56              :    CALL inversion_representation(metric, coeff(:, :2), [1, 2], phase, energies(:2), .TRUE., &
      57            2 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      58            2 :    IF (status == 0) ERROR STOP 'Broken physical time reversal accepted'
      59            2 :    energies(2) = 1.0_dp
      60              :    CALL inversion_representation(metric, coeff, [1, 2], phase, energies, .TRUE., &
      61            2 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      62            2 :    IF (status == 0) ERROR STOP 'Noncommuting eigenspace accepted'
      63              :    ! Scalar two-site inversion with a genuinely off-diagonal AO metric.
      64           14 :    metric = CMPLX(0.2_dp, 0, dp)
      65            2 :    metric(1, 1) = 1.0_dp
      66            2 :    metric(2, 2) = 1.0_dp
      67            6 :    coeff(:2, 1) = CMPLX([1.0_dp, 1.0_dp]/SQRT(2.4_dp), 0, dp)
      68            6 :    coeff(:2, 2) = CMPLX([1.0_dp, -1.0_dp]/SQRT(1.6_dp), 0, dp)
      69            6 :    phase = CMPLX(1, 0, dp)
      70              :    CALL inversion_representation(metric, coeff(:2, :2), [2, 1], phase, energies(:2), .FALSE., &
      71            2 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      72            6 :    IF (status /= 0 .OR. ANY(counts /= [1, 1])) ERROR STOP 'Scalar two-site inversion failed'
      73              :    CALL inversion_representation(metric, coeff(:2, :1), [2, 1], -phase, energies(:1), .FALSE., &
      74            6 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      75            6 :    IF (status /= 0 .OR. ANY(counts /= [0, 1])) ERROR STOP 'Zone-boundary inversion phase failed'
      76            2 :    metric(1, 2) = CMPLX(0.2_dp, 0.01_dp, dp)
      77              :    CALL inversion_representation(metric, coeff(:2, :2), [2, 1], phase, energies(:2), .FALSE., &
      78            2 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      79            2 :    IF (status == 0) ERROR STOP 'Invalid metric accepted'
      80            2 :    metric(1, 2) = CMPLX(ieee_value(0.0_dp, ieee_quiet_nan), 0, dp)
      81              :    CALL inversion_representation(metric, coeff(:2, :2), [2, 1], phase, energies(:2), .FALSE., &
      82            2 :                                  1.e-9_dp, 1.e-9_dp, counts, error, energy_error, status)
      83            2 :    IF (status == 0) ERROR STOP 'Nonfinite metric accepted'
      84           18 :    DO a = 0, 7
      85           34 :       DO sign = -1, 1, 2
      86          288 :          DO k = 0, 7
      87          288 :             odd(k + 1) = (1 - sign*(-1)**POPCNT(IAND(a, k)))/2
      88              :          END DO
      89           32 :          CALL inversion_indicators(odd, 1, 3, strong, weak, z4, status)
      90          128 :          IF (status /= 0 .OR. strong /= 0 .OR. ANY(weak /= 0) .OR. z4 /= 0) ERROR STOP 'Atomic indicator failed'
      91           32 :          CALL inversion_ebr_signature(odd, 1, signed_atomic, nonnegative_atomic, ebr, status)
      92           32 :          IF (status /= 0 .OR. .NOT. nonnegative_atomic .OR. .NOT. signed_atomic) ERROR STOP 'Atomic EBR failed'
      93          816 :          IF (SUM(ebr) /= 1) ERROR STOP 'EBR rank failed'
      94              :       END DO
      95              :    END DO
      96            2 :    odd = 0
      97            2 :    odd(1) = 1
      98            2 :    CALL inversion_indicators(odd, 1, 3, strong, weak, z4, status)
      99            2 :    IF (status /= 0 .OR. strong /= 1 .OR. z4 /= 1) ERROR STOP 'Strong index failed'
     100            2 :    CALL inversion_ebr_signature(odd, 1, signed_atomic, nonnegative_atomic, ebr, status)
     101            2 :    IF (status /= 0 .OR. signed_atomic .OR. nonnegative_atomic) ERROR STOP 'Stable obstruction missed'
     102            2 :    odd(1) = 2
     103            2 :    CALL inversion_indicators(odd, 2, 3, strong, weak, z4, status)
     104            2 :    IF (status /= 0 .OR. strong /= 0 .OR. z4 /= 2) ERROR STOP 'Inversion Z4=2 missed'
     105            2 :    odd(1) = 4
     106            2 :    CALL inversion_ebr_signature(odd, 4, signed_atomic, nonnegative_atomic, ebr, status)
     107            2 :    IF (status /= 0 .OR. .NOT. signed_atomic .OR. nonnegative_atomic) ERROR STOP 'Signed-only EBR missed'
     108           18 :    odd = -1
     109            2 :    CALL inversion_ebr_signature(odd, 4, signed_atomic, nonnegative_atomic, ebr, status)
     110            2 :    IF (status == 0) ERROR STOP 'Invalid multiplicity accepted'
     111            2 :    CALL exhaustive_signatures(2)
     112            2 :    CALL exhaustive_signatures(3)
     113            2 :    WRITE (io_unit, *) 'Native characters, parity, inversion indicators and EBR tests passed.'
     114              : CONTAINS
     115              : 
     116              : ! **************************************************************************************************
     117              : !> \brief Compare the exact solver against independent enumeration of rank-two atomic sums.
     118              : !> \param DIMENSION Dimension of the inversion subgroup
     119              : ! **************************************************************************************************
     120            4 :    SUBROUTINE exhaustive_signatures(DIMENSION)
     121              :       INTEGER, INTENT(IN)                                :: dimension
     122              : 
     123              :       INTEGER :: code, digit, i, ierr, indicator, indices(3), j, n, nu, x, y, &
     124            4 :          values(2**DIMENSION), signatures(2**DIMENSION, 2**(DIMENSION + 1)), &
     125            8 :          reconstructed(2**DIMENSION), coefficients(2, 2**DIMENSION)
     126            4 :       LOGICAL :: indicators_zero, nonnegative_ok, possible(0:3**(2**DIMENSION) - 1), signed_ok
     127              : 
     128            4 :       n = 2**DIMENSION
     129           28 :       DO x = 0, n - 1
     130          188 :          DO i = 0, n - 1
     131          160 :             signatures(i + 1, 2*x + 1) = (1 - (-1)**POPCNT(IAND(x, i)))/2
     132          184 :             signatures(i + 1, 2*x + 2) = 1 - signatures(i + 1, 2*x + 1)
     133              :          END DO
     134              :       END DO
     135        13288 :       possible = .FALSE.
     136           52 :       DO x = 1, 2*n
     137          692 :          DO y = 1, 2*n
     138         9856 :             code = SUM((signatures(:, x) + signatures(:, y))*[(3**i, i=0, n - 1)])
     139          688 :             possible(code) = .TRUE.
     140              :          END DO
     141              :       END DO
     142        13288 :       DO code = 0, 3**n - 1
     143              :          digit = code
     144       118908 :          DO i = 1, n
     145       105624 :             values(i) = MOD(digit, 3)
     146       118908 :             digit = digit/3
     147              :          END DO
     148        13284 :          CALL inversion_ebr_signature(values, 2, signed_ok, nonnegative_ok, coefficients, ierr)
     149        13284 :          IF (ierr /= 0 .OR. (nonnegative_ok .NEQV. possible(code))) ERROR STOP 'Enumerated EBR mismatch'
     150        13284 :          CALL inversion_indicators(values, 2, DIMENSION, nu, indices, indicator, ierr)
     151        13284 :          IF (ierr /= 0) ERROR STOP 'Enumerated indicator failure'
     152        13284 :          indicators_zero = nu == 0
     153        25110 :          IF (DIMENSION == 3) indicators_zero = indicator == 0 .AND. ALL(indices == 0)
     154        13284 :          IF (signed_ok .NEQV. indicators_zero) ERROR STOP 'Indicator/atomic-lattice mismatch'
     155        13284 :          IF (.NOT. nonnegative_ok) CYCLE
     156         2652 :          reconstructed = 0
     157         2652 :          DO j = 1, n
     158              :             reconstructed = reconstructed + coefficients(1, j)*signatures(:, 2*j - 1) + &
     159        20220 :                             coefficients(2, j)*signatures(:, 2*j)
     160              :          END DO
     161         9964 :          IF (ANY(reconstructed /= values) .OR. SUM(coefficients) /= 2) ERROR STOP 'EBR reconstruction failed'
     162              :       END DO
     163            2 :    END SUBROUTINE exhaustive_signatures
     164              : END PROGRAM topology_symmetry_unittest
        

Generated by: LCOV version 2.0-1