use crate::copp::copp2::opt2::copp2_socp::{
copp2_socp_expert_with_info as rust_copp2_socp_expert, objective_value_copp2_opt,
};
use crate::copp::{ClarabelOptions, clarabel_to_copp3_solution};
use crate::ffi::c::core::CoppVerbosity;
use crate::ffi::c::core::status::{clear_last_error, panic_to_status};
use crate::ffi::c::formulation::{Copp2Problem, objective_slice};
use crate::ffi::c::{CoppProfile3rd, CoppRobot, CoppSliceF64, CoppStatus, CoppVecF64};
use crate::solver::copp2_socp::{
ClarabelOptionsBuilder, Copp2ProblemBuilder, copp2_socp as rust_copp2_socp,
};
use clarabel::solver::{DefaultSettings, DefaultSolution, LinearSolverInfo, SolverStatus};
use std::panic::{AssertUnwindSafe, catch_unwind};
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CoppClarabelDirectSolveMethod {
Auto = 0,
Qdldl = 1,
Faer = 2,
Mkl = 3,
Panua = 4,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CoppClarabelSolverStatus {
Unsolved = 0,
Solved = 1,
PrimalInfeasible = 2,
DualInfeasible = 3,
AlmostSolved = 4,
AlmostPrimalInfeasible = 5,
AlmostDualInfeasible = 6,
MaxIterations = 7,
MaxTime = 8,
NumericalError = 9,
InsufficientProgress = 10,
CallbackTerminated = 11,
}
impl From<SolverStatus> for CoppClarabelSolverStatus {
fn from(status: SolverStatus) -> Self {
match status {
SolverStatus::Unsolved => Self::Unsolved,
SolverStatus::Solved => Self::Solved,
SolverStatus::PrimalInfeasible => Self::PrimalInfeasible,
SolverStatus::DualInfeasible => Self::DualInfeasible,
SolverStatus::AlmostSolved => Self::AlmostSolved,
SolverStatus::AlmostPrimalInfeasible => Self::AlmostPrimalInfeasible,
SolverStatus::AlmostDualInfeasible => Self::AlmostDualInfeasible,
SolverStatus::MaxIterations => Self::MaxIterations,
SolverStatus::MaxTime => Self::MaxTime,
SolverStatus::NumericalError => Self::NumericalError,
SolverStatus::InsufficientProgress => Self::InsufficientProgress,
SolverStatus::CallbackTerminated => Self::CallbackTerminated,
}
}
}
impl CoppClarabelDirectSolveMethod {
fn from_settings(settings: &DefaultSettings<f64>) -> Self {
Self::from_clarabel_name(settings.direct_solve_method.as_str())
}
fn from_clarabel_name(name: &str) -> Self {
match name {
"qdldl" => Self::Qdldl,
"faer" => Self::Faer,
"mkl" => Self::Mkl,
"panua" => Self::Panua,
_ => Self::Auto,
}
}
fn as_clarabel_name(self) -> &'static str {
match self {
Self::Auto => "auto",
Self::Qdldl => "qdldl",
Self::Faer => "faer",
Self::Mkl => "mkl",
Self::Panua => "panua",
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoppClarabelLinearSolverInfo {
pub method: CoppClarabelDirectSolveMethod,
pub threads: usize,
pub direct: bool,
pub nnz_a: usize,
pub nnz_l: usize,
}
impl CoppClarabelLinearSolverInfo {
pub(crate) const fn empty() -> Self {
Self {
method: CoppClarabelDirectSolveMethod::Auto,
threads: 0,
direct: false,
nnz_a: 0,
nnz_l: 0,
}
}
}
impl From<LinearSolverInfo> for CoppClarabelLinearSolverInfo {
fn from(info: LinearSolverInfo) -> Self {
Self {
method: CoppClarabelDirectSolveMethod::from_clarabel_name(info.name.as_str()),
threads: info.threads,
direct: info.direct,
nnz_a: info.nnzA,
nnz_l: info.nnzL,
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct Copp2SocpResult {
pub has_a: bool,
pub a: CoppVecF64,
pub x: CoppVecF64,
pub z: CoppVecF64,
pub s: CoppVecF64,
pub solver_status: CoppClarabelSolverStatus,
pub obj_val: f64,
pub obj_val_dual: f64,
pub solve_time: f64,
pub iterations: u32,
pub r_prim: f64,
pub r_dual: f64,
pub linsolver: CoppClarabelLinearSolverInfo,
pub objective_value: f64,
pub objective_terms: CoppVecF64,
}
impl Copp2SocpResult {
fn empty() -> Self {
Self {
has_a: false,
a: CoppVecF64::empty(),
x: CoppVecF64::empty(),
z: CoppVecF64::empty(),
s: CoppVecF64::empty(),
solver_status: CoppClarabelSolverStatus::Unsolved,
obj_val: f64::NAN,
obj_val_dual: f64::NAN,
solve_time: 0.0,
iterations: 0,
r_prim: f64::NAN,
r_dual: f64::NAN,
linsolver: CoppClarabelLinearSolverInfo::empty(),
objective_value: f64::NAN,
objective_terms: CoppVecF64::empty(),
}
}
fn from_solution(
a_profile: Option<Vec<f64>>,
solution: DefaultSolution<f64>,
linsolver: LinearSolverInfo,
objective_breakdown: Option<(f64, Vec<f64>)>,
) -> Self {
let DefaultSolution {
x,
z,
s,
status,
obj_val,
obj_val_dual,
solve_time,
iterations,
r_prim,
r_dual,
} = solution;
let (objective_value, objective_terms) =
objective_breakdown.unwrap_or((f64::NAN, Vec::new()));
Self {
has_a: a_profile.is_some(),
a: a_profile.map_or_else(CoppVecF64::empty, CoppVecF64::from_vec),
x: CoppVecF64::from_vec(x),
z: CoppVecF64::from_vec(z),
s: CoppVecF64::from_vec(s),
solver_status: status.into(),
obj_val,
obj_val_dual,
solve_time,
iterations,
r_prim,
r_dual,
linsolver: linsolver.into(),
objective_value,
objective_terms: CoppVecF64::from_vec(objective_terms),
}
}
fn free(self) {
self.a.free();
self.x.free();
self.z.free();
self.s.free();
self.objective_terms.free();
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoppClarabelSettings {
pub max_iter: u32,
pub time_limit: f64,
pub verbose: bool,
pub max_step_fraction: f64,
pub tol_gap_abs: f64,
pub tol_gap_rel: f64,
pub tol_feas: f64,
pub tol_infeas_abs: f64,
pub tol_infeas_rel: f64,
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 equilibrate_enable: bool,
pub equilibrate_max_iter: u32,
pub equilibrate_min_scaling: f64,
pub equilibrate_max_scaling: f64,
pub linesearch_backtrack_step: f64,
pub min_switch_step_length: f64,
pub min_terminate_step_length: f64,
pub max_threads: u32,
pub direct_kkt_solver: bool,
pub direct_solve_method: CoppClarabelDirectSolveMethod,
pub static_regularization_enable: bool,
pub static_regularization_constant: f64,
pub static_regularization_proportional: 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,
pub iterative_refinement_stop_ratio: f64,
pub presolve_enable: bool,
pub input_sparse_dropzeros: bool,
}
impl CoppClarabelSettings {
fn from_settings(settings: &DefaultSettings<f64>) -> Self {
Self {
max_iter: settings.max_iter,
time_limit: settings.time_limit,
verbose: settings.verbose,
max_step_fraction: settings.max_step_fraction,
tol_gap_abs: settings.tol_gap_abs,
tol_gap_rel: settings.tol_gap_rel,
tol_feas: settings.tol_feas,
tol_infeas_abs: settings.tol_infeas_abs,
tol_infeas_rel: settings.tol_infeas_rel,
tol_ktratio: settings.tol_ktratio,
reduced_tol_gap_abs: settings.reduced_tol_gap_abs,
reduced_tol_gap_rel: settings.reduced_tol_gap_rel,
reduced_tol_feas: settings.reduced_tol_feas,
reduced_tol_infeas_abs: settings.reduced_tol_infeas_abs,
reduced_tol_infeas_rel: settings.reduced_tol_infeas_rel,
reduced_tol_ktratio: settings.reduced_tol_ktratio,
equilibrate_enable: settings.equilibrate_enable,
equilibrate_max_iter: settings.equilibrate_max_iter,
equilibrate_min_scaling: settings.equilibrate_min_scaling,
equilibrate_max_scaling: settings.equilibrate_max_scaling,
linesearch_backtrack_step: settings.linesearch_backtrack_step,
min_switch_step_length: settings.min_switch_step_length,
min_terminate_step_length: settings.min_terminate_step_length,
max_threads: settings.max_threads,
direct_kkt_solver: settings.direct_kkt_solver,
direct_solve_method: CoppClarabelDirectSolveMethod::from_settings(settings),
static_regularization_enable: settings.static_regularization_enable,
static_regularization_constant: settings.static_regularization_constant,
static_regularization_proportional: settings.static_regularization_proportional,
dynamic_regularization_enable: settings.dynamic_regularization_enable,
dynamic_regularization_eps: settings.dynamic_regularization_eps,
dynamic_regularization_delta: settings.dynamic_regularization_delta,
iterative_refinement_enable: settings.iterative_refinement_enable,
iterative_refinement_reltol: settings.iterative_refinement_reltol,
iterative_refinement_abstol: settings.iterative_refinement_abstol,
iterative_refinement_max_iter: settings.iterative_refinement_max_iter,
iterative_refinement_stop_ratio: settings.iterative_refinement_stop_ratio,
presolve_enable: settings.presolve_enable,
input_sparse_dropzeros: settings.input_sparse_dropzeros,
}
}
fn build(self) -> DefaultSettings<f64> {
DefaultSettings::<f64> {
max_iter: self.max_iter,
time_limit: self.time_limit,
verbose: self.verbose,
max_step_fraction: self.max_step_fraction,
tol_gap_abs: self.tol_gap_abs,
tol_gap_rel: self.tol_gap_rel,
tol_feas: self.tol_feas,
tol_infeas_abs: self.tol_infeas_abs,
tol_infeas_rel: self.tol_infeas_rel,
tol_ktratio: self.tol_ktratio,
reduced_tol_gap_abs: self.reduced_tol_gap_abs,
reduced_tol_gap_rel: self.reduced_tol_gap_rel,
reduced_tol_feas: self.reduced_tol_feas,
reduced_tol_infeas_abs: self.reduced_tol_infeas_abs,
reduced_tol_infeas_rel: self.reduced_tol_infeas_rel,
reduced_tol_ktratio: self.reduced_tol_ktratio,
equilibrate_enable: self.equilibrate_enable,
equilibrate_max_iter: self.equilibrate_max_iter,
equilibrate_min_scaling: self.equilibrate_min_scaling,
equilibrate_max_scaling: self.equilibrate_max_scaling,
linesearch_backtrack_step: self.linesearch_backtrack_step,
min_switch_step_length: self.min_switch_step_length,
min_terminate_step_length: self.min_terminate_step_length,
max_threads: self.max_threads,
direct_kkt_solver: self.direct_kkt_solver,
direct_solve_method: self.direct_solve_method.as_clarabel_name().to_owned(),
static_regularization_enable: self.static_regularization_enable,
static_regularization_constant: self.static_regularization_constant,
static_regularization_proportional: self.static_regularization_proportional,
dynamic_regularization_enable: self.dynamic_regularization_enable,
dynamic_regularization_eps: self.dynamic_regularization_eps,
dynamic_regularization_delta: self.dynamic_regularization_delta,
iterative_refinement_enable: self.iterative_refinement_enable,
iterative_refinement_reltol: self.iterative_refinement_reltol,
iterative_refinement_abstol: self.iterative_refinement_abstol,
iterative_refinement_max_iter: self.iterative_refinement_max_iter,
iterative_refinement_stop_ratio: self.iterative_refinement_stop_ratio,
presolve_enable: self.presolve_enable,
input_sparse_dropzeros: self.input_sparse_dropzeros,
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoppClarabelOptions {
pub verbosity: CoppVerbosity,
pub allow_almost_solved: bool,
pub allow_max_iterations: bool,
pub allow_max_time: bool,
pub allow_callback_terminated: bool,
pub allow_insufficient_progress: bool,
pub clarabel_settings: CoppClarabelSettings,
}
impl CoppClarabelOptions {
pub(crate) fn default_options() -> Result<Self, CoppStatus> {
let options = ClarabelOptionsBuilder::new()
.allow_almost_solved(true)
.build()
.map_err(|error| CoppStatus::from(&error))?;
Ok(Self {
verbosity: options.verbosity().into(),
allow_almost_solved: true,
allow_max_iterations: false,
allow_max_time: false,
allow_callback_terminated: false,
allow_insufficient_progress: false,
clarabel_settings: CoppClarabelSettings::from_settings(options.clarabel_settings()),
})
}
pub(crate) fn build(self) -> ClarabelOptions {
ClarabelOptions {
verbosity: self.verbosity.into(),
clarabel_settings: self.clarabel_settings.build(),
allow_almost_solved: self.allow_almost_solved,
allow_max_iterations: self.allow_max_iterations,
allow_max_time: self.allow_max_time,
allow_callback_terminated: self.allow_callback_terminated,
allow_insufficient_progress: self.allow_insufficient_progress,
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn copp_clarabel_default_options(
out_options: *mut CoppClarabelOptions,
) -> CoppStatus {
crate::ffi::c::core::status::clear_last_error();
if out_options.is_null() {
return CoppStatus::NullPointer.into_ffi_status();
}
match catch_unwind(AssertUnwindSafe(|| {
let options = CoppClarabelOptions::default_options()?;
unsafe {
out_options.write(options);
}
Ok(CoppStatus::Ok)
})) {
Ok(Ok(status)) | Ok(Err(status)) => status.into_ffi_status(),
Err(payload) => panic_to_status(payload).into_ffi_status(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn copp_clarabel_solution_to_profile_2nd(
s_len: usize,
x: CoppSliceF64,
out_a: *mut CoppVecF64,
) -> CoppStatus {
crate::ffi::c::core::status::clear_last_error();
let out_a = match CoppVecF64::out_ptr(out_a) {
Ok(out_a) => out_a,
Err(status) => return status,
};
unsafe {
CoppVecF64::write_empty_to(out_a);
}
match catch_unwind(AssertUnwindSafe(|| {
let x = unsafe { x.as_slice()? };
let mut a = vec![0.0; s_len];
for (dst, src) in a.iter_mut().zip(x.iter()) {
*dst = src.max(0.0);
}
unsafe {
CoppVecF64::write_vec_to(out_a, a);
}
Ok(CoppStatus::Ok)
})) {
Ok(Ok(status)) | Ok(Err(status)) => status.into_ffi_status(),
Err(payload) => panic_to_status(payload).into_ffi_status(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn copp_clarabel_solution_to_profile_3rd(
s: CoppSliceF64,
x: CoppSliceF64,
num_stationary_start: usize,
num_stationary_end: usize,
out_profile: *mut CoppProfile3rd,
) -> CoppStatus {
crate::ffi::c::core::status::clear_last_error();
if out_profile.is_null() {
return CoppStatus::NullPointer.into_ffi_status();
}
unsafe {
out_profile.write(CoppProfile3rd::empty());
}
match catch_unwind(AssertUnwindSafe(|| {
let s = unsafe { s.as_slice()? };
let x = unsafe { x.as_slice()? };
if s.len() < 2 {
return Err(CoppStatus::InvalidShape);
}
if num_stationary_start + num_stationary_end >= s.len() {
return Err(CoppStatus::InvalidArgument);
}
let min_x_len = s.len().checked_mul(2).ok_or(CoppStatus::InvalidShape)?;
if x.len() < min_x_len {
return Err(CoppStatus::InvalidShape);
}
let profile = clarabel_to_copp3_solution(x, s, (num_stationary_start, num_stationary_end));
unsafe {
out_profile.write(CoppProfile3rd::from_parts(
profile.a,
profile.b,
profile.num_stationary,
));
}
Ok(CoppStatus::Ok)
})) {
Ok(Ok(status)) | Ok(Err(status)) => status.into_ffi_status(),
Err(payload) => panic_to_status(payload).into_ffi_status(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn copp2_socp(
problem: Copp2Problem,
options: CoppClarabelOptions,
out_a: *mut CoppVecF64,
) -> CoppStatus {
crate::ffi::c::core::status::clear_last_error();
let out_a = match CoppVecF64::out_ptr(out_a) {
Ok(out_a) => out_a,
Err(status) => return status,
};
unsafe {
CoppVecF64::write_empty_to(out_a);
}
match catch_unwind(AssertUnwindSafe(|| {
let inner = unsafe { CoppRobot::inner(problem.robot) }.ok_or(CoppStatus::NullPointer)?;
let robot = &inner.robot;
let objectives = unsafe { objective_slice(problem.objectives, problem.num_objectives)? };
let objectives = objectives
.iter()
.map(|objective| {
unsafe { objective.as_rust_objective(true) }
})
.collect::<Result<Vec<_>, _>>()?;
let options = options.build();
let problem = Copp2ProblemBuilder::new(
robot,
(problem.idx_s_start, problem.idx_s_final),
(problem.a_start, problem.a_final),
&objectives,
)
.build()
.map_err(|error| CoppStatus::from(&error))?;
let a = rust_copp2_socp(&problem, &options).map_err(|error| CoppStatus::from(&error))?;
unsafe {
CoppVecF64::write_vec_to(out_a, a);
}
Ok(CoppStatus::Ok)
})) {
Ok(Ok(status)) | Ok(Err(status)) => status.into_ffi_status(),
Err(payload) => panic_to_status(payload).into_ffi_status(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn copp2_socp_expert(
problem: Copp2Problem,
options: CoppClarabelOptions,
out_result: *mut Copp2SocpResult,
) -> CoppStatus {
crate::ffi::c::core::status::clear_last_error();
if out_result.is_null() {
return CoppStatus::NullPointer.into_ffi_status();
}
unsafe {
out_result.write(Copp2SocpResult::empty());
}
match catch_unwind(AssertUnwindSafe(|| {
let inner = unsafe { CoppRobot::inner(problem.robot) }.ok_or(CoppStatus::NullPointer)?;
let robot = &inner.robot;
let objectives = unsafe { objective_slice(problem.objectives, problem.num_objectives)? };
let objectives = objectives
.iter()
.map(|objective| {
unsafe { objective.as_rust_objective(true) }
})
.collect::<Result<Vec<_>, _>>()?;
let options = options.build();
let problem = Copp2ProblemBuilder::new(
robot,
(problem.idx_s_start, problem.idx_s_final),
(problem.a_start, problem.a_final),
&objectives,
)
.build()
.map_err(|error| CoppStatus::from(&error))?;
let expert =
rust_copp2_socp_expert(&problem, &options).map_err(|error| CoppStatus::from(&error))?;
let objective_breakdown = expert
.result
.as_ref()
.map(|a| objective_value_copp2_opt(robot, problem.idx_s_interval.0, &objectives, a));
let result = Copp2SocpResult::from_solution(
expert.result,
expert.solution,
expert.linsolver,
objective_breakdown,
);
unsafe {
out_result.write(result);
}
Ok(CoppStatus::Ok)
})) {
Ok(Ok(status)) | Ok(Err(status)) => status.into_ffi_status(),
Err(payload) => panic_to_status(payload).into_ffi_status(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn copp2_socp_result_free(result: Copp2SocpResult) {
result.free();
clear_last_error();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::diag::Verbosity;
use std::mem::MaybeUninit;
fn custom_settings(method: &str) -> DefaultSettings<f64> {
DefaultSettings::<f64> {
max_iter: 77,
time_limit: 12.5,
verbose: true,
max_step_fraction: 0.91,
tol_gap_abs: 1.0e-7,
tol_gap_rel: 2.0e-7,
tol_feas: 3.0e-7,
tol_infeas_abs: 4.0e-7,
tol_infeas_rel: 5.0e-7,
tol_ktratio: 6.0e-7,
reduced_tol_gap_abs: 7.0e-5,
reduced_tol_gap_rel: 8.0e-5,
reduced_tol_feas: 9.0e-5,
reduced_tol_infeas_abs: 1.0e-4,
reduced_tol_infeas_rel: 1.1e-4,
reduced_tol_ktratio: 1.2e-4,
equilibrate_enable: false,
equilibrate_max_iter: 13,
equilibrate_min_scaling: 1.0e-3,
equilibrate_max_scaling: 1.0e3,
linesearch_backtrack_step: 0.73,
min_switch_step_length: 1.0e-2,
min_terminate_step_length: 2.0e-2,
max_threads: 4,
direct_kkt_solver: true,
direct_solve_method: method.to_owned(),
static_regularization_enable: false,
static_regularization_constant: 1.0e-9,
static_regularization_proportional: 2.0e-9,
dynamic_regularization_enable: false,
dynamic_regularization_eps: 3.0e-9,
dynamic_regularization_delta: 4.0e-9,
iterative_refinement_enable: false,
iterative_refinement_reltol: 5.0e-9,
iterative_refinement_abstol: 6.0e-9,
iterative_refinement_max_iter: 9,
iterative_refinement_stop_ratio: 7.0,
presolve_enable: false,
input_sparse_dropzeros: true,
}
}
fn assert_settings_eq(actual: &DefaultSettings<f64>, expected: &DefaultSettings<f64>) {
assert_eq!(actual.max_iter, expected.max_iter);
assert_eq!(actual.time_limit, expected.time_limit);
assert_eq!(actual.verbose, expected.verbose);
assert_eq!(actual.max_step_fraction, expected.max_step_fraction);
assert_eq!(actual.tol_gap_abs, expected.tol_gap_abs);
assert_eq!(actual.tol_gap_rel, expected.tol_gap_rel);
assert_eq!(actual.tol_feas, expected.tol_feas);
assert_eq!(actual.tol_infeas_abs, expected.tol_infeas_abs);
assert_eq!(actual.tol_infeas_rel, expected.tol_infeas_rel);
assert_eq!(actual.tol_ktratio, expected.tol_ktratio);
assert_eq!(actual.reduced_tol_gap_abs, expected.reduced_tol_gap_abs);
assert_eq!(actual.reduced_tol_gap_rel, expected.reduced_tol_gap_rel);
assert_eq!(actual.reduced_tol_feas, expected.reduced_tol_feas);
assert_eq!(
actual.reduced_tol_infeas_abs,
expected.reduced_tol_infeas_abs
);
assert_eq!(
actual.reduced_tol_infeas_rel,
expected.reduced_tol_infeas_rel
);
assert_eq!(actual.reduced_tol_ktratio, expected.reduced_tol_ktratio);
assert_eq!(actual.equilibrate_enable, expected.equilibrate_enable);
assert_eq!(actual.equilibrate_max_iter, expected.equilibrate_max_iter);
assert_eq!(
actual.equilibrate_min_scaling,
expected.equilibrate_min_scaling
);
assert_eq!(
actual.equilibrate_max_scaling,
expected.equilibrate_max_scaling
);
assert_eq!(
actual.linesearch_backtrack_step,
expected.linesearch_backtrack_step
);
assert_eq!(
actual.min_switch_step_length,
expected.min_switch_step_length
);
assert_eq!(
actual.min_terminate_step_length,
expected.min_terminate_step_length
);
assert_eq!(actual.max_threads, expected.max_threads);
assert_eq!(actual.direct_kkt_solver, expected.direct_kkt_solver);
assert_eq!(actual.direct_solve_method, expected.direct_solve_method);
assert_eq!(
actual.static_regularization_enable,
expected.static_regularization_enable
);
assert_eq!(
actual.static_regularization_constant,
expected.static_regularization_constant
);
assert_eq!(
actual.static_regularization_proportional,
expected.static_regularization_proportional
);
assert_eq!(
actual.dynamic_regularization_enable,
expected.dynamic_regularization_enable
);
assert_eq!(
actual.dynamic_regularization_eps,
expected.dynamic_regularization_eps
);
assert_eq!(
actual.dynamic_regularization_delta,
expected.dynamic_regularization_delta
);
assert_eq!(
actual.iterative_refinement_enable,
expected.iterative_refinement_enable
);
assert_eq!(
actual.iterative_refinement_reltol,
expected.iterative_refinement_reltol
);
assert_eq!(
actual.iterative_refinement_abstol,
expected.iterative_refinement_abstol
);
assert_eq!(
actual.iterative_refinement_max_iter,
expected.iterative_refinement_max_iter
);
assert_eq!(
actual.iterative_refinement_stop_ratio,
expected.iterative_refinement_stop_ratio
);
assert_eq!(actual.presolve_enable, expected.presolve_enable);
assert_eq!(
actual.input_sparse_dropzeros,
expected.input_sparse_dropzeros
);
}
fn assert_c_settings_eq(actual: CoppClarabelSettings, expected: &DefaultSettings<f64>) {
let CoppClarabelSettings {
max_iter,
time_limit,
verbose,
max_step_fraction,
tol_gap_abs,
tol_gap_rel,
tol_feas,
tol_infeas_abs,
tol_infeas_rel,
tol_ktratio,
reduced_tol_gap_abs,
reduced_tol_gap_rel,
reduced_tol_feas,
reduced_tol_infeas_abs,
reduced_tol_infeas_rel,
reduced_tol_ktratio,
equilibrate_enable,
equilibrate_max_iter,
equilibrate_min_scaling,
equilibrate_max_scaling,
linesearch_backtrack_step,
min_switch_step_length,
min_terminate_step_length,
max_threads,
direct_kkt_solver,
direct_solve_method,
static_regularization_enable,
static_regularization_constant,
static_regularization_proportional,
dynamic_regularization_enable,
dynamic_regularization_eps,
dynamic_regularization_delta,
iterative_refinement_enable,
iterative_refinement_reltol,
iterative_refinement_abstol,
iterative_refinement_max_iter,
iterative_refinement_stop_ratio,
presolve_enable,
input_sparse_dropzeros,
} = actual;
assert_eq!(max_iter, expected.max_iter);
assert_eq!(time_limit, expected.time_limit);
assert_eq!(verbose, expected.verbose);
assert_eq!(max_step_fraction, expected.max_step_fraction);
assert_eq!(tol_gap_abs, expected.tol_gap_abs);
assert_eq!(tol_gap_rel, expected.tol_gap_rel);
assert_eq!(tol_feas, expected.tol_feas);
assert_eq!(tol_infeas_abs, expected.tol_infeas_abs);
assert_eq!(tol_infeas_rel, expected.tol_infeas_rel);
assert_eq!(tol_ktratio, expected.tol_ktratio);
assert_eq!(reduced_tol_gap_abs, expected.reduced_tol_gap_abs);
assert_eq!(reduced_tol_gap_rel, expected.reduced_tol_gap_rel);
assert_eq!(reduced_tol_feas, expected.reduced_tol_feas);
assert_eq!(reduced_tol_infeas_abs, expected.reduced_tol_infeas_abs);
assert_eq!(reduced_tol_infeas_rel, expected.reduced_tol_infeas_rel);
assert_eq!(reduced_tol_ktratio, expected.reduced_tol_ktratio);
assert_eq!(equilibrate_enable, expected.equilibrate_enable);
assert_eq!(equilibrate_max_iter, expected.equilibrate_max_iter);
assert_eq!(equilibrate_min_scaling, expected.equilibrate_min_scaling);
assert_eq!(equilibrate_max_scaling, expected.equilibrate_max_scaling);
assert_eq!(
linesearch_backtrack_step,
expected.linesearch_backtrack_step
);
assert_eq!(min_switch_step_length, expected.min_switch_step_length);
assert_eq!(
min_terminate_step_length,
expected.min_terminate_step_length
);
assert_eq!(max_threads, expected.max_threads);
assert_eq!(direct_kkt_solver, expected.direct_kkt_solver);
assert_eq!(
direct_solve_method,
CoppClarabelDirectSolveMethod::from_settings(expected)
);
assert_eq!(
static_regularization_enable,
expected.static_regularization_enable
);
assert_eq!(
static_regularization_constant,
expected.static_regularization_constant
);
assert_eq!(
static_regularization_proportional,
expected.static_regularization_proportional
);
assert_eq!(
dynamic_regularization_enable,
expected.dynamic_regularization_enable
);
assert_eq!(
dynamic_regularization_eps,
expected.dynamic_regularization_eps
);
assert_eq!(
dynamic_regularization_delta,
expected.dynamic_regularization_delta
);
assert_eq!(
iterative_refinement_enable,
expected.iterative_refinement_enable
);
assert_eq!(
iterative_refinement_reltol,
expected.iterative_refinement_reltol
);
assert_eq!(
iterative_refinement_abstol,
expected.iterative_refinement_abstol
);
assert_eq!(
iterative_refinement_max_iter,
expected.iterative_refinement_max_iter
);
assert_eq!(
iterative_refinement_stop_ratio,
expected.iterative_refinement_stop_ratio
);
assert_eq!(presolve_enable, expected.presolve_enable);
assert_eq!(input_sparse_dropzeros, expected.input_sparse_dropzeros);
}
fn assert_c_options_eq(actual: CoppClarabelOptions, expected: &ClarabelOptions) {
let CoppClarabelOptions {
verbosity,
allow_almost_solved,
allow_max_iterations,
allow_max_time,
allow_callback_terminated,
allow_insufficient_progress,
clarabel_settings,
} = actual;
assert_eq!(verbosity, expected.verbosity.into());
assert_eq!(allow_almost_solved, expected.allow_almost_solved);
assert_eq!(allow_max_iterations, expected.allow_max_iterations);
assert_eq!(allow_max_time, expected.allow_max_time);
assert_eq!(
allow_callback_terminated,
expected.allow_callback_terminated
);
assert_eq!(
allow_insufficient_progress,
expected.allow_insufficient_progress
);
assert_c_settings_eq(clarabel_settings, &expected.clarabel_settings);
}
#[test]
fn clarabel_direct_solve_method_names_roundtrip() {
for (name, method) in [
("auto", CoppClarabelDirectSolveMethod::Auto),
("qdldl", CoppClarabelDirectSolveMethod::Qdldl),
("faer", CoppClarabelDirectSolveMethod::Faer),
("mkl", CoppClarabelDirectSolveMethod::Mkl),
("panua", CoppClarabelDirectSolveMethod::Panua),
] {
assert_eq!(
CoppClarabelDirectSolveMethod::from_clarabel_name(name),
method
);
assert_eq!(method.as_clarabel_name(), name);
}
assert_eq!(
CoppClarabelDirectSolveMethod::from_clarabel_name("unknown"),
CoppClarabelDirectSolveMethod::Auto
);
}
#[test]
fn clarabel_settings_map_all_fields_both_directions() {
let settings = custom_settings("faer");
let c_settings = CoppClarabelSettings::from_settings(&settings);
assert_c_settings_eq(c_settings, &settings);
assert_settings_eq(&c_settings.build(), &settings);
}
#[test]
fn clarabel_default_options_match_rust_policy() {
let expected = ClarabelOptionsBuilder::new().build().unwrap();
assert!(expected.is_allow(SolverStatus::Solved));
assert!(expected.is_allow(SolverStatus::AlmostSolved));
assert!(!expected.is_allow(SolverStatus::MaxIterations));
assert!(!expected.is_allow(SolverStatus::MaxTime));
assert!(!expected.is_allow(SolverStatus::CallbackTerminated));
assert!(!expected.is_allow(SolverStatus::InsufficientProgress));
let mut out = MaybeUninit::<CoppClarabelOptions>::uninit();
let status = unsafe { copp_clarabel_default_options(out.as_mut_ptr()) };
assert_eq!(status, CoppStatus::Ok);
let actual = unsafe { out.assume_init() };
assert_c_options_eq(actual, &expected);
}
#[test]
fn clarabel_options_build_maps_all_fields() {
let actual = CoppClarabelOptions {
verbosity: CoppVerbosity::Debug,
allow_almost_solved: false,
allow_max_iterations: true,
allow_max_time: true,
allow_callback_terminated: false,
allow_insufficient_progress: true,
clarabel_settings: CoppClarabelSettings::from_settings(&custom_settings("mkl")),
}
.build();
let ClarabelOptions {
verbosity,
clarabel_settings,
allow_almost_solved,
allow_max_iterations,
allow_max_time,
allow_callback_terminated,
allow_insufficient_progress,
} = actual;
assert!(verbosity == Verbosity::Debug);
assert!(!allow_almost_solved);
assert!(allow_max_iterations);
assert!(allow_max_time);
assert!(!allow_callback_terminated);
assert!(allow_insufficient_progress);
assert_settings_eq(&clarabel_settings, &custom_settings("mkl"));
}
}