-
-
Save lh3/c280b2ac477e85c5c666 to your computer and use it in GitHub Desktop.
| #include <math.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #define RADIX 2.0 | |
| /************************* | |
| * balance a real matrix * | |
| *************************/ | |
| static void balanc(double **a, int n) | |
| { | |
| int i, j, last = 0; | |
| double s, r, g, f, c, sqrdx; | |
| sqrdx = RADIX * RADIX; | |
| while (last == 0) { | |
| last = 1; | |
| for (i = 0; i < n; i++) { | |
| r = c = 0.0; | |
| for (j = 0; j < n; j++) | |
| if (j != i) { | |
| c += fabs(a[j][i]); | |
| r += fabs(a[i][j]); | |
| } | |
| if (c != 0.0 && r != 0.0) { | |
| g = r / RADIX; | |
| f = 1.0; | |
| s = c + r; | |
| while (c < g) { | |
| f *= RADIX; | |
| c *= sqrdx; | |
| } | |
| g = r * RADIX; | |
| while (c > g) { | |
| f /= RADIX; | |
| c /= sqrdx; | |
| } | |
| if ((c + r) / f < 0.95 * s) { | |
| last = 0; | |
| g = 1.0 / f; | |
| for (j = 0; j < n; j++) | |
| a[i][j] *= g; | |
| for (j = 0; j < n; j++) | |
| a[j][i] *= f; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #define SWAP(a,b) do { double t = (a); (a) = (b); (b) = t; } while (0) | |
| /***************************************************** | |
| * convert a non-symmetric matrix to Hessenberg form * | |
| *****************************************************/ | |
| static void elmhes(double **a, int n) | |
| { | |
| int i, j, m; | |
| double y, x; | |
| for (m = 1; m < n - 1; m++) { | |
| x = 0.0; | |
| i = m; | |
| for (j = m; j < n; j++) { | |
| if (fabs(a[j][m - 1]) > fabs(x)) { | |
| x = a[j][m - 1]; | |
| i = j; | |
| } | |
| } | |
| if (i != m) { | |
| for (j = m - 1; j < n; j++) | |
| SWAP(a[i][j], a[m][j]); | |
| for (j = 0; j < n; j++) | |
| SWAP(a[j][i], a[j][m]); | |
| } | |
| if (x != 0.0) { | |
| for (i = m + 1; i < n; i++) { | |
| if ((y = a[i][m - 1]) != 0.0) { | |
| y /= x; | |
| a[i][m - 1] = y; | |
| for (j = m; j < n; j++) | |
| a[i][j] -= y * a[m][j]; | |
| for (j = 0; j < n; j++) | |
| a[j][m] += y * a[j][i]; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) | |
| /************************************** | |
| * QR algorithm for Hessenberg matrix * | |
| **************************************/ | |
| static void hqr(double **a, int n, double *wr, double *wi) | |
| { | |
| int nn, m, l, k, j, its, i, mmin; | |
| double z, y, x, w, v, u, t, s, r, q, p, anorm; | |
| p = q = r = 0.0; | |
| anorm = 0.0; | |
| for (i = 0; i < n; i++) | |
| for (j = i - 1 > 0 ? i - 1 : 0; j < n; j++) | |
| anorm += fabs(a[i][j]); | |
| nn = n - 1; | |
| t = 0.0; | |
| while (nn >= 0) { | |
| its = 0; | |
| do { | |
| for (l = nn; l > 0; l--) { | |
| s = fabs(a[l - 1][l - 1]) + fabs(a[l][l]); | |
| if (s == 0.0) | |
| s = anorm; | |
| if (fabs(a[l][l - 1]) + s == s) { | |
| a[l][l - 1] = 0.0; | |
| break; | |
| } | |
| } | |
| x = a[nn][nn]; | |
| if (l == nn) { | |
| wr[nn] = x + t; | |
| wi[nn--] = 0.0; | |
| } else { | |
| y = a[nn - 1][nn - 1]; | |
| w = a[nn][nn - 1] * a[nn - 1][nn]; | |
| if (l == nn - 1) { | |
| p = 0.5 * (y - x); | |
| q = p * p + w; | |
| z = sqrt(fabs(q)); | |
| x += t; | |
| if (q >= 0.0) { | |
| z = p + SIGN(z, p); | |
| wr[nn - 1] = wr[nn] = x + z; | |
| if (z != 0.0) | |
| wr[nn] = x - w / z; | |
| wi[nn - 1] = wi[nn] = 0.0; | |
| } else { | |
| wr[nn - 1] = wr[nn] = x + p; | |
| wi[nn - 1] = -(wi[nn] = z); | |
| } | |
| nn -= 2; | |
| } else { | |
| if (its == 30) { | |
| fprintf(stderr, "[hqr] too many iterations.\n"); | |
| break; | |
| } | |
| if (its == 10 || its == 20) { | |
| t += x; | |
| for (i = 0; i < nn + 1; i++) | |
| a[i][i] -= x; | |
| s = fabs(a[nn][nn - 1]) + fabs(a[nn - 1][nn - 2]); | |
| y = x = 0.75 * s; | |
| w = -0.4375 * s * s; | |
| } | |
| ++its; | |
| for (m = nn - 2; m >= l; m--) { | |
| z = a[m][m]; | |
| r = x - z; | |
| s = y - z; | |
| p = (r * s - w) / a[m + 1][m] + a[m][m + 1]; | |
| q = a[m + 1][m + 1] - z - r - s; | |
| r = a[m + 2][m + 1]; | |
| s = fabs(p) + fabs(q) + fabs(r); | |
| p /= s; | |
| q /= s; | |
| r /= s; | |
| if (m == l) | |
| break; | |
| u = fabs(a[m][m - 1]) * (fabs(q) + fabs(r)); | |
| v = fabs(p) * (fabs(a[m - 1][m - 1]) + fabs(z) + fabs(a[m + 1][m + 1])); | |
| if (u + v == v) | |
| break; | |
| } | |
| for (i = m; i < nn - 1; i++) { | |
| a[i + 2][i] = 0.0; | |
| if (i != m) | |
| a[i + 2][i - 1] = 0.0; | |
| } | |
| for (k = m; k < nn; k++) { | |
| if (k != m) { | |
| p = a[k][k - 1]; | |
| q = a[k + 1][k - 1]; | |
| r = 0.0; | |
| if (k + 1 != nn) | |
| r = a[k + 2][k - 1]; | |
| if ((x = fabs(p) + fabs(q) + fabs(r)) != 0.0) { | |
| p /= x; | |
| q /= x; | |
| r /= x; | |
| } | |
| } | |
| if ((s = SIGN(sqrt(p * p + q * q + r * r), p)) != 0.0) { | |
| if (k == m) { | |
| if (l != m) | |
| a[k][k - 1] = -a[k][k - 1]; | |
| } else | |
| a[k][k - 1] = -s * x; | |
| p += s; | |
| x = p / s; | |
| y = q / s; | |
| z = r / s; | |
| q /= p; | |
| r /= p; | |
| for (j = k; j < nn + 1; j++) { | |
| p = a[k][j] + q * a[k + 1][j]; | |
| if (k + 1 != nn) { | |
| p += r * a[k + 2][j]; | |
| a[k + 2][j] -= p * z; | |
| } | |
| a[k + 1][j] -= p * y; | |
| a[k][j] -= p * x; | |
| } | |
| mmin = nn < k + 3 ? nn : k + 3; | |
| for (i = l; i < mmin + 1; i++) { | |
| p = x * a[i][k] + y * a[i][k + 1]; | |
| if (k != (nn)) { | |
| p += z * a[i][k + 2]; | |
| a[i][k + 2] -= p * r; | |
| } | |
| a[i][k + 1] -= p * q; | |
| a[i][k] -= p; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } while (l + 1 < nn); | |
| } | |
| } | |
| /********************************************************* | |
| * calculate eigenvalues for a non-symmetric real matrix * | |
| *********************************************************/ | |
| void n_eigen(double *_a, int n, double *wr, double *wi) | |
| { | |
| int i; | |
| double **a = (double **) calloc(n, sizeof(void *)); | |
| for (i = 0; i < n; ++i) | |
| a[i] = _a + i * n; | |
| balanc(a, n); | |
| elmhes(a, n); | |
| hqr(a, n, wr, wi); | |
| free(a); | |
| } | |
| /* convert a symmetric matrix to tridiagonal form */ | |
| #define SQR(a) ((a)*(a)) | |
| static double pythag(double a, double b) | |
| { | |
| double absa, absb; | |
| absa = fabs(a); | |
| absb = fabs(b); | |
| if (absa > absb) return absa * sqrt(1.0 + SQR(absb / absa)); | |
| else return (absb == 0.0 ? 0.0 : absb * sqrt(1.0 + SQR(absa / absb))); | |
| } | |
| static void tred2(double **a, int n, double *d, double *e) | |
| { | |
| int l, k, j, i; | |
| double scale, hh, h, g, f; | |
| for (i = n - 1; i > 0; i--) { | |
| l = i - 1; | |
| h = scale = 0.0; | |
| if (l > 0) { | |
| for (k = 0; k < l + 1; k++) | |
| scale += fabs(a[i][k]); | |
| if (scale == 0.0) | |
| e[i] = a[i][l]; | |
| else { | |
| for (k = 0; k < l + 1; k++) { | |
| a[i][k] /= scale; | |
| h += a[i][k] * a[i][k]; | |
| } | |
| f = a[i][l]; | |
| g = (f >= 0.0 ? -sqrt(h) : sqrt(h)); | |
| e[i] = scale * g; | |
| h -= f * g; | |
| a[i][l] = f - g; | |
| f = 0.0; | |
| for (j = 0; j < l + 1; j++) { | |
| /* Next statement can be omitted if eigenvectors not wanted */ | |
| a[j][i] = a[i][j] / h; | |
| g = 0.0; | |
| for (k = 0; k < j + 1; k++) | |
| g += a[j][k] * a[i][k]; | |
| for (k = j + 1; k < l + 1; k++) | |
| g += a[k][j] * a[i][k]; | |
| e[j] = g / h; | |
| f += e[j] * a[i][j]; | |
| } | |
| hh = f / (h + h); | |
| for (j = 0; j < l + 1; j++) { | |
| f = a[i][j]; | |
| e[j] = g = e[j] - hh * f; | |
| for (k = 0; k < j + 1; k++) | |
| a[j][k] -= (f * e[k] + g * a[i][k]); | |
| } | |
| } | |
| } else | |
| e[i] = a[i][l]; | |
| d[i] = h; | |
| } | |
| /* Next statement can be omitted if eigenvectors not wanted */ | |
| d[0] = 0.0; | |
| e[0] = 0.0; | |
| /* Contents of this loop can be omitted if eigenvectors not wanted except for statement d[i]=a[i][i]; */ | |
| for (i = 0; i < n; i++) { | |
| l = i; | |
| if (d[i] != 0.0) { | |
| for (j = 0; j < l; j++) { | |
| g = 0.0; | |
| for (k = 0; k < l; k++) | |
| g += a[i][k] * a[k][j]; | |
| for (k = 0; k < l; k++) | |
| a[k][j] -= g * a[k][i]; | |
| } | |
| } | |
| d[i] = a[i][i]; | |
| a[i][i] = 1.0; | |
| for (j = 0; j < l; j++) | |
| a[j][i] = a[i][j] = 0.0; | |
| } | |
| } | |
| /* calculate the eigenvalues and eigenvectors of a symmetric tridiagonal matrix */ | |
| static void tqli(double *d, double *e, int n, double **z) | |
| { | |
| int m, l, iter, i, k; | |
| double s, r, p, g, f, dd, c, b; | |
| for (i = 1; i < n; i++) | |
| e[i - 1] = e[i]; | |
| e[n - 1] = 0.0; | |
| for (l = 0; l < n; l++) { | |
| iter = 0; | |
| do { | |
| for (m = l; m < n - 1; m++) { | |
| dd = fabs(d[m]) + fabs(d[m + 1]); | |
| if (fabs(e[m]) + dd == dd) | |
| break; | |
| } | |
| if (m != l) { | |
| if (iter++ == 30) { | |
| fprintf(stderr, "[tqli] Too many iterations in tqli.\n"); | |
| break; | |
| } | |
| g = (d[l + 1] - d[l]) / (2.0 * e[l]); | |
| r = pythag(g, 1.0); | |
| g = d[m] - d[l] + e[l] / (g + SIGN(r, g)); | |
| s = c = 1.0; | |
| p = 0.0; | |
| for (i = m - 1; i >= l; i--) { | |
| f = s * e[i]; | |
| b = c * e[i]; | |
| e[i + 1] = (r = pythag(f, g)); | |
| if (r == 0.0) { | |
| d[i + 1] -= p; | |
| e[m] = 0.0; | |
| break; | |
| } | |
| s = f / r; | |
| c = g / r; | |
| g = d[i + 1] - p; | |
| r = (d[i] - g) * s + 2.0 * c * b; | |
| d[i + 1] = g + (p = s * r); | |
| g = c * r - b; | |
| /* Next loop can be omitted if eigenvectors not wanted */ | |
| for (k = 0; k < n; k++) { | |
| f = z[k][i + 1]; | |
| z[k][i + 1] = s * z[k][i] + c * f; | |
| z[k][i] = c * z[k][i] - s * f; | |
| } | |
| } | |
| if (r == 0.0 && i >= l) | |
| continue; | |
| d[l] -= p; | |
| e[l] = g; | |
| e[m] = 0.0; | |
| } | |
| } while (m != l); | |
| } | |
| } | |
| int n_eigen_symm(double *_a, int n, double *eval) | |
| { | |
| double **a, *e; | |
| int i; | |
| a = (double**)calloc(n, sizeof(void*)); | |
| e = (double*)calloc(n, sizeof(double)); | |
| for (i = 0; i < n; ++i) a[i] = _a + i * n; | |
| tred2(a, n, eval, e); | |
| tqli(eval, e, n, a); | |
| free(a); free(e); | |
| return 0; | |
| } | |
| #ifdef LH3_EIGEN_MAIN | |
| int main(void) | |
| { | |
| int i, j; | |
| static double u[5], v[5]; | |
| static double a[5][5] = {{1.0, 6.0, -3.0, -1.0, 7.0}, | |
| {8.0, -15.0, 18.0, 5.0, 4.0}, {-2.0, 11.0, 9.0, 15.0, 20.0}, | |
| {-13.0, 2.0, 21.0, 30.0, -6.0}, {17.0, 22.0, -5.0, 3.0, 6.0}}; | |
| static double b[5][5]={ {10.0,1.0,2.0,3.0,4.0}, | |
| {1.0,9.0,-1.0,2.0,-3.0}, | |
| {2.0,-1.0,7.0,3.0,-5.0}, | |
| {3.0,2.0,3.0,12.0,-1.0}, | |
| {4.0,-3.0,-5.0,-1.0,15.0}}; | |
| printf("MAT H IS:\n"); | |
| for (i = 0; i <= 4; i++) { | |
| for (j = 0; j <= 4; j++) | |
| printf("%13.7e ", a[i][j]); | |
| printf("\n"); | |
| } | |
| printf("\n"); | |
| n_eigen(a[0], 5, u, v); | |
| for (i = 0; i <= 4; i++) | |
| printf("%13.7e +J %13.7e\n", u[i], v[i]); | |
| printf("\n"); | |
| printf("\n\n"); | |
| n_eigen_symm(b[0], 5, u); | |
| for (i = 0; i <= 4; i++) | |
| printf("%13.7e\n", u[i]); | |
| printf("\n"); | |
| for (i = 0; i <= 4; i++) { | |
| for (j = 0; j <= 4; j++) | |
| printf("%12.6e ", b[i][j]); | |
| printf("\n"); | |
| } | |
| printf("\n"); | |
| return 0; | |
| } | |
| /* correct output: | |
| 4.2961008e+01 +J 0.0000000e+00 | |
| -6.6171383e-01 +J 0.0000000e+00 | |
| -1.5339638e+01 +J -6.7556929e+00 | |
| -1.5339638e+01 +J 6.7556929e+00 | |
| 1.9379983e+01 +J 0.0000000e+00 | |
| */ | |
| #endif |
This was not written by me. I forgot where it comes from. I only remember that the original source code doesn't come with license information.
I appreciate your reply. For what it's worth, I finished testing my version and it's finally mature and safe to use now. (I finally added automated tests and benchmarks. Again, please forgive the self-promotion.) License issues are a huge headache.
Hello,
I was wondering if this process had a generic name. Like the power method can be used to find eigenvalue but this seems to be different for the symmetric matrix in particular. Any help on a source for the theory would be great!
p.s. code works great, i checked it with MATLABS eig() function and results are identical. (This function uses the power method fyi)
Also checkout this version: https://github.com/swedishembedded/control/blob/main/src/linalg/eig_sym.c
Thanks
(License?)
(I have recently been having a hard time finding a simple eigenvector calculator with a permissive explicit license. Out of that frustration, I wrote this repository and released it in the public domain using the CC0 license. Please forgive the self-promotion.)