Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
NVIDIA
GitHub Repository: NVIDIA/cuda-q-academic
Path: blob/main/chemistry-simulations/aux_files/krylov/qchem/eigenvaluesolver.py
583 views
1
import numpy as np
2
import scipy.linalg as la
3
4
def eigen(H, S, *, cond_limit=1e12, eig_tol=1e-10, verbose=False):
5
"""
6
Solve H C = S C E in double precision with automatic stabilisation.
7
8
• Prints κ(S) before and, if projection happens, after stabilisation.
9
• Projects out tiny eigenvalues of S when κ(S) is too large.
10
11
Parameters
12
----------
13
H, S : (n, n) ndarray (real/complex)
14
The Hamiltonian (H) and overlap (S) matrices.
15
cond_limit : float
16
Trigger value for printing the projection warning.
17
eig_tol : float
18
Relative cut-off (s_val < eig_tol * s_val_max are removed).
19
verbose : bool
20
Print diagnostics.
21
"""
22
H = np.asarray(H, dtype=np.complex128)
23
S = np.asarray(S, dtype=np.complex128)
24
25
# 1. Diagonalise S (assumed Hermitian)
26
s_vals, U = la.eigh(S)
27
# Drop negative round-off noise by clipping to zero
28
s_vals = np.clip(s_vals, 0.0, None)
29
30
pos = s_vals[s_vals > 0]
31
if pos.size == 0:
32
raise np.linalg.LinAlgError("S is numerically singular: no positive eigenvalues.")
33
34
kappa0 = pos.max() / pos.min()
35
if verbose:
36
print(f"κ(S) before projection : {kappa0:.3e}")
37
38
# 2. Decide which eigenvalues to keep
39
s_max = pos.max()
40
# Define an absolute cutoff based on machine precision and the relative tolerance
41
abs_cut = max(np.finfo(s_vals.dtype).eps, eig_tol * s_max)
42
43
keep = s_vals > abs_cut
44
45
# Check if we discarded everything
46
if not np.any(keep):
47
raise np.linalg.LinAlgError(f"All eigenvalues discarded; S is singular (cutoff={abs_cut:.2e}).")
48
49
# Conditionally print the projection message if the condition number was the trigger
50
if verbose and kappa0 > cond_limit:
51
n_removed = S.shape[0] - np.sum(keep)
52
if n_removed > 0:
53
print(f" projecting out {n_removed} eigenvalue(s) < {abs_cut:.3e}")
54
55
lam_keep = s_vals[keep] # Now guaranteed to be strictly positive
56
U_keep = U[:, keep]
57
58
# 3. Build the transformation matrix X = U_keep @ diag(1/sqrt(lam_keep))
59
# Broadcasting correctly scales each column of U_keep
60
X = U_keep / np.sqrt(lam_keep)
61
62
# 4. Solve the reduced (and well-conditioned) standard eigenvalue problem
63
H_prime = (X.conj().T @ H) @ X
64
e_prime, C_prime = la.eigh(H_prime) # Use eigh since H_prime is Hermitian
65
66
# 5. Back-transform eigenvectors to the original basis
67
C = X @ C_prime
68
69
# 6. Report improved condition number if projection happened
70
if verbose and kappa0 > cond_limit:
71
kappa1 = lam_keep.max() / lam_keep.min()
72
print(f"κ(S) after projection : {kappa1:.3e}")
73
74
return e_prime, C
75