diff --git a/Clarabel.rs b/Clarabel.rs index 770d408..e0ed282 160000 --- a/Clarabel.rs +++ b/Clarabel.rs @@ -1 +1 @@ -Subproject commit 770d4082a5fdcf9d875af6d95ce3b7034a029210 +Subproject commit e0ed282a5a01ed485a2ab4f05b8599f4dd3cb368 diff --git a/examples/c/example_qp.c b/examples/c/example_qp.c index 296ecd7..a85d6c9 100644 --- a/examples/c/example_qp.c +++ b/examples/c/example_qp.c @@ -2,7 +2,7 @@ // #define CLARABEL_USE_FLOAT #include "utils.h" - +#include #include int main(void) @@ -66,6 +66,14 @@ int main(void) ClarabelDefaultSolution solution = clarabel_DefaultSolver_solution(solver); print_solution(&solution); + // Get some detailed solve information + ClarabelDefaultInfo_f64 info = clarabel_DefaultSolver_info(solver); + printf("primal residual = %e\n", info.res_primal); + printf("dual residual = %e\n", info.res_dual); + printf("# of threads = %d\n", info.linsolver.threads); + printf("KKT nonzeros = %d\n", info.linsolver.nnzA); + printf("factor nonzeros = %d\n", info.linsolver.nnzL); + // Free the matrices and the solver clarabel_DefaultSolver_free(solver); diff --git a/examples/cpp/example_qp.cpp b/examples/cpp/example_qp.cpp index 88cf7c3..b3b989a 100644 --- a/examples/cpp/example_qp.cpp +++ b/examples/cpp/example_qp.cpp @@ -1,5 +1,5 @@ #include "utils.h" - +#include #include #include #include @@ -56,5 +56,13 @@ int main(void) DefaultSolution solution = solver.solution(); utils::print_solution(solution); + // Get some detailed solve information + DefaultInfo info = solver.info(); + std::cout << "primal residual = " << info.res_primal << std::endl; + std::cout << "dual residual = " << info.res_dual << std::endl; + std::cout << "# of threads = " << info.linsolver.threads << std::endl; + std::cout << "KKT nonzeros = " << info.linsolver.nnzA << std::endl; + std::cout << "factor nonzeros = " << info.linsolver.nnzL << std::endl; + return 0; } diff --git a/examples/cpp/utils.h b/examples/cpp/utils.h index 5bf40e0..1b4bfa5 100644 --- a/examples/cpp/utils.h +++ b/examples/cpp/utils.h @@ -33,6 +33,7 @@ namespace utils printf("Slacks (s)\t = "); print_array(solution.s); } + } #endif /* UTILS_H */ \ No newline at end of file diff --git a/include/Clarabel b/include/Clarabel index 2631557..410348e 100644 --- a/include/Clarabel +++ b/include/Clarabel @@ -2,8 +2,8 @@ #define CLARABEL_H #include "cpp/CscMatrix.h" -#include "cpp/DefaultInfo.h" #include "cpp/DefaultSettings.h" +#include "cpp/DefaultInfo.h" #include "cpp/DefaultSolution.h" #include "cpp/DefaultSolver.h" #include "cpp/SupportedConeT.h" diff --git a/include/Clarabel.h b/include/Clarabel.h index 7334955..f14dd90 100644 --- a/include/Clarabel.h +++ b/include/Clarabel.h @@ -2,8 +2,8 @@ #define CLARABEL_H #include "c/CscMatrix.h" -#include "c/DefaultInfo.h" #include "c/DefaultSettings.h" +#include "c/DefaultInfo.h" #include "c/DefaultSolution.h" #include "c/DefaultSolver.h" #include "c/SupportedConeT.h" diff --git a/include/c/DefaultInfo.h b/include/c/DefaultInfo.h index db795ba..8b726f9 100644 --- a/include/c/DefaultInfo.h +++ b/include/c/DefaultInfo.h @@ -2,10 +2,23 @@ #define CLARABEL_DEFAULT_INFO_H #include "ClarabelTypes.h" +#include "DefaultSettings.h" #include "DefaultSolution.h" #include + +typedef struct ClarabelLinearSolverInfo +{ + ClarabelDirectSolveMethods name; + uint32_t threads; + bool direct; + uint32_t nnzA; + uint32_t nnzL; + enum ClarabelSolverStatus status; +} ClarabelLinearSolverInfo; + + // ClarabelDefaultInfo types typedef struct ClarabelDefaultInfo_f64 { @@ -24,6 +37,8 @@ typedef struct ClarabelDefaultInfo_f64 double ktratio; double solve_time; enum ClarabelSolverStatus status; + ClarabelLinearSolverInfo linsolver; + // NB : `PrintStream stream` not passed to C API } ClarabelDefaultInfo_f64; typedef struct ClarabelDefaultInfo_f32 @@ -43,6 +58,8 @@ typedef struct ClarabelDefaultInfo_f32 float ktratio; double solve_time; enum ClarabelSolverStatus status; + ClarabelLinearSolverInfo linsolver; + // NB : `PrintStream stream` not passed to C API } ClarabelDefaultInfo_f32; #ifdef CLARABEL_USE_FLOAT diff --git a/include/c/DefaultSettings.h b/include/c/DefaultSettings.h index 0fd83f4..e2c9b12 100644 --- a/include/c/DefaultSettings.h +++ b/include/c/DefaultSettings.h @@ -10,6 +10,7 @@ // ClarabelDefaultSettings types typedef enum ClarabelDirectSolveMethods { + AUTO, QDLDL, #ifdef FEATURE_FAER_SPARSE FAER, diff --git a/include/cpp/DefaultInfo.h b/include/cpp/DefaultInfo.h index 162d1a9..16982a9 100644 --- a/include/cpp/DefaultInfo.h +++ b/include/cpp/DefaultInfo.h @@ -1,5 +1,6 @@ #pragma once +#include "DefaultSettings.h" #include "DefaultSolution.h" #include @@ -8,6 +9,16 @@ namespace clarabel { + +struct LinearSolverInfo +{ + ClarabelDirectSolveMethods name; + uint32_t threads; + bool direct; + uint32_t nnzA; + uint32_t nnzL; +}; + template struct DefaultInfo { @@ -28,6 +39,7 @@ struct DefaultInfo T ktratio; double solve_time; clarabel::SolverStatus status; + clarabel::LinearSolverInfo linsolver; // NB : `PrintStream stream` not passed to C++ API }; diff --git a/include/cpp/DefaultSettings.h b/include/cpp/DefaultSettings.h index b680753..8df5ee0 100644 --- a/include/cpp/DefaultSettings.h +++ b/include/cpp/DefaultSettings.h @@ -8,6 +8,7 @@ namespace clarabel enum class ClarabelDirectSolveMethods { + AUTO, QDLDL, #ifdef FEATURE_FAER_SPARSE FAER, diff --git a/rust_wrapper/src/solver/implementations/default/info.rs b/rust_wrapper/src/solver/implementations/default/info.rs index 2e81222..0a6d8a3 100644 --- a/rust_wrapper/src/solver/implementations/default/info.rs +++ b/rust_wrapper/src/solver/implementations/default/info.rs @@ -1,6 +1,30 @@ -use clarabel::{algebra::FloatT, solver::DefaultInfo}; +#![allow(non_snake_case)] +use clarabel::{algebra::FloatT, solver::DefaultInfo}; +use clarabel::solver::LinearSolverInfo; use crate::solver::implementations::default::solver::ClarabelSolverStatus; +use super::settings::ClarabelDirectSolveMethods; + +#[repr(C)] +pub struct ClarabelLinearSolverInfo { + pub name: ClarabelDirectSolveMethods, + pub threads: u32, + pub direct: bool, + pub nnzA: u32, + pub nnzL: u32, +} + +impl From<&LinearSolverInfo> for ClarabelLinearSolverInfo { + fn from(linsolver: &LinearSolverInfo) -> Self { + Self { + name: (&linsolver.name).into(), + threads: linsolver.threads as u32, + direct: linsolver.direct, + nnzA: linsolver.nnzA as u32, + nnzL: linsolver.nnzL as u32, + } + } +} #[repr(C)] pub struct ClarabelDefaultInfo { @@ -20,10 +44,12 @@ pub struct ClarabelDefaultInfo { pub solve_time: f64, pub status: ClarabelSolverStatus, + + pub linsolver: ClarabelLinearSolverInfo, } -impl From<&mut DefaultInfo> for ClarabelDefaultInfo { - fn from(info: &mut DefaultInfo) -> Self { +impl From<&DefaultInfo> for ClarabelDefaultInfo { + fn from(info: &DefaultInfo) -> Self { Self { μ: info.μ, sigma: info.sigma, @@ -39,7 +65,8 @@ impl From<&mut DefaultInfo> for ClarabelDefaultInfo { gap_rel: info.gap_rel, ktratio: info.ktratio, solve_time: info.solve_time, - status: ClarabelSolverStatus::from(&mut info.status), + status: ClarabelSolverStatus::from(&info.status), + linsolver: ClarabelLinearSolverInfo::from(&info.linsolver), } } } diff --git a/rust_wrapper/src/solver/implementations/default/settings.rs b/rust_wrapper/src/solver/implementations/default/settings.rs index 8bf2f31..d50257f 100644 --- a/rust_wrapper/src/solver/implementations/default/settings.rs +++ b/rust_wrapper/src/solver/implementations/default/settings.rs @@ -5,6 +5,7 @@ use clarabel::algebra::FloatT; #[allow(dead_code)] #[allow(clippy::upper_case_acronyms)] pub enum ClarabelDirectSolveMethods { + AUTO, QDLDL, #[cfg(feature = "faer-sparse")] FAER, @@ -12,6 +13,30 @@ pub enum ClarabelDirectSolveMethods { // CHOLMOD, (not supported yet) } +impl From<&ClarabelDirectSolveMethods> for String { + fn from(value: &ClarabelDirectSolveMethods) -> Self { + match value { + ClarabelDirectSolveMethods::AUTO => String::from("auto"), + ClarabelDirectSolveMethods::QDLDL => String::from("qdldl"), + #[cfg(feature = "faer-sparse")] + ClarabelDirectSolveMethods::FAER => String::from("faer"), + } + } +} + +impl From<&String> for ClarabelDirectSolveMethods { + fn from(value: &String) -> Self { + match value.as_str() { + "auto" => ClarabelDirectSolveMethods::AUTO, + "qdldl" => ClarabelDirectSolveMethods::QDLDL, + #[cfg(feature = "faer-sparse")] + "faer" => ClarabelDirectSolveMethods::FAER, + _ => ClarabelDirectSolveMethods::AUTO, + } + } +} + + #[allow(non_camel_case_types)] #[cfg(feature = "sdp")] #[repr(C)] @@ -102,10 +127,11 @@ fn _internal_DefaultSettings_default() -> ClarabelDefaultSettings let default = clarabel::solver::DefaultSettings::::default(); let default_direct_solver_setting = match default.direct_solve_method.as_str() { + "auto" => ClarabelDirectSolveMethods::AUTO, "qdldl" => ClarabelDirectSolveMethods::QDLDL, //"mkl" => ClarabelDirectSolveMethods::MKL, //"cholmod" => ClarabelDirectSolveMethods::CHOLMOD, - _ => ClarabelDirectSolveMethods::QDLDL, // Default + _ => ClarabelDirectSolveMethods::AUTO, // Default }; #[cfg(feature = "sdp")] diff --git a/rust_wrapper/src/solver/implementations/default/solution.rs b/rust_wrapper/src/solver/implementations/default/solution.rs index 08d8438..eaad3f3 100644 --- a/rust_wrapper/src/solver/implementations/default/solution.rs +++ b/rust_wrapper/src/solver/implementations/default/solution.rs @@ -31,7 +31,7 @@ impl DefaultSolution { z_length: solution.z.len(), s: solution.s.as_mut_ptr(), s_length: solution.s.len(), - status: ClarabelSolverStatus::from(&mut solution.status), + status: ClarabelSolverStatus::from(&solution.status), obj_val: solution.obj_val, obj_val_dual: solution.obj_val_dual, solve_time: solution.solve_time, diff --git a/rust_wrapper/src/solver/implementations/default/solver.rs b/rust_wrapper/src/solver/implementations/default/solver.rs index bf31b71..3632bbb 100644 --- a/rust_wrapper/src/solver/implementations/default/solver.rs +++ b/rust_wrapper/src/solver/implementations/default/solver.rs @@ -362,8 +362,8 @@ pub enum ClarabelSolverStatus { ClarabelInsufficientProgress, } -impl From<&mut SolverStatus> for ClarabelSolverStatus { - fn from(value: &mut SolverStatus) -> Self { +impl From<&SolverStatus> for ClarabelSolverStatus { + fn from(value: &SolverStatus) -> Self { match value { SolverStatus::Unsolved => ClarabelSolverStatus::ClarabelUnsolved, SolverStatus::Solved => ClarabelSolverStatus::ClarabelSolved, @@ -418,7 +418,7 @@ fn _internal_DefaultSolver_info(solver: *mut c_void) -> ClarabelDefau let solver = unsafe { &mut *(solver as *mut lib::DefaultSolver) }; // Get the info field and convert it to a C struct. - ClarabelDefaultInfo::::from(&mut solver.info) + ClarabelDefaultInfo::::from(&solver.info) } #[no_mangle] diff --git a/rust_wrapper/src/utils/mod.rs b/rust_wrapper/src/utils/mod.rs index 1f6a044..d693bf4 100644 --- a/rust_wrapper/src/utils/mod.rs +++ b/rust_wrapper/src/utils/mod.rs @@ -41,14 +41,7 @@ pub fn get_solver_settings_from_c( min_terminate_step_length: value.min_terminate_step_length, max_threads: value.max_threads, direct_kkt_solver: value.direct_kkt_solver, - direct_solve_method: match value.direct_solve_method { - ClarabelDirectSolveMethods::QDLDL => String::from("qdldl"), - #[cfg(feature = "faer-sparse")] - ClarabelDirectSolveMethods::FAER => String::from("faer"), - // Not supported yet - //ClarabelDirectSolveMethods::MKL => String::from("mkl"), - //ClarabelDirectSolveMethods::CHOLMOD => String::from("cholmod"), - }, + direct_solve_method: (&value.direct_solve_method).into(), static_regularization_enable: value.static_regularization_enable, static_regularization_constant: value.static_regularization_constant, static_regularization_proportional: value.static_regularization_proportional, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5f863ea..8c14322 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(clarabel_cpp_tests mixed_conic.cpp data_updating.cpp sdp_chordal.cpp + get_info.cpp ) target_link_libraries(clarabel_cpp_tests libclarabel_c_shared diff --git a/tests/get_info.cpp b/tests/get_info.cpp new file mode 100644 index 0000000..b28a19a --- /dev/null +++ b/tests/get_info.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace clarabel; +using namespace Eigen; + +class GetInfoTest : public ::testing::Test +{ + protected: + SparseMatrix P, A; + Vector c = { 1., 1. }; + Vector b = { -1., 0., 0., 1., 0.7, 0.7 }; + vector> cones = { + NonnegativeConeT(3), + NonnegativeConeT(3) + }; + DefaultSettings settings = DefaultSettings::default_settings(); + + GetInfoTest() + { + MatrixXd P_dense(2, 2); + P_dense << 4., 1., + 1., 2.; + P = P_dense.sparseView(); + P.makeCompressed(); + + MatrixXd A_dense(6, 2); + A_dense << + -1., -1., + -1., 0., + 0., -1., + 1., 1., + 1., 0., + 0., 1.; + A = A_dense.sparseView(); + A.makeCompressed(); + } +}; + + +TEST_F(GetInfoTest, Feasible) +{ + DefaultSolver solver(P, c, A, b, cones, settings); + solver.solve(); + auto info = solver.info(); + + DefaultSolution solution = solver.solution(); + ASSERT_EQ(solution.status, SolverStatus::Solved); + + // check the linear solver reporting + ASSERT_EQ(info.linsolver.name, ClarabelDirectSolveMethods::QDLDL); + ASSERT_EQ(info.linsolver.threads, 1); + ASSERT_EQ(info.linsolver.direct, true); + ASSERT_EQ(info.linsolver.nnzA, 17); + ASSERT_EQ(info.linsolver.nnzL, 9); +} \ No newline at end of file