resopt 0.3.0

Declarative constrained residual optimization in Rust
Documentation
use crate::core::{Error, Matrix, Vector};

/// Linear residual block r(x) = A x - b.
#[derive(Debug, Clone, PartialEq)]
pub struct LinearResidual {
    matrix: Matrix,
    target: Vector,
}

impl LinearResidual {
    /// Creates a new residual block.
    pub fn new(matrix: Matrix, target: Vector) -> Result<Self, Error> {
        if matrix.nrows() != target.len() {
            return Err(Error::DimensionMismatch {
                message: format!(
                    "residual matrix row count ({}) must match target length ({})",
                    matrix.nrows(),
                    target.len()
                ),
            });
        }

        Ok(Self { matrix, target })
    }

    /// Returns the matrix A.
    pub fn matrix(&self) -> &Matrix {
        &self.matrix
    }

    /// Returns the target vector b.
    pub fn target(&self) -> &[f64] {
        &self.target
    }

    /// Returns the decision variable dimension.
    pub fn x_dim(&self) -> usize {
        self.matrix.ncols()
    }

    /// Returns the residual dimension.
    pub fn residual_dim(&self) -> usize {
        self.matrix.nrows()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_matrix_2x3() -> Matrix {
        Matrix::from_row_major(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap()
    }

    #[test]
    fn valid_construction() {
        let r = LinearResidual::new(make_matrix_2x3(), vec![10.0, 20.0]).unwrap();
        assert_eq!(r.x_dim(), 3);
        assert_eq!(r.residual_dim(), 2);
        assert_eq!(r.target(), &[10.0, 20.0]);
    }

    #[test]
    fn dimension_mismatch() {
        let result = LinearResidual::new(make_matrix_2x3(), vec![1.0, 2.0, 3.0]);
        match result.unwrap_err() {
            Error::DimensionMismatch { message } => assert!(message.contains("residual")),
            other => panic!("unexpected: {:?}", other),
        }
    }

    #[test]
    fn single_row() {
        let m = Matrix::from_row_major(1, 4, vec![1.0, 0.0, 0.0, 1.0]).unwrap();
        let r = LinearResidual::new(m, vec![5.0]).unwrap();
        assert_eq!(r.x_dim(), 4);
        assert_eq!(r.residual_dim(), 1);
    }

    #[test]
    fn accessors() {
        let m = make_matrix_2x3();
        let r = LinearResidual::new(m.clone(), vec![1.0, 2.0]).unwrap();
        assert_eq!(r.matrix(), &m);
        assert_eq!(r.target(), &[1.0, 2.0]);
    }
}