src-local/eigen_decomposition.h
Matrix Eigenvalue Solver
This module provides algorithms for computing eigenvalues and eigenvectors of 3x3 symmetric matrices, which are commonly encountered in computational fluid dynamics, solid mechanics, and other physics-based simulations.
Mathematical Background
The implementation uses the Householder transformation to reduce a symmetric matrix to tridiagonal form, followed by the QL algorithm with implicit shifts to compute the eigenvalues and eigenvectors.
For a 3x3 symmetric matrix, these methods are particularly efficient and numerically stable, providing accurate results even for matrices with closely spaced eigenvalues.
Tridiagonalize a 3x3 Symmetric Matrix
Reduces a 3x3 symmetric matrix to tridiagonal form using the Householder method, which applies a series of orthogonal transformations to eliminate elements below the subdiagonal.
- Parameters:
- matrix[in]: Input 3x3 symmetric matrix to be tridiagonalized
- eigenvectors[out]: Orthogonal matrix of Householder vectors
- diagonal[out]: Diagonal elements of the resulting tridiagonal matrix
- subdiagonal[out]: Subdiagonal elements of the tridiagonal matrix
- Implementation Notes:
- The original matrix is preserved
- The eigenvectors matrix is initialized to identity and then transformed
- The algorithm exploits the symmetry of the input matrix
#define SQUARE(x) ((x)*(x))36
static void tridiagonalize_symmetric_3x3(double matrix[3][3],
double eigenvectors[3][3],
double diagonal[3],
double subdiagonal[2])
{
const int size = 3;
double householder_vector[size], temp_vector[size];
double omega, scale, sigma, tau;
// Initialize eigenvectors to the identity matrix
for (int i = 0; i < size; i++) {
eigenvectors[i][i] = 1.0;
for (int j = 0; j < i; j++)
eigenvectors[i][j] = eigenvectors[j][i] = 0.0;
}
// Compute the first Householder reflection
scale = SQUARE(matrix[0][1]) + SQUARE(matrix[0][2]);
sigma = (matrix[0][1] > 0) ? -sqrt(scale) : sqrt(scale);
subdiagonal[0] = sigma;
tau = sigma * matrix[0][1];
householder_vector[1] = matrix[0][1] - sigma;
householder_vector[2] = matrix[0][2];
omega = scale - tau;
if (omega > 0.0) {
omega = 1.0 / omega;
sigma = 0.0;
for (int i = 1; i < size; i++) {
tau = matrix[1][i] * householder_vector[1] +
matrix[i][2] * householder_vector[2];
temp_vector[i] = omega * tau;
sigma += householder_vector[i] * tau;
}
sigma *= 0.5 * SQUARE(omega);
for (int i = 1; i < size; i++)
temp_vector[i] -= sigma * householder_vector[i];
diagonal[0] = matrix[0][0];
diagonal[1] = matrix[1][1] - 2.0 * temp_vector[1] * householder_vector[1];
diagonal[2] = matrix[2][2] - 2.0 * temp_vector[2] * householder_vector[2];
for (int j = 1; j < size; j++) {
tau = omega * householder_vector[j];
for (int i = 1; i < size; i++)
eigenvectors[i][j] -= tau * householder_vector[i];
}
subdiagonal[1] = matrix[1][2] - temp_vector[1] * householder_vector[2] -
householder_vector[1] * temp_vector[2];
}
else {
for (int i = 0; i < size; i++)
diagonal[i] = matrix[i][i];
subdiagonal[1] = matrix[1][2];
}
}
### Compute Eigenvalues and Eigenvectors of a 3x3 Symmetric Matrix
Calculates the complete eigensystem (eigenvalues and eigenvectors) of a 3x3 symmetric matrix using the QL algorithm with implicit shifts, after first reducing the matrix to tridiagonal form.
Parameters:
- matrix[in]: Input 3x3 symmetric matrix whose eigensystem will be computed
- eigenvectors[out]: Matrix whose columns are the eigenvectors
- eigenvalues[out]: Array containing the eigenvalues
Return Value:
- 0: Computation successful
- -1: Algorithm failed to converge within the maximum number of iterations
Algorithm Details:
- First checks if the matrix is already diagonal
- If not, tridiagonalizes the matrix using Householder transformations
- Applies the QL algorithm with implicit shifts to compute eigenvalues
- Maximum of 30 iterations are allowed for convergence
- Numerical tolerance of 1e-15 is used for detecting diagonal matrices
Usage Example:
double matrix[3][3] = {{1.0, 0.5, 0.3}, {0.5, 2.0, 0.1}, {0.3, 0.1, 3.0}}; double eigenvectors[3][3]; double eigenvalues[3]; int result = compute_eigensystem_symmetric_3x3(matrix, eigenvectors, eigenvalues); if (result == 0) { // Computation successful }
static int compute_eigensystem_symmetric_3x3(double matrix[3][3],
double eigenvectors[3][3],
double eigenvalues[3])
{
const int size = 3;
const int max_iterations = 30;
double subdiagonal[3];
double g, r, p, f, b, s, c, t;
int iteration_count;
int m;
// Check for diagonal matrix with unit entries
if (SQUARE(matrix[0][1]) < 1e-15 && SQUARE(matrix[0][2]) < 1e-15 &&
SQUARE(matrix[1][2]) < 1e-15) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
eigenvectors[i][j] = (i == j) ? 1.0 : 0.0;
}
eigenvalues[i] = matrix[i][i];
}
return 0;
}
tridiagonalize_symmetric_3x3(matrix, eigenvectors, eigenvalues, subdiagonal);
for (int l = 0; l < size - 1; l++) {
iteration_count = 0;
while (1) {
for (m = l; m <= size - 2; m++) {
g = fabs(eigenvalues[m]) + fabs(eigenvalues[m+1]);
if (fabs(subdiagonal[m]) + g == g)
break;
}
if (m == l)
break;
if (iteration_count++ >= max_iterations)
return -1;
g = (eigenvalues[l+1] - eigenvalues[l]) / (2.0 * subdiagonal[l]);
r = sqrt(SQUARE(g) + 1.0);
g = eigenvalues[m] - eigenvalues[l] + subdiagonal[l] /
(g + (g > 0 ? fabs(r) : -fabs(r)));
s = c = 1.0;
p = 0.0;
for (int i = m - 1; i >= l; i--) {
f = s * subdiagonal[i];
b = c * subdiagonal[i];
if (fabs(f) > fabs(g)) {
c = g / f;
r = sqrt(SQUARE(c) + 1.0);
subdiagonal[i+1] = f * r;
c *= (s = 1.0 / r);
}
else {
s = f / g;
r = sqrt(SQUARE(s) + 1.0);
subdiagonal[i+1] = g * r;
s *= (c = 1.0 / r);
}
g = eigenvalues[i+1] - p;
r = (eigenvalues[i] - g) * s + 2.0 * c * b;
p = s * r;
eigenvalues[i+1] = g + p;
g = c * r - b;
for (int k = 0; k < size; k++) {
t = eigenvectors[k][i+1];
eigenvectors[k][i+1] = s * eigenvectors[k][i] + c * t;
eigenvectors[k][i] = c * eigenvectors[k][i] - s * t;
}
}
eigenvalues[l] -= p;
subdiagonal[l] = g;
subdiagonal[m] = 0.0;
}
}
return 0;
}