(git:08825b9)
Loading...
Searching...
No Matches
spectral_localizer.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 Dense reference spectral localizers in a nonorthogonal finite basis.
10!> \author Thomas D. Kuehne, tkuehne@cp2k.org
11! **************************************************************************************************
13 USE ieee_arithmetic, ONLY: ieee_is_finite
14#if defined(__HAS_IEEE_EXCEPTIONS)
15 USE ieee_exceptions, ONLY: ieee_all, ieee_get_halting_mode, ieee_set_halting_mode
16#endif
17 USE kinds, ONLY: dp
18
19 IMPLICIT NONE
20 PRIVATE
24
26 MODULE PROCEDURE localizer_inertia_complex, localizer_inertia_real
27 END INTERFACE localizer_inertia
28
29CONTAINS
30
31! **************************************************************************************************
32!> \brief Finite three-dimensional class AII localizer, using all three Cartesian moments.
33!> \param h Covariant physical-spin Hamiltonian, all up AOs followed by all down AOs
34!> \param s Positive physical-spin AO metric
35!> \param r Three covariant Hermitian position operators
36!> \param point Cartesian query coordinates
37!> \param energy Absolute energy query
38!> \param kappa Positive energy/length scale
39!> \param matrix_tol Relative matrix-structure tolerance
40!> \param metric_tol Minimum overlap eigenvalue ratio
41!> \param gap_tol Absolute unresolved-gap threshold
42!> \param invariant Strong local Z2 index, valid only for status zero
43!> \param gap Smallest metric-normalized singular value of the chiral block
44!> \param metric_ratio Smallest/largest physical metric eigenvalue
45!> \param status Same convention as localizer_evaluate
46! **************************************************************************************************
47 SUBROUTINE localizer_3d_evaluate(h, s, r, point, energy, kappa, matrix_tol, metric_tol, gap_tol, &
48 invariant, gap, metric_ratio, status)
49 COMPLEX(dp), INTENT(IN) :: h(:, :), s(:, :), r(:, :, :)
50 REAL(dp), INTENT(IN) :: point(3), energy, kappa, matrix_tol, &
51 metric_tol, gap_tol
52 INTEGER, INTENT(OUT) :: invariant
53 REAL(dp), INTENT(OUT) :: gap, metric_ratio
54 INTEGER, INTENT(OUT) :: status
55
56 COMPLEX(dp), PARAMETER :: ii = cmplx(0.0_dp, 1.0_dp, dp)
57
58 COMPLEX(dp), ALLOCATABLE :: block(:, :), shifted(:, :, :)
59 INTEGER :: j, n
60
61 invariant = 0
62 gap = 0.0_dp
63 metric_ratio = 0.0_dp
64 status = -1
65 n = SIZE(h, 1)
66 IF (n < 2 .OR. mod(n, 2) /= 0 .OR. SIZE(h, 2) /= n) RETURN
67 IF (any(shape(s) /= [n, n]) .OR. any(shape(r) /= [n, n, 3])) RETURN
68 IF (.NOT. all(ieee_is_finite([point, energy, kappa, matrix_tol, metric_tol, gap_tol]))) RETURN
69 IF (kappa <= 0.0_dp .OR. matrix_tol <= 0.0_dp .OR. matrix_tol >= 1.0_dp) RETURN
70 IF (metric_tol <= 0.0_dp .OR. metric_tol >= 1.0_dp .OR. gap_tol < 0.0_dp) RETURN
71 IF (.NOT. hermitian(h, matrix_tol) .OR. .NOT. hermitian(s, matrix_tol)) RETURN
72 DO j = 1, 3
73 IF (.NOT. hermitian(r(:, :, j), matrix_tol)) RETURN
74 END DO
75 status = -3
76 IF (.NOT. self_dual(h, matrix_tol) .OR. .NOT. self_dual(s, matrix_tol)) RETURN
77 DO j = 1, 3
78 IF (.NOT. self_dual(r(:, :, j), matrix_tol)) RETURN
79 END DO
80 ALLOCATE (block(2*n, 2*n), shifted(n, n, 3))
81 DO j = 1, 3
82 shifted(:, :, j) = kappa*(r(:, :, j) - point(j)*s)
83 END DO
84 ! A = I_aux (H-E S) - i sum_j sigma_j (kappa (X_j-x_j S)).
85 block(:n, :n) = h - energy*s - ii*shifted(:, :, 3)
86 block(n + 1:, n + 1:) = h - energy*s + ii*shifted(:, :, 3)
87 block(:n, n + 1:) = -ii*shifted(:, :, 1) - shifted(:, :, 2)
88 block(n + 1:, :n) = -ii*shifted(:, :, 1) + shifted(:, :, 2)
89 CALL localizer_3d_matrix_evaluate(block, s, matrix_tol, metric_tol, gap_tol, invariant, gap, metric_ratio, status)
90 END SUBROUTINE localizer_3d_evaluate
91
92! **************************************************************************************************
93!> \brief Metric gap and real determinant sign of a three-dimensional AII chiral block.
94!> \param block Covariant A=I_aux(H-E S)-i kappa sigma.(X-x S); auxiliary index outermost
95!> \param s Positive physical-spin AO metric
96!> \param matrix_tol Relative structure tolerance
97!> \param metric_tol Minimum overlap eigenvalue ratio
98!> \param gap_tol Absolute unresolved-gap threshold
99!> \param invariant Zero for positive determinant, one for negative; valid only on success
100!> \param gap Minimum absolute generalized eigenvalue of [0,A;A^dagger,0]
101!> \param metric_ratio Smallest/largest physical metric eigenvalue
102!> \param status Same convention as localizer_evaluate
103!> \note A positive atomic Hamiltonian gives positive determinant in every nonsingular AO basis.
104!> Unlike the 2D Pfaffian, no basis-orientation correction is required for this determinant.
105! **************************************************************************************************
106 SUBROUTINE localizer_3d_matrix_evaluate(block, s, matrix_tol, metric_tol, gap_tol, &
107 invariant, gap, metric_ratio, status)
108 COMPLEX(dp), INTENT(IN) :: block(:, :), s(:, :)
109 REAL(dp), INTENT(IN) :: matrix_tol, metric_tol, gap_tol
110 INTEGER, INTENT(OUT) :: invariant, status
111 REAL(dp), INTENT(OUT) :: gap, metric_ratio
112 COMPLEX(dp), ALLOCATABLE :: pencil(:, :), metric(:, :)
113 COMPLEX(dp) :: value
114 REAL(dp), ALLOCATABLE :: real_block(:, :)
115 REAL(dp) :: residual, scale, metric_scale, error_budget
116 INTEGER :: n, m, q, i, j, pi, pj, si, sj, signature, determinant
117#if defined(__HAS_IEEE_EXCEPTIONS)
118 LOGICAL :: halt(size(ieee_all))
119#endif
120
121 status = -1
122 invariant = 0
123 gap = 0.0_dp
124 metric_ratio = 0.0_dp
125 n = SIZE(s, 1)
126 IF (n < 2 .OR. mod(n, 2) /= 0 .OR. SIZE(s, 2) /= n) RETURN
127 m = 2*n
128 IF (any(shape(block) /= [m, m])) RETURN
129 IF (.NOT. all(ieee_is_finite([matrix_tol, metric_tol, gap_tol]))) RETURN
130 IF (matrix_tol <= 0.0_dp .OR. matrix_tol >= 1.0_dp) RETURN
131 IF (metric_tol <= 0.0_dp .OR. metric_tol >= 1.0_dp .OR. gap_tol < 0.0_dp) RETURN
132 IF (.NOT. hermitian(s, matrix_tol)) RETURN
133 IF (.NOT. all(ieee_is_finite(real(block, dp))) .OR. .NOT. all(ieee_is_finite(aimag(block)))) RETURN
134 status = -3
135 IF (.NOT. self_dual(s, matrix_tol)) RETURN
136 ALLOCATE (real_block(m, m))
137 q = n/2
138 residual = 0.0_dp
139 ! Q=(I-i C)/sqrt(2), C=J_aux tensor J_spin: Q^dagger A Q is REAL, not skew.
140 DO j = 1, m
141 pj = (3 - (j - 1)/q)*q + modulo(j - 1, q) + 1
142 sj = merge(1, -1, j <= q .OR. j > 3*q)
143 DO i = 1, m
144 pi = (3 - (i - 1)/q)*q + modulo(i - 1, q) + 1
145 si = merge(1, -1, i <= q .OR. i > 3*q)
146 value = 0.5_dp*(block(i, j) + si*sj*block(pi, pj) + &
147 cmplx(0.0_dp, 1.0_dp, dp)*(si*block(pi, j) - sj*block(i, pj)))
148 residual = max(residual, abs(aimag(value)))
149 real_block(i, j) = real(value, dp)
150 END DO
151 END DO
152 scale = max(tiny(1.0_dp), maxval(abs(block)))
153 IF (.NOT. all(ieee_is_finite(real_block)) .OR. .NOT. ieee_is_finite(residual)) RETURN
154 IF (residual > matrix_tol*scale) RETURN
155
156 ALLOCATE (pencil(2*m, 2*m), metric(m, m))
157 pencil = 0.0_dp
158 pencil(:m, m + 1:) = block
159 pencil(m + 1:, :m) = conjg(transpose(block))
160 metric = 0.0_dp
161 metric(:n, :n) = s
162 metric(n + 1:, n + 1:) = s
163 CALL localizer_matrix_evaluate(pencil, metric, .false., matrix_tol, metric_tol, gap_tol, &
164 signature, gap, metric_ratio, status)
165 IF (status /= 0) RETURN
166 status = -4
167 IF (signature /= 0) RETURN
168 ! ||S||_max <= lambda_max(S), hence this lower bound is conservative.
169 metric_scale = metric_ratio*maxval(abs(s))
170 status = 1
171 IF (gap <= real(m, dp)*(residual + 100.0_dp*epsilon(1.0_dp)*scale)/metric_scale) RETURN
172 DEALLOCATE (pencil, metric)
173 real_block = real_block/scale
174 ! Frobenius residuals bound the spectral perturbation after metric normalization.
175 error_budget = gap*metric_scale/scale - real(m, dp)*(residual/scale + 100.0_dp*epsilon(1.0_dp))
176#if defined(__HAS_IEEE_EXCEPTIONS)
177 CALL ieee_get_halting_mode(ieee_all, halt)
178 CALL ieee_set_halting_mode(ieee_all, .false.)
179#endif
180 CALL real_determinant_sign(real_block, error_budget, determinant, status)
181#if defined(__HAS_IEEE_EXCEPTIONS)
182 CALL ieee_set_halting_mode(ieee_all, halt)
183#endif
184 IF (status /= 0) RETURN
185 invariant = (1 - determinant)/2
186 status = 0
187 END SUBROUTINE localizer_3d_matrix_evaluate
188
189! **************************************************************************************************
190!> \brief LU determinant sign with a QR fallback when pivot growth exhausts the error budget.
191!> \param matrix Scaled real nonsingular chiral block
192!> \param error_budget Maximum admissible Frobenius backward error from the independent metric gap
193!> \param determinant Sign of the determinant, only valid on success
194!> \param status Zero on success, one for unresolved numerical error, minus four for LAPACK failure
195!> \note The caller disables IEEE halting while LAPACK is active and restores it afterwards.
196! **************************************************************************************************
197 SUBROUTINE real_determinant_sign(matrix, error_budget, determinant, status)
198 REAL(dp), INTENT(IN) :: matrix(:, :), error_budget
199 INTEGER, INTENT(OUT) :: determinant, status
200
201 INTEGER :: i, info, method, n
202 INTEGER, ALLOCATABLE :: pivots(:)
203 REAL(dp) :: error, roundoff
204 REAL(dp), ALLOCATABLE :: factors(:, :), permuted(:, :), &
205 product(:, :), row(:), tau(:), work(:)
206
207 n = SIZE(matrix, 1)
208 ALLOCATE (factors(n, n), product(n, n), permuted(n, n), row(n), pivots(n), tau(n), work(64*n))
209 determinant = 0
210 status = -4
211 DO method = 1, 2
212 factors(:, :) = matrix
213 permuted(:, :) = matrix
214 IF (method == 1) THEN
215 CALL dgetrf(n, n, factors, n, pivots, info)
216 ELSE
217 CALL dgeqrf(n, n, factors, n, tau, work, SIZE(work), info)
218 END IF
219 IF (info /= 0 .OR. .NOT. all(ieee_is_finite(factors))) cycle
220 product(:, :) = factors
221 DO i = 1, n
222 product(i + 1:, i) = 0.0_dp
223 END DO
224 ! Include rounding of both the triangular product and the residual evaluation.
225 roundoff = 8.0_dp*epsilon(1.0_dp)*real(n, dp)*sqrt(sum(product**2))
226 IF (method == 1) THEN
227 CALL dtrmm('L', 'L', 'N', 'U', n, n, 1.0_dp, factors, n, product, n)
228 DO i = 1, n
229 row(:) = permuted(i, :)
230 permuted(i, :) = permuted(pivots(i), :)
231 permuted(pivots(i), :) = row
232 END DO
233 ELSE
234 CALL dormqr('L', 'N', n, n, n, factors, n, tau, product, n, work, SIZE(work), info)
235 IF (info /= 0) cycle
236 END IF
237 IF (.NOT. all(ieee_is_finite(product))) cycle
238 error = sqrt(sum((product - permuted)**2))
239 IF (method == 1) THEN
240 ! Bound cancellation in the reconstructed product by |L| |U|, not by |L U|.
241 permuted(:, :) = abs(factors)
242 product(:, :) = permuted
243 DO i = 1, n
244 product(i + 1:, i) = 0.0_dp
245 END DO
246 CALL dtrmm('L', 'L', 'N', 'U', n, n, 1.0_dp, permuted, n, product, n)
247 roundoff = 8.0_dp*epsilon(1.0_dp)*real(n, dp)*sqrt(sum(product**2))
248 END IF
249 error = error + roundoff
250 status = 1
251 IF (.NOT. ieee_is_finite(error) .OR. error >= error_budget) cycle
252 determinant = 1
253 DO i = 1, n
254 IF (factors(i, i) == 0.0_dp) EXIT
255 IF (factors(i, i) < 0.0_dp) determinant = -determinant
256 IF (method == 1) THEN
257 IF (pivots(i) /= i) determinant = -determinant
258 ELSE
259 IF (tau(i) /= 0.0_dp) determinant = -determinant
260 END IF
261 END DO
262 IF (i <= n) cycle
263 status = 0
264 RETURN
265 END DO
266 determinant = 0
267 END SUBROUTINE real_determinant_sign
268
269! **************************************************************************************************
270!> \brief Two-dimensional periodic localizer from covariant trigonometric position operators.
271!> \param h Periodic Hamiltonian, including all boundary-crossing matrix elements
272!> \param s Positive definite overlap
273!> \param cosine Covariant cos(G_j.r) matrices, not cos of projected AO positions
274!> \param sine Covariant sin(G_j.r) matrices
275!> \param origin Two dimensionless phase offsets G_j.r0
276!> \param energy Absolute reference energy
277!> \param eta Positive energy scale multiplying the torus coordinates
278!> \param aii Use physical spinful time reversal
279!> \param matrix_tol Relative structure tolerance
280!> \param metric_tol Minimum overlap eigenvalue ratio
281!> \param gap_tol Absolute unresolved-gap threshold in energy units
282!> \param invariant Index in the same orientation convention as localizer_evaluate
283!> \param gap Minimum absolute generalized eigenvalue, in energy units
284!> \param metric_ratio Smallest/largest overlap eigenvalue ratio
285!> \param status Same status convention as localizer_evaluate
286!> \note Adapted from Doll, Loring, Schulz-Baldes (2025), Eq. (1): minus eta times
287!> their localizer, conjugated by diag(I,-I), to retain our (H,X,Y) orientation.
288!> A resolved finite-matrix signature is not a certificate of bulk convergence.
289! **************************************************************************************************
290 SUBROUTINE localizer_periodic_evaluate(h, s, cosine, sine, origin, energy, eta, aii, &
291 matrix_tol, metric_tol, gap_tol, invariant, gap, metric_ratio, status)
292 COMPLEX(dp), INTENT(IN) :: h(:, :), s(:, :), cosine(:, :, :), &
293 sine(:, :, :)
294 REAL(dp), INTENT(IN) :: origin(2), energy, eta
295 LOGICAL, INTENT(IN) :: aii
296 REAL(dp), INTENT(IN) :: matrix_tol, metric_tol, gap_tol
297 INTEGER, INTENT(OUT) :: invariant
298 REAL(dp), INTENT(OUT) :: gap, metric_ratio
299 INTEGER, INTENT(OUT) :: status
300
301 COMPLEX(dp), ALLOCATABLE :: l(:, :), mass(:, :), &
302 shifted_sine(:, :, :)
303 INTEGER :: j, n
304
305 status = -1
306 invariant = 0
307 gap = 0.0_dp
308 metric_ratio = 0.0_dp
309 n = SIZE(h, 1)
310 IF (n < 1 .OR. SIZE(h, 2) /= n) RETURN
311 IF (any(shape(s) /= [n, n])) RETURN
312 IF (any(shape(cosine) /= [n, n, 2]) .OR. any(shape(sine) /= [n, n, 2])) RETURN
313 IF (.NOT. all(ieee_is_finite([origin, energy, eta, matrix_tol]))) RETURN
314 IF (eta <= 0.0_dp .OR. matrix_tol <= 0.0_dp .OR. matrix_tol >= 1.0_dp) RETURN
315 IF (.NOT. hermitian(h, matrix_tol) .OR. .NOT. hermitian(s, matrix_tol)) RETURN
316 DO j = 1, 2
317 IF (.NOT. hermitian(cosine(:, :, j), matrix_tol)) RETURN
318 IF (.NOT. hermitian(sine(:, :, j), matrix_tol)) RETURN
319 END DO
320 IF (aii) THEN
321 status = -3
322 IF (mod(n, 2) /= 0) RETURN
323 IF (.NOT. self_dual(h, matrix_tol) .OR. .NOT. self_dual(s, matrix_tol)) RETURN
324 DO j = 1, 2
325 IF (.NOT. self_dual(cosine(:, :, j), matrix_tol)) RETURN
326 IF (.NOT. self_dual(sine(:, :, j), matrix_tol)) RETURN
327 END DO
328 END IF
329 ALLOCATE (mass(n, n), shifted_sine(n, n, 2), l(2*n, 2*n))
330 mass(:, :) = h - energy*s
331 DO j = 1, 2
332 mass(:, :) = mass - eta*(s - cos(origin(j))*cosine(:, :, j) - sin(origin(j))*sine(:, :, j))
333 shifted_sine(:, :, j) = eta*(cos(origin(j))*sine(:, :, j) - sin(origin(j))*cosine(:, :, j))
334 END DO
335 l(:n, :n) = mass
336 l(n + 1:, n + 1:) = -mass
337 l(:n, n + 1:) = shifted_sine(:, :, 1) - cmplx(0.0_dp, 1.0_dp, dp)*shifted_sine(:, :, 2)
338 l(n + 1:, :n) = conjg(transpose(l(:n, n + 1:)))
339 CALL localizer_matrix_evaluate(l, s, aii, matrix_tol, metric_tol, gap_tol, &
340 invariant, gap, metric_ratio, status)
341 END SUBROUTINE localizer_periodic_evaluate
342
343! **************************************************************************************************
344!> \brief Evaluate the 2D class A or AII localizer without projecting onto occupied states.
345!> \param h Covariant Hamiltonian matrix
346!> \param s Positive definite AO overlap
347!> \param r Covariant Cartesian position matrices in the two oriented directions
348!> \param point Position in the same coordinates and length units as r
349!> \param energy Absolute reference energy in the units of h
350!> \param kappa Positive energy/length scale
351!> \param aii Use spinful time reversal, with all up AOs followed by all down AOs
352!> \param matrix_tol Relative Hermiticity and time-reversal tolerance
353!> \param metric_tol Minimum allowed smallest/largest overlap eigenvalue ratio
354!> \param gap_tol Absolute unresolved-gap threshold in energy units
355!> \param invariant Half-signature (A), or 0 trivial / 1 nontrivial (AII); valid only if status=0
356!> \param gap Minimum absolute generalized localizer eigenvalue
357!> \param metric_ratio Smallest/largest overlap eigenvalue ratio
358!> \param status 0 resolved; 1 unresolved gap; -1 invalid data; -2 bad metric; -3 broken TR;
359!> -4 factorization failure; positive values above 1 indicate LAPACK failure
360!> \note This is a finite-basis diagnostic, not a certificate of basis or volume convergence.
361! **************************************************************************************************
362 SUBROUTINE localizer_evaluate(h, s, r, point, energy, kappa, aii, matrix_tol, metric_tol, &
363 gap_tol, invariant, gap, metric_ratio, status)
364 COMPLEX(KIND=dp), INTENT(IN) :: h(:, :), s(:, :), r(:, :, :)
365 REAL(kind=dp), INTENT(IN) :: point(2), energy, kappa
366 LOGICAL, INTENT(IN) :: aii
367 REAL(kind=dp), INTENT(IN) :: matrix_tol, metric_tol, gap_tol
368 INTEGER, INTENT(OUT) :: invariant
369 REAL(kind=dp), INTENT(OUT) :: gap, metric_ratio
370 INTEGER, INTENT(OUT) :: status
371
372 COMPLEX(KIND=dp), ALLOCATABLE :: localizer(:, :)
373 INTEGER :: i, n
374
375 status = -1
376 invariant = 0
377 gap = 0.0_dp
378 metric_ratio = 0.0_dp
379 n = SIZE(h, 1)
380 IF (n < 1 .OR. SIZE(h, 2) /= n) RETURN
381 IF (any(shape(s) /= [n, n]) .OR. any(shape(r) /= [n, n, 2])) RETURN
382 IF (.NOT. all(ieee_is_finite([point, energy, kappa, matrix_tol, metric_tol, gap_tol]))) RETURN
383 IF (kappa <= 0.0_dp .OR. matrix_tol <= 0.0_dp .OR. matrix_tol >= 1.0_dp) RETURN
384 IF (metric_tol <= 0.0_dp .OR. metric_tol >= 1.0_dp .OR. gap_tol < 0.0_dp) RETURN
385 IF (.NOT. hermitian(h, matrix_tol) .OR. .NOT. hermitian(s, matrix_tol)) RETURN
386 DO i = 1, 2
387 IF (.NOT. hermitian(r(:, :, i), matrix_tol)) RETURN
388 END DO
389 IF (aii) THEN
390 status = -3
391 IF (mod(n, 2) /= 0) RETURN
392 IF (.NOT. self_dual(h, matrix_tol) .OR. .NOT. self_dual(s, matrix_tol)) RETURN
393 DO i = 1, 2
394 IF (.NOT. self_dual(r(:, :, i), matrix_tol)) RETURN
395 END DO
396 END IF
397
398 ALLOCATE (localizer(2*n, 2*n))
399 localizer(:n, :n) = h - energy*s
400 localizer(n + 1:, n + 1:) = -localizer(:n, :n)
401 localizer(:n, n + 1:) = kappa*((r(:, :, 1) - point(1)*s) - &
402 cmplx(0.0_dp, 1.0_dp, dp)*(r(:, :, 2) - point(2)*s))
403 localizer(n + 1:, :n) = conjg(transpose(localizer(:n, n + 1:)))
404 CALL localizer_matrix_evaluate(localizer, s, aii, matrix_tol, metric_tol, gap_tol, &
405 invariant, gap, metric_ratio, status)
406 END SUBROUTINE localizer_evaluate
407
408! **************************************************************************************************
409!> \brief Dense reference evaluation of an assembled localizer with metric diag(S,S).
410!> \param localizer Covariant Hermitian localizer
411!> \param s Positive definite physical AO/spinor overlap
412!> \param aii Whether to use the physical spinful time-reversal structure
413!> \param matrix_tol Relative structure tolerance
414!> \param metric_tol Minimum overlap eigenvalue ratio
415!> \param gap_tol Absolute unresolved-gap tolerance
416!> \param invariant Resolved class A or AII index
417!> \param gap Minimum absolute generalized eigenvalue
418!> \param metric_ratio Smallest/largest overlap eigenvalue ratio
419!> \param status Same status convention as localizer_evaluate
420! **************************************************************************************************
421 SUBROUTINE localizer_matrix_evaluate(localizer, s, aii, matrix_tol, metric_tol, gap_tol, &
422 invariant, gap, metric_ratio, status)
423 COMPLEX(KIND=dp), INTENT(IN) :: localizer(:, :), s(:, :)
424 LOGICAL, INTENT(IN) :: aii
425 REAL(kind=dp), INTENT(IN) :: matrix_tol, metric_tol, gap_tol
426 INTEGER, INTENT(OUT) :: invariant, status
427 REAL(kind=dp), INTENT(OUT) :: gap, metric_ratio
428
429 COMPLEX(KIND=dp), ALLOCATABLE :: a(:, :), b(:, :), work(:)
430 INTEGER :: info, n, negative, pf, pf_reference, &
431 positive, ldl_positive, ldl_negative
432#if defined(__HAS_IEEE_EXCEPTIONS)
433 LOGICAL :: halt(size(ieee_all))
434#endif
435 REAL(kind=dp) :: metric_min, residual, threshold
436 REAL(kind=dp), ALLOCATABLE :: eigenvalues(:), rwork(:), skew(:, :)
437
438 status = -1
439 invariant = 0
440 gap = 0.0_dp
441 metric_ratio = 0.0_dp
442 n = SIZE(s, 1)
443 IF (n < 1 .OR. SIZE(s, 2) /= n) RETURN
444 IF (any(shape(localizer) /= [2*n, 2*n])) RETURN
445 IF (.NOT. all(ieee_is_finite([matrix_tol, metric_tol, gap_tol]))) RETURN
446 IF (matrix_tol <= 0.0_dp .OR. matrix_tol >= 1.0_dp) RETURN
447 IF (metric_tol <= 0.0_dp .OR. metric_tol >= 1.0_dp .OR. gap_tol < 0.0_dp) RETURN
448 IF (.NOT. hermitian(s, matrix_tol) .OR. .NOT. hermitian(localizer, matrix_tol)) RETURN
449 IF (aii) THEN
450 status = -3
451 IF (mod(n, 2) /= 0) RETURN
452 IF (.NOT. self_dual(s, matrix_tol)) RETURN
453 END IF
454
455 ALLOCATE (a(n, n), eigenvalues(n), work(max(1, 2*n)), rwork(max(1, 3*n)))
456 a(:, :) = s
457 ! Follow CP2K's eigensolvers: LAPACK may use exceptional IEEE intermediates internally.
458#if defined(__HAS_IEEE_EXCEPTIONS)
459 CALL ieee_get_halting_mode(ieee_all, halt)
460 CALL ieee_set_halting_mode(ieee_all, .false.)
461#endif
462 CALL zheev('N', 'U', n, a, n, eigenvalues, work, SIZE(work), rwork, info)
463#if defined(__HAS_IEEE_EXCEPTIONS)
464 CALL ieee_set_halting_mode(ieee_all, halt)
465#endif
466 status = abs(info) + 2
467 IF (info /= 0 .OR. .NOT. all(ieee_is_finite(eigenvalues))) RETURN
468 status = -2
469 IF (maxval(eigenvalues) <= 0.0_dp) RETURN
470 metric_ratio = minval(eigenvalues)/maxval(eigenvalues)
471 IF (metric_ratio <= metric_tol) RETURN
472 metric_min = minval(eigenvalues)
473 DEALLOCATE (a, eigenvalues, work, rwork)
474
475 ALLOCATE (a(2*n, 2*n), b(2*n, 2*n))
476 ALLOCATE (eigenvalues(2*n), work(4*n), rwork(6*n))
477 status = -1
478 IF (.NOT. all(ieee_is_finite(real(localizer, dp))) .OR. &
479 .NOT. all(ieee_is_finite(aimag(localizer)))) RETURN
480 a(:, :) = localizer
481 b(:, :) = cmplx(0.0_dp, 0.0_dp, dp)
482 b(:n, :n) = s
483 b(n + 1:, n + 1:) = s
484 ! Generalized eigenvalues, unlike eigenvalues of raw AO matrices, are basis invariant.
485#if defined(__HAS_IEEE_EXCEPTIONS)
486 CALL ieee_get_halting_mode(ieee_all, halt)
487 CALL ieee_set_halting_mode(ieee_all, .false.)
488#endif
489 CALL zhegv(1, 'N', 'U', 2*n, a, 2*n, b, 2*n, eigenvalues, work, SIZE(work), rwork, info)
490#if defined(__HAS_IEEE_EXCEPTIONS)
491 CALL ieee_set_halting_mode(ieee_all, halt)
492#endif
493 status = abs(info) + 2
494 IF (info /= 0 .OR. .NOT. all(ieee_is_finite(eigenvalues))) RETURN
495 gap = minval(abs(eigenvalues))
496 ! Bound roundoff and triangle inconsistencies after conversion to the orthonormal metric.
497 residual = real(2*n, dp)*maxval(abs(localizer - conjg(transpose(localizer))))/metric_min + &
498 REAL(n, dp)*maxval(abs(eigenvalues))*maxval(abs(s - conjg(transpose(s))))/metric_min
499 threshold = max(gap_tol, residual + 100.0_dp*epsilon(1.0_dp)*real(2*n, dp)* &
500 max(maxval(abs(eigenvalues)), maxval(abs(localizer))/metric_min))
501 status = 1
502 IF (gap <= threshold) RETURN
503 positive = count(eigenvalues > threshold)
504 negative = count(eigenvalues < -threshold)
505 CALL localizer_inertia(localizer, ldl_positive, ldl_negative, info)
506 status = -4
507 IF (info /= 0) RETURN
508 IF (positive /= ldl_positive .OR. negative /= ldl_negative) RETURN
509 IF (.NOT. aii) THEN
510 invariant = (ldl_positive - ldl_negative)/2
511 ELSE
512 status = -3
513 IF (positive /= negative) RETURN
514 ALLOCATE (skew(2*n, 2*n))
515 CALL localizer_aii_skew(localizer, skew, matrix_tol, residual, info)
516 IF (info /= 0) RETURN
517 status = 1
518 IF (gap <= threshold + real(2*n, dp)*residual/metric_min) RETURN
519 CALL localizer_pfaffian_sign(skew, pf, info)
520 status = -4
521 IF (info /= 0) RETURN
522 ! Fix the orientation against the positive atomic Hamiltonian in the SAME AO metric.
523 a(:, :) = cmplx(0.0_dp, 0.0_dp, dp)
524 a(:n, :n) = s
525 a(n + 1:, n + 1:) = -s
526 CALL localizer_aii_skew(a, skew, matrix_tol, residual, info)
527 IF (info /= 0) RETURN
528 IF (real(2*n, dp)*residual >= metric_min) RETURN
529 CALL localizer_pfaffian_sign(skew, pf_reference, info)
530 IF (info /= 0) RETURN
531 invariant = (1 - pf*pf_reference)/2
532 END IF
533 status = 0
534 END SUBROUTINE localizer_matrix_evaluate
535
536! **************************************************************************************************
537!> \brief Count inertia by pivoted Hermitian LDL^dagger, without orthogonalizing the AO basis.
538!> \param matrix Hermitian matrix; input is not modified
539!> \param positive Number of positive eigenvalues
540!> \param negative Number of negative eigenvalues
541!> \param status 0 resolved, 1 singular, -1 invalid data, -4 factorization failure
542!> \note Pivot magnitudes are not eigenvalues or a metric-aware protection gap.
543! **************************************************************************************************
544 SUBROUTINE localizer_inertia_complex(matrix, positive, negative, status)
545 COMPLEX(KIND=dp), INTENT(IN) :: matrix(:, :)
546 INTEGER, INTENT(OUT) :: positive, negative, status
547
548 COMPLEX(KIND=dp), ALLOCATABLE :: a(:, :), work(:)
549 INTEGER :: i, info, n
550 INTEGER, ALLOCATABLE :: pivots(:)
551 REAL(KIND=dp) :: scale
552#if defined(__HAS_IEEE_EXCEPTIONS)
553 LOGICAL :: halt(SIZE(IEEE_ALL))
554#endif
555
556 positive = 0
557 negative = 0
558 status = -1
559 n = SIZE(matrix, 1)
560 IF (n < 1 .OR. SIZE(matrix, 2) /= n) RETURN
561 IF (.NOT. hermitian(matrix, 1.e-10_dp)) RETURN
562 ! Never discard a small imaginary term: only exactly real matrices use LDL^T.
563 IF (all(aimag(matrix) == 0.0_dp)) THEN
564 CALL localizer_inertia_real(real(matrix, dp), positive, negative, status)
565 RETURN
566 END IF
567 scale = maxval(abs(matrix))
568 status = 1
569 IF (scale <= tiny(1.0_dp)) RETURN
570 ALLOCATE (a(n, n), pivots(n), work(max(1, 64*n)))
571 a(:, :) = matrix/scale
572#if defined(__HAS_IEEE_EXCEPTIONS)
573 CALL ieee_get_halting_mode(ieee_all, halt)
574 CALL ieee_set_halting_mode(ieee_all, .false.)
575#endif
576 CALL zhetrf('L', n, a, n, pivots, work, SIZE(work), info)
577#if defined(__HAS_IEEE_EXCEPTIONS)
578 CALL ieee_set_halting_mode(ieee_all, halt)
579#endif
580 IF (info > 0) RETURN
581 status = -4
582 IF (info < 0) RETURN
583 IF (.NOT. all(ieee_is_finite(real(a, dp))) .OR. .NOT. all(ieee_is_finite(aimag(a)))) RETURN
584 CALL block_inertia([(real(a(i, i), dp), i=1, n)], [(abs(a(i + 1, i)), i=1, n - 1)], &
585 pivots, positive, negative, status)
586 END SUBROUTINE localizer_inertia_complex
587
588! **************************************************************************************************
589!> \brief Native real symmetric LDL^T inertia, without converting to complex storage.
590!> \param matrix Real symmetric matrix; input is not modified
591!> \param positive Number of positive eigenvalues
592!> \param negative Number of negative eigenvalues
593!> \param status 0 resolved, 1 singular, -1 invalid data, -4 factorization failure
594! **************************************************************************************************
595 SUBROUTINE localizer_inertia_real(matrix, positive, negative, status)
596 REAL(dp), INTENT(IN) :: matrix(:, :)
597 INTEGER, INTENT(OUT) :: positive, negative, status
598 REAL(dp), ALLOCATABLE :: a(:, :), work(:)
599 INTEGER, ALLOCATABLE :: pivots(:)
600 INTEGER :: i, info, n
601 REAL(dp) :: scale
602#if defined(__HAS_IEEE_EXCEPTIONS)
603 LOGICAL :: halt(SIZE(IEEE_ALL))
604#endif
605
606 positive = 0
607 negative = 0
608 status = -1
609 n = SIZE(matrix, 1)
610 IF (n < 1 .OR. SIZE(matrix, 2) /= n) RETURN
611 IF (.NOT. all(ieee_is_finite(matrix))) RETURN
612 scale = maxval(abs(matrix))
613 IF (maxval(abs(matrix - transpose(matrix))) > 1.e-10_dp*max(tiny(1.0_dp), scale)) RETURN
614 status = 1
615 IF (scale <= tiny(1.0_dp)) RETURN
616 ALLOCATE (a(n, n), pivots(n), work(max(1, 64*n)))
617 a(:, :) = matrix/scale
618#if defined(__HAS_IEEE_EXCEPTIONS)
619 CALL ieee_get_halting_mode(ieee_all, halt)
620 CALL ieee_set_halting_mode(ieee_all, .false.)
621#endif
622 CALL dsytrf('L', n, a, n, pivots, work, SIZE(work), info)
623#if defined(__HAS_IEEE_EXCEPTIONS)
624 CALL ieee_set_halting_mode(ieee_all, halt)
625#endif
626 IF (info > 0) RETURN
627 status = -4
628 IF (info < 0 .OR. .NOT. all(ieee_is_finite(a))) RETURN
629 CALL block_inertia([(a(i, i), i=1, n)], [(abs(a(i + 1, i)), i=1, n - 1)], &
630 pivots, positive, negative, status)
631 END SUBROUTINE localizer_inertia_real
632
633! **************************************************************************************************
634!> \brief Count signs of lower Bunch-Kaufman D blocks, shared by real and complex factors.
635!> \param diagonal Real diagonal of the factored matrix
636!> \param coupling Magnitudes of first subdiagonal entries
637!> \param pivots LAPACK lower-triangle pivot encoding
638!> \param positive Positive inertia count
639!> \param negative Negative inertia count
640!> \param status 0 resolved, 1 singular, -4 invalid block structure
641!> \note A Hermitian 2x2 block has the same eigenvalues when its offdiagonal is replaced by its modulus.
642! **************************************************************************************************
643 SUBROUTINE block_inertia(diagonal, coupling, pivots, positive, negative, status)
644 REAL(dp), INTENT(IN) :: diagonal(:), coupling(:)
645 INTEGER, INTENT(IN) :: pivots(:)
646 INTEGER, INTENT(OUT) :: positive, negative, status
647
648 INTEGER :: i, n
649 REAL(dp) :: values(2)
650 INTERFACE
651 SUBROUTINE dlae2(a, b, c, rt1, rt2)
652 IMPORT :: dp
653 REAL(dp), INTENT(IN) :: a, b, c
654 REAL(dp), INTENT(OUT) :: rt1, rt2
655 END SUBROUTINE dlae2
656 END INTERFACE
657
658 positive = 0
659 negative = 0
660 status = -4
661 n = SIZE(pivots)
662 i = 1
663 DO WHILE (i <= n)
664 IF (pivots(i) == 0) RETURN
665 IF (pivots(i) > 0) THEN
666 values = [diagonal(i), 0.0_dp]
667 IF (values(1) == 0.0_dp) THEN
668 status = 1
669 RETURN
670 END IF
671 i = i + 1
672 ELSE
673 IF (i == n) RETURN
674 IF (pivots(i + 1) /= pivots(i)) RETURN
675 ! A 2x2 pivot can have zero diagonal and still contribute one sign of each kind.
676 CALL dlae2(diagonal(i), coupling(i), diagonal(i + 1), values(1), values(2))
677 IF (any(values == 0.0_dp)) THEN
678 status = 1
679 RETURN
680 END IF
681 i = i + 2
682 END IF
683 positive = positive + count(values > 0.0_dp)
684 negative = negative + count(values < 0.0_dp)
685 END DO
686 status = 0
687 END SUBROUTINE block_inertia
688
689! **************************************************************************************************
690!> \brief Test finiteness and Hermiticity before passing only one triangle to LAPACK.
691!> \param a Square matrix
692!> \param tolerance Relative tolerance
693!> \return Whether the matrix is Hermitian and finite
694! **************************************************************************************************
695 LOGICAL FUNCTION hermitian(a, tolerance)
696 COMPLEX(KIND=dp), INTENT(IN) :: a(:, :)
697 REAL(KIND=dp), INTENT(IN) :: tolerance
698
699 hermitian = .false.
700 IF (.NOT. all(ieee_is_finite(real(a, dp))) .OR. .NOT. all(ieee_is_finite(aimag(a)))) RETURN
701 hermitian = maxval(abs(a - conjg(transpose(a)))) <= tolerance*max(tiny(1.0_dp), maxval(abs(a)))
702 END FUNCTION hermitian
703
704! **************************************************************************************************
705!> \brief Check J conj(A) J^dagger=A for J=(0,I;-I,0), without assuming spin conservation.
706!> \param a Even-dimensional matrix in the fixed up/down ordering
707!> \param tolerance Relative tolerance
708!> \return Whether physical fermionic time reversal is respected
709! **************************************************************************************************
710 LOGICAL FUNCTION self_dual(a, tolerance)
711 COMPLEX(KIND=dp), INTENT(IN) :: a(:, :)
712 REAL(KIND=dp), INTENT(IN) :: tolerance
713
714 INTEGER :: n
715 REAL(KIND=dp) :: scale
716
717 n = SIZE(a, 1)/2
718 scale = max(tiny(1.0_dp), maxval(abs(a)))
719 self_dual = maxval(abs(a(:n, :n) - conjg(a(n + 1:, n + 1:)))) <= tolerance*scale .AND. &
720 maxval(abs(a(:n, n + 1:) + conjg(a(n + 1:, :n)))) <= tolerance*scale
721 END FUNCTION self_dual
722
723! **************************************************************************************************
724!> \brief Convert the class AII localizer to a real skew matrix using Q=(I-i C)/sqrt(2).
725!> \param localizer Hermitian localizer; auxiliary index outside physical up/down index
726!> \param skew Real part of i Q^dagger L Q
727!> \param tolerance Relative imaginary/skew residual tolerance
728!> \param residual Entrywise bound for the discarded real and symmetric components
729!> \param status Zero on success; negative if the AII structure is violated
730! **************************************************************************************************
731 SUBROUTINE localizer_aii_skew(localizer, skew, tolerance, residual, status)
732 COMPLEX(KIND=dp), INTENT(IN) :: localizer(:, :)
733 REAL(kind=dp), INTENT(OUT) :: skew(:, :)
734 REAL(kind=dp), INTENT(IN) :: tolerance
735 REAL(kind=dp), INTENT(OUT) :: residual
736 INTEGER, INTENT(OUT) :: status
737
738 COMPLEX(KIND=dp) :: value
739 INTEGER :: i, j, m, n, pi, pj, si, sj
740 REAL(kind=dp) :: antisymmetric, scale, skew_residual
741
742 m = SIZE(localizer, 1)
743 status = -1
744 residual = huge(1.0_dp)
745 IF (m < 4 .OR. mod(m, 4) /= 0) RETURN
746 IF (SIZE(localizer, 2) /= m .OR. any(shape(skew) /= [m, m])) RETURN
747 IF (.NOT. ieee_is_finite(tolerance)) RETURN
748 IF (tolerance <= 0.0_dp .OR. tolerance >= 1.0_dp) RETURN
749 IF (.NOT. all(ieee_is_finite(real(localizer, dp))) .OR. &
750 .NOT. all(ieee_is_finite(aimag(localizer)))) RETURN
751 n = m/4
752 residual = 0.0_dp
753 ! C is a signed permutation: Q^dagger L Q = (L + CLC + i(CL-LC))/2.
754 DO j = 1, m
755 pj = (3 - (j - 1)/n)*n + modulo(j - 1, n) + 1
756 sj = merge(1, -1, j <= n .OR. j > 3*n)
757 DO i = 1, m
758 pi = (3 - (i - 1)/n)*n + modulo(i - 1, n) + 1
759 si = merge(1, -1, i <= n .OR. i > 3*n)
760 value = 0.5_dp*(localizer(i, j) + si*sj*localizer(pi, pj) + &
761 cmplx(0.0_dp, 1.0_dp, dp)*(si*localizer(pi, j) - sj*localizer(i, pj)))
762 IF (.NOT. ieee_is_finite(real(value, dp)) .OR. .NOT. ieee_is_finite(aimag(value))) THEN
763 residual = huge(1.0_dp)
764 RETURN
765 END IF
766 residual = max(residual, abs(real(value, dp)))
767 skew(i, j) = -aimag(value)
768 END DO
769 END DO
770 scale = max(tiny(1.0_dp), maxval(abs(localizer)))
771 IF (residual > tolerance*scale) RETURN
772 skew_residual = 0.0_dp
773 DO j = 1, m
774 DO i = 1, j
775 skew_residual = max(skew_residual, abs(0.5_dp*skew(i, j) + 0.5_dp*skew(j, i)))
776 END DO
777 END DO
778 IF (skew_residual > 0.5_dp*tolerance*scale) RETURN
779 ! Project only validated roundoff; callers include this bound in the gap test.
780 residual = residual + skew_residual
781 DO j = 1, m
782 DO i = 1, j
783 antisymmetric = 0.5_dp*skew(i, j) - 0.5_dp*skew(j, i)
784 skew(i, j) = antisymmetric
785 skew(j, i) = -antisymmetric
786 END DO
787 END DO
788 status = 0
789 END SUBROUTINE localizer_aii_skew
790
791! **************************************************************************************************
792!> \brief Dense pivoted skew elimination, accumulating only the sign of the Pfaffian.
793!> \param matrix Real skew-symmetric matrix; input is not modified
794!> \param pf_sign Sign, or zero if singular/unresolved
795!> \param status Zero on success, -1 invalid data, -2 numerically singular factorization
796!> \note Each simultaneous row/column interchange changes the Pfaffian sign, not its square.
797! **************************************************************************************************
798 SUBROUTINE localizer_pfaffian_sign(matrix, pf_sign, status)
799 REAL(kind=dp), INTENT(IN) :: matrix(:, :)
800 INTEGER, INTENT(OUT) :: pf_sign, status
801
802 INTEGER :: i, j, k, n, p, sign_accum
803 REAL(kind=dp) :: pivot, scale
804 REAL(kind=dp), ALLOCATABLE :: a(:, :), row(:)
805
806 n = SIZE(matrix, 1)
807 pf_sign = 0
808 status = -1
809 IF (n < 2 .OR. mod(n, 2) /= 0 .OR. SIZE(matrix, 2) /= n) RETURN
810 IF (.NOT. all(ieee_is_finite(matrix))) RETURN
811 scale = maxval(abs(matrix))
812 IF (maxval(abs(matrix + transpose(matrix))) > 100.0_dp*epsilon(1.0_dp)*scale) RETURN
813 status = -2
814 IF (scale <= tiny(1.0_dp)) RETURN
815 ALLOCATE (a(n, n), row(n))
816 a(:, :) = matrix/scale
817 sign_accum = 1
818 DO k = 1, n - 1, 2
819 p = k + maxloc(abs(a(k, k + 1:)), dim=1)
820 IF (p /= k + 1) THEN
821 row(:) = a(k + 1, :)
822 a(k + 1, :) = a(p, :)
823 a(p, :) = row
824 row(:) = a(:, k + 1)
825 a(:, k + 1) = a(:, p)
826 a(:, p) = row
827 sign_accum = -sign_accum
828 END IF
829 pivot = a(k, k + 1)
830 IF (abs(pivot) <= 100.0_dp*epsilon(1.0_dp)*maxval(abs(a(k:, k:)))) RETURN
831 IF (pivot < 0.0_dp) sign_accum = -sign_accum
832 DO j = k + 2, n
833 DO i = k + 2, j - 1
834 a(i, j) = a(i, j) + (a(k + 1, i)*a(k, j) - a(k, i)*a(k + 1, j))/pivot
835 a(j, i) = -a(i, j)
836 END DO
837 END DO
838 IF (.NOT. all(ieee_is_finite(a(k:, k:)))) RETURN
839 END DO
840 pf_sign = sign_accum
841 status = 0
842 END SUBROUTINE localizer_pfaffian_sign
843
844END MODULE spectral_localizer
static GRID_HOST_DEVICE int modulo(int a, int m)
Equivalent of Fortran's MODULO, which always return a positive number. https://gcc....
Defines the basic variable types.
Definition kinds.F:23
integer, parameter, public dp
Definition kinds.F:34
Dense reference spectral localizers in a nonorthogonal finite basis.
subroutine, public localizer_matrix_evaluate(localizer, s, aii, matrix_tol, metric_tol, gap_tol, invariant, gap, metric_ratio, status)
Dense reference evaluation of an assembled localizer with metric diag(S,S).
subroutine, public localizer_3d_evaluate(h, s, r, point, energy, kappa, matrix_tol, metric_tol, gap_tol, invariant, gap, metric_ratio, status)
Finite three-dimensional class AII localizer, using all three Cartesian moments.
subroutine, public localizer_aii_skew(localizer, skew, tolerance, residual, status)
Convert the class AII localizer to a real skew matrix using Q=(I-i C)/sqrt(2).
subroutine, public localizer_3d_matrix_evaluate(block, s, matrix_tol, metric_tol, gap_tol, invariant, gap, metric_ratio, status)
Metric gap and real determinant sign of a three-dimensional AII chiral block.
subroutine, public localizer_periodic_evaluate(h, s, cosine, sine, origin, energy, eta, aii, matrix_tol, metric_tol, gap_tol, invariant, gap, metric_ratio, status)
Two-dimensional periodic localizer from covariant trigonometric position operators.
subroutine, public localizer_pfaffian_sign(matrix, pf_sign, status)
Dense pivoted skew elimination, accumulating only the sign of the Pfaffian.
subroutine, public localizer_evaluate(h, s, r, point, energy, kappa, aii, matrix_tol, metric_tol, gap_tol, invariant, gap, metric_ratio, status)
Evaluate the 2D class A or AII localizer without projecting onto occupied states.