|
| 1 | + |
| 2 | +import numpy as np |
| 3 | +from scipy.linalg import cholesky, solve_triangular |
| 4 | +from scipy.linalg import eigh, eig |
| 5 | + |
| 6 | +def generalized_eigensolve_scipy(A, B, hermitian=True, sort=True): |
| 7 | + """ |
| 8 | + Solve the generalized eigenvalue problem A v = λ B v using SciPy. |
| 9 | +
|
| 10 | + Args: |
| 11 | + A (np.ndarray): Matrix A (N x N) |
| 12 | + B (np.ndarray): Matrix B (N x N) |
| 13 | + hermitian (bool): Set True if A and B are Hermitian/symmetric. |
| 14 | + sort (bool): If True, sort eigenpairs by ascending eigenvalue. |
| 15 | +
|
| 16 | + Returns: |
| 17 | + eigvals (np.ndarray): Eigenvalues (N,) |
| 18 | + eigvecs (np.ndarray): Corresponding eigenvectors (N, N) |
| 19 | +
|
| 20 | + Example: |
| 21 | +
|
| 22 | + import numpy as np |
| 23 | +
|
| 24 | + # Symmetric A and B |
| 25 | + A = np.array([[6., 2.], [2., 3.]]) |
| 26 | + B = np.array([[1., 0.], [0., 2.]]) |
| 27 | + eigvals, eigvecs = generalized_eigensolve_scipy(A, B) |
| 28 | + print("Eigenvalues:", eigvals) |
| 29 | + print("Eigenvectors (columns):\n", eigvecs) |
| 30 | +
|
| 31 | + """ |
| 32 | + |
| 33 | + if hermitian: |
| 34 | + eigvals, eigvecs = eigh(A, B) |
| 35 | + else: |
| 36 | + eigvals, eigvecs = eig(A, B) |
| 37 | + |
| 38 | + if sort: |
| 39 | + idx = np.argsort(eigvals.real if not hermitian else eigvals) |
| 40 | + eigvals = eigvals[idx] |
| 41 | + eigvecs = eigvecs[:, idx] |
| 42 | + |
| 43 | + # Normalize eigenvectors with respect to B: v† B v = 1 |
| 44 | + for i in range(eigvecs.shape[1]): |
| 45 | + vec = eigvecs[:, i] |
| 46 | + #norm = np.sqrt(np.conj(vec).T @ B @ vec) |
| 47 | + norm = np.sqrt(np.vdot(vec, B @ vec)) # vdot does conjugation |
| 48 | + eigvecs[:, i] = vec / norm |
| 49 | + |
| 50 | + |
| 51 | + return eigvals, eigvecs |
| 52 | + |
| 53 | + |
| 54 | + |
| 55 | +def generalized_eigensolve_scipy_cholesky(A, B, hermitian=True, sort=True): |
| 56 | + """ |
| 57 | + Robust solver for A v = λ B v, with B-orthonormal eigenvectors: |
| 58 | + v_i^† B v_j = δ_ij (up to numerical error) |
| 59 | + |
| 60 | + For Hermitian A, B > 0. |
| 61 | + """ |
| 62 | + if not hermitian: |
| 63 | + raise NotImplementedError("Only Hermitian positive-definite B is supported in stable form") |
| 64 | + |
| 65 | + # Cholesky decomposition: B = L L^H |
| 66 | + L = cholesky(B, lower=True) |
| 67 | + |
| 68 | + # Transform to standard eigenvalue problem: C = L^{-1} A L^{-H} |
| 69 | + Linv_A = solve_triangular(L, A, lower=True, trans='N') |
| 70 | + C = solve_triangular(L, Linv_A.T, lower=True, trans='C').T |
| 71 | + |
| 72 | + # Solve C y = λ y |
| 73 | + eigvals, y = eigh(C) |
| 74 | + |
| 75 | + if sort: |
| 76 | + idx = np.argsort(eigvals) |
| 77 | + eigvals = eigvals[idx] |
| 78 | + y = y[:, idx] |
| 79 | + |
| 80 | + # Recover v = L^{-H} y |
| 81 | + eigvecs = solve_triangular(L, y, lower=True, trans='C') |
| 82 | + |
| 83 | + # At this point, eigvecs should satisfy v.T.conj() @ B @ v ≈ I |
| 84 | + return eigvals, eigvecs |
| 85 | + |
| 86 | + |
| 87 | +""" |
| 88 | +
|
| 89 | +Tests and conventions for this module: |
| 90 | +
|
| 91 | +from liblibra_core import * |
| 92 | +from libra_py import eigensolvers, data_conv |
| 93 | +import numpy as np |
| 94 | +
|
| 95 | +# ===== Do the solution ========== |
| 96 | +A = np.array([[0, 1.0], |
| 97 | + [1.0, 1.0]]) |
| 98 | +B = np.array([[1.0, 0.0], |
| 99 | + [0.0, 1.0]]) |
| 100 | +
|
| 101 | +eigvals, eigvecs = eigensolvers.generalized_eigensolve_scipy(A, B, hermitian=False) |
| 102 | +
|
| 103 | +
|
| 104 | +# ======= Numpy multiplications ========== |
| 105 | +E = np.diag(eigvals) |
| 106 | +C = eigvecs |
| 107 | +print( E ) |
| 108 | +print( C ) |
| 109 | +print("LHS = ", A @ C) |
| 110 | +print("RHS = ", B @ C @ E) |
| 111 | +
|
| 112 | +Selection deleted |
| 113 | +E = np.diag(eigvals) |
| 114 | +C = eigvecs |
| 115 | +print( E ) |
| 116 | +print( C ) |
| 117 | +print("LHS = ", A @ C) |
| 118 | +
|
| 119 | +print("RHS = ", B @ C @ E) |
| 120 | +
|
| 121 | +>>[[-0.61803399+0.j 0. +0.j] |
| 122 | +>> [ 0. +0.j 1.61803399+0.j]] |
| 123 | +>>[[ 0.85065081 0.52573111] |
| 124 | +>> [-0.52573111 0.85065081]] |
| 125 | +>>LHS = [[-0.52573111 0.85065081] |
| 126 | +>> [ 0.3249197 1.37638192]] |
| 127 | +>>RHS = [[-0.52573111+0.j 0.85065081+0.j] |
| 128 | +>> [ 0.3249197 +0.j 1.37638192+0.j]] |
| 129 | +
|
| 130 | +
|
| 131 | +# ======= Conversion to MATRIX =========== |
| 132 | +
|
| 133 | +H = data_conv.nparray2MATRIX(A) |
| 134 | +U = data_conv.nparray2MATRIX(C) |
| 135 | +S = data_conv.nparray2MATRIX(B) |
| 136 | +e = data_conv.nparray2MATRIX(E) |
| 137 | +
|
| 138 | +# ======== Libra multiplications ======== |
| 139 | +
|
| 140 | +(H * U).show_matrix() |
| 141 | +
|
| 142 | +>>-0.52573111 0.85065081 |
| 143 | +>>0.32491970 1.3763819 |
| 144 | +
|
| 145 | +(S * U * e).show_matrix() |
| 146 | +
|
| 147 | +>>-0.52573111 0.85065081 |
| 148 | +>>0.32491970 1.3763819 |
| 149 | +
|
| 150 | +# ========= Eigenvectors ============== |
| 151 | +print("First eigenvector") |
| 152 | +print(U.get(0, 0)) |
| 153 | +print(U.get(1, 0)) |
| 154 | +
|
| 155 | +>>First eigenvector |
| 156 | +>>0.8506508083520399 |
| 157 | +>>-0.5257311121191337 |
| 158 | +
|
| 159 | +print("First eigenvector") |
| 160 | +print(C[0, 0]) |
| 161 | +print(C[1, 0]) |
| 162 | +
|
| 163 | +>>First eigenvector |
| 164 | +>>0.8506508083520399 |
| 165 | +>>-0.5257311121191337 |
| 166 | +
|
| 167 | +
|
| 168 | +""" |
| 169 | + |
0 commit comments