Menu

simulationCases/testEigenDecomposition.c

Eigen-Decomposition Test (3x3 Symmetric)

Validates compute_eigensystem_symmetric_3x3() using diagnostic checks for orthonormality, diagonalization, and reconstruction.

Usage

./testEigenDecomposition a11 a12 a13 a22 a23 a33

If no arguments are provided, a default test matrix is used.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "../src-local/eigen_decomposition.h"

#define EPSILON 1e-9
#define RELATIVE_TOLERANCE 1e-6
#define sq(x) ((x)*(x))

typedef struct { long double x, y, z; } pseudo_v3d;
typedef struct { pseudo_v3d x, y, z; } pseudo_t3d;

diagonalization_3D()

Wrapper around compute_eigensystem_symmetric_3x3() that fills Lambda and R for the test structures.

static void diagonalization_3D(pseudo_v3d* Lambda, pseudo_t3d* R,
                               pseudo_t3d* A)
{
    // Check if the matrix is already diagonal
    if (sq(A->x.y) + sq(A->x.z) + sq(A->y.z) < 1e-15) {
        R->x.x = R->y.y = R->z.z = 1.;
        R->y.x = R->x.y = R->z.x = R->x.z = R->z.y = R->y.z = 0.;
        Lambda->x = A->x.x; Lambda->y = A->y.y; Lambda->z = A->z.z;
        return;
    }

    // Compute eigenvalues using the eigen_decomposition function
    double matrix[3][3] = {
        {A->x.x, A->x.y, A->x.z},
        {A->x.y, A->y.y, A->y.z},
        {A->x.z, A->y.z, A->z.z}
    };
    double eigenvectors[3][3];
    double eigenvalues[3];

    compute_eigensystem_symmetric_3x3(matrix, eigenvectors, eigenvalues);

    // Corrected: Store eigenvalues and eigenvectors with proper index mapping
    Lambda->x = eigenvalues[0];
    Lambda->y = eigenvalues[1];
    Lambda->z = eigenvalues[2];

    R->x.x = eigenvectors[0][0]; R->x.y = eigenvectors[0][1]; R->x.z = eigenvectors[0][2];
    R->y.x = eigenvectors[1][0]; R->y.y = eigenvectors[1][1]; R->y.z = eigenvectors[1][2];
    R->z.x = eigenvectors[2][0]; R->z.y = eigenvectors[2][1]; R->z.z = eigenvectors[2][2];
}

Prints a 3x3 tensor in row-major form.

void print_pseudo_t3d(const char* name, const pseudo_t3d* matrix) {
    printf("%s:\n", name);
    printf("%12.9Lf %12.9Lf %12.9Lf\n", matrix->x.x, matrix->x.y, matrix->x.z);
    printf("%12.9Lf %12.9Lf %12.9Lf\n", matrix->y.x, matrix->y.y, matrix->y.z);
    printf("%12.9Lf %12.9Lf %12.9Lf\n", matrix->z.x, matrix->z.y, matrix->z.z);
    printf("\n");
}

Prints a 3-vector.

void print_pseudo_v3d(const char* name, const pseudo_v3d* vector) {
    printf("%s: %12.9Lf %12.9Lf %12.9Lf\n\n", name, vector->x, vector->y, vector->z);
}

verify_orthonormality()

Checks that eigenvectors are unit length and mutually orthogonal.

int verify_orthonormality(const pseudo_t3d* R) {
    for (int i = 0; i < 3; i++) {
        long double norm = sq(((long double*)R)[i]) + sq(((long double*)R)[i+3]) + sq(((long double*)R)[i+6]);
        if (fabsl(norm - 1.0L) > EPSILON) {
            printf("Eigenvector %d is not normalized. Norm = %Lf\n", i, sqrtl(norm));
            return 0;
        }

        for (int j = i + 1; j < 3; j++) {
            long double dot_product = ((long double*)R)[i] * ((long double*)R)[j] +
                                      ((long double*)R)[i+3] * ((long double*)R)[j+3] +
                                      ((long double*)R)[i+6] * ((long double*)R)[j+6];
            if (fabsl(dot_product) > EPSILON) {
                printf("Eigenvectors %d and %d are not orthogonal. Dot product = %Lf\n", i, j, dot_product);
                return 0;
            }
        }
    }
    return 1;
}

verify_diagonalization()

Checks that R^T * A * R is diagonal with the provided eigenvalues.

