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