resopt 0.3.0

Declarative constrained residual optimization in Rust
Documentation
/// External problem scaling applied before the backend-specific solve.
#[derive(Debug, Clone, PartialEq)]
pub struct ScalingOptions {
    pub enable: bool,
    pub min_scale: f64,
    pub max_scale: f64,
}

impl Default for ScalingOptions {
    fn default() -> Self {
        Self {
            enable: true,
            min_scale: 1e-4,
            max_scale: 1e4,
        }
    }
}

/// Clarabel-specific tuning knobs that materially affect numerical robustness.
#[derive(Debug, Clone, PartialEq)]
pub struct ClarabelOptions {
    pub equilibrate_enable: bool,
    pub equilibrate_max_iter: u32,
    pub presolve_enable: bool,
    pub direct_solve_method: Option<String>,
    pub tol_ktratio: f64,
    pub reduced_tol_gap_abs: f64,
    pub reduced_tol_gap_rel: f64,
    pub reduced_tol_feas: f64,
    pub reduced_tol_infeas_abs: f64,
    pub reduced_tol_infeas_rel: f64,
    pub reduced_tol_ktratio: f64,
    pub static_regularization_enable: bool,
    pub static_regularization_constant: f64,
    pub dynamic_regularization_enable: bool,
    pub dynamic_regularization_eps: f64,
    pub dynamic_regularization_delta: f64,
    pub iterative_refinement_enable: bool,
    pub iterative_refinement_reltol: f64,
    pub iterative_refinement_abstol: f64,
    pub iterative_refinement_max_iter: u32,
}

impl Default for ClarabelOptions {
    fn default() -> Self {
        Self {
            equilibrate_enable: true,
            equilibrate_max_iter: 10,
            presolve_enable: true,
            direct_solve_method: None,
            tol_ktratio: 1e-6,
            reduced_tol_gap_abs: 5e-5,
            reduced_tol_gap_rel: 5e-5,
            reduced_tol_feas: 1e-4,
            reduced_tol_infeas_abs: 5e-12,
            reduced_tol_infeas_rel: 5e-5,
            reduced_tol_ktratio: 1e-4,
            static_regularization_enable: true,
            static_regularization_constant: 1e-8,
            dynamic_regularization_enable: true,
            dynamic_regularization_eps: 1e-13,
            dynamic_regularization_delta: 2e-7,
            iterative_refinement_enable: true,
            iterative_refinement_reltol: 1e-13,
            iterative_refinement_abstol: 1e-12,
            iterative_refinement_max_iter: 10,
        }
    }
}

/// Generic solver options shared by the public API.
#[derive(Debug, Clone, PartialEq)]
pub struct SolveOptions {
    pub tolerance: f64,
    pub max_iterations: usize,
    pub verbose: bool,
    pub scaling: ScalingOptions,
    pub clarabel: ClarabelOptions,
    pub retry_on_numerical_failure: bool,
}

impl Default for SolveOptions {
    fn default() -> Self {
        Self {
            tolerance: 1e-8,
            max_iterations: 10_000,
            verbose: false,
            scaling: ScalingOptions::default(),
            clarabel: ClarabelOptions::default(),
            retry_on_numerical_failure: true,
        }
    }
}

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

    #[test]
    fn defaults() {
        let opts = SolveOptions::default();
        assert!((opts.tolerance - 1e-8).abs() < f64::EPSILON);
        assert_eq!(opts.max_iterations, 10_000);
        assert!(!opts.verbose);
        assert!(opts.scaling.enable);
        assert!(opts.clarabel.equilibrate_enable);
        assert!(opts.retry_on_numerical_failure);
    }

    #[test]
    fn custom() {
        let opts = SolveOptions {
            tolerance: 1e-4,
            max_iterations: 500,
            verbose: true,
            scaling: ScalingOptions {
                enable: false,
                min_scale: 1e-3,
                max_scale: 1e3,
            },
            clarabel: ClarabelOptions {
                presolve_enable: false,
                direct_solve_method: Some("qdldl".to_string()),
                ..ClarabelOptions::default()
            },
            retry_on_numerical_failure: false,
        };

        assert!((opts.tolerance - 1e-4).abs() < f64::EPSILON);
        assert_eq!(opts.max_iterations, 500);
        assert!(opts.verbose);
        assert!(!opts.scaling.enable);
        assert_eq!(opts.clarabel.direct_solve_method.as_deref(), Some("qdldl"));
        assert!(!opts.retry_on_numerical_failure);
    }

    #[test]
    fn clone_and_eq() {
        let a = SolveOptions::default();
        assert_eq!(a, a.clone());
    }
}