int verify_diagonalization(const pseudo_t3d* A, const pseudo_t3d* R, const pseudo_v3d* Lambda) {
    pseudo_t3d temp, diagonalized;

    // Compute R^T * A * R
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            ((long double*)&temp)[i*3 + j] = 0;
            for (int k = 0; k < 3; k++) {
                ((long double*)&temp)[i*3 + j] += ((long double*)R)[k*3 + i] * ((long double*)A)[k*3 + j];
            }
        }
    }

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            ((long double*)&diagonalized)[i*3 + j] = 0;
            for (int k = 0; k < 3; k++) {
                ((long double*)&diagonalized)[i*3 + j] += ((long double*)&temp)[i*3 + k] * ((long double*)R)[k*3 + j];
            }
        }
    }

    // Print the diagonalized matrix
    print_pseudo_t3d("Diagonalized matrix: (R^T * A * R)", &diagonalized);

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (i == j) {
                long double relative_error = fabsl(((long double*)&diagonalized)[i*3 + j] - ((long double*)Lambda)[i]) /
                                             (fabsl(((long double*)Lambda)[i]) + EPSILON);
                if (relative_error > RELATIVE_TOLERANCE) {
                    printf("Diagonal element (%d,%d) does not match eigenvalue. %Lf != %Lf (relative error: %Lf)\n",
                           i, j, ((long double*)&diagonalized)[i*3 + j], ((long double*)Lambda)[i], relative_error);
                }
            } else {
                if (fabsl(((long double*)&diagonalized)[i*3 + j]) > EPSILON) {
                    printf("Off-diagonal element (%d,%d) is not zero. Value = %Lf\n",
                           i, j, ((long double*)&diagonalized)[i*3 + j]);
                }
            }
        }
    }
    return 1;
}

verify_eigenpairs()

Checks A * v = lambda * v for each eigenpair.

int verify_eigenpairs(const pseudo_t3d* A, const pseudo_t3d* R, const pseudo_v3d* Lambda) {
    printf("Verifying A * v = λ * v for each eigenpair:\n");
    for (int i = 0; i < 3; i++) {
        pseudo_v3d Av = {0}, lambda_v = {0};

        // Compute A * v
        for (int j = 0; j < 3; j++) {
            ((double*)&Av)[j] = ((double*)A)[j*3] * ((double*)R)[i] +
                                ((double*)A)[j*3+1] * ((double*)R)[i+3] +
                                ((double*)A)[j*3+2] * ((double*)R)[i+6];
        }

        // Compute λ * v
        for (int j = 0; j < 3; j++) {
            ((double*)&lambda_v)[j] = ((double*)Lambda)[i] * ((double*)R)[i+j*3];
        }

        printf("Eigenpair %d:\n", i + 1);
        printf("A * v = %8.4Lf %8.4Lf %8.4Lf\n", Av.x, Av.y, Av.z);
        printf("λ * v = %8.4Lf %8.4Lf %8.4Lf\n", lambda_v.x, lambda_v.y, lambda_v.z);

        // Check if A * v ≈ λ * v
        for (int j = 0; j < 3; j++) {
            if (fabs(((double*)&Av)[j] - ((double*)&lambda_v)[j]) > EPSILON) {
                printf("Mismatch for eigenpair %d, component %d\n", i + 1, j + 1);
            }
        }
        printf("Verification A * v = λ * v for each eigenpair done. See the messages above!\n\n");
    }
    return 1;
}

verify_eigendecomposition()

Runs orthonormality, diagonalization, and eigenpair checks.

int verify_eigendecomposition(const pseudo_t3d* A, const pseudo_t3d* R, const pseudo_v3d* Lambda) {
    if (!verify_orthonormality(R)) {
        printf("Eigenvectors are not orthonormal.\n");
    } else {
        printf("Eigenvectors are orthonormal.\n");
    }
    printf("\n");

    if (!verify_diagonalization(A, R, Lambda)) {
        printf("Matrix is not properly diagonalized.\n");
    } else {
        printf("Matrix is properly diagonalized.\n");
    }

    if (!verify_eigenpairs(A, R, Lambda)) {
        printf("Eigenpair verification failed.\n");
    } else {
        printf("Eigenpair verification passed.\n");
    }

    printf("Eigendecomposition verification completed. See the messages above!\n");
    return 1;
}

verify_reconstruction()

Checks reconstruction A = R * Lambda * R^T.

int verify_reconstruction(const pseudo_t3d* A, const pseudo_t3d* R, const pseudo_v3d* Lambda) {
    pseudo_t3d Lambda_diag, temp, A_reconstructed;

    // Create diagonal matrix from Lambda
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            ((long double*)&Lambda_diag)[i*3 + j] = (i == j) ? ((long double*)Lambda)[i] : 0.0L;
        }
    }

    // Compute R * Lambda
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            ((long double*)&temp)[i*3 + j] = 0;
            for (int k = 0; k < 3; k++) {
                ((long double*)&temp)[i*3 + j] += ((long double*)R)[i*3 + k] * ((long double*)&Lambda_diag)[k*3 + j];
            }
        }
    }

    // Compute (R * Lambda) * R^T
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            ((long double*)&A_reconstructed)[i*3 + j] = 0;
            for (int k = 0; k < 3; k++) {
                ((long double*)&A_reconstructed)[i*3 + j] += ((long double*)&temp)[i*3 + k] * ((long double*)R)[j*3 + k];
            }
        }
    }

    print_pseudo_t3d("Reconstructed A = R * Lambda * R^T", &A_reconstructed);

    // Compare original A with reconstructed A
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            long double diff = fabsl(((long double*)A)[i*3 + j] - ((long double*)&A_reconstructed)[i*3 + j]);
            if (diff > EPSILON) {
                printf("Mismatch at (%d,%d): original = %Lf, reconstructed = %Lf, difference = %Lf\n",
                       i, j, ((long double*)A)[i*3 + j], ((long double*)&A_reconstructed)[i*3 + j], diff);
            }
        }
    }

    printf("A = R * Lambda * R^T verification completed. See the messages above!\n");
    return 1;
}

main()

Builds the test matrix, runs eigen-decomposition, and prints checks.

int main(int argc, char *argv[]) {
    pseudo_t3d A, R;
    pseudo_v3d Lambda;

    // Initialize A with the given values

    /*
    this is a diagonal not I matrix
    */
    // A.x.x = 1;  A.x.y = 0; A.x.z = 0;
    // A.y.x = 0; A.y.y = 2;  A.y.z = 0;
    // A.z.x = 0;  A.z.y = 0;  A.z.z = 3;

    /*
    this is a identity matrix
    */
    // A.x.x = 1;  A.x.y = 0; A.x.z = 0;
    // A.y.x = 0; A.y.y = 1;  A.y.z = 0;
    // A.z.x = 0;  A.z.y = 0;  A.z.z = 1;

    /*
    this is a non-diagonal matrix
    */
    // A.x.x = 1;  A.x.y = 2; A.x.z = 3;
    // A.y.x = 2; A.y.y = 4;  A.y.z = 5;
    // A.z.x = 3;  A.z.y = 5;  A.z.z = -6;

    /*
    this is a small matrix
    */
    // A.x.x = 1e-6;  A.x.y = 0; A.x.z = 0;
    // A.y.x = 0; A.y.y = 1e-12;  A.y.z = 0;
    // A.z.x = 0;  A.z.y = 0;  A.z.z = 1e-10;

    /*
    this is a large matrix
    */
    // A.x.x = 100;  A.x.y = 100; A.x.z = 10;
    // A.y.x = 100; A.y.y = 10;  A.y.z = 1;
    // A.z.x = 10;  A.z.y = 1;  A.z.z = 0.1;

    if (argc != 7) {
        A.x.x = 1;  A.x.y = 2; A.x.z = 3;
        A.y.x = 2; A.y.y = 4;  A.y.z = 5;
        A.z.x = 3;  A.z.y = 5;  A.z.z = -6;
    } else {
        // get Aij from terminal
        A.x.x = atof(argv[1]);  A.x.y = atof(argv[2]); A.x.z = atof(argv[3]);
        A.y.x = atof(argv[2]); A.y.y = atof(argv[4]);  A.y.z = atof(argv[5]);
        A.z.x = atof(argv[3]);  A.z.y = atof(argv[5]);  A.z.z = atof(argv[6]);
    }

    print_pseudo_t3d("Original matrix A", &A);

    diagonalization_3D(&Lambda, &R, &A);

    print_pseudo_t3d("Eigenvectors R", &R);
    print_pseudo_v3d("Eigenvalues Lambda", &Lambda);

    verify_eigendecomposition(&A, &R, &Lambda);

    // lastly check if A = R * Lambda * R^T
    verify_reconstruction(&A, &R, &Lambda);


    return 0;
}