pub mod covariance;
pub mod dense;
pub(crate) mod regularization;
pub mod schur;
pub mod sparse;
pub mod utils;
use crate::core::{VarKey, variable::ManifoldVariable};
use crate::error::ErrorLogging;
use faer::Mat;
use slotmap::{SecondaryMap, SlotMap};
use std::collections::HashSet;
use std::fmt::{self, Debug, Display, Formatter};
use thiserror::Error;
pub use schur::{
BlockSpan, ColSlot, EliminatedBlocks, SchurOrdering, SchurPartition, SchurPreconditioner,
};
pub use sparse::{
ChunkLayout, ChunkedSchurEliminator, ExplicitSchurVariant, ExplicitSparseSchur,
ImplicitSparseSchur, PatternFingerprint, ReducedSystem, SparseCholeskySolver, SparseQRSolver,
};
pub use dense::{DenseCholeskySolver, DenseQRSolver, ExplicitDenseSchur};
pub use covariance::{Covariance, CovarianceAlgorithm, CovarianceError, CovarianceOptions};
pub use crate::linearizer::cpu::{DenseMode, LinearizationMode, SparseMode};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum JacobianMode {
#[default]
Sparse,
Dense,
}
#[non_exhaustive]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum LinearSolverType {
#[default]
SparseCholesky,
SparseQR,
ExplicitSparseSchur,
ImplicitSparseSchur,
DenseCholesky,
DenseQR,
ExplicitDenseSchur,
}
impl Display for LinearSolverType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
LinearSolverType::SparseCholesky => write!(f, "Sparse Cholesky"),
LinearSolverType::SparseQR => write!(f, "Sparse QR"),
LinearSolverType::ExplicitSparseSchur => write!(f, "Explicit Sparse Schur Complement"),
LinearSolverType::ImplicitSparseSchur => write!(f, "Implicit Sparse Schur Complement"),
LinearSolverType::DenseCholesky => write!(f, "Dense Cholesky"),
LinearSolverType::DenseQR => write!(f, "Dense QR"),
LinearSolverType::ExplicitDenseSchur => write!(f, "Explicit Dense Schur Complement"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Damping {
pub lambda: f64,
pub min_diagonal: f64,
pub max_diagonal: f64,
}
impl Damping {
pub fn new(lambda: f64, min_diagonal: f64, max_diagonal: f64) -> LinAlgResult<Self> {
if !lambda.is_finite() || lambda < 0.0 {
return Err(LinAlgError::InvalidInput(format!(
"damping lambda must be finite and non-negative, got {lambda}"
))
.log());
}
if !min_diagonal.is_finite() || min_diagonal <= 0.0 {
return Err(LinAlgError::InvalidInput(format!(
"min_diagonal must be finite and positive, got {min_diagonal}"
))
.log());
}
if max_diagonal.is_nan() || max_diagonal <= 0.0 {
return Err(LinAlgError::InvalidInput(format!(
"max_diagonal must be positive, got {max_diagonal}"
))
.log());
}
if min_diagonal > max_diagonal {
return Err(LinAlgError::InvalidInput(format!(
"min_diagonal ({min_diagonal}) must not exceed max_diagonal ({max_diagonal})"
))
.log());
}
Ok(Self {
lambda,
min_diagonal,
max_diagonal,
})
}
pub fn identity(lambda: f64) -> Self {
Self {
lambda,
min_diagonal: 1.0,
max_diagonal: 1.0,
}
}
#[inline]
pub fn diagonal_term(&self, hessian_diagonal: f64) -> f64 {
self.lambda * hessian_diagonal.clamp(self.min_diagonal, self.max_diagonal)
}
}
#[derive(Debug, Clone, Error)]
pub enum LinAlgError {
#[error("Matrix factorization failed: {0}")]
FactorizationFailed(String),
#[error("Singular matrix detected: {0}")]
SingularMatrix(String),
#[error("Failed to create sparse matrix: {0}")]
SparseMatrixCreation(String),
#[error("Matrix conversion failed: {0}")]
MatrixConversion(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Invalid solver state: {0}")]
InvalidState(String),
}
pub type LinAlgResult<T> = Result<T, LinAlgError>;
pub trait StructureAware {
fn initialize_structure(
&mut self,
variables: &SlotMap<VarKey, Box<dyn ManifoldVariable>>,
variable_index_map: &SecondaryMap<VarKey, usize>,
schur_landmark_keys: &HashSet<VarKey>,
) -> LinAlgResult<()>;
}
pub trait LinearSolver<M: LinearizationMode> {
fn solve_normal_equation(
&mut self,
residuals: &Mat<f64>,
jacobian: &M::Jacobian,
) -> LinAlgResult<Mat<f64>>;
fn solve_augmented_equation(
&mut self,
residuals: &Mat<f64>,
jacobian: &M::Jacobian,
damping: &Damping,
) -> LinAlgResult<Mat<f64>>;
fn hessian_vec_product(&self, v: &Mat<f64>) -> Option<Mat<f64>>;
fn get_hessian(&self) -> Option<&M::Hessian> {
None
}
fn get_gradient(&self) -> Option<&Mat<f64>>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ErrorLogging;
#[test]
fn test_jacobian_mode_default_is_sparse() {
assert_eq!(JacobianMode::default(), JacobianMode::Sparse);
}
#[test]
fn test_jacobian_mode_equality() {
assert_eq!(JacobianMode::Sparse, JacobianMode::Sparse);
assert_eq!(JacobianMode::Dense, JacobianMode::Dense);
assert_ne!(JacobianMode::Sparse, JacobianMode::Dense);
}
#[test]
fn test_linear_solver_type_default_is_cholesky() {
assert_eq!(
LinearSolverType::default(),
LinearSolverType::SparseCholesky
);
}
#[test]
fn test_linear_solver_type_display_all_variants() {
assert_eq!(
format!("{}", LinearSolverType::SparseCholesky),
"Sparse Cholesky"
);
assert_eq!(format!("{}", LinearSolverType::SparseQR), "Sparse QR");
assert_eq!(
format!("{}", LinearSolverType::ExplicitSparseSchur),
"Explicit Sparse Schur Complement"
);
assert_eq!(
format!("{}", LinearSolverType::ImplicitSparseSchur),
"Implicit Sparse Schur Complement"
);
assert_eq!(
format!("{}", LinearSolverType::DenseCholesky),
"Dense Cholesky"
);
assert_eq!(format!("{}", LinearSolverType::DenseQR), "Dense QR");
assert_eq!(
format!("{}", LinearSolverType::ExplicitDenseSchur),
"Explicit Dense Schur Complement"
);
}
#[test]
fn test_lin_alg_error_factorization_failed_display() {
let e = LinAlgError::FactorizationFailed("non-positive definite".into());
assert!(e.to_string().contains("non-positive definite"));
}
#[test]
fn test_lin_alg_error_singular_matrix_display() {
let e = LinAlgError::SingularMatrix("rank deficient".into());
assert!(e.to_string().contains("rank deficient"));
}
#[test]
fn test_lin_alg_error_sparse_matrix_creation_display() {
let e = LinAlgError::SparseMatrixCreation("bad triplets".into());
assert!(e.to_string().contains("bad triplets"));
}
#[test]
fn test_lin_alg_error_matrix_conversion_display() {
let e = LinAlgError::MatrixConversion("size mismatch".into());
assert!(e.to_string().contains("size mismatch"));
}
#[test]
fn test_lin_alg_error_invalid_input_display() {
let e = LinAlgError::InvalidInput("null jacobian".into());
assert!(e.to_string().contains("null jacobian"));
}
#[test]
fn test_lin_alg_error_invalid_state_display() {
let e = LinAlgError::InvalidState("not initialized".into());
assert!(e.to_string().contains("not initialized"));
}
#[test]
fn test_lin_alg_error_log_returns_self() {
let e = LinAlgError::InvalidInput("log_test".into());
let returned = e.log();
assert!(returned.to_string().contains("log_test"));
}
#[test]
fn test_lin_alg_error_log_with_source_returns_self() {
let e = LinAlgError::SingularMatrix("source_test".into());
let source = std::io::Error::other("src");
let returned = e.log_with_source(source);
assert!(returned.to_string().contains("source_test"));
}
#[test]
fn test_lin_alg_result_ok() {
let r: LinAlgResult<i32> = Ok(7);
assert!(matches!(r, Ok(7)));
}
#[test]
fn test_lin_alg_result_err() {
let r: LinAlgResult<i32> = Err(LinAlgError::InvalidInput("oops".into()));
assert!(r.is_err());
}
}