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

            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 streaming mesh analysis with physical endpoint sewing.
      10              : ! **************************************************************************************************
      11              : MODULE topology_phason_analysis
      12              :    USE cp_files,                        ONLY: close_file,&
      13              :                                               open_file
      14              :    USE ieee_arithmetic,                 ONLY: ieee_is_finite
      15              :    USE iso_fortran_env,                 ONLY: int64
      16              :    USE kinds,                           ONLY: default_path_length,&
      17              :                                               dp
      18              :    USE topology_curvature,              ONLY: curvature_density,&
      19              :                                               link_plaquette,&
      20              :                                               polar_link
      21              :    USE topology_snapshot,               ONLY: check_snapshot,&
      22              :                                               check_snapshot_seam,&
      23              :                                               read_snapshot,&
      24              :                                               snapshot_overlap,&
      25              :                                               snapshot_type
      26              : #include "./base/base_uses.f90"
      27              : 
      28              :    IMPLICIT NONE
      29              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'topology_phason_analysis'
      30              :    PRIVATE
      31              :    TYPE :: phason_result_type
      32              :       INTEGER :: dimension = 0, rank = 0, SHAPE(4) = 0, nat = 0, point = 0
      33              :       REAL(KIND=dp) :: value = 0.0_dp, origin(3) = 0.0_dp, directions(3, 4) = 0.0_dp
      34              :       CHARACTER(LEN=default_path_length) :: first_file = ''
      35              :    END TYPE phason_result_type
      36              :    PUBLIC :: analyze_mesh, phason_result_type, compare_refinements
      37              : 
      38              : CONTAINS
      39              : 
      40              : ! **************************************************************************************************
      41              : !> \brief Flatten a zero-based index with the first coordinate varying fastest.
      42              : !> \param index Zero-based coordinate along each axis
      43              : !> \param shape Number of entries along each axis
      44              : !> \return One-based linear index
      45              : ! **************************************************************************************************
      46            0 :    INTEGER FUNCTION flat(index, shape) RESULT(value)
      47              :       INTEGER, INTENT(IN)                                :: index(:), SHAPE(:)
      48              : 
      49              :       INTEGER                                            :: d, stride
      50              : 
      51            0 :       value = 1
      52            0 :       stride = 1
      53            0 :       DO d = 1, SIZE(shape)
      54            0 :          value = value + INDEX(d)*stride
      55            0 :          stride = stride*SHAPE(d)
      56              :       END DO
      57            0 :    END FUNCTION flat
      58              : 
      59              : ! **************************************************************************************************
      60              : !> \brief Inverse of flat; first coordinate varies fastest.
      61              : !> \param value One-based linear index
      62              : !> \param shape Number of entries along each axis
      63              : !> \param index Zero-based coordinate along each axis
      64              : ! **************************************************************************************************
      65            0 :    SUBROUTINE unflat(value, shape, index)
      66              :       INTEGER, INTENT(IN)                                :: value, SHAPE(:)
      67              :       INTEGER, INTENT(OUT)                               :: index(:)
      68              : 
      69              :       INTEGER                                            :: d, remainder
      70              : 
      71            0 :       remainder = value - 1
      72            0 :       DO d = 1, SIZE(shape)
      73            0 :          INDEX(d) = MOD(remainder, SHAPE(d))
      74            0 :          remainder = remainder/SHAPE(d)
      75              :       END DO
      76            0 :    END SUBROUTINE unflat
      77              : 
      78              : ! **************************************************************************************************
      79              : !> \brief Build physical links or read reference links; stream plaquettes through an anonymous file.
      80              : !> \param path Manifest or reference-link filename
      81              : !> \param physical True for physical STATE_EXPORT frames, false for reference links
      82              : !> \param report Mesh metadata and unrounded Chern estimate for refinement comparison
      83              : !> \param io_unit Unit receiving mesh diagnostics
      84              : ! **************************************************************************************************
      85            0 :    SUBROUTINE analyze_mesh(path, physical, report, io_unit)
      86              :       CHARACTER(LEN=*), INTENT(IN) :: path
      87              :       LOGICAL, INTENT(IN) :: physical
      88              :       TYPE(phason_result_type), INTENT(OUT) :: report
      89              :       INTEGER, INTENT(IN) :: io_unit
      90              :       CHARACTER(LEN=256) :: header
      91            0 :       CHARACTER(LEN=default_path_length), ALLOCATABLE :: files(:)
      92              :       INTEGER :: input, store, stat, dim, rank, nvertex, nframe, nat, vertex, axis, mu, nu, p, i, j, na, nb, nbase, idx, record_size
      93            0 :       INTEGER, ALLOCATABLE :: shape(:), extended(:), INDEX(:), next(:), zero(:), points(:), permutation(:, :)
      94            0 :       LOGICAL, ALLOCATABLE :: checked(:)
      95            0 :       REAL(KIND=dp), ALLOCATABLE :: offsets(:), directions(:, :)
      96              :       REAL(KIND=dp) :: origin(3), ka(3), kb(3), tol_metric, tol_gap, tol_sv, phase_limit, minimum, local_min, &
      97              :                        max_metric, min_gap, err, gap, value, contribution, max_phase, local_phase, seam_error, &
      98              :                        valence_max, conduction_min, energy_shift, compensation, y, t
      99            0 :       COMPLEX(KIND=dp), ALLOCATABLE :: raw(:, :), link(:, :), sewing(:, :), u(:, :, :), pqs(:, :, :)
     100            0 :       TYPE(snapshot_type), ALLOCATABLE :: a, b, base
     101              :       LOGICAL :: common_reference, prefix
     102              :       INTEGER(KIND=int64) :: total
     103              : 
     104            0 :       CALL open_file(path, unit_number=input, file_status='OLD', file_action='READ')
     105            0 :       READ (input, '(A)', IOSTAT=stat) header
     106            0 :       CPASSERT(stat == 0)
     107            0 :       IF (physical) THEN
     108            0 :          IF (TRIM(header) /= 'CP2K_PHASON_MESH 1') THEN
     109            0 :             CPABORT('Expected CP2K_PHASON_MESH 1')
     110              :          END IF
     111              :       ELSE
     112            0 :          IF (TRIM(header) /= 'CP2K_TOPOLOGY_LINKS 1') THEN
     113            0 :             CPABORT('Expected CP2K_TOPOLOGY_LINKS 1')
     114              :          END IF
     115              :       END IF
     116            0 :       READ (input, *, IOSTAT=stat) dim, rank
     117            0 :       CPASSERT(stat == 0)
     118            0 :       IF ((dim /= 2 .AND. dim /= 4) .OR. rank < 1) THEN
     119            0 :          CPABORT('Invalid mesh dimension or rank')
     120              :       END IF
     121            0 :       ALLOCATE (SHAPE(dim), extended(dim), INDEX(dim), next(dim), zero(dim))
     122            0 :       READ (input, *, IOSTAT=stat) shape
     123            0 :       CPASSERT(stat == 0)
     124            0 :       IF (ANY(shape < 3)) THEN
     125            0 :          CPABORT('At least three samples per mesh axis are required')
     126              :       END IF
     127            0 :       total = 1_int64
     128            0 :       DO axis = 1, dim
     129            0 :          IF (total > INT(HUGE(1), int64)/INT(SHAPE(axis) + 1, int64)) THEN
     130            0 :             CPABORT('Mesh index overflow')
     131              :          END IF
     132            0 :          total = total*INT(SHAPE(axis) + 1, int64)
     133              :       END DO
     134            0 :       extended(:) = shape + 1
     135            0 :       nvertex = PRODUCT(shape)
     136            0 :       nframe = INT(total)
     137            0 :       report%dimension = dim
     138            0 :       report%rank = rank
     139            0 :       report%shape(1:dim) = shape
     140            0 :       READ (input, *, IOSTAT=stat) tol_metric, tol_gap, tol_sv, phase_limit
     141            0 :       CPASSERT(stat == 0)
     142            0 :       IF (.NOT. ALL(ieee_is_finite([tol_metric, tol_gap, tol_sv, phase_limit]))) THEN
     143            0 :          CPABORT('Nonfinite tolerances')
     144              :       END IF
     145            0 :       IF (MIN(tol_metric, tol_gap, tol_sv, phase_limit) <= 0.0_dp .OR. MAX(tol_metric, tol_sv) >= 1.0_dp .OR. &
     146              :           phase_limit >= ACOS(-1.0_dp)) THEN
     147            0 :          CPABORT('Invalid mesh tolerances')
     148              :       END IF
     149            0 :       ALLOCATE (raw(rank, rank), link(rank, rank), sewing(rank, rank), u(rank, rank, 4), pqs(rank, rank, dim*(dim - 1)/2))
     150              :       ! Anonymous scratch storage avoids persistent, stale link caches.
     151              :       CALL open_file('', unit_number=store, file_status='SCRATCH', file_access='STREAM', &
     152            0 :                      file_form='UNFORMATTED', file_action='READWRITE')
     153            0 :       INQUIRE (IOLENGTH=record_size) link
     154            0 :       minimum = HUGE(1.0_dp)
     155            0 :       max_metric = 0.0_dp
     156            0 :       min_gap = HUGE(1.0_dp)
     157            0 :       seam_error = 0.0_dp
     158            0 :       valence_max = -HUGE(1.0_dp)
     159            0 :       conduction_min = HUGE(1.0_dp)
     160            0 :       common_reference = .FALSE.
     161            0 :       prefix = .TRUE.
     162            0 :       IF (physical) THEN
     163            0 :          ALLOCATE (a, b, base)
     164            0 :          ALLOCATE (files(nframe), points(nframe), offsets(nframe), checked(nframe), directions(3, dim))
     165            0 :          checked(:) = .FALSE.
     166            0 :          READ (input, *, IOSTAT=stat) origin
     167            0 :          CPASSERT(stat == 0)
     168            0 :          IF (.NOT. ALL(ieee_is_finite(origin))) THEN
     169            0 :             CPABORT('Nonfinite mesh origin')
     170              :          END IF
     171            0 :          DO axis = 1, dim
     172            0 :             READ (input, *, IOSTAT=stat) directions(:, axis)
     173            0 :             CPASSERT(stat == 0)
     174              :          END DO
     175            0 :          IF (.NOT. ALL(ieee_is_finite(directions))) THEN
     176            0 :             CPABORT('Nonfinite reciprocal periods')
     177              :          END IF
     178            0 :          IF (MAXVAL(ABS(directions - ANINT(directions))) > 1.e-10_dp) THEN
     179            0 :             CPABORT('Noninteger reciprocal periods')
     180              :          END IF
     181            0 :          READ (input, *, IOSTAT=stat) nat, common_reference
     182            0 :          CPASSERT(stat == 0)
     183            0 :          IF (nat < 1) THEN
     184            0 :             CPABORT('Invalid mesh atom count')
     185              :          END IF
     186            0 :          ALLOCATE (permutation(nat, dim))
     187            0 :          DO axis = 1, dim
     188            0 :             READ (input, *, IOSTAT=stat) permutation(:, axis)
     189            0 :             CPASSERT(stat == 0)
     190            0 :             IF (ANY(permutation(:, axis) < 1) .OR. ANY(permutation(:, axis) > nat)) THEN
     191            0 :                CPABORT('Invalid seam permutation')
     192              :             END IF
     193            0 :             DO idx = 1, nat
     194            0 :                IF (COUNT(permutation(:, axis) == idx) /= 1) THEN
     195            0 :                   CPABORT('Non-bijective seam map')
     196              :                END IF
     197              :             END DO
     198              :          END DO
     199            0 :          DO mu = 1, dim
     200            0 :             DO nu = mu + 1, dim
     201            0 :                IF (ANY(permutation(permutation(:, mu), nu) /= permutation(permutation(:, nu), mu))) THEN
     202            0 :                   CPABORT('The declared torus atom permutations do not commute')
     203              :                END IF
     204              :             END DO
     205              :          END DO
     206            0 :          DO idx = 1, nframe
     207            0 :             READ (input, *, IOSTAT=stat) files(idx), points(idx), offsets(idx)
     208            0 :             CPASSERT(stat == 0)
     209            0 :             IF (points(idx) < 1 .OR. .NOT. ieee_is_finite(offsets(idx))) THEN
     210            0 :                CPABORT('Invalid frame record')
     211              :             END IF
     212              :          END DO
     213            0 :          report%nat = nat
     214            0 :          report%origin(:) = origin
     215            0 :          report%directions(:, 1:dim) = directions
     216            0 :          report%first_file = files(1)
     217            0 :          report%point = points(1)
     218              :       END IF
     219            0 :       DO vertex = 1, nvertex
     220            0 :          CALL unflat(vertex, shape, index)
     221            0 :          IF (physical) THEN
     222            0 :             na = flat(index, extended)
     223            0 :             CALL frame(na, a)
     224            0 :             ka(:) = origin + MATMUL(directions, REAL(index, dp)/REAL(shape, dp))
     225              :          END IF
     226            0 :          DO axis = 1, dim
     227            0 :             IF (physical) THEN
     228            0 :                next(:) = index
     229            0 :                next(axis) = next(axis) + 1
     230            0 :                nb = flat(next, extended)
     231            0 :                CALL frame(nb, b)
     232            0 :                kb(:) = origin + MATMUL(directions, REAL(next, dp)/REAL(shape, dp))
     233            0 :                CALL snapshot_overlap(a, b, ka, kb, raw)
     234            0 :                IF (next(axis) == SHAPE(axis)) THEN
     235            0 :                   zero(:) = next
     236            0 :                   zero(axis) = 0
     237            0 :                   nbase = flat(zero, extended)
     238            0 :                   CALL frame(nbase, base)
     239            0 :                   CALL check_snapshot_seam(base, b, permutation(:, axis), tol_metric)
     240            0 :                   CALL snapshot_overlap(b, base, kb, kb, sewing)
     241              :                   ! Both endpoint frames must span the same physical subspace, not merely have full rank.
     242            0 :                   link(:, :) = MATMUL(CONJG(TRANSPOSE(sewing)), sewing)
     243            0 :                   DO idx = 1, rank
     244            0 :                      link(idx, idx) = link(idx, idx) - 1.0_dp
     245              :                   END DO
     246            0 :                   err = MAXVAL(ABS(link))
     247            0 :                   seam_error = MAX(seam_error, err)
     248            0 :                   IF (err > tol_metric) THEN
     249            0 :                      CPABORT('Endpoint selected subspaces do not close')
     250              :                   END IF
     251            0 :                   CALL polar_link(sewing, link, local_min, stat, tol_sv)
     252            0 :                   IF (stat /= 0) THEN
     253            0 :                      CPABORT('Invalid endpoint sewing link')
     254              :                   END IF
     255            0 :                   raw(:, :) = MATMUL(raw, link)
     256              :                END IF
     257              :             ELSE
     258            0 :                DO j = 1, rank
     259            0 :                   DO i = 1, rank
     260            0 :                      READ (input, *, IOSTAT=stat) ka(1:2)
     261            0 :                      CPASSERT(stat == 0)
     262            0 :                      raw(i, j) = CMPLX(ka(1), ka(2), dp)
     263              :                   END DO
     264              :                END DO
     265              :             END IF
     266            0 :             CALL polar_link(raw, link, local_min, stat, tol_sv)
     267            0 :             IF (stat /= 0) THEN
     268            0 :                CPABORT('Singular/nonfinite mesh link; refine or check the subspace')
     269              :             END IF
     270            0 :             minimum = MIN(minimum, local_min)
     271            0 :             WRITE (store, IOSTAT=stat) link
     272            0 :             CPASSERT(stat == 0)
     273              :          END DO
     274              :       END DO
     275              :       DO
     276            0 :          READ (input, '(A)', IOSTAT=stat) header
     277            0 :          IF (stat < 0) EXIT
     278            0 :          IF (stat /= 0 .OR. LEN_TRIM(header) /= 0) THEN
     279            0 :             CPABORT('Unexpected trailing mesh data')
     280              :          END IF
     281              :       END DO
     282            0 :       CALL close_file(input)
     283            0 :       value = 0.0_dp
     284            0 :       compensation = 0.0_dp
     285            0 :       max_phase = 0.0_dp
     286            0 :       DO vertex = 1, nvertex
     287            0 :          CALL unflat(vertex, shape, index)
     288            0 :          p = 0
     289            0 :          DO mu = 1, dim
     290            0 :             DO nu = mu + 1, dim
     291            0 :                p = p + 1
     292            0 :                CALL read_link(vertex, mu, u(:, :, 1))
     293            0 :                next(:) = index
     294            0 :                next(mu) = MOD(next(mu) + 1, SHAPE(mu))
     295            0 :                CALL read_link(flat(next, shape), nu, u(:, :, 2))
     296            0 :                next(:) = index
     297            0 :                next(nu) = MOD(next(nu) + 1, SHAPE(nu))
     298            0 :                CALL read_link(flat(next, shape), mu, u(:, :, 3))
     299            0 :                CALL read_link(vertex, nu, u(:, :, 4))
     300            0 :                CALL link_plaquette(u(:, :, 1), u(:, :, 2), u(:, :, 3), u(:, :, 4), pqs(:, :, p))
     301              :             END DO
     302              :          END DO
     303            0 :          CALL curvature_density(pqs, dim, phase_limit, contribution, local_phase, stat)
     304            0 :          IF (stat /= 0) THEN
     305            0 :             CPABORT('Unresolved/nonunitary plaquette; refine the mesh')
     306              :          END IF
     307            0 :          max_phase = MAX(max_phase, local_phase)
     308            0 :          y = contribution - compensation
     309            0 :          t = value + y
     310            0 :          compensation = (t - value) - y
     311            0 :          value = t
     312              :       END DO
     313            0 :       CALL close_file(store)
     314            0 :       report%value = value
     315            0 :       WRITE (io_unit, '(A,I0)') 'PARAMETER_DIMENSION ', dim
     316            0 :       WRITE (io_unit, '(A,*(I0,1X))') 'MESH ', shape
     317            0 :       WRITE (io_unit, '(A,ES26.17)') 'CHERN_RAW ', value
     318            0 :       WRITE (io_unit, '(A,ES26.17)') 'MINIMUM_LINK_SINGULAR_VALUE ', minimum
     319            0 :       WRITE (io_unit, '(A,ES26.17)') 'MAXIMUM_PLAQUETTE_PHASE ', max_phase
     320            0 :       IF (physical) THEN
     321            0 :          WRITE (io_unit, '(A,ES26.17)') 'MAXIMUM_METRIC_ERROR ', max_metric
     322            0 :          WRITE (io_unit, '(A,ES26.17)') 'MAXIMUM_SEAM_ERROR ', seam_error
     323            0 :          WRITE (io_unit, '(A,ES26.17)') 'MINIMUM_DIRECT_GAP_HA ', min_gap
     324            0 :          IF (common_reference .AND. prefix) THEN
     325            0 :             WRITE (io_unit, '(A,ES26.17)') 'SAMPLED_INDIRECT_GAP_HA ', conduction_min - valence_max
     326              :          ELSE
     327            0 :             WRITE (io_unit, '(A)') 'INDIRECT_GAP_NOT_EVALUATED: no asserted common energy reference or not a lowest-band prefix'
     328              :          END IF
     329              :       END IF
     330            0 :       WRITE (io_unit, '(A)') 'MESH_CONVERGENCE_NOT_ESTABLISHED: repeat on finer meshes; retain unrounded C2 values.'
     331              :    CONTAINS
     332              : ! **************************************************************************************************
     333              : !> \brief Read one frame and update validation diagnostics on its first use.
     334              : !> \param number One-based frame index in the extended mesh
     335              : !> \param state Physical frame with selected coefficients
     336              : ! **************************************************************************************************
     337            0 :       SUBROUTINE frame(number, state)
     338              :       INTEGER, INTENT(IN)                                :: number
     339              :       TYPE(snapshot_type), INTENT(OUT)                   :: state
     340              : 
     341              :       INTEGER                                            :: iband
     342              : 
     343            0 :          CALL read_snapshot(TRIM(files(number)), points(number), state)
     344            0 :          IF (state%rank /= rank .OR. SIZE(state%atoms) /= nat) THEN
     345            0 :             CPABORT('Frame rank/atom count differs from manifest')
     346              :          END IF
     347            0 :          IF (.NOT. checked(number)) THEN
     348              :             CALL check_snapshot(state, tol_metric, tol_gap, err, gap)
     349            0 :             max_metric = MAX(max_metric, err)
     350            0 :             min_gap = MIN(min_gap, gap)
     351            0 :             checked(number) = .TRUE.
     352            0 :             DO iband = 1, rank
     353            0 :                IF (state%bands(iband) /= iband) prefix = .FALSE.
     354              :             END DO
     355            0 :             IF (rank >= SIZE(state%energies)) prefix = .FALSE.
     356            0 :             IF (prefix) THEN
     357            0 :                energy_shift = offsets(number)
     358            0 :                valence_max = MAX(valence_max, state%energies(rank) + energy_shift)
     359            0 :                conduction_min = MIN(conduction_min, state%energies(rank + 1) + energy_shift)
     360              :             END IF
     361              :          END IF
     362            0 :       END SUBROUTINE frame
     363              : ! **************************************************************************************************
     364              : !> \brief Read a previously validated link from the scratch stream.
     365              : !> \param vertex_index One-based mesh vertex
     366              : !> \param direction One-based parameter axis
     367              : !> \param matrix Unitary selected-state link
     368              : ! **************************************************************************************************
     369            0 :       SUBROUTINE read_link(vertex_index, direction, matrix)
     370              :       INTEGER, INTENT(IN)                                :: vertex_index, direction
     371              :       COMPLEX(KIND=dp), INTENT(OUT)                      :: matrix(:, :)
     372              : 
     373              :       INTEGER(KIND=int64)                                :: position
     374              : 
     375            0 :          position = (INT(vertex_index - 1, int64)*INT(dim, int64) + INT(direction - 1, int64))*INT(record_size, int64) + 1_int64
     376            0 :          READ (store, POS=position, IOSTAT=stat) matrix
     377            0 :          CPASSERT(stat == 0)
     378            0 :       END SUBROUTINE read_link
     379              :    END SUBROUTINE analyze_mesh
     380              : 
     381              : ! **************************************************************************************************
     382              : !> \brief Compare two jointly refined meshes of the same declared family; never round the estimate.
     383              : !> \param coarse Coarser mesh report
     384              : !> \param fine Finer mesh report
     385              : !> \param tolerance Maximum inter-mesh difference and distance to an integer
     386              : !> \param io_unit Unit receiving convergence diagnostics
     387              : ! **************************************************************************************************
     388            0 :    SUBROUTINE compare_refinements(coarse, fine, tolerance, io_unit)
     389              :       TYPE(phason_result_type), INTENT(IN) :: coarse, fine
     390              :       REAL(KIND=dp), INTENT(IN) :: tolerance
     391              :       INTEGER, INTENT(IN) :: io_unit
     392            0 :       TYPE(snapshot_type), ALLOCATABLE :: a, b
     393            0 :       COMPLEX(KIND=dp), ALLOCATABLE :: m(:, :), metric(:, :)
     394              :       INTEGER :: dim, i
     395              :       REAL(KIND=dp) :: difference, residual
     396            0 :       IF (.NOT. ieee_is_finite(tolerance)) THEN
     397            0 :          CPABORT('Nonfinite convergence tolerance')
     398              :       END IF
     399            0 :       IF (tolerance <= 0.0_dp .OR. tolerance >= 0.5_dp) THEN
     400            0 :          CPABORT('Invalid convergence tolerance')
     401              :       END IF
     402            0 :       dim = coarse%dimension
     403            0 :       IF (fine%dimension /= dim .OR. fine%rank /= coarse%rank .OR. fine%nat /= coarse%nat) THEN
     404            0 :          CPABORT('Incompatible refinement reports')
     405              :       END IF
     406            0 :       DO i = 1, dim
     407            0 :          IF (fine%shape(i) < 2*coarse%shape(i) .OR. MOD(fine%shape(i), coarse%shape(i)) /= 0) THEN
     408            0 :             CPABORT('Refine every mesh axis by an integer factor of at least two')
     409              :          END IF
     410              :       END DO
     411            0 :       IF (MAXVAL(ABS(coarse%origin - fine%origin)) > 1.e-10_dp .OR. &
     412              :           MAXVAL(ABS(coarse%directions - fine%directions)) > 1.e-10_dp) THEN
     413            0 :          CPABORT('Refinement changes the parameter origin or reciprocal periods')
     414              :       END IF
     415            0 :       IF (coarse%nat > 0) THEN
     416            0 :          ALLOCATE (a, b)
     417            0 :          CALL read_snapshot(TRIM(coarse%first_file), coarse%point, a)
     418            0 :          CALL read_snapshot(TRIM(fine%first_file), fine%point, b)
     419            0 :          ALLOCATE (m(a%rank, a%rank), metric(a%rank, a%rank))
     420            0 :          CALL snapshot_overlap(a, b, a%k, a%k, m)
     421            0 :          metric(:, :) = MATMUL(CONJG(TRANSPOSE(m)), m)
     422            0 :          DO i = 1, a%rank
     423            0 :             metric(i, i) = metric(i, i) - 1.0_dp
     424              :          END DO
     425            0 :          IF (MAXVAL(ABS(metric)) > 1.e-7_dp) THEN
     426            0 :             CPABORT('Refinement changes the initial physical subspace')
     427              :          END IF
     428              :       END IF
     429            0 :       difference = ABS(fine%value - coarse%value)
     430            0 :       residual = ABS(fine%value - ANINT(fine%value))
     431            0 :       WRITE (io_unit, '(A,ES26.17)') 'REFINEMENT_CHANGE ', difference
     432            0 :       WRITE (io_unit, '(A,ES26.17)') 'INTEGER_RESIDUAL ', residual
     433            0 :       IF (difference > tolerance .OR. residual > tolerance) THEN
     434            0 :          CPABORT('Chern mesh refinement has not converged to the requested tolerance')
     435              :       END IF
     436            0 :       WRITE (io_unit, '(A)') 'REFINEMENT_CHECK_PASSED: numerical check, not a certificate of a bulk gap.'
     437            0 :    END SUBROUTINE compare_refinements
     438            0 : END MODULE topology_phason_analysis
        

Generated by: LCOV version 2.0-1