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

            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              : ! **************************************************************************************************
       9              : !> \brief Native character decomposition and metric-aware inversion representations.
      10              : ! **************************************************************************************************
      11              : MODULE topology_symmetry
      12              :    USE ieee_arithmetic,                 ONLY: ieee_is_finite
      13              :    USE kinds,                           ONLY: dp
      14              : 
      15              :    IMPLICIT NONE
      16              :    PRIVATE
      17              :    PUBLIC :: character_multiplicities, inversion_representation
      18              :    PUBLIC :: spin_rotation, transform_spinor_frame
      19              : CONTAINS
      20              : 
      21              : ! **************************************************************************************************
      22              : !> \brief Lift a Cartesian orthogonal operation to the physical spin-1/2 representation.
      23              : !> \param rotation Cartesian polar-vector operation, including improper operations
      24              : !> \param spin SU(2) lift of det(rotation)*rotation; inversion acts trivially on spin
      25              : !> \param tolerance Orthogonality tolerance
      26              : !> \param status Zero on success, negative for invalid geometry
      27              : !> \note The quaternion sign fixes one representative, not a single-valued SO(3) representation.
      28              : !>       Products must retain the resulting double-group factor system. Orbital parity is separate.
      29              : ! **************************************************************************************************
      30           12 :    SUBROUTINE spin_rotation(rotation, spin, tolerance, status)
      31              :       REAL(dp), INTENT(IN)                               :: rotation(3, 3)
      32              :       COMPLEX(dp), INTENT(OUT)                           :: spin(2, 2)
      33              :       REAL(dp), INTENT(IN)                               :: tolerance
      34              :       INTEGER, INTENT(OUT)                               :: status
      35              : 
      36              :       INTEGER                                            :: branch, i, j, k
      37              :       REAL(dp)                                           :: candidates(0:3), det, q(0:3), r(3, 3), &
      38              :                                                             scale, work(3, 3)
      39              : 
      40           12 :       spin = 0.0_dp
      41           12 :       status = -1
      42          156 :       IF (.NOT. ALL(ieee_is_finite(rotation)) .OR. .NOT. ieee_is_finite(tolerance)) RETURN
      43           12 :       IF (tolerance <= 0.0_dp .OR. tolerance >= 1.0_dp) RETURN
      44          480 :       work = MATMUL(TRANSPOSE(rotation), rotation)
      45           48 :       DO i = 1, 3
      46           48 :          work(i, i) = work(i, i) - 1.0_dp
      47              :       END DO
      48          156 :       IF (MAXVAL(ABS(work)) > tolerance) RETURN
      49              :       det = rotation(1, 1)*(rotation(2, 2)*rotation(3, 3) - rotation(2, 3)*rotation(3, 2)) &
      50              :             - rotation(1, 2)*(rotation(2, 1)*rotation(3, 3) - rotation(2, 3)*rotation(3, 1)) &
      51           12 :             + rotation(1, 3)*(rotation(2, 1)*rotation(3, 2) - rotation(2, 2)*rotation(3, 1))
      52           12 :       IF (ABS(ABS(det) - 1.0_dp) > 3*tolerance) RETURN
      53          156 :       r = SIGN(1.0_dp, det)*rotation
      54           12 :       candidates(0) = 1.0_dp + r(1, 1) + r(2, 2) + r(3, 3)
      55           48 :       DO i = 1, 3
      56           48 :          candidates(i) = 2.0_dp*(1.0_dp + r(i, i)) - candidates(0)
      57              :       END DO
      58           60 :       branch = MAXLOC(candidates, DIM=1) - 1
      59           12 :       scale = 2.0_dp*SQRT(MAX(0.0_dp, candidates(branch)))
      60           12 :       q(branch) = 0.25_dp*scale
      61           12 :       IF (branch == 0) THEN
      62            4 :          q(1) = (r(3, 2) - r(2, 3))/scale
      63            4 :          q(2) = (r(1, 3) - r(3, 1))/scale
      64            4 :          q(3) = (r(2, 1) - r(1, 2))/scale
      65              :       ELSE
      66            8 :          i = branch
      67            8 :          j = MODULO(i, 3) + 1
      68            8 :          k = MODULO(i + 1, 3) + 1
      69            8 :          q(0) = (r(k, j) - r(j, k))/scale
      70            8 :          q(j) = (r(j, i) + r(i, j))/scale
      71            8 :          q(k) = (r(k, i) + r(i, k))/scale
      72              :       END IF
      73          108 :       q = q/SQRT(SUM(q**2))
      74           12 :       spin(1, 1) = CMPLX(q(0), -q(3), dp)
      75           12 :       spin(2, 2) = CONJG(spin(1, 1))
      76           12 :       spin(1, 2) = CMPLX(-q(2), -q(1), dp)
      77           12 :       spin(2, 1) = -CONJG(spin(1, 2))
      78           12 :       status = 0
      79              :    END SUBROUTINE spin_rotation
      80              : 
      81              : ! **************************************************************************************************
      82              : !> \brief Compose orbital, Bloch, physical-spin and optional time-reversal transformations.
      83              : !> \param orbital Scalar AO action from the source to the target k, including all lattice phases
      84              : !> \param spin Unitary spin matrix from spin_rotation
      85              : !> \param source Source spin-major frame
      86              : !> \param antiunitary Apply physical Theta=i*sigma_y*K before the spatial operation
      87              : !> \param image Transformed spin-major frame in the target AO basis
      88              : !> \param tolerance Spin unitarity tolerance
      89              : !> \param status Zero on success, negative for malformed data
      90              : !> \note Orbital metric/Hamiltonian covariance and k mapping must be checked by the caller.
      91              : ! **************************************************************************************************
      92           12 :    SUBROUTINE transform_spinor_frame(orbital, spin, source, antiunitary, image, tolerance, status)
      93              :       COMPLEX(dp), INTENT(IN)                            :: orbital(:, :), spin(2, 2), source(:, :)
      94              :       LOGICAL, INTENT(IN)                                :: antiunitary
      95              :       COMPLEX(dp), INTENT(OUT)                           :: image(:, :)
      96              :       REAL(dp), INTENT(IN)                               :: tolerance
      97              :       INTEGER, INTENT(OUT)                               :: status
      98              : 
      99              :       COMPLEX(dp)                                        :: gram(2, 2)
     100           12 :       COMPLEX(dp), ALLOCATABLE                           :: down(:, :), up(:, :)
     101              :       INTEGER                                            :: m, n
     102              : 
     103           12 :       status = -1
     104        28976 :       image = 0.0_dp
     105           12 :       n = SIZE(orbital, 2)
     106           12 :       m = SIZE(orbital, 1)
     107           12 :       IF (n < 1 .OR. m < 1 .OR. SIZE(source, 1) /= 2*n) RETURN
     108           36 :       IF (ANY(SHAPE(image) /= [2*m, SIZE(source, 2)])) RETURN
     109           12 :       IF (.NOT. ieee_is_finite(tolerance) .OR. tolerance <= 0.0_dp .OR. tolerance >= 1.0_dp) RETURN
     110        14792 :       IF (.NOT. ALL(ieee_is_finite(REAL(orbital, dp))) .OR. .NOT. ALL(ieee_is_finite(AIMAG(orbital)))) RETURN
     111        57952 :       IF (.NOT. ALL(ieee_is_finite(REAL(source, dp))) .OR. .NOT. ALL(ieee_is_finite(AIMAG(source)))) RETURN
     112          168 :       IF (.NOT. ALL(ieee_is_finite(REAL(spin, dp))) .OR. .NOT. ALL(ieee_is_finite(AIMAG(spin)))) RETURN
     113          180 :       gram = MATMUL(CONJG(TRANSPOSE(spin)), spin)
     114           12 :       gram(1, 1) = gram(1, 1) - 1.0_dp
     115           12 :       gram(2, 2) = gram(2, 2) - 1.0_dp
     116           84 :       IF (MAXVAL(ABS(gram)) > tolerance) RETURN
     117           72 :       ALLOCATE (up(m, SIZE(source, 2)), down(m, SIZE(source, 2)))
     118           12 :       IF (antiunitary) THEN
     119         9518 :          up(:, :) = MATMUL(orbital, CONJG(source(n + 1:, :)))
     120        10254 :          down(:, :) = -MATMUL(orbital, CONJG(source(:n, :)))
     121              :       ELSE
     122           10 :          up(:, :) = MATMUL(orbital, source(:n, :))
     123           10 :          down(:, :) = MATMUL(orbital, source(n + 1:, :))
     124              :       END IF
     125        14780 :       image(:m, :) = spin(1, 1)*up + spin(1, 2)*down
     126        14780 :       image(m + 1:, :) = spin(2, 1)*up + spin(2, 2)*down
     127        57952 :       IF (.NOT. ALL(ieee_is_finite(REAL(image, dp))) .OR. .NOT. ALL(ieee_is_finite(AIMAG(image)))) RETURN
     128           12 :       status = 0
     129           12 :    END SUBROUTINE transform_spinor_frame
     130              : 
     131              : ! **************************************************************************************************
     132              : !> \brief Decompose a character against a complete unitary character table.
     133              : !> \param CHARACTER One character per group element (not per conjugacy class)
     134              : !> \param table Characters, group element first and irrep second; identity first
     135              : !> \param multiplicities Nonnegative integer multiplicities, invalid unless status=0
     136              : !> \param tolerance Absolute numerical tolerance
     137              : !> \param status Zero on success, negative for invalid/incomplete/nonintegral data
     138              : !> \note All projective irreps must use the same factor system. This kernel does not
     139              : !>       infer a space group or certify the supplied character table's provenance.
     140              : ! **************************************************************************************************
     141           56 :    SUBROUTINE character_multiplicities(CHARACTER, table, multiplicities, tolerance, status)
     142              :       COMPLEX(KIND=dp), INTENT(IN)                       :: character(:), table(:, :)
     143              :       INTEGER, INTENT(OUT)                               :: multiplicities(:)
     144              :       REAL(KIND=dp), INTENT(IN)                          :: tolerance
     145              :       INTEGER, INTENT(OUT)                               :: status
     146              : 
     147              :       COMPLEX(KIND=dp)                                   :: value
     148              :       INTEGER                                            :: dimension, i, j, nirrep, order, total
     149              : 
     150           56 :       status = -1
     151          172 :       multiplicities = -1
     152           56 :       order = SIZE(CHARACTER)
     153           56 :       nirrep = SIZE(table, 2)
     154           56 :       IF (order < 1 .OR. order > 1024 .OR. nirrep < 1 .OR. SIZE(table, 1) /= order) RETURN
     155           56 :       IF (SIZE(multiplicities) /= nirrep .OR. tolerance <= 0.0_dp) RETURN
     156           56 :       IF (.NOT. ieee_is_finite(tolerance)) RETURN
     157          184 :       IF (.NOT. ALL(ieee_is_finite(REAL(CHARACTER, dp)))) RETURN
     158          184 :       IF (.NOT. ALL(ieee_is_finite(AIMAG(CHARACTER)))) RETURN
     159          452 :       IF (.NOT. ALL(ieee_is_finite(REAL(table, dp)))) RETURN
     160          452 :       IF (.NOT. ALL(ieee_is_finite(AIMAG(table)))) RETURN
     161          452 :       IF (MAXVAL(ABS(table)) > REAL(order, dp) + tolerance) RETURN
     162          184 :       IF (MAXVAL(ABS(CHARACTER)) > 1.e6_dp) RETURN
     163              :       total = 0
     164          172 :       DO i = 1, nirrep
     165          116 :          DIMENSION = NINT(REAL(table(1, i), dp))
     166          116 :          IF (DIMENSION < 1 .OR. DIMENSION > order) RETURN
     167          116 :          IF (ABS(table(1, i) - DIMENSION) > tolerance) RETURN
     168          116 :          total = total + DIMENSION**2
     169          116 :          IF (total > order) RETURN
     170          416 :          DO j = 1, nirrep
     171          876 :             value = SUM(CONJG(table(:, i))*table(:, j))/REAL(order, dp)
     172          244 :             IF (i == j) value = value - 1.0_dp
     173          360 :             IF (ABS(value) > tolerance) RETURN
     174              :          END DO
     175              :       END DO
     176           56 :       IF (total /= order) RETURN
     177           56 :       status = -2
     178          158 :       DO i = 1, nirrep
     179          356 :          value = SUM(CONJG(table(:, i))*CHARACTER)/REAL(order, dp)
     180          108 :          j = NINT(REAL(value, dp))
     181          108 :          IF (j < 0 .OR. ABS(value - j) > tolerance) RETURN
     182          152 :          multiplicities(i) = j
     183              :       END DO
     184          690 :       IF (MAXVAL(ABS(MATMUL(table, CMPLX(multiplicities, 0, dp)) - CHARACTER)) > tolerance) RETURN
     185           50 :       status = 0
     186              :    END SUBROUTINE character_multiplicities
     187              : 
     188              : ! **************************************************************************************************
     189              : !> \brief Inversion irreps of an isolated scalar or spinor eigenspace in an AO metric.
     190              : !> \param metric Scalar AO overlap at a TRIM
     191              : !> \param coeff Selected coefficients, spinor components stacked by AO
     192              : !> \param mapping Target AO index under inversion
     193              : !> \param phase Source-AO multiplier, including orbital parity and lattice phase
     194              : !> \param energies Selected eigenvalues in Hartree
     195              : !> \param check_tr Check physical spinful time reversal as well as inversion
     196              : !> \param tolerance Dimensionless metric/subspace/character tolerance
     197              : !> \param energy_tolerance Eigenvalue-commutator tolerance in Hartree
     198              : !> \param counts Multiplicities of even/odd inversion irreps, counting states not pairs
     199              : !> \param error Largest dimensionless residual
     200              : !> \param energy_error Largest projected symmetry/eigenvalue commutator
     201              : !> \param status Zero on success; invalid output for nonzero status
     202              : !> \param diagnostics Optional metric, normalization, inversion and time-reversal residuals
     203              : ! **************************************************************************************************
     204           54 :    SUBROUTINE inversion_representation(metric, coeff, mapping, phase, energies, check_tr, &
     205              :                                        tolerance, energy_tolerance, counts, error, energy_error, status, diagnostics)
     206              :       COMPLEX(KIND=dp), INTENT(IN)                       :: metric(:, :), coeff(:, :)
     207              :       INTEGER, INTENT(IN)                                :: mapping(:)
     208              :       COMPLEX(KIND=dp), INTENT(IN)                       :: phase(:)
     209              :       REAL(KIND=dp), INTENT(IN)                          :: energies(:)
     210              :       LOGICAL, INTENT(IN)                                :: check_tr
     211              :       REAL(KIND=dp), INTENT(IN)                          :: tolerance, energy_tolerance
     212              :       INTEGER, INTENT(OUT)                               :: counts(2)
     213              :       REAL(KIND=dp), INTENT(OUT)                         :: error, energy_error
     214              :       INTEGER, INTENT(OUT)                               :: status
     215              :       REAL(KIND=dp), INTENT(OUT), OPTIONAL               :: diagnostics(4)
     216              : 
     217              :       COMPLEX(KIND=dp)                                   :: characters(2), table(2, 2)
     218           54 :       COMPLEX(KIND=dp), ALLOCATABLE                      :: pc(:, :), projected(:, :), sc(:, :), &
     219           54 :                                                             trc(:, :), work(:, :)
     220              :       INTEGER                                            :: first, i, j, last, nao, nspin, rank, s
     221              :       REAL(KIND=dp)                                      :: metric_scale
     222              : 
     223           54 :       status = -1
     224          162 :       counts = -1
     225           54 :       error = HUGE(1.0_dp)
     226           54 :       energy_error = HUGE(1.0_dp)
     227          198 :       IF (PRESENT(diagnostics)) diagnostics = HUGE(1.0_dp)
     228           54 :       nao = SIZE(metric, 1)
     229           54 :       rank = SIZE(coeff, 2)
     230           54 :       IF (nao < 1 .OR. rank < 1 .OR. SIZE(metric, 2) /= nao) RETURN
     231           54 :       IF (MOD(SIZE(coeff, 1), nao) /= 0) RETURN
     232           54 :       nspin = SIZE(coeff, 1)/nao
     233           54 :       IF (nspin < 1 .OR. nspin > 2) RETURN
     234           54 :       IF (check_tr .AND. (nspin /= 2 .OR. MOD(rank, 2) /= 0)) RETURN
     235           54 :       IF (SIZE(mapping) /= nao .OR. SIZE(phase) /= nao .OR. SIZE(energies) /= rank) RETURN
     236           54 :       IF (tolerance <= 0.0_dp .OR. energy_tolerance <= 0.0_dp) RETURN
     237           54 :       IF (.NOT. ieee_is_finite(tolerance) .OR. .NOT. ieee_is_finite(energy_tolerance)) RETURN
     238        20526 :       IF (.NOT. ALL(ieee_is_finite(REAL(metric, dp))) .OR. .NOT. ALL(ieee_is_finite(AIMAG(metric)))) RETURN
     239        17664 :       IF (.NOT. ALL(ieee_is_finite(REAL(coeff, dp))) .OR. .NOT. ALL(ieee_is_finite(AIMAG(coeff)))) RETURN
     240         1248 :       IF (.NOT. ALL(ieee_is_finite(REAL(phase, dp))) .OR. .NOT. ALL(ieee_is_finite(AIMAG(phase)))) RETURN
     241          352 :       IF (.NOT. ALL(ieee_is_finite(energies))) RETURN
     242         1248 :       IF (ANY(mapping < 1) .OR. ANY(mapping > nao)) RETURN
     243          624 :       IF (ANY(ABS(ABS(phase) - 1.0_dp) > tolerance)) RETURN
     244          624 :       DO i = 1, nao
     245          572 :          IF (mapping(mapping(i)) /= i) RETURN
     246          624 :          IF (ABS(phase(i)*phase(mapping(i)) - 1.0_dp) > tolerance) RETURN
     247              :       END DO
     248        10252 :       metric_scale = MAX(1.0_dp, MAXVAL(ABS(metric)))
     249        10252 :       error = MAXVAL(ABS(metric - CONJG(TRANSPOSE(metric))))/metric_scale
     250          624 :       DO j = 1, nao
     251        10252 :          DO i = 1, nao
     252              :             error = MAX(error, ABS(CONJG(phase(i))*metric(mapping(i), mapping(j))*phase(j) - metric(i, j))/ &
     253        10200 :                         metric_scale)
     254              :          END DO
     255              :       END DO
     256           52 :       IF (PRESENT(diagnostics)) diagnostics(1) = error
     257          572 :       ALLOCATE (sc(nao*nspin, rank), pc(nao*nspin, rank), projected(rank, rank), work(rank, rank))
     258          146 :       DO s = 1, nspin
     259           94 :          first = (s - 1)*nao
     260           94 :          last = s*nao
     261       170030 :          sc(first + 1:last, :) = MATMUL(metric, coeff(first + 1:last, :))
     262         1258 :          DO i = 1, nao
     263         9686 :             pc(first + mapping(i), :) = phase(i)*coeff(first + i, :)
     264              :          END DO
     265              :       END DO
     266        69568 :       work(:, :) = MATMUL(CONJG(TRANSPOSE(coeff)), sc)
     267          352 :       DO i = 1, rank
     268          352 :          work(i, i) = work(i, i) - 1.0_dp
     269              :       END DO
     270         2528 :       error = MAX(error, MAXVAL(ABS(work)))
     271         2364 :       IF (PRESENT(diagnostics)) diagnostics(2) = MAXVAL(ABS(work))
     272        69568 :       projected(:, :) = MATMUL(CONJG(TRANSPOSE(sc)), pc)
     273         2528 :       IF (.NOT. ALL(ieee_is_finite(REAL(projected, dp)))) RETURN
     274         2528 :       IF (.NOT. ALL(ieee_is_finite(AIMAG(projected)))) RETURN
     275        19352 :       work(:, :) = MATMUL(CONJG(TRANSPOSE(projected)), projected)
     276          352 :       DO i = 1, rank
     277          352 :          work(i, i) = work(i, i) - 1.0_dp
     278              :       END DO
     279         5056 :       error = MAX(error, MAXVAL(ABS(work)), MAXVAL(ABS(projected - CONJG(TRANSPOSE(projected)))))
     280           52 :       IF (PRESENT(diagnostics)) diagnostics(3) = MAX(MAXVAL(ABS(work)), &
     281         4696 :                                                      MAXVAL(ABS(projected - CONJG(TRANSPOSE(projected)))))
     282              :       IF (PRESENT(diagnostics)) THEN
     283           36 :          IF (.NOT. check_tr) diagnostics(4) = 0.0_dp
     284              :       END IF
     285           52 :       energy_error = 0.0_dp
     286          352 :       DO j = 1, rank
     287         2528 :          DO i = 1, rank
     288         2476 :             energy_error = MAX(energy_error, ABS(projected(i, j)*(energies(i) - energies(j))))
     289              :          END DO
     290              :       END DO
     291           52 :       characters(1) = CMPLX(rank, 0, dp)
     292           52 :       characters(2) = CMPLX(0, 0, dp)
     293          352 :       DO i = 1, rank
     294          352 :          characters(2) = characters(2) + projected(i, i)
     295              :       END DO
     296          156 :       table(:, 1) = CMPLX([1, 1], 0, dp)
     297          156 :       table(:, 2) = CMPLX([1, -1], 0, dp)
     298           52 :       CALL character_multiplicities(characters, table, counts, tolerance*rank, status)
     299           52 :       IF (status /= 0) RETURN
     300           48 :       status = -3
     301           48 :       IF (check_tr) THEN
     302              :          ! In a real Gaussian Bloch basis at a TRIM, Theta=(i sigma_y) K.
     303        20144 :          error = MAX(error, MAXVAL(ABS(AIMAG(metric)))/MAX(1.0_dp, MAXVAL(ABS(metric))))
     304          160 :          ALLOCATE (trc(2*nao, rank))
     305         4540 :          trc(:nao, :) = CONJG(coeff(nao + 1:, :))
     306         4540 :          trc(nao + 1:, :) = -CONJG(coeff(:nao, :))
     307        69452 :          projected(:, :) = MATMUL(CONJG(TRANSPOSE(sc)), trc)
     308        19260 :          work(:, :) = MATMUL(CONJG(TRANSPOSE(projected)), projected)
     309          324 :          DO i = 1, rank
     310          324 :             work(i, i) = work(i, i) - 1.0_dp
     311              :          END DO
     312         4952 :          error = MAX(error, MAXVAL(ABS(work)), MAXVAL(ABS(projected + TRANSPOSE(projected))))
     313           40 :          IF (PRESENT(diagnostics)) diagnostics(4) = MAX(MAXVAL(ABS(work)), &
     314         4672 :                                                         MAXVAL(ABS(projected + TRANSPOSE(projected))))
     315          324 :          DO j = 1, rank
     316         2476 :             DO i = 1, rank
     317         2436 :                energy_error = MAX(energy_error, ABS(projected(i, j)*(energies(i) - energies(j))))
     318              :             END DO
     319              :          END DO
     320          116 :          IF (ANY(MOD(counts, 2) /= 0)) RETURN
     321              :       END IF
     322           46 :       IF (.NOT. ieee_is_finite(error) .OR. .NOT. ieee_is_finite(energy_error)) RETURN
     323           46 :       IF (error > tolerance .OR. energy_error > energy_tolerance) RETURN
     324           44 :       status = 0
     325           54 :    END SUBROUTINE inversion_representation
     326           52 : END MODULE topology_symmetry
        

Generated by: LCOV version 2.0-1