mathhook-core 0.2.0

Core mathematical engine for MathHook - expressions, algebra, and solving
Documentation
//! Eigenvalue and eigenvector computation algorithms
//!
//! This module provides core algorithms for computing eigenvalues and eigenvectors
//! of matrices, including both real and complex cases.

use crate::core::Expression;
use crate::matrices::types::*;
use crate::matrices::unified::Matrix;
use crate::simplify::Simplify;

impl Matrix {
    /// Compute eigenvalues and eigenvectors
    ///
    /// Returns eigenvalues and corresponding eigenvectors for real matrices.
    /// For matrices with complex eigenvalues, use `complex_eigen_decomposition`.
    ///
    /// # Examples
    ///
    /// ```
    /// use mathhook_core::matrices::Matrix;
    /// use mathhook_core::Expression;
    ///
    /// let matrix = Matrix::diagonal(vec![
    ///     Expression::integer(2),
    ///     Expression::integer(3)
    /// ]);
    ///
    /// let eigen = matrix.eigen_decomposition().unwrap();
    /// assert_eq!(eigen.eigenvalues.len(), 2);
    /// assert_eq!(eigen.eigenvalues[0], Expression::integer(2));
    /// assert_eq!(eigen.eigenvalues[1], Expression::integer(3));
    /// ```
    pub fn eigen_decomposition(&self) -> Option<EigenDecomposition> {
        match self {
            Matrix::Identity(data) => {
                // Identity matrix: all eigenvalues are 1
                let eigenvalues = vec![Expression::integer(1); data.size];
                Some(EigenDecomposition {
                    eigenvalues,
                    eigenvectors: Matrix::identity(data.size),
                })
            }
            Matrix::Zero(data) => {
                // Zero matrix: all eigenvalues are 0
                let eigenvalues = vec![Expression::integer(0); data.rows];
                Some(EigenDecomposition {
                    eigenvalues,
                    eigenvectors: Matrix::identity(data.rows),
                })
            }
            Matrix::Scalar(data) => {
                // Scalar matrix cI: all eigenvalues are c
                let eigenvalues = vec![data.scalar_value.clone(); data.size];
                Some(EigenDecomposition {
                    eigenvalues,
                    eigenvectors: Matrix::identity(data.size),
                })
            }
            Matrix::Diagonal(data) => {
                // Diagonal matrix: eigenvalues are diagonal elements
                Some(EigenDecomposition {
                    eigenvalues: data.diagonal_elements.clone(),
                    eigenvectors: Matrix::identity(data.diagonal_elements.len()),
                })
            }
            _ => {
                // General eigenvalue computation using characteristic polynomial
                self.compute_general_eigenvalues()
            }
        }
    }

    /// Compute complex eigenvalues and eigenvectors
    ///
    /// Handles matrices that may have complex eigenvalues and eigenvectors.
    ///
    /// # Examples
    ///
    /// ```
    /// use mathhook_core::matrices::Matrix;
    ///
    /// let matrix = Matrix::from_arrays([
    ///     [0, -1],
    ///     [1, 0]
    /// ]);
    ///
    /// // This matrix has complex eigenvalues ±i
    /// let complex_eigen = matrix.complex_eigen_decomposition();
    /// // Returns None as complex eigenvalue computation requires specialized algorithms
    /// assert!(complex_eigen.is_none());
    /// ```
    pub fn complex_eigen_decomposition(&self) -> Option<ComplexEigenDecomposition> {
        // For matrices with real entries that have complex eigenvalues
        match self {
            Matrix::Identity(_) | Matrix::Zero(_) | Matrix::Scalar(_) | Matrix::Diagonal(_) => {
                // These special matrices have real eigenvalues only
                None
            }
            _ => {
                // For general matrices, would implement complex eigenvalue algorithms
                // such as QR algorithm with complex arithmetic
                None
            }
        }
    }

    /// Compute only eigenvalues (faster than full decomposition)
    ///
    /// # Examples
    ///
    /// ```
    /// use mathhook_core::matrices::Matrix;
    /// use mathhook_core::Expression;
    ///
    /// let matrix = Matrix::scalar(3, Expression::integer(5));
    /// let eigenvals = matrix.eigenvalues();
    /// assert_eq!(eigenvals.len(), 3);
    /// assert_eq!(eigenvals[0], Expression::integer(5));
    /// ```
    pub fn eigenvalues(&self) -> Vec<Expression> {
        match self {
            Matrix::Identity(data) => vec![Expression::integer(1); data.size],
            Matrix::Zero(data) => vec![Expression::integer(0); data.rows],
            Matrix::Scalar(data) => vec![data.scalar_value.clone(); data.size],
            Matrix::Diagonal(data) => data.diagonal_elements.clone(),
            _ => {
                // Compute eigenvalues for general matrices
                if let Some(eigen) = self.eigen_decomposition() {
                    eigen.eigenvalues
                } else {
                    vec![]
                }
            }
        }
    }

    /// Compute general eigenvalues for arbitrary matrices
    pub(crate) fn compute_general_eigenvalues(&self) -> Option<EigenDecomposition> {
        let (n, _) = self.dimensions();

        // For small matrices, use direct computation
        if n == 1 {
            let eigenvalue = self.get_element(0, 0);
            return Some(EigenDecomposition {
                eigenvalues: vec![eigenvalue],
                eigenvectors: Matrix::identity(1),
            });
        }

        if n == 2 {
            return self.compute_2x2_eigenvalues();
        }

        // For larger matrices, use power iteration method
        self.power_iteration_eigenvalues()
    }

    /// Compute eigenvalues for 2x2 matrices
    pub(crate) fn compute_2x2_eigenvalues(&self) -> Option<EigenDecomposition> {
        let (rows, cols) = self.dimensions();
        if rows != 2 || cols != 2 {
            return None;
        }

        // For 2x2 matrix [[a, b], [c, d]]
        let a = self.get_element(0, 0);
        let b = self.get_element(0, 1);
        let c = self.get_element(1, 0);
        let d = self.get_element(1, 1);

        // Characteristic equation: λ² - (a+d)λ + (ad-bc) = 0
        let trace = Expression::add(vec![a.clone(), d.clone()]).simplify();
        let det = Expression::add(vec![
            Expression::mul(vec![a, d]),
            Expression::mul(vec![Expression::integer(-1), b, c]),
        ])
        .simplify();

        // Use quadratic formula: λ = (trace ± √(trace² - 4*det)) / 2
        let discriminant = Expression::add(vec![
            Expression::pow(trace.clone(), Expression::integer(2)),
            Expression::mul(vec![Expression::integer(-4), det]),
        ])
        .simplify();

        let sqrt_discriminant = Expression::pow(discriminant, Expression::rational(1, 2));

        // Use canonical form for division: a / b = a * b^(-1)
        let lambda1 = Expression::mul(vec![
            Expression::add(vec![trace.clone(), sqrt_discriminant.clone()]),
            Expression::pow(Expression::integer(2), Expression::integer(-1)),
        ])
        .simplify();

        let lambda2 = Expression::mul(vec![
            Expression::add(vec![
                trace,
                Expression::mul(vec![Expression::integer(-1), sqrt_discriminant]),
            ]),
            Expression::pow(Expression::integer(2), Expression::integer(-1)),
        ])
        .simplify();

        Some(EigenDecomposition {
            eigenvalues: vec![lambda1, lambda2],
            eigenvectors: Matrix::identity(2), // Simplified - would compute actual eigenvectors
        })
    }

    /// Check if matrix is diagonalizable
    ///
    /// # Examples
    ///
    /// ```
    /// use mathhook_core::matrices::Matrix;
    /// use mathhook_core::Expression;
    ///
    /// let diagonal = Matrix::diagonal(vec![
    ///     Expression::integer(1),
    ///     Expression::integer(2),
    ///     Expression::integer(3)
    /// ]);
    /// assert!(diagonal.is_diagonalizable());
    ///
    /// let identity = Matrix::identity(3);
    /// assert!(identity.is_diagonalizable());
    /// ```
    pub fn is_diagonalizable(&self) -> bool {
        match self {
            Matrix::Identity(_) | Matrix::Zero(_) | Matrix::Scalar(_) | Matrix::Diagonal(_) => true,
            Matrix::Symmetric(_) => true, // Symmetric matrices are always diagonalizable
            _ => {
                // Check if matrix is diagonalizable by examining eigenvalue multiplicities
                let eigenvals = self.eigenvalues();
                if eigenvals.len() <= 1 {
                    return true;
                }

                // Check for distinct eigenvalues (simplified)
                for i in 0..eigenvals.len() {
                    for j in (i + 1)..eigenvals.len() {
                        if eigenvals[i] == eigenvals[j] {
                            return false; // Repeated eigenvalue found
                        }
                    }
                }
                true
            }
        }
    }

    /// Power iteration method for finding dominant eigenvalue
    /// # Examples
    ///
    /// ```
    /// use mathhook_core::matrices::Matrix;
    /// use mathhook_core::{Expression, expr};
    ///
    /// let matrix = Matrix::diagonal(vec![expr!(2), expr!(3)]);
    /// let eigen = matrix.power_iteration_eigenvalues().unwrap();
    /// // Power iteration returns the dominant (largest) eigenvalue
    /// // For symbolic computation, the result may be a complex expression
    /// assert_eq!(eigen.eigenvalues.len(), 1);
    /// // The eigenvalue exists but may not simplify to integer form symbolically
    /// assert!(!eigen.eigenvalues[0].is_zero());
    /// ```
    pub fn power_iteration_eigenvalues(&self) -> Option<EigenDecomposition> {
        let (n, _) = self.dimensions();

        // Start with random vector (simplified as [1, 1, ..., 1])
        let mut v: Vec<Expression> = vec![Expression::integer(1); n];
        let max_iterations = 10; // Reduced iterations for symbolic computation
        let _tolerance = Expression::rational(1, 100); // More relaxed tolerance

        for iteration in 0..max_iterations {
            // v_new = A * v
            let mut v_new = vec![Expression::integer(0); n];
            for (i, v_new_elem) in v_new.iter_mut().enumerate().take(n) {
                let mut sum = Expression::integer(0);
                for (j, v_elem) in v.iter().enumerate().take(n) {
                    let a_ij = self.get_element(i, j);
                    sum = Expression::add(vec![sum, Expression::mul(vec![a_ij, v_elem.clone()])])
                        .simplify();
                }
                *v_new_elem = sum;
            }

            // Normalize v_new
            let norm = self.compute_vector_norm(&v_new);
            if norm.is_zero() {
                break;
            }

            for v_new_elem in v_new.iter_mut().take(n) {
                // Use canonical form for division: a / b = a * b^(-1)
                *v_new_elem = Expression::mul(vec![
                    v_new_elem.clone(),
                    Expression::pow(norm.clone(), Expression::integer(-1)),
                ])
                .simplify();
            }

            // Simplified convergence check - just check if we've done enough iterations
            // For symbolic computation, exact convergence is difficult
            if iteration >= 3 {
                v = v_new;
                break;
            }

            v = v_new;
        }

        // Compute dominant eigenvalue: λ = v^T * A * v
        let mut av = vec![Expression::integer(0); n];
        for (i, av_elem) in av.iter_mut().enumerate().take(n) {
            let mut sum = Expression::integer(0);
            for (j, v_elem) in v.iter().enumerate().take(n) {
                let a_ij = self.get_element(i, j);
                sum = Expression::add(vec![sum, Expression::mul(vec![a_ij, v_elem.clone()])])
                    .simplify();
            }
            *av_elem = sum;
        }

        let mut eigenvalue = Expression::integer(0);
        for i in 0..n {
            eigenvalue = Expression::add(vec![
                eigenvalue,
                Expression::mul(vec![v[i].clone(), av[i].clone()]),
            ])
            .simplify();
        }

        // Return single dominant eigenvalue
        Some(EigenDecomposition {
            eigenvalues: vec![eigenvalue],
            eigenvectors: Matrix::dense(vec![v]), // Single eigenvector as row
        })
    }

    /// Compute norm of a vector
    pub fn compute_vector_norm(&self, v: &[Expression]) -> Expression {
        let sum_of_squares: Vec<Expression> = v
            .iter()
            .map(|x| Expression::pow(x.clone(), Expression::integer(2)))
            .collect();

        let sum = Expression::add(sum_of_squares).simplify();
        Expression::pow(sum, Expression::rational(1, 2))
    }

    /// Check if a value is small (simplified convergence test)
    pub fn is_small_value(&self, value: &Expression, tolerance: &Expression) -> bool {
        // Simplified check - in practice would need numerical comparison
        value.is_zero() || *value == *tolerance
    }
}