resopt 0.3.0

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

pub fn compute_p_dense(matrix: &Matrix) -> Vec<f64> {
    let rows = matrix.nrows();
    let cols = matrix.ncols();
    let data = matrix.data();

    let mut p = vec![0.0; cols * cols];

    for i in 0..cols {
        for j in 0..cols {
            let mut sum = 0.0;
            for k in 0..rows {
                sum += data[k * cols + i] * data[k * cols + j];
            }
            p[i * cols + j] = sum;
        }
    }

    p
}

pub fn compute_q(matrix: &Matrix, target: &[f64]) -> Vec<f64> {
    let rows = matrix.nrows();
    let cols = matrix.ncols();
    let data = matrix.data();

    let mut q = vec![0.0; cols];

    for j in 0..cols {
        let mut sum = 0.0;
        for i in 0..rows {
            sum += data[i * cols + j] * target[i];
        }
        q[j] = -sum;
    }

    q
}

pub fn compute_residual(matrix: &Matrix, x: &[f64], target: &[f64]) -> Vec<f64> {
    let rows = matrix.nrows();
    let cols = matrix.ncols();
    let data = matrix.data();

    let mut residual = vec![0.0; rows];

    for i in 0..rows {
        let mut value = 0.0;
        for j in 0..cols {
            value += data[i * cols + j] * x[j];
        }
        residual[i] = value - target[i];
    }

    residual
}

pub fn l2_squared_value(residual: &[f64]) -> f64 {
    0.5 * residual.iter().map(|value| value * value).sum::<f64>()
}

pub fn tikhonov_value(regularization: &TikhonovRegularization, x: &[f64]) -> f64 {
    let residual = compute_residual(regularization.matrix(), x, regularization.target());
    regularization.lambda() * l2_squared_value(&residual)
}

pub fn apply_matrix(matrix: &Matrix, x: &[f64]) -> Vec<f64> {
    let rows = matrix.nrows();
    let cols = matrix.ncols();
    let data = matrix.data();

    let mut out = vec![0.0; rows];

    for i in 0..rows {
        let mut value = 0.0;
        for j in 0..cols {
            value += data[i * cols + j] * x[j];
        }
        out[i] = value;
    }

    out
}

pub fn max_constraint_violation(problem: &ConstrainedResidualProblem, x: &[f64]) -> f64 {
    let mut max_violation: f64 = 0.0;

    for eq in problem.equalities() {
        let vals = apply_matrix(eq.matrix(), x);
        for (lhs, rhs) in vals.iter().zip(eq.rhs().iter()) {
            max_violation = max_violation.max((lhs - rhs).abs());
        }
    }

    for ineq in problem.inequalities() {
        let vals = apply_matrix(ineq.matrix(), x);
        for (lhs, rhs) in vals.iter().zip(ineq.rhs().iter()) {
            max_violation = max_violation.max((lhs - rhs).max(0.0));
        }
    }

    if let Some(bounds) = problem.bounds() {
        for (i, x_i) in x.iter().copied().enumerate().take(bounds.len()) {
            if let Some(lb) = bounds.lower()[i] {
                max_violation = max_violation.max((lb - x_i).max(0.0));
            }
            if let Some(ub) = bounds.upper()[i] {
                max_violation = max_violation.max((x_i - ub).max(0.0));
            }
        }
    }

    max_violation
}

pub fn copy_block_rows(
    block_matrix: &Matrix,
    a_dense: &mut [f64],
    total_ncols: usize,
    start_row: usize,
) -> Result<(), Error> {
    if block_matrix.ncols() != total_ncols {
        return Err(Error::DimensionMismatch {
            message: format!(
                "constraint block has {} columns but expected {}",
                block_matrix.ncols(),
                total_ncols
            ),
        });
    }

    let rows = block_matrix.nrows();
    let cols = block_matrix.ncols();
    let src = block_matrix.data();

    for i in 0..rows {
        for j in 0..cols {
            a_dense[(start_row + i) * total_ncols + j] = src[i * cols + j];
        }
    }

    Ok(())
}