#[cfg(feature = "clarabel")]
use resopt::{
Bounds, ClarabelSolver, ConstrainedResidualProblem, LinearEqualities, LinearInequalities,
LinearResidual, Loss, Matrix, SolveResult, SolveStatus, Solver, TikhonovRegularization,
};
#[cfg(feature = "clarabel")]
fn assert_slice_close(actual: &[f64], expected: &[f64], tol: f64) {
assert_eq!(actual.len(), expected.len());
for (idx, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
assert!(
(a - e).abs() <= tol,
"mismatch at index {idx}: actual={a}, expected={e}, tol={tol}"
);
}
}
#[cfg(feature = "clarabel")]
fn identity_problem(target: Vec<f64>) -> ConstrainedResidualProblem {
let matrix = Matrix::from_row_major(2, 2, vec![1.0, 0.0, 0.0, 1.0]).unwrap();
let residual = LinearResidual::new(matrix, target).unwrap();
ConstrainedResidualProblem::new(residual, Loss::L2Squared).unwrap()
}
#[cfg(feature = "clarabel")]
fn solve(problem: &ConstrainedResidualProblem) -> resopt::SolveResult {
ClarabelSolver::new().solve(problem).unwrap()
}
#[cfg(feature = "clarabel")]
fn assert_solved_x(
problem: &ConstrainedResidualProblem,
expected_x: &[f64],
tol: f64,
) -> SolveResult {
let result = solve(problem);
assert_eq!(result.status(), SolveStatus::Solved);
let solution = result.solution().unwrap();
assert_slice_close(solution.x(), expected_x, tol);
assert!(
result
.diagnostics()
.max_constraint_violation()
.unwrap_or(f64::INFINITY)
<= 1e-8
);
result
}
#[cfg(feature = "clarabel")]
fn sum_leq_constraint(rhs: f64) -> LinearInequalities {
LinearInequalities::new(
Matrix::from_row_major(1, 2, vec![1.0, 1.0]).unwrap(),
vec![rhs],
)
.unwrap()
}
#[cfg(feature = "clarabel")]
#[test]
fn test_unconstrained_identity_problem() {
let problem = identity_problem(vec![2.0, -3.0]);
assert_solved_x(&problem, &[2.0, -3.0], 1e-8);
}
#[cfg(feature = "clarabel")]
#[test]
fn test_box_constraints() {
let problem = identity_problem(vec![3.0, -2.0])
.with_bounds(Bounds::new(vec![Some(0.0), Some(-1.0)], vec![Some(2.0), Some(4.0)]).unwrap())
.unwrap();
assert_solved_x(&problem, &[2.0, -1.0], 1e-7);
}
#[cfg(feature = "clarabel")]
#[test]
fn test_equality_constraint() {
let equality = LinearEqualities::new(
Matrix::from_row_major(1, 2, vec![1.0, 1.0]).unwrap(),
vec![1.0],
)
.unwrap();
let problem = identity_problem(vec![2.0, 0.0])
.add_equalities(equality)
.unwrap();
assert_solved_x(&problem, &[1.5, -0.5], 1e-7);
}
#[cfg(feature = "clarabel")]
#[test]
fn test_inequality_constraint() {
let problem = identity_problem(vec![3.0, 3.0])
.add_inequalities(sum_leq_constraint(4.0))
.unwrap();
assert_solved_x(&problem, &[2.0, 2.0], 1e-7);
}
#[cfg(feature = "clarabel")]
#[test]
fn test_kkt_conditions_hold() {
let problem = identity_problem(vec![3.0, 3.0])
.add_inequalities(sum_leq_constraint(4.0))
.unwrap();
let result = assert_solved_x(&problem, &[2.0, 2.0], 1e-7);
let x = result.solution().unwrap().x();
let lambda = 2.0;
let constraint_value = x[0] + x[1] - 4.0;
let gradient = [2.0 * (x[0] - 3.0), 2.0 * (x[1] - 3.0)];
let stationarity = [gradient[0] + lambda, gradient[1] + lambda];
assert!(constraint_value <= 1e-8, "primal feasibility violated");
assert!(lambda >= 0.0, "dual feasibility violated");
assert!(
(lambda * constraint_value).abs() <= 1e-6,
"complementarity violated"
);
assert_slice_close(&stationarity, &[0.0, 0.0], 1e-6);
}
#[cfg(feature = "clarabel")]
#[test]
fn test_tikhonov_ridge_identity_problem() {
let problem = identity_problem(vec![2.0, -3.0])
.with_regularization(TikhonovRegularization::ridge(2, 1.0).unwrap())
.unwrap();
let result = assert_solved_x(&problem, &[1.0, -1.5], 1e-7);
assert!((result.objective_value().unwrap() - 3.25).abs() <= 1e-7);
}
#[cfg(feature = "clarabel")]
#[test]
fn test_general_tikhonov_regularization() {
let regularization = TikhonovRegularization::new(
2.0,
Matrix::from_row_major(1, 2, vec![1.0, -1.0]).unwrap(),
vec![0.0],
)
.unwrap();
let problem = identity_problem(vec![2.0, 0.0])
.with_regularization(regularization)
.unwrap();
assert_solved_x(&problem, &[1.2, 0.8], 1e-6);
}