LCOV - code coverage report
Current view: top level - src - topology_snapshot.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:92574dc) Lines: 39.4 % 241 95
Test Date: 2026-09-24 01:27:39 Functions: 90.9 % 11 10

            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 Reader and physical moving-basis links for version-1 topology snapshots.
      10              : ! **************************************************************************************************
      11              : MODULE topology_snapshot
      12              :    USE ai_moments,                      ONLY: cossin
      13              :    USE cp_files,                        ONLY: close_file,&
      14              :                                               open_file
      15              :    USE ieee_arithmetic,                 ONLY: ieee_is_finite
      16              :    USE kinds,                           ONLY: dp
      17              :    USE orbital_pointers,                ONLY: indco,&
      18              :                                               init_orbital_pointers
      19              : #include "./base/base_uses.f90"
      20              : 
      21              :    IMPLICIT NONE
      22              :    PRIVATE
      23              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'topology_snapshot'
      24              :    REAL(KIND=dp), PARAMETER :: pi = 3.1415926535897932384626433832795_dp, cell_tol = 1.e-10_dp
      25              :    TYPE :: snapshot_shell_type
      26              :       INTEGER :: first = 0, count = 0, lmin = 0, lmax = 0
      27              :       REAL(KIND=dp) :: center(3) = 0.0_dp, radius = 0.0_dp
      28              :       REAL(KIND=dp), ALLOCATABLE :: exponent(:), radii(:), contraction(:, :)
      29              :    END TYPE snapshot_shell_type
      30              :    TYPE :: snapshot_atom_type
      31              :       INTEGER :: kind = 0, count = 0
      32              :       REAL(KIND=dp) :: center(3) = 0.0_dp
      33              :       TYPE(snapshot_shell_type), ALLOCATABLE :: shells(:)
      34              :    END TYPE snapshot_atom_type
      35              :    TYPE :: snapshot_type
      36              :       INTEGER :: nao = 0, rank = 0, nspin = 0, channel = 0
      37              :       INTEGER :: periodic(3) = 0
      38              :       REAL(KIND=dp) :: cell(3, 3) = 0.0_dp, inverse(3, 3) = 0.0_dp, k(3) = 0.0_dp
      39              :       TYPE(snapshot_atom_type), ALLOCATABLE :: atoms(:)
      40              :       INTEGER, ALLOCATABLE :: bands(:)
      41              :       REAL(KIND=dp), ALLOCATABLE :: energies(:)
      42              :       COMPLEX(KIND=dp), ALLOCATABLE :: coefficients(:, :)
      43              :    END TYPE snapshot_type
      44              :    PUBLIC :: snapshot_type, read_snapshot, snapshot_overlap, check_snapshot, check_snapshot_seam
      45              : CONTAINS
      46              : 
      47              : ! **************************************************************************************************
      48              : !> \brief Read a single point, retaining only its states; reject malformed/truncated exports.
      49              : !> \param filename Version-1 STATE_EXPORT file
      50              : !> \param point One-based point index
      51              : !> \param state Selected frame
      52              : ! **************************************************************************************************
      53            0 :    SUBROUTINE read_snapshot(filename, point, state)
      54              :       CHARACTER(LEN=*), INTENT(IN)                       :: filename
      55              :       INTEGER, INTENT(IN)                                :: point
      56              :       TYPE(snapshot_type), INTENT(OUT)                   :: state
      57              : 
      58              :       CHARACTER(LEN=256)                                 :: header
      59              :       INTEGER                                            :: first, i, ia, ic, ios, ip, iset, ix, j, &
      60              :                                                             nat, nc, nk, np, ns, nt, offset, p, &
      61              :                                                             powers(3), refpowers(3), unit
      62            0 :       LOGICAL, ALLOCATABLE                               :: covered(:)
      63              :       REAL(KIND=dp)                                      :: det, k(3), pair(2)
      64            0 :       REAL(KIND=dp), ALLOCATABLE                         :: energies(:), row(:)
      65              : 
      66            0 :       CALL open_file(filename, unit_number=unit, file_status='OLD', file_action='READ')
      67            0 :       READ (unit, '(A)', IOSTAT=ios) header
      68            0 :       CPASSERT(ios == 0)
      69            0 :       IF (TRIM(header) /= 'CP2K_TOPOLOGY_STATE 1') THEN
      70            0 :          CPABORT('Unsupported topology snapshot version')
      71              :       END IF
      72            0 :       READ (unit, *, IOSTAT=ios) nat, state%nao, state%rank, nk, state%nspin, nt, state%channel
      73            0 :       CPASSERT(ios == 0)
      74            0 :       IF (MIN(nat, state%nao, state%rank, nk, nt, state%channel) < 1) THEN
      75            0 :          CPABORT('Invalid snapshot dimensions')
      76              :       END IF
      77            0 :       IF (state%rank > nt .OR. point < 1 .OR. point > nk) THEN
      78            0 :          CPABORT('Invalid snapshot point or rank')
      79              :       END IF
      80            0 :       IF (state%nspin /= 1 .AND. state%nspin /= 2) THEN
      81            0 :          CPABORT('Invalid snapshot spin components')
      82              :       END IF
      83            0 :       ALLOCATE (state%bands(state%rank), state%energies(nt), energies(nt), &
      84            0 :                 state%coefficients(state%nao*state%nspin, state%rank), state%atoms(nat))
      85            0 :       READ (unit, *, IOSTAT=ios) state%bands
      86            0 :       CPASSERT(ios == 0)
      87            0 :       IF (ANY(state%bands < 1) .OR. ANY(state%bands > nt)) THEN
      88            0 :          CPABORT('Invalid snapshot band indices')
      89              :       END IF
      90            0 :       DO i = 2, state%rank
      91            0 :          IF (state%bands(i) <= state%bands(i - 1)) THEN
      92            0 :             CPABORT('Snapshot bands must be strictly ordered')
      93              :          END IF
      94              :       END DO
      95            0 :       DO j = 1, 3
      96            0 :          READ (unit, *, IOSTAT=ios) state%cell(:, j)
      97            0 :          CPASSERT(ios == 0)
      98              :       END DO
      99            0 :       IF (.NOT. ALL(ieee_is_finite(state%cell))) THEN
     100            0 :          CPABORT('Nonfinite snapshot cell')
     101              :       END IF
     102              :       ASSOCIATE (a => state%cell, b => state%inverse)
     103            0 :          b(1, :) = [a(2, 2)*a(3, 3) - a(2, 3)*a(3, 2), a(1, 3)*a(3, 2) - a(1, 2)*a(3, 3), a(1, 2)*a(2, 3) - a(1, 3)*a(2, 2)]
     104            0 :          b(2, :) = [a(2, 3)*a(3, 1) - a(2, 1)*a(3, 3), a(1, 1)*a(3, 3) - a(1, 3)*a(3, 1), a(1, 3)*a(2, 1) - a(1, 1)*a(2, 3)]
     105            0 :          b(3, :) = [a(2, 1)*a(3, 2) - a(2, 2)*a(3, 1), a(1, 2)*a(3, 1) - a(1, 1)*a(3, 2), a(1, 1)*a(2, 2) - a(1, 2)*a(2, 1)]
     106            0 :          det = DOT_PRODUCT(a(:, 1), b(1, :))
     107            0 :          IF (ABS(det) < cell_tol) THEN
     108            0 :             CPABORT('Singular snapshot cell')
     109              :          END IF
     110            0 :          b(:, :) = b/det
     111              :       END ASSOCIATE
     112            0 :       READ (unit, *, IOSTAT=ios) state%periodic
     113            0 :       CPASSERT(ios == 0)
     114            0 :       IF (ANY(state%periodic < 0) .OR. ANY(state%periodic > 1)) THEN
     115            0 :          CPABORT('Invalid snapshot periodicity')
     116              :       END IF
     117              :       offset = 0
     118            0 :       DO ia = 1, nat
     119            0 :          ASSOCIATE (atom => state%atoms(ia))
     120            0 :             READ (unit, *, IOSTAT=ios) ix, atom%kind, ns, atom%count
     121            0 :             CPASSERT(ios == 0)
     122            0 :             IF (ix /= ia .OR. MIN(atom%kind, ns, atom%count) < 1) THEN
     123            0 :                CPABORT('Invalid snapshot atom')
     124              :             END IF
     125            0 :             READ (unit, *, IOSTAT=ios) atom%center
     126            0 :             CPASSERT(ios == 0)
     127            0 :             IF (.NOT. ALL(ieee_is_finite(atom%center))) THEN
     128            0 :                CPABORT('Nonfinite atom center')
     129              :             END IF
     130            0 :             ALLOCATE (atom%shells(ns), covered(atom%count))
     131            0 :             covered(:) = .FALSE.
     132            0 :             DO iset = 1, ns
     133            0 :                ASSOCIATE (s => atom%shells(iset))
     134            0 :                   READ (unit, *, IOSTAT=ios) first, s%count, np, nc, s%lmin, s%radius
     135            0 :                   CPASSERT(ios == 0)
     136            0 :                   IF (MIN(first, s%count, np, nc) < 1 .OR. s%lmin < 0) THEN
     137            0 :                      CPABORT('Invalid Gaussian shell')
     138              :                   END IF
     139            0 :                   IF (.NOT. ieee_is_finite(s%radius) .OR. s%radius <= 0) THEN
     140            0 :                      CPABORT('Invalid shell screening radius')
     141              :                   END IF
     142            0 :                   IF (first + s%count - 1 > atom%count) THEN
     143            0 :                      CPABORT('Shell AO range exceeds atom')
     144              :                   END IF
     145            0 :                   IF (ANY(covered(first:first + s%count - 1))) THEN
     146            0 :                      CPABORT('Overlapping shell AO ranges')
     147              :                   END IF
     148            0 :                   covered(first:first + s%count - 1) = .TRUE.
     149            0 :                   s%lmax = 0
     150            0 :                   DO WHILE ((s%lmax + 1)*(s%lmax + 2)*(s%lmax + 3)/6 < nc)
     151            0 :                      s%lmax = s%lmax + 1
     152              :                   END DO
     153            0 :                   IF ((s%lmax + 1)*(s%lmax + 2)*(s%lmax + 3)/6 /= nc .OR. s%lmin > s%lmax) THEN
     154            0 :                      CPABORT('Invalid Cartesian Gaussian count')
     155              :                   END IF
     156            0 :                   CALL init_orbital_pointers(s%lmax + 1)
     157            0 :                   s%first = offset + first
     158            0 :                   s%center(:) = atom%center
     159            0 :                   ALLOCATE (s%exponent(np), s%radii(np), s%contraction(nc*np, s%count), row(s%count))
     160            0 :                   DO ip = 1, np
     161            0 :                      READ (unit, *, IOSTAT=ios) s%exponent(ip), s%radii(ip)
     162            0 :                      CPASSERT(ios == 0)
     163            0 :                      IF (.NOT. ieee_is_finite(s%exponent(ip)) .OR. .NOT. ieee_is_finite(s%radii(ip))) THEN
     164            0 :                         CPABORT('Nonfinite primitive Gaussian')
     165              :                      END IF
     166            0 :                      IF (s%exponent(ip) <= 0 .OR. s%radii(ip) <= 0) THEN
     167            0 :                         CPABORT('Invalid primitive Gaussian')
     168              :                      END IF
     169            0 :                      DO ic = 1, nc
     170            0 :                         READ (unit, *, IOSTAT=ios) powers, row
     171            0 :                         CPASSERT(ios == 0)
     172            0 :                         refpowers(:) = indco(:, ic)
     173            0 :                         IF (ANY(powers /= refpowers)) THEN
     174            0 :                            CPABORT('Unexpected Cartesian Gaussian ordering')
     175              :                         END IF
     176            0 :                         IF (.NOT. ALL(ieee_is_finite(row))) THEN
     177            0 :                            CPABORT('Nonfinite contraction')
     178              :                         END IF
     179            0 :                         s%contraction((ip - 1)*nc + ic, :) = row
     180              :                      END DO
     181              :                   END DO
     182            0 :                   DEALLOCATE (row)
     183              :                END ASSOCIATE
     184              :             END DO
     185            0 :             IF (.NOT. ALL(covered)) THEN
     186            0 :                CPABORT('Incomplete atom AO coverage')
     187              :             END IF
     188            0 :             DEALLOCATE (covered)
     189            0 :             offset = offset + atom%count
     190              :          END ASSOCIATE
     191              :       END DO
     192            0 :       IF (offset /= state%nao) THEN
     193            0 :          CPABORT('Snapshot AO count mismatch')
     194              :       END IF
     195            0 :       DO p = 1, nk
     196            0 :          READ (unit, *, IOSTAT=ios) ix, k
     197            0 :          CPASSERT(ios == 0)
     198            0 :          IF (ix /= p .OR. .NOT. ALL(ieee_is_finite(k))) THEN
     199            0 :             CPABORT('Invalid snapshot k-point')
     200              :          END IF
     201            0 :          READ (unit, *, IOSTAT=ios) energies
     202            0 :          CPASSERT(ios == 0)
     203            0 :          IF (.NOT. ALL(ieee_is_finite(energies))) THEN
     204            0 :             CPABORT('Nonfinite snapshot spectrum')
     205              :          END IF
     206            0 :          IF (ANY(energies(2:) < energies(:nt - 1))) THEN
     207            0 :             CPABORT('Unordered snapshot spectrum')
     208              :          END IF
     209            0 :          IF (p == point) THEN
     210            0 :             state%k(:) = k
     211            0 :             state%energies(:) = energies
     212              :          END IF
     213            0 :          DO j = 1, state%rank
     214            0 :             DO i = 1, state%nao*state%nspin
     215            0 :                READ (unit, *, IOSTAT=ios) pair
     216            0 :                CPASSERT(ios == 0)
     217            0 :                IF (.NOT. ALL(ieee_is_finite(pair))) THEN
     218            0 :                   CPABORT('Nonfinite state coefficient')
     219              :                END IF
     220            0 :                IF (p == point) state%coefficients(i, j) = CMPLX(pair(1), pair(2), dp)
     221              :             END DO
     222              :          END DO
     223              :       END DO
     224              :       DO
     225            0 :          READ (unit, '(A)', IOSTAT=ios) header
     226            0 :          IF (ios < 0) EXIT
     227            0 :          IF (ios /= 0 .OR. LEN_TRIM(header) /= 0) THEN
     228            0 :             CPABORT('Unexpected trailing snapshot content')
     229              :          END IF
     230              :       END DO
     231            0 :       CALL close_file(unit)
     232            0 :    END SUBROUTINE read_snapshot
     233              : 
     234              : ! **************************************************************************************************
     235              : !> \brief Apply screened Gaussian cross-geometry operator by atom blocks, without a dense AO matrix.
     236              : !> \param a Left physical state
     237              : !> \param b Right physical state
     238              : !> \param ka Unwrapped left fractional k-point
     239              : !> \param kb Unwrapped right fractional k-point
     240              : !> \param overlap Selected-state overlap including all spinor components
     241              : ! **************************************************************************************************
     242            8 :    SUBROUTINE snapshot_overlap(a, b, ka, kb, overlap)
     243              :       TYPE(snapshot_type), INTENT(IN)                    :: a, b
     244              :       REAL(KIND=dp), INTENT(IN)                          :: ka(3), kb(3)
     245              :       COMPLEX(KIND=dp), INTENT(OUT)                      :: overlap(:, :)
     246              : 
     247            8 :       COMPLEX(KIND=dp) :: part(SIZE(overlap, 1), SIZE(overlap, 2))
     248              :       INTEGER                                            :: ia
     249              : 
     250           24 :       IF (ANY(SHAPE(overlap) /= [a%rank, b%rank])) THEN
     251            0 :          CPABORT('Incorrect overlap output dimensions')
     252              :       END IF
     253            8 :       IF (a%nspin /= b%nspin .OR. a%channel /= b%channel .OR. a%rank /= b%rank) THEN
     254            0 :          CPABORT('Changed spin or rank')
     255              :       END IF
     256           16 :       IF (ANY(a%bands /= b%bands)) THEN
     257            0 :          CPABORT('Changed selected band indices')
     258              :       END IF
     259            8 :       IF (SIZE(a%atoms) /= SIZE(b%atoms) .OR. a%nao /= b%nao) THEN
     260            0 :          CPABORT('Changed atom or AO count')
     261              :       END IF
     262          136 :       IF (MAXVAL(ABS(a%cell - b%cell)) > cell_tol .OR. ANY(a%periodic /= b%periodic)) THEN
     263            0 :          CPABORT('Changed cell')
     264              :       END IF
     265           64 :       IF (.NOT. ALL(ieee_is_finite(ka)) .OR. .NOT. ALL(ieee_is_finite(kb))) THEN
     266            0 :          CPABORT('Nonfinite k-point')
     267              :       END IF
     268           56 :       IF (MAXVAL(ABS(ka - a%k - ANINT(ka - a%k))) > cell_tol .OR. &
     269              :           MAXVAL(ABS(kb - b%k - ANINT(kb - b%k))) > cell_tol) THEN
     270            0 :          CPABORT('Frame k-point does not match request')
     271              :       END IF
     272           24 :       overlap(:, :) = 0.0_dp
     273            8 : !$OMP PARALLEL DO DEFAULT(NONE) SHARED(A,b,ka,kb) PRIVATE(ia,part) REDUCTION(+:overlap) SCHEDULE(DYNAMIC)
     274              :       DO ia = 1, SIZE(a%atoms)
     275              :          CALL atom_overlap(a, b, ia, ka, kb, part)
     276              :          overlap(:, :) = overlap + part
     277              :       END DO
     278              : !$OMP END PARALLEL DO
     279            8 :    END SUBROUTINE snapshot_overlap
     280              : 
     281              : ! **************************************************************************************************
     282              : !> \brief Screened atom-row contribution using CP2K's cossin primitive integral recurrence.
     283              : !> \param a Left physical state
     284              : !> \param b Right physical state
     285              : !> \param ia Left atom whose AO rows are contracted
     286              : !> \param ka Unwrapped left fractional k-point
     287              : !> \param kb Unwrapped right fractional k-point
     288              : !> \param atom_link This atom's contribution to the selected-state overlap
     289              : ! **************************************************************************************************
     290            8 :    SUBROUTINE atom_overlap(a, b, ia, ka, kb, atom_link)
     291              :       TYPE(snapshot_type), INTENT(IN)                    :: a, b
     292              :       INTEGER, INTENT(IN)                                :: ia
     293              :       REAL(KIND=dp), INTENT(IN)                          :: ka(3), kb(3)
     294              :       COMPLEX(KIND=dp), INTENT(OUT)                      :: atom_link(:, :)
     295              : 
     296              :       COMPLEX(KIND=dp)                                   :: phase
     297            8 :       COMPLEX(KIND=dp), ALLOCATABLE                      :: block(:, :), oc(:, :, :)
     298              :       INTEGER                                            :: af, bf, hi(3), i, ib, image(3), j, k, &
     299              :                                                             lo(3), nc_a, nc_b, s, sa, sb
     300              :       REAL(KIND=dp)                                      :: bounds(3), cutoff, disp(3), q(3), rb(3)
     301            8 :       REAL(KIND=dp), ALLOCATABLE                         :: cosine(:, :), sine(:, :)
     302              : 
     303           64 :       q(:) = 2.0_dp*pi*MATMUL(TRANSPOSE(a%inverse), kb - ka)
     304           24 :       atom_link(:, :) = 0.0_dp
     305           16 :       DO sa = 1, SIZE(a%atoms(ia)%shells)
     306            8 :          ASSOCIATE (left => a%atoms(ia)%shells(sa))
     307           40 :             ALLOCATE (oc(left%count, b%rank, a%nspin))
     308            8 :             oc(:, :, :) = 0.0_dp
     309            8 :             nc_a = SIZE(left%contraction, 1)
     310           16 :             DO ib = 1, SIZE(b%atoms)
     311           24 :                DO sb = 1, SIZE(b%atoms(ib)%shells)
     312            8 :                   ASSOCIATE (right => b%atoms(ib)%shells(sb))
     313            8 :                      cutoff = left%radius + right%radius
     314          128 :                      disp(:) = MATMUL(a%inverse, right%center - left%center)
     315           32 :                      DO i = 1, 3
     316          104 :                         bounds(i) = cutoff*NORM2(a%inverse(i, :))
     317              :                      END DO
     318           32 :                      lo(:) = CEILING(-disp - bounds)
     319           32 :                      hi(:) = FLOOR(-disp + bounds)
     320           80 :                      WHERE (a%periodic == 0)
     321              :                         lo = 0
     322              :                         hi = 0
     323              :                      END WHERE
     324            8 :                      nc_b = SIZE(right%contraction, 1)
     325           72 :                      ALLOCATE (cosine(nc_a, nc_b), sine(nc_a, nc_b), block(left%count, right%count))
     326            8 :                      block(:, :) = 0.0_dp
     327           16 :                      DO k = lo(3), hi(3)
     328           24 :                         DO j = lo(2), hi(2)
     329           24 :                            DO i = lo(1), hi(1)
     330           32 :                               image(:) = [i, j, k]
     331          152 :                               rb(:) = right%center + MATMUL(a%cell, REAL(image, dp))
     332           32 :                               IF (NORM2(rb - left%center) > cutoff) CYCLE
     333              :                               CALL cossin(left%lmax, SIZE(left%exponent), left%exponent, left%radii, left%lmin, &
     334              :                                           right%lmax, SIZE(right%exponent), right%exponent, right%radii, right%lmin, &
     335            8 :                                           left%center, rb, q, cosine, sine)
     336           32 :                               phase = EXP(CMPLX(0.0_dp, 2.0_dp*pi*DOT_PRODUCT(kb, REAL(image, dp)), dp))
     337           32 :                               block(:, :) = block + phase*MATMUL(TRANSPOSE(left%contraction), &
     338          200 :                                                                  MATMUL(CMPLX(cosine, -sine, dp), right%contraction))
     339              :                            END DO
     340              :                         END DO
     341              :                      END DO
     342           16 :                      DO s = 1, a%nspin
     343            8 :                         bf = right%first + (s - 1)*b%nao
     344           80 :                         oc(:, :, s) = oc(:, :, s) + MATMUL(block, b%coefficients(bf:bf + right%count - 1, :))
     345              :                      END DO
     346           16 :                      DEALLOCATE (cosine, sine, block)
     347              :                   END ASSOCIATE
     348              :                END DO
     349              :             END DO
     350           16 :             DO s = 1, a%nspin
     351            8 :                af = left%first + (s - 1)*a%nao
     352           80 :                atom_link(:, :) = atom_link + MATMUL(CONJG(TRANSPOSE(a%coefficients(af:af + left%count - 1, :))), oc(:, :, s))
     353              :             END DO
     354           16 :             DEALLOCATE (oc)
     355              :          END ASSOCIATE
     356              :       END DO
     357            8 :    END SUBROUTINE atom_overlap
     358              : 
     359              : ! **************************************************************************************************
     360              : !> \brief Check AO-metric normalization and separation at every selected/excluded boundary.
     361              : !> \param a Physical frame to validate
     362              : !> \param metric_tol Maximum entrywise error in C^dagger S C minus identity
     363              : !> \param gap_tol Minimum sampled selected/excluded-band separation in hartree
     364              : !> \param metric_error Observed normalization error
     365              : !> \param gap Smallest sampled selected/excluded-band separation in hartree
     366              : ! **************************************************************************************************
     367            2 :    SUBROUTINE check_snapshot(a, metric_tol, gap_tol, metric_error, gap)
     368              :       TYPE(snapshot_type), INTENT(IN)                    :: a
     369              :       REAL(KIND=dp), INTENT(IN)                          :: metric_tol, gap_tol
     370              :       REAL(KIND=dp), INTENT(OUT)                         :: metric_error, gap
     371              : 
     372              :       INTEGER                                            :: i
     373            4 :       LOGICAL                                            :: selected(SIZE(a%energies))
     374            4 :       COMPLEX(KIND=dp)                                   :: metric(a%rank, a%rank)
     375              : 
     376            2 :       CALL snapshot_overlap(a, a, a%k, a%k, metric)
     377            4 :       DO i = 1, a%rank
     378            4 :          metric(i, i) = metric(i, i) - 1.0_dp
     379              :       END DO
     380            6 :       metric_error = MAXVAL(ABS(metric))
     381            2 :       IF (.NOT. ieee_is_finite(metric_error) .OR. metric_error > metric_tol) THEN
     382            0 :          CPABORT('Frame is not AO-metric normalized')
     383              :       END IF
     384            6 :       selected(:) = .FALSE.
     385            6 :       selected(a%bands) = .TRUE.
     386            2 :       gap = HUGE(1.0_dp)
     387            4 :       DO i = 1, SIZE(a%energies) - 1
     388            4 :          IF (selected(i) .NEQV. selected(i + 1)) gap = MIN(gap, a%energies(i + 1) - a%energies(i))
     389              :       END DO
     390            2 :       IF (gap == HUGE(1.0_dp) .OR. gap <= gap_tol) THEN
     391            0 :          CPABORT('No verified sampled subspace isolation')
     392              :       END IF
     393            2 :    END SUBROUTINE check_snapshot
     394              : 
     395              : ! **************************************************************************************************
     396              : !> \brief Check explicitly prescribed atom permutation, periodic translations and basis at a seam.
     397              : !> \param a Start frame
     398              : !> \param b Endpoint frame
     399              : !> \param permutation One-based endpoint-atom to start-atom map
     400              : !> \param tolerance Absolute tolerance for geometry and basis agreement
     401              : ! **************************************************************************************************
     402            2 :    SUBROUTINE check_snapshot_seam(a, b, permutation, tolerance)
     403              :       TYPE(snapshot_type), INTENT(IN)                    :: a, b
     404              :       INTEGER, INTENT(IN)                                :: permutation(:)
     405              :       REAL(KIND=dp), INTENT(IN)                          :: tolerance
     406              : 
     407              :       INTEGER                                            :: i, j, n, s
     408              :       REAL(KIND=dp)                                      :: delta(3), shift(3)
     409              : 
     410            2 :       n = SIZE(a%atoms)
     411            2 :       IF (SIZE(b%atoms) /= n .OR. SIZE(permutation) /= n) THEN
     412            0 :          CPABORT('Seam atom count mismatch')
     413              :       END IF
     414            8 :       IF (ANY(permutation < 1) .OR. ANY(permutation > n)) THEN
     415            0 :          CPABORT('Invalid seam permutation')
     416              :       END IF
     417            4 :       DO i = 1, n
     418            6 :          IF (COUNT(permutation == i) /= 1) THEN
     419            0 :             CPABORT('Seam atom map is not a permutation')
     420              :          END IF
     421              :       END DO
     422           34 :       IF (MAXVAL(ABS(a%cell - b%cell)) > tolerance .OR. ANY(a%periodic /= b%periodic)) THEN
     423            0 :          CPABORT('Seam cell mismatch')
     424              :       END IF
     425            4 :       DO i = 1, n
     426            2 :          j = permutation(i)
     427            2 :          ASSOCIATE (left => a%atoms(j), right => b%atoms(i))
     428            2 :             IF (left%kind /= right%kind .OR. left%count /= right%count) THEN
     429            0 :                CPABORT('Seam changes atom kind or AO count')
     430              :             END IF
     431            2 :             IF (SIZE(left%shells) /= SIZE(right%shells)) THEN
     432            0 :                CPABORT('Seam changes basis')
     433              :             END IF
     434            8 :             delta(:) = right%center - left%center
     435           32 :             shift(:) = ANINT(MATMUL(a%inverse, delta))
     436            8 :             WHERE (a%periodic == 0) shift = 0.0_dp
     437           32 :             IF (MAXVAL(ABS(delta - MATMUL(a%cell, shift))) > tolerance) THEN
     438            0 :                CPABORT('Geometry does not close at seam')
     439              :             END IF
     440            6 :             DO s = 1, SIZE(left%shells)
     441            2 :                ASSOCIATE (x => left%shells(s), y => right%shells(s))
     442            2 :                   IF (x%count /= y%count .OR. x%lmin /= y%lmin .OR. x%lmax /= y%lmax) THEN
     443            0 :                      CPABORT('Seam changes shell')
     444              :                   END IF
     445            6 :                   IF (ANY(SHAPE(x%contraction) /= SHAPE(y%contraction))) THEN
     446            0 :                      CPABORT('Seam changes contractions')
     447              :                   END IF
     448            2 :                   IF (SIZE(x%exponent) /= SIZE(y%exponent)) THEN
     449            0 :                      CPABORT('Seam changes primitives')
     450              :                   END IF
     451            4 :                   IF (MAXVAL(ABS(x%radii - y%radii)) > tolerance .OR. ABS(x%radius - y%radius) > tolerance) THEN
     452            0 :                      CPABORT('Seam changes Gaussian screening radii')
     453              :                   END IF
     454            8 :                   IF (MAXVAL(ABS(x%exponent - y%exponent)) > tolerance .OR. &
     455            2 :                       MAXVAL(ABS(x%contraction - y%contraction)) > tolerance) THEN
     456            0 :                      CPABORT('Seam changes physical basis')
     457              :                   END IF
     458              :                END ASSOCIATE
     459              :             END DO
     460              :          END ASSOCIATE
     461              :       END DO
     462            2 :    END SUBROUTINE check_snapshot_seam
     463           24 : END MODULE topology_snapshot
        

Generated by: LCOV version 2.0-1