From f508be6cd58176301e225548fab4d6c0bbbfd366 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Mon, 21 Aug 2023 12:08:44 -0400 Subject: [PATCH 01/36] Updated the Matlab interface --- .gitmodules | 4 +- make_osqp.m | 8 +- osqp_mex.cpp | 1234 +++++++++++++++++++++++--------------------------- osqp_mex.hpp | 1 + osqp_sources | 2 +- 5 files changed, 576 insertions(+), 673 deletions(-) diff --git a/.gitmodules b/.gitmodules index d8ab27a..d19485e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "osqp_sources"] +[submodule "osqp"] path = osqp_sources - url = https://github.com/osqp/osqp + url = git@github.com:osqp/osqp.git diff --git a/make_osqp.m b/make_osqp.m index ce3a271..a267b99 100644 --- a/make_osqp.m +++ b/make_osqp.m @@ -111,7 +111,7 @@ function make_osqp(varargin) % Set library extension lib_ext = '.a'; -lib_name = sprintf('libosqp%s', lib_ext); +lib_name = sprintf('libosqpstatic%s', lib_ext); % Set osqp directory and osqp_build directory @@ -119,12 +119,14 @@ function make_osqp(varargin) [makefile_path,~,~] = fileparts(which('make_osqp.m')); osqp_dir = fullfile(makefile_path, 'osqp_sources'); osqp_build_dir = fullfile(osqp_dir, 'build'); -qdldl_dir = fullfile(osqp_dir, 'lin_sys', 'direct', 'qdldl'); +qdldl_dir = fullfile(osqp_dir, 'algebra', '_common', 'lin_sys', 'qdldl'); cg_sources_dir = fullfile('.', 'codegen', 'sources'); % Include directory inc_dir = [ - fullfile(sprintf(' -I%s', osqp_dir), 'include'), ... + fullfile(sprintf(' -I%s', osqp_dir), 'include', 'public'), ... + fullfile(sprintf(' -I%s', osqp_dir), 'include', 'private'), ... + fullfile(sprintf(' -I%s', osqp_dir), 'build', 'include', 'public'), ... sprintf(' -I%s', qdldl_dir), ... fullfile(sprintf(' -I%s', qdldl_dir), 'qdldl_sources', 'include')]; diff --git a/osqp_mex.cpp b/osqp_mex.cpp index 5506e1d..103dcfb 100755 --- a/osqp_mex.cpp +++ b/osqp_mex.cpp @@ -2,104 +2,60 @@ #include "matrix.h" #include "osqp_mex.hpp" #include "osqp.h" -#include "ctrlc.h" // Needed for interrupt +//#include "ctrlc.h" // Needed for interrupt #include "qdldl_interface.h" // To extract workspace for codegen -// all of the OSQP_INFO fieldnames as strings -const char* OSQP_INFO_FIELDS[] = {"iter", //c_int - "status" , //char* - "status_val" , //c_int - "status_polish", //c_int - "obj_val", //c_float - "pri_res", //c_float - "dua_res", //c_float - "setup_time", //c_float, only used if PROFILING - "solve_time", //c_float, only used if PROFILING - "update_time", //c_float, only used if PROFILING - "polish_time", //c_float, only used if PROFILING - "run_time", //c_float, only used if PROFILING - "rho_updates", //c_int - "rho_estimate"}; //c_float - -const char* OSQP_SETTINGS_FIELDS[] = {"rho", //c_float - "sigma", //c_float - "scaling", //c_int - "adaptive_rho", //c_int - "adaptive_rho_interval", //c_int - "adaptive_rho_tolerance", //c_float - "adaptive_rho_fraction", //c_float - "max_iter", //c_int - "eps_abs", //c_float - "eps_rel", //c_float - "eps_prim_inf", //c_float - "eps_dual_inf", //c_float - "alpha", //c_float - "linsys_solver", //c_int - "delta", //c_float - "polish", //c_int - "polish_refine_iter", //c_int - "verbose", //c_int - "scaled_termination", //c_int - "check_termination", //c_int - "warm_start", //c_int - "time_limit"}; //c_float - -const char* CSC_FIELDS[] = {"nzmax", //c_int - "m", //c_int - "n", //c_int - "p", //c_int* - "i", //c_int* - "x", //c_float* - "nz"}; //c_int - -const char* OSQP_DATA_FIELDS[] = {"n", //c_int - "m", //c_int - "P", //csc - "A", //csc - "q", //c_float* - "l", //c_float* - "u"}; //c_float* - -const char* LINSYS_SOLVER_FIELDS[] = {"L", //csc - "Dinv", //c_float* - "P", //c_int* - "bp", //c_float* - "sol", //c_float* - "rho_inv_vec", //c_float* - "sigma", //c_float - "polish", //c_int - "n", //c_int - "m", //c_int - "Pdiag_idx", //c_int* - "Pdiag_n", //c_int - "KKT", //csc - "PtoKKT", //c_int* - "AtoKKT", //c_int* - "rhotoKKT", //c_int* - "D", //c_float* - "etree", //c_int* - "Lnz", //c_int* - "iwork", //c_int* - "bwork", //c_int* - "fwork"}; //c_float* - -const char* OSQP_SCALING_FIELDS[] = {"c", //c_float - "D", //c_float* - "E", //c_float* - "cinv", //c_float - "Dinv", //c_float* - "Einv"}; //c_float* - -const char* OSQP_RHO_VECTORS_FIELDS[] = {"rho_vec", //c_float* - "rho_inv_vec", //c_float* - "constr_type"}; //c_int* - -const char* OSQP_WORKSPACE_FIELDS[] = {"rho_vectors", - "data", - "linsys_solver", - "scaling", - "settings"}; +//c_int is replaced with OSQPInt +//c_float is replaced with OSQPFloat + +//TODO: Check if this definition is required, and maybe replace it with: +// enum linsys_solver_type { QDLDL_SOLVER, MKL_PARDISO_SOLVER }; +#define QDLDL_SOLVER 0 //Based on the previous API +// all of the OSQP_INFO fieldnames as strings +const char* OSQP_INFO_FIELDS[] = {"status", //char* + "status_val", //OSQPInt + "status_polish", //OSQPInt + "obj_val", //OSQPFloat + "prim_res", //OSQPFloat + "dual_res", //OSQPFloat + "iter", //OSQPInt + "rho_updates", //OSQPInt + "rho_estimate", //OSQPFloat + "setup_time", //OSQPFloat + "solve_time", //OSQPFloat + "update_time", //OSQPFloat + "polish_time", //OSQPFloat + "run_time"}; //OSQPFloat + +const char* OSQP_SETTINGS_FIELDS[] = {"device", //OSQPInt + "linsys_solver", //enum osqp_linsys_solver_type + "verbose", //OSQPInt + "warm_starting", //OSQPInt + "scaling", //OSQPInt + "polishing", //OSQPInt + "rho", //OSQPFloat + "rho_is_vec", //OSQPInt + "sigma", //OSQPFloat + "alpha", //OSQPFloat + "cg_max_iter", //OSQPInt + "cg_tol_reduction", //OSQPInt + "cg_tol_fraction", //OSQPFloat + "cg_precond", //osqp_precond_type + "adaptive_rho", //OSQPInt + "adaptive_rho_interval", //OSQPInt + "adaptive_rho_fraction", //OSQPFloat + "adaptive_rho_tolerance", //OSQPFloat + "max_iter", //OSQPInt + "eps_abs", //OSQPFloat + "eps_rel", //OSQPFloat + "eps_prim_inf", //OSQPFloat + "eps_dual_inf", //OSQPFloat + "scaled_termination", //OSQPInt + "check_termination", //OSQPInt + "time_limit", //OSQPFloat + "delta", //OSQPFloat + "polish_refine_iter"}; //OSQPInt #define NEW_SETTINGS_TOL (1e-10) @@ -114,28 +70,42 @@ class OsqpData }; // internal utility functions -void castToDoubleArr(c_float *arr, double* arr_out, c_int len); -void setToNaN(double* arr_out, c_int len); -void copyMxStructToSettings(const mxArray*, OSQPSettings*); -void copyUpdatedSettingsToWork(const mxArray*, OsqpData*); -void castCintToDoubleArr(c_int *arr, double* arr_out, c_int len); -c_int* copyToCintVector(mwIndex * vecData, c_int numel); -c_int* copyDoubleToCintVector(double* vecData, c_int numel); -c_float* copyToCfloatVector(double * vecData, c_int numel); -mxArray* copyInfoToMxStruct(OSQPInfo* info); -mxArray* copySettingsToMxStruct(OSQPSettings* settings); -mxArray* copyCscMatrixToMxStruct(csc* M); -mxArray* copyDataToMxStruct(OSQPWorkspace* work); -mxArray* copyLinsysSolverToMxStruct(OSQPWorkspace* work); -mxArray* copyScalingToMxStruct(OSQPWorkspace * work); -mxArray* copyWorkToMxStruct(OSQPWorkspace* work); +void initializeOSQPSolver(OSQPSolver* osqpSolver); +void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len); +void setToNaN(double* arr_out, OSQPInt len); +void copyMxStructToSettings(const mxArray*, OSQPSettings*); +void copyUpdatedSettingsToWork(const mxArray*, OSQPSolver*); +void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len); +void freeCscMatrix(OSQPCscMatrix* M); +OSQPInt* copyToCintVector(mwIndex * vecData, OSQPInt numel); +OSQPInt* copyDoubleToCintVector(double* vecData, OSQPInt numel); +OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel); +mxArray* copyInfoToMxStruct(OSQPInfo* info); +mxArray* copySettingsToMxStruct(OSQPSettings* settings); +OSQPInt osqp_update_max_iter(OSQPSolver* osqpSolver, OSQPInt max_iter_new); +OSQPInt osqp_update_eps_abs(OSQPSolver* osqpSolver, OSQPFloat eps_abs_new); +OSQPInt osqp_update_eps_rel(OSQPSolver* osqpSolver, OSQPFloat eps_rel_new); +OSQPInt osqp_update_eps_prim_inf(OSQPSolver* osqpSolver, OSQPFloat eps_prim_inf_new); +OSQPInt osqp_update_eps_dual_inf(OSQPSolver* osqpSolver, OSQPFloat eps_dual_inf_new); +OSQPInt osqp_update_alpha(OSQPSolver* osqpSolver, OSQPFloat alpha_new); +OSQPInt osqp_update_delta(OSQPSolver* osqpSolver, OSQPFloat delta_new); +OSQPInt osqp_update_polish_refine_iter(OSQPSolver* osqpSolver, OSQPInt polish_refine_iter_new); +OSQPInt osqp_update_verbose(OSQPSolver* osqpSolver, OSQPInt verbose_new); +OSQPInt osqp_update_scaled_termination(OSQPSolver* osqpSolver, OSQPInt scaled_termination_new); +OSQPInt osqp_update_check_termination(OSQPSolver* osqpSolver, OSQPInt check_termination_new); +OSQPInt osqp_update_warm_start(OSQPSolver* osqpSolver, OSQPInt warm_start_new); +#ifdef PROFILING +OSQPInt osqp_update_time_limit(OSQPSolver* osqpSolver, OSQPFloat time_limit_new); +#endif /* ifdef PROFILING */ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) -{ OsqpData* osqpData; // OSQP data identifier +{ + OSQPSolver* osqpSolver; + initializeOSQPSolver(osqpSolver); // Exitflag - c_int exitflag = 0; + OSQPInt exitflag = 0; // Static string for static methods char stat_string[64]; @@ -162,7 +132,9 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if(mxGetString(prhs[1], stat_string, sizeof(stat_string))){ // If we are dealing with non-static methods, get the class instance pointer from the second input + OsqpData* osqpData; // OSQP data identifier osqpData = convertMat2Ptr(prhs[1]); + osqpSolver->work = osqpData->work; } else { if (strcmp("static", stat_string)){ mexErrMsgTxt("Second argument for static functions is string 'static'"); @@ -172,10 +144,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // delete the object and its data if (!strcmp("delete", cmd)) { - //clean up the problem workspace - if(osqpData->work){ - osqp_cleanup(osqpData->work); - } + osqp_cleanup(osqpSolver); //clean up the handle object destroyObject(prhs[1]); @@ -189,30 +158,11 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("current_settings", cmd)) { //throw an error if this is called before solver is configured - if(!osqpData->work){ + if(!osqpSolver->work){ mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); } //report the current settings - plhs[0] = copySettingsToMxStruct(osqpData->work->settings); - return; - } - - - // return workspace structure - if (!strcmp("get_workspace", cmd)) { - - //throw an error if this is called before solver is configured - if(!osqpData->work){ - mexErrMsgTxt("Solver is uninitialized. No data have been configured."); - } - - //throw an error if linear systems solver is different than qdldl - if(osqpData->work->linsys_solver->type != QDLDL_SOLVER){ - mexErrMsgTxt("Solver setup was not performed using QDLDL! Please perform setup with linsys_solver as QDLDL."); - } - - //return data - plhs[0] = copyWorkToMxStruct(osqpData->work); + plhs[0] = copySettingsToMxStruct(osqpSolver->settings); return; } @@ -223,11 +173,11 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) //for disallowing overwrite of selected settings after initialization, //and for all error checking //throw an error if this is called before solver is configured - if(!osqpData->work){ + if(!osqpSolver->work){ mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); } - copyUpdatedSettingsToWork(prhs[2],osqpData); + copyUpdatedSettingsToWork(prhs[2],osqpSolver); return; } @@ -253,13 +203,12 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("setup", cmd)) { //throw an error if this is called more than once - if(osqpData->work){ + if(osqpSolver->work){ mexErrMsgTxt("Solver is already initialized with problem data."); } //Create data and settings containers OSQPSettings* settings = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); - OSQPData* data = (OSQPData *)mxCalloc(1,sizeof(OSQPData)); // handle the problem data first. Matlab-side // class wrapper is responsible for ensuring that @@ -274,24 +223,26 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) const mxArray* l = prhs[7]; const mxArray* u = prhs[8]; - // Create Data Structure - data->n = (c_int) mxGetScalar(prhs[2]); - data->m = (c_int) mxGetScalar(prhs[3]); - data->q = copyToCfloatVector(mxGetPr(q), data->n); - data->l = copyToCfloatVector(mxGetPr(l), data->m); - data->u = copyToCfloatVector(mxGetPr(u), data->m); + + OSQPInt dataN = (OSQPInt)mxGetScalar(prhs[2]); + OSQPInt dataM = (OSQPInt)mxGetScalar(prhs[3]); + OSQPFloat* dataQ = copyToOSQPFloatVector(mxGetPr(q), dataN); + OSQPFloat* dataL = copyToOSQPFloatVector(mxGetPr(l), dataM); + OSQPFloat* dataU = copyToOSQPFloatVector(mxGetPr(u), dataM); // Matrix P: nnz = P->p[n] - c_int * Pp = copyToCintVector(mxGetJc(P), data->n + 1); - c_int * Pi = copyToCintVector(mxGetIr(P), Pp[data->n]); - c_float * Px = copyToCfloatVector(mxGetPr(P), Pp[data->n]); - data->P = csc_matrix(data->n, data->n, Pp[data->n], Px, Pi, Pp); + OSQPInt * Pp = (OSQPInt*)copyToCintVector(mxGetJc(P), dataN + 1); + OSQPInt * Pi = (OSQPInt*)copyToCintVector(mxGetIr(P), Pp[dataN]); + OSQPFloat * Px = copyToOSQPFloatVector(mxGetPr(P), Pp[dataN]); + OSQPCscMatrix* dataP = NULL; + csc_set_data(dataP, dataN, dataN, Pp[dataN], Px, Pi, Pp); // Matrix A: nnz = A->p[n] - c_int * Ap = copyToCintVector(mxGetJc(A), data->n + 1); - c_int * Ai = copyToCintVector(mxGetIr(A), Ap[data->n]); - c_float * Ax = copyToCfloatVector(mxGetPr(A), Ap[data->n]); - data->A = csc_matrix(data->m, data->n, Ap[data->n], Ax, Ai, Ap); + OSQPInt* Ap = (OSQPInt*)copyToCintVector(mxGetJc(A), dataN + 1); + OSQPInt* Ai = (OSQPInt*)copyToCintVector(mxGetIr(A), Ap[dataN]); + OSQPFloat * Ax = copyToOSQPFloatVector(mxGetPr(A), Ap[dataN]); + OSQPCscMatrix* dataA = NULL; + csc_set_data(dataA, dataM, dataN, Ap[dataN], Ax, Ai, Ap); // Create Settings const mxArray* mxSettings = prhs[9]; @@ -304,22 +255,24 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } // Setup workspace - exitflag = osqp_setup(&(osqpData->work), data, settings); + //exitflag = osqp_setup(&(osqpData->work), data, settings); + exitflag = osqp_setup(&osqpSolver, dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); //cleanup temporary structures // Data - if (data->q) c_free(data->q); - if (data->l) c_free(data->l); - if (data->u) c_free(data->u); if (Px) c_free(Px); if (Pi) c_free(Pi); if (Pp) c_free(Pp); - if (data->P) c_free(data->P); if (Ax) c_free(Ax); if (Ai) c_free(Ai); if (Ap) c_free(Ap); - if (data->A) c_free(data->A); - if (data) mxFree(data); + //if (data->A) c_free(data->A); + //if (data) mxFree(data); + if (dataQ) c_free(dataQ); + if (dataL) c_free(dataL); + if (dataU) c_free(dataU); + if (dataP) freeCscMatrix(dataP); + if (dataA) freeCscMatrix(dataA); // Settings if (settings) mxFree(settings); @@ -336,12 +289,12 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("get_dimensions", cmd)) { //throw an error if this is called before solver is configured - if(!osqpData->work){ + if(!osqpSolver->work){ mexErrMsgTxt("Solver has not been initialized."); } - plhs[0] = mxCreateDoubleScalar(osqpData->work->data->n); - plhs[1] = mxCreateDoubleScalar(osqpData->work->data->m); + plhs[0] = mxCreateDoubleScalar(osqpSolver->work->data->n); + plhs[1] = mxCreateDoubleScalar(osqpSolver->work->data->m); return; } @@ -358,7 +311,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("update", cmd)) { //throw an error if this is called before solver is configured - if(!osqpData->work){ + if(!osqpSolver->work){ mexErrMsgTxt("Solver has not been initialized."); } @@ -375,31 +328,31 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) Px_n = mxGetScalar(prhs[7]); Ax_n = mxGetScalar(prhs[10]); - // Copy vectors to ensure they are cast as c_float - c_float *q_vec; - c_float *l_vec; - c_float *u_vec; - c_float *Px_vec; - c_float *Ax_vec; - c_int *Px_idx_vec = NULL; - c_int *Ax_idx_vec = NULL; + // Copy vectors to ensure they are cast as OSQPFloat + OSQPFloat *q_vec = NULL; + OSQPFloat *l_vec = NULL; + OSQPFloat *u_vec = NULL; + OSQPFloat *Px_vec = NULL; + OSQPFloat *Ax_vec = NULL; + OSQPInt *Px_idx_vec = NULL; + OSQPInt *Ax_idx_vec = NULL; if(!mxIsEmpty(q)){ - q_vec = copyToCfloatVector(mxGetPr(q), - osqpData->work->data->n); + q_vec = copyToOSQPFloatVector(mxGetPr(q), + osqpSolver->work->data->n); } if(!mxIsEmpty(l)){ - l_vec = copyToCfloatVector(mxGetPr(l), - osqpData->work->data->m); + l_vec = copyToOSQPFloatVector(mxGetPr(l), + osqpSolver->work->data->m); } if(!mxIsEmpty(u)){ - u_vec = copyToCfloatVector(mxGetPr(u), - osqpData->work->data->m); + u_vec = copyToOSQPFloatVector(mxGetPr(u), + osqpSolver->work->data->m); } if(!mxIsEmpty(Px)){ - Px_vec = copyToCfloatVector(mxGetPr(Px), Px_n); + Px_vec = copyToOSQPFloatVector(mxGetPr(Px), Px_n); } if(!mxIsEmpty(Ax)){ - Ax_vec = copyToCfloatVector(mxGetPr(Ax), Ax_n); + Ax_vec = copyToOSQPFloatVector(mxGetPr(Ax), Ax_n); } if(!mxIsEmpty(Px_idx)){ Px_idx_vec = copyDoubleToCintVector(mxGetPr(Px_idx), Px_n); @@ -408,54 +361,21 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) Ax_idx_vec = copyDoubleToCintVector(mxGetPr(Ax_idx), Ax_n); } - if(!exitflag && !mxIsEmpty(q)){ - exitflag = osqp_update_lin_cost(osqpData->work, q_vec); - if(exitflag){ - exitflag = 1; - } - } - if(!exitflag && !mxIsEmpty(l) && !mxIsEmpty(u)){ - exitflag = osqp_update_bounds(osqpData->work, l_vec, u_vec); - if(exitflag){ - exitflag = 2; - } + if (!exitflag && (!mxIsEmpty(q) || !mxIsEmpty(l) || !mxIsEmpty(u))) { + exitflag = osqp_update_data_vec(osqpSolver, q_vec, l_vec, u_vec); + if (exitflag) exitflag=1; } - else if(!exitflag && !mxIsEmpty(l)){ - exitflag = osqp_update_lower_bound(osqpData->work, l_vec); - if(exitflag){ - exitflag = 3; - } - } - else if(!exitflag && !mxIsEmpty(u)){ - exitflag = osqp_update_upper_bound(osqpData->work, u_vec); - if(exitflag){ - exitflag = 4; - } - } - if(!exitflag && !mxIsEmpty(Px) && !mxIsEmpty(Ax)){ - exitflag = osqp_update_P_A(osqpData->work, Px_vec, Px_idx_vec, Px_n, - Ax_vec, Ax_idx_vec, Ax_n); - if(exitflag){ - exitflag = 5; - } - } - else if(!exitflag && !mxIsEmpty(Px)){ - exitflag = osqp_update_P(osqpData->work, Px_vec, Px_idx_vec, Px_n); - if(exitflag){ - exitflag = 6; - } - } - else if(!exitflag && !mxIsEmpty(Ax)){ - exitflag = osqp_update_A(osqpData->work, Ax_vec, Ax_idx_vec, Ax_n); - if(exitflag){ - exitflag = 7; - } + + if (!exitflag && (!mxIsEmpty(Px) || !mxIsEmpty(Ax))) { + exitflag = osqp_update_data_mat(osqpSolver, Px_vec, Px_idx_vec, Px_n, Ax_vec, Ax_idx_vec, Ax_n); + if (exitflag) exitflag=2; } + // Free vectors - if(!mxIsEmpty(q)) c_free(q_vec); - if(!mxIsEmpty(l)) c_free(l_vec); - if(!mxIsEmpty(u)) c_free(u_vec); + if(!mxIsEmpty(q)) c_free(q_vec); + if(!mxIsEmpty(l)) c_free(l_vec); + if(!mxIsEmpty(u)) c_free(u_vec); if(!mxIsEmpty(Px)) c_free(Px_vec); if(!mxIsEmpty(Ax)) c_free(Ax_vec); if(!mxIsEmpty(Px_idx)) c_free(Px_idx_vec); @@ -464,196 +384,134 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // Report errors (if any) switch (exitflag) { case 1: - mexErrMsgTxt("Linear cost update error!"); + mexErrMsgTxt("Data update error!"); case 2: - mexErrMsgTxt("Bounds update error!"); - case 3: - mexErrMsgTxt("Lower bound update error!"); - case 4: - mexErrMsgTxt("Upper bound update error!"); - case 5: - mexErrMsgTxt("Matrices P and A update error!"); - case 6: - mexErrMsgTxt("Matrix P update error!"); - case 7: - mexErrMsgTxt("Matrix A update error!"); - } - - return; - } - - - // Warm start x and y variables - if (!strcmp("warm_start", cmd)) { - - //throw an error if this is called before solver is configured - if(!osqpData->work){ - mexErrMsgTxt("Solver has not been initialized."); - } - - // Fill x and y - const mxArray *x = prhs[2]; - const mxArray *y = prhs[3]; - - - // Copy vectors to ensure they are cast as c_float - c_float *x_vec; - c_float *y_vec; - - if(!mxIsEmpty(x)){ - x_vec = copyToCfloatVector(mxGetPr(x), - osqpData->work->data->n); - } - if(!mxIsEmpty(y)){ - y_vec = copyToCfloatVector(mxGetPr(y), - osqpData->work->data->m); - } - - // Warm start x and y - osqp_warm_start(osqpData->work, x_vec, y_vec); - - // Free vectors - if(!mxIsEmpty(x)) c_free(x_vec); - if(!mxIsEmpty(y)) c_free(y_vec); - - return; - } - - - // Warm start x variable - if (!strcmp("warm_start_x", cmd)) { - - //throw an error if this is called before solver is configured - if(!osqpData->work){ - mexErrMsgTxt("Solver has not been initialized."); + mexErrMsgTxt("Matrix update error!"); } - // Fill x and y - const mxArray *x = prhs[2]; - - - // Copy vectors to ensure they are cast as c_float - c_float *x_vec; - - if(!mxIsEmpty(x)){ - x_vec = copyToCfloatVector(mxGetPr(x), - osqpData->work->data->n); - } - - // Warm start x - osqp_warm_start_x(osqpData->work, x_vec); - - // Free vectors - if(!mxIsEmpty(x)) c_free(x_vec); - return; } - - // Warm start y variable - if (!strcmp("warm_start_y", cmd)) { - - //throw an error if this is called before solver is configured - if(!osqpData->work){ + if (!strcmp("warm_start", cmd) || !strcmp("warm_start_x", cmd) || !strcmp("warm_start_y", cmd)) { + + //throw an error if this is called before solver is configured + if(!osqpSolver->work){ mexErrMsgTxt("Solver has not been initialized."); } - // Fill x and y - const mxArray *y = prhs[2]; + // Fill x and y + const mxArray *x = NULL; + const mxArray *y = NULL; + if (!strcmp("warm_start", cmd)) { + x = prhs[2]; + y = prhs[3]; + } + else if (!strcmp("warm_start_x", cmd)) { + x = prhs[2]; + y = NULL; + } + else if (!strcmp("warm_start_y", cmd)) { + x = NULL; + y = prhs[2]; + } - // Copy vectors to ensure they are cast as c_float - c_float *y_vec; + // Copy vectors to ensure they are cast as OSQPFloat + OSQPFloat *x_vec = NULL; + OSQPFloat *y_vec = NULL; - if(!mxIsEmpty(y)){ - y_vec = copyToCfloatVector(mxGetPr(y), - osqpData->work->data->m); - } + if(!mxIsEmpty(x)){ + x_vec = copyToOSQPFloatVector(mxGetPr(x), + osqpSolver->work->data->n); + } + if(!mxIsEmpty(y)){ + y_vec = copyToOSQPFloatVector(mxGetPr(y), + osqpSolver->work->data->m); + } - // Warm start x - osqp_warm_start_y(osqpData->work, y_vec); + // Warm start x and y + osqp_warm_start(osqpSolver, x_vec, y_vec); - // Free vectors - if(!mxIsEmpty(y)) c_free(y_vec); + // Free vectors + if(!x_vec) c_free(x_vec); + if(!y_vec) c_free(y_vec); - return; + return; } - - // SOLVE if (!strcmp("solve", cmd)) { if (nlhs != 5 || nrhs != 2){ mexErrMsgTxt("Solve : wrong number of inputs / outputs"); } - if(!osqpData->work){ + if(!osqpSolver->work){ mexErrMsgTxt("No problem data has been given."); } // solve the problem - osqp_solve(osqpData->work); + osqp_solve(osqpSolver); // Allocate space for solution // primal variables - plhs[0] = mxCreateDoubleMatrix(osqpData->work->data->n,1,mxREAL); + plhs[0] = mxCreateDoubleMatrix(osqpSolver->work->data->n,1,mxREAL); // dual variables - plhs[1] = mxCreateDoubleMatrix(osqpData->work->data->m,1,mxREAL); + plhs[1] = mxCreateDoubleMatrix(osqpSolver->work->data->m,1,mxREAL); // primal infeasibility certificate - plhs[2] = mxCreateDoubleMatrix(osqpData->work->data->m,1,mxREAL); + plhs[2] = mxCreateDoubleMatrix(osqpSolver->work->data->m,1,mxREAL); // dual infeasibility certificate - plhs[3] = mxCreateDoubleMatrix(osqpData->work->data->n,1,mxREAL); + plhs[3] = mxCreateDoubleMatrix(osqpSolver->work->data->n,1,mxREAL); //copy results to mxArray outputs //assume that five outputs will always //be returned to matlab-side class wrapper - if ((osqpData->work->info->status_val != OSQP_PRIMAL_INFEASIBLE) && - (osqpData->work->info->status_val != OSQP_DUAL_INFEASIBLE)){ + if ((osqpSolver->info->status_val != OSQP_PRIMAL_INFEASIBLE) && + (osqpSolver->info->status_val != OSQP_DUAL_INFEASIBLE)){ //primal and dual solutions - castToDoubleArr(osqpData->work->solution->x, mxGetPr(plhs[0]), osqpData->work->data->n); - castToDoubleArr(osqpData->work->solution->y, mxGetPr(plhs[1]), osqpData->work->data->m); + castToDoubleArr(osqpSolver->solution->x, mxGetPr(plhs[0]), osqpSolver->work->data->n); + castToDoubleArr(osqpSolver->solution->y, mxGetPr(plhs[1]), osqpSolver->work->data->m); //infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[2]), osqpData->work->data->m); - setToNaN(mxGetPr(plhs[3]), osqpData->work->data->n); + setToNaN(mxGetPr(plhs[2]), osqpSolver->work->data->m); + setToNaN(mxGetPr(plhs[3]), osqpSolver->work->data->n); - } else if (osqpData->work->info->status_val == OSQP_PRIMAL_INFEASIBLE || - osqpData->work->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE){ //primal infeasible + } else if (osqpSolver->info->status_val == OSQP_PRIMAL_INFEASIBLE || + osqpSolver->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE){ //primal infeasible //primal and dual solutions -> NaN values - setToNaN(mxGetPr(plhs[0]), osqpData->work->data->n); - setToNaN(mxGetPr(plhs[1]), osqpData->work->data->m); + setToNaN(mxGetPr(plhs[0]), osqpSolver->work->data->n); + setToNaN(mxGetPr(plhs[1]), osqpSolver->work->data->m); //primal infeasibility certificates - castToDoubleArr(osqpData->work->delta_y, mxGetPr(plhs[2]), osqpData->work->data->m); + castToDoubleArr(osqpSolver->solution->prim_inf_cert, mxGetPr(plhs[2]), osqpSolver->work->data->m); //dual infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[3]), osqpData->work->data->n); + setToNaN(mxGetPr(plhs[3]), osqpSolver->work->data->n); // Set objective value to infinity - osqpData->work->info->obj_val = mxGetInf(); + osqpSolver->info->obj_val = mxGetInf(); } else { //dual infeasible //primal and dual solutions -> NaN values - setToNaN(mxGetPr(plhs[0]), osqpData->work->data->n); - setToNaN(mxGetPr(plhs[1]), osqpData->work->data->m); + setToNaN(mxGetPr(plhs[0]), osqpSolver->work->data->n); + setToNaN(mxGetPr(plhs[1]), osqpSolver->work->data->m); //primal infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[2]), osqpData->work->data->m); + setToNaN(mxGetPr(plhs[2]), osqpSolver->work->data->m); //dual infeasibility certificates - castToDoubleArr(osqpData->work->delta_x, mxGetPr(plhs[3]), osqpData->work->data->n); + castToDoubleArr(osqpSolver->solution->dual_inf_cert, mxGetPr(plhs[3]), osqpSolver->work->data->n); // Set objective value to -infinity - osqpData->work->info->obj_val = -mxGetInf(); + osqpSolver->info->obj_val = -mxGetInf(); } - if (osqpData->work->info->status_val == OSQP_NON_CVX) { - osqpData->work->info->obj_val = mxGetNaN(); + if (osqpSolver->info->status_val == OSQP_NON_CVX) { + osqpSolver->info->obj_val = mxGetNaN(); } - plhs[4] = copyInfoToMxStruct(osqpData->work->info); // Info structure + plhs[4] = copyInfoToMxStruct(osqpSolver->info); // Info structure return; } @@ -728,8 +586,18 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } - if (!strcmp("MKL_PARDISO_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(MKL_PARDISO_SOLVER); + if (!strcmp("OSQP_UNKNOWN_SOLVER", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_UNKNOWN_SOLVER); + return; + } + + if (!strcmp("OSQP_DIRECT_SOLVER", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_DIRECT_SOLVER); + return; + } + + if (!strcmp("OSQP_INDIRECT_SOLVER", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_INDIRECT_SOLVER); return; } @@ -739,68 +607,90 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } - // Got here, so command not recognized mexErrMsgTxt("Command not recognized."); } +/** + * This function sets all the properties of OSQPSolver to NULL. + * WARNING: Any dynamically allocated pointers must be freed before calling this function. +*/ +void initializeOSQPSolver(OSQPSolver* osqpSolver) { + osqpSolver->info = NULL; + osqpSolver->settings = NULL; + osqpSolver->solution = NULL; + osqpSolver->work = NULL; +} -c_float* copyToCfloatVector(double* vecData, c_int numel){ - // This memory needs to be freed! - c_float* out = (c_float*)c_malloc(numel * sizeof(c_float)); +//Dynamically creates a OSQPFloat vector copy of the input. +//Returns an empty pointer if vecData is NULL +OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel){ + if (!vecData) return NULL; + + //This needs to be freed! + OSQPFloat* out = (OSQPFloat*)c_malloc(numel * sizeof(OSQPFloat)); //copy data - for(c_int i=0; i < numel; i++){ - out[i] = (c_float)vecData[i]; + for(OSQPInt i=0; i < numel; i++){ + out[i] = (OSQPFloat)vecData[i]; } return out; - } -c_int* copyToCintVector(mwIndex* vecData, c_int numel){ +//Dynamically creates a OSQPInt vector copy of the input. +OSQPInt* copyToCintVector(mwIndex* vecData, OSQPInt numel){ // This memory needs to be freed! - c_int* out = (c_int*)c_malloc(numel * sizeof(c_int)); + OSQPInt* out = (OSQPInt*)c_malloc(numel * sizeof(OSQPInt)); //copy data - for(c_int i=0; i < numel; i++){ - out[i] = (c_int)vecData[i]; + for(OSQPInt i=0; i < numel; i++){ + out[i] = (OSQPInt)vecData[i]; } return out; } -c_int* copyDoubleToCintVector(double* vecData, c_int numel){ +//Dynamically copies a double vector to OSQPInt. +OSQPInt* copyDoubleToCintVector(double* vecData, OSQPInt numel){ // This memory needs to be freed! - c_int* out = (c_int*)c_malloc(numel * sizeof(c_int)); + OSQPInt* out = (OSQPInt*)c_malloc(numel * sizeof(OSQPInt)); //copy data - for(c_int i=0; i < numel; i++){ - out[i] = (c_int)vecData[i]; + for(OSQPInt i=0; i < numel; i++){ + out[i] = (OSQPInt)vecData[i]; } return out; } -void castCintToDoubleArr(c_int *arr, double* arr_out, c_int len) { - for (c_int i = 0; i < len; i++) { +void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len) { + for (OSQPInt i = 0; i < len; i++) { arr_out[i] = (double)arr[i]; } } -void castToDoubleArr(c_float *arr, double* arr_out, c_int len) { - for (c_int i = 0; i < len; i++) { +//This function frees the memory allocated in an OSQPCscMatrix M +void freeCscMatrix(OSQPCscMatrix* M) { + if (!M) return; + if (M->p) c_free(M->p); + if (M->i) c_free(M->i); + if (M->x) c_free(M->x); + c_free(M); +} + +void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len) { + for (OSQPInt i = 0; i < len; i++) { arr_out[i] = (double)arr[i]; } } -void setToNaN(double* arr_out, c_int len){ - c_int i; +void setToNaN(double* arr_out, OSQPInt len){ + OSQPInt i; for (i = 0; i < len; i++) { arr_out[i] = mxGetNaN(); } } - mxArray* copyInfoToMxStruct(OSQPInfo* info){ //create mxArray with the right number of fields @@ -814,8 +704,8 @@ mxArray* copyInfoToMxStruct(OSQPInfo* info){ mxSetField(mxPtr, 0, "status_val", mxCreateDoubleScalar(info->status_val)); mxSetField(mxPtr, 0, "status_polish", mxCreateDoubleScalar(info->status_polish)); mxSetField(mxPtr, 0, "obj_val", mxCreateDoubleScalar(info->obj_val)); - mxSetField(mxPtr, 0, "pri_res", mxCreateDoubleScalar(info->pri_res)); - mxSetField(mxPtr, 0, "dua_res", mxCreateDoubleScalar(info->dua_res)); + mxSetField(mxPtr, 0, "prim_res", mxCreateDoubleScalar(info->prim_res)); + mxSetField(mxPtr, 0, "dual_res", mxCreateDoubleScalar(info->dual_res)); #ifdef PROFILING //if not profiling, these fields will be empty @@ -827,14 +717,13 @@ mxArray* copyInfoToMxStruct(OSQPInfo* info){ #endif mxSetField(mxPtr, 0, "rho_updates", mxCreateDoubleScalar(info->rho_updates)); - mxSetField(mxPtr, 0, "rho_estimate", mxCreateDoubleScalar(info->rho_estimate)); + mxSetField(mxPtr, 0, "rho_estimate", mxCreateDoubleScalar(info->rho_estimate)); return mxPtr; } - mxArray* copySettingsToMxStruct(OSQPSettings* settings){ int nfields = sizeof(OSQP_SETTINGS_FIELDS) / sizeof(OSQP_SETTINGS_FIELDS[0]); @@ -842,357 +731,368 @@ mxArray* copySettingsToMxStruct(OSQPSettings* settings){ //map the OSQP_SETTINGS fields one at a time into mxArrays //matlab handles everything as a double - mxSetField(mxPtr, 0, "rho", mxCreateDoubleScalar(settings->rho)); - mxSetField(mxPtr, 0, "sigma", mxCreateDoubleScalar(settings->sigma)); - mxSetField(mxPtr, 0, "scaling", mxCreateDoubleScalar(settings->scaling)); - mxSetField(mxPtr, 0, "adaptive_rho", mxCreateDoubleScalar(settings->adaptive_rho)); - mxSetField(mxPtr, 0, "adaptive_rho_interval", mxCreateDoubleScalar(settings->adaptive_rho_interval)); - mxSetField(mxPtr, 0, "adaptive_rho_tolerance", mxCreateDoubleScalar(settings->adaptive_rho_tolerance)); - mxSetField(mxPtr, 0, "adaptive_rho_fraction", mxCreateDoubleScalar(settings->adaptive_rho_fraction)); - mxSetField(mxPtr, 0, "max_iter", mxCreateDoubleScalar(settings->max_iter)); - mxSetField(mxPtr, 0, "eps_abs", mxCreateDoubleScalar(settings->eps_abs)); - mxSetField(mxPtr, 0, "eps_rel", mxCreateDoubleScalar(settings->eps_rel)); - mxSetField(mxPtr, 0, "eps_prim_inf", mxCreateDoubleScalar(settings->eps_prim_inf)); - mxSetField(mxPtr, 0, "eps_dual_inf", mxCreateDoubleScalar(settings->eps_dual_inf)); - mxSetField(mxPtr, 0, "alpha", mxCreateDoubleScalar(settings->alpha)); - mxSetField(mxPtr, 0, "linsys_solver", mxCreateDoubleScalar(settings->linsys_solver)); - mxSetField(mxPtr, 0, "delta", mxCreateDoubleScalar(settings->delta)); - mxSetField(mxPtr, 0, "polish", mxCreateDoubleScalar(settings->polish)); - mxSetField(mxPtr, 0, "polish_refine_iter", mxCreateDoubleScalar(settings->polish_refine_iter)); - mxSetField(mxPtr, 0, "verbose", mxCreateDoubleScalar(settings->verbose)); - mxSetField(mxPtr, 0, "scaled_termination", mxCreateDoubleScalar(settings->scaled_termination)); - mxSetField(mxPtr, 0, "check_termination", mxCreateDoubleScalar(settings->check_termination)); - mxSetField(mxPtr, 0, "warm_start", mxCreateDoubleScalar(settings->warm_start)); - mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); + mxSetField(mxPtr, 0, "rho", mxCreateDoubleScalar(settings->rho)); + mxSetField(mxPtr, 0, "sigma", mxCreateDoubleScalar(settings->sigma)); + mxSetField(mxPtr, 0, "scaling", mxCreateDoubleScalar(settings->scaling)); + mxSetField(mxPtr, 0, "adaptive_rho", mxCreateDoubleScalar(settings->adaptive_rho)); + mxSetField(mxPtr, 0, "adaptive_rho_interval", mxCreateDoubleScalar(settings->adaptive_rho_interval)); + mxSetField(mxPtr, 0, "adaptive_rho_tolerance", mxCreateDoubleScalar(settings->adaptive_rho_tolerance)); + mxSetField(mxPtr, 0, "adaptive_rho_fraction", mxCreateDoubleScalar(settings->adaptive_rho_fraction)); + mxSetField(mxPtr, 0, "max_iter", mxCreateDoubleScalar(settings->max_iter)); + mxSetField(mxPtr, 0, "eps_abs", mxCreateDoubleScalar(settings->eps_abs)); + mxSetField(mxPtr, 0, "eps_rel", mxCreateDoubleScalar(settings->eps_rel)); + mxSetField(mxPtr, 0, "eps_prim_inf", mxCreateDoubleScalar(settings->eps_prim_inf)); + mxSetField(mxPtr, 0, "eps_dual_inf", mxCreateDoubleScalar(settings->eps_dual_inf)); + mxSetField(mxPtr, 0, "alpha", mxCreateDoubleScalar(settings->alpha)); + mxSetField(mxPtr, 0, "linsys_solver", mxCreateDoubleScalar(settings->linsys_solver)); + mxSetField(mxPtr, 0, "delta", mxCreateDoubleScalar(settings->delta)); + mxSetField(mxPtr, 0, "polish_refine_iter", mxCreateDoubleScalar(settings->polish_refine_iter)); + mxSetField(mxPtr, 0, "verbose", mxCreateDoubleScalar(settings->verbose)); + mxSetField(mxPtr, 0, "scaled_termination", mxCreateDoubleScalar(settings->scaled_termination)); + mxSetField(mxPtr, 0, "check_termination", mxCreateDoubleScalar(settings->check_termination)); + mxSetField(mxPtr, 0, "warm_starting", mxCreateDoubleScalar(settings->warm_starting)); + mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); return mxPtr; } // ====================================================================== -mxArray* copyCscMatrixToMxStruct(csc* M){ - int nnzM; - int nfields = sizeof(CSC_FIELDS) / sizeof(CSC_FIELDS[0]); - mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,CSC_FIELDS); - // Get number of nonzeros - nnzM = M->p[M->n]; - - // Create vectors - mxArray* p = mxCreateDoubleMatrix((M->n)+1,1,mxREAL); - mxArray* i = mxCreateDoubleMatrix(nnzM,1,mxREAL); - mxArray* x = mxCreateDoubleMatrix(nnzM,1,mxREAL); +void copyMxStructToSettings(const mxArray* mxPtr, OSQPSettings* settings){ - // Populate vectors - castCintToDoubleArr(M->p, mxGetPr(p), (M->n)+1); - castCintToDoubleArr(M->i, mxGetPr(i), nnzM); - castToDoubleArr(M->x, mxGetPr(x), nnzM); + //this function assumes that only a complete and validated structure + //will be passed. matlab mex-side function is responsible for checking + //structure validity - //map the CSC fields one at a time into mxArrays + //map the OSQP_SETTINGS fields one at a time into mxArrays //matlab handles everything as a double - mxSetField(mxPtr, 0, "nzmax", mxCreateDoubleScalar(M->nzmax)); - mxSetField(mxPtr, 0, "m", mxCreateDoubleScalar(M->m)); - mxSetField(mxPtr, 0, "n", mxCreateDoubleScalar(M->n)); - mxSetField(mxPtr, 0, "p", p); - mxSetField(mxPtr, 0, "i", i); - mxSetField(mxPtr, 0, "x", x); - mxSetField(mxPtr, 0, "nz", mxCreateDoubleScalar(M->nz)); + settings->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); + settings->sigma = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "sigma")); + settings->scaling = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaling")); + settings->adaptive_rho = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho")); + settings->adaptive_rho_interval = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_interval")); + settings->adaptive_rho_tolerance = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_tolerance")); + settings->adaptive_rho_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_fraction")); + settings->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); + settings->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); + settings->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); + settings->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); + settings->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); + settings->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); + settings->linsys_solver = (enum osqp_linsys_solver_type) (OSQPInt) mxGetScalar(mxGetField(mxPtr, 0, "linsys_solver")); + settings->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); + settings->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); + settings->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); + settings->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); + settings->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); + settings->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); + settings->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); - return mxPtr; } -mxArray* copyDataToMxStruct(OSQPWorkspace* work){ +void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ - int nfields = sizeof(OSQP_DATA_FIELDS) / sizeof(OSQP_DATA_FIELDS[0]); - mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_DATA_FIELDS); + OSQPInt exitflag; - // Create vectors - mxArray* q = mxCreateDoubleMatrix(work->data->n,1,mxREAL); - mxArray* l = mxCreateDoubleMatrix(work->data->m,1,mxREAL); - mxArray* u = mxCreateDoubleMatrix(work->data->m,1,mxREAL); + //This does basically the same job as copyMxStructToSettings which was used + //during setup, but uses the provided update functions in osqp.h to update + //settings in the osqp workspace. Protects against bad parameter writes + //or future modifications to updated settings handling - // Populate vectors - castToDoubleArr(work->data->q, mxGetPr(q), work->data->n); - castToDoubleArr(work->data->l, mxGetPr(l), work->data->m); - castToDoubleArr(work->data->u, mxGetPr(u), work->data->m); + osqp_update_max_iter(osqpSolver, + (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter"))); + osqp_update_eps_abs(osqpSolver, + (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs"))); + osqp_update_eps_rel(osqpSolver, + (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel"))); + osqp_update_eps_prim_inf(osqpSolver, + (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf"))); + osqp_update_eps_dual_inf(osqpSolver, + (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf"))); + osqp_update_alpha(osqpSolver, + (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha"))); + osqp_update_delta(osqpSolver, + (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta"))); + osqp_update_polish_refine_iter(osqpSolver, + (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter"))); + osqp_update_verbose(osqpSolver, + (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose"))); + osqp_update_scaled_termination(osqpSolver, + (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination"))); + osqp_update_check_termination(osqpSolver, + (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination"))); + osqp_update_warm_start(osqpSolver, + (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_start"))); +#ifdef PROFILING + osqp_update_time_limit(osqpSolver, + (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit"))); +#endif /* ifdef PROFILING */ - // Create matrices - mxArray* P = copyCscMatrixToMxStruct(work->data->P); - mxArray* A = copyCscMatrixToMxStruct(work->data->A); + // Check for settings that need special update + // Update them only if they are different than already set values + OSQPFloat rho_new = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); + // Check if it has changed + if (c_absval(rho_new - osqpSolver->settings->rho) > NEW_SETTINGS_TOL){ + exitflag = osqp_update_rho(osqpSolver, rho_new); + if (exitflag){ + mexErrMsgTxt("rho update error!"); + } + } - //map the OSQP_DATA fields one at a time into mxArrays - //matlab handles everything as a double - mxSetField(mxPtr, 0, "n", mxCreateDoubleScalar(work->data->n)); - mxSetField(mxPtr, 0, "m", mxCreateDoubleScalar(work->data->m)); - mxSetField(mxPtr, 0, "P", P); - mxSetField(mxPtr, 0, "A", A); - mxSetField(mxPtr, 0, "q", q); - mxSetField(mxPtr, 0, "l", l); - mxSetField(mxPtr, 0, "u", u); - return mxPtr; } -mxArray* copyLinsysSolverToMxStruct(OSQPWorkspace * work){ - - int nfields; - mxArray* mxPtr; - OSQPData * data; - qdldl_solver * linsys_solver; - - nfields = sizeof(LINSYS_SOLVER_FIELDS) / sizeof(LINSYS_SOLVER_FIELDS[0]); - mxPtr = mxCreateStructMatrix(1,1,nfields,LINSYS_SOLVER_FIELDS); - - data = work->data; - linsys_solver = (qdldl_solver *) work->linsys_solver; - - // Dimensions - int n = linsys_solver->L->n; - int Pdiag_n = linsys_solver->Pdiag_n; - int nnzP = data->P->p[data->P->n]; - int nnzA = data->A->p[data->A->n]; - - // Create vectors - mxArray* Dinv = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* P = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* bp = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* sol = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* rho_inv_vec = mxCreateDoubleMatrix(data->m,1,mxREAL); - mxArray* Pdiag_idx = mxCreateDoubleMatrix(Pdiag_n,1,mxREAL); - mxArray* PtoKKT = mxCreateDoubleMatrix(nnzP,1,mxREAL); - mxArray* AtoKKT = mxCreateDoubleMatrix(nnzA,1,mxREAL); - mxArray* rhotoKKT = mxCreateDoubleMatrix(data->m,1,mxREAL); - mxArray* D = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* etree = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* Lnz = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* iwork = mxCreateDoubleMatrix(3*n,1,mxREAL); - mxArray* bwork = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* fwork = mxCreateDoubleMatrix(n,1,mxREAL); - - // Populate vectors - castToDoubleArr(linsys_solver->Dinv, mxGetPr(Dinv), n); - castCintToDoubleArr(linsys_solver->P, mxGetPr(P), n); - castToDoubleArr(linsys_solver->bp, mxGetPr(bp), n); - castToDoubleArr(linsys_solver->sol, mxGetPr(sol), n); - castToDoubleArr(linsys_solver->rho_inv_vec, mxGetPr(rho_inv_vec), data->m); - castCintToDoubleArr(linsys_solver->Pdiag_idx, mxGetPr(Pdiag_idx), Pdiag_n); - castCintToDoubleArr(linsys_solver->PtoKKT, mxGetPr(PtoKKT), nnzP); - castCintToDoubleArr(linsys_solver->AtoKKT, mxGetPr(AtoKKT), nnzA); - castCintToDoubleArr(linsys_solver->rhotoKKT, mxGetPr(rhotoKKT), data->m); - castToDoubleArr(linsys_solver->D, mxGetPr(D), n); - castCintToDoubleArr(linsys_solver->etree, mxGetPr(etree), n); - castCintToDoubleArr(linsys_solver->Lnz, mxGetPr(Lnz), n); - castCintToDoubleArr(linsys_solver->iwork, mxGetPr(iwork), 3*n); - castCintToDoubleArr((c_int *)linsys_solver->bwork, mxGetPr(bwork), n); - castToDoubleArr(linsys_solver->fwork, mxGetPr(fwork), n); - - // Create matrices - mxArray* L = copyCscMatrixToMxStruct(linsys_solver->L); - mxArray* KKT = copyCscMatrixToMxStruct(linsys_solver->KKT); - - //map the PRIV fields one at a time into mxArrays - mxSetField(mxPtr, 0, "L", L); - mxSetField(mxPtr, 0, "Dinv", Dinv); - mxSetField(mxPtr, 0, "P", P); - mxSetField(mxPtr, 0, "bp", bp); - mxSetField(mxPtr, 0, "sol", sol); - mxSetField(mxPtr, 0, "rho_inv_vec", rho_inv_vec); - mxSetField(mxPtr, 0, "sigma", mxCreateDoubleScalar(linsys_solver->sigma)); - mxSetField(mxPtr, 0, "polish", mxCreateDoubleScalar(linsys_solver->polish)); - mxSetField(mxPtr, 0, "n", mxCreateDoubleScalar(linsys_solver->n)); - mxSetField(mxPtr, 0, "m", mxCreateDoubleScalar(linsys_solver->m)); - mxSetField(mxPtr, 0, "Pdiag_idx", Pdiag_idx); - mxSetField(mxPtr, 0, "Pdiag_n", mxCreateDoubleScalar(Pdiag_n)); - mxSetField(mxPtr, 0, "KKT", KKT); - mxSetField(mxPtr, 0, "PtoKKT", PtoKKT); - mxSetField(mxPtr, 0, "AtoKKT", AtoKKT); - mxSetField(mxPtr, 0, "rhotoKKT", rhotoKKT); - mxSetField(mxPtr, 0, "D", D); - mxSetField(mxPtr, 0, "etree", etree); - mxSetField(mxPtr, 0, "Lnz", Lnz); - mxSetField(mxPtr, 0, "iwork", iwork); - mxSetField(mxPtr, 0, "bwork", bwork); - mxSetField(mxPtr, 0, "fwork", fwork); +/**************************** +* Update problem settings * +****************************/ +OSQPInt osqp_update_max_iter(OSQPSolver* osqpSolver, OSQPInt max_iter_new) { - return mxPtr; + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); + + // Check that max_iter is positive + if (max_iter_new <= 0) { +#ifdef PRINTING + c_eprint("max_iter must be positive"); +#endif /* ifdef PRINTING */ + return 1; + } + + // Update max_iter + osqpSolver->settings->max_iter = max_iter_new; + + return 0; } -mxArray* copyScalingToMxStruct(OSQPWorkspace *work){ +OSQPInt osqp_update_eps_abs(OSQPSolver* osqpSolver, OSQPFloat eps_abs_new) { - int n, m, nfields; - mxArray* mxPtr; + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); + // Check that eps_abs is positive + if (eps_abs_new < 0.) { +#ifdef PRINTING + c_eprint("eps_abs must be nonnegative"); +#endif /* ifdef PRINTING */ + return 1; + } - if (work->settings->scaling){ // Scaling performed - n = work->data->n; - m = work->data->m; + // Update eps_abs + osqpSolver->settings->eps_abs = eps_abs_new; - nfields = sizeof(OSQP_SCALING_FIELDS) / sizeof(OSQP_SCALING_FIELDS[0]); - mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_SCALING_FIELDS); + return 0; +} - // Create vectors - mxArray* D = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* E = mxCreateDoubleMatrix(m,1,mxREAL); - mxArray* Dinv = mxCreateDoubleMatrix(n,1,mxREAL); - mxArray* Einv = mxCreateDoubleMatrix(m,1,mxREAL); +OSQPInt osqp_update_eps_rel(OSQPSolver* osqpSolver, OSQPFloat eps_rel_new) { - // Populate vectors - castToDoubleArr(work->scaling->D, mxGetPr(D), n); - castToDoubleArr(work->scaling->E, mxGetPr(E), m); - castToDoubleArr(work->scaling->Dinv, mxGetPr(Dinv), n); - castToDoubleArr(work->scaling->Einv, mxGetPr(Einv), m); + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); + + // Check that eps_rel is positive + if (eps_rel_new < 0.) { +#ifdef PRINTING + c_eprint("eps_rel must be nonnegative"); +#endif /* ifdef PRINTING */ + return 1; + } + + // Update eps_rel + osqpSolver->settings->eps_rel = eps_rel_new; + + return 0; +} - //map the SCALING fields one at a time - mxSetField(mxPtr, 0, "c", mxCreateDoubleScalar(work->scaling->c)); - mxSetField(mxPtr, 0, "D", D); - mxSetField(mxPtr, 0, "E", E); - mxSetField(mxPtr, 0, "cinv", mxCreateDoubleScalar(work->scaling->cinv)); - mxSetField(mxPtr, 0, "Dinv", Dinv); - mxSetField(mxPtr, 0, "Einv", Einv); +OSQPInt osqp_update_eps_prim_inf(OSQPSolver* osqpSolver, OSQPFloat eps_prim_inf_new) { - } else { - mxPtr = mxCreateDoubleMatrix(0, 0, mxREAL); + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); + + // Check that eps_prim_inf is positive + if (eps_prim_inf_new < 0.) { +#ifdef PRINTING + c_eprint("eps_prim_inf must be nonnegative"); +#endif /* ifdef PRINTING */ + return 1; + } + + // Update eps_prim_inf + osqpSolver->settings->eps_prim_inf = eps_prim_inf_new; + + return 0; +} + +OSQPInt osqp_update_eps_dual_inf(OSQPSolver* osqpSolver, OSQPFloat eps_dual_inf_new) { + + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); + + // Check that eps_dual_inf is positive + if (eps_dual_inf_new < 0.) { +#ifdef PRINTING + c_eprint("eps_dual_inf must be nonnegative"); +#endif /* ifdef PRINTING */ + return 1; } - return mxPtr; + // Update eps_dual_inf + osqpSolver->settings->eps_dual_inf = eps_dual_inf_new; + + + return 0; } -mxArray* copyRhoVectorsToMxStruct(OSQPWorkspace *work){ +OSQPInt osqp_update_alpha(OSQPSolver* osqpSolver, OSQPFloat alpha_new) { - int m, nfields; - mxArray* mxPtr; + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - m = work->data->m; + // Check that alpha is between 0 and 2 + if ((alpha_new <= 0.) || (alpha_new >= 2.)) { +#ifdef PRINTING + c_eprint("alpha must be between 0 and 2"); +#endif /* ifdef PRINTING */ + return 1; + } + + // Update alpha + osqpSolver->settings->alpha = alpha_new; + + return 0; +} - nfields = sizeof(OSQP_RHO_VECTORS_FIELDS) / sizeof(OSQP_RHO_VECTORS_FIELDS[0]); - mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_RHO_VECTORS_FIELDS); +OSQPInt osqp_update_warm_start(OSQPSolver* osqpSolver, OSQPInt warm_start_new) { - // Create vectors - mxArray* rho_vec = mxCreateDoubleMatrix(m,1,mxREAL); - mxArray* rho_inv_vec = mxCreateDoubleMatrix(m,1,mxREAL); - mxArray* constr_type = mxCreateDoubleMatrix(m,1,mxREAL); + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - // Populate vectors - castToDoubleArr(work->rho_vec, mxGetPr(rho_vec), m); - castToDoubleArr(work->rho_inv_vec, mxGetPr(rho_inv_vec), m); - castCintToDoubleArr(work->constr_type, mxGetPr(constr_type), m); + // Check that warm_start is either 0 or 1 + if ((warm_start_new != 0) && (warm_start_new != 1)) { +#ifdef PRINTING + c_eprint("warm_start should be either 0 or 1"); +#endif /* ifdef PRINTING */ + return 1; + } - //map the RHO_VECTORS fields one at a time into mxArrays - mxSetField(mxPtr, 0, "rho_vec", rho_vec); - mxSetField(mxPtr, 0, "rho_inv_vec", rho_inv_vec); - mxSetField(mxPtr, 0, "constr_type", constr_type); + // Update warm_start + osqpSolver->settings->warm_starting = warm_start_new; - return mxPtr; + return 0; } +OSQPInt osqp_update_delta(OSQPSolver* osqpSolver, OSQPFloat delta_new) { -mxArray* copyWorkToMxStruct(OSQPWorkspace* work){ + // Check if workspace has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - int nfields = sizeof(OSQP_WORKSPACE_FIELDS) / sizeof(OSQP_WORKSPACE_FIELDS[0]); - mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_WORKSPACE_FIELDS); + // Check that delta is positive + if (delta_new <= 0.) { +# ifdef PRINTING + c_eprint("delta must be positive"); +# endif /* ifdef PRINTING */ + return 1; + } - // Create workspace substructures - mxArray* rho_vectors = copyRhoVectorsToMxStruct(work); - mxArray* data = copyDataToMxStruct(work); - mxArray* linsys_solver = copyLinsysSolverToMxStruct(work); - mxArray* scaling = copyScalingToMxStruct(work); - mxArray* settings = copySettingsToMxStruct(work->settings); + // Update delta + osqpSolver->settings->delta = delta_new; - //map the WORKSPACE fields one at a time into mxArrays - mxSetField(mxPtr, 0, "rho_vectors", rho_vectors); - mxSetField(mxPtr, 0, "data", data); - mxSetField(mxPtr, 0, "linsys_solver", linsys_solver); - mxSetField(mxPtr, 0, "scaling", scaling); - mxSetField(mxPtr, 0, "settings", settings); + return 0; +} - return mxPtr; +OSQPInt osqp_update_polish_refine_iter(OSQPSolver* osqpSolver, OSQPInt polish_refine_iter_new) { + + // Check if workspace has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); + + // Check that polish_refine_iter is nonnegative + if (polish_refine_iter_new < 0) { +# ifdef PRINTING + c_eprint("polish_refine_iter must be nonnegative"); +# endif /* ifdef PRINTING */ + return 1; + } + + // Update polish_refine_iter + osqpSolver->settings->polish_refine_iter = polish_refine_iter_new; + + return 0; } -// ====================================================================== +OSQPInt osqp_update_verbose(OSQPSolver* osqpSolver, OSQPInt verbose_new){ + // Check if workspace has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); -void copyMxStructToSettings(const mxArray* mxPtr, OSQPSettings* settings){ + // Check that verbose is either 0 or 1 + if ((verbose_new != 0) && (verbose_new != 1)) { +# ifdef PRINTING + c_eprint("verbose should be either 0 or 1"); +# endif /* ifdef PRINTING */ + return 1; + } - //this function assumes that only a complete and validated structure - //will be passed. matlab mex-side function is responsible for checking - //structure validity + // Update verbose + osqpSolver->settings->verbose = verbose_new; - //map the OSQP_SETTINGS fields one at a time into mxArrays - //matlab handles everything as a double - settings->rho = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "rho")); - settings->sigma = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "sigma")); - settings->scaling = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "scaling")); - settings->adaptive_rho = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho")); - settings->adaptive_rho_interval = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_interval")); - settings->adaptive_rho_tolerance = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_tolerance")); - settings->adaptive_rho_fraction = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_fraction")); - settings->max_iter = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); - settings->eps_abs = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); - settings->eps_rel = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); - settings->eps_prim_inf = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - settings->eps_dual_inf = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - settings->alpha = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); - settings->linsys_solver = (enum linsys_solver_type) (c_int) mxGetScalar(mxGetField(mxPtr, 0, "linsys_solver")); - settings->delta = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "delta")); - settings->polish = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "polish")); - settings->polish_refine_iter = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); - settings->verbose = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); - settings->scaled_termination = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); - settings->check_termination = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); - settings->warm_start = (c_int)mxGetScalar(mxGetField(mxPtr, 0, "warm_start")); - settings->time_limit = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); + return 0; +} + +OSQPInt osqp_update_scaled_termination(OSQPSolver* osqpSolver, OSQPInt scaled_termination_new) { + + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); + + // Check that scaled_termination is either 0 or 1 + if ((scaled_termination_new != 0) && (scaled_termination_new != 1)) { +#ifdef PRINTING + c_eprint("scaled_termination should be either 0 or 1"); +#endif /* ifdef PRINTING */ + return 1; + } + + // Update scaled_termination + osqpSolver->settings->scaled_termination = scaled_termination_new; + return 0; } -void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OsqpData* osqpData){ +OSQPInt osqp_update_check_termination(OSQPSolver* osqpSolver, OSQPInt check_termination_new) { - c_int exitflag; + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - //This does basically the same job as copyMxStructToSettings which was used - //during setup, but uses the provided update functions in osqp.h to update - //settings in the osqp workspace. Protects against bad parameter writes - //or future modifications to updated settings handling + // Check that check_termination is nonnegative + if (check_termination_new < 0) { +#ifdef PRINTING + c_eprint("check_termination should be nonnegative"); +#endif /* ifdef PRINTING */ + return 1; + } - osqp_update_max_iter(osqpData->work, - (c_int)mxGetScalar(mxGetField(mxPtr, 0, "max_iter"))); - osqp_update_eps_abs(osqpData->work, - (c_float)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs"))); - osqp_update_eps_rel(osqpData->work, - (c_float)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel"))); - osqp_update_eps_prim_inf(osqpData->work, - (c_float)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf"))); - osqp_update_eps_dual_inf(osqpData->work, - (c_float)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf"))); - osqp_update_alpha(osqpData->work, - (c_float)mxGetScalar(mxGetField(mxPtr, 0, "alpha"))); - osqp_update_delta(osqpData->work, - (c_float)mxGetScalar(mxGetField(mxPtr, 0, "delta"))); - osqp_update_polish(osqpData->work, - (c_int)mxGetScalar(mxGetField(mxPtr, 0, "polish"))); - osqp_update_polish_refine_iter(osqpData->work, - (c_int)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter"))); - osqp_update_verbose(osqpData->work, - (c_int)mxGetScalar(mxGetField(mxPtr, 0, "verbose"))); - osqp_update_scaled_termination(osqpData->work, - (c_int)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination"))); - osqp_update_check_termination(osqpData->work, - (c_int)mxGetScalar(mxGetField(mxPtr, 0, "check_termination"))); - osqp_update_warm_start(osqpData->work, - (c_int)mxGetScalar(mxGetField(mxPtr, 0, "warm_start"))); - osqp_update_time_limit(osqpData->work, - (c_float)mxGetScalar(mxGetField(mxPtr, 0, "time_limit"))); + // Update check_termination + osqpSolver->settings->check_termination = check_termination_new; + return 0; +} - // Check for settings that need special update - // Update them only if they are different than already set values - c_float rho_new = (c_float)mxGetScalar(mxGetField(mxPtr, 0, "rho")); - // Check if it has changed - if (c_absval(rho_new - osqpData->work->settings->rho) > NEW_SETTINGS_TOL){ - exitflag = osqp_update_rho(osqpData->work, rho_new); - if (exitflag){ - mexErrMsgTxt("rho update error!"); - } +#ifdef PROFILING + +OSQPInt osqp_update_time_limit(OSQPSolver* osqpSolver, OSQPFloat time_limit_new) { + + // Check if osqpSolver has been initialized + if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); + + // Check that time_limit is nonnegative + if (time_limit_new < 0.) { +# ifdef PRINTING + c_print("time_limit must be nonnegative\n"); +# endif /* ifdef PRINTING */ + return 1; } + // Update time_limit + osqpSolver->settings->time_limit = time_limit_new; + return 0; } +#endif /* ifdef PROFILING */ diff --git a/osqp_mex.hpp b/osqp_mex.hpp index ceb3b07..b3f9d33 100755 --- a/osqp_mex.hpp +++ b/osqp_mex.hpp @@ -1,6 +1,7 @@ #ifndef __OSQP_MEX_HPP__ #define __OSQP_MEX_HPP__ #include "mex.h" +#include "error.h" #include #include #include diff --git a/osqp_sources b/osqp_sources index f9fc23d..1ac3fab 160000 --- a/osqp_sources +++ b/osqp_sources @@ -1 +1 @@ -Subproject commit f9fc23d3436e4b17dd2cb95f70cfa1f37d122c24 +Subproject commit 1ac3fab74ddeebb0a24b8b7f96e1e539423d4a6a From 33e2f9828e6a0096e375f27fb28a8667be2b2b41 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Fri, 8 Sep 2023 13:58:11 -0400 Subject: [PATCH 02/36] Debugging --- osqp.m | 26 ++++--- osqp_mex.cpp | 195 ++++++++++++++++++++++++++++----------------------- 2 files changed, 122 insertions(+), 99 deletions(-) diff --git a/osqp.m b/osqp.m index acc653a..c87cbcc 100755 --- a/osqp.m +++ b/osqp.m @@ -647,10 +647,12 @@ function codegen(this, target_dir, varargin) function [linsys_solver_string] = linsys_solver_to_string(linsys_solver) % Convert linear systme solver integer to stringh switch linsys_solver - case osqp.constant('QDLDL_SOLVER') - linsys_solver_string = 'qdldl'; - case osqp.constant('MKL_PARDISO_SOLVER') - linsys_solver_string = 'mkl pardiso'; + case osqp.constant('OSQP_UNKNOWN_SOLVER') + linsys_solver_string = 'unknown solver'; + case osqp.constant('OSQP_DIRECT_SOLVER') + linsys_solver_string = 'direct solver'; + case osqp.constant('OSQP_INDIRECT_SOLVER') + linsys_solver_string = 'indirect solver'; otherwise error('Unrecognized linear system solver.'); end @@ -661,16 +663,18 @@ function codegen(this, target_dir, varargin) function [linsys_solver] = string_to_linsys_solver(linsys_solver_string) linsys_solver_string = lower(linsys_solver_string); switch linsys_solver_string - case 'qdldl' - linsys_solver = osqp.constant('QDLDL_SOLVER'); - case 'mkl pardiso' - linsys_solver = osqp.constant('MKL_PARDISO_SOLVER'); + case 'unknown solver' + linsys_solver = osqp.constant('OSQP_UNKNOWN_SOLVER'); + case 'direct solver' + linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); + case 'indirect solver' + linsys_solver = osqp.constant('OSQP_INDIRECT_SOLVER'); % Default solver: QDLDL case '' - linsys_solver = osqp.constant('QDLDL_SOLVER'); + linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); otherwise - warning('Linear system solver not recognized. Using default solver QDLDL.') - linsys_solver = osqp.constant('QDLDL_SOLVER'); + warning('Linear system solver not recognized. Using default solver OSQP_DIRECT_SOLVER.') + linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); end end diff --git a/osqp_mex.cpp b/osqp_mex.cpp index 103dcfb..187879a 100755 --- a/osqp_mex.cpp +++ b/osqp_mex.cpp @@ -4,6 +4,10 @@ #include "osqp.h" //#include "ctrlc.h" // Needed for interrupt #include "qdldl_interface.h" // To extract workspace for codegen +//#include //DELETE HERE +//#include //DELETE HERE +//using std::cout; //DELETE HERE +//using std::endl; //DELETE HERE //c_int is replaced with OSQPInt //c_float is replaced with OSQPFloat @@ -22,11 +26,14 @@ const char* OSQP_INFO_FIELDS[] = {"status", //char* "iter", //OSQPInt "rho_updates", //OSQPInt "rho_estimate", //OSQPFloat + #ifdef PROFILING "setup_time", //OSQPFloat "solve_time", //OSQPFloat "update_time", //OSQPFloat "polish_time", //OSQPFloat - "run_time"}; //OSQPFloat + "run_time", //OSQPFloat + #endif /* ifdef PROFILING */ + }; const char* OSQP_SETTINGS_FIELDS[] = {"device", //OSQPInt "linsys_solver", //enum osqp_linsys_solver_type @@ -55,7 +62,8 @@ const char* OSQP_SETTINGS_FIELDS[] = {"device", //OSQPInt "check_termination", //OSQPInt "time_limit", //OSQPFloat "delta", //OSQPFloat - "polish_refine_iter"}; //OSQPInt + "polish_refine_iter", //OSQPInt + }; #define NEW_SETTINGS_TOL (1e-10) @@ -63,14 +71,12 @@ const char* OSQP_SETTINGS_FIELDS[] = {"device", //OSQPInt class OsqpData { public: - OsqpData(){ - work = NULL; - } - OSQPWorkspace * work; // Workspace + OsqpData() : solver(NULL){} + OSQPSolver * solver; }; // internal utility functions -void initializeOSQPSolver(OSQPSolver* osqpSolver); +OSQPSolver* initializeOSQPSolver(); void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len); void setToNaN(double* arr_out, OSQPInt len); void copyMxStructToSettings(const mxArray*, OSQPSettings*); @@ -101,8 +107,8 @@ OSQPInt osqp_update_time_limit(OSQPSolver* osqpSolver, OSQPFloat time_lim void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { - OSQPSolver* osqpSolver; - initializeOSQPSolver(osqpSolver); + OsqpData* osqpData; + //OSQPSolver* osqpSolver = NULL; // Exitflag OSQPInt exitflag = 0; @@ -114,27 +120,29 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) char cmd[64]; if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) mexErrMsgTxt("First input should be a command string less than 64 characters long."); - // new object + //std::cout << "cmd = " << cmd << std::endl; //DELETE HERE if (!strcmp("new", cmd)) { // Check parameters if (nlhs != 1){ mexErrMsgTxt("New: One output expected."); } // Return a handle to a new C++ wrapper instance - plhs[0] = convertPtr2Mat(new OsqpData); + osqpData = new OsqpData; + //osqpData->solver = initializeOSQPSolver(); + osqpData->solver = NULL; + plhs[0] = convertPtr2Mat(osqpData); return; } // Check for a second input, which should be the class instance handle or string 'static' - if (nrhs < 2) + if (nrhs < 2) mexErrMsgTxt("Second input should be a class instance handle or the string 'static'."); if(mxGetString(prhs[1], stat_string, sizeof(stat_string))){ // If we are dealing with non-static methods, get the class instance pointer from the second input - OsqpData* osqpData; // OSQP data identifier + //OsqpData* osqpData; // OSQP data identifier //DELETE HERE osqpData = convertMat2Ptr(prhs[1]); - osqpSolver->work = osqpData->work; } else { if (strcmp("static", stat_string)){ mexErrMsgTxt("Second argument for static functions is string 'static'"); @@ -143,11 +151,12 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // delete the object and its data if (!strcmp("delete", cmd)) { - - osqp_cleanup(osqpSolver); - - //clean up the handle object + + osqp_cleanup(osqpData->solver); destroyObject(prhs[1]); + //clean up the handle object + //if (prhs[1]) destroyObject(prhs[1]); + //osqp_cleanup(osqpSolver); // Warn if other commands were ignored if (nlhs != 0 || nrhs != 2) mexWarnMsgTxt("Delete: Unexpected arguments ignored."); @@ -156,34 +165,31 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // report the current settings if (!strcmp("current_settings", cmd)) { - //throw an error if this is called before solver is configured - if(!osqpSolver->work){ + if(!osqpData->solver){ mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); } //report the current settings - plhs[0] = copySettingsToMxStruct(osqpSolver->settings); + plhs[0] = copySettingsToMxStruct(osqpData->solver->settings); return; } // write_settings if (!strcmp("update_settings", cmd)) { - //overwrite the current settings. Mex function is responsible //for disallowing overwrite of selected settings after initialization, //and for all error checking //throw an error if this is called before solver is configured - if(!osqpSolver->work){ + if(!osqpData->solver){ mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); } - copyUpdatedSettingsToWork(prhs[2],osqpSolver); + copyUpdatedSettingsToWork(prhs[2],osqpData->solver); return; } // report the default settings if (!strcmp("default_settings", cmd)) { - // Warn if other commands were ignored if (nrhs > 2) mexWarnMsgTxt("Default settings: unexpected number of arguments."); @@ -203,10 +209,9 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("setup", cmd)) { //throw an error if this is called more than once - if(osqpSolver->work){ + if(osqpData->solver){ mexErrMsgTxt("Solver is already initialized with problem data."); } - //Create data and settings containers OSQPSettings* settings = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); @@ -234,14 +239,14 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPInt * Pp = (OSQPInt*)copyToCintVector(mxGetJc(P), dataN + 1); OSQPInt * Pi = (OSQPInt*)copyToCintVector(mxGetIr(P), Pp[dataN]); OSQPFloat * Px = copyToOSQPFloatVector(mxGetPr(P), Pp[dataN]); - OSQPCscMatrix* dataP = NULL; + OSQPCscMatrix* dataP = new OSQPCscMatrix; csc_set_data(dataP, dataN, dataN, Pp[dataN], Px, Pi, Pp); // Matrix A: nnz = A->p[n] OSQPInt* Ap = (OSQPInt*)copyToCintVector(mxGetJc(A), dataN + 1); OSQPInt* Ai = (OSQPInt*)copyToCintVector(mxGetIr(A), Ap[dataN]); OSQPFloat * Ax = copyToOSQPFloatVector(mxGetPr(A), Ap[dataN]); - OSQPCscMatrix* dataA = NULL; + OSQPCscMatrix* dataA = new OSQPCscMatrix; csc_set_data(dataA, dataM, dataN, Ap[dataN], Ax, Ai, Ap); // Create Settings @@ -256,7 +261,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // Setup workspace //exitflag = osqp_setup(&(osqpData->work), data, settings); - exitflag = osqp_setup(&osqpSolver, dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); + exitflag = osqp_setup(&osqpData->solver, dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); //cleanup temporary structures // Data @@ -266,13 +271,11 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (Ax) c_free(Ax); if (Ai) c_free(Ai); if (Ap) c_free(Ap); - //if (data->A) c_free(data->A); - //if (data) mxFree(data); if (dataQ) c_free(dataQ); if (dataL) c_free(dataL); if (dataU) c_free(dataU); - if (dataP) freeCscMatrix(dataP); - if (dataA) freeCscMatrix(dataA); + if (dataP) c_free(dataP); + if (dataA) c_free(dataA); // Settings if (settings) mxFree(settings); @@ -289,12 +292,12 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("get_dimensions", cmd)) { //throw an error if this is called before solver is configured - if(!osqpSolver->work){ + if(!osqpData->solver){ mexErrMsgTxt("Solver has not been initialized."); } - plhs[0] = mxCreateDoubleScalar(osqpSolver->work->data->n); - plhs[1] = mxCreateDoubleScalar(osqpSolver->work->data->m); + plhs[0] = mxCreateDoubleScalar(osqpData->solver->work->data->n); + plhs[1] = mxCreateDoubleScalar(osqpData->solver->work->data->m); return; } @@ -311,7 +314,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("update", cmd)) { //throw an error if this is called before solver is configured - if(!osqpSolver->work){ + if(!osqpData->solver){ mexErrMsgTxt("Solver has not been initialized."); } @@ -329,24 +332,24 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) Ax_n = mxGetScalar(prhs[10]); // Copy vectors to ensure they are cast as OSQPFloat - OSQPFloat *q_vec = NULL; - OSQPFloat *l_vec = NULL; - OSQPFloat *u_vec = NULL; - OSQPFloat *Px_vec = NULL; - OSQPFloat *Ax_vec = NULL; + OSQPFloat *q_vec = NULL; + OSQPFloat *l_vec = NULL; + OSQPFloat *u_vec = NULL; + OSQPFloat *Px_vec = NULL; + OSQPFloat *Ax_vec = NULL; OSQPInt *Px_idx_vec = NULL; OSQPInt *Ax_idx_vec = NULL; if(!mxIsEmpty(q)){ q_vec = copyToOSQPFloatVector(mxGetPr(q), - osqpSolver->work->data->n); + osqpData->solver->work->data->n); } if(!mxIsEmpty(l)){ l_vec = copyToOSQPFloatVector(mxGetPr(l), - osqpSolver->work->data->m); + osqpData->solver->work->data->m); } if(!mxIsEmpty(u)){ u_vec = copyToOSQPFloatVector(mxGetPr(u), - osqpSolver->work->data->m); + osqpData->solver->work->data->m); } if(!mxIsEmpty(Px)){ Px_vec = copyToOSQPFloatVector(mxGetPr(Px), Px_n); @@ -362,12 +365,12 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } if (!exitflag && (!mxIsEmpty(q) || !mxIsEmpty(l) || !mxIsEmpty(u))) { - exitflag = osqp_update_data_vec(osqpSolver, q_vec, l_vec, u_vec); + exitflag = osqp_update_data_vec(osqpData->solver, q_vec, l_vec, u_vec); if (exitflag) exitflag=1; } if (!exitflag && (!mxIsEmpty(Px) || !mxIsEmpty(Ax))) { - exitflag = osqp_update_data_mat(osqpSolver, Px_vec, Px_idx_vec, Px_n, Ax_vec, Ax_idx_vec, Ax_n); + exitflag = osqp_update_data_mat(osqpData->solver, Px_vec, Px_idx_vec, Px_n, Ax_vec, Ax_idx_vec, Ax_n); if (exitflag) exitflag=2; } @@ -395,7 +398,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("warm_start", cmd) || !strcmp("warm_start_x", cmd) || !strcmp("warm_start_y", cmd)) { //throw an error if this is called before solver is configured - if(!osqpSolver->work){ + if(!osqpData->solver){ mexErrMsgTxt("Solver has not been initialized."); } @@ -422,15 +425,15 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if(!mxIsEmpty(x)){ x_vec = copyToOSQPFloatVector(mxGetPr(x), - osqpSolver->work->data->n); + osqpData->solver->work->data->n); } if(!mxIsEmpty(y)){ y_vec = copyToOSQPFloatVector(mxGetPr(y), - osqpSolver->work->data->m); + osqpData->solver->work->data->m); } // Warm start x and y - osqp_warm_start(osqpSolver, x_vec, y_vec); + osqp_warm_start(osqpData->solver, x_vec, y_vec); // Free vectors if(!x_vec) c_free(x_vec); @@ -444,74 +447,74 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (nlhs != 5 || nrhs != 2){ mexErrMsgTxt("Solve : wrong number of inputs / outputs"); } - if(!osqpSolver->work){ + if(!osqpData->solver){ mexErrMsgTxt("No problem data has been given."); } // solve the problem - osqp_solve(osqpSolver); + osqp_solve(osqpData->solver); // Allocate space for solution // primal variables - plhs[0] = mxCreateDoubleMatrix(osqpSolver->work->data->n,1,mxREAL); + plhs[0] = mxCreateDoubleMatrix(osqpData->solver->work->data->n,1,mxREAL); // dual variables - plhs[1] = mxCreateDoubleMatrix(osqpSolver->work->data->m,1,mxREAL); + plhs[1] = mxCreateDoubleMatrix(osqpData->solver->work->data->m,1,mxREAL); // primal infeasibility certificate - plhs[2] = mxCreateDoubleMatrix(osqpSolver->work->data->m,1,mxREAL); + plhs[2] = mxCreateDoubleMatrix(osqpData->solver->work->data->m,1,mxREAL); // dual infeasibility certificate - plhs[3] = mxCreateDoubleMatrix(osqpSolver->work->data->n,1,mxREAL); + plhs[3] = mxCreateDoubleMatrix(osqpData->solver->work->data->n,1,mxREAL); //copy results to mxArray outputs //assume that five outputs will always //be returned to matlab-side class wrapper - if ((osqpSolver->info->status_val != OSQP_PRIMAL_INFEASIBLE) && - (osqpSolver->info->status_val != OSQP_DUAL_INFEASIBLE)){ + if ((osqpData->solver->info->status_val != OSQP_PRIMAL_INFEASIBLE) && + (osqpData->solver->info->status_val != OSQP_DUAL_INFEASIBLE)){ //primal and dual solutions - castToDoubleArr(osqpSolver->solution->x, mxGetPr(plhs[0]), osqpSolver->work->data->n); - castToDoubleArr(osqpSolver->solution->y, mxGetPr(plhs[1]), osqpSolver->work->data->m); + castToDoubleArr(osqpData->solver->solution->x, mxGetPr(plhs[0]), osqpData->solver->work->data->n); + castToDoubleArr(osqpData->solver->solution->y, mxGetPr(plhs[1]), osqpData->solver->work->data->m); //infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[2]), osqpSolver->work->data->m); - setToNaN(mxGetPr(plhs[3]), osqpSolver->work->data->n); + setToNaN(mxGetPr(plhs[2]), osqpData->solver->work->data->m); + setToNaN(mxGetPr(plhs[3]), osqpData->solver->work->data->n); - } else if (osqpSolver->info->status_val == OSQP_PRIMAL_INFEASIBLE || - osqpSolver->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE){ //primal infeasible + } else if (osqpData->solver->info->status_val == OSQP_PRIMAL_INFEASIBLE || + osqpData->solver->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE){ //primal infeasible //primal and dual solutions -> NaN values - setToNaN(mxGetPr(plhs[0]), osqpSolver->work->data->n); - setToNaN(mxGetPr(plhs[1]), osqpSolver->work->data->m); + setToNaN(mxGetPr(plhs[0]), osqpData->solver->work->data->n); + setToNaN(mxGetPr(plhs[1]), osqpData->solver->work->data->m); //primal infeasibility certificates - castToDoubleArr(osqpSolver->solution->prim_inf_cert, mxGetPr(plhs[2]), osqpSolver->work->data->m); + castToDoubleArr(osqpData->solver->solution->prim_inf_cert, mxGetPr(plhs[2]), osqpData->solver->work->data->m); //dual infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[3]), osqpSolver->work->data->n); + setToNaN(mxGetPr(plhs[3]), osqpData->solver->work->data->n); // Set objective value to infinity - osqpSolver->info->obj_val = mxGetInf(); + osqpData->solver->info->obj_val = mxGetInf(); } else { //dual infeasible //primal and dual solutions -> NaN values - setToNaN(mxGetPr(plhs[0]), osqpSolver->work->data->n); - setToNaN(mxGetPr(plhs[1]), osqpSolver->work->data->m); + setToNaN(mxGetPr(plhs[0]), osqpData->solver->work->data->n); + setToNaN(mxGetPr(plhs[1]), osqpData->solver->work->data->m); //primal infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[2]), osqpSolver->work->data->m); + setToNaN(mxGetPr(plhs[2]), osqpData->solver->work->data->m); //dual infeasibility certificates - castToDoubleArr(osqpSolver->solution->dual_inf_cert, mxGetPr(plhs[3]), osqpSolver->work->data->n); + castToDoubleArr(osqpData->solver->solution->dual_inf_cert, mxGetPr(plhs[3]), osqpData->solver->work->data->n); // Set objective value to -infinity - osqpSolver->info->obj_val = -mxGetInf(); + osqpData->solver->info->obj_val = -mxGetInf(); } - if (osqpSolver->info->status_val == OSQP_NON_CVX) { - osqpSolver->info->obj_val = mxGetNaN(); + if (osqpData->solver->info->status_val == OSQP_NON_CVX) { + osqpData->solver->info->obj_val = mxGetNaN(); } - plhs[4] = copyInfoToMxStruct(osqpSolver->info); // Info structure + plhs[4] = copyInfoToMxStruct(osqpData->solver->info); // Info structure return; } @@ -612,14 +615,18 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } /** - * This function sets all the properties of OSQPSolver to NULL. + * This function dynamically allocates OSQPSovler and sets all the properties of OSQPSolver to NULL. + * WARNING: The memory allocated here (OSQPSolver*) needs to be freed. * WARNING: Any dynamically allocated pointers must be freed before calling this function. */ -void initializeOSQPSolver(OSQPSolver* osqpSolver) { - osqpSolver->info = NULL; - osqpSolver->settings = NULL; - osqpSolver->solution = NULL; - osqpSolver->work = NULL; +OSQPSolver* initializeOSQPSolver() { + OSQPSolver* osqpSolver = new OSQPSolver; + osqpSolver->info = NULL; + osqpSolver->settings = NULL; + osqpSolver->solution = NULL; + osqpSolver->work = NULL; + //osqp_set_default_settings(osqpSolver->settings); + return osqpSolver; } //Dynamically creates a OSQPFloat vector copy of the input. @@ -714,7 +721,7 @@ mxArray* copyInfoToMxStruct(OSQPInfo* info){ mxSetField(mxPtr, 0, "update_time", mxCreateDoubleScalar(info->update_time)); mxSetField(mxPtr, 0, "polish_time", mxCreateDoubleScalar(info->polish_time)); mxSetField(mxPtr, 0, "run_time", mxCreateDoubleScalar(info->run_time)); - #endif + #endif /* ifdef PROFILING */ mxSetField(mxPtr, 0, "rho_updates", mxCreateDoubleScalar(info->rho_updates)); mxSetField(mxPtr, 0, "rho_estimate", mxCreateDoubleScalar(info->rho_estimate)); @@ -752,7 +759,13 @@ mxArray* copySettingsToMxStruct(OSQPSettings* settings){ mxSetField(mxPtr, 0, "check_termination", mxCreateDoubleScalar(settings->check_termination)); mxSetField(mxPtr, 0, "warm_starting", mxCreateDoubleScalar(settings->warm_starting)); mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); - + mxSetField(mxPtr, 0, "device", mxCreateDoubleScalar(settings->device)); + mxSetField(mxPtr, 0, "polishing", mxCreateDoubleScalar(settings->polishing)); + mxSetField(mxPtr, 0, "rho_is_vec", mxCreateDoubleScalar(settings->rho_is_vec)); + mxSetField(mxPtr, 0, "cg_max_iter", mxCreateDoubleScalar(settings->cg_max_iter)); + mxSetField(mxPtr, 0, "cg_tol_reduction", mxCreateDoubleScalar(settings->cg_tol_reduction)); + mxSetField(mxPtr, 0, "cg_tol_fraction", mxCreateDoubleScalar(settings->cg_tol_fraction)); + mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->cg_precond)); return mxPtr; } @@ -788,7 +801,13 @@ void copyMxStructToSettings(const mxArray* mxPtr, OSQPSettings* settings){ settings->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); settings->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); settings->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); - + settings->device = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "device")); + settings->polishing = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polishing")); + settings->rho_is_vec = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "rho_is_vec")); + settings->cg_max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_max_iter")); + settings->cg_tol_reduction = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_reduction")); + settings->cg_tol_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_fraction")); + settings->cg_precond = (osqp_precond_type) (OSQPInt) (mxGetField(mxPtr, 0, "cg_precond")); } void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ From ef80b1fdec8db8a670247c4b843ee59b3d6cd35c Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Fri, 8 Sep 2023 15:17:41 -0400 Subject: [PATCH 03/36] warm_start -> warm_starting --- osqp_mex.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osqp_mex.cpp b/osqp_mex.cpp index 187879a..f332e3d 100755 --- a/osqp_mex.cpp +++ b/osqp_mex.cpp @@ -121,7 +121,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) mexErrMsgTxt("First input should be a command string less than 64 characters long."); // new object - //std::cout << "cmd = " << cmd << std::endl; //DELETE HERE if (!strcmp("new", cmd)) { // Check parameters if (nlhs != 1){ @@ -141,7 +140,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if(mxGetString(prhs[1], stat_string, sizeof(stat_string))){ // If we are dealing with non-static methods, get the class instance pointer from the second input - //OsqpData* osqpData; // OSQP data identifier //DELETE HERE osqpData = convertMat2Ptr(prhs[1]); } else { if (strcmp("static", stat_string)){ @@ -166,8 +164,9 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // report the current settings if (!strcmp("current_settings", cmd)) { //throw an error if this is called before solver is configured - if(!osqpData->solver){ - mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + if(!osqpData->solver) mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + if(!osqpData->solver->settings){ + mexErrMsgTxt("Solver settings is uninitialized. No settings have been configured."); } //report the current settings plhs[0] = copySettingsToMxStruct(osqpData->solver->settings); @@ -818,7 +817,6 @@ void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ //during setup, but uses the provided update functions in osqp.h to update //settings in the osqp workspace. Protects against bad parameter writes //or future modifications to updated settings handling - osqp_update_max_iter(osqpSolver, (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter"))); osqp_update_eps_abs(osqpSolver, @@ -842,7 +840,7 @@ void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ osqp_update_check_termination(osqpSolver, (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination"))); osqp_update_warm_start(osqpSolver, - (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_start"))); + (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting"))); #ifdef PROFILING osqp_update_time_limit(osqpSolver, (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit"))); From 3283b10cadda30acbc6b02f554653e58cdcd8a7d Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Tue, 12 Sep 2023 15:19:35 -0400 Subject: [PATCH 04/36] Fixed a bug with time_limit --- osqp_mex.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/osqp_mex.cpp b/osqp_mex.cpp index f332e3d..11e24a2 100755 --- a/osqp_mex.cpp +++ b/osqp_mex.cpp @@ -4,10 +4,10 @@ #include "osqp.h" //#include "ctrlc.h" // Needed for interrupt #include "qdldl_interface.h" // To extract workspace for codegen -//#include //DELETE HERE -//#include //DELETE HERE -//using std::cout; //DELETE HERE -//using std::endl; //DELETE HERE +#include //DELETE HERE +#include //DELETE HERE +using std::cout; //DELETE HERE +using std::endl; //DELETE HERE //c_int is replaced with OSQPInt //c_float is replaced with OSQPFloat @@ -109,13 +109,10 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { OsqpData* osqpData; //OSQPSolver* osqpSolver = NULL; - // Exitflag OSQPInt exitflag = 0; - // Static string for static methods char stat_string[64]; - // Get the command string char cmd[64]; if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) @@ -146,7 +143,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) mexErrMsgTxt("Second argument for static functions is string 'static'"); } } - // delete the object and its data if (!strcmp("delete", cmd)) { @@ -206,7 +202,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // setup if (!strcmp("setup", cmd)) { - //throw an error if this is called more than once if(osqpData->solver){ mexErrMsgTxt("Solver is already initialized with problem data."); @@ -260,8 +255,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // Setup workspace //exitflag = osqp_setup(&(osqpData->work), data, settings); - exitflag = osqp_setup(&osqpData->solver, dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); - + exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); //cleanup temporary structures // Data if (Px) c_free(Px); @@ -764,7 +758,7 @@ mxArray* copySettingsToMxStruct(OSQPSettings* settings){ mxSetField(mxPtr, 0, "cg_max_iter", mxCreateDoubleScalar(settings->cg_max_iter)); mxSetField(mxPtr, 0, "cg_tol_reduction", mxCreateDoubleScalar(settings->cg_tol_reduction)); mxSetField(mxPtr, 0, "cg_tol_fraction", mxCreateDoubleScalar(settings->cg_tol_fraction)); - mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->cg_precond)); + mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); return mxPtr; } From 734eb8f58fcfa1deba85779e1a80bd2813f05428 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Tue, 12 Sep 2023 16:18:18 -0400 Subject: [PATCH 05/36] Updated unittests: basic_tests (initial time_limit) and update_matrices_test (polish -> polishing) --- unittests/basic_tests.m | 2 +- unittests/update_matrices_tests.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unittests/basic_tests.m b/unittests/basic_tests.m index 269efe0..dedb073 100644 --- a/unittests/basic_tests.m +++ b/unittests/basic_tests.m @@ -170,7 +170,7 @@ function test_update_rho(testCase) end function test_update_time_limit(testCase) - testCase.verifyEqual(testCase.options.time_limit, 0) + testCase.verifyEqual(testCase.options.time_limit, 1e10) results = testCase.solver.solve(); testCase.verifyEqual(results.info.status_val, ... diff --git a/unittests/update_matrices_tests.m b/unittests/update_matrices_tests.m index 8948822..08eeb79 100644 --- a/unittests/update_matrices_tests.m +++ b/unittests/update_matrices_tests.m @@ -41,7 +41,7 @@ function setup_problem(testCase) % Setup solver testCase.solver = osqp; testCase.solver.setup(testCase.P, testCase.q, testCase.A, ... - testCase.l, testCase.u, 'verbose', false, 'eps_rel', 1e-7, 'eps_abs', 1e-07, 'polish', true); + testCase.l, testCase.u, 'verbose', false, 'eps_rel', 1e-7, 'eps_abs', 1e-07, 'polishing', true); % Setup tolerance testCase.tol = 1e-04; From 463030b4508241b077303afa10dd008e2f6816d5 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Wed, 13 Sep 2023 10:10:46 -0400 Subject: [PATCH 06/36] Fixed an issue where time_limit would not update --- osqp_mex.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/osqp_mex.cpp b/osqp_mex.cpp index 11e24a2..4619b07 100755 --- a/osqp_mex.cpp +++ b/osqp_mex.cpp @@ -4,10 +4,6 @@ #include "osqp.h" //#include "ctrlc.h" // Needed for interrupt #include "qdldl_interface.h" // To extract workspace for codegen -#include //DELETE HERE -#include //DELETE HERE -using std::cout; //DELETE HERE -using std::endl; //DELETE HERE //c_int is replaced with OSQPInt //c_float is replaced with OSQPFloat @@ -26,13 +22,11 @@ const char* OSQP_INFO_FIELDS[] = {"status", //char* "iter", //OSQPInt "rho_updates", //OSQPInt "rho_estimate", //OSQPFloat - #ifdef PROFILING "setup_time", //OSQPFloat "solve_time", //OSQPFloat "update_time", //OSQPFloat "polish_time", //OSQPFloat "run_time", //OSQPFloat - #endif /* ifdef PROFILING */ }; const char* OSQP_SETTINGS_FIELDS[] = {"device", //OSQPInt @@ -100,9 +94,7 @@ OSQPInt osqp_update_verbose(OSQPSolver* osqpSolver, OSQPInt verbose_new); OSQPInt osqp_update_scaled_termination(OSQPSolver* osqpSolver, OSQPInt scaled_termination_new); OSQPInt osqp_update_check_termination(OSQPSolver* osqpSolver, OSQPInt check_termination_new); OSQPInt osqp_update_warm_start(OSQPSolver* osqpSolver, OSQPInt warm_start_new); -#ifdef PROFILING OSQPInt osqp_update_time_limit(OSQPSolver* osqpSolver, OSQPFloat time_limit_new); -#endif /* ifdef PROFILING */ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) @@ -707,14 +699,12 @@ mxArray* copyInfoToMxStruct(OSQPInfo* info){ mxSetField(mxPtr, 0, "prim_res", mxCreateDoubleScalar(info->prim_res)); mxSetField(mxPtr, 0, "dual_res", mxCreateDoubleScalar(info->dual_res)); - #ifdef PROFILING - //if not profiling, these fields will be empty mxSetField(mxPtr, 0, "setup_time", mxCreateDoubleScalar(info->setup_time)); mxSetField(mxPtr, 0, "solve_time", mxCreateDoubleScalar(info->solve_time)); mxSetField(mxPtr, 0, "update_time", mxCreateDoubleScalar(info->update_time)); mxSetField(mxPtr, 0, "polish_time", mxCreateDoubleScalar(info->polish_time)); mxSetField(mxPtr, 0, "run_time", mxCreateDoubleScalar(info->run_time)); - #endif /* ifdef PROFILING */ + mxSetField(mxPtr, 0, "rho_updates", mxCreateDoubleScalar(info->rho_updates)); mxSetField(mxPtr, 0, "rho_estimate", mxCreateDoubleScalar(info->rho_estimate)); @@ -835,10 +825,8 @@ void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination"))); osqp_update_warm_start(osqpSolver, (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting"))); -#ifdef PROFILING osqp_update_time_limit(osqpSolver, (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit"))); -#endif /* ifdef PROFILING */ // Check for settings that need special update // Update them only if they are different than already set values @@ -1086,7 +1074,6 @@ OSQPInt osqp_update_check_termination(OSQPSolver* osqpSolver, OSQPInt check_term return 0; } -#ifdef PROFILING OSQPInt osqp_update_time_limit(OSQPSolver* osqpSolver, OSQPFloat time_limit_new) { @@ -1106,4 +1093,3 @@ OSQPInt osqp_update_time_limit(OSQPSolver* osqpSolver, OSQPFloat time_limit_new) return 0; } -#endif /* ifdef PROFILING */ From a0bb37063b9ae5569123b70953a55d58c308a5a0 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Wed, 27 Sep 2023 10:26:01 -0400 Subject: [PATCH 07/36] Disabling codegen tests --- unittests/codegen_mat_tests.m | 1 + unittests/codegen_vec_tests.m | 1 + 2 files changed, 2 insertions(+) diff --git a/unittests/codegen_mat_tests.m b/unittests/codegen_mat_tests.m index 31be98d..07c3d28 100644 --- a/unittests/codegen_mat_tests.m +++ b/unittests/codegen_mat_tests.m @@ -17,6 +17,7 @@ methods(TestMethodSetup) function setup_problem(testCase) % Create Problem + assumeFail(testCase); testCase.P = sparse([11 0; 0, 0.1]); testCase.q = [3; 4]; testCase.A = sparse([-1. 0; 0 -1; -1 -3; 2 5; 3 4]); diff --git a/unittests/codegen_vec_tests.m b/unittests/codegen_vec_tests.m index 972c11f..f5a8f42 100644 --- a/unittests/codegen_vec_tests.m +++ b/unittests/codegen_vec_tests.m @@ -17,6 +17,7 @@ methods(TestMethodSetup) function setup_problem(testCase) % Create Problem + assumeFail(testCase); testCase.P = sparse([11 0; 0, 0]); testCase.q = [3; 4]; testCase.A = sparse([-1. 0; 0 -1; -1 -3; 2 5; 3 4]); From 3c48d73e946805ef50a4c246be7471d72c17b7e1 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Thu, 9 Nov 2023 16:16:09 -0500 Subject: [PATCH 08/36] Addressed Ian's comments: use the public functions and update settings through the API. Also reversed the change to .gitmodules. --- .gitmodules | 2 +- osqp_mex.cpp | 478 ++++++++++----------------------------------------- 2 files changed, 95 insertions(+), 385 deletions(-) diff --git a/.gitmodules b/.gitmodules index d19485e..22d1c4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "osqp"] path = osqp_sources - url = git@github.com:osqp/osqp.git + url = https://github.com/osqp/osqp diff --git a/osqp_mex.cpp b/osqp_mex.cpp index 4619b07..da71634 100755 --- a/osqp_mex.cpp +++ b/osqp_mex.cpp @@ -2,8 +2,6 @@ #include "matrix.h" #include "osqp_mex.hpp" #include "osqp.h" -//#include "ctrlc.h" // Needed for interrupt -#include "qdldl_interface.h" // To extract workspace for codegen //c_int is replaced with OSQPInt //c_float is replaced with OSQPFloat @@ -75,26 +73,13 @@ void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len); void setToNaN(double* arr_out, OSQPInt len); void copyMxStructToSettings(const mxArray*, OSQPSettings*); void copyUpdatedSettingsToWork(const mxArray*, OSQPSolver*); -void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len); +//void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len); //DELETE HERE void freeCscMatrix(OSQPCscMatrix* M); -OSQPInt* copyToCintVector(mwIndex * vecData, OSQPInt numel); -OSQPInt* copyDoubleToCintVector(double* vecData, OSQPInt numel); +OSQPInt* copyToOSQPIntVector(mwIndex * vecData, OSQPInt numel); +OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel); OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel); mxArray* copyInfoToMxStruct(OSQPInfo* info); mxArray* copySettingsToMxStruct(OSQPSettings* settings); -OSQPInt osqp_update_max_iter(OSQPSolver* osqpSolver, OSQPInt max_iter_new); -OSQPInt osqp_update_eps_abs(OSQPSolver* osqpSolver, OSQPFloat eps_abs_new); -OSQPInt osqp_update_eps_rel(OSQPSolver* osqpSolver, OSQPFloat eps_rel_new); -OSQPInt osqp_update_eps_prim_inf(OSQPSolver* osqpSolver, OSQPFloat eps_prim_inf_new); -OSQPInt osqp_update_eps_dual_inf(OSQPSolver* osqpSolver, OSQPFloat eps_dual_inf_new); -OSQPInt osqp_update_alpha(OSQPSolver* osqpSolver, OSQPFloat alpha_new); -OSQPInt osqp_update_delta(OSQPSolver* osqpSolver, OSQPFloat delta_new); -OSQPInt osqp_update_polish_refine_iter(OSQPSolver* osqpSolver, OSQPInt polish_refine_iter_new); -OSQPInt osqp_update_verbose(OSQPSolver* osqpSolver, OSQPInt verbose_new); -OSQPInt osqp_update_scaled_termination(OSQPSolver* osqpSolver, OSQPInt scaled_termination_new); -OSQPInt osqp_update_check_termination(OSQPSolver* osqpSolver, OSQPInt check_termination_new); -OSQPInt osqp_update_warm_start(OSQPSolver* osqpSolver, OSQPInt warm_start_new); -OSQPInt osqp_update_time_limit(OSQPSolver* osqpSolver, OSQPFloat time_limit_new); void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) @@ -140,9 +125,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) osqp_cleanup(osqpData->solver); destroyObject(prhs[1]); - //clean up the handle object - //if (prhs[1]) destroyObject(prhs[1]); - //osqp_cleanup(osqpSolver); // Warn if other commands were ignored if (nlhs != 0 || nrhs != 2) mexWarnMsgTxt("Delete: Unexpected arguments ignored."); @@ -222,15 +204,15 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPFloat* dataU = copyToOSQPFloatVector(mxGetPr(u), dataM); // Matrix P: nnz = P->p[n] - OSQPInt * Pp = (OSQPInt*)copyToCintVector(mxGetJc(P), dataN + 1); - OSQPInt * Pi = (OSQPInt*)copyToCintVector(mxGetIr(P), Pp[dataN]); + OSQPInt * Pp = (OSQPInt*)copyToOSQPIntVector(mxGetJc(P), dataN + 1); + OSQPInt * Pi = (OSQPInt*)copyToOSQPIntVector(mxGetIr(P), Pp[dataN]); OSQPFloat * Px = copyToOSQPFloatVector(mxGetPr(P), Pp[dataN]); OSQPCscMatrix* dataP = new OSQPCscMatrix; csc_set_data(dataP, dataN, dataN, Pp[dataN], Px, Pi, Pp); // Matrix A: nnz = A->p[n] - OSQPInt* Ap = (OSQPInt*)copyToCintVector(mxGetJc(A), dataN + 1); - OSQPInt* Ai = (OSQPInt*)copyToCintVector(mxGetIr(A), Ap[dataN]); + OSQPInt* Ap = (OSQPInt*)copyToOSQPIntVector(mxGetJc(A), dataN + 1); + OSQPInt* Ai = (OSQPInt*)copyToOSQPIntVector(mxGetIr(A), Ap[dataN]); OSQPFloat * Ax = copyToOSQPFloatVector(mxGetPr(A), Ap[dataN]); OSQPCscMatrix* dataA = new OSQPCscMatrix; csc_set_data(dataA, dataM, dataN, Ap[dataN], Ax, Ai, Ap); @@ -250,17 +232,17 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); //cleanup temporary structures // Data - if (Px) c_free(Px); - if (Pi) c_free(Pi); - if (Pp) c_free(Pp); - if (Ax) c_free(Ax); - if (Ai) c_free(Ai); - if (Ap) c_free(Ap); - if (dataQ) c_free(dataQ); - if (dataL) c_free(dataL); - if (dataU) c_free(dataU); - if (dataP) c_free(dataP); - if (dataA) c_free(dataA); + if (Px) free(Px); + if (Pi) free(Pi); + if (Pp) free(Pp); + if (Ax) free(Ax); + if (Ai) free(Ai); + if (Ap) free(Ap); + if (dataQ) free(dataQ); + if (dataL) free(dataL); + if (dataU) free(dataU); + if (dataP) free(dataP); + if (dataA) free(dataA); // Settings if (settings) mxFree(settings); @@ -280,9 +262,10 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if(!osqpData->solver){ mexErrMsgTxt("Solver has not been initialized."); } - - plhs[0] = mxCreateDoubleScalar(osqpData->solver->work->data->n); - plhs[1] = mxCreateDoubleScalar(osqpData->solver->work->data->m); + OSQPInt n, m; + osqp_get_dimensions(osqpData->solver, &m, &n); + plhs[0] = mxCreateDoubleScalar(n); + plhs[1] = mxCreateDoubleScalar(m); return; } @@ -324,17 +307,17 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPFloat *Ax_vec = NULL; OSQPInt *Px_idx_vec = NULL; OSQPInt *Ax_idx_vec = NULL; + + OSQPInt n, m; + osqp_get_dimensions(osqpData->solver, &m, &n); if(!mxIsEmpty(q)){ - q_vec = copyToOSQPFloatVector(mxGetPr(q), - osqpData->solver->work->data->n); + q_vec = copyToOSQPFloatVector(mxGetPr(q), n); } if(!mxIsEmpty(l)){ - l_vec = copyToOSQPFloatVector(mxGetPr(l), - osqpData->solver->work->data->m); + l_vec = copyToOSQPFloatVector(mxGetPr(l), m); } if(!mxIsEmpty(u)){ - u_vec = copyToOSQPFloatVector(mxGetPr(u), - osqpData->solver->work->data->m); + u_vec = copyToOSQPFloatVector(mxGetPr(u), m); } if(!mxIsEmpty(Px)){ Px_vec = copyToOSQPFloatVector(mxGetPr(Px), Px_n); @@ -343,10 +326,10 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) Ax_vec = copyToOSQPFloatVector(mxGetPr(Ax), Ax_n); } if(!mxIsEmpty(Px_idx)){ - Px_idx_vec = copyDoubleToCintVector(mxGetPr(Px_idx), Px_n); + Px_idx_vec = copyDoubleToOSQPIntVector(mxGetPr(Px_idx), Px_n); } if(!mxIsEmpty(Ax_idx)){ - Ax_idx_vec = copyDoubleToCintVector(mxGetPr(Ax_idx), Ax_n); + Ax_idx_vec = copyDoubleToOSQPIntVector(mxGetPr(Ax_idx), Ax_n); } if (!exitflag && (!mxIsEmpty(q) || !mxIsEmpty(l) || !mxIsEmpty(u))) { @@ -361,13 +344,13 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // Free vectors - if(!mxIsEmpty(q)) c_free(q_vec); - if(!mxIsEmpty(l)) c_free(l_vec); - if(!mxIsEmpty(u)) c_free(u_vec); - if(!mxIsEmpty(Px)) c_free(Px_vec); - if(!mxIsEmpty(Ax)) c_free(Ax_vec); - if(!mxIsEmpty(Px_idx)) c_free(Px_idx_vec); - if(!mxIsEmpty(Ax_idx)) c_free(Ax_idx_vec); + if(!mxIsEmpty(q)) free(q_vec); + if(!mxIsEmpty(l)) free(l_vec); + if(!mxIsEmpty(u)) free(u_vec); + if(!mxIsEmpty(Px)) free(Px_vec); + if(!mxIsEmpty(Ax)) free(Ax_vec); + if(!mxIsEmpty(Px_idx)) free(Px_idx_vec); + if(!mxIsEmpty(Ax_idx)) free(Ax_idx_vec); // Report errors (if any) switch (exitflag) { @@ -408,21 +391,21 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPFloat *x_vec = NULL; OSQPFloat *y_vec = NULL; + OSQPInt n, m; + osqp_get_dimensions(osqpData->solver, &m, &n); if(!mxIsEmpty(x)){ - x_vec = copyToOSQPFloatVector(mxGetPr(x), - osqpData->solver->work->data->n); + x_vec = copyToOSQPFloatVector(mxGetPr(x),n); } if(!mxIsEmpty(y)){ - y_vec = copyToOSQPFloatVector(mxGetPr(y), - osqpData->solver->work->data->m); + y_vec = copyToOSQPFloatVector(mxGetPr(y),m); } // Warm start x and y osqp_warm_start(osqpData->solver, x_vec, y_vec); // Free vectors - if(!x_vec) c_free(x_vec); - if(!y_vec) c_free(y_vec); + if(!x_vec) free(x_vec); + if(!y_vec) free(y_vec); return; } @@ -438,16 +421,17 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // solve the problem osqp_solve(osqpData->solver); - + OSQPInt n, m; + osqp_get_dimensions(osqpData->solver, &m, &n); // Allocate space for solution // primal variables - plhs[0] = mxCreateDoubleMatrix(osqpData->solver->work->data->n,1,mxREAL); + plhs[0] = mxCreateDoubleMatrix(n,1,mxREAL); // dual variables - plhs[1] = mxCreateDoubleMatrix(osqpData->solver->work->data->m,1,mxREAL); + plhs[1] = mxCreateDoubleMatrix(m,1,mxREAL); // primal infeasibility certificate - plhs[2] = mxCreateDoubleMatrix(osqpData->solver->work->data->m,1,mxREAL); + plhs[2] = mxCreateDoubleMatrix(m,1,mxREAL); // dual infeasibility certificate - plhs[3] = mxCreateDoubleMatrix(osqpData->solver->work->data->n,1,mxREAL); + plhs[3] = mxCreateDoubleMatrix(n,1,mxREAL); //copy results to mxArray outputs //assume that five outputs will always @@ -456,25 +440,25 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) (osqpData->solver->info->status_val != OSQP_DUAL_INFEASIBLE)){ //primal and dual solutions - castToDoubleArr(osqpData->solver->solution->x, mxGetPr(plhs[0]), osqpData->solver->work->data->n); - castToDoubleArr(osqpData->solver->solution->y, mxGetPr(plhs[1]), osqpData->solver->work->data->m); + castToDoubleArr(osqpData->solver->solution->x, mxGetPr(plhs[0]), n); + castToDoubleArr(osqpData->solver->solution->y, mxGetPr(plhs[1]), m); //infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[2]), osqpData->solver->work->data->m); - setToNaN(mxGetPr(plhs[3]), osqpData->solver->work->data->n); + setToNaN(mxGetPr(plhs[2]), m); + setToNaN(mxGetPr(plhs[3]), n); } else if (osqpData->solver->info->status_val == OSQP_PRIMAL_INFEASIBLE || osqpData->solver->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE){ //primal infeasible //primal and dual solutions -> NaN values - setToNaN(mxGetPr(plhs[0]), osqpData->solver->work->data->n); - setToNaN(mxGetPr(plhs[1]), osqpData->solver->work->data->m); + setToNaN(mxGetPr(plhs[0]), n); + setToNaN(mxGetPr(plhs[1]), m); //primal infeasibility certificates - castToDoubleArr(osqpData->solver->solution->prim_inf_cert, mxGetPr(plhs[2]), osqpData->solver->work->data->m); + castToDoubleArr(osqpData->solver->solution->prim_inf_cert, mxGetPr(plhs[2]), m); //dual infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[3]), osqpData->solver->work->data->n); + setToNaN(mxGetPr(plhs[3]), n); // Set objective value to infinity osqpData->solver->info->obj_val = mxGetInf(); @@ -482,14 +466,14 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } else { //dual infeasible //primal and dual solutions -> NaN values - setToNaN(mxGetPr(plhs[0]), osqpData->solver->work->data->n); - setToNaN(mxGetPr(plhs[1]), osqpData->solver->work->data->m); + setToNaN(mxGetPr(plhs[0]), n); + setToNaN(mxGetPr(plhs[1]), m); //primal infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[2]), osqpData->solver->work->data->m); + setToNaN(mxGetPr(plhs[2]), m); //dual infeasibility certificates - castToDoubleArr(osqpData->solver->solution->dual_inf_cert, mxGetPr(plhs[3]), osqpData->solver->work->data->n); + castToDoubleArr(osqpData->solver->solution->dual_inf_cert, mxGetPr(plhs[3]), n); // Set objective value to -infinity osqpData->solver->info->obj_val = -mxGetInf(); @@ -620,7 +604,7 @@ OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel){ if (!vecData) return NULL; //This needs to be freed! - OSQPFloat* out = (OSQPFloat*)c_malloc(numel * sizeof(OSQPFloat)); + OSQPFloat* out = (OSQPFloat*)malloc(numel * sizeof(OSQPFloat)); //copy data for(OSQPInt i=0; i < numel; i++){ @@ -630,9 +614,9 @@ OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel){ } //Dynamically creates a OSQPInt vector copy of the input. -OSQPInt* copyToCintVector(mwIndex* vecData, OSQPInt numel){ +OSQPInt* copyToOSQPIntVector(mwIndex* vecData, OSQPInt numel){ // This memory needs to be freed! - OSQPInt* out = (OSQPInt*)c_malloc(numel * sizeof(OSQPInt)); + OSQPInt* out = (OSQPInt*)malloc(numel * sizeof(OSQPInt)); //copy data for(OSQPInt i=0; i < numel; i++){ @@ -643,9 +627,9 @@ OSQPInt* copyToCintVector(mwIndex* vecData, OSQPInt numel){ } //Dynamically copies a double vector to OSQPInt. -OSQPInt* copyDoubleToCintVector(double* vecData, OSQPInt numel){ +OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel){ // This memory needs to be freed! - OSQPInt* out = (OSQPInt*)c_malloc(numel * sizeof(OSQPInt)); + OSQPInt* out = (OSQPInt*)malloc(numel * sizeof(OSQPInt)); //copy data for(OSQPInt i=0; i < numel; i++){ @@ -655,19 +639,20 @@ OSQPInt* copyDoubleToCintVector(double* vecData, OSQPInt numel){ } +/* DELETE HERE void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len) { for (OSQPInt i = 0; i < len; i++) { arr_out[i] = (double)arr[i]; } -} +}*/ //This function frees the memory allocated in an OSQPCscMatrix M void freeCscMatrix(OSQPCscMatrix* M) { if (!M) return; - if (M->p) c_free(M->p); - if (M->i) c_free(M->i); - if (M->x) c_free(M->x); - c_free(M); + if (M->p) free(M->p); + if (M->i) free(M->i); + if (M->x) free(M->x); + free(M); } void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len) { @@ -796,300 +781,25 @@ void copyMxStructToSettings(const mxArray* mxPtr, OSQPSettings* settings){ void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ OSQPInt exitflag; - - //This does basically the same job as copyMxStructToSettings which was used - //during setup, but uses the provided update functions in osqp.h to update - //settings in the osqp workspace. Protects against bad parameter writes - //or future modifications to updated settings handling - osqp_update_max_iter(osqpSolver, - (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter"))); - osqp_update_eps_abs(osqpSolver, - (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs"))); - osqp_update_eps_rel(osqpSolver, - (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel"))); - osqp_update_eps_prim_inf(osqpSolver, - (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf"))); - osqp_update_eps_dual_inf(osqpSolver, - (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf"))); - osqp_update_alpha(osqpSolver, - (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha"))); - osqp_update_delta(osqpSolver, - (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta"))); - osqp_update_polish_refine_iter(osqpSolver, - (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter"))); - osqp_update_verbose(osqpSolver, - (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose"))); - osqp_update_scaled_termination(osqpSolver, - (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination"))); - osqp_update_check_termination(osqpSolver, - (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination"))); - osqp_update_warm_start(osqpSolver, - (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting"))); - osqp_update_time_limit(osqpSolver, - (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit"))); - - // Check for settings that need special update - // Update them only if they are different than already set values - OSQPFloat rho_new = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); - // Check if it has changed - if (c_absval(rho_new - osqpSolver->settings->rho) > NEW_SETTINGS_TOL){ - exitflag = osqp_update_rho(osqpSolver, rho_new); - if (exitflag){ - mexErrMsgTxt("rho update error!"); - } - } - - -} - -/**************************** -* Update problem settings * -****************************/ -OSQPInt osqp_update_max_iter(OSQPSolver* osqpSolver, OSQPInt max_iter_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that max_iter is positive - if (max_iter_new <= 0) { -#ifdef PRINTING - c_eprint("max_iter must be positive"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update max_iter - osqpSolver->settings->max_iter = max_iter_new; - - return 0; -} - -OSQPInt osqp_update_eps_abs(OSQPSolver* osqpSolver, OSQPFloat eps_abs_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that eps_abs is positive - if (eps_abs_new < 0.) { -#ifdef PRINTING - c_eprint("eps_abs must be nonnegative"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update eps_abs - osqpSolver->settings->eps_abs = eps_abs_new; - - return 0; -} - -OSQPInt osqp_update_eps_rel(OSQPSolver* osqpSolver, OSQPFloat eps_rel_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that eps_rel is positive - if (eps_rel_new < 0.) { -#ifdef PRINTING - c_eprint("eps_rel must be nonnegative"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update eps_rel - osqpSolver->settings->eps_rel = eps_rel_new; - - return 0; -} - -OSQPInt osqp_update_eps_prim_inf(OSQPSolver* osqpSolver, OSQPFloat eps_prim_inf_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that eps_prim_inf is positive - if (eps_prim_inf_new < 0.) { -#ifdef PRINTING - c_eprint("eps_prim_inf must be nonnegative"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update eps_prim_inf - osqpSolver->settings->eps_prim_inf = eps_prim_inf_new; - - return 0; -} - -OSQPInt osqp_update_eps_dual_inf(OSQPSolver* osqpSolver, OSQPFloat eps_dual_inf_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that eps_dual_inf is positive - if (eps_dual_inf_new < 0.) { -#ifdef PRINTING - c_eprint("eps_dual_inf must be nonnegative"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update eps_dual_inf - osqpSolver->settings->eps_dual_inf = eps_dual_inf_new; - - - return 0; -} - -OSQPInt osqp_update_alpha(OSQPSolver* osqpSolver, OSQPFloat alpha_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that alpha is between 0 and 2 - if ((alpha_new <= 0.) || (alpha_new >= 2.)) { -#ifdef PRINTING - c_eprint("alpha must be between 0 and 2"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update alpha - osqpSolver->settings->alpha = alpha_new; - - return 0; -} - -OSQPInt osqp_update_warm_start(OSQPSolver* osqpSolver, OSQPInt warm_start_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that warm_start is either 0 or 1 - if ((warm_start_new != 0) && (warm_start_new != 1)) { -#ifdef PRINTING - c_eprint("warm_start should be either 0 or 1"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update warm_start - osqpSolver->settings->warm_starting = warm_start_new; - - return 0; -} - -OSQPInt osqp_update_delta(OSQPSolver* osqpSolver, OSQPFloat delta_new) { - - // Check if workspace has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that delta is positive - if (delta_new <= 0.) { -# ifdef PRINTING - c_eprint("delta must be positive"); -# endif /* ifdef PRINTING */ - return 1; - } - - // Update delta - osqpSolver->settings->delta = delta_new; - - return 0; -} - -OSQPInt osqp_update_polish_refine_iter(OSQPSolver* osqpSolver, OSQPInt polish_refine_iter_new) { - - // Check if workspace has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that polish_refine_iter is nonnegative - if (polish_refine_iter_new < 0) { -# ifdef PRINTING - c_eprint("polish_refine_iter must be nonnegative"); -# endif /* ifdef PRINTING */ - return 1; - } - - // Update polish_refine_iter - osqpSolver->settings->polish_refine_iter = polish_refine_iter_new; - - return 0; -} - -OSQPInt osqp_update_verbose(OSQPSolver* osqpSolver, OSQPInt verbose_new){ - - // Check if workspace has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that verbose is either 0 or 1 - if ((verbose_new != 0) && (verbose_new != 1)) { -# ifdef PRINTING - c_eprint("verbose should be either 0 or 1"); -# endif /* ifdef PRINTING */ - return 1; - } - - // Update verbose - osqpSolver->settings->verbose = verbose_new; - - return 0; -} - -OSQPInt osqp_update_scaled_termination(OSQPSolver* osqpSolver, OSQPInt scaled_termination_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that scaled_termination is either 0 or 1 - if ((scaled_termination_new != 0) && (scaled_termination_new != 1)) { -#ifdef PRINTING - c_eprint("scaled_termination should be either 0 or 1"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update scaled_termination - osqpSolver->settings->scaled_termination = scaled_termination_new; - - return 0; -} - -OSQPInt osqp_update_check_termination(OSQPSolver* osqpSolver, OSQPInt check_termination_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that check_termination is nonnegative - if (check_termination_new < 0) { -#ifdef PRINTING - c_eprint("check_termination should be nonnegative"); -#endif /* ifdef PRINTING */ - return 1; - } - - // Update check_termination - osqpSolver->settings->check_termination = check_termination_new; - - return 0; -} - - -OSQPInt osqp_update_time_limit(OSQPSolver* osqpSolver, OSQPFloat time_limit_new) { - - // Check if osqpSolver has been initialized - if (!osqpSolver) return osqp_error(OSQP_WORKSPACE_NOT_INIT_ERROR); - - // Check that time_limit is nonnegative - if (time_limit_new < 0.) { -# ifdef PRINTING - c_print("time_limit must be nonnegative\n"); -# endif /* ifdef PRINTING */ - return 1; - } - - // Update time_limit - osqpSolver->settings->time_limit = time_limit_new; - - return 0; -} + //TODO (Amit): Update this + OSQPSettings* update_template = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); + if (!update_template) mexErrMsgTxt("Failed to allocate temporary OSQPSettings object."); + + update_template->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); + update_template->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); + update_template->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); + update_template->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf")); + update_template->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); + update_template->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); + update_template->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); + update_template->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); + update_template->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); + update_template->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); + update_template->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); + update_template->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); + update_template->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); + update_template->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); + + osqp_update_settings(osqpSolver, update_template); + if (update_template) free(update_template); +} \ No newline at end of file From 3a853cd66b67e5de12a4b5fce79fbbadddbcef46 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Wed, 15 Nov 2023 13:10:00 +0000 Subject: [PATCH 09/36] Consolidate C sources into one folder --- .gitmodules | 2 +- osqp_mex.cpp => c_sources/osqp_mex.cpp | 1608 ++++++++++++------------ osqp_mex.hpp => c_sources/osqp_mex.hpp | 108 +- osqp_sources => c_sources/osqp_sources | 0 4 files changed, 859 insertions(+), 859 deletions(-) rename osqp_mex.cpp => c_sources/osqp_mex.cpp (97%) rename osqp_mex.hpp => c_sources/osqp_mex.hpp (96%) rename osqp_sources => c_sources/osqp_sources (100%) diff --git a/.gitmodules b/.gitmodules index 22d1c4d..48058bf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "osqp"] - path = osqp_sources + path = c_sources/osqp_sources url = https://github.com/osqp/osqp diff --git a/osqp_mex.cpp b/c_sources/osqp_mex.cpp similarity index 97% rename from osqp_mex.cpp rename to c_sources/osqp_mex.cpp index da71634..c6b838d 100755 --- a/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -1,805 +1,805 @@ -#include "mex.h" -#include "matrix.h" -#include "osqp_mex.hpp" -#include "osqp.h" - -//c_int is replaced with OSQPInt -//c_float is replaced with OSQPFloat - -//TODO: Check if this definition is required, and maybe replace it with: -// enum linsys_solver_type { QDLDL_SOLVER, MKL_PARDISO_SOLVER }; -#define QDLDL_SOLVER 0 //Based on the previous API - -// all of the OSQP_INFO fieldnames as strings -const char* OSQP_INFO_FIELDS[] = {"status", //char* - "status_val", //OSQPInt - "status_polish", //OSQPInt - "obj_val", //OSQPFloat - "prim_res", //OSQPFloat - "dual_res", //OSQPFloat - "iter", //OSQPInt - "rho_updates", //OSQPInt - "rho_estimate", //OSQPFloat - "setup_time", //OSQPFloat - "solve_time", //OSQPFloat - "update_time", //OSQPFloat - "polish_time", //OSQPFloat - "run_time", //OSQPFloat - }; - -const char* OSQP_SETTINGS_FIELDS[] = {"device", //OSQPInt - "linsys_solver", //enum osqp_linsys_solver_type - "verbose", //OSQPInt - "warm_starting", //OSQPInt - "scaling", //OSQPInt - "polishing", //OSQPInt - "rho", //OSQPFloat - "rho_is_vec", //OSQPInt - "sigma", //OSQPFloat - "alpha", //OSQPFloat - "cg_max_iter", //OSQPInt - "cg_tol_reduction", //OSQPInt - "cg_tol_fraction", //OSQPFloat - "cg_precond", //osqp_precond_type - "adaptive_rho", //OSQPInt - "adaptive_rho_interval", //OSQPInt - "adaptive_rho_fraction", //OSQPFloat - "adaptive_rho_tolerance", //OSQPFloat - "max_iter", //OSQPInt - "eps_abs", //OSQPFloat - "eps_rel", //OSQPFloat - "eps_prim_inf", //OSQPFloat - "eps_dual_inf", //OSQPFloat - "scaled_termination", //OSQPInt - "check_termination", //OSQPInt - "time_limit", //OSQPFloat - "delta", //OSQPFloat - "polish_refine_iter", //OSQPInt - }; - -#define NEW_SETTINGS_TOL (1e-10) - -// wrapper class for all osqp data and settings -class OsqpData -{ -public: - OsqpData() : solver(NULL){} - OSQPSolver * solver; -}; - -// internal utility functions -OSQPSolver* initializeOSQPSolver(); -void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len); -void setToNaN(double* arr_out, OSQPInt len); -void copyMxStructToSettings(const mxArray*, OSQPSettings*); -void copyUpdatedSettingsToWork(const mxArray*, OSQPSolver*); -//void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len); //DELETE HERE -void freeCscMatrix(OSQPCscMatrix* M); -OSQPInt* copyToOSQPIntVector(mwIndex * vecData, OSQPInt numel); -OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel); -OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel); -mxArray* copyInfoToMxStruct(OSQPInfo* info); -mxArray* copySettingsToMxStruct(OSQPSettings* settings); - - -void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) -{ - OsqpData* osqpData; - //OSQPSolver* osqpSolver = NULL; - // Exitflag - OSQPInt exitflag = 0; - // Static string for static methods - char stat_string[64]; - // Get the command string - char cmd[64]; - if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) - mexErrMsgTxt("First input should be a command string less than 64 characters long."); - // new object - if (!strcmp("new", cmd)) { - // Check parameters - if (nlhs != 1){ - mexErrMsgTxt("New: One output expected."); - } - // Return a handle to a new C++ wrapper instance - osqpData = new OsqpData; - //osqpData->solver = initializeOSQPSolver(); - osqpData->solver = NULL; - plhs[0] = convertPtr2Mat(osqpData); - return; - } - - // Check for a second input, which should be the class instance handle or string 'static' - if (nrhs < 2) - mexErrMsgTxt("Second input should be a class instance handle or the string 'static'."); - - if(mxGetString(prhs[1], stat_string, sizeof(stat_string))){ - // If we are dealing with non-static methods, get the class instance pointer from the second input - osqpData = convertMat2Ptr(prhs[1]); - } else { - if (strcmp("static", stat_string)){ - mexErrMsgTxt("Second argument for static functions is string 'static'"); - } - } - // delete the object and its data - if (!strcmp("delete", cmd)) { - - osqp_cleanup(osqpData->solver); - destroyObject(prhs[1]); - // Warn if other commands were ignored - if (nlhs != 0 || nrhs != 2) - mexWarnMsgTxt("Delete: Unexpected arguments ignored."); - return; - } - - // report the current settings - if (!strcmp("current_settings", cmd)) { - //throw an error if this is called before solver is configured - if(!osqpData->solver) mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); - if(!osqpData->solver->settings){ - mexErrMsgTxt("Solver settings is uninitialized. No settings have been configured."); - } - //report the current settings - plhs[0] = copySettingsToMxStruct(osqpData->solver->settings); - return; - } - - // write_settings - if (!strcmp("update_settings", cmd)) { - //overwrite the current settings. Mex function is responsible - //for disallowing overwrite of selected settings after initialization, - //and for all error checking - //throw an error if this is called before solver is configured - if(!osqpData->solver){ - mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); - } - - copyUpdatedSettingsToWork(prhs[2],osqpData->solver); - return; - } - - // report the default settings - if (!strcmp("default_settings", cmd)) { - // Warn if other commands were ignored - if (nrhs > 2) - mexWarnMsgTxt("Default settings: unexpected number of arguments."); - - - //Create a Settings structure in default form and report the results - //Useful for external solver packages (e.g. Yalmip) that want to - //know which solver settings are supported - OSQPSettings* defaults = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); - osqp_set_default_settings(defaults); - plhs[0] = copySettingsToMxStruct(defaults); - mxFree(defaults); - return; - } - - // setup - if (!strcmp("setup", cmd)) { - //throw an error if this is called more than once - if(osqpData->solver){ - mexErrMsgTxt("Solver is already initialized with problem data."); - } - //Create data and settings containers - OSQPSettings* settings = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); - - // handle the problem data first. Matlab-side - // class wrapper is responsible for ensuring that - // P and A are sparse matrices, everything - // else is a dense vector and all inputs are - // of compatible dimension - - // Get mxArrays - const mxArray* P = prhs[4]; - const mxArray* q = prhs[5]; - const mxArray* A = prhs[6]; - const mxArray* l = prhs[7]; - const mxArray* u = prhs[8]; - - - OSQPInt dataN = (OSQPInt)mxGetScalar(prhs[2]); - OSQPInt dataM = (OSQPInt)mxGetScalar(prhs[3]); - OSQPFloat* dataQ = copyToOSQPFloatVector(mxGetPr(q), dataN); - OSQPFloat* dataL = copyToOSQPFloatVector(mxGetPr(l), dataM); - OSQPFloat* dataU = copyToOSQPFloatVector(mxGetPr(u), dataM); - - // Matrix P: nnz = P->p[n] - OSQPInt * Pp = (OSQPInt*)copyToOSQPIntVector(mxGetJc(P), dataN + 1); - OSQPInt * Pi = (OSQPInt*)copyToOSQPIntVector(mxGetIr(P), Pp[dataN]); - OSQPFloat * Px = copyToOSQPFloatVector(mxGetPr(P), Pp[dataN]); - OSQPCscMatrix* dataP = new OSQPCscMatrix; - csc_set_data(dataP, dataN, dataN, Pp[dataN], Px, Pi, Pp); - - // Matrix A: nnz = A->p[n] - OSQPInt* Ap = (OSQPInt*)copyToOSQPIntVector(mxGetJc(A), dataN + 1); - OSQPInt* Ai = (OSQPInt*)copyToOSQPIntVector(mxGetIr(A), Ap[dataN]); - OSQPFloat * Ax = copyToOSQPFloatVector(mxGetPr(A), Ap[dataN]); - OSQPCscMatrix* dataA = new OSQPCscMatrix; - csc_set_data(dataA, dataM, dataN, Ap[dataN], Ax, Ai, Ap); - - // Create Settings - const mxArray* mxSettings = prhs[9]; - if(mxIsEmpty(mxSettings)){ - // use defaults - osqp_set_default_settings(settings); - } else { - //populate settings structure from mxArray input - copyMxStructToSettings(mxSettings, settings); - } - - // Setup workspace - //exitflag = osqp_setup(&(osqpData->work), data, settings); - exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); - //cleanup temporary structures - // Data - if (Px) free(Px); - if (Pi) free(Pi); - if (Pp) free(Pp); - if (Ax) free(Ax); - if (Ai) free(Ai); - if (Ap) free(Ap); - if (dataQ) free(dataQ); - if (dataL) free(dataL); - if (dataU) free(dataU); - if (dataP) free(dataP); - if (dataA) free(dataA); - // Settings - if (settings) mxFree(settings); - - // Report error (if any) - if(exitflag){ - mexErrMsgTxt("Invalid problem setup"); - } - - return; - - } - - // get #constraints and variables - if (!strcmp("get_dimensions", cmd)) { - - //throw an error if this is called before solver is configured - if(!osqpData->solver){ - mexErrMsgTxt("Solver has not been initialized."); - } - OSQPInt n, m; - osqp_get_dimensions(osqpData->solver, &m, &n); - plhs[0] = mxCreateDoubleScalar(n); - plhs[1] = mxCreateDoubleScalar(m); - - return; - } - - // report the version - if (!strcmp("version", cmd)) { - - plhs[0] = mxCreateString(osqp_version()); - - return; - } - - // update linear cost and bounds - if (!strcmp("update", cmd)) { - - //throw an error if this is called before solver is configured - if(!osqpData->solver){ - mexErrMsgTxt("Solver has not been initialized."); - } - - // Fill q, l, u - const mxArray *q = prhs[2]; - const mxArray *l = prhs[3]; - const mxArray *u = prhs[4]; - const mxArray *Px = prhs[5]; - const mxArray *Px_idx = prhs[6]; - const mxArray *Ax = prhs[8]; - const mxArray *Ax_idx = prhs[9]; - - int Px_n, Ax_n; - Px_n = mxGetScalar(prhs[7]); - Ax_n = mxGetScalar(prhs[10]); - - // Copy vectors to ensure they are cast as OSQPFloat - OSQPFloat *q_vec = NULL; - OSQPFloat *l_vec = NULL; - OSQPFloat *u_vec = NULL; - OSQPFloat *Px_vec = NULL; - OSQPFloat *Ax_vec = NULL; - OSQPInt *Px_idx_vec = NULL; - OSQPInt *Ax_idx_vec = NULL; - - OSQPInt n, m; - osqp_get_dimensions(osqpData->solver, &m, &n); - if(!mxIsEmpty(q)){ - q_vec = copyToOSQPFloatVector(mxGetPr(q), n); - } - if(!mxIsEmpty(l)){ - l_vec = copyToOSQPFloatVector(mxGetPr(l), m); - } - if(!mxIsEmpty(u)){ - u_vec = copyToOSQPFloatVector(mxGetPr(u), m); - } - if(!mxIsEmpty(Px)){ - Px_vec = copyToOSQPFloatVector(mxGetPr(Px), Px_n); - } - if(!mxIsEmpty(Ax)){ - Ax_vec = copyToOSQPFloatVector(mxGetPr(Ax), Ax_n); - } - if(!mxIsEmpty(Px_idx)){ - Px_idx_vec = copyDoubleToOSQPIntVector(mxGetPr(Px_idx), Px_n); - } - if(!mxIsEmpty(Ax_idx)){ - Ax_idx_vec = copyDoubleToOSQPIntVector(mxGetPr(Ax_idx), Ax_n); - } - - if (!exitflag && (!mxIsEmpty(q) || !mxIsEmpty(l) || !mxIsEmpty(u))) { - exitflag = osqp_update_data_vec(osqpData->solver, q_vec, l_vec, u_vec); - if (exitflag) exitflag=1; - } - - if (!exitflag && (!mxIsEmpty(Px) || !mxIsEmpty(Ax))) { - exitflag = osqp_update_data_mat(osqpData->solver, Px_vec, Px_idx_vec, Px_n, Ax_vec, Ax_idx_vec, Ax_n); - if (exitflag) exitflag=2; - } - - - // Free vectors - if(!mxIsEmpty(q)) free(q_vec); - if(!mxIsEmpty(l)) free(l_vec); - if(!mxIsEmpty(u)) free(u_vec); - if(!mxIsEmpty(Px)) free(Px_vec); - if(!mxIsEmpty(Ax)) free(Ax_vec); - if(!mxIsEmpty(Px_idx)) free(Px_idx_vec); - if(!mxIsEmpty(Ax_idx)) free(Ax_idx_vec); - - // Report errors (if any) - switch (exitflag) { - case 1: - mexErrMsgTxt("Data update error!"); - case 2: - mexErrMsgTxt("Matrix update error!"); - } - - return; - } - - if (!strcmp("warm_start", cmd) || !strcmp("warm_start_x", cmd) || !strcmp("warm_start_y", cmd)) { - - //throw an error if this is called before solver is configured - if(!osqpData->solver){ - mexErrMsgTxt("Solver has not been initialized."); - } - - // Fill x and y - const mxArray *x = NULL; - const mxArray *y = NULL; - if (!strcmp("warm_start", cmd)) { - x = prhs[2]; - y = prhs[3]; - } - else if (!strcmp("warm_start_x", cmd)) { - x = prhs[2]; - y = NULL; - } - - else if (!strcmp("warm_start_y", cmd)) { - x = NULL; - y = prhs[2]; - } - - // Copy vectors to ensure they are cast as OSQPFloat - OSQPFloat *x_vec = NULL; - OSQPFloat *y_vec = NULL; - - OSQPInt n, m; - osqp_get_dimensions(osqpData->solver, &m, &n); - if(!mxIsEmpty(x)){ - x_vec = copyToOSQPFloatVector(mxGetPr(x),n); - } - if(!mxIsEmpty(y)){ - y_vec = copyToOSQPFloatVector(mxGetPr(y),m); - } - - // Warm start x and y - osqp_warm_start(osqpData->solver, x_vec, y_vec); - - // Free vectors - if(!x_vec) free(x_vec); - if(!y_vec) free(y_vec); - - return; - } - - // SOLVE - if (!strcmp("solve", cmd)) { - if (nlhs != 5 || nrhs != 2){ - mexErrMsgTxt("Solve : wrong number of inputs / outputs"); - } - if(!osqpData->solver){ - mexErrMsgTxt("No problem data has been given."); - } - // solve the problem - osqp_solve(osqpData->solver); - - OSQPInt n, m; - osqp_get_dimensions(osqpData->solver, &m, &n); - // Allocate space for solution - // primal variables - plhs[0] = mxCreateDoubleMatrix(n,1,mxREAL); - // dual variables - plhs[1] = mxCreateDoubleMatrix(m,1,mxREAL); - // primal infeasibility certificate - plhs[2] = mxCreateDoubleMatrix(m,1,mxREAL); - // dual infeasibility certificate - plhs[3] = mxCreateDoubleMatrix(n,1,mxREAL); - - //copy results to mxArray outputs - //assume that five outputs will always - //be returned to matlab-side class wrapper - if ((osqpData->solver->info->status_val != OSQP_PRIMAL_INFEASIBLE) && - (osqpData->solver->info->status_val != OSQP_DUAL_INFEASIBLE)){ - - //primal and dual solutions - castToDoubleArr(osqpData->solver->solution->x, mxGetPr(plhs[0]), n); - castToDoubleArr(osqpData->solver->solution->y, mxGetPr(plhs[1]), m); - - //infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[2]), m); - setToNaN(mxGetPr(plhs[3]), n); - - } else if (osqpData->solver->info->status_val == OSQP_PRIMAL_INFEASIBLE || - osqpData->solver->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE){ //primal infeasible - - //primal and dual solutions -> NaN values - setToNaN(mxGetPr(plhs[0]), n); - setToNaN(mxGetPr(plhs[1]), m); - - //primal infeasibility certificates - castToDoubleArr(osqpData->solver->solution->prim_inf_cert, mxGetPr(plhs[2]), m); - - //dual infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[3]), n); - - // Set objective value to infinity - osqpData->solver->info->obj_val = mxGetInf(); - - } else { //dual infeasible - - //primal and dual solutions -> NaN values - setToNaN(mxGetPr(plhs[0]), n); - setToNaN(mxGetPr(plhs[1]), m); - - //primal infeasibility certificates -> NaN values - setToNaN(mxGetPr(plhs[2]), m); - - //dual infeasibility certificates - castToDoubleArr(osqpData->solver->solution->dual_inf_cert, mxGetPr(plhs[3]), n); - - // Set objective value to -infinity - osqpData->solver->info->obj_val = -mxGetInf(); - } - - if (osqpData->solver->info->status_val == OSQP_NON_CVX) { - osqpData->solver->info->obj_val = mxGetNaN(); - } - - plhs[4] = copyInfoToMxStruct(osqpData->solver->info); // Info structure - - return; - } - - if (!strcmp("constant", cmd)) { // Return solver constants - - char constant[32]; - mxGetString(prhs[2], constant, sizeof(constant)); - - if (!strcmp("OSQP_INFTY", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_INFTY); - return; - } - if (!strcmp("OSQP_NAN", constant)){ - plhs[0] = mxCreateDoubleScalar(mxGetNaN()); - return; - } - - if (!strcmp("OSQP_SOLVED", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_SOLVED); - return; - } - - if (!strcmp("OSQP_SOLVED_INACCURATE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_SOLVED_INACCURATE); - return; - } - - if (!strcmp("OSQP_UNSOLVED", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_UNSOLVED); - return; - } - - if (!strcmp("OSQP_PRIMAL_INFEASIBLE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_PRIMAL_INFEASIBLE); - return; - } - - if (!strcmp("OSQP_PRIMAL_INFEASIBLE_INACCURATE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_PRIMAL_INFEASIBLE_INACCURATE); - return; - } - - if (!strcmp("OSQP_DUAL_INFEASIBLE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_DUAL_INFEASIBLE); - return; - } - - if (!strcmp("OSQP_DUAL_INFEASIBLE_INACCURATE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_DUAL_INFEASIBLE_INACCURATE); - return; - } - - if (!strcmp("OSQP_MAX_ITER_REACHED", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_MAX_ITER_REACHED); - return; - } - - if (!strcmp("OSQP_NON_CVX", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_NON_CVX); - return; - } - - if (!strcmp("OSQP_TIME_LIMIT_REACHED", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_TIME_LIMIT_REACHED); - return; - } - - // Linear system solvers - if (!strcmp("QDLDL_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(QDLDL_SOLVER); - return; - } - - if (!strcmp("OSQP_UNKNOWN_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_UNKNOWN_SOLVER); - return; - } - - if (!strcmp("OSQP_DIRECT_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_DIRECT_SOLVER); - return; - } - - if (!strcmp("OSQP_INDIRECT_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_INDIRECT_SOLVER); - return; - } - - - mexErrMsgTxt("Constant not recognized."); - - return; - } - - // Got here, so command not recognized - mexErrMsgTxt("Command not recognized."); -} - -/** - * This function dynamically allocates OSQPSovler and sets all the properties of OSQPSolver to NULL. - * WARNING: The memory allocated here (OSQPSolver*) needs to be freed. - * WARNING: Any dynamically allocated pointers must be freed before calling this function. -*/ -OSQPSolver* initializeOSQPSolver() { - OSQPSolver* osqpSolver = new OSQPSolver; - osqpSolver->info = NULL; - osqpSolver->settings = NULL; - osqpSolver->solution = NULL; - osqpSolver->work = NULL; - //osqp_set_default_settings(osqpSolver->settings); - return osqpSolver; -} - -//Dynamically creates a OSQPFloat vector copy of the input. -//Returns an empty pointer if vecData is NULL -OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel){ - if (!vecData) return NULL; - - //This needs to be freed! - OSQPFloat* out = (OSQPFloat*)malloc(numel * sizeof(OSQPFloat)); - - //copy data - for(OSQPInt i=0; i < numel; i++){ - out[i] = (OSQPFloat)vecData[i]; - } - return out; -} - -//Dynamically creates a OSQPInt vector copy of the input. -OSQPInt* copyToOSQPIntVector(mwIndex* vecData, OSQPInt numel){ - // This memory needs to be freed! - OSQPInt* out = (OSQPInt*)malloc(numel * sizeof(OSQPInt)); - - //copy data - for(OSQPInt i=0; i < numel; i++){ - out[i] = (OSQPInt)vecData[i]; - } - return out; - -} - -//Dynamically copies a double vector to OSQPInt. -OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel){ - // This memory needs to be freed! - OSQPInt* out = (OSQPInt*)malloc(numel * sizeof(OSQPInt)); - - //copy data - for(OSQPInt i=0; i < numel; i++){ - out[i] = (OSQPInt)vecData[i]; - } - return out; - -} - -/* DELETE HERE -void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len) { - for (OSQPInt i = 0; i < len; i++) { - arr_out[i] = (double)arr[i]; - } -}*/ - -//This function frees the memory allocated in an OSQPCscMatrix M -void freeCscMatrix(OSQPCscMatrix* M) { - if (!M) return; - if (M->p) free(M->p); - if (M->i) free(M->i); - if (M->x) free(M->x); - free(M); -} - -void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len) { - for (OSQPInt i = 0; i < len; i++) { - arr_out[i] = (double)arr[i]; - } -} - -void setToNaN(double* arr_out, OSQPInt len){ - OSQPInt i; - for (i = 0; i < len; i++) { - arr_out[i] = mxGetNaN(); - } -} - -mxArray* copyInfoToMxStruct(OSQPInfo* info){ - - //create mxArray with the right number of fields - int nfields = sizeof(OSQP_INFO_FIELDS) / sizeof(OSQP_INFO_FIELDS[0]); - mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_INFO_FIELDS); - - //map the OSQP_INFO fields one at a time into mxArrays - //matlab all numeric values as doubles - mxSetField(mxPtr, 0, "iter", mxCreateDoubleScalar(info->iter)); - mxSetField(mxPtr, 0, "status", mxCreateString(info->status)); - mxSetField(mxPtr, 0, "status_val", mxCreateDoubleScalar(info->status_val)); - mxSetField(mxPtr, 0, "status_polish", mxCreateDoubleScalar(info->status_polish)); - mxSetField(mxPtr, 0, "obj_val", mxCreateDoubleScalar(info->obj_val)); - mxSetField(mxPtr, 0, "prim_res", mxCreateDoubleScalar(info->prim_res)); - mxSetField(mxPtr, 0, "dual_res", mxCreateDoubleScalar(info->dual_res)); - - mxSetField(mxPtr, 0, "setup_time", mxCreateDoubleScalar(info->setup_time)); - mxSetField(mxPtr, 0, "solve_time", mxCreateDoubleScalar(info->solve_time)); - mxSetField(mxPtr, 0, "update_time", mxCreateDoubleScalar(info->update_time)); - mxSetField(mxPtr, 0, "polish_time", mxCreateDoubleScalar(info->polish_time)); - mxSetField(mxPtr, 0, "run_time", mxCreateDoubleScalar(info->run_time)); - - - mxSetField(mxPtr, 0, "rho_updates", mxCreateDoubleScalar(info->rho_updates)); - mxSetField(mxPtr, 0, "rho_estimate", mxCreateDoubleScalar(info->rho_estimate)); - - - return mxPtr; - -} - -mxArray* copySettingsToMxStruct(OSQPSettings* settings){ - - int nfields = sizeof(OSQP_SETTINGS_FIELDS) / sizeof(OSQP_SETTINGS_FIELDS[0]); - mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_SETTINGS_FIELDS); - - //map the OSQP_SETTINGS fields one at a time into mxArrays - //matlab handles everything as a double - mxSetField(mxPtr, 0, "rho", mxCreateDoubleScalar(settings->rho)); - mxSetField(mxPtr, 0, "sigma", mxCreateDoubleScalar(settings->sigma)); - mxSetField(mxPtr, 0, "scaling", mxCreateDoubleScalar(settings->scaling)); - mxSetField(mxPtr, 0, "adaptive_rho", mxCreateDoubleScalar(settings->adaptive_rho)); - mxSetField(mxPtr, 0, "adaptive_rho_interval", mxCreateDoubleScalar(settings->adaptive_rho_interval)); - mxSetField(mxPtr, 0, "adaptive_rho_tolerance", mxCreateDoubleScalar(settings->adaptive_rho_tolerance)); - mxSetField(mxPtr, 0, "adaptive_rho_fraction", mxCreateDoubleScalar(settings->adaptive_rho_fraction)); - mxSetField(mxPtr, 0, "max_iter", mxCreateDoubleScalar(settings->max_iter)); - mxSetField(mxPtr, 0, "eps_abs", mxCreateDoubleScalar(settings->eps_abs)); - mxSetField(mxPtr, 0, "eps_rel", mxCreateDoubleScalar(settings->eps_rel)); - mxSetField(mxPtr, 0, "eps_prim_inf", mxCreateDoubleScalar(settings->eps_prim_inf)); - mxSetField(mxPtr, 0, "eps_dual_inf", mxCreateDoubleScalar(settings->eps_dual_inf)); - mxSetField(mxPtr, 0, "alpha", mxCreateDoubleScalar(settings->alpha)); - mxSetField(mxPtr, 0, "linsys_solver", mxCreateDoubleScalar(settings->linsys_solver)); - mxSetField(mxPtr, 0, "delta", mxCreateDoubleScalar(settings->delta)); - mxSetField(mxPtr, 0, "polish_refine_iter", mxCreateDoubleScalar(settings->polish_refine_iter)); - mxSetField(mxPtr, 0, "verbose", mxCreateDoubleScalar(settings->verbose)); - mxSetField(mxPtr, 0, "scaled_termination", mxCreateDoubleScalar(settings->scaled_termination)); - mxSetField(mxPtr, 0, "check_termination", mxCreateDoubleScalar(settings->check_termination)); - mxSetField(mxPtr, 0, "warm_starting", mxCreateDoubleScalar(settings->warm_starting)); - mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); - mxSetField(mxPtr, 0, "device", mxCreateDoubleScalar(settings->device)); - mxSetField(mxPtr, 0, "polishing", mxCreateDoubleScalar(settings->polishing)); - mxSetField(mxPtr, 0, "rho_is_vec", mxCreateDoubleScalar(settings->rho_is_vec)); - mxSetField(mxPtr, 0, "cg_max_iter", mxCreateDoubleScalar(settings->cg_max_iter)); - mxSetField(mxPtr, 0, "cg_tol_reduction", mxCreateDoubleScalar(settings->cg_tol_reduction)); - mxSetField(mxPtr, 0, "cg_tol_fraction", mxCreateDoubleScalar(settings->cg_tol_fraction)); - mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); - return mxPtr; -} - - -// ====================================================================== - -void copyMxStructToSettings(const mxArray* mxPtr, OSQPSettings* settings){ - - //this function assumes that only a complete and validated structure - //will be passed. matlab mex-side function is responsible for checking - //structure validity - - //map the OSQP_SETTINGS fields one at a time into mxArrays - //matlab handles everything as a double - settings->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); - settings->sigma = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "sigma")); - settings->scaling = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaling")); - settings->adaptive_rho = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho")); - settings->adaptive_rho_interval = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_interval")); - settings->adaptive_rho_tolerance = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_tolerance")); - settings->adaptive_rho_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_fraction")); - settings->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); - settings->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); - settings->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); - settings->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - settings->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - settings->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); - settings->linsys_solver = (enum osqp_linsys_solver_type) (OSQPInt) mxGetScalar(mxGetField(mxPtr, 0, "linsys_solver")); - settings->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); - settings->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); - settings->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); - settings->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); - settings->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); - settings->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); - settings->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); - settings->device = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "device")); - settings->polishing = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polishing")); - settings->rho_is_vec = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "rho_is_vec")); - settings->cg_max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_max_iter")); - settings->cg_tol_reduction = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_reduction")); - settings->cg_tol_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_fraction")); - settings->cg_precond = (osqp_precond_type) (OSQPInt) (mxGetField(mxPtr, 0, "cg_precond")); -} - -void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ - - OSQPInt exitflag; - //TODO (Amit): Update this - OSQPSettings* update_template = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); - if (!update_template) mexErrMsgTxt("Failed to allocate temporary OSQPSettings object."); - - update_template->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); - update_template->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); - update_template->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); - update_template->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf")); - update_template->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - update_template->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); - update_template->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); - update_template->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); - update_template->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); - update_template->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); - update_template->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); - update_template->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); - update_template->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); - update_template->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); - - osqp_update_settings(osqpSolver, update_template); - if (update_template) free(update_template); +#include "mex.h" +#include "matrix.h" +#include "osqp_mex.hpp" +#include "osqp.h" + +//c_int is replaced with OSQPInt +//c_float is replaced with OSQPFloat + +//TODO: Check if this definition is required, and maybe replace it with: +// enum linsys_solver_type { QDLDL_SOLVER, MKL_PARDISO_SOLVER }; +#define QDLDL_SOLVER 0 //Based on the previous API + +// all of the OSQP_INFO fieldnames as strings +const char* OSQP_INFO_FIELDS[] = {"status", //char* + "status_val", //OSQPInt + "status_polish", //OSQPInt + "obj_val", //OSQPFloat + "prim_res", //OSQPFloat + "dual_res", //OSQPFloat + "iter", //OSQPInt + "rho_updates", //OSQPInt + "rho_estimate", //OSQPFloat + "setup_time", //OSQPFloat + "solve_time", //OSQPFloat + "update_time", //OSQPFloat + "polish_time", //OSQPFloat + "run_time", //OSQPFloat + }; + +const char* OSQP_SETTINGS_FIELDS[] = {"device", //OSQPInt + "linsys_solver", //enum osqp_linsys_solver_type + "verbose", //OSQPInt + "warm_starting", //OSQPInt + "scaling", //OSQPInt + "polishing", //OSQPInt + "rho", //OSQPFloat + "rho_is_vec", //OSQPInt + "sigma", //OSQPFloat + "alpha", //OSQPFloat + "cg_max_iter", //OSQPInt + "cg_tol_reduction", //OSQPInt + "cg_tol_fraction", //OSQPFloat + "cg_precond", //osqp_precond_type + "adaptive_rho", //OSQPInt + "adaptive_rho_interval", //OSQPInt + "adaptive_rho_fraction", //OSQPFloat + "adaptive_rho_tolerance", //OSQPFloat + "max_iter", //OSQPInt + "eps_abs", //OSQPFloat + "eps_rel", //OSQPFloat + "eps_prim_inf", //OSQPFloat + "eps_dual_inf", //OSQPFloat + "scaled_termination", //OSQPInt + "check_termination", //OSQPInt + "time_limit", //OSQPFloat + "delta", //OSQPFloat + "polish_refine_iter", //OSQPInt + }; + +#define NEW_SETTINGS_TOL (1e-10) + +// wrapper class for all osqp data and settings +class OsqpData +{ +public: + OsqpData() : solver(NULL){} + OSQPSolver * solver; +}; + +// internal utility functions +OSQPSolver* initializeOSQPSolver(); +void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len); +void setToNaN(double* arr_out, OSQPInt len); +void copyMxStructToSettings(const mxArray*, OSQPSettings*); +void copyUpdatedSettingsToWork(const mxArray*, OSQPSolver*); +//void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len); //DELETE HERE +void freeCscMatrix(OSQPCscMatrix* M); +OSQPInt* copyToOSQPIntVector(mwIndex * vecData, OSQPInt numel); +OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel); +OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel); +mxArray* copyInfoToMxStruct(OSQPInfo* info); +mxArray* copySettingsToMxStruct(OSQPSettings* settings); + + +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) +{ + OsqpData* osqpData; + //OSQPSolver* osqpSolver = NULL; + // Exitflag + OSQPInt exitflag = 0; + // Static string for static methods + char stat_string[64]; + // Get the command string + char cmd[64]; + if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) + mexErrMsgTxt("First input should be a command string less than 64 characters long."); + // new object + if (!strcmp("new", cmd)) { + // Check parameters + if (nlhs != 1){ + mexErrMsgTxt("New: One output expected."); + } + // Return a handle to a new C++ wrapper instance + osqpData = new OsqpData; + //osqpData->solver = initializeOSQPSolver(); + osqpData->solver = NULL; + plhs[0] = convertPtr2Mat(osqpData); + return; + } + + // Check for a second input, which should be the class instance handle or string 'static' + if (nrhs < 2) + mexErrMsgTxt("Second input should be a class instance handle or the string 'static'."); + + if(mxGetString(prhs[1], stat_string, sizeof(stat_string))){ + // If we are dealing with non-static methods, get the class instance pointer from the second input + osqpData = convertMat2Ptr(prhs[1]); + } else { + if (strcmp("static", stat_string)){ + mexErrMsgTxt("Second argument for static functions is string 'static'"); + } + } + // delete the object and its data + if (!strcmp("delete", cmd)) { + + osqp_cleanup(osqpData->solver); + destroyObject(prhs[1]); + // Warn if other commands were ignored + if (nlhs != 0 || nrhs != 2) + mexWarnMsgTxt("Delete: Unexpected arguments ignored."); + return; + } + + // report the current settings + if (!strcmp("current_settings", cmd)) { + //throw an error if this is called before solver is configured + if(!osqpData->solver) mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + if(!osqpData->solver->settings){ + mexErrMsgTxt("Solver settings is uninitialized. No settings have been configured."); + } + //report the current settings + plhs[0] = copySettingsToMxStruct(osqpData->solver->settings); + return; + } + + // write_settings + if (!strcmp("update_settings", cmd)) { + //overwrite the current settings. Mex function is responsible + //for disallowing overwrite of selected settings after initialization, + //and for all error checking + //throw an error if this is called before solver is configured + if(!osqpData->solver){ + mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + } + + copyUpdatedSettingsToWork(prhs[2],osqpData->solver); + return; + } + + // report the default settings + if (!strcmp("default_settings", cmd)) { + // Warn if other commands were ignored + if (nrhs > 2) + mexWarnMsgTxt("Default settings: unexpected number of arguments."); + + + //Create a Settings structure in default form and report the results + //Useful for external solver packages (e.g. Yalmip) that want to + //know which solver settings are supported + OSQPSettings* defaults = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); + osqp_set_default_settings(defaults); + plhs[0] = copySettingsToMxStruct(defaults); + mxFree(defaults); + return; + } + + // setup + if (!strcmp("setup", cmd)) { + //throw an error if this is called more than once + if(osqpData->solver){ + mexErrMsgTxt("Solver is already initialized with problem data."); + } + //Create data and settings containers + OSQPSettings* settings = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); + + // handle the problem data first. Matlab-side + // class wrapper is responsible for ensuring that + // P and A are sparse matrices, everything + // else is a dense vector and all inputs are + // of compatible dimension + + // Get mxArrays + const mxArray* P = prhs[4]; + const mxArray* q = prhs[5]; + const mxArray* A = prhs[6]; + const mxArray* l = prhs[7]; + const mxArray* u = prhs[8]; + + + OSQPInt dataN = (OSQPInt)mxGetScalar(prhs[2]); + OSQPInt dataM = (OSQPInt)mxGetScalar(prhs[3]); + OSQPFloat* dataQ = copyToOSQPFloatVector(mxGetPr(q), dataN); + OSQPFloat* dataL = copyToOSQPFloatVector(mxGetPr(l), dataM); + OSQPFloat* dataU = copyToOSQPFloatVector(mxGetPr(u), dataM); + + // Matrix P: nnz = P->p[n] + OSQPInt * Pp = (OSQPInt*)copyToOSQPIntVector(mxGetJc(P), dataN + 1); + OSQPInt * Pi = (OSQPInt*)copyToOSQPIntVector(mxGetIr(P), Pp[dataN]); + OSQPFloat * Px = copyToOSQPFloatVector(mxGetPr(P), Pp[dataN]); + OSQPCscMatrix* dataP = new OSQPCscMatrix; + csc_set_data(dataP, dataN, dataN, Pp[dataN], Px, Pi, Pp); + + // Matrix A: nnz = A->p[n] + OSQPInt* Ap = (OSQPInt*)copyToOSQPIntVector(mxGetJc(A), dataN + 1); + OSQPInt* Ai = (OSQPInt*)copyToOSQPIntVector(mxGetIr(A), Ap[dataN]); + OSQPFloat * Ax = copyToOSQPFloatVector(mxGetPr(A), Ap[dataN]); + OSQPCscMatrix* dataA = new OSQPCscMatrix; + csc_set_data(dataA, dataM, dataN, Ap[dataN], Ax, Ai, Ap); + + // Create Settings + const mxArray* mxSettings = prhs[9]; + if(mxIsEmpty(mxSettings)){ + // use defaults + osqp_set_default_settings(settings); + } else { + //populate settings structure from mxArray input + copyMxStructToSettings(mxSettings, settings); + } + + // Setup workspace + //exitflag = osqp_setup(&(osqpData->work), data, settings); + exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); + //cleanup temporary structures + // Data + if (Px) free(Px); + if (Pi) free(Pi); + if (Pp) free(Pp); + if (Ax) free(Ax); + if (Ai) free(Ai); + if (Ap) free(Ap); + if (dataQ) free(dataQ); + if (dataL) free(dataL); + if (dataU) free(dataU); + if (dataP) free(dataP); + if (dataA) free(dataA); + // Settings + if (settings) mxFree(settings); + + // Report error (if any) + if(exitflag){ + mexErrMsgTxt("Invalid problem setup"); + } + + return; + + } + + // get #constraints and variables + if (!strcmp("get_dimensions", cmd)) { + + //throw an error if this is called before solver is configured + if(!osqpData->solver){ + mexErrMsgTxt("Solver has not been initialized."); + } + OSQPInt n, m; + osqp_get_dimensions(osqpData->solver, &m, &n); + plhs[0] = mxCreateDoubleScalar(n); + plhs[1] = mxCreateDoubleScalar(m); + + return; + } + + // report the version + if (!strcmp("version", cmd)) { + + plhs[0] = mxCreateString(osqp_version()); + + return; + } + + // update linear cost and bounds + if (!strcmp("update", cmd)) { + + //throw an error if this is called before solver is configured + if(!osqpData->solver){ + mexErrMsgTxt("Solver has not been initialized."); + } + + // Fill q, l, u + const mxArray *q = prhs[2]; + const mxArray *l = prhs[3]; + const mxArray *u = prhs[4]; + const mxArray *Px = prhs[5]; + const mxArray *Px_idx = prhs[6]; + const mxArray *Ax = prhs[8]; + const mxArray *Ax_idx = prhs[9]; + + int Px_n, Ax_n; + Px_n = mxGetScalar(prhs[7]); + Ax_n = mxGetScalar(prhs[10]); + + // Copy vectors to ensure they are cast as OSQPFloat + OSQPFloat *q_vec = NULL; + OSQPFloat *l_vec = NULL; + OSQPFloat *u_vec = NULL; + OSQPFloat *Px_vec = NULL; + OSQPFloat *Ax_vec = NULL; + OSQPInt *Px_idx_vec = NULL; + OSQPInt *Ax_idx_vec = NULL; + + OSQPInt n, m; + osqp_get_dimensions(osqpData->solver, &m, &n); + if(!mxIsEmpty(q)){ + q_vec = copyToOSQPFloatVector(mxGetPr(q), n); + } + if(!mxIsEmpty(l)){ + l_vec = copyToOSQPFloatVector(mxGetPr(l), m); + } + if(!mxIsEmpty(u)){ + u_vec = copyToOSQPFloatVector(mxGetPr(u), m); + } + if(!mxIsEmpty(Px)){ + Px_vec = copyToOSQPFloatVector(mxGetPr(Px), Px_n); + } + if(!mxIsEmpty(Ax)){ + Ax_vec = copyToOSQPFloatVector(mxGetPr(Ax), Ax_n); + } + if(!mxIsEmpty(Px_idx)){ + Px_idx_vec = copyDoubleToOSQPIntVector(mxGetPr(Px_idx), Px_n); + } + if(!mxIsEmpty(Ax_idx)){ + Ax_idx_vec = copyDoubleToOSQPIntVector(mxGetPr(Ax_idx), Ax_n); + } + + if (!exitflag && (!mxIsEmpty(q) || !mxIsEmpty(l) || !mxIsEmpty(u))) { + exitflag = osqp_update_data_vec(osqpData->solver, q_vec, l_vec, u_vec); + if (exitflag) exitflag=1; + } + + if (!exitflag && (!mxIsEmpty(Px) || !mxIsEmpty(Ax))) { + exitflag = osqp_update_data_mat(osqpData->solver, Px_vec, Px_idx_vec, Px_n, Ax_vec, Ax_idx_vec, Ax_n); + if (exitflag) exitflag=2; + } + + + // Free vectors + if(!mxIsEmpty(q)) free(q_vec); + if(!mxIsEmpty(l)) free(l_vec); + if(!mxIsEmpty(u)) free(u_vec); + if(!mxIsEmpty(Px)) free(Px_vec); + if(!mxIsEmpty(Ax)) free(Ax_vec); + if(!mxIsEmpty(Px_idx)) free(Px_idx_vec); + if(!mxIsEmpty(Ax_idx)) free(Ax_idx_vec); + + // Report errors (if any) + switch (exitflag) { + case 1: + mexErrMsgTxt("Data update error!"); + case 2: + mexErrMsgTxt("Matrix update error!"); + } + + return; + } + + if (!strcmp("warm_start", cmd) || !strcmp("warm_start_x", cmd) || !strcmp("warm_start_y", cmd)) { + + //throw an error if this is called before solver is configured + if(!osqpData->solver){ + mexErrMsgTxt("Solver has not been initialized."); + } + + // Fill x and y + const mxArray *x = NULL; + const mxArray *y = NULL; + if (!strcmp("warm_start", cmd)) { + x = prhs[2]; + y = prhs[3]; + } + else if (!strcmp("warm_start_x", cmd)) { + x = prhs[2]; + y = NULL; + } + + else if (!strcmp("warm_start_y", cmd)) { + x = NULL; + y = prhs[2]; + } + + // Copy vectors to ensure they are cast as OSQPFloat + OSQPFloat *x_vec = NULL; + OSQPFloat *y_vec = NULL; + + OSQPInt n, m; + osqp_get_dimensions(osqpData->solver, &m, &n); + if(!mxIsEmpty(x)){ + x_vec = copyToOSQPFloatVector(mxGetPr(x),n); + } + if(!mxIsEmpty(y)){ + y_vec = copyToOSQPFloatVector(mxGetPr(y),m); + } + + // Warm start x and y + osqp_warm_start(osqpData->solver, x_vec, y_vec); + + // Free vectors + if(!x_vec) free(x_vec); + if(!y_vec) free(y_vec); + + return; + } + + // SOLVE + if (!strcmp("solve", cmd)) { + if (nlhs != 5 || nrhs != 2){ + mexErrMsgTxt("Solve : wrong number of inputs / outputs"); + } + if(!osqpData->solver){ + mexErrMsgTxt("No problem data has been given."); + } + // solve the problem + osqp_solve(osqpData->solver); + + OSQPInt n, m; + osqp_get_dimensions(osqpData->solver, &m, &n); + // Allocate space for solution + // primal variables + plhs[0] = mxCreateDoubleMatrix(n,1,mxREAL); + // dual variables + plhs[1] = mxCreateDoubleMatrix(m,1,mxREAL); + // primal infeasibility certificate + plhs[2] = mxCreateDoubleMatrix(m,1,mxREAL); + // dual infeasibility certificate + plhs[3] = mxCreateDoubleMatrix(n,1,mxREAL); + + //copy results to mxArray outputs + //assume that five outputs will always + //be returned to matlab-side class wrapper + if ((osqpData->solver->info->status_val != OSQP_PRIMAL_INFEASIBLE) && + (osqpData->solver->info->status_val != OSQP_DUAL_INFEASIBLE)){ + + //primal and dual solutions + castToDoubleArr(osqpData->solver->solution->x, mxGetPr(plhs[0]), n); + castToDoubleArr(osqpData->solver->solution->y, mxGetPr(plhs[1]), m); + + //infeasibility certificates -> NaN values + setToNaN(mxGetPr(plhs[2]), m); + setToNaN(mxGetPr(plhs[3]), n); + + } else if (osqpData->solver->info->status_val == OSQP_PRIMAL_INFEASIBLE || + osqpData->solver->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE){ //primal infeasible + + //primal and dual solutions -> NaN values + setToNaN(mxGetPr(plhs[0]), n); + setToNaN(mxGetPr(plhs[1]), m); + + //primal infeasibility certificates + castToDoubleArr(osqpData->solver->solution->prim_inf_cert, mxGetPr(plhs[2]), m); + + //dual infeasibility certificates -> NaN values + setToNaN(mxGetPr(plhs[3]), n); + + // Set objective value to infinity + osqpData->solver->info->obj_val = mxGetInf(); + + } else { //dual infeasible + + //primal and dual solutions -> NaN values + setToNaN(mxGetPr(plhs[0]), n); + setToNaN(mxGetPr(plhs[1]), m); + + //primal infeasibility certificates -> NaN values + setToNaN(mxGetPr(plhs[2]), m); + + //dual infeasibility certificates + castToDoubleArr(osqpData->solver->solution->dual_inf_cert, mxGetPr(plhs[3]), n); + + // Set objective value to -infinity + osqpData->solver->info->obj_val = -mxGetInf(); + } + + if (osqpData->solver->info->status_val == OSQP_NON_CVX) { + osqpData->solver->info->obj_val = mxGetNaN(); + } + + plhs[4] = copyInfoToMxStruct(osqpData->solver->info); // Info structure + + return; + } + + if (!strcmp("constant", cmd)) { // Return solver constants + + char constant[32]; + mxGetString(prhs[2], constant, sizeof(constant)); + + if (!strcmp("OSQP_INFTY", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_INFTY); + return; + } + if (!strcmp("OSQP_NAN", constant)){ + plhs[0] = mxCreateDoubleScalar(mxGetNaN()); + return; + } + + if (!strcmp("OSQP_SOLVED", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_SOLVED); + return; + } + + if (!strcmp("OSQP_SOLVED_INACCURATE", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_SOLVED_INACCURATE); + return; + } + + if (!strcmp("OSQP_UNSOLVED", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_UNSOLVED); + return; + } + + if (!strcmp("OSQP_PRIMAL_INFEASIBLE", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_PRIMAL_INFEASIBLE); + return; + } + + if (!strcmp("OSQP_PRIMAL_INFEASIBLE_INACCURATE", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_PRIMAL_INFEASIBLE_INACCURATE); + return; + } + + if (!strcmp("OSQP_DUAL_INFEASIBLE", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_DUAL_INFEASIBLE); + return; + } + + if (!strcmp("OSQP_DUAL_INFEASIBLE_INACCURATE", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_DUAL_INFEASIBLE_INACCURATE); + return; + } + + if (!strcmp("OSQP_MAX_ITER_REACHED", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_MAX_ITER_REACHED); + return; + } + + if (!strcmp("OSQP_NON_CVX", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_NON_CVX); + return; + } + + if (!strcmp("OSQP_TIME_LIMIT_REACHED", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_TIME_LIMIT_REACHED); + return; + } + + // Linear system solvers + if (!strcmp("QDLDL_SOLVER", constant)){ + plhs[0] = mxCreateDoubleScalar(QDLDL_SOLVER); + return; + } + + if (!strcmp("OSQP_UNKNOWN_SOLVER", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_UNKNOWN_SOLVER); + return; + } + + if (!strcmp("OSQP_DIRECT_SOLVER", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_DIRECT_SOLVER); + return; + } + + if (!strcmp("OSQP_INDIRECT_SOLVER", constant)){ + plhs[0] = mxCreateDoubleScalar(OSQP_INDIRECT_SOLVER); + return; + } + + + mexErrMsgTxt("Constant not recognized."); + + return; + } + + // Got here, so command not recognized + mexErrMsgTxt("Command not recognized."); +} + +/** + * This function dynamically allocates OSQPSovler and sets all the properties of OSQPSolver to NULL. + * WARNING: The memory allocated here (OSQPSolver*) needs to be freed. + * WARNING: Any dynamically allocated pointers must be freed before calling this function. +*/ +OSQPSolver* initializeOSQPSolver() { + OSQPSolver* osqpSolver = new OSQPSolver; + osqpSolver->info = NULL; + osqpSolver->settings = NULL; + osqpSolver->solution = NULL; + osqpSolver->work = NULL; + //osqp_set_default_settings(osqpSolver->settings); + return osqpSolver; +} + +//Dynamically creates a OSQPFloat vector copy of the input. +//Returns an empty pointer if vecData is NULL +OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel){ + if (!vecData) return NULL; + + //This needs to be freed! + OSQPFloat* out = (OSQPFloat*)malloc(numel * sizeof(OSQPFloat)); + + //copy data + for(OSQPInt i=0; i < numel; i++){ + out[i] = (OSQPFloat)vecData[i]; + } + return out; +} + +//Dynamically creates a OSQPInt vector copy of the input. +OSQPInt* copyToOSQPIntVector(mwIndex* vecData, OSQPInt numel){ + // This memory needs to be freed! + OSQPInt* out = (OSQPInt*)malloc(numel * sizeof(OSQPInt)); + + //copy data + for(OSQPInt i=0; i < numel; i++){ + out[i] = (OSQPInt)vecData[i]; + } + return out; + +} + +//Dynamically copies a double vector to OSQPInt. +OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel){ + // This memory needs to be freed! + OSQPInt* out = (OSQPInt*)malloc(numel * sizeof(OSQPInt)); + + //copy data + for(OSQPInt i=0; i < numel; i++){ + out[i] = (OSQPInt)vecData[i]; + } + return out; + +} + +/* DELETE HERE +void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len) { + for (OSQPInt i = 0; i < len; i++) { + arr_out[i] = (double)arr[i]; + } +}*/ + +//This function frees the memory allocated in an OSQPCscMatrix M +void freeCscMatrix(OSQPCscMatrix* M) { + if (!M) return; + if (M->p) free(M->p); + if (M->i) free(M->i); + if (M->x) free(M->x); + free(M); +} + +void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len) { + for (OSQPInt i = 0; i < len; i++) { + arr_out[i] = (double)arr[i]; + } +} + +void setToNaN(double* arr_out, OSQPInt len){ + OSQPInt i; + for (i = 0; i < len; i++) { + arr_out[i] = mxGetNaN(); + } +} + +mxArray* copyInfoToMxStruct(OSQPInfo* info){ + + //create mxArray with the right number of fields + int nfields = sizeof(OSQP_INFO_FIELDS) / sizeof(OSQP_INFO_FIELDS[0]); + mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_INFO_FIELDS); + + //map the OSQP_INFO fields one at a time into mxArrays + //matlab all numeric values as doubles + mxSetField(mxPtr, 0, "iter", mxCreateDoubleScalar(info->iter)); + mxSetField(mxPtr, 0, "status", mxCreateString(info->status)); + mxSetField(mxPtr, 0, "status_val", mxCreateDoubleScalar(info->status_val)); + mxSetField(mxPtr, 0, "status_polish", mxCreateDoubleScalar(info->status_polish)); + mxSetField(mxPtr, 0, "obj_val", mxCreateDoubleScalar(info->obj_val)); + mxSetField(mxPtr, 0, "prim_res", mxCreateDoubleScalar(info->prim_res)); + mxSetField(mxPtr, 0, "dual_res", mxCreateDoubleScalar(info->dual_res)); + + mxSetField(mxPtr, 0, "setup_time", mxCreateDoubleScalar(info->setup_time)); + mxSetField(mxPtr, 0, "solve_time", mxCreateDoubleScalar(info->solve_time)); + mxSetField(mxPtr, 0, "update_time", mxCreateDoubleScalar(info->update_time)); + mxSetField(mxPtr, 0, "polish_time", mxCreateDoubleScalar(info->polish_time)); + mxSetField(mxPtr, 0, "run_time", mxCreateDoubleScalar(info->run_time)); + + + mxSetField(mxPtr, 0, "rho_updates", mxCreateDoubleScalar(info->rho_updates)); + mxSetField(mxPtr, 0, "rho_estimate", mxCreateDoubleScalar(info->rho_estimate)); + + + return mxPtr; + +} + +mxArray* copySettingsToMxStruct(OSQPSettings* settings){ + + int nfields = sizeof(OSQP_SETTINGS_FIELDS) / sizeof(OSQP_SETTINGS_FIELDS[0]); + mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_SETTINGS_FIELDS); + + //map the OSQP_SETTINGS fields one at a time into mxArrays + //matlab handles everything as a double + mxSetField(mxPtr, 0, "rho", mxCreateDoubleScalar(settings->rho)); + mxSetField(mxPtr, 0, "sigma", mxCreateDoubleScalar(settings->sigma)); + mxSetField(mxPtr, 0, "scaling", mxCreateDoubleScalar(settings->scaling)); + mxSetField(mxPtr, 0, "adaptive_rho", mxCreateDoubleScalar(settings->adaptive_rho)); + mxSetField(mxPtr, 0, "adaptive_rho_interval", mxCreateDoubleScalar(settings->adaptive_rho_interval)); + mxSetField(mxPtr, 0, "adaptive_rho_tolerance", mxCreateDoubleScalar(settings->adaptive_rho_tolerance)); + mxSetField(mxPtr, 0, "adaptive_rho_fraction", mxCreateDoubleScalar(settings->adaptive_rho_fraction)); + mxSetField(mxPtr, 0, "max_iter", mxCreateDoubleScalar(settings->max_iter)); + mxSetField(mxPtr, 0, "eps_abs", mxCreateDoubleScalar(settings->eps_abs)); + mxSetField(mxPtr, 0, "eps_rel", mxCreateDoubleScalar(settings->eps_rel)); + mxSetField(mxPtr, 0, "eps_prim_inf", mxCreateDoubleScalar(settings->eps_prim_inf)); + mxSetField(mxPtr, 0, "eps_dual_inf", mxCreateDoubleScalar(settings->eps_dual_inf)); + mxSetField(mxPtr, 0, "alpha", mxCreateDoubleScalar(settings->alpha)); + mxSetField(mxPtr, 0, "linsys_solver", mxCreateDoubleScalar(settings->linsys_solver)); + mxSetField(mxPtr, 0, "delta", mxCreateDoubleScalar(settings->delta)); + mxSetField(mxPtr, 0, "polish_refine_iter", mxCreateDoubleScalar(settings->polish_refine_iter)); + mxSetField(mxPtr, 0, "verbose", mxCreateDoubleScalar(settings->verbose)); + mxSetField(mxPtr, 0, "scaled_termination", mxCreateDoubleScalar(settings->scaled_termination)); + mxSetField(mxPtr, 0, "check_termination", mxCreateDoubleScalar(settings->check_termination)); + mxSetField(mxPtr, 0, "warm_starting", mxCreateDoubleScalar(settings->warm_starting)); + mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); + mxSetField(mxPtr, 0, "device", mxCreateDoubleScalar(settings->device)); + mxSetField(mxPtr, 0, "polishing", mxCreateDoubleScalar(settings->polishing)); + mxSetField(mxPtr, 0, "rho_is_vec", mxCreateDoubleScalar(settings->rho_is_vec)); + mxSetField(mxPtr, 0, "cg_max_iter", mxCreateDoubleScalar(settings->cg_max_iter)); + mxSetField(mxPtr, 0, "cg_tol_reduction", mxCreateDoubleScalar(settings->cg_tol_reduction)); + mxSetField(mxPtr, 0, "cg_tol_fraction", mxCreateDoubleScalar(settings->cg_tol_fraction)); + mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); + return mxPtr; +} + + +// ====================================================================== + +void copyMxStructToSettings(const mxArray* mxPtr, OSQPSettings* settings){ + + //this function assumes that only a complete and validated structure + //will be passed. matlab mex-side function is responsible for checking + //structure validity + + //map the OSQP_SETTINGS fields one at a time into mxArrays + //matlab handles everything as a double + settings->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); + settings->sigma = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "sigma")); + settings->scaling = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaling")); + settings->adaptive_rho = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho")); + settings->adaptive_rho_interval = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_interval")); + settings->adaptive_rho_tolerance = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_tolerance")); + settings->adaptive_rho_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_fraction")); + settings->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); + settings->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); + settings->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); + settings->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); + settings->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); + settings->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); + settings->linsys_solver = (enum osqp_linsys_solver_type) (OSQPInt) mxGetScalar(mxGetField(mxPtr, 0, "linsys_solver")); + settings->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); + settings->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); + settings->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); + settings->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); + settings->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); + settings->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); + settings->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); + settings->device = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "device")); + settings->polishing = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polishing")); + settings->rho_is_vec = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "rho_is_vec")); + settings->cg_max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_max_iter")); + settings->cg_tol_reduction = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_reduction")); + settings->cg_tol_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_fraction")); + settings->cg_precond = (osqp_precond_type) (OSQPInt) (mxGetField(mxPtr, 0, "cg_precond")); +} + +void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ + + OSQPInt exitflag; + //TODO (Amit): Update this + OSQPSettings* update_template = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); + if (!update_template) mexErrMsgTxt("Failed to allocate temporary OSQPSettings object."); + + update_template->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); + update_template->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); + update_template->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); + update_template->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf")); + update_template->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); + update_template->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); + update_template->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); + update_template->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); + update_template->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); + update_template->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); + update_template->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); + update_template->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); + update_template->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); + update_template->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); + + osqp_update_settings(osqpSolver, update_template); + if (update_template) free(update_template); } \ No newline at end of file diff --git a/osqp_mex.hpp b/c_sources/osqp_mex.hpp similarity index 96% rename from osqp_mex.hpp rename to c_sources/osqp_mex.hpp index b3f9d33..0c2ff05 100755 --- a/osqp_mex.hpp +++ b/c_sources/osqp_mex.hpp @@ -1,54 +1,54 @@ -#ifndef __OSQP_MEX_HPP__ -#define __OSQP_MEX_HPP__ -#include "mex.h" -#include "error.h" -#include -#include -#include -#include - -#define OSQP_MEX_SIGNATURE 0x271C1A7A -template class osqp_mex_handle -{ -public: - osqp_mex_handle(base *ptr) : ptr_m(ptr), name_m(typeid(base).name()) { signature_m = OSQP_MEX_SIGNATURE; } - ~osqp_mex_handle() { signature_m = 0; delete ptr_m; } - bool isValid() { return ((signature_m == OSQP_MEX_SIGNATURE) && !strcmp(name_m.c_str(), typeid(base).name())); } - base *ptr() { return ptr_m; } - -private: - uint32_t signature_m; - std::string name_m; - base *ptr_m; -}; - -template inline mxArray *convertPtr2Mat(base *ptr) -{ - mexLock(); - mxArray *out = mxCreateNumericMatrix(1, 1, mxUINT64_CLASS, mxREAL); - *((uint64_t *)mxGetData(out)) = reinterpret_cast(new osqp_mex_handle(ptr)); - return out; -} - -template inline osqp_mex_handle *convertMat2HandlePtr(const mxArray *in) -{ - if (mxGetNumberOfElements(in) != 1 || mxGetClassID(in) != mxUINT64_CLASS || mxIsComplex(in)) - mexErrMsgTxt("Input must be a real uint64 scalar."); - osqp_mex_handle *ptr = reinterpret_cast *>(*((uint64_t *)mxGetData(in))); - if (!ptr->isValid()) - mexErrMsgTxt("Handle not valid."); - return ptr; -} - -template inline base *convertMat2Ptr(const mxArray *in) -{ - return convertMat2HandlePtr(in)->ptr(); -} - -template inline void destroyObject(const mxArray *in) -{ - delete convertMat2HandlePtr(in); - mexUnlock(); -} - -#endif // __OSQP_MEX_HPP__ +#ifndef __OSQP_MEX_HPP__ +#define __OSQP_MEX_HPP__ +#include "mex.h" +#include "error.h" +#include +#include +#include +#include + +#define OSQP_MEX_SIGNATURE 0x271C1A7A +template class osqp_mex_handle +{ +public: + osqp_mex_handle(base *ptr) : ptr_m(ptr), name_m(typeid(base).name()) { signature_m = OSQP_MEX_SIGNATURE; } + ~osqp_mex_handle() { signature_m = 0; delete ptr_m; } + bool isValid() { return ((signature_m == OSQP_MEX_SIGNATURE) && !strcmp(name_m.c_str(), typeid(base).name())); } + base *ptr() { return ptr_m; } + +private: + uint32_t signature_m; + std::string name_m; + base *ptr_m; +}; + +template inline mxArray *convertPtr2Mat(base *ptr) +{ + mexLock(); + mxArray *out = mxCreateNumericMatrix(1, 1, mxUINT64_CLASS, mxREAL); + *((uint64_t *)mxGetData(out)) = reinterpret_cast(new osqp_mex_handle(ptr)); + return out; +} + +template inline osqp_mex_handle *convertMat2HandlePtr(const mxArray *in) +{ + if (mxGetNumberOfElements(in) != 1 || mxGetClassID(in) != mxUINT64_CLASS || mxIsComplex(in)) + mexErrMsgTxt("Input must be a real uint64 scalar."); + osqp_mex_handle *ptr = reinterpret_cast *>(*((uint64_t *)mxGetData(in))); + if (!ptr->isValid()) + mexErrMsgTxt("Handle not valid."); + return ptr; +} + +template inline base *convertMat2Ptr(const mxArray *in) +{ + return convertMat2HandlePtr(in)->ptr(); +} + +template inline void destroyObject(const mxArray *in) +{ + delete convertMat2HandlePtr(in); + mexUnlock(); +} + +#endif // __OSQP_MEX_HPP__ diff --git a/osqp_sources b/c_sources/osqp_sources similarity index 100% rename from osqp_sources rename to c_sources/osqp_sources From 8bb91ad4c88e5f8780332f08a998fd380da6750f Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Wed, 15 Nov 2023 16:55:45 +0000 Subject: [PATCH 10/36] Refactor mex build system to simplify it --- .gitignore | 1 + c_sources/CMakeLists.txt | 56 +++++++ c_sources/interrupt_matlab.c | 25 +++ c_sources/memory_matlab.h | 22 +++ c_sources/osqp_mex.cpp | 1 + make_osqp.m | 312 +++++++++-------------------------- 6 files changed, 187 insertions(+), 230 deletions(-) create mode 100644 c_sources/CMakeLists.txt create mode 100644 c_sources/interrupt_matlab.c create mode 100644 c_sources/memory_matlab.h diff --git a/.gitignore b/.gitignore index 225d671..c3d34c7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # ------------------------------------------------------------------- # Out folder out/ +c_sources/build/ # Prerequisites *.d diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt new file mode 100644 index 0000000..0f92c5c --- /dev/null +++ b/c_sources/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required( VERSION 3.18 ) + +project( osqp-matlab ) + + +# Find the MATLAB installation directory and various settings for it +find_package( Matlab REQUIRED COMPONENTS MEX_COMPILER) + +message( STATUS "Matlab root is " ${Matlab_ROOT_DIR} ) + +include_directories( ${Matlab_INCLUDE_DIRS} ) + +# Add debug symbols and optimizations to the build +set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2" ) +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O2" ) + +# Insist on the pre-2018 complex data API so that mxGetPr will work correctly +add_compile_definitions( MATLAB_MEXSRC_RELEASE=R2017b ) +message( STATUS "Using Matlab pre-2018a API for mxGetPr compatibility" ) + +# Some parts of the main libraries need to know we are building for MATLAB +# So pass a definition to let them know +add_compile_definitions( MATLAB ) + +# Configure the settings of the OSQP library +set( OSQP_ALGEBRA_BACKEND "builtin" ) +set( OSQP_USE_FLOAT OFF ) +set( OSQP_CODEGEN ON ) +set( OSQP_ENABLE_DERIVATIVES ON ) +set( OSQP_ENABLE_PRINTING ON ) +set( OSQP_ENABLE_PROFILING ON ) +set( OSQP_ENABLE_INTERRUPT ON ) + +# Configure the build outputs for the OSQP library +set( OSQP_BUILD_STATIC_LIB ON ) +set( OSQP_BUILD_SHARED_LIB OFF ) +set( OSQP_BUILD_UNITTESTS OFF ) +set( OSQP_BUILD_DEMO_EXE OFF ) + +# Add the custom MATLAB memory handling +set( OSQP_CUSTOM_MEMORY "memory_matlab.h" ) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + +# Actually pull in the OSQP targets +add_subdirectory( osqp_sources ) + +# Add OSQP include directories that are needed +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/osqp_sources/include/public + ${CMAKE_CURRENT_SOURCE_DIR}/osqp_sources/include/private ) + +matlab_add_mex( NAME osqp_mex + SRC ${CMAKE_CURRENT_SOURCE_DIR}/osqp_mex.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/interrupt_matlab.c + LINK_TO osqpstatic + # Force compilation in the traditional C API (equivalent to the -R2017b flag) + R2017b ) \ No newline at end of file diff --git a/c_sources/interrupt_matlab.c b/c_sources/interrupt_matlab.c new file mode 100644 index 0000000..1a43135 --- /dev/null +++ b/c_sources/interrupt_matlab.c @@ -0,0 +1,25 @@ +/* + * Implements interrupt handling using ctrl-c for MATLAB mex files. + */ + +#include "interrupt.h" + +#include + +/* No header file available here; define the prototypes ourselves */ +bool utIsInterruptPending(void); +bool utSetInterruptEnabled(bool); + +static int istate; + +void osqp_start_interrupt_listener(void) { + istate = utSetInterruptEnabled(1); +} + +void osqp_end_interrupt_listener(void) { + utSetInterruptEnabled(istate); +} + +int osqp_is_interrupted(void) { + return utIsInterruptPending(); +} diff --git a/c_sources/memory_matlab.h b/c_sources/memory_matlab.h new file mode 100644 index 0000000..c8276b8 --- /dev/null +++ b/c_sources/memory_matlab.h @@ -0,0 +1,22 @@ +/* Memory managment for MATLAB */ +#include "mex.h" + +static void* c_calloc(size_t num, size_t size) { + void *m = mxCalloc(num, size); + mexMakeMemoryPersistent(m); + return m; +} + +static void* c_malloc(size_t size) { + void *m = mxMalloc(size); + mexMakeMemoryPersistent(m); + return m; +} + +static void* c_realloc(void *ptr, size_t size) { + void *m = mxRealloc(ptr, size); + mexMakeMemoryPersistent(m); + return m; +} + +#define c_free mxFree \ No newline at end of file diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index c6b838d..a1e56ee 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -2,6 +2,7 @@ #include "matrix.h" #include "osqp_mex.hpp" #include "osqp.h" +#include "memory_matlab.h" //c_int is replaced with OSQPInt //c_float is replaced with OSQPFloat diff --git a/make_osqp.m b/make_osqp.m index a267b99..9e0ae13 100644 --- a/make_osqp.m +++ b/make_osqp.m @@ -9,17 +9,12 @@ function make_osqp(varargin) % % {}, '' (empty string) or 'all': build all components and link. % -% 'osqp': builds the OSQP solver using CMake -% -% 'osqp_mex': builds the OSQP mex interface and links it to the OSQP -% library -% -% VARARGIN{1:NARGIN-1} specifies the optional flags passed to the compiler +% 'osqp_mex': builds the OSQP mex interface and the OSQP library % % Additional commands: % -% 'clean': delete all object files (.o and .obj) -% 'purge' : same as above, and also delete the mex files. +% 'clean': Delete all compiled files +% 'purge': Delete all compiled files and copied code generation files if( nargin == 0 ) @@ -31,7 +26,6 @@ function make_osqp(varargin) else what = varargin{nargin}; if(isempty(strfind(what, 'all')) && ... - isempty(strfind(what, 'osqp')) && ... isempty(strfind(what, 'osqp_mex')) && ... isempty(strfind(what, 'clean')) && ... isempty(strfind(what, 'purge'))) @@ -41,233 +35,101 @@ function make_osqp(varargin) verbose = ismember('-verbose', varargin); end -%% Try to unlock any pre-existing version of osqp_mex +%% Determine where the various files are all located +% Various parts of the build system +[makefile_path,~,~] = fileparts( which( 'make_osqp.m' ) ); +osqp_mex_src_dir = fullfile( makefile_path, 'c_sources' ); +osqp_mex_build_dir = fullfile( osqp_mex_src_dir, 'build' ); +osqp_cg_src_dir = fullfile( osqp_mex_build_dir, 'codegen_src' ); +osqp_cg_dest_dir = fullfile( makefile_path, 'codegen', 'sources' ); +% Determine where CMake should look for MATLAB +Matlab_ROOT = strrep( matlabroot, '\', '/' ); + +%% Try to unlock any pre-existing version of osqp_mex % this prevents compile errors if a user builds, runs osqp % and then tries to recompile if(mislocked('osqp_mex')) munlock('osqp_mex'); end +%% Configure, build and install the OSQP mex interface +if( any(strcmpi(what,'osqp_mex')) || any(strcmpi(what,'all')) ) + fprintf('Compiling OSQP solver mex interface...\n'); - -%% Basic compile commands - -% Get make and mex commands -make_cmd = 'cmake --build .'; -mex_cmd = sprintf('mex -O -silent'); -mex_libs = ''; - - -% Add arguments to cmake and mex compiler -cmake_args = '-DMATLAB=ON'; -mexoptflags = '-DMATLAB'; - -% Add specific generators for windows linux or mac -if (ispc) - cmake_args = sprintf('%s %s', cmake_args, '-G "MinGW Makefiles"'); -else - cmake_args = sprintf('%s %s', cmake_args, '-G "Unix Makefiles"'); -end - -% Pass Matlab root to cmake -Matlab_ROOT = strrep(matlabroot, '\', '/'); -cmake_args = sprintf('%s %s%s%s', cmake_args, ... - '-DMatlab_ROOT_DIR="', Matlab_ROOT, '"'); - -% Add parameters options to mex and cmake -% CTRLC -if (ispc) - ut = fullfile(matlabroot, 'extern', 'lib', computer('arch'), ... - 'mingw64', 'libut.lib'); - mex_libs = sprintf('%s "%s"', mex_libs, ut); -else - mex_libs = sprintf('%s %s', mex_libs, '-lut'); -end -% Shared library loading -if (isunix && ~ismac) - mex_libs = sprintf('%s %s', mex_libs, '-ldl'); -end - -% Add large arrays support if computer is 64 bit and a pre-2018 version -% Release R2018a corresponds to Matlab version 9.4 -if (~isempty(strfind(computer, '64')) && verLessThan('matlab', '9.4')) - mexoptflags = sprintf('%s %s', mexoptflags, '-largeArrayDims'); -end - -%Force Matlab to respect old-style usage of mxGetPr in releases after 2018a, -%which use interleaved complex data. Note that the -R2017b flag is badly -%named since it indicates that non-interleaved complex data model is being used; -%it is not really specific to the release year -if ~verLessThan('matlab', '9.4') - mexoptflags = sprintf('%s %s', mexoptflags, '-R2017b'); -end - - -% Set optimizer flag -if (~ispc) - mexoptflags = sprintf('%s %s', mexoptflags, 'COPTIMFLAGS=''-O3'''); -end - -% Set library extension -lib_ext = '.a'; -lib_name = sprintf('libosqpstatic%s', lib_ext); - - -% Set osqp directory and osqp_build directory -current_dir = pwd; -[makefile_path,~,~] = fileparts(which('make_osqp.m')); -osqp_dir = fullfile(makefile_path, 'osqp_sources'); -osqp_build_dir = fullfile(osqp_dir, 'build'); -qdldl_dir = fullfile(osqp_dir, 'algebra', '_common', 'lin_sys', 'qdldl'); -cg_sources_dir = fullfile('.', 'codegen', 'sources'); - -% Include directory -inc_dir = [ - fullfile(sprintf(' -I%s', osqp_dir), 'include', 'public'), ... - fullfile(sprintf(' -I%s', osqp_dir), 'include', 'private'), ... - fullfile(sprintf(' -I%s', osqp_dir), 'build', 'include', 'public'), ... - sprintf(' -I%s', qdldl_dir), ... - fullfile(sprintf(' -I%s', qdldl_dir), 'qdldl_sources', 'include')]; - - -%% OSQP Solver -if( any(strcmpi(what,'osqp')) || any(strcmpi(what,'all')) ) - fprintf('Compiling OSQP solver...'); - - % Create build directory and go inside - if exist(osqp_build_dir, 'dir') - rmdir(osqp_build_dir, 's'); + % Create build for the mex file and go inside + if exist( osqp_mex_build_dir, 'dir' ) + rmdir( osqp_mex_build_dir, 's' ); end - mkdir(osqp_build_dir); - cd(osqp_build_dir); + mkdir( osqp_mex_build_dir ); +% cd( osqp_mex_build_dir ); % Extend path for CMake mac (via Homebrew) PATH = getenv('PATH'); - if ((ismac) && (isempty(strfind(PATH, '/usr/local/bin')))) + if( (ismac) && (isempty(strfind(PATH, '/usr/local/bin'))) ) setenv('PATH', [PATH ':/usr/local/bin']); end - % Compile static library with CMake - [status, output] = system(sprintf('%s %s ..', 'cmake', cmake_args)); - if(status) - fprintf('\n'); - disp(output); - error('Error configuring CMake environment'); - elseif(verbose) - fprintf('\n'); - disp(output); + %% Configure CMake for the mex interface + fprintf(' Configuring...' ) + [status, output] = system( sprintf( 'cmake -B %s -S %s -DMatlab_ROOT_DIR=\"%s\"', osqp_mex_build_dir, osqp_mex_src_dir, Matlab_ROOT ), 'LD_LIBRARY_PATH', '' ); + if( status ) + fprintf( '\n' ); + disp( output ); + error( 'Error configuring CMake environment' ); + elseif( verbose ) + fprintf( '\n' ); + disp( output ); + else + fprintf( '\t\t\t\t\t[done]\n' ); end - [status, output] = system(sprintf('%s %s', make_cmd, '--target osqpstatic')); - if (status) - fprintf('\n'); - disp(output); - error('Error compiling OSQP'); - elseif(verbose) - fprintf('\n'); - disp(output); + %% Build the mex interface + fprintf( ' Building...') + [status, output] = system( sprintf( 'cmake --build %s', osqp_mex_build_dir ), 'LD_LIBRARY_PATH', '' ); + if( status ) + fprintf( '\n' ); + disp( output ); + error( 'Error compiling OSQP mex interface' ); + elseif( verbose ) + fprintf( '\n' ); + disp( output ); + else + fprintf( '\t\t\t\t\t\t[done]\n' ); end - - % Change directory back to matlab interface - cd(makefile_path); - - % Copy static library to current folder - lib_origin = fullfile(osqp_build_dir, 'out', lib_name); - copyfile(lib_origin, lib_name); - - fprintf('\t\t\t\t\t\t[done]\n'); - -end - -%% osqpmex -if( any(strcmpi(what,'osqp_mex')) || any(strcmpi(what,'all')) ) - % Compile interface - fprintf('Compiling and linking osqpmex...'); - - % Compile command - %cmd = sprintf('%s %s %s %s osqp_mex.cpp', mex_cmd, mexoptflags, inc_dir, lib_name); - cmd = sprintf('%s %s %s %s osqp_mex.cpp %s', ... - mex_cmd, mexoptflags, inc_dir, lib_name, mex_libs); - - % Compile - eval(cmd); - fprintf('\t\t\t\t\t[done]\n'); - -end - - -%% codegen -if( any(strcmpi(what,'codegen')) || any(strcmpi(what,'all')) ) - fprintf('Copying source files for codegen...'); - - % Copy C files - cg_src_dir = fullfile(cg_sources_dir, 'src'); - if ~exist(cg_src_dir, 'dir') - mkdir(cg_src_dir); - end - cdirs = {fullfile(osqp_dir, 'src'),... - fullfile(qdldl_dir),... - fullfile(qdldl_dir, 'qdldl_sources', 'src')}; - for j = 1:length(cdirs) - cfiles = dir(fullfile(cdirs{j},'*.c')); - for i = 1 : length(cfiles) - if ~any(strcmp(cfiles(i).name, {'cs.c', 'ctrlc.c', 'lin_sys.c', 'polish.c'})) - copyfile(fullfile(cdirs{j}, cfiles(i).name), ... - fullfile(cg_src_dir, cfiles(i).name)); - end - end + + %% Install various files + fprintf( ' Installing...' ) + + % Copy mex file to root directory for use + [err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'osqp_mex.mex*'], makefile_path ); + if( ~err ) + fprintf( '\n' ) + disp( errmsg ) + error( ' Error copying mex file' ) end - % Copy H files - cg_include_dir = fullfile(cg_sources_dir, 'include'); - if ~exist(cg_include_dir, 'dir') - mkdir(cg_include_dir); - end - hdirs = {fullfile(osqp_dir, 'include'),... - fullfile(qdldl_dir),... - fullfile(qdldl_dir, 'qdldl_sources', 'include')}; - for j = 1:length(hdirs) - hfiles = dir(fullfile(hdirs{j},'*.h')); - for i = 1 : length(hfiles) - if ~any(strcmp(hfiles(i).name, {'qdldl_types.h', 'osqp_configure.h', ... - 'cs.h', 'ctrlc.h', 'lin_sys.h', 'polish.h'})) - copyfile(fullfile(hdirs{j}, hfiles(i).name), ... - fullfile(cg_include_dir, hfiles(i).name)); - end - end + % Copy the code generation source files + % Create build for the mex file and go inside + if exist( osqp_cg_dest_dir, 'dir' ) + rmdir( osqp_cg_dest_dir, 's' ); end + mkdir( osqp_cg_dest_dir ); - % Copy configure files - cg_configure_dir = fullfile(cg_sources_dir, 'configure'); - if ~exist(cg_configure_dir, 'dir') - mkdir(cg_configure_dir); - end - configure_dirs = {fullfile(osqp_dir, 'configure'),... - fullfile(qdldl_dir, 'qdldl_sources', 'configure')}; - for j = 1:length(configure_dirs) - configure_files = dir(fullfile(configure_dirs{j},'*.h.in')); - for i = 1 : length(configure_files) - copyfile(fullfile(configure_dirs{j}, configure_files(i).name), ... - fullfile(cg_configure_dir, configure_files(i).name)); - end + [err, errmsg, ~] = copyfile( [osqp_cg_src_dir, filesep, '*'], osqp_cg_dest_dir ); + if( ~err ) + fprintf( '\n' ) + disp( errmsg ) + error( ' Error copying code generation source files' ) end - - % Copy cmake files - copyfile(fullfile(osqp_dir, 'src', 'CMakeLists.txt'), ... - fullfile(cg_src_dir, 'CMakeLists.txt')); - copyfile(fullfile(osqp_dir, 'include', 'CMakeLists.txt'), ... - fullfile(cg_include_dir, 'CMakeLists.txt')); - - fprintf('\t\t\t\t\t[done]\n'); + fprintf( '\t\t\t\t\t\t[done]\n' ); end - -%% clean +%% Clean and purge if( any(strcmpi(what,'clean')) || any(strcmpi(what,'purge')) ) - fprintf('Cleaning mex files and library...'); + fprintf('Cleaning OSQP mex files and build directory...'); % Delete mex file mexfiles = dir(['*.', mexext]); @@ -275,35 +137,25 @@ function make_osqp(varargin) delete(mexfiles(i).name); end - % Delete static library - lib_full_path = fullfile(makefile_path, lib_name); - if( exist(lib_full_path,'file') ) - delete(lib_full_path); + % Delete OSQP build directory + if exist(osqp_mex_build_dir, 'dir') + rmdir(osqp_mex_build_dir, 's'); end - fprintf('\t\t\t[done]\n'); -end - + fprintf('\t\t[done]\n'); -%% purge -if( any(strcmpi(what,'purge')) ) - fprintf('Cleaning OSQP build and codegen directories...'); + %% Purge only + if( any(strcmpi(what,'purge')) ) + fprintf('Cleaning OSQP codegen directories...'); - % Delete OSQP build directory - if exist(osqp_build_dir, 'dir') - rmdir(osqp_build_dir, 's'); - end + % Delete codegen files + if exist(osqp_cg_dest_dir, 'dir') + rmdir(osqp_cg_dest_dir, 's'); + end - % Delete codegen files - if exist(cg_sources_dir, 'dir') - rmdir(cg_sources_dir, 's'); + fprintf('\t\t\t[done]\n'); end - fprintf('\t\t[done]\n'); end - -%% Go back to the original directory -cd(current_dir); - end From 265ab653d3c2b2c126e9fc7b39332778c7cdf775 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Wed, 15 Nov 2023 16:56:10 +0000 Subject: [PATCH 11/36] Fix memory management in the OSQP mex interface --- c_sources/osqp_mex.cpp | 62 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index a1e56ee..2535302 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -208,14 +208,14 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPInt * Pp = (OSQPInt*)copyToOSQPIntVector(mxGetJc(P), dataN + 1); OSQPInt * Pi = (OSQPInt*)copyToOSQPIntVector(mxGetIr(P), Pp[dataN]); OSQPFloat * Px = copyToOSQPFloatVector(mxGetPr(P), Pp[dataN]); - OSQPCscMatrix* dataP = new OSQPCscMatrix; + OSQPCscMatrix* dataP = (OSQPCscMatrix*)c_calloc(1,sizeof(OSQPCscMatrix)); csc_set_data(dataP, dataN, dataN, Pp[dataN], Px, Pi, Pp); // Matrix A: nnz = A->p[n] OSQPInt* Ap = (OSQPInt*)copyToOSQPIntVector(mxGetJc(A), dataN + 1); OSQPInt* Ai = (OSQPInt*)copyToOSQPIntVector(mxGetIr(A), Ap[dataN]); OSQPFloat * Ax = copyToOSQPFloatVector(mxGetPr(A), Ap[dataN]); - OSQPCscMatrix* dataA = new OSQPCscMatrix; + OSQPCscMatrix* dataA = (OSQPCscMatrix*)c_calloc(1,sizeof(OSQPCscMatrix)); csc_set_data(dataA, dataM, dataN, Ap[dataN], Ax, Ai, Ap); // Create Settings @@ -233,19 +233,19 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); //cleanup temporary structures // Data - if (Px) free(Px); - if (Pi) free(Pi); - if (Pp) free(Pp); - if (Ax) free(Ax); - if (Ai) free(Ai); - if (Ap) free(Ap); - if (dataQ) free(dataQ); - if (dataL) free(dataL); - if (dataU) free(dataU); - if (dataP) free(dataP); - if (dataA) free(dataA); + if (Px) c_free(Px); + if (Pi) c_free(Pi); + if (Pp) c_free(Pp); + if (Ax) c_free(Ax); + if (Ai) c_free(Ai); + if (Ap) c_free(Ap); + if (dataQ) c_free(dataQ); + if (dataL) c_free(dataL); + if (dataU) c_free(dataU); + if (dataP) c_free(dataP); + if (dataA) c_free(dataA); // Settings - if (settings) mxFree(settings); + if (settings) c_free(settings); // Report error (if any) if(exitflag){ @@ -345,13 +345,13 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // Free vectors - if(!mxIsEmpty(q)) free(q_vec); - if(!mxIsEmpty(l)) free(l_vec); - if(!mxIsEmpty(u)) free(u_vec); - if(!mxIsEmpty(Px)) free(Px_vec); - if(!mxIsEmpty(Ax)) free(Ax_vec); - if(!mxIsEmpty(Px_idx)) free(Px_idx_vec); - if(!mxIsEmpty(Ax_idx)) free(Ax_idx_vec); + if(!mxIsEmpty(q)) c_free(q_vec); + if(!mxIsEmpty(l)) c_free(l_vec); + if(!mxIsEmpty(u)) c_free(u_vec); + if(!mxIsEmpty(Px)) c_free(Px_vec); + if(!mxIsEmpty(Ax)) c_free(Ax_vec); + if(!mxIsEmpty(Px_idx)) c_free(Px_idx_vec); + if(!mxIsEmpty(Ax_idx)) c_free(Ax_idx_vec); // Report errors (if any) switch (exitflag) { @@ -405,8 +405,8 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) osqp_warm_start(osqpData->solver, x_vec, y_vec); // Free vectors - if(!x_vec) free(x_vec); - if(!y_vec) free(y_vec); + if(!x_vec) c_free(x_vec); + if(!y_vec) c_free(y_vec); return; } @@ -605,7 +605,7 @@ OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel){ if (!vecData) return NULL; //This needs to be freed! - OSQPFloat* out = (OSQPFloat*)malloc(numel * sizeof(OSQPFloat)); + OSQPFloat* out = (OSQPFloat*)c_malloc(numel * sizeof(OSQPFloat)); //copy data for(OSQPInt i=0; i < numel; i++){ @@ -617,7 +617,7 @@ OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel){ //Dynamically creates a OSQPInt vector copy of the input. OSQPInt* copyToOSQPIntVector(mwIndex* vecData, OSQPInt numel){ // This memory needs to be freed! - OSQPInt* out = (OSQPInt*)malloc(numel * sizeof(OSQPInt)); + OSQPInt* out = (OSQPInt*)c_malloc(numel * sizeof(OSQPInt)); //copy data for(OSQPInt i=0; i < numel; i++){ @@ -630,7 +630,7 @@ OSQPInt* copyToOSQPIntVector(mwIndex* vecData, OSQPInt numel){ //Dynamically copies a double vector to OSQPInt. OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel){ // This memory needs to be freed! - OSQPInt* out = (OSQPInt*)malloc(numel * sizeof(OSQPInt)); + OSQPInt* out = (OSQPInt*)c_malloc(numel * sizeof(OSQPInt)); //copy data for(OSQPInt i=0; i < numel; i++){ @@ -650,10 +650,10 @@ void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len) { //This function frees the memory allocated in an OSQPCscMatrix M void freeCscMatrix(OSQPCscMatrix* M) { if (!M) return; - if (M->p) free(M->p); - if (M->i) free(M->i); - if (M->x) free(M->x); - free(M); + if (M->p) c_free(M->p); + if (M->i) c_free(M->i); + if (M->x) c_free(M->x); + c_free(M); } void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len) { @@ -802,5 +802,5 @@ void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ update_template->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); osqp_update_settings(osqpSolver, update_template); - if (update_template) free(update_template); + if (update_template) c_free(update_template); } \ No newline at end of file From 6ba8078999c58d20a218ca6cf16f5c8074febad9 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Wed, 15 Nov 2023 17:08:03 +0000 Subject: [PATCH 12/36] Gate debug information on GNU compilers only --- c_sources/CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt index 0f92c5c..5ed2cd4 100644 --- a/c_sources/CMakeLists.txt +++ b/c_sources/CMakeLists.txt @@ -10,9 +10,11 @@ message( STATUS "Matlab root is " ${Matlab_ROOT_DIR} ) include_directories( ${Matlab_INCLUDE_DIRS} ) -# Add debug symbols and optimizations to the build -set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2" ) -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O2" ) +if( CMAKE_COMPILER_IS_GNUCXX ) + # Add debug symbols and optimizations to the build + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O2" ) +endif() # Insist on the pre-2018 complex data API so that mxGetPr will work correctly add_compile_definitions( MATLAB_MEXSRC_RELEASE=R2017b ) From 48fbbc43352d5f8f5b24bc50ff93ba9c8e919d1f Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Wed, 15 Nov 2023 17:21:17 +0000 Subject: [PATCH 13/36] Add linking against the ut library --- c_sources/CMakeLists.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt index 5ed2cd4..58169e2 100644 --- a/c_sources/CMakeLists.txt +++ b/c_sources/CMakeLists.txt @@ -17,7 +17,7 @@ if( CMAKE_COMPILER_IS_GNUCXX ) endif() # Insist on the pre-2018 complex data API so that mxGetPr will work correctly -add_compile_definitions( MATLAB_MEXSRC_RELEASE=R2017b ) + add_compile_definitions( MATLAB_MEXSRC_RELEASE=R2017b ) message( STATUS "Using Matlab pre-2018a API for mxGetPr compatibility" ) # Some parts of the main libraries need to know we are building for MATLAB @@ -50,9 +50,18 @@ add_subdirectory( osqp_sources ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/osqp_sources/include/public ${CMAKE_CURRENT_SOURCE_DIR}/osqp_sources/include/private ) +# Interrupt support requires the libut library +if( WIN32 ) + get_filename_component( Matlab_EXTERN_LIB_DIR ${Matlab_MEX_LIBRARY} DIRECTORY ) + set( UT_LIBRARY "${Matlab_EXTERN_LIB_DIR}/libut.lib" ) +else() + set( UT_LIBRARY "-lut" ) +endif() + matlab_add_mex( NAME osqp_mex SRC ${CMAKE_CURRENT_SOURCE_DIR}/osqp_mex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/interrupt_matlab.c LINK_TO osqpstatic + ${UT_LIBRARY} # Force compilation in the traditional C API (equivalent to the -R2017b flag) R2017b ) \ No newline at end of file From 1ee89e873014c5b6d93966cae8179c89e4d5cafa Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Wed, 15 Nov 2023 17:40:56 +0000 Subject: [PATCH 14/36] Fix submodule --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 48058bf..a1ed43b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "osqp"] +[submodule "osqp_sources"] path = c_sources/osqp_sources url = https://github.com/osqp/osqp From 03f955452ecf5a052e1e5b4c89e54517b3e3a171 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Wed, 15 Nov 2023 17:59:10 +0000 Subject: [PATCH 15/36] Fix building and installing on Windows --- make_osqp.m | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/make_osqp.m b/make_osqp.m index 9e0ae13..79058b7 100644 --- a/make_osqp.m +++ b/make_osqp.m @@ -70,9 +70,11 @@ function make_osqp(varargin) setenv('PATH', [PATH ':/usr/local/bin']); end + + %% Configure CMake for the mex interface fprintf(' Configuring...' ) - [status, output] = system( sprintf( 'cmake -B %s -S %s -DMatlab_ROOT_DIR=\"%s\"', osqp_mex_build_dir, osqp_mex_src_dir, Matlab_ROOT ), 'LD_LIBRARY_PATH', '' ); + [status, output] = system( sprintf( 'cmake -B %s -S %s -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMatlab_ROOT_DIR=\"%s\"', osqp_mex_build_dir, osqp_mex_src_dir, Matlab_ROOT ), 'LD_LIBRARY_PATH', '' ); if( status ) fprintf( '\n' ); disp( output ); @@ -86,7 +88,7 @@ function make_osqp(varargin) %% Build the mex interface fprintf( ' Building...') - [status, output] = system( sprintf( 'cmake --build %s', osqp_mex_build_dir ), 'LD_LIBRARY_PATH', '' ); + [status, output] = system( sprintf( 'cmake --build %s --config Release', osqp_mex_build_dir ), 'LD_LIBRARY_PATH', '' ); if( status ) fprintf( '\n' ); disp( output ); @@ -103,7 +105,11 @@ function make_osqp(varargin) fprintf( ' Installing...' ) % Copy mex file to root directory for use - [err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'osqp_mex.mex*'], makefile_path ); + if( ispc ) + [err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'Release', filesep, 'osqp_mex.mex*'], makefile_path ); + else + [err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'osqp_mex.mex*'], makefile_path ); + end if( ~err ) fprintf( '\n' ) disp( errmsg ) From 756dc9931c3f1b16e1e029d855723d31e5e8c73d Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Wed, 15 Nov 2023 18:14:09 +0000 Subject: [PATCH 16/36] Include libut link path on macos/linux as well --- c_sources/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt index 58169e2..f35ceaa 100644 --- a/c_sources/CMakeLists.txt +++ b/c_sources/CMakeLists.txt @@ -51,11 +51,12 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/osqp_sources/include/public ${CMAKE_CURRENT_SOURCE_DIR}/osqp_sources/include/private ) # Interrupt support requires the libut library +get_filename_component( Matlab_LIB_DIR ${Matlab_MEX_LIBRARY} DIRECTORY ) + if( WIN32 ) - get_filename_component( Matlab_EXTERN_LIB_DIR ${Matlab_MEX_LIBRARY} DIRECTORY ) - set( UT_LIBRARY "${Matlab_EXTERN_LIB_DIR}/libut.lib" ) + set( UT_LIBRARY "${Matlab_LIB_DIR}/libut.lib" ) else() - set( UT_LIBRARY "-lut" ) + set( UT_LIBRARY "-L${Matlab_LIB_DIR} -lut" ) endif() matlab_add_mex( NAME osqp_mex From dd86a792042900490add0394e7a7d574b0cc07c6 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Thu, 16 Nov 2023 10:42:11 -0500 Subject: [PATCH 17/36] Fixed copyUpdatedSettingsToWork --- c_sources/osqp_mex.cpp | 51 +++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 2535302..5bd3ea9 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -784,22 +784,41 @@ void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ OSQPInt exitflag; //TODO (Amit): Update this OSQPSettings* update_template = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); - if (!update_template) mexErrMsgTxt("Failed to allocate temporary OSQPSettings object."); - - update_template->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); - update_template->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); - update_template->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); - update_template->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf")); - update_template->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - update_template->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); - update_template->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); - update_template->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); - update_template->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); - update_template->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); - update_template->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); - update_template->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); - update_template->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); - update_template->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); + if (!update_template) mexErrMsgTxt("Failed to allocate a temporary OSQPSettings object."); + + update_template->device = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "device")); + update_template->linsys_solver = (enum osqp_linsys_solver_type)mxGetScalar(mxGetField(mxPtr, 0, "linsys_solver")); + update_template->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); + update_template->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); + update_template->scaling = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaling")); + update_template->polishing = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polishing")); + + update_template->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); + update_template->rho_is_vec = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "rho_is_vec")); + update_template->sigma = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "sigma")); + update_template->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); + + update_template->cg_max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_max_iter")); + update_template->cg_tol_reduction = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_reduction")); + update_template->cg_tol_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_fraction")); + update_template->cg_precond = (osqp_precond_type)mxGetScalar(mxGetField(mxPtr, 0, "cg_precond")); + + update_template->adaptive_rho = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho")); + update_template->adaptive_rho_interval = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_interval")); + update_template->adaptive_rho_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_fraction")); + update_template->adaptive_rho_tolerance = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_tolerance")); + + update_template->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); + update_template->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); + update_template->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); + update_template->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf")); + update_template->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); + update_template->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); + update_template->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); + update_template->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); + + update_template->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); + update_template->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); osqp_update_settings(osqpSolver, update_template); if (update_template) c_free(update_template); From fcc7b4b800d5bb6c2ee8b7685d899ce2d9799cba Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Thu, 16 Nov 2023 12:29:00 -0500 Subject: [PATCH 18/36] added cg_precond to copySettingsToMxStruct --- c_sources/osqp_mex.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 5bd3ea9..553b3f6 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -735,6 +735,7 @@ mxArray* copySettingsToMxStruct(OSQPSettings* settings){ mxSetField(mxPtr, 0, "cg_tol_reduction", mxCreateDoubleScalar(settings->cg_tol_reduction)); mxSetField(mxPtr, 0, "cg_tol_fraction", mxCreateDoubleScalar(settings->cg_tol_fraction)); mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); + mxSetField(mxPtr, 0, "cg_precond", mxCreateDoubleScalar(settings->cg_precond)); return mxPtr; } From aaae7618fd844c671fe5e47104f92664b6baffff Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Thu, 16 Nov 2023 13:00:14 -0500 Subject: [PATCH 19/36] rho is updated through update_rho --- c_sources/osqp_mex.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 553b3f6..5b0b93b 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -822,5 +822,9 @@ void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ update_template->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); osqp_update_settings(osqpSolver, update_template); + //rho needs to be updated separetly, it is not updated in osqp_update_settings + OSQPFloat rho_new = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); + if (rho_new != osqpSolver->settings->rho) osqp_update_rho(osqpSolver, rho_new); + if (update_template) c_free(update_template); } \ No newline at end of file From 4a93044cfd5806905ae14e7cbfb5d4575e679250 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Fri, 17 Nov 2023 11:33:53 +0000 Subject: [PATCH 20/36] Reorganize OSQP class structure --- .github/workflows/main.yml | 2 +- @osqp/build.m | 164 ++++++ @osqp/codegen.m | 208 ++++++++ @osqp/osqp.m | 105 ++++ @osqp/private/linsys_solver_to_string.m | 13 + @osqp/private/string_to_linsys_solver.m | 18 + @osqp/setup.m | 106 ++++ @osqp/solve.m | 11 + @osqp/update.m | 69 +++ @osqp/validate_settings.m | 71 +++ @osqp/warm_start.m | 62 +++ make_osqp.m | 167 ------ osqp.m | 681 ------------------------ run_osqp_tests.m | 4 +- 14 files changed, 830 insertions(+), 851 deletions(-) create mode 100644 @osqp/build.m create mode 100644 @osqp/codegen.m create mode 100644 @osqp/osqp.m create mode 100644 @osqp/private/linsys_solver_to_string.m create mode 100644 @osqp/private/string_to_linsys_solver.m create mode 100644 @osqp/setup.m create mode 100644 @osqp/solve.m create mode 100644 @osqp/update.m create mode 100644 @osqp/validate_settings.m create mode 100644 @osqp/warm_start.m delete mode 100644 make_osqp.m delete mode 100755 osqp.m diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 128f361..059aef6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: - name: Build OSQP interface uses: matlab-actions/run-command@v1 with: - command: make_osqp + command: osqp.build('osqp') - name: Run tests uses: matlab-actions/run-tests@v1 diff --git a/@osqp/build.m b/@osqp/build.m new file mode 100644 index 0000000..0d13215 --- /dev/null +++ b/@osqp/build.m @@ -0,0 +1,164 @@ +function build(varargin) +% Matlab MEX makefile for OSQP. +% +% MAKE_OSQP(VARARGIN) is a make file for OSQP solver. It +% builds OSQP and its components from source. +% +% WHAT is the last element of VARARGIN and cell array of strings, +% with the following options: +% +% {}, '' (empty string) or 'all': build all components and link. +% +% 'osqp_mex': builds the OSQP mex interface and the OSQP library +% +% Additional commands: +% +% 'clean': Delete all compiled files +% 'purge': Delete all compiled files and copied code generation files + + if( nargin == 0 ) + what = {'all'}; + verbose = false; + elseif ( nargin == 1 && ismember('-verbose', varargin) ) + what = {'all'}; + verbose = true; + else + what = varargin{nargin}; + if(isempty(strfind(what, 'all')) && ... + isempty(strfind(what, 'osqp_mex')) && ... + isempty(strfind(what, 'clean')) && ... + isempty(strfind(what, 'purge'))) + fprintf('No rule to make target "%s", exiting.\n', what); + end + + verbose = ismember('-verbose', varargin); + end + + %% Determine where the various files are all located + % Various parts of the build system + [osqp_classpath,~,~] = fileparts( mfilename( 'fullpath' ) ); + osqp_mex_src_dir = fullfile( osqp_classpath, '..', 'c_sources' ); + osqp_mex_build_dir = fullfile( osqp_mex_src_dir, 'build' ); + osqp_cg_src_dir = fullfile( osqp_mex_build_dir, 'codegen_src' ); + osqp_cg_dest_dir = fullfile( osqp_classpath, '..', 'codegen', 'sources' ); + + % Determine where CMake should look for MATLAB + Matlab_ROOT = strrep( matlabroot, '\', '/' ); + + %% Try to unlock any pre-existing version of osqp_mex + % this prevents compile errors if a user builds, runs osqp + % and then tries to recompile + if(mislocked('osqp_mex')) + munlock('osqp_mex'); + end + + %% Configure, build and install the OSQP mex interface + if( any(strcmpi(what,'osqp_mex')) || any(strcmpi(what,'all')) ) + fprintf('Compiling OSQP solver mex interface...\n'); + + % Create build for the mex file and go inside + if exist( osqp_mex_build_dir, 'dir' ) + rmdir( osqp_mex_build_dir, 's' ); + end + mkdir( osqp_mex_build_dir ); + % cd( osqp_mex_build_dir ); + + % Extend path for CMake mac (via Homebrew) + PATH = getenv('PATH'); + if( (ismac) && (isempty(strfind(PATH, '/usr/local/bin'))) ) + setenv('PATH', [PATH ':/usr/local/bin']); + end + + + + %% Configure CMake for the mex interface + fprintf(' Configuring...' ) + [status, output] = system( sprintf( 'cmake -B %s -S %s -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMatlab_ROOT_DIR=\"%s\"', osqp_mex_build_dir, osqp_mex_src_dir, Matlab_ROOT ), 'LD_LIBRARY_PATH', '' ); + if( status ) + fprintf( '\n' ); + disp( output ); + error( 'Error configuring CMake environment' ); + elseif( verbose ) + fprintf( '\n' ); + disp( output ); + else + fprintf( '\t\t\t\t\t[done]\n' ); + end + + %% Build the mex interface + fprintf( ' Building...') + [status, output] = system( sprintf( 'cmake --build %s --config Release', osqp_mex_build_dir ), 'LD_LIBRARY_PATH', '' ); + if( status ) + fprintf( '\n' ); + disp( output ); + error( 'Error compiling OSQP mex interface' ); + elseif( verbose ) + fprintf( '\n' ); + disp( output ); + else + fprintf( '\t\t\t\t\t\t[done]\n' ); + end + + + %% Install various files + fprintf( ' Installing...' ) + + % Copy mex file to root directory for use + if( ispc ) + [err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'Release', filesep, 'osqp_mex.mex*'], [osqp_classpath, filesep, 'private'] ); + else + [err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'osqp_mex.mex*'], [osqp_classpath, filesep, 'private'] ); + end + if( ~err ) + fprintf( '\n' ) + disp( errmsg ) + error( ' Error copying mex file' ) + end + + % Copy the code generation source files + % Create build for the mex file and go inside + if exist( osqp_cg_dest_dir, 'dir' ) + rmdir( osqp_cg_dest_dir, 's' ); + end + mkdir( osqp_cg_dest_dir ); + + [err, errmsg, ~] = copyfile( [osqp_cg_src_dir, filesep, '*'], osqp_cg_dest_dir ); + if( ~err ) + fprintf( '\n' ) + disp( errmsg ) + error( ' Error copying code generation source files' ) + end + + fprintf( '\t\t\t\t\t\t[done]\n' ); + end + + %% Clean and purge + if( any(strcmpi(what,'clean')) || any(strcmpi(what,'purge')) ) + fprintf('Cleaning OSQP mex files and build directory...'); + + % Delete mex file + mexfiles = dir(['*.', mexext]); + for i = 1 : length(mexfiles) + delete(mexfiles(i).name); + end + + % Delete OSQP build directory + if exist(osqp_mex_build_dir, 'dir') + rmdir(osqp_mex_build_dir, 's'); + end + + fprintf('\t\t[done]\n'); + + %% Purge only + if( any(strcmpi(what,'purge')) ) + fprintf('Cleaning OSQP codegen directories...'); + + % Delete codegen files + if exist(osqp_cg_dest_dir, 'dir') + rmdir(osqp_cg_dest_dir, 's'); + end + + fprintf('\t\t\t[done]\n'); + end + end +end diff --git a/@osqp/codegen.m b/@osqp/codegen.m new file mode 100644 index 0000000..d753762 --- /dev/null +++ b/@osqp/codegen.m @@ -0,0 +1,208 @@ +%% +function codegen(this, target_dir, varargin) + % CODEGEN generate C code for the parametric problem + % + % codegen(target_dir,options) + + % Parse input arguments + p = inputParser; + defaultProject = ''; + expectedProject = {'', 'Makefile', 'MinGW Makefiles', 'Unix Makefiles', 'CodeBlocks', 'Xcode'}; + defaultParams = 'vectors'; + expectedParams = {'vectors', 'matrices'}; + defaultMexname = 'emosqp'; + defaultFloat = false; + defaultLong = true; + defaultFW = false; + + addRequired(p, 'target_dir', @isstr); + addParameter(p, 'project_type', defaultProject, ... + @(x) ischar(validatestring(x, expectedProject))); + addParameter(p, 'parameters', defaultParams, ... + @(x) ischar(validatestring(x, expectedParams))); + addParameter(p, 'mexname', defaultMexname, @isstr); + addParameter(p, 'FLOAT', defaultFloat, @islogical); + addParameter(p, 'LONG', defaultLong, @islogical); + addParameter(p, 'force_rewrite', defaultFW, @islogical); + + parse(p, target_dir, varargin{:}); + + % Set internal variables + if strcmp(p.Results.parameters, 'vectors') + embedded = 1; + else + embedded = 2; + end + if p.Results.FLOAT + float_flag = 'ON'; + else + float_flag = 'OFF'; + end + if p.Results.LONG + long_flag = 'ON'; + else + long_flag = 'OFF'; + end + if strcmp(p.Results.project_type, 'Makefile') + if (ispc) + project_type = 'MinGW Makefiles'; % Windows + elseif (ismac || isunix) + project_type = 'Unix Makefiles'; % Unix + end + else + project_type = p.Results.project_type; + end + + % Check whether the specified directory already exists + if exist(target_dir, 'dir') + if p.Results.force_rewrite + rmdir(target_dir, 's'); + else + while(1) + prompt = sprintf('Directory "%s" already exists. Do you want to replace it? y/n [y]: ', target_dir); + str = input(prompt, 's'); + + if any(strcmpi(str, {'','y'})) + rmdir(target_dir, 's'); + break; + elseif strcmpi(str, 'n') + return; + end + end + end + end + + % Import OSQP path + [osqp_path,~,~] = fileparts(which('osqp.m')); + + % Add codegen directory to path + addpath(fullfile(osqp_path, 'codegen')); + + % Path of osqp module + cg_dir = fullfile(osqp_path, 'codegen'); + files_to_generate_path = fullfile(cg_dir, 'files_to_generate'); + + % Get workspace structure + work = osqp_mex('get_workspace', this.objectHandle); + + % Make target directory + fprintf('Creating target directories...\t\t\t\t\t'); + target_configure_dir = fullfile(target_dir, 'configure'); + target_include_dir = fullfile(target_dir, 'include'); + target_src_dir = fullfile(target_dir, 'src'); + + if ~exist(target_dir, 'dir') + mkdir(target_dir); + end + if ~exist(target_configure_dir, 'dir') + mkdir(target_configure_dir); + end + if ~exist(target_include_dir, 'dir') + mkdir(target_include_dir); + end + if ~exist(target_src_dir, 'dir') + mkdir(fullfile(target_src_dir, 'osqp')); + end + fprintf('[done]\n'); + + % Copy source files to target directory + fprintf('Copying OSQP source files...\t\t\t\t\t'); + cdir = fullfile(cg_dir, 'sources', 'src'); + cfiles = dir(fullfile(cdir, '*.c')); + for i = 1 : length(cfiles) + if embedded == 1 + % Do not copy kkt.c if embedded is 1 + if ~strcmp(cfiles(i).name, 'kkt.c') + copyfile(fullfile(cdir, cfiles(i).name), ... + fullfile(target_src_dir, 'osqp', cfiles(i).name)); + end + else + copyfile(fullfile(cdir, cfiles(i).name), ... + fullfile(target_src_dir, 'osqp', cfiles(i).name)); + end + end + configure_dir = fullfile(cg_dir, 'sources', 'configure'); + configure_files = dir(fullfile(configure_dir, '*.h.in')); + for i = 1 : length(configure_files) + copyfile(fullfile(configure_dir, configure_files(i).name), ... + fullfile(target_configure_dir, configure_files(i).name)); + end + hdir = fullfile(cg_dir, 'sources', 'include'); + hfiles = dir(fullfile(hdir, '*.h')); + for i = 1 : length(hfiles) + if embedded == 1 + % Do not copy kkt.h if embedded is 1 + if ~strcmp(hfiles(i).name, 'kkt.h') + copyfile(fullfile(hdir, hfiles(i).name), ... + fullfile(target_include_dir, hfiles(i).name)); + end + else + copyfile(fullfile(hdir, hfiles(i).name), ... + fullfile(target_include_dir, hfiles(i).name)); + end + end + + % Copy cmake files + copyfile(fullfile(cdir, 'CMakeLists.txt'), ... + fullfile(target_src_dir, 'osqp', 'CMakeLists.txt')); + copyfile(fullfile(hdir, 'CMakeLists.txt'), ... + fullfile(target_include_dir, 'CMakeLists.txt')); + fprintf('[done]\n'); + + % Copy example.c + copyfile(fullfile(files_to_generate_path, 'example.c'), target_src_dir); + + % Render CMakeLists.txt + fidi = fopen(fullfile(files_to_generate_path, 'CMakeLists.txt'),'r'); + fido = fopen(fullfile(target_dir, 'CMakeLists.txt'),'w'); + while ~feof(fidi) + l = fgetl(fidi); % read line + % Replace EMBEDDED_FLAG in CMakeLists.txt by a numerical value + newl = strrep(l, 'EMBEDDED_FLAG', num2str(embedded)); + fprintf(fido, '%s\n', newl); + end + fclose(fidi); + fclose(fido); + + % Render workspace.h and workspace.c + work_hfile = fullfile(target_include_dir, 'workspace.h'); + work_cfile = fullfile(target_src_dir, 'osqp', 'workspace.c'); + fprintf('Generating workspace.h/.c...\t\t\t\t\t\t'); + render_workspace(work, work_hfile, work_cfile, embedded); + fprintf('[done]\n'); + + % Create project + if ~isempty(project_type) + + % Extend path for CMake mac (via Homebrew) + PATH = getenv('PATH'); + if ((ismac) && (isempty(strfind(PATH, '/usr/local/bin')))) + setenv('PATH', [PATH ':/usr/local/bin']); + end + + fprintf('Creating project...\t\t\t\t\t\t\t\t'); + orig_dir = pwd; + cd(target_dir); + mkdir('build') + cd('build'); + cmd = sprintf('cmake -G "%s" ..', project_type); + [status, output] = system(cmd); + if(status) + fprintf('\n'); + fprintf(output); + error('Error configuring CMake environment'); + else + fprintf('[done]\n'); + end + cd(orig_dir); + end + + % Make mex interface to the generated code + mex_cfile = fullfile(files_to_generate_path, 'emosqp_mex.c'); + make_emosqp(target_dir, mex_cfile, embedded, float_flag, long_flag); + + % Rename the mex file + old_mexfile = ['emosqp_mex.', mexext]; + new_mexfile = [p.Results.mexname, '.', mexext]; + movefile(old_mexfile, new_mexfile); +end \ No newline at end of file diff --git a/@osqp/osqp.m b/@osqp/osqp.m new file mode 100644 index 0000000..0cfda3e --- /dev/null +++ b/@osqp/osqp.m @@ -0,0 +1,105 @@ +classdef osqp < handle + % osqp interface class for OSQP solver + % This class provides a complete interface to the C implementation + % of the OSQP solver. + % + % osqp Properties: + % objectHandle - pointer to the C structure of OSQP solver + % + % osqp Methods: + % + % setup - configure solver with problem data + % solve - solve the QP + % update - modify problem vectors + % warm_start - set warm starting variables x and y + % + % default_settings - create default settings structure + % current_settings - get the current solver settings structure + % update_settings - update the current solver settings structure + % + % get_dimensions - get the number of variables and constraints + % version - return OSQP version + % constant - return a OSQP internal constant + % + % codegen - generate embeddable C code for the problem + + + properties(SetAccess = private, Hidden = true) + objectHandle % Handle to underlying C instance + end + + methods(Static) + output = build(varargin) + + %% + function out = default_settings() + % DEFAULT_SETTINGS get the default solver settings structure + out = osqp_mex('default_settings', 'static'); + + % Convert linsys solver to string + out.linsys_solver = linsys_solver_to_string(out.linsys_solver); + + end + + %% + function out = constant(constant_name) + % CONSTANT Return solver constant + % C = CONSTANT(CONSTANT_NAME) return constant called CONSTANT_NAME + out = osqp_mex('constant', 'static', constant_name); + end + + %% + function out = version() + % Return OSQP version + out = osqp_mex('version', 'static'); + end + end + + methods(Access = private) + currentSettings = validate_settings(this, isInitialization, varargin) + end + + methods + %% Constructor - Create a new solver instance + function this = osqp(varargin) + % Construct OSQP solver class + this.objectHandle = osqp_mex('new', varargin{:}); + end + + %% Destructor - destroy the solver instance + function delete(this) + % Destroy OSQP solver class + osqp_mex('delete', this.objectHandle); + end + + %% + function out = current_settings(this) + % CURRENT_SETTINGS get the current solver settings structure + out = osqp_mex('current_settings', this.objectHandle); + + % Convert linsys solver to string + out.linsys_solver = linsys_solver_to_string(out.linsys_solver); + + end + + function update_settings(this, varargin) + % UPDATE_SETTINGS update the current solver settings structure + + %second input 'false' means that this is *not* a settings + %initialization, so some parameter/values will be disallowed + newSettings = validate_settings(this, false, varargin{:}); + + %write the solver settings. C-mex does not check input + %data or protect against disallowed parameter modifications + osqp_mex('update_settings', this.objectHandle, newSettings); + end + + %% + function [n,m] = get_dimensions(this) + % GET_DIMENSIONS get the number of variables and constraints + + [n,m] = osqp_mex('get_dimensions', this.objectHandle); + + end + end +end \ No newline at end of file diff --git a/@osqp/private/linsys_solver_to_string.m b/@osqp/private/linsys_solver_to_string.m new file mode 100644 index 0000000..bac8047 --- /dev/null +++ b/@osqp/private/linsys_solver_to_string.m @@ -0,0 +1,13 @@ +% Convert linear systme solver integer to string +function [linsys_solver_string] = linsys_solver_to_string(linsys_solver) + switch linsys_solver + case osqp.constant('OSQP_UNKNOWN_SOLVER') + linsys_solver_string = 'unknown solver'; + case osqp.constant('OSQP_DIRECT_SOLVER') + linsys_solver_string = 'direct solver'; + case osqp.constant('OSQP_INDIRECT_SOLVER') + linsys_solver_string = 'indirect solver'; + otherwise + error('Unrecognized linear system solver.'); + end +end diff --git a/@osqp/private/string_to_linsys_solver.m b/@osqp/private/string_to_linsys_solver.m new file mode 100644 index 0000000..51e5c99 --- /dev/null +++ b/@osqp/private/string_to_linsys_solver.m @@ -0,0 +1,18 @@ +function [linsys_solver] = string_to_linsys_solver(linsys_solver_string) + linsys_solver_string = lower(linsys_solver_string); + switch linsys_solver_string + case 'unknown solver' + linsys_solver = osqp.constant('OSQP_UNKNOWN_SOLVER'); + case 'direct solver' + linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); + case 'indirect solver' + linsys_solver = osqp.constant('OSQP_INDIRECT_SOLVER'); + % Default solver: QDLDL + case '' + linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); + otherwise + warning('Linear system solver not recognized. Using default solver OSQP_DIRECT_SOLVER.') + linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); + end +end + \ No newline at end of file diff --git a/@osqp/setup.m b/@osqp/setup.m new file mode 100644 index 0000000..800a442 --- /dev/null +++ b/@osqp/setup.m @@ -0,0 +1,106 @@ +%% +function varargout = setup(this, varargin) + % SETUP configure solver with problem data + % + % setup(P,q,A,l,u,options) + + nargin = length(varargin); + + %dimension checks on user data. Mex function does not + %perform any checks on inputs, so check everything here + assert(nargin >= 5, 'incorrect number of inputs'); + [P,q,A,l,u] = deal(varargin{1:5}); + + % + % Get problem dimensions + % + + % Get number of variables n + if (isempty(P)) + if (~isempty(q)) + n = length(q); + else + if (~isempty(A)) + n = size(A, 2); + else + error('The problem does not have any variables'); + end + end + else + n = size(P, 1); + end + + % Get number of constraints m + if (isempty(A)) + m = 0; + else + m = size(A, 1); + assert(size(A, 2) == n, 'Incorrect dimension of A'); + end + + % + % Create sparse matrices and full vectors if they are empty + % + + if (isempty(P)) + P = sparse(n, n); + else + P = sparse(P); + end + if (~istriu(P)) + P = triu(P); + end + if (isempty(q)) + q = zeros(n, 1); + else + q = full(q(:)); + end + + % Create proper constraints if they are not passed + if (isempty(A) && (~isempty(l) || ~isempty(u))) || ... + (~isempty(A) && (isempty(l) && isempty(u))) + error('A must be supplied together with at least one bound l or u'); + end + + if (~isempty(A) && isempty(l)) + l = -Inf(m, 1); + end + + if (~isempty(A) && isempty(u)) + u = Inf(m, 1); + end + + if (isempty(A)) + A = sparse(m, n); + l = -Inf(m, 1); + u = Inf(m, 1); + else + l = full(l(:)); + u = full(u(:)); + A = sparse(A); + end + + + % + % Check vector dimensions (not checked from the C solver) + % + + assert(length(q) == n, 'Incorrect dimension of q'); + assert(length(l) == m, 'Incorrect dimension of l'); + assert(length(u) == m, 'Incorrect dimension of u'); + + % + % Convert infinity values to OSQP_INFINITY + % + u = min(u, osqp.constant('OSQP_INFTY')); + l = max(l, -osqp.constant('OSQP_INFTY')); + + + %make a settings structure from the remainder of the arguments. + %'true' means that this is a settings initialization, so all + %parameter/values are allowed. No extra inputs will result + %in default settings being passed back + theSettings = validate_settings(this,true,varargin{6:end}); + + [varargout{1:nargout}] = osqp_mex('setup', this.objectHandle, n,m,P,q,A,l,u,theSettings); +end \ No newline at end of file diff --git a/@osqp/solve.m b/@osqp/solve.m new file mode 100644 index 0000000..2ce96cd --- /dev/null +++ b/@osqp/solve.m @@ -0,0 +1,11 @@ +%% +function varargout = solve(this, varargin) + % SOLVE solve the QP + + nargoutchk(0,1); %either return nothing (but still solve), or a single output structure + [out.x, out.y, out.prim_inf_cert, out.dual_inf_cert, out.info] = osqp_mex('solve', this.objectHandle); + if(nargout) + varargout{1} = out; + end + return; +end \ No newline at end of file diff --git a/@osqp/update.m b/@osqp/update.m new file mode 100644 index 0000000..4f27b6e --- /dev/null +++ b/@osqp/update.m @@ -0,0 +1,69 @@ +%% +function update(this,varargin) + % UPDATE modify the linear cost term and/or lower and upper bounds + + %second input 'false' means that this is *not* a settings + %initialization, so some parameter/values will be disallowed + allowedFields = {'q','l','u','Px','Px_idx','Ax','Ax_idx'}; + + if(isempty(varargin)) + return; + elseif(length(varargin) == 1) + if(~isstruct(varargin{1})) + error('Single input should be a structure with new problem data'); + else + newData = varargin{1}; + end + else % param / value style assumed + newData = struct(varargin{:}); + end + + %check for unknown fields + newFields = fieldnames(newData); + badFieldsIdx = find(~ismember(newFields,allowedFields)); + if(~isempty(badFieldsIdx)) + error('Unrecognized input field ''%s'' detected',newFields{badFieldsIdx(1)}); + end + + %get all of the terms. Nonexistent fields will be passed + %as empty mxArrays + try q = double(full(newData.q(:))); catch q = []; end + try l = double(full(newData.l(:))); catch l = []; end + try u = double(full(newData.u(:))); catch u = []; end + try Px = double(full(newData.Px(:))); catch Px = []; end + try Px_idx = double(full(newData.Px_idx(:))); catch Px_idx = []; end + try Ax = double(full(newData.Ax(:))); catch Ax = []; end + try Ax_idx = double(full(newData.Ax_idx(:))); catch Ax_idx = []; end + + [n,m] = get_dimensions(this); + + assert(isempty(q) || length(q) == n, 'input ''q'' is the wrong size'); + assert(isempty(l) || length(l) == m, 'input ''u'' is the wrong size'); + assert(isempty(u) || length(u) == m, 'input ''l'' is the wrong size'); + assert(isempty(Px) || isempty(Px_idx) || length(Px) == length(Px_idx), ... + 'inputs ''Px'' and ''Px_idx'' must be the same size'); + assert(isempty(Ax) || isempty(Ax_idx) || length(Ax) == length(Ax_idx), ... + 'inputs ''Ax'' and ''Ax_idx'' must be the same size'); + + % Adjust index of Px_idx and Ax_idx to match 0-based indexing + % in C + if (~isempty(Px_idx)) + Px_idx = Px_idx - 1; + end + if (~isempty(Ax_idx)) + Ax_idx = Ax_idx - 1; + end + + % Convert infinity values to OSQP_INFTY + if (~isempty(u)) + u = min(u, osqp.constant('OSQP_INFTY')); + end + if (~isempty(l)) + l = max(l, -osqp.constant('OSQP_INFTY')); + end + + %write the new problem data. C-mex does not protect + %against unknown fields, but will handle empty values + osqp_mex('update', this.objectHandle, ... + q, l, u, Px, Px_idx, length(Px), Ax, Ax_idx, length(Ax)); +end \ No newline at end of file diff --git a/@osqp/validate_settings.m b/@osqp/validate_settings.m new file mode 100644 index 0000000..3ce7d6d --- /dev/null +++ b/@osqp/validate_settings.m @@ -0,0 +1,71 @@ +function currentSettings = validate_settings(this, isInitialization, varargin) + % Don't allow these fields to be changed + unmodifiableFields = {'scaling', 'linsys_solver'}; + + % Get the current settings + if(isInitialization) + currentSettings = osqp_mex('default_settings', this.objectHandle); + else + currentSettings = osqp_mex('current_settings', this.objectHandle); + end + + % No settings passed -> return defaults + if(isempty(varargin)) + return; + end + + % Check for structure style input + if(isstruct(varargin{1})) + newSettings = varargin{1}; + assert(length(varargin) == 1, 'too many input arguments'); + else + newSettings = struct(varargin{:}); + end + + % Get the osqp settings fields + currentFields = fieldnames(currentSettings); + + % Get the requested fields in the update + newFields = fieldnames(newSettings); + + % Check for unknown parameters + badFieldsIdx = find(~ismember(newFields,currentFields)); + if(~isempty(badFieldsIdx)) + error('Unrecognized solver setting ''%s'' detected',newFields{badFieldsIdx(1)}); + end + + % Convert linsys_solver string to integer + if ismember('linsys_solver',newFields) + if ~ischar(newSettings.linsys_solver) + error('Setting linsys_solver is required to be a string.'); + end + % Convert linsys_solver to number + newSettings.linsys_solver = string_to_linsys_solver(newSettings.linsys_solver); + end + + + % Check for disallowed fields if this in not an initialization call + if(~isInitialization) + badFieldsIdx = find(ismember(newFields,unmodifiableFields)); + for i = badFieldsIdx(:)' + if(~isequal(newSettings.(newFields{i}),currentSettings.(newFields{i}))) + error('Solver setting ''%s'' can only be changed at solver initialization.', newFields{i}); + end + end + end + + + % Check that everything is a nonnegative scalar (this check is already + % performed in C) + % for i = 1:length(newFields) + % val = double(newSettings.(newFields{i})); + % assert(isscalar(val) & isnumeric(val) & val >= 0, ... + % 'Solver setting ''%s'' not specified as nonnegative scalar', newFields{i}); + % end + + % Everything checks out - merge the newSettings into the current ones + for i = 1:length(newFields) + currentSettings.(newFields{i}) = double(newSettings.(newFields{i})); + end +end + \ No newline at end of file diff --git a/@osqp/warm_start.m b/@osqp/warm_start.m new file mode 100644 index 0000000..322898d --- /dev/null +++ b/@osqp/warm_start.m @@ -0,0 +1,62 @@ +function warm_start(this, varargin) + % WARM_START warm start primal and/or dual variables + % + % warm_start('x', x, 'y', y) + % + % or warm_start('x', x) + % or warm_start('y', y) + + + % Get problem dimensions + [n, m] = get_dimensions(this); + + % Get data + allowedFields = {'x','y'}; + + if(isempty(varargin)) + return; + elseif(length(varargin) == 1) + if(~isstruct(varargin{1})) + error('Single input should be a structure with new problem data'); + else + newData = varargin{1}; + end + else % param / value style assumed + newData = struct(varargin{:}); + end + + %check for unknown fields + newFields = fieldnames(newData); + badFieldsIdx = find(~ismember(newFields,allowedFields)); + if(~isempty(badFieldsIdx)) + error('Unrecognized input field ''%s'' detected',newFields{badFieldsIdx(1)}); + end + + %get all of the terms. Nonexistent fields will be passed + %as empty mxArrays + try x = double(full(newData.x(:))); catch x = []; end + try y = double(full(newData.y(:))); catch y = []; end + + % Check dimensions + assert(isempty(x) || length(x) == n, 'input ''x'' is the wrong size'); + assert(isempty(y) || length(y) == m, 'input ''y'' is the wrong size'); + + + % Decide which function to call + if (~isempty(x) && isempty(y)) + osqp_mex('warm_start_x', this.objectHandle, x); + return; + end + + if (isempty(x) && ~isempty(y)) + osqp_mex('warm_start_y', this.objectHandle, y); + end + + if (~isempty(x) && ~isempty(y)) + osqp_mex('warm_start', this.objectHandle, x, y); + end + + if (isempty(x) && isempty(y)) + error('Unrecognized fields'); + end +end \ No newline at end of file diff --git a/make_osqp.m b/make_osqp.m deleted file mode 100644 index 79058b7..0000000 --- a/make_osqp.m +++ /dev/null @@ -1,167 +0,0 @@ -function make_osqp(varargin) -% Matlab MEX makefile for OSQP. -% -% MAKE_OSQP(VARARGIN) is a make file for OSQP solver. It -% builds OSQP and its components from source. -% -% WHAT is the last element of VARARGIN and cell array of strings, -% with the following options: -% -% {}, '' (empty string) or 'all': build all components and link. -% -% 'osqp_mex': builds the OSQP mex interface and the OSQP library -% -% Additional commands: -% -% 'clean': Delete all compiled files -% 'purge': Delete all compiled files and copied code generation files - - -if( nargin == 0 ) - what = {'all'}; - verbose = false; -elseif ( nargin == 1 && ismember('-verbose', varargin) ) - what = {'all'}; - verbose = true; -else - what = varargin{nargin}; - if(isempty(strfind(what, 'all')) && ... - isempty(strfind(what, 'osqp_mex')) && ... - isempty(strfind(what, 'clean')) && ... - isempty(strfind(what, 'purge'))) - fprintf('No rule to make target "%s", exiting.\n', what); - end - - verbose = ismember('-verbose', varargin); -end - -%% Determine where the various files are all located -% Various parts of the build system -[makefile_path,~,~] = fileparts( which( 'make_osqp.m' ) ); -osqp_mex_src_dir = fullfile( makefile_path, 'c_sources' ); -osqp_mex_build_dir = fullfile( osqp_mex_src_dir, 'build' ); -osqp_cg_src_dir = fullfile( osqp_mex_build_dir, 'codegen_src' ); -osqp_cg_dest_dir = fullfile( makefile_path, 'codegen', 'sources' ); - -% Determine where CMake should look for MATLAB -Matlab_ROOT = strrep( matlabroot, '\', '/' ); - -%% Try to unlock any pre-existing version of osqp_mex -% this prevents compile errors if a user builds, runs osqp -% and then tries to recompile -if(mislocked('osqp_mex')) - munlock('osqp_mex'); -end - -%% Configure, build and install the OSQP mex interface -if( any(strcmpi(what,'osqp_mex')) || any(strcmpi(what,'all')) ) - fprintf('Compiling OSQP solver mex interface...\n'); - - % Create build for the mex file and go inside - if exist( osqp_mex_build_dir, 'dir' ) - rmdir( osqp_mex_build_dir, 's' ); - end - mkdir( osqp_mex_build_dir ); -% cd( osqp_mex_build_dir ); - - % Extend path for CMake mac (via Homebrew) - PATH = getenv('PATH'); - if( (ismac) && (isempty(strfind(PATH, '/usr/local/bin'))) ) - setenv('PATH', [PATH ':/usr/local/bin']); - end - - - - %% Configure CMake for the mex interface - fprintf(' Configuring...' ) - [status, output] = system( sprintf( 'cmake -B %s -S %s -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMatlab_ROOT_DIR=\"%s\"', osqp_mex_build_dir, osqp_mex_src_dir, Matlab_ROOT ), 'LD_LIBRARY_PATH', '' ); - if( status ) - fprintf( '\n' ); - disp( output ); - error( 'Error configuring CMake environment' ); - elseif( verbose ) - fprintf( '\n' ); - disp( output ); - else - fprintf( '\t\t\t\t\t[done]\n' ); - end - - %% Build the mex interface - fprintf( ' Building...') - [status, output] = system( sprintf( 'cmake --build %s --config Release', osqp_mex_build_dir ), 'LD_LIBRARY_PATH', '' ); - if( status ) - fprintf( '\n' ); - disp( output ); - error( 'Error compiling OSQP mex interface' ); - elseif( verbose ) - fprintf( '\n' ); - disp( output ); - else - fprintf( '\t\t\t\t\t\t[done]\n' ); - end - - - %% Install various files - fprintf( ' Installing...' ) - - % Copy mex file to root directory for use - if( ispc ) - [err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'Release', filesep, 'osqp_mex.mex*'], makefile_path ); - else - [err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'osqp_mex.mex*'], makefile_path ); - end - if( ~err ) - fprintf( '\n' ) - disp( errmsg ) - error( ' Error copying mex file' ) - end - - % Copy the code generation source files - % Create build for the mex file and go inside - if exist( osqp_cg_dest_dir, 'dir' ) - rmdir( osqp_cg_dest_dir, 's' ); - end - mkdir( osqp_cg_dest_dir ); - - [err, errmsg, ~] = copyfile( [osqp_cg_src_dir, filesep, '*'], osqp_cg_dest_dir ); - if( ~err ) - fprintf( '\n' ) - disp( errmsg ) - error( ' Error copying code generation source files' ) - end - - fprintf( '\t\t\t\t\t\t[done]\n' ); -end - -%% Clean and purge -if( any(strcmpi(what,'clean')) || any(strcmpi(what,'purge')) ) - fprintf('Cleaning OSQP mex files and build directory...'); - - % Delete mex file - mexfiles = dir(['*.', mexext]); - for i = 1 : length(mexfiles) - delete(mexfiles(i).name); - end - - % Delete OSQP build directory - if exist(osqp_mex_build_dir, 'dir') - rmdir(osqp_mex_build_dir, 's'); - end - - fprintf('\t\t[done]\n'); - - %% Purge only - if( any(strcmpi(what,'purge')) ) - fprintf('Cleaning OSQP codegen directories...'); - - % Delete codegen files - if exist(osqp_cg_dest_dir, 'dir') - rmdir(osqp_cg_dest_dir, 's'); - end - - fprintf('\t\t\t[done]\n'); - end - -end - -end diff --git a/osqp.m b/osqp.m deleted file mode 100755 index c87cbcc..0000000 --- a/osqp.m +++ /dev/null @@ -1,681 +0,0 @@ -classdef osqp < handle - % osqp interface class for OSQP solver - % This class provides a complete interface to the C implementation - % of the OSQP solver. - % - % osqp Properties: - % objectHandle - pointer to the C structure of OSQP solver - % - % osqp Methods: - % - % setup - configure solver with problem data - % solve - solve the QP - % update - modify problem vectors - % warm_start - set warm starting variables x and y - % - % default_settings - create default settings structure - % current_settings - get the current solver settings structure - % update_settings - update the current solver settings structure - % - % get_dimensions - get the number of variables and constraints - % version - return OSQP version - % constant - return a OSQP internal constant - % - % codegen - generate embeddable C code for the problem - - - properties (SetAccess = private, Hidden = true) - objectHandle % Handle to underlying C instance - end - methods(Static) - %% - function out = default_settings() - % DEFAULT_SETTINGS get the default solver settings structure - out = osqp_mex('default_settings', 'static'); - - % Convert linsys solver to string - out.linsys_solver = linsys_solver_to_string(out.linsys_solver); - - end - - %% - function out = constant(constant_name) - % CONSTANT Return solver constant - % C = CONSTANT(CONSTANT_NAME) return constant called CONSTANT_NAME - out = osqp_mex('constant', 'static', constant_name); - end - - %% - function out = version() - % Return OSQP version - out = osqp_mex('version', 'static'); - end - - end - methods - %% Constructor - Create a new solver instance - function this = osqp(varargin) - % Construct OSQP solver class - this.objectHandle = osqp_mex('new', varargin{:}); - end - - %% Destructor - destroy the solver instance - function delete(this) - % Destroy OSQP solver class - osqp_mex('delete', this.objectHandle); - end - - %% - function out = current_settings(this) - % CURRENT_SETTINGS get the current solver settings structure - out = osqp_mex('current_settings', this.objectHandle); - - % Convert linsys solver to string - out.linsys_solver = linsys_solver_to_string(out.linsys_solver); - - end - - %% - function update_settings(this,varargin) - % UPDATE_SETTINGS update the current solver settings structure - - %second input 'false' means that this is *not* a settings - %initialization, so some parameter/values will be disallowed - newSettings = validateSettings(this,false,varargin{:}); - - %write the solver settings. C-mex does not check input - %data or protect against disallowed parameter modifications - osqp_mex('update_settings', this.objectHandle, newSettings); - - end - - %% - function [n,m] = get_dimensions(this) - % GET_DIMENSIONS get the number of variables and constraints - - [n,m] = osqp_mex('get_dimensions', this.objectHandle); - - end - - %% - function update(this,varargin) - % UPDATE modify the linear cost term and/or lower and upper bounds - - %second input 'false' means that this is *not* a settings - %initialization, so some parameter/values will be disallowed - allowedFields = {'q','l','u','Px','Px_idx','Ax','Ax_idx'}; - - if(isempty(varargin)) - return; - elseif(length(varargin) == 1) - if(~isstruct(varargin{1})) - error('Single input should be a structure with new problem data'); - else - newData = varargin{1}; - end - else % param / value style assumed - newData = struct(varargin{:}); - end - - %check for unknown fields - newFields = fieldnames(newData); - badFieldsIdx = find(~ismember(newFields,allowedFields)); - if(~isempty(badFieldsIdx)) - error('Unrecognized input field ''%s'' detected',newFields{badFieldsIdx(1)}); - end - - %get all of the terms. Nonexistent fields will be passed - %as empty mxArrays - try q = double(full(newData.q(:))); catch q = []; end - try l = double(full(newData.l(:))); catch l = []; end - try u = double(full(newData.u(:))); catch u = []; end - try Px = double(full(newData.Px(:))); catch Px = []; end - try Px_idx = double(full(newData.Px_idx(:))); catch Px_idx = []; end - try Ax = double(full(newData.Ax(:))); catch Ax = []; end - try Ax_idx = double(full(newData.Ax_idx(:))); catch Ax_idx = []; end - - [n,m] = get_dimensions(this); - - assert(isempty(q) || length(q) == n, 'input ''q'' is the wrong size'); - assert(isempty(l) || length(l) == m, 'input ''u'' is the wrong size'); - assert(isempty(u) || length(u) == m, 'input ''l'' is the wrong size'); - assert(isempty(Px) || isempty(Px_idx) || length(Px) == length(Px_idx), ... - 'inputs ''Px'' and ''Px_idx'' must be the same size'); - assert(isempty(Ax) || isempty(Ax_idx) || length(Ax) == length(Ax_idx), ... - 'inputs ''Ax'' and ''Ax_idx'' must be the same size'); - - % Adjust index of Px_idx and Ax_idx to match 0-based indexing - % in C - if (~isempty(Px_idx)) - Px_idx = Px_idx - 1; - end - if (~isempty(Ax_idx)) - Ax_idx = Ax_idx - 1; - end - - % Convert infinity values to OSQP_INFTY - if (~isempty(u)) - u = min(u, osqp.constant('OSQP_INFTY')); - end - if (~isempty(l)) - l = max(l, -osqp.constant('OSQP_INFTY')); - end - - %write the new problem data. C-mex does not protect - %against unknown fields, but will handle empty values - osqp_mex('update', this.objectHandle, ... - q, l, u, Px, Px_idx, length(Px), Ax, Ax_idx, length(Ax)); - - end - - %% - function varargout = setup(this, varargin) - % SETUP configure solver with problem data - % - % setup(P,q,A,l,u,options) - - nargin = length(varargin); - - %dimension checks on user data. Mex function does not - %perform any checks on inputs, so check everything here - assert(nargin >= 5, 'incorrect number of inputs'); - [P,q,A,l,u] = deal(varargin{1:5}); - - % - % Get problem dimensions - % - - % Get number of variables n - if (isempty(P)) - if (~isempty(q)) - n = length(q); - else - if (~isempty(A)) - n = size(A, 2); - else - error('The problem does not have any variables'); - end - end - else - n = size(P, 1); - end - - % Get number of constraints m - if (isempty(A)) - m = 0; - else - m = size(A, 1); - assert(size(A, 2) == n, 'Incorrect dimension of A'); - end - - % - % Create sparse matrices and full vectors if they are empty - % - - if (isempty(P)) - P = sparse(n, n); - else - P = sparse(P); - end - if (~istriu(P)) - P = triu(P); - end - if (isempty(q)) - q = zeros(n, 1); - else - q = full(q(:)); - end - - % Create proper constraints if they are not passed - if (isempty(A) && (~isempty(l) || ~isempty(u))) || ... - (~isempty(A) && (isempty(l) && isempty(u))) - error('A must be supplied together with at least one bound l or u'); - end - - if (~isempty(A) && isempty(l)) - l = -Inf(m, 1); - end - - if (~isempty(A) && isempty(u)) - u = Inf(m, 1); - end - - if (isempty(A)) - A = sparse(m, n); - l = -Inf(m, 1); - u = Inf(m, 1); - else - l = full(l(:)); - u = full(u(:)); - A = sparse(A); - end - - - % - % Check vector dimensions (not checked from the C solver) - % - - assert(length(q) == n, 'Incorrect dimension of q'); - assert(length(l) == m, 'Incorrect dimension of l'); - assert(length(u) == m, 'Incorrect dimension of u'); - - % - % Convert infinity values to OSQP_INFINITY - % - u = min(u, osqp.constant('OSQP_INFTY')); - l = max(l, -osqp.constant('OSQP_INFTY')); - - - %make a settings structure from the remainder of the arguments. - %'true' means that this is a settings initialization, so all - %parameter/values are allowed. No extra inputs will result - %in default settings being passed back - theSettings = validateSettings(this,true,varargin{6:end}); - - [varargout{1:nargout}] = osqp_mex('setup', this.objectHandle, n,m,P,q,A,l,u,theSettings); - - end - - - %% - - function warm_start(this, varargin) - % WARM_START warm start primal and/or dual variables - % - % warm_start('x', x, 'y', y) - % - % or warm_start('x', x) - % or warm_start('y', y) - - - % Get problem dimensions - [n, m] = get_dimensions(this); - - % Get data - allowedFields = {'x','y'}; - - if(isempty(varargin)) - return; - elseif(length(varargin) == 1) - if(~isstruct(varargin{1})) - error('Single input should be a structure with new problem data'); - else - newData = varargin{1}; - end - else % param / value style assumed - newData = struct(varargin{:}); - end - - %check for unknown fields - newFields = fieldnames(newData); - badFieldsIdx = find(~ismember(newFields,allowedFields)); - if(~isempty(badFieldsIdx)) - error('Unrecognized input field ''%s'' detected',newFields{badFieldsIdx(1)}); - end - - %get all of the terms. Nonexistent fields will be passed - %as empty mxArrays - try x = double(full(newData.x(:))); catch x = []; end - try y = double(full(newData.y(:))); catch y = []; end - - % Check dimensions - assert(isempty(x) || length(x) == n, 'input ''x'' is the wrong size'); - assert(isempty(y) || length(y) == m, 'input ''y'' is the wrong size'); - - - % Decide which function to call - if (~isempty(x) && isempty(y)) - osqp_mex('warm_start_x', this.objectHandle, x); - return; - end - - if (isempty(x) && ~isempty(y)) - osqp_mex('warm_start_y', this.objectHandle, y); - end - - if (~isempty(x) && ~isempty(y)) - osqp_mex('warm_start', this.objectHandle, x, y); - end - - if (isempty(x) && isempty(y)) - error('Unrecognized fields'); - end - - end - - %% - function varargout = solve(this, varargin) - % SOLVE solve the QP - - nargoutchk(0,1); %either return nothing (but still solve), or a single output structure - [out.x, out.y, out.prim_inf_cert, out.dual_inf_cert, out.info] = osqp_mex('solve', this.objectHandle); - if(nargout) - varargout{1} = out; - end - return; - end - - %% - function codegen(this, target_dir, varargin) - % CODEGEN generate C code for the parametric problem - % - % codegen(target_dir,options) - - % Parse input arguments - p = inputParser; - defaultProject = ''; - expectedProject = {'', 'Makefile', 'MinGW Makefiles', 'Unix Makefiles', 'CodeBlocks', 'Xcode'}; - defaultParams = 'vectors'; - expectedParams = {'vectors', 'matrices'}; - defaultMexname = 'emosqp'; - defaultFloat = false; - defaultLong = true; - defaultFW = false; - - addRequired(p, 'target_dir', @isstr); - addParameter(p, 'project_type', defaultProject, ... - @(x) ischar(validatestring(x, expectedProject))); - addParameter(p, 'parameters', defaultParams, ... - @(x) ischar(validatestring(x, expectedParams))); - addParameter(p, 'mexname', defaultMexname, @isstr); - addParameter(p, 'FLOAT', defaultFloat, @islogical); - addParameter(p, 'LONG', defaultLong, @islogical); - addParameter(p, 'force_rewrite', defaultFW, @islogical); - - parse(p, target_dir, varargin{:}); - - % Set internal variables - if strcmp(p.Results.parameters, 'vectors') - embedded = 1; - else - embedded = 2; - end - if p.Results.FLOAT - float_flag = 'ON'; - else - float_flag = 'OFF'; - end - if p.Results.LONG - long_flag = 'ON'; - else - long_flag = 'OFF'; - end - if strcmp(p.Results.project_type, 'Makefile') - if (ispc) - project_type = 'MinGW Makefiles'; % Windows - elseif (ismac || isunix) - project_type = 'Unix Makefiles'; % Unix - end - else - project_type = p.Results.project_type; - end - - % Check whether the specified directory already exists - if exist(target_dir, 'dir') - if p.Results.force_rewrite - rmdir(target_dir, 's'); - else - while(1) - prompt = sprintf('Directory "%s" already exists. Do you want to replace it? y/n [y]: ', target_dir); - str = input(prompt, 's'); - - if any(strcmpi(str, {'','y'})) - rmdir(target_dir, 's'); - break; - elseif strcmpi(str, 'n') - return; - end - end - end - end - - % Import OSQP path - [osqp_path,~,~] = fileparts(which('osqp.m')); - - % Add codegen directory to path - addpath(fullfile(osqp_path, 'codegen')); - - % Path of osqp module - cg_dir = fullfile(osqp_path, 'codegen'); - files_to_generate_path = fullfile(cg_dir, 'files_to_generate'); - - % Get workspace structure - work = osqp_mex('get_workspace', this.objectHandle); - - % Make target directory - fprintf('Creating target directories...\t\t\t\t\t'); - target_configure_dir = fullfile(target_dir, 'configure'); - target_include_dir = fullfile(target_dir, 'include'); - target_src_dir = fullfile(target_dir, 'src'); - - if ~exist(target_dir, 'dir') - mkdir(target_dir); - end - if ~exist(target_configure_dir, 'dir') - mkdir(target_configure_dir); - end - if ~exist(target_include_dir, 'dir') - mkdir(target_include_dir); - end - if ~exist(target_src_dir, 'dir') - mkdir(fullfile(target_src_dir, 'osqp')); - end - fprintf('[done]\n'); - - % Copy source files to target directory - fprintf('Copying OSQP source files...\t\t\t\t\t'); - cdir = fullfile(cg_dir, 'sources', 'src'); - cfiles = dir(fullfile(cdir, '*.c')); - for i = 1 : length(cfiles) - if embedded == 1 - % Do not copy kkt.c if embedded is 1 - if ~strcmp(cfiles(i).name, 'kkt.c') - copyfile(fullfile(cdir, cfiles(i).name), ... - fullfile(target_src_dir, 'osqp', cfiles(i).name)); - end - else - copyfile(fullfile(cdir, cfiles(i).name), ... - fullfile(target_src_dir, 'osqp', cfiles(i).name)); - end - end - configure_dir = fullfile(cg_dir, 'sources', 'configure'); - configure_files = dir(fullfile(configure_dir, '*.h.in')); - for i = 1 : length(configure_files) - copyfile(fullfile(configure_dir, configure_files(i).name), ... - fullfile(target_configure_dir, configure_files(i).name)); - end - hdir = fullfile(cg_dir, 'sources', 'include'); - hfiles = dir(fullfile(hdir, '*.h')); - for i = 1 : length(hfiles) - if embedded == 1 - % Do not copy kkt.h if embedded is 1 - if ~strcmp(hfiles(i).name, 'kkt.h') - copyfile(fullfile(hdir, hfiles(i).name), ... - fullfile(target_include_dir, hfiles(i).name)); - end - else - copyfile(fullfile(hdir, hfiles(i).name), ... - fullfile(target_include_dir, hfiles(i).name)); - end - end - - % Copy cmake files - copyfile(fullfile(cdir, 'CMakeLists.txt'), ... - fullfile(target_src_dir, 'osqp', 'CMakeLists.txt')); - copyfile(fullfile(hdir, 'CMakeLists.txt'), ... - fullfile(target_include_dir, 'CMakeLists.txt')); - fprintf('[done]\n'); - - % Copy example.c - copyfile(fullfile(files_to_generate_path, 'example.c'), target_src_dir); - - % Render CMakeLists.txt - fidi = fopen(fullfile(files_to_generate_path, 'CMakeLists.txt'),'r'); - fido = fopen(fullfile(target_dir, 'CMakeLists.txt'),'w'); - while ~feof(fidi) - l = fgetl(fidi); % read line - % Replace EMBEDDED_FLAG in CMakeLists.txt by a numerical value - newl = strrep(l, 'EMBEDDED_FLAG', num2str(embedded)); - fprintf(fido, '%s\n', newl); - end - fclose(fidi); - fclose(fido); - - % Render workspace.h and workspace.c - work_hfile = fullfile(target_include_dir, 'workspace.h'); - work_cfile = fullfile(target_src_dir, 'osqp', 'workspace.c'); - fprintf('Generating workspace.h/.c...\t\t\t\t\t\t'); - render_workspace(work, work_hfile, work_cfile, embedded); - fprintf('[done]\n'); - - % Create project - if ~isempty(project_type) - - % Extend path for CMake mac (via Homebrew) - PATH = getenv('PATH'); - if ((ismac) && (isempty(strfind(PATH, '/usr/local/bin')))) - setenv('PATH', [PATH ':/usr/local/bin']); - end - - fprintf('Creating project...\t\t\t\t\t\t\t\t'); - orig_dir = pwd; - cd(target_dir); - mkdir('build') - cd('build'); - cmd = sprintf('cmake -G "%s" ..', project_type); - [status, output] = system(cmd); - if(status) - fprintf('\n'); - fprintf(output); - error('Error configuring CMake environment'); - else - fprintf('[done]\n'); - end - cd(orig_dir); - end - - % Make mex interface to the generated code - mex_cfile = fullfile(files_to_generate_path, 'emosqp_mex.c'); - make_emosqp(target_dir, mex_cfile, embedded, float_flag, long_flag); - - % Rename the mex file - old_mexfile = ['emosqp_mex.', mexext]; - new_mexfile = [p.Results.mexname, '.', mexext]; - movefile(old_mexfile, new_mexfile); - - end - - end -end - - - -function currentSettings = validateSettings(this,isInitialization,varargin) - -%don't allow these fields to be changed -unmodifiableFields = {'scaling', 'linsys_solver'}; - -%get the current settings -if(isInitialization) - currentSettings = osqp_mex('default_settings', this.objectHandle); -else - currentSettings = osqp_mex('current_settings', this.objectHandle); -end - -%no settings passed -> return defaults -if(isempty(varargin)) - return; -end - -%check for structure style input -if(isstruct(varargin{1})) - newSettings = varargin{1}; - assert(length(varargin) == 1, 'too many input arguments'); -else - newSettings = struct(varargin{:}); -end - -%get the osqp settings fields -currentFields = fieldnames(currentSettings); - -%get the requested fields in the update -newFields = fieldnames(newSettings); - -%check for unknown parameters -badFieldsIdx = find(~ismember(newFields,currentFields)); -if(~isempty(badFieldsIdx)) - error('Unrecognized solver setting ''%s'' detected',newFields{badFieldsIdx(1)}); -end - -%convert linsys_solver string to integer -if ismember('linsys_solver',newFields) - if ~ischar(newSettings.linsys_solver) - error('Setting linsys_solver is required to be a string.'); - end - % Convert linsys_solver to number - newSettings.linsys_solver = string_to_linsys_solver(newSettings.linsys_solver); -end - - -%check for disallowed fields if this in not an initialization call -if(~isInitialization) - badFieldsIdx = find(ismember(newFields,unmodifiableFields)); - for i = badFieldsIdx(:)' - if(~isequal(newSettings.(newFields{i}),currentSettings.(newFields{i}))) - error('Solver setting ''%s'' can only be changed at solver initialization.', newFields{i}); - end - end -end - - -%check that everything is a nonnegative scalar (this check is already -%performed in C) -% for i = 1:length(newFields) -% val = double(newSettings.(newFields{i})); -% assert(isscalar(val) & isnumeric(val) & val >= 0, ... -% 'Solver setting ''%s'' not specified as nonnegative scalar', newFields{i}); -% end - -%everything checks out - merge the newSettings into the current ones -for i = 1:length(newFields) - currentSettings.(newFields{i}) = double(newSettings.(newFields{i})); -end - - -end - -function [linsys_solver_string] = linsys_solver_to_string(linsys_solver) -% Convert linear systme solver integer to stringh -switch linsys_solver - case osqp.constant('OSQP_UNKNOWN_SOLVER') - linsys_solver_string = 'unknown solver'; - case osqp.constant('OSQP_DIRECT_SOLVER') - linsys_solver_string = 'direct solver'; - case osqp.constant('OSQP_INDIRECT_SOLVER') - linsys_solver_string = 'indirect solver'; - otherwise - error('Unrecognized linear system solver.'); -end -end - - - -function [linsys_solver] = string_to_linsys_solver(linsys_solver_string) -linsys_solver_string = lower(linsys_solver_string); -switch linsys_solver_string - case 'unknown solver' - linsys_solver = osqp.constant('OSQP_UNKNOWN_SOLVER'); - case 'direct solver' - linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); - case 'indirect solver' - linsys_solver = osqp.constant('OSQP_INDIRECT_SOLVER'); - % Default solver: QDLDL - case '' - linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); - otherwise - warning('Linear system solver not recognized. Using default solver OSQP_DIRECT_SOLVER.') - linsys_solver = osqp.constant('OSQP_DIRECT_SOLVER'); -end -end - - diff --git a/run_osqp_tests.m b/run_osqp_tests.m index 29d9d18..2bfca1c 100644 --- a/run_osqp_tests.m +++ b/run_osqp_tests.m @@ -1,7 +1,7 @@ import matlab.unittest.TestSuite; -[osqp_path,~,~] = fileparts(which('osqp.m')); -unittest_dir = fullfile(osqp_path, 'unittests'); +[osqp_classpath,~,~] = fileparts( mfilename( 'fullpath' ) ); +unittest_dir = fullfile(osqp_classpath, 'unittests'); suiteFolder = TestSuite.fromFolder(unittest_dir); % Solve individual test file From e6a2ca8fa1b323e8eb53da9a884e951cdd41dcb1 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Fri, 17 Nov 2023 12:43:45 +0000 Subject: [PATCH 21/36] Expose rho update function over the mex interface --- @osqp/osqp.m | 15 --------------- @osqp/update_settings.m | 25 +++++++++++++++++++++++++ c_sources/osqp_mex.cpp | 16 +++++++++++++--- 3 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 @osqp/update_settings.m diff --git a/@osqp/osqp.m b/@osqp/osqp.m index 0cfda3e..d26fffc 100644 --- a/@osqp/osqp.m +++ b/@osqp/osqp.m @@ -38,7 +38,6 @@ % Convert linsys solver to string out.linsys_solver = linsys_solver_to_string(out.linsys_solver); - end %% @@ -79,19 +78,6 @@ function delete(this) % Convert linsys solver to string out.linsys_solver = linsys_solver_to_string(out.linsys_solver); - - end - - function update_settings(this, varargin) - % UPDATE_SETTINGS update the current solver settings structure - - %second input 'false' means that this is *not* a settings - %initialization, so some parameter/values will be disallowed - newSettings = validate_settings(this, false, varargin{:}); - - %write the solver settings. C-mex does not check input - %data or protect against disallowed parameter modifications - osqp_mex('update_settings', this.objectHandle, newSettings); end %% @@ -99,7 +85,6 @@ function update_settings(this, varargin) % GET_DIMENSIONS get the number of variables and constraints [n,m] = osqp_mex('get_dimensions', this.objectHandle); - end end end \ No newline at end of file diff --git a/@osqp/update_settings.m b/@osqp/update_settings.m new file mode 100644 index 0000000..233710d --- /dev/null +++ b/@osqp/update_settings.m @@ -0,0 +1,25 @@ +function update_settings(this, varargin) + % UPDATE_SETTINGS update the current solver settings structure + + % Check for structure style input + if(isstruct(varargin{1})) + newSettings = varargin{1}; + assert(length(varargin) == 1, 'too many input arguments'); + else + newSettings = struct(varargin{:}); + end + + % Rho update must be handled special + if( isfield(newSettings, 'rho') ) + osqp_mex('update_rho', this.objectHandle, newSettings.rho); + newSettings = rmfield(newSettings, 'rho'); + end + + % Second input 'false' means that this is *not* a settings + % initialization, so some parameter/values will be disallowed + newSettings = validate_settings(this, false, varargin{:}); + + % Write the solver settings. C-mex does not check input + % data or protect against disallowed parameter modifications + osqp_mex('update_settings', this.objectHandle, newSettings); +end \ No newline at end of file diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 5b0b93b..3c52819 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -158,6 +158,19 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } + // Update rho value + if (!strcmp("update_rho", cmd)) { + //throw an error if this is called before solver is configured + if(!osqpData->solver){ + mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + } + + OSQPFloat rho = (OSQPFloat)mxGetScalar(prhs[2]); + + osqp_update_rho(osqpData->solver, rho); + return; + } + // report the default settings if (!strcmp("default_settings", cmd)) { // Warn if other commands were ignored @@ -822,9 +835,6 @@ void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ update_template->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); osqp_update_settings(osqpSolver, update_template); - //rho needs to be updated separetly, it is not updated in osqp_update_settings - OSQPFloat rho_new = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); - if (rho_new != osqpSolver->settings->rho) osqp_update_rho(osqpSolver, rho_new); if (update_template) c_free(update_template); } \ No newline at end of file From 2f5aba317ad9ced5c4b7133cb075d9380beebc2d Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Fri, 17 Nov 2023 14:48:16 +0000 Subject: [PATCH 22/36] Simplify the constant generation --- c_sources/CMakeLists.txt | 4 ++ c_sources/osqp_mex.cpp | 116 ++++++++++++++------------------------- 2 files changed, 45 insertions(+), 75 deletions(-) diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt index f35ceaa..ae1c798 100644 --- a/c_sources/CMakeLists.txt +++ b/c_sources/CMakeLists.txt @@ -10,6 +10,10 @@ message( STATUS "Matlab root is " ${Matlab_ROOT_DIR} ) include_directories( ${Matlab_INCLUDE_DIRS} ) +# The mex interface uses C++11 +set( CMAKE_CXX_STANDARD 11 ) +set( CMAKE_CXX_STANDARD_REQUIRED ON ) + if( CMAKE_COMPILER_IS_GNUCXX ) # Add debug symbols and optimizations to the build set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2" ) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 3c52819..1fa3e5f 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -4,6 +4,8 @@ #include "osqp.h" #include "memory_matlab.h" +#include + //c_int is replaced with OSQPInt //c_float is replaced with OSQPFloat @@ -503,91 +505,55 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } if (!strcmp("constant", cmd)) { // Return solver constants - - char constant[32]; - mxGetString(prhs[2], constant, sizeof(constant)); - - if (!strcmp("OSQP_INFTY", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_INFTY); - return; - } - if (!strcmp("OSQP_NAN", constant)){ - plhs[0] = mxCreateDoubleScalar(mxGetNaN()); - return; - } - - if (!strcmp("OSQP_SOLVED", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_SOLVED); + static std::map floatConstants{ + // Numerical constants + {"OSQP_INFTY", OSQP_INFTY} + }; + + static std::map intConstants{ + // Return codes + {"OSQP_SOLVED", OSQP_SOLVED}, + {"OSQP_SOLVED_INACCURATE", OSQP_SOLVED_INACCURATE}, + {"OSQP_UNSOLVED", OSQP_UNSOLVED}, + {"OSQP_PRIMAL_INFEASIBLE", OSQP_PRIMAL_INFEASIBLE}, + {"OSQP_PRIMAL_INFEASIBLE_INACCURATE", OSQP_PRIMAL_INFEASIBLE_INACCURATE}, + {"OSQP_DUAL_INFEASIBLE", OSQP_DUAL_INFEASIBLE}, + {"OSQP_DUAL_INFEASIBLE_INACCURATE", OSQP_DUAL_INFEASIBLE_INACCURATE}, + {"OSQP_MAX_ITER_REACHED", OSQP_MAX_ITER_REACHED}, + {"OSQP_NON_CVX", OSQP_NON_CVX}, + {"OSQP_TIME_LIMIT_REACHED", OSQP_TIME_LIMIT_REACHED}, + + // Linear system solvers + {"QDLDL_SOLVER", QDLDL_SOLVER}, + {"OSQP_UNKNOWN_SOLVER", OSQP_UNKNOWN_SOLVER}, + {"OSQP_DIRECT_SOLVER", OSQP_DIRECT_SOLVER}, + {"OSQP_INDIRECT_SOLVER", OSQP_INDIRECT_SOLVER} + }; + + char constant[64]; + int constantLength = mxGetN(prhs[2]) + 1; + mxGetString(prhs[2], constant, constantLength); + + auto ci = intConstants.find(constant); + + if(ci != intConstants.end()) { + plhs[0] = mxCreateDoubleScalar(ci->second); return; } - if (!strcmp("OSQP_SOLVED_INACCURATE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_SOLVED_INACCURATE); - return; - } + auto cf = floatConstants.find(constant); - if (!strcmp("OSQP_UNSOLVED", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_UNSOLVED); + if(cf != floatConstants.end()) { + plhs[0] = mxCreateDoubleScalar(cf->second); return; } - if (!strcmp("OSQP_PRIMAL_INFEASIBLE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_PRIMAL_INFEASIBLE); - return; - } - - if (!strcmp("OSQP_PRIMAL_INFEASIBLE_INACCURATE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_PRIMAL_INFEASIBLE_INACCURATE); - return; - } - - if (!strcmp("OSQP_DUAL_INFEASIBLE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_DUAL_INFEASIBLE); - return; - } - - if (!strcmp("OSQP_DUAL_INFEASIBLE_INACCURATE", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_DUAL_INFEASIBLE_INACCURATE); - return; - } - - if (!strcmp("OSQP_MAX_ITER_REACHED", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_MAX_ITER_REACHED); - return; - } - - if (!strcmp("OSQP_NON_CVX", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_NON_CVX); - return; - } - - if (!strcmp("OSQP_TIME_LIMIT_REACHED", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_TIME_LIMIT_REACHED); - return; - } - - // Linear system solvers - if (!strcmp("QDLDL_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(QDLDL_SOLVER); - return; - } - - if (!strcmp("OSQP_UNKNOWN_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_UNKNOWN_SOLVER); - return; - } - - if (!strcmp("OSQP_DIRECT_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_DIRECT_SOLVER); - return; - } - - if (!strcmp("OSQP_INDIRECT_SOLVER", constant)){ - plhs[0] = mxCreateDoubleScalar(OSQP_INDIRECT_SOLVER); + // NaN is special because we need the Matlab version + if (!strcmp("OSQP_NAN", constant)){ + plhs[0] = mxCreateDoubleScalar(mxGetNaN()); return; } - mexErrMsgTxt("Constant not recognized."); return; From c3d24fc29b7e2ebd8219430f683bb9e815f73e41 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Fri, 17 Nov 2023 16:06:44 +0000 Subject: [PATCH 23/36] Only compile the memory management functions once then link them --- c_sources/CMakeLists.txt | 1 + c_sources/memory_matlab.c | 19 +++++++++++++++++++ c_sources/memory_matlab.h | 23 +++++++++-------------- 3 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 c_sources/memory_matlab.c diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt index ae1c798..2e31ddd 100644 --- a/c_sources/CMakeLists.txt +++ b/c_sources/CMakeLists.txt @@ -66,6 +66,7 @@ endif() matlab_add_mex( NAME osqp_mex SRC ${CMAKE_CURRENT_SOURCE_DIR}/osqp_mex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/interrupt_matlab.c + ${CMAKE_CURRENT_SOURCE_DIR}/memory_matlab.c LINK_TO osqpstatic ${UT_LIBRARY} # Force compilation in the traditional C API (equivalent to the -R2017b flag) diff --git a/c_sources/memory_matlab.c b/c_sources/memory_matlab.c new file mode 100644 index 0000000..c3b06a8 --- /dev/null +++ b/c_sources/memory_matlab.c @@ -0,0 +1,19 @@ +#include + +void* c_calloc(size_t num, size_t size) { + void *m = mxCalloc(num, size); + mexMakeMemoryPersistent(m); + return m; +} + +void* c_malloc(size_t size) { + void *m = mxMalloc(size); + mexMakeMemoryPersistent(m); + return m; +} + +void* c_realloc(void *ptr, size_t size) { + void *m = mxRealloc(ptr, size); + mexMakeMemoryPersistent(m); + return m; +} \ No newline at end of file diff --git a/c_sources/memory_matlab.h b/c_sources/memory_matlab.h index c8276b8..abd48b6 100644 --- a/c_sources/memory_matlab.h +++ b/c_sources/memory_matlab.h @@ -1,22 +1,17 @@ /* Memory managment for MATLAB */ #include "mex.h" -static void* c_calloc(size_t num, size_t size) { - void *m = mxCalloc(num, size); - mexMakeMemoryPersistent(m); - return m; -} -static void* c_malloc(size_t size) { - void *m = mxMalloc(size); - mexMakeMemoryPersistent(m); - return m; -} +#ifdef __cplusplus +extern "C" { +#endif + + void* c_calloc(size_t num, size_t size); + void* c_malloc(size_t size); + void* c_realloc(void *ptr, size_t size); -static void* c_realloc(void *ptr, size_t size) { - void *m = mxRealloc(ptr, size); - mexMakeMemoryPersistent(m); - return m; +#ifdef __cplusplus } +#endif #define c_free mxFree \ No newline at end of file From cc96cf2df68557dc12bf86e0dbf73d047dc7d63c Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Mon, 20 Nov 2023 17:11:35 +0000 Subject: [PATCH 24/36] Restructure settings handling to make it easier to maintain --- c_sources/CMakeLists.txt | 1 + c_sources/osqp_mex.cpp | 239 ++++++---------------------------- c_sources/settings_matlab.cpp | 103 +++++++++++++++ c_sources/settings_matlab.h | 125 ++++++++++++++++++ 4 files changed, 269 insertions(+), 199 deletions(-) create mode 100644 c_sources/settings_matlab.cpp create mode 100644 c_sources/settings_matlab.h diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt index 2e31ddd..86a1ec3 100644 --- a/c_sources/CMakeLists.txt +++ b/c_sources/CMakeLists.txt @@ -67,6 +67,7 @@ matlab_add_mex( NAME osqp_mex SRC ${CMAKE_CURRENT_SOURCE_DIR}/osqp_mex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/interrupt_matlab.c ${CMAKE_CURRENT_SOURCE_DIR}/memory_matlab.c + ${CMAKE_CURRENT_SOURCE_DIR}/settings_matlab.cpp LINK_TO osqpstatic ${UT_LIBRARY} # Force compilation in the traditional C API (equivalent to the -R2017b flag) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 1fa3e5f..d97b5d4 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -1,10 +1,13 @@ +#include + #include "mex.h" #include "matrix.h" -#include "osqp_mex.hpp" #include "osqp.h" -#include "memory_matlab.h" -#include +// Mex-specific functionality +#include "osqp_mex.hpp" +#include "memory_matlab.h" +#include "settings_matlab.h" //c_int is replaced with OSQPInt //c_float is replaced with OSQPFloat @@ -30,36 +33,6 @@ const char* OSQP_INFO_FIELDS[] = {"status", //char* "run_time", //OSQPFloat }; -const char* OSQP_SETTINGS_FIELDS[] = {"device", //OSQPInt - "linsys_solver", //enum osqp_linsys_solver_type - "verbose", //OSQPInt - "warm_starting", //OSQPInt - "scaling", //OSQPInt - "polishing", //OSQPInt - "rho", //OSQPFloat - "rho_is_vec", //OSQPInt - "sigma", //OSQPFloat - "alpha", //OSQPFloat - "cg_max_iter", //OSQPInt - "cg_tol_reduction", //OSQPInt - "cg_tol_fraction", //OSQPFloat - "cg_precond", //osqp_precond_type - "adaptive_rho", //OSQPInt - "adaptive_rho_interval", //OSQPInt - "adaptive_rho_fraction", //OSQPFloat - "adaptive_rho_tolerance", //OSQPFloat - "max_iter", //OSQPInt - "eps_abs", //OSQPFloat - "eps_rel", //OSQPFloat - "eps_prim_inf", //OSQPFloat - "eps_dual_inf", //OSQPFloat - "scaled_termination", //OSQPInt - "check_termination", //OSQPInt - "time_limit", //OSQPFloat - "delta", //OSQPFloat - "polish_refine_iter", //OSQPInt - }; - #define NEW_SETTINGS_TOL (1e-10) // wrapper class for all osqp data and settings @@ -74,15 +47,12 @@ class OsqpData OSQPSolver* initializeOSQPSolver(); void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len); void setToNaN(double* arr_out, OSQPInt len); -void copyMxStructToSettings(const mxArray*, OSQPSettings*); -void copyUpdatedSettingsToWork(const mxArray*, OSQPSolver*); //void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len); //DELETE HERE void freeCscMatrix(OSQPCscMatrix* M); OSQPInt* copyToOSQPIntVector(mwIndex * vecData, OSQPInt numel); OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel); OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel); mxArray* copyInfoToMxStruct(OSQPInfo* info); -mxArray* copySettingsToMxStruct(OSQPSettings* settings); void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) @@ -136,28 +106,33 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // report the current settings if (!strcmp("current_settings", cmd)) { - //throw an error if this is called before solver is configured - if(!osqpData->solver) mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); - if(!osqpData->solver->settings){ - mexErrMsgTxt("Solver settings is uninitialized. No settings have been configured."); - } - //report the current settings - plhs[0] = copySettingsToMxStruct(osqpData->solver->settings); - return; + // Throw an error if this is called before solver is configured + if(!osqpData->solver) { + mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + } + if(!osqpData->solver->settings) { + mexErrMsgTxt("Solver settings is uninitialized. No settings have been configured."); + } + + // Report the current settings + OSQPSettingsWrapper settings(osqpData->solver->settings); + plhs[0] = settings.GetMxStruct(); + return; } // write_settings if (!strcmp("update_settings", cmd)) { - //overwrite the current settings. Mex function is responsible - //for disallowing overwrite of selected settings after initialization, - //and for all error checking - //throw an error if this is called before solver is configured - if(!osqpData->solver){ - mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); - } + // Overwrite the current settings. Mex function is responsible + // for disallowing overwrite of selected settings after initialization, + // and for all error checking + // throw an error if this is called before solver is configured + if(!osqpData->solver){ + mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + } - copyUpdatedSettingsToWork(prhs[2],osqpData->solver); - return; + OSQPSettingsWrapper settings(prhs[2]); + osqp_update_settings(osqpData->solver, settings.GetOSQPSettings()); + return; } // Update rho value @@ -180,14 +155,12 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) mexWarnMsgTxt("Default settings: unexpected number of arguments."); - //Create a Settings structure in default form and report the results - //Useful for external solver packages (e.g. Yalmip) that want to - //know which solver settings are supported - OSQPSettings* defaults = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); - osqp_set_default_settings(defaults); - plhs[0] = copySettingsToMxStruct(defaults); - mxFree(defaults); - return; + // Create a Settings structure in default form and report the results + // Useful for external solver packages (e.g. Yalmip) that want to + // know which solver settings are supported + OSQPSettingsWrapper settings; + plhs[0] = settings.GetMxStruct(); + return; } // setup @@ -196,8 +169,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if(osqpData->solver){ mexErrMsgTxt("Solver is already initialized with problem data."); } - //Create data and settings containers - OSQPSettings* settings = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); // handle the problem data first. Matlab-side // class wrapper is responsible for ensuring that @@ -234,18 +205,16 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) csc_set_data(dataA, dataM, dataN, Ap[dataN], Ax, Ai, Ap); // Create Settings - const mxArray* mxSettings = prhs[9]; - if(mxIsEmpty(mxSettings)){ - // use defaults - osqp_set_default_settings(settings); - } else { - //populate settings structure from mxArray input - copyMxStructToSettings(mxSettings, settings); + OSQPSettingsWrapper settings; + + if(!mxIsEmpty(prhs[9])){ + // Populate settings structure from mxArray input, otherwise the default settings are used + settings.ParseMxStruct(prhs[9]); } // Setup workspace //exitflag = osqp_setup(&(osqpData->work), data, settings); - exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings); + exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings.GetOSQPSettings()); //cleanup temporary structures // Data if (Px) c_free(Px); @@ -259,8 +228,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (dataU) c_free(dataU); if (dataP) c_free(dataP); if (dataA) c_free(dataA); - // Settings - if (settings) c_free(settings); // Report error (if any) if(exitflag){ @@ -678,129 +645,3 @@ mxArray* copyInfoToMxStruct(OSQPInfo* info){ return mxPtr; } - -mxArray* copySettingsToMxStruct(OSQPSettings* settings){ - - int nfields = sizeof(OSQP_SETTINGS_FIELDS) / sizeof(OSQP_SETTINGS_FIELDS[0]); - mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_SETTINGS_FIELDS); - - //map the OSQP_SETTINGS fields one at a time into mxArrays - //matlab handles everything as a double - mxSetField(mxPtr, 0, "rho", mxCreateDoubleScalar(settings->rho)); - mxSetField(mxPtr, 0, "sigma", mxCreateDoubleScalar(settings->sigma)); - mxSetField(mxPtr, 0, "scaling", mxCreateDoubleScalar(settings->scaling)); - mxSetField(mxPtr, 0, "adaptive_rho", mxCreateDoubleScalar(settings->adaptive_rho)); - mxSetField(mxPtr, 0, "adaptive_rho_interval", mxCreateDoubleScalar(settings->adaptive_rho_interval)); - mxSetField(mxPtr, 0, "adaptive_rho_tolerance", mxCreateDoubleScalar(settings->adaptive_rho_tolerance)); - mxSetField(mxPtr, 0, "adaptive_rho_fraction", mxCreateDoubleScalar(settings->adaptive_rho_fraction)); - mxSetField(mxPtr, 0, "max_iter", mxCreateDoubleScalar(settings->max_iter)); - mxSetField(mxPtr, 0, "eps_abs", mxCreateDoubleScalar(settings->eps_abs)); - mxSetField(mxPtr, 0, "eps_rel", mxCreateDoubleScalar(settings->eps_rel)); - mxSetField(mxPtr, 0, "eps_prim_inf", mxCreateDoubleScalar(settings->eps_prim_inf)); - mxSetField(mxPtr, 0, "eps_dual_inf", mxCreateDoubleScalar(settings->eps_dual_inf)); - mxSetField(mxPtr, 0, "alpha", mxCreateDoubleScalar(settings->alpha)); - mxSetField(mxPtr, 0, "linsys_solver", mxCreateDoubleScalar(settings->linsys_solver)); - mxSetField(mxPtr, 0, "delta", mxCreateDoubleScalar(settings->delta)); - mxSetField(mxPtr, 0, "polish_refine_iter", mxCreateDoubleScalar(settings->polish_refine_iter)); - mxSetField(mxPtr, 0, "verbose", mxCreateDoubleScalar(settings->verbose)); - mxSetField(mxPtr, 0, "scaled_termination", mxCreateDoubleScalar(settings->scaled_termination)); - mxSetField(mxPtr, 0, "check_termination", mxCreateDoubleScalar(settings->check_termination)); - mxSetField(mxPtr, 0, "warm_starting", mxCreateDoubleScalar(settings->warm_starting)); - mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); - mxSetField(mxPtr, 0, "device", mxCreateDoubleScalar(settings->device)); - mxSetField(mxPtr, 0, "polishing", mxCreateDoubleScalar(settings->polishing)); - mxSetField(mxPtr, 0, "rho_is_vec", mxCreateDoubleScalar(settings->rho_is_vec)); - mxSetField(mxPtr, 0, "cg_max_iter", mxCreateDoubleScalar(settings->cg_max_iter)); - mxSetField(mxPtr, 0, "cg_tol_reduction", mxCreateDoubleScalar(settings->cg_tol_reduction)); - mxSetField(mxPtr, 0, "cg_tol_fraction", mxCreateDoubleScalar(settings->cg_tol_fraction)); - mxSetField(mxPtr, 0, "time_limit", mxCreateDoubleScalar(settings->time_limit)); - mxSetField(mxPtr, 0, "cg_precond", mxCreateDoubleScalar(settings->cg_precond)); - return mxPtr; -} - - -// ====================================================================== - -void copyMxStructToSettings(const mxArray* mxPtr, OSQPSettings* settings){ - - //this function assumes that only a complete and validated structure - //will be passed. matlab mex-side function is responsible for checking - //structure validity - - //map the OSQP_SETTINGS fields one at a time into mxArrays - //matlab handles everything as a double - settings->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); - settings->sigma = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "sigma")); - settings->scaling = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaling")); - settings->adaptive_rho = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho")); - settings->adaptive_rho_interval = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_interval")); - settings->adaptive_rho_tolerance = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_tolerance")); - settings->adaptive_rho_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_fraction")); - settings->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); - settings->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); - settings->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); - settings->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - settings->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - settings->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); - settings->linsys_solver = (enum osqp_linsys_solver_type) (OSQPInt) mxGetScalar(mxGetField(mxPtr, 0, "linsys_solver")); - settings->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); - settings->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); - settings->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); - settings->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); - settings->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); - settings->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); - settings->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); - settings->device = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "device")); - settings->polishing = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polishing")); - settings->rho_is_vec = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "rho_is_vec")); - settings->cg_max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_max_iter")); - settings->cg_tol_reduction = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_reduction")); - settings->cg_tol_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_fraction")); - settings->cg_precond = (osqp_precond_type) (OSQPInt) (mxGetField(mxPtr, 0, "cg_precond")); -} - -void copyUpdatedSettingsToWork(const mxArray* mxPtr ,OSQPSolver* osqpSolver){ - - OSQPInt exitflag; - //TODO (Amit): Update this - OSQPSettings* update_template = (OSQPSettings *)mxCalloc(1,sizeof(OSQPSettings)); - if (!update_template) mexErrMsgTxt("Failed to allocate a temporary OSQPSettings object."); - - update_template->device = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "device")); - update_template->linsys_solver = (enum osqp_linsys_solver_type)mxGetScalar(mxGetField(mxPtr, 0, "linsys_solver")); - update_template->verbose = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "verbose")); - update_template->warm_starting = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "warm_starting")); - update_template->scaling = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaling")); - update_template->polishing = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polishing")); - - update_template->rho = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "rho")); - update_template->rho_is_vec = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "rho_is_vec")); - update_template->sigma = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "sigma")); - update_template->alpha = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "alpha")); - - update_template->cg_max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_max_iter")); - update_template->cg_tol_reduction = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_reduction")); - update_template->cg_tol_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "cg_tol_fraction")); - update_template->cg_precond = (osqp_precond_type)mxGetScalar(mxGetField(mxPtr, 0, "cg_precond")); - - update_template->adaptive_rho = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho")); - update_template->adaptive_rho_interval = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_interval")); - update_template->adaptive_rho_fraction = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_fraction")); - update_template->adaptive_rho_tolerance = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "adaptive_rho_tolerance")); - - update_template->max_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "max_iter")); - update_template->eps_abs = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_abs")); - update_template->eps_rel = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_rel")); - update_template->eps_prim_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_prim_inf")); - update_template->eps_dual_inf = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "eps_dual_inf")); - update_template->scaled_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "scaled_termination")); - update_template->check_termination = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "check_termination")); - update_template->time_limit = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "time_limit")); - - update_template->delta = (OSQPFloat)mxGetScalar(mxGetField(mxPtr, 0, "delta")); - update_template->polish_refine_iter = (OSQPInt)mxGetScalar(mxGetField(mxPtr, 0, "polish_refine_iter")); - - osqp_update_settings(osqpSolver, update_template); - - if (update_template) c_free(update_template); -} \ No newline at end of file diff --git a/c_sources/settings_matlab.cpp b/c_sources/settings_matlab.cpp new file mode 100644 index 0000000..001375a --- /dev/null +++ b/c_sources/settings_matlab.cpp @@ -0,0 +1,103 @@ +#include + +#include "memory_matlab.h" +#include "settings_matlab.h" + +#include + + +void OSQPSettingsWrapper::registerFields() { + m_settings = static_cast(c_calloc(1, sizeof(OSQPSettings))); + + if(!m_settings) + mexErrMsgTxt("Failed to allocate a OSQPSettings object."); + + osqp_set_default_settings(m_settings); + + /* + * Register the mapping between struct field name and the settings memory location + */ + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->device, "device")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->linsys_solver, "linsys_solver")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->verbose, "verbose")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->warm_starting, "warm_starting")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->scaling, "scaling")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->polishing, "polishing")); + + // ADMM parameters + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->rho, "rho")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->rho_is_vec, "rho_is_vec")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->sigma, "sigma")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->alpha, "alpha")); + + // CG settings + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->cg_max_iter, "cg_max_iter")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->cg_tol_reduction, "cg_tol_reduction")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->cg_tol_fraction, "cg_tol_fraction")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->cg_precond, "cg_precond")); + + // adaptive rho logic + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->adaptive_rho, "adaptive_rho")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->adaptive_rho_interval, "adaptive_rho_interval")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->adaptive_rho_fraction, "adaptive_rho_fraction")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->adaptive_rho_tolerance, "adaptive_rho_tolerance")); + + // termination parameters + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->max_iter, "max_iter")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->eps_abs, "eps_abs")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->eps_rel, "eps_rel")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->eps_prim_inf, "eps_prim_inf")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->eps_dual_inf, "eps_dual_inf")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->scaled_termination, "scaled_termination")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->check_termination, "check_termination")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->time_limit, "time_limit")); + + // polishing parameters + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->delta, "delta")); + m_settingsFields.push_back(new OSQPSettingsField(&m_settings->polish_refine_iter, "polish_refine_iter")); +} + + +OSQPSettingsWrapper::~OSQPSettingsWrapper() { + for(auto& s : m_settingsFields) { + delete s; + } + + c_free(m_settings); +} + + +mxArray* OSQPSettingsWrapper::GetMxStruct() { + // No fields are added right now, they are added in the for loop when they are set + mxArray* mxSettings = mxCreateStructMatrix(1, 1, 0, NULL); + + // Copy the current settings into the struct to return + for(const auto& s : m_settingsFields) { + s->ToMxStruct(mxSettings); + } + + return mxSettings; +} + + +void OSQPSettingsWrapper::ParseMxStruct(const mxArray* aStruct) { + for(const auto& s : m_settingsFields) { + s->ToOSQPSettings(aStruct); + } +} + + +OSQPSettings* OSQPSettingsWrapper::GetOSQPSettingsCopy() { + // Allocate the default settings + OSQPSettings* ret = static_cast(c_calloc(1, sizeof(OSQPSettings))); + + // Copy the current settings for their return + std::memcpy(ret, m_settings, sizeof(ret)); + + return ret; +} + + +void OSQPSettingsWrapper::ParseOSQPSettings(const OSQPSettings* aSettings) { + std::memcpy(m_settings, aSettings, sizeof(m_settings)); +} diff --git a/c_sources/settings_matlab.h b/c_sources/settings_matlab.h new file mode 100644 index 0000000..28ee832 --- /dev/null +++ b/c_sources/settings_matlab.h @@ -0,0 +1,125 @@ +#ifndef SETTINGS_MATLAB_H_ +#define SETTINGS_MATLAB_H_ + +#include +#include +#include + +#include +#include +#include + +/* + * Base class used to store the templated settings field types. + */ +class OSQPSettingsFieldBase { +public: + OSQPSettingsFieldBase() {} + + virtual void ToMxStruct(mxArray* aStruct) = 0; + virtual void ToOSQPSettings(const mxArray* aStruct) = 0; +}; + +template +class OSQPSettingsField : public OSQPSettingsFieldBase { +public: + OSQPSettingsField(T* aSettingPtr, std::string aName) : + m_settingsPtr(aSettingPtr), + m_name(aName) { + } + + /* + * Set the field in the given Matlab struct to the value of this settings field + */ + void ToMxStruct(mxArray* aStruct) override { + mxAddField(aStruct, m_name.data()); + mxSetField(aStruct, 0, m_name.data(), mxCreateDoubleScalar(*m_settingsPtr)); + } + + /* + * Set the field in the internal OSQPSettings struct with the data from aStruct + */ + void ToOSQPSettings(const mxArray* aStruct) override { + *(m_settingsPtr) = static_cast(mxGetScalar(mxGetField(aStruct, 0, m_name.data()))); + } + +private: + T* m_settingsPtr; + std::string m_name; +}; + +class OSQPSettingsWrapper { +public: + /* + * Initialize the settings wrapper using the default settings. + */ + OSQPSettingsWrapper() { + // Allocate the default settings and register field handlers + registerFields(); + } + + /* + * Initialize the settings wrapper using the values from aSettings. + */ + OSQPSettingsWrapper(const OSQPSettings* aSettings) { + // Allocate the default settings and register field handlers + registerFields(); + ParseOSQPSettings(aSettings); + } + + /* + * Initialize the settings wrapper using the values from aStruct + */ + OSQPSettingsWrapper(const mxArray* aStruct) { + // Allocate the default settings and register field handlers + registerFields(); + ParseMxStruct(aStruct); + } + + ~OSQPSettingsWrapper(); + + /* + * Return a Matlab structu populated with the values of the current settings + * contained in this wrapper. + * + * @return a Matlab struct with a copy of the settings (caller owns this copy and must free it) + */ + mxArray* GetMxStruct(); + + /* + * Read a Matlab struct and populate the wrapper with its values. + */ + void ParseMxStruct(const mxArray* aStruct); + + /* + * Get a copy of the settings contained inside this wrapper. + * + * @return a copy of the settings (caller owns this copy and must free it) + */ + OSQPSettings* GetOSQPSettingsCopy(); + + /* + * Get the pointer to the internal settings object. + */ + OSQPSettings* GetOSQPSettings() { + return m_settings; + } + + /* + * Read an existing OSQPSettings object into this wrapper. + * The settings are copied, so no ownership of the aSettings pointer is transferred. + */ + void ParseOSQPSettings(const OSQPSettings* aSettings); + +private: + // Register all the fields + void registerFields(); + + // All settings fields + std::vector m_settingsFields; + + // Base OSQP settings object. Owned by this wrapper. + OSQPSettings* m_settings; +}; + +#endif \ No newline at end of file From a6d5c701b1238c07b751f8da3643c74e7ff3ae2b Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Mon, 20 Nov 2023 17:42:57 +0000 Subject: [PATCH 25/36] Fix copying OSQP Settings objects and add better update tests --- c_sources/settings_matlab.cpp | 4 ++-- unittests/basic_tests.m | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/c_sources/settings_matlab.cpp b/c_sources/settings_matlab.cpp index 001375a..6607331 100644 --- a/c_sources/settings_matlab.cpp +++ b/c_sources/settings_matlab.cpp @@ -92,12 +92,12 @@ OSQPSettings* OSQPSettingsWrapper::GetOSQPSettingsCopy() { OSQPSettings* ret = static_cast(c_calloc(1, sizeof(OSQPSettings))); // Copy the current settings for their return - std::memcpy(ret, m_settings, sizeof(ret)); + std::memcpy(ret, m_settings, sizeof(OSQPSettings)); return ret; } void OSQPSettingsWrapper::ParseOSQPSettings(const OSQPSettings* aSettings) { - std::memcpy(m_settings, aSettings, sizeof(m_settings)); + std::memcpy(m_settings, aSettings, sizeof(OSQPSettings)); } diff --git a/unittests/basic_tests.m b/unittests/basic_tests.m index dedb073..c938a7a 100644 --- a/unittests/basic_tests.m +++ b/unittests/basic_tests.m @@ -124,6 +124,9 @@ function test_update_max_iter(testCase) opts.max_iter = 30; testCase.solver.update_settings(opts); + set = testCase.solver.current_settings(); + testCase.verifyEqual(set.max_iter, 30) + % Solve again results = testCase.solver.solve(); @@ -139,6 +142,9 @@ function test_update_early_termination(testCase) opts.check_termination = 0; testCase.solver.update_settings(opts); + set = testCase.solver.current_settings(); + testCase.verifyEqual(set.check_termination, 0) + % Solve again results = testCase.solver.solve(); @@ -184,6 +190,11 @@ function test_update_time_limit(testCase) 'max_iter', 2e9,... 'check_termination', 0); + set = testCase.solver.current_settings(); + testCase.verifyEqual(set.check_termination, 0) + testCase.verifyEqual(set.time_limit, 1e-6) + testCase.verifyEqual(set.max_iter, 2e9) + results = testCase.solver.solve(); testCase.verifyEqual(results.info.status_val, ... testCase.solver.constant('OSQP_TIME_LIMIT_REACHED')) From 7b7f38f93d9f52eed3f2b0c01e905ff8bc56870a Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Mon, 20 Nov 2023 17:48:01 +0000 Subject: [PATCH 26/36] [CI] Update OSQP Mex build step --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 059aef6..317b966 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: - name: Build OSQP interface uses: matlab-actions/run-command@v1 with: - command: osqp.build('osqp') + command: osqp.build('osqp_mex') - name: Run tests uses: matlab-actions/run-tests@v1 From 5e42763478ee898f3978edac49173d1d586c1ccc Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Tue, 21 Nov 2023 16:26:26 +0000 Subject: [PATCH 27/36] Refactor how structs are passed to allow cleaner passing of OSQPInfo --- c_sources/CMakeLists.txt | 3 +- c_sources/osqp_mex.cpp | 59 +-------- c_sources/osqp_struct.h | 205 +++++++++++++++++++++++++++++ c_sources/osqp_struct_info.cpp | 43 ++++++ c_sources/osqp_struct_settings.cpp | 61 +++++++++ c_sources/settings_matlab.cpp | 103 --------------- c_sources/settings_matlab.h | 125 ------------------ 7 files changed, 317 insertions(+), 282 deletions(-) create mode 100644 c_sources/osqp_struct.h create mode 100644 c_sources/osqp_struct_info.cpp create mode 100644 c_sources/osqp_struct_settings.cpp delete mode 100644 c_sources/settings_matlab.cpp delete mode 100644 c_sources/settings_matlab.h diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt index 86a1ec3..5c42782 100644 --- a/c_sources/CMakeLists.txt +++ b/c_sources/CMakeLists.txt @@ -67,7 +67,8 @@ matlab_add_mex( NAME osqp_mex SRC ${CMAKE_CURRENT_SOURCE_DIR}/osqp_mex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/interrupt_matlab.c ${CMAKE_CURRENT_SOURCE_DIR}/memory_matlab.c - ${CMAKE_CURRENT_SOURCE_DIR}/settings_matlab.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/osqp_struct_info.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/osqp_struct_settings.cpp LINK_TO osqpstatic ${UT_LIBRARY} # Force compilation in the traditional C API (equivalent to the -R2017b flag) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index d97b5d4..52ec218 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -6,8 +6,8 @@ // Mex-specific functionality #include "osqp_mex.hpp" +#include "osqp_struct.h" #include "memory_matlab.h" -#include "settings_matlab.h" //c_int is replaced with OSQPInt //c_float is replaced with OSQPFloat @@ -16,23 +16,6 @@ // enum linsys_solver_type { QDLDL_SOLVER, MKL_PARDISO_SOLVER }; #define QDLDL_SOLVER 0 //Based on the previous API -// all of the OSQP_INFO fieldnames as strings -const char* OSQP_INFO_FIELDS[] = {"status", //char* - "status_val", //OSQPInt - "status_polish", //OSQPInt - "obj_val", //OSQPFloat - "prim_res", //OSQPFloat - "dual_res", //OSQPFloat - "iter", //OSQPInt - "rho_updates", //OSQPInt - "rho_estimate", //OSQPFloat - "setup_time", //OSQPFloat - "solve_time", //OSQPFloat - "update_time", //OSQPFloat - "polish_time", //OSQPFloat - "run_time", //OSQPFloat - }; - #define NEW_SETTINGS_TOL (1e-10) // wrapper class for all osqp data and settings @@ -52,7 +35,6 @@ void freeCscMatrix(OSQPCscMatrix* M); OSQPInt* copyToOSQPIntVector(mwIndex * vecData, OSQPInt numel); OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel); OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel); -mxArray* copyInfoToMxStruct(OSQPInfo* info); void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) @@ -131,7 +113,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } OSQPSettingsWrapper settings(prhs[2]); - osqp_update_settings(osqpData->solver, settings.GetOSQPSettings()); + osqp_update_settings(osqpData->solver, settings.GetOSQPStruct()); return; } @@ -214,7 +196,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // Setup workspace //exitflag = osqp_setup(&(osqpData->work), data, settings); - exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings.GetOSQPSettings()); + exitflag = osqp_setup(&(osqpData->solver), dataP, dataQ, dataA, dataL, dataU, dataM, dataN, settings.GetOSQPStruct()); //cleanup temporary structures // Data if (Px) c_free(Px); @@ -466,7 +448,9 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) osqpData->solver->info->obj_val = mxGetNaN(); } - plhs[4] = copyInfoToMxStruct(osqpData->solver->info); // Info structure + // Populate the info structure + OSQPInfoWrapper info(osqpData->solver->info); + plhs[4] = info.GetMxStruct(); return; } @@ -614,34 +598,3 @@ void setToNaN(double* arr_out, OSQPInt len){ arr_out[i] = mxGetNaN(); } } - -mxArray* copyInfoToMxStruct(OSQPInfo* info){ - - //create mxArray with the right number of fields - int nfields = sizeof(OSQP_INFO_FIELDS) / sizeof(OSQP_INFO_FIELDS[0]); - mxArray* mxPtr = mxCreateStructMatrix(1,1,nfields,OSQP_INFO_FIELDS); - - //map the OSQP_INFO fields one at a time into mxArrays - //matlab all numeric values as doubles - mxSetField(mxPtr, 0, "iter", mxCreateDoubleScalar(info->iter)); - mxSetField(mxPtr, 0, "status", mxCreateString(info->status)); - mxSetField(mxPtr, 0, "status_val", mxCreateDoubleScalar(info->status_val)); - mxSetField(mxPtr, 0, "status_polish", mxCreateDoubleScalar(info->status_polish)); - mxSetField(mxPtr, 0, "obj_val", mxCreateDoubleScalar(info->obj_val)); - mxSetField(mxPtr, 0, "prim_res", mxCreateDoubleScalar(info->prim_res)); - mxSetField(mxPtr, 0, "dual_res", mxCreateDoubleScalar(info->dual_res)); - - mxSetField(mxPtr, 0, "setup_time", mxCreateDoubleScalar(info->setup_time)); - mxSetField(mxPtr, 0, "solve_time", mxCreateDoubleScalar(info->solve_time)); - mxSetField(mxPtr, 0, "update_time", mxCreateDoubleScalar(info->update_time)); - mxSetField(mxPtr, 0, "polish_time", mxCreateDoubleScalar(info->polish_time)); - mxSetField(mxPtr, 0, "run_time", mxCreateDoubleScalar(info->run_time)); - - - mxSetField(mxPtr, 0, "rho_updates", mxCreateDoubleScalar(info->rho_updates)); - mxSetField(mxPtr, 0, "rho_estimate", mxCreateDoubleScalar(info->rho_estimate)); - - - return mxPtr; - -} diff --git a/c_sources/osqp_struct.h b/c_sources/osqp_struct.h new file mode 100644 index 0000000..94a432f --- /dev/null +++ b/c_sources/osqp_struct.h @@ -0,0 +1,205 @@ +#ifndef OSQP_STRUCT_H_ +#define OSQP_STRUCT_H_ + +#include +#include +#include +#include + +#include +#include + +#include "memory_matlab.h" +#include + +/** + * Base class used to store the field types for a struct. + */ +class OSQPStructFieldBase { +public: + OSQPStructFieldBase() {} + + /** + * Set the field in the given Matlab struct to the value of this field + */ + virtual void ToMxStruct(mxArray* aStruct) = 0; + + /** + * Set the field in the internal struct with the data from aStruct + */ + virtual void ToOSQPStruct(const mxArray* aStruct) = 0; +}; + +/** + * Class to hold a numeric struct field (e.g. float/double/int/enum, etc.). + */ +template +class OSQPStructField : public OSQPStructFieldBase { +public: + OSQPStructField(T* aStructPtr, std::string aName) : + m_structPtr(aStructPtr), + m_name(aName) { + } + + void ToMxStruct(mxArray* aStruct) override { + mxAddField(aStruct, m_name.data()); + mxSetField(aStruct, 0, m_name.data(), mxCreateDoubleScalar(*m_structPtr)); + } + + void ToOSQPStruct(const mxArray* aStruct) override { + *(m_structPtr) = static_cast(mxGetScalar(mxGetField(aStruct, 0, m_name.data()))); + } + +private: + T* m_structPtr; + std::string m_name; +}; + +/** + * Class to hold a character array (actual array, not char* array) field in a struct. + */ +class OSQPStructFieldCharArray : public OSQPStructFieldBase { +public: + OSQPStructFieldCharArray(char* aStructPtr, size_t aLength, std::string aName) : + m_structPtr(aStructPtr), + m_name(aName), + m_length(aLength) { + } + + void ToMxStruct(mxArray* aStruct) override { + mxAddField(aStruct, m_name.data()); + mxSetField(aStruct, 0, m_name.data(), mxCreateString(m_structPtr)); + } + + void ToOSQPStruct(const mxArray* aStruct) override { + mxArray* tmp = mxGetField(aStruct, 0, m_name.data()); + mxGetString(tmp, m_structPtr, m_length); + } + +private: + char* m_structPtr; + std::string m_name; + size_t m_length; +}; + +/** + * Wrap a struct from OSQP to automatically transfer the data between OSQP and Matlab. + */ +template +class OSQPStructWrapper { +public: + /** + * Initialize the wrapper using the default values. + */ + OSQPStructWrapper() { + // Allocate the default struct and register field handlers + registerFields(); + } + + /** + * Initialize the wrapper using the values from the OSQP struct. + */ + OSQPStructWrapper(const T* aStruct) { + // Allocate the default struct and register field handlers + registerFields(); + ParseOSQPStruct(aStruct); + } + + /** + * Initialize the wrapper using the values from the Matlab struct + */ + OSQPStructWrapper(const mxArray* aStruct) { + // Allocate the default struct and register field handlers + registerFields(); + ParseMxStruct(aStruct); + } + + ~OSQPStructWrapper() { + for(auto& s : m_structFields) { + delete s; + } + + c_free(m_struct); + } + + /** + * Return a Matlab struct populated with the values of the current struct + * contained in this wrapper. + * + * @return a Matlab struct with a copy of the struct (caller owns this copy and must free it) + */ + mxArray* GetMxStruct() { + // No fields are added right now, they are added in the for loop when they are set + mxArray* matStruct = mxCreateStructMatrix(1, 1, 0, NULL); + + // Copy the current struct into the struct to return + for(const auto& s : m_structFields) { + s->ToMxStruct(matStruct); + } + + return matStruct; + } + + /** + * Read a Matlab struct and populate the wrapper with its values. + */ + void ParseMxStruct(const mxArray* aStruct) { + for(const auto& s : m_structFields) { + s->ToOSQPStruct(aStruct); + } + } + + /** + * Get a copy of the struct contained inside this wrapper. + * + * @return a copy of the struct (caller owns this copy and must free it) + */ + T* GetOSQPStructCopy() { + // Allocate the default struct + T* ret = static_cast(c_calloc(1, sizeof(T))); + + // Copy the current values for their return + std::memcpy(ret, m_struct, sizeof(T)); + return ret; + } + + /** + * Get the pointer to the internal struct object. + */ + T* GetOSQPStruct() { + return m_struct; + } + + /* + * Read an existing OSQP struct object into this wrapper. + * The struct elements are copied, so no ownership of the aStruct pointer is transferred. + */ + void ParseOSQPStruct(const T* aStruct) { + std::memcpy(m_struct, aStruct, sizeof(T)); + } + +private: + /** + * Register all the fields for the wrapper. + * This function should be specialized for each struct type to map the fields appropriately. + */ + void registerFields(); + + // All struct fields + std::vector m_structFields; + + // Base OSQP struct object. Owned by this wrapper. + T* m_struct; +}; + +/** + * Wrapper around the OSQPSettings struct + */ +typedef OSQPStructWrapper OSQPSettingsWrapper; + +/** + * Wrapper around the OSQPInfo struct + */ +typedef OSQPStructWrapper OSQPInfoWrapper; + +#endif \ No newline at end of file diff --git a/c_sources/osqp_struct_info.cpp b/c_sources/osqp_struct_info.cpp new file mode 100644 index 0000000..b454a1c --- /dev/null +++ b/c_sources/osqp_struct_info.cpp @@ -0,0 +1,43 @@ +#include +#include "osqp_struct.h" + + +/* + * Specialization of the struct wrapper for the OSQPInfo struct. + */ +template<> +void OSQPStructWrapper::registerFields() { + m_struct = static_cast(c_calloc(1, sizeof(OSQPInfo))); + + if(!m_struct) + mexErrMsgTxt("Failed to allocate a OSQPInfo object."); + + /* + * Register the mapping between struct field name and the info struct memory location + */ + // Solver status + m_structFields.push_back(new OSQPStructFieldCharArray(m_struct->status, 32, "status")); + m_structFields.push_back(new OSQPStructField(&m_struct->status_val, "status_val")); + m_structFields.push_back(new OSQPStructField(&m_struct->status_polish, "status_polish")); + + // Solution quality + m_structFields.push_back(new OSQPStructField(&m_struct->obj_val, "obj_val")); + m_structFields.push_back(new OSQPStructField(&m_struct->prim_res, "prim_res")); + m_structFields.push_back(new OSQPStructField(&m_struct->dual_res, "dual_res")); + + // Algorithm information + m_structFields.push_back(new OSQPStructField(&m_struct->iter, "iter")); + m_structFields.push_back(new OSQPStructField(&m_struct->rho_updates, "rho_updates")); + m_structFields.push_back(new OSQPStructField(&m_struct->rho_estimate, "rho_estimate")); + + // Timing information + m_structFields.push_back(new OSQPStructField(&m_struct->setup_time, "setup_time")); + m_structFields.push_back(new OSQPStructField(&m_struct->solve_time, "solve_time")); + m_structFields.push_back(new OSQPStructField(&m_struct->update_time, "update_time")); + m_structFields.push_back(new OSQPStructField(&m_struct->polish_time, "polish_time")); + m_structFields.push_back(new OSQPStructField(&m_struct->run_time, "run_time")); +} + + +// Instantiate the OSQPInfo wrapper class +template class OSQPStructWrapper; \ No newline at end of file diff --git a/c_sources/osqp_struct_settings.cpp b/c_sources/osqp_struct_settings.cpp new file mode 100644 index 0000000..97fc357 --- /dev/null +++ b/c_sources/osqp_struct_settings.cpp @@ -0,0 +1,61 @@ +#include +#include "osqp_struct.h" + +/* + * Specialization for the settings struct + */ +template<> +void OSQPStructWrapper::registerFields() { + m_struct = static_cast(c_calloc(1, sizeof(OSQPSettings))); + + if(!m_struct) + mexErrMsgTxt("Failed to allocate a OSQPSettings object."); + + osqp_set_default_settings(m_struct); + + /* + * Register the mapping between struct field name and the settings memory location + */ + m_structFields.push_back(new OSQPStructField(&m_struct->device, "device")); + m_structFields.push_back(new OSQPStructField(&m_struct->linsys_solver, "linsys_solver")); + m_structFields.push_back(new OSQPStructField(&m_struct->verbose, "verbose")); + m_structFields.push_back(new OSQPStructField(&m_struct->warm_starting, "warm_starting")); + m_structFields.push_back(new OSQPStructField(&m_struct->scaling, "scaling")); + m_structFields.push_back(new OSQPStructField(&m_struct->polishing, "polishing")); + + // ADMM parameters + m_structFields.push_back(new OSQPStructField(&m_struct->rho, "rho")); + m_structFields.push_back(new OSQPStructField(&m_struct->rho_is_vec, "rho_is_vec")); + m_structFields.push_back(new OSQPStructField(&m_struct->sigma, "sigma")); + m_structFields.push_back(new OSQPStructField(&m_struct->alpha, "alpha")); + + // CG settings + m_structFields.push_back(new OSQPStructField(&m_struct->cg_max_iter, "cg_max_iter")); + m_structFields.push_back(new OSQPStructField(&m_struct->cg_tol_reduction, "cg_tol_reduction")); + m_structFields.push_back(new OSQPStructField(&m_struct->cg_tol_fraction, "cg_tol_fraction")); + m_structFields.push_back(new OSQPStructField(&m_struct->cg_precond, "cg_precond")); + + // adaptive rho logic + m_structFields.push_back(new OSQPStructField(&m_struct->adaptive_rho, "adaptive_rho")); + m_structFields.push_back(new OSQPStructField(&m_struct->adaptive_rho_interval, "adaptive_rho_interval")); + m_structFields.push_back(new OSQPStructField(&m_struct->adaptive_rho_fraction, "adaptive_rho_fraction")); + m_structFields.push_back(new OSQPStructField(&m_struct->adaptive_rho_tolerance, "adaptive_rho_tolerance")); + + // termination parameters + m_structFields.push_back(new OSQPStructField(&m_struct->max_iter, "max_iter")); + m_structFields.push_back(new OSQPStructField(&m_struct->eps_abs, "eps_abs")); + m_structFields.push_back(new OSQPStructField(&m_struct->eps_rel, "eps_rel")); + m_structFields.push_back(new OSQPStructField(&m_struct->eps_prim_inf, "eps_prim_inf")); + m_structFields.push_back(new OSQPStructField(&m_struct->eps_dual_inf, "eps_dual_inf")); + m_structFields.push_back(new OSQPStructField(&m_struct->scaled_termination, "scaled_termination")); + m_structFields.push_back(new OSQPStructField(&m_struct->check_termination, "check_termination")); + m_structFields.push_back(new OSQPStructField(&m_struct->time_limit, "time_limit")); + + // polishing parameters + m_structFields.push_back(new OSQPStructField(&m_struct->delta, "delta")); + m_structFields.push_back(new OSQPStructField(&m_struct->polish_refine_iter, "polish_refine_iter")); +} + + +// Instantiate the OSQPSettings wrapper class +template class OSQPStructWrapper; \ No newline at end of file diff --git a/c_sources/settings_matlab.cpp b/c_sources/settings_matlab.cpp deleted file mode 100644 index 6607331..0000000 --- a/c_sources/settings_matlab.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include - -#include "memory_matlab.h" -#include "settings_matlab.h" - -#include - - -void OSQPSettingsWrapper::registerFields() { - m_settings = static_cast(c_calloc(1, sizeof(OSQPSettings))); - - if(!m_settings) - mexErrMsgTxt("Failed to allocate a OSQPSettings object."); - - osqp_set_default_settings(m_settings); - - /* - * Register the mapping between struct field name and the settings memory location - */ - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->device, "device")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->linsys_solver, "linsys_solver")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->verbose, "verbose")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->warm_starting, "warm_starting")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->scaling, "scaling")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->polishing, "polishing")); - - // ADMM parameters - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->rho, "rho")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->rho_is_vec, "rho_is_vec")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->sigma, "sigma")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->alpha, "alpha")); - - // CG settings - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->cg_max_iter, "cg_max_iter")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->cg_tol_reduction, "cg_tol_reduction")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->cg_tol_fraction, "cg_tol_fraction")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->cg_precond, "cg_precond")); - - // adaptive rho logic - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->adaptive_rho, "adaptive_rho")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->adaptive_rho_interval, "adaptive_rho_interval")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->adaptive_rho_fraction, "adaptive_rho_fraction")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->adaptive_rho_tolerance, "adaptive_rho_tolerance")); - - // termination parameters - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->max_iter, "max_iter")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->eps_abs, "eps_abs")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->eps_rel, "eps_rel")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->eps_prim_inf, "eps_prim_inf")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->eps_dual_inf, "eps_dual_inf")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->scaled_termination, "scaled_termination")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->check_termination, "check_termination")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->time_limit, "time_limit")); - - // polishing parameters - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->delta, "delta")); - m_settingsFields.push_back(new OSQPSettingsField(&m_settings->polish_refine_iter, "polish_refine_iter")); -} - - -OSQPSettingsWrapper::~OSQPSettingsWrapper() { - for(auto& s : m_settingsFields) { - delete s; - } - - c_free(m_settings); -} - - -mxArray* OSQPSettingsWrapper::GetMxStruct() { - // No fields are added right now, they are added in the for loop when they are set - mxArray* mxSettings = mxCreateStructMatrix(1, 1, 0, NULL); - - // Copy the current settings into the struct to return - for(const auto& s : m_settingsFields) { - s->ToMxStruct(mxSettings); - } - - return mxSettings; -} - - -void OSQPSettingsWrapper::ParseMxStruct(const mxArray* aStruct) { - for(const auto& s : m_settingsFields) { - s->ToOSQPSettings(aStruct); - } -} - - -OSQPSettings* OSQPSettingsWrapper::GetOSQPSettingsCopy() { - // Allocate the default settings - OSQPSettings* ret = static_cast(c_calloc(1, sizeof(OSQPSettings))); - - // Copy the current settings for their return - std::memcpy(ret, m_settings, sizeof(OSQPSettings)); - - return ret; -} - - -void OSQPSettingsWrapper::ParseOSQPSettings(const OSQPSettings* aSettings) { - std::memcpy(m_settings, aSettings, sizeof(OSQPSettings)); -} diff --git a/c_sources/settings_matlab.h b/c_sources/settings_matlab.h deleted file mode 100644 index 28ee832..0000000 --- a/c_sources/settings_matlab.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef SETTINGS_MATLAB_H_ -#define SETTINGS_MATLAB_H_ - -#include -#include -#include - -#include -#include -#include - -/* - * Base class used to store the templated settings field types. - */ -class OSQPSettingsFieldBase { -public: - OSQPSettingsFieldBase() {} - - virtual void ToMxStruct(mxArray* aStruct) = 0; - virtual void ToOSQPSettings(const mxArray* aStruct) = 0; -}; - -template -class OSQPSettingsField : public OSQPSettingsFieldBase { -public: - OSQPSettingsField(T* aSettingPtr, std::string aName) : - m_settingsPtr(aSettingPtr), - m_name(aName) { - } - - /* - * Set the field in the given Matlab struct to the value of this settings field - */ - void ToMxStruct(mxArray* aStruct) override { - mxAddField(aStruct, m_name.data()); - mxSetField(aStruct, 0, m_name.data(), mxCreateDoubleScalar(*m_settingsPtr)); - } - - /* - * Set the field in the internal OSQPSettings struct with the data from aStruct - */ - void ToOSQPSettings(const mxArray* aStruct) override { - *(m_settingsPtr) = static_cast(mxGetScalar(mxGetField(aStruct, 0, m_name.data()))); - } - -private: - T* m_settingsPtr; - std::string m_name; -}; - -class OSQPSettingsWrapper { -public: - /* - * Initialize the settings wrapper using the default settings. - */ - OSQPSettingsWrapper() { - // Allocate the default settings and register field handlers - registerFields(); - } - - /* - * Initialize the settings wrapper using the values from aSettings. - */ - OSQPSettingsWrapper(const OSQPSettings* aSettings) { - // Allocate the default settings and register field handlers - registerFields(); - ParseOSQPSettings(aSettings); - } - - /* - * Initialize the settings wrapper using the values from aStruct - */ - OSQPSettingsWrapper(const mxArray* aStruct) { - // Allocate the default settings and register field handlers - registerFields(); - ParseMxStruct(aStruct); - } - - ~OSQPSettingsWrapper(); - - /* - * Return a Matlab structu populated with the values of the current settings - * contained in this wrapper. - * - * @return a Matlab struct with a copy of the settings (caller owns this copy and must free it) - */ - mxArray* GetMxStruct(); - - /* - * Read a Matlab struct and populate the wrapper with its values. - */ - void ParseMxStruct(const mxArray* aStruct); - - /* - * Get a copy of the settings contained inside this wrapper. - * - * @return a copy of the settings (caller owns this copy and must free it) - */ - OSQPSettings* GetOSQPSettingsCopy(); - - /* - * Get the pointer to the internal settings object. - */ - OSQPSettings* GetOSQPSettings() { - return m_settings; - } - - /* - * Read an existing OSQPSettings object into this wrapper. - * The settings are copied, so no ownership of the aSettings pointer is transferred. - */ - void ParseOSQPSettings(const OSQPSettings* aSettings); - -private: - // Register all the fields - void registerFields(); - - // All settings fields - std::vector m_settingsFields; - - // Base OSQP settings object. Owned by this wrapper. - OSQPSettings* m_settings; -}; - -#endif \ No newline at end of file From 8025d54262258f57a4911be8da7e8bc079e56d52 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Tue, 21 Nov 2023 17:29:27 +0000 Subject: [PATCH 28/36] Rework how vectors are created and passed around --- c_sources/arrays_matlab.h | 43 ++++++++++++++++ c_sources/osqp_mex.cpp | 104 +++++++++----------------------------- 2 files changed, 66 insertions(+), 81 deletions(-) create mode 100644 c_sources/arrays_matlab.h diff --git a/c_sources/arrays_matlab.h b/c_sources/arrays_matlab.h new file mode 100644 index 0000000..0d5408b --- /dev/null +++ b/c_sources/arrays_matlab.h @@ -0,0 +1,43 @@ +#ifndef ARRAYS_MATLAB_H_ +#define ARRAYS_MATLAB_H_ + +#include + +/** + * Copy the data from one array to another provided array. + */ +template +void copyVector(outArr* out, inArr* in, OSQPInt numel) { + // Don't bother doing anything if there is no input data + if(!in || !out || (numel == 0)) + return; + + // Copy the data + for(OSQPInt i=0; i < numel; i++){ + out[i] = static_cast(in[i]); + } +} + + +/** + * Copy the data from one array to another newly allocated array. + * The caller gains ownership of the returned array. + */ +template +outArr* cloneVector(inArr* in, OSQPInt numel) { + // Don't bother doing anything if there is no input data + if(!in || (numel == 0)) + return NULL; + + // Allocate new array + outArr* out = static_cast(c_malloc(numel * sizeof(outArr))); + + if(!out) + mexErrMsgTxt("Failed to allocate a vector object."); + + // Copy the data + copyVector(out, in, numel); + return out; +} + +#endif \ No newline at end of file diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 52ec218..43baea2 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -7,6 +7,7 @@ // Mex-specific functionality #include "osqp_mex.hpp" #include "osqp_struct.h" +#include "arrays_matlab.h" #include "memory_matlab.h" //c_int is replaced with OSQPInt @@ -28,13 +29,8 @@ class OsqpData // internal utility functions OSQPSolver* initializeOSQPSolver(); -void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len); void setToNaN(double* arr_out, OSQPInt len); -//void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len); //DELETE HERE void freeCscMatrix(OSQPCscMatrix* M); -OSQPInt* copyToOSQPIntVector(mwIndex * vecData, OSQPInt numel); -OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel); -OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel); void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) @@ -168,21 +164,21 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPInt dataN = (OSQPInt)mxGetScalar(prhs[2]); OSQPInt dataM = (OSQPInt)mxGetScalar(prhs[3]); - OSQPFloat* dataQ = copyToOSQPFloatVector(mxGetPr(q), dataN); - OSQPFloat* dataL = copyToOSQPFloatVector(mxGetPr(l), dataM); - OSQPFloat* dataU = copyToOSQPFloatVector(mxGetPr(u), dataM); + OSQPFloat* dataQ = cloneVector(mxGetPr(q), dataN); + OSQPFloat* dataL = cloneVector(mxGetPr(l), dataM); + OSQPFloat* dataU = cloneVector(mxGetPr(u), dataM); // Matrix P: nnz = P->p[n] - OSQPInt * Pp = (OSQPInt*)copyToOSQPIntVector(mxGetJc(P), dataN + 1); - OSQPInt * Pi = (OSQPInt*)copyToOSQPIntVector(mxGetIr(P), Pp[dataN]); - OSQPFloat * Px = copyToOSQPFloatVector(mxGetPr(P), Pp[dataN]); + OSQPInt * Pp = cloneVector(mxGetJc(P), dataN + 1); + OSQPInt * Pi = cloneVector(mxGetIr(P), Pp[dataN]); + OSQPFloat * Px = cloneVector(mxGetPr(P), Pp[dataN]); OSQPCscMatrix* dataP = (OSQPCscMatrix*)c_calloc(1,sizeof(OSQPCscMatrix)); csc_set_data(dataP, dataN, dataN, Pp[dataN], Px, Pi, Pp); // Matrix A: nnz = A->p[n] - OSQPInt* Ap = (OSQPInt*)copyToOSQPIntVector(mxGetJc(A), dataN + 1); - OSQPInt* Ai = (OSQPInt*)copyToOSQPIntVector(mxGetIr(A), Ap[dataN]); - OSQPFloat * Ax = copyToOSQPFloatVector(mxGetPr(A), Ap[dataN]); + OSQPInt* Ap = cloneVector(mxGetJc(A), dataN + 1); + OSQPInt* Ai = cloneVector(mxGetIr(A), Ap[dataN]); + OSQPFloat * Ax = cloneVector(mxGetPr(A), Ap[dataN]); OSQPCscMatrix* dataA = (OSQPCscMatrix*)c_calloc(1,sizeof(OSQPCscMatrix)); csc_set_data(dataA, dataM, dataN, Ap[dataN], Ax, Ai, Ap); @@ -276,25 +272,25 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPInt n, m; osqp_get_dimensions(osqpData->solver, &m, &n); if(!mxIsEmpty(q)){ - q_vec = copyToOSQPFloatVector(mxGetPr(q), n); + q_vec = cloneVector(mxGetPr(q), n); } if(!mxIsEmpty(l)){ - l_vec = copyToOSQPFloatVector(mxGetPr(l), m); + l_vec = cloneVector(mxGetPr(l), m); } if(!mxIsEmpty(u)){ - u_vec = copyToOSQPFloatVector(mxGetPr(u), m); + u_vec = cloneVector(mxGetPr(u), m); } if(!mxIsEmpty(Px)){ - Px_vec = copyToOSQPFloatVector(mxGetPr(Px), Px_n); + Px_vec = cloneVector(mxGetPr(Px), Px_n); } if(!mxIsEmpty(Ax)){ - Ax_vec = copyToOSQPFloatVector(mxGetPr(Ax), Ax_n); + Ax_vec = cloneVector(mxGetPr(Ax), Ax_n); } if(!mxIsEmpty(Px_idx)){ - Px_idx_vec = copyDoubleToOSQPIntVector(mxGetPr(Px_idx), Px_n); + Px_idx_vec = cloneVector(mxGetPr(Px_idx), Px_n); } if(!mxIsEmpty(Ax_idx)){ - Ax_idx_vec = copyDoubleToOSQPIntVector(mxGetPr(Ax_idx), Ax_n); + Ax_idx_vec = cloneVector(mxGetPr(Ax_idx), Ax_n); } if (!exitflag && (!mxIsEmpty(q) || !mxIsEmpty(l) || !mxIsEmpty(u))) { @@ -359,10 +355,10 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPInt n, m; osqp_get_dimensions(osqpData->solver, &m, &n); if(!mxIsEmpty(x)){ - x_vec = copyToOSQPFloatVector(mxGetPr(x),n); + x_vec = cloneVector(mxGetPr(x),n); } if(!mxIsEmpty(y)){ - y_vec = copyToOSQPFloatVector(mxGetPr(y),m); + y_vec = cloneVector(mxGetPr(y),m); } // Warm start x and y @@ -405,8 +401,8 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) (osqpData->solver->info->status_val != OSQP_DUAL_INFEASIBLE)){ //primal and dual solutions - castToDoubleArr(osqpData->solver->solution->x, mxGetPr(plhs[0]), n); - castToDoubleArr(osqpData->solver->solution->y, mxGetPr(plhs[1]), m); + copyVector(mxGetPr(plhs[0]), osqpData->solver->solution->x, n); + copyVector(mxGetPr(plhs[1]), osqpData->solver->solution->y, m); //infeasibility certificates -> NaN values setToNaN(mxGetPr(plhs[2]), m); @@ -420,7 +416,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) setToNaN(mxGetPr(plhs[1]), m); //primal infeasibility certificates - castToDoubleArr(osqpData->solver->solution->prim_inf_cert, mxGetPr(plhs[2]), m); + copyVector(mxGetPr(plhs[2]), osqpData->solver->solution->prim_inf_cert, m); //dual infeasibility certificates -> NaN values setToNaN(mxGetPr(plhs[3]), n); @@ -438,7 +434,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) setToNaN(mxGetPr(plhs[2]), m); //dual infeasibility certificates - castToDoubleArr(osqpData->solver->solution->dual_inf_cert, mxGetPr(plhs[3]), n); + copyVector(mxGetPr(plhs[3]), osqpData->solver->solution->dual_inf_cert, n); // Set objective value to -infinity osqpData->solver->info->obj_val = -mxGetInf(); @@ -529,54 +525,6 @@ OSQPSolver* initializeOSQPSolver() { return osqpSolver; } -//Dynamically creates a OSQPFloat vector copy of the input. -//Returns an empty pointer if vecData is NULL -OSQPFloat* copyToOSQPFloatVector(double * vecData, OSQPInt numel){ - if (!vecData) return NULL; - - //This needs to be freed! - OSQPFloat* out = (OSQPFloat*)c_malloc(numel * sizeof(OSQPFloat)); - - //copy data - for(OSQPInt i=0; i < numel; i++){ - out[i] = (OSQPFloat)vecData[i]; - } - return out; -} - -//Dynamically creates a OSQPInt vector copy of the input. -OSQPInt* copyToOSQPIntVector(mwIndex* vecData, OSQPInt numel){ - // This memory needs to be freed! - OSQPInt* out = (OSQPInt*)c_malloc(numel * sizeof(OSQPInt)); - - //copy data - for(OSQPInt i=0; i < numel; i++){ - out[i] = (OSQPInt)vecData[i]; - } - return out; - -} - -//Dynamically copies a double vector to OSQPInt. -OSQPInt* copyDoubleToOSQPIntVector(double* vecData, OSQPInt numel){ - // This memory needs to be freed! - OSQPInt* out = (OSQPInt*)c_malloc(numel * sizeof(OSQPInt)); - - //copy data - for(OSQPInt i=0; i < numel; i++){ - out[i] = (OSQPInt)vecData[i]; - } - return out; - -} - -/* DELETE HERE -void castCintToDoubleArr(OSQPInt *arr, double* arr_out, OSQPInt len) { - for (OSQPInt i = 0; i < len; i++) { - arr_out[i] = (double)arr[i]; - } -}*/ - //This function frees the memory allocated in an OSQPCscMatrix M void freeCscMatrix(OSQPCscMatrix* M) { if (!M) return; @@ -586,12 +534,6 @@ void freeCscMatrix(OSQPCscMatrix* M) { c_free(M); } -void castToDoubleArr(OSQPFloat *arr, double* arr_out, OSQPInt len) { - for (OSQPInt i = 0; i < len; i++) { - arr_out[i] = (double)arr[i]; - } -} - void setToNaN(double* arr_out, OSQPInt len){ OSQPInt i; for (i = 0; i < len; i++) { From 424bccd02700650fe45f0f05d6745750b2232d2c Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Tue, 21 Nov 2023 17:36:41 +0000 Subject: [PATCH 29/36] Cleanup unused routines and some style changes --- c_sources/osqp_mex.cpp | 61 +++++++++++------------------------------- 1 file changed, 15 insertions(+), 46 deletions(-) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 43baea2..12b0e9e 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -10,33 +10,35 @@ #include "arrays_matlab.h" #include "memory_matlab.h" -//c_int is replaced with OSQPInt -//c_float is replaced with OSQPFloat - //TODO: Check if this definition is required, and maybe replace it with: // enum linsys_solver_type { QDLDL_SOLVER, MKL_PARDISO_SOLVER }; #define QDLDL_SOLVER 0 //Based on the previous API -#define NEW_SETTINGS_TOL (1e-10) - -// wrapper class for all osqp data and settings +// Wrapper class to pass the OSQP solver back and forth with Matlab class OsqpData { public: - OsqpData() : solver(NULL){} - OSQPSolver * solver; + OsqpData() : + solver(NULL) + {} + OSQPSolver* solver; }; -// internal utility functions -OSQPSolver* initializeOSQPSolver(); -void setToNaN(double* arr_out, OSQPInt len); -void freeCscMatrix(OSQPCscMatrix* M); + +// Internal utility function +static void setToNaN(double* arr_out, OSQPInt len){ + OSQPInt i; + for (i = 0; i < len; i++) { + arr_out[i] = mxGetNaN(); + } +} +// Main mex function void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { OsqpData* osqpData; - //OSQPSolver* osqpSolver = NULL; + // Exitflag OSQPInt exitflag = 0; // Static string for static methods @@ -53,8 +55,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } // Return a handle to a new C++ wrapper instance osqpData = new OsqpData; - //osqpData->solver = initializeOSQPSolver(); - osqpData->solver = NULL; plhs[0] = convertPtr2Mat(osqpData); return; } @@ -509,34 +509,3 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // Got here, so command not recognized mexErrMsgTxt("Command not recognized."); } - -/** - * This function dynamically allocates OSQPSovler and sets all the properties of OSQPSolver to NULL. - * WARNING: The memory allocated here (OSQPSolver*) needs to be freed. - * WARNING: Any dynamically allocated pointers must be freed before calling this function. -*/ -OSQPSolver* initializeOSQPSolver() { - OSQPSolver* osqpSolver = new OSQPSolver; - osqpSolver->info = NULL; - osqpSolver->settings = NULL; - osqpSolver->solution = NULL; - osqpSolver->work = NULL; - //osqp_set_default_settings(osqpSolver->settings); - return osqpSolver; -} - -//This function frees the memory allocated in an OSQPCscMatrix M -void freeCscMatrix(OSQPCscMatrix* M) { - if (!M) return; - if (M->p) c_free(M->p); - if (M->i) c_free(M->i); - if (M->x) c_free(M->x); - c_free(M); -} - -void setToNaN(double* arr_out, OSQPInt len){ - OSQPInt i; - for (i = 0; i < len; i++) { - arr_out[i] = mxGetNaN(); - } -} From 5cf4a612a5b8a681c88fd25048003dae3d312f29 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Thu, 23 Nov 2023 17:29:34 +0000 Subject: [PATCH 30/36] Remove extraneous static string on mex call to static methods --- @osqp/osqp.m | 6 +- c_sources/osqp_mex.cpp | 189 +++++++++++++++++++++-------------------- 2 files changed, 100 insertions(+), 95 deletions(-) diff --git a/@osqp/osqp.m b/@osqp/osqp.m index d26fffc..cc58bcd 100644 --- a/@osqp/osqp.m +++ b/@osqp/osqp.m @@ -34,7 +34,7 @@ %% function out = default_settings() % DEFAULT_SETTINGS get the default solver settings structure - out = osqp_mex('default_settings', 'static'); + out = osqp_mex('default_settings'); % Convert linsys solver to string out.linsys_solver = linsys_solver_to_string(out.linsys_solver); @@ -44,13 +44,13 @@ function out = constant(constant_name) % CONSTANT Return solver constant % C = CONSTANT(CONSTANT_NAME) return constant called CONSTANT_NAME - out = osqp_mex('constant', 'static', constant_name); + out = osqp_mex('constant', constant_name); end %% function out = version() % Return OSQP version - out = osqp_mex('version', 'static'); + out = osqp_mex('version'); end end diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 12b0e9e..5fc90d3 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -37,17 +37,21 @@ static void setToNaN(double* arr_out, OSQPInt len){ // Main mex function void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { + // OSQP solver wrapper OsqpData* osqpData; // Exitflag OSQPInt exitflag = 0; - // Static string for static methods - char stat_string[64]; + // Get the command string char cmd[64]; - if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) + + if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) mexErrMsgTxt("First input should be a command string less than 64 characters long."); - // new object + + /* + * First check to see if a new object was requested + */ if (!strcmp("new", cmd)) { // Check parameters if (nlhs != 1){ @@ -59,18 +63,97 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } - // Check for a second input, which should be the class instance handle or string 'static' - if (nrhs < 2) - mexErrMsgTxt("Second input should be a class instance handle or the string 'static'."); - - if(mxGetString(prhs[1], stat_string, sizeof(stat_string))){ - // If we are dealing with non-static methods, get the class instance pointer from the second input - osqpData = convertMat2Ptr(prhs[1]); - } else { - if (strcmp("static", stat_string)){ - mexErrMsgTxt("Second argument for static functions is string 'static'"); + /* + * Next check to see if any of the static methods were called + */ + // Report the version + if (!strcmp("version", cmd)) { + plhs[0] = mxCreateString(osqp_version()); + return; + } + + // Report the default settings + if (!strcmp("default_settings", cmd)) { + // Warn if other commands were ignored + if (nrhs > 2) + mexWarnMsgTxt("Default settings: unexpected number of arguments."); + + // Create a Settings structure in default form and report the results + // Useful for external solver packages (e.g. Yalmip) that want to + // know which solver settings are supported + OSQPSettingsWrapper settings; + plhs[0] = settings.GetMxStruct(); + return; + } + + // Return solver constants + if (!strcmp("constant", cmd)) { + static std::map floatConstants{ + // Numerical constants + {"OSQP_INFTY", OSQP_INFTY} + }; + + static std::map intConstants{ + // Return codes + {"OSQP_SOLVED", OSQP_SOLVED}, + {"OSQP_SOLVED_INACCURATE", OSQP_SOLVED_INACCURATE}, + {"OSQP_UNSOLVED", OSQP_UNSOLVED}, + {"OSQP_PRIMAL_INFEASIBLE", OSQP_PRIMAL_INFEASIBLE}, + {"OSQP_PRIMAL_INFEASIBLE_INACCURATE", OSQP_PRIMAL_INFEASIBLE_INACCURATE}, + {"OSQP_DUAL_INFEASIBLE", OSQP_DUAL_INFEASIBLE}, + {"OSQP_DUAL_INFEASIBLE_INACCURATE", OSQP_DUAL_INFEASIBLE_INACCURATE}, + {"OSQP_MAX_ITER_REACHED", OSQP_MAX_ITER_REACHED}, + {"OSQP_NON_CVX", OSQP_NON_CVX}, + {"OSQP_TIME_LIMIT_REACHED", OSQP_TIME_LIMIT_REACHED}, + + // Linear system solvers + {"QDLDL_SOLVER", QDLDL_SOLVER}, + {"OSQP_UNKNOWN_SOLVER", OSQP_UNKNOWN_SOLVER}, + {"OSQP_DIRECT_SOLVER", OSQP_DIRECT_SOLVER}, + {"OSQP_INDIRECT_SOLVER", OSQP_INDIRECT_SOLVER} + }; + + char constant[64]; + int constantLength = mxGetN(prhs[1]) + 1; + mxGetString(prhs[1], constant, constantLength); + + auto ci = intConstants.find(constant); + + if(ci != intConstants.end()) { + plhs[0] = mxCreateDoubleScalar(ci->second); + return; } + + auto cf = floatConstants.find(constant); + + if(cf != floatConstants.end()) { + plhs[0] = mxCreateDoubleScalar(cf->second); + return; + } + + // NaN is special because we need the Matlab version + if (!strcmp("OSQP_NAN", constant)){ + plhs[0] = mxCreateDoubleScalar(mxGetNaN()); + return; + } + + mexErrMsgTxt("Constant not recognized."); + + return; } + + /* + * Finally, check to see if this is a function operating on a solver instance + */ + + // Check for a second input, which should be the class instance handle + if (nrhs < 2) + mexErrMsgTxt("Second input should be a class instance handle."); + + + // Get the class instance pointer from the second input + osqpData = convertMat2Ptr(prhs[1]); + // delete the object and its data if (!strcmp("delete", cmd)) { @@ -126,21 +209,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } - // report the default settings - if (!strcmp("default_settings", cmd)) { - // Warn if other commands were ignored - if (nrhs > 2) - mexWarnMsgTxt("Default settings: unexpected number of arguments."); - - - // Create a Settings structure in default form and report the results - // Useful for external solver packages (e.g. Yalmip) that want to - // know which solver settings are supported - OSQPSettingsWrapper settings; - plhs[0] = settings.GetMxStruct(); - return; - } - // setup if (!strcmp("setup", cmd)) { //throw an error if this is called more than once @@ -231,14 +299,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } - // report the version - if (!strcmp("version", cmd)) { - - plhs[0] = mxCreateString(osqp_version()); - - return; - } - // update linear cost and bounds if (!strcmp("update", cmd)) { @@ -451,61 +511,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } - if (!strcmp("constant", cmd)) { // Return solver constants - static std::map floatConstants{ - // Numerical constants - {"OSQP_INFTY", OSQP_INFTY} - }; - - static std::map intConstants{ - // Return codes - {"OSQP_SOLVED", OSQP_SOLVED}, - {"OSQP_SOLVED_INACCURATE", OSQP_SOLVED_INACCURATE}, - {"OSQP_UNSOLVED", OSQP_UNSOLVED}, - {"OSQP_PRIMAL_INFEASIBLE", OSQP_PRIMAL_INFEASIBLE}, - {"OSQP_PRIMAL_INFEASIBLE_INACCURATE", OSQP_PRIMAL_INFEASIBLE_INACCURATE}, - {"OSQP_DUAL_INFEASIBLE", OSQP_DUAL_INFEASIBLE}, - {"OSQP_DUAL_INFEASIBLE_INACCURATE", OSQP_DUAL_INFEASIBLE_INACCURATE}, - {"OSQP_MAX_ITER_REACHED", OSQP_MAX_ITER_REACHED}, - {"OSQP_NON_CVX", OSQP_NON_CVX}, - {"OSQP_TIME_LIMIT_REACHED", OSQP_TIME_LIMIT_REACHED}, - - // Linear system solvers - {"QDLDL_SOLVER", QDLDL_SOLVER}, - {"OSQP_UNKNOWN_SOLVER", OSQP_UNKNOWN_SOLVER}, - {"OSQP_DIRECT_SOLVER", OSQP_DIRECT_SOLVER}, - {"OSQP_INDIRECT_SOLVER", OSQP_INDIRECT_SOLVER} - }; - - char constant[64]; - int constantLength = mxGetN(prhs[2]) + 1; - mxGetString(prhs[2], constant, constantLength); - - auto ci = intConstants.find(constant); - - if(ci != intConstants.end()) { - plhs[0] = mxCreateDoubleScalar(ci->second); - return; - } - - auto cf = floatConstants.find(constant); - - if(cf != floatConstants.end()) { - plhs[0] = mxCreateDoubleScalar(cf->second); - return; - } - - // NaN is special because we need the Matlab version - if (!strcmp("OSQP_NAN", constant)){ - plhs[0] = mxCreateDoubleScalar(mxGetNaN()); - return; - } - - mexErrMsgTxt("Constant not recognized."); - - return; - } - // Got here, so command not recognized mexErrMsgTxt("Command not recognized."); } From b2b3be00650f8e0c4def1a8cd216a334d5b75419 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Mon, 4 Dec 2023 17:29:04 +0000 Subject: [PATCH 31/36] Simplify check inside setup function --- @osqp/setup.m | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/@osqp/setup.m b/@osqp/setup.m index 800a442..5317e2d 100644 --- a/@osqp/setup.m +++ b/@osqp/setup.m @@ -16,18 +16,14 @@ % % Get number of variables n - if (isempty(P)) - if (~isempty(q)) - n = length(q); - else - if (~isempty(A)) - n = size(A, 2); - else - error('The problem does not have any variables'); - end - end - else + if (~isempty(P)) n = size(P, 1); + elseif (~isempty(q)) + n = length(q); + elseif (~isempty(A)) + n = size(A, 2); + else + error('The problem does not have any variables'); end % Get number of constraints m @@ -53,7 +49,7 @@ if (isempty(q)) q = zeros(n, 1); else - q = full(q(:)); + q = full(q(:)); end % Create proper constraints if they are not passed @@ -84,7 +80,6 @@ % % Check vector dimensions (not checked from the C solver) % - assert(length(q) == n, 'Incorrect dimension of q'); assert(length(l) == m, 'Incorrect dimension of l'); assert(length(u) == m, 'Incorrect dimension of u'); From 38b984f6d514fb42903675c2d0af7cc070ebbf36 Mon Sep 17 00:00:00 2001 From: Ian McInerney Date: Mon, 4 Dec 2023 17:53:33 +0000 Subject: [PATCH 32/36] Simplify warm start routine --- @osqp/warm_start.m | 18 ++------ c_sources/osqp_mex.cpp | 38 ++++++--------- unittests/warm_start_tests.m | 90 +++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 42 deletions(-) diff --git a/@osqp/warm_start.m b/@osqp/warm_start.m index 322898d..81d56ae 100644 --- a/@osqp/warm_start.m +++ b/@osqp/warm_start.m @@ -41,22 +41,10 @@ function warm_start(this, varargin) assert(isempty(x) || length(x) == n, 'input ''x'' is the wrong size'); assert(isempty(y) || length(y) == m, 'input ''y'' is the wrong size'); - - % Decide which function to call - if (~isempty(x) && isempty(y)) - osqp_mex('warm_start_x', this.objectHandle, x); - return; - end - - if (isempty(x) && ~isempty(y)) - osqp_mex('warm_start_y', this.objectHandle, y); - end - - if (~isempty(x) && ~isempty(y)) + % Only call when there is a vector to update + if (~isempty(x) || ~isempty(y)) osqp_mex('warm_start', this.objectHandle, x, y); - end - - if (isempty(x) && isempty(y)) + else error('Unrecognized fields'); end end \ No newline at end of file diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 5fc90d3..05f82b3 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -36,7 +36,7 @@ static void setToNaN(double* arr_out, OSQPInt len){ // Main mex function void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) -{ +{ // OSQP solver wrapper OsqpData* osqpData; @@ -48,7 +48,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) mexErrMsgTxt("First input should be a command string less than 64 characters long."); - + /* * First check to see if a new object was requested */ @@ -156,7 +156,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // delete the object and its data if (!strcmp("delete", cmd)) { - + osqp_cleanup(osqpData->solver); destroyObject(prhs[1]); // Warn if other commands were ignored @@ -202,7 +202,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if(!osqpData->solver){ mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); } - + OSQPFloat rho = (OSQPFloat)mxGetScalar(prhs[2]); osqp_update_rho(osqpData->solver, rho); @@ -355,14 +355,14 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!exitflag && (!mxIsEmpty(q) || !mxIsEmpty(l) || !mxIsEmpty(u))) { exitflag = osqp_update_data_vec(osqpData->solver, q_vec, l_vec, u_vec); - if (exitflag) exitflag=1; + if (exitflag) exitflag=1; } - + if (!exitflag && (!mxIsEmpty(Px) || !mxIsEmpty(Ax))) { exitflag = osqp_update_data_mat(osqpData->solver, Px_vec, Px_idx_vec, Px_n, Ax_vec, Ax_idx_vec, Ax_n); if (exitflag) exitflag=2; } - + // Free vectors if(!mxIsEmpty(q)) c_free(q_vec); @@ -384,29 +384,16 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } - if (!strcmp("warm_start", cmd) || !strcmp("warm_start_x", cmd) || !strcmp("warm_start_y", cmd)) { - + if (!strcmp("warm_start", cmd)) { + //throw an error if this is called before solver is configured if(!osqpData->solver){ mexErrMsgTxt("Solver has not been initialized."); - } - - // Fill x and y - const mxArray *x = NULL; - const mxArray *y = NULL; - if (!strcmp("warm_start", cmd)) { - x = prhs[2]; - y = prhs[3]; - } - else if (!strcmp("warm_start_x", cmd)) { - x = prhs[2]; - y = NULL; } - else if (!strcmp("warm_start_y", cmd)) { - x = NULL; - y = prhs[2]; - } + // Fill x and y + const mxArray *x = prhs[2]; + const mxArray *y = prhs[3]; // Copy vectors to ensure they are cast as OSQPFloat OSQPFloat *x_vec = NULL; @@ -414,6 +401,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPInt n, m; osqp_get_dimensions(osqpData->solver, &m, &n); + if(!mxIsEmpty(x)){ x_vec = cloneVector(mxGetPr(x),n); } diff --git a/unittests/warm_start_tests.m b/unittests/warm_start_tests.m index f6ab098..db5ff7b 100644 --- a/unittests/warm_start_tests.m +++ b/unittests/warm_start_tests.m @@ -33,8 +33,7 @@ function setup_problem(testCase) end methods (Test) - function test_warm_start(testCase) - + function test_warm_start_zeros(testCase) % big example rng(4) testCase.n = 100; @@ -63,12 +62,99 @@ function test_warm_start(testCase) testCase.solver.warm_start('x', zeros(testCase.n, 1), 'y', zeros(testCase.m, 1)); results = testCase.solver.solve(); testCase.verifyEqual(results.info.iter, tot_iter, 'AbsTol', testCase.tol) + end + + function test_warm_start_optimal(testCase) + % big example + rng(4) + testCase.n = 100; + testCase.m = 200; + Pt = sprandn(testCase.n, testCase.n, 0.6); + testCase.P = Pt' * Pt; + testCase.q = randn(testCase.n, 1); + testCase.A = sprandn(testCase.m, testCase.n, 0.8); + testCase.u = 2*rand(testCase.m, 1); + testCase.l = -2*rand(testCase.m, 1); + + % Setup solver + testCase.solver = osqp; + testCase.solver.setup(testCase.P, testCase.q, ... + testCase.A, testCase.l, testCase.u, testCase.options); + + % Solve with OSQP + results = testCase.solver.solve(); + + % Store optimal values + x_opt = results.x; + y_opt = results.y; + tot_iter = results.info.iter; % Warm start with optimal values and check that number of iterations is < 10 testCase.solver.warm_start('x', x_opt, 'y', y_opt); results = testCase.solver.solve(); testCase.verifyThat(results.info.iter, matlab.unittest.constraints.IsLessThan(10)); + end + + function test_warm_start_duals(testCase) + % big example + rng(4) + testCase.n = 100; + testCase.m = 200; + Pt = sprandn(testCase.n, testCase.n, 0.6); + testCase.P = Pt' * Pt; + testCase.q = randn(testCase.n, 1); + testCase.A = sprandn(testCase.m, testCase.n, 0.8); + testCase.u = 2*rand(testCase.m, 1); + testCase.l = -2*rand(testCase.m, 1); + % Setup solver + testCase.solver = osqp; + testCase.solver.setup(testCase.P, testCase.q, ... + testCase.A, testCase.l, testCase.u, testCase.options); + + % Solve with OSQP + results = testCase.solver.solve(); + + % Store optimal values + x_opt = results.x; + y_opt = results.y; + tot_iter = results.info.iter; + + % Warm start with zeros for dual variables + testCase.solver.warm_start('y', zeros(testCase.m, 1)); + results = testCase.solver.solve(); + testCase.verifyEqual(results.y, y_opt, 'AbsTol', testCase.tol) + end + + function test_warm_start_primal(testCase) + % big example + rng(4) + testCase.n = 100; + testCase.m = 200; + Pt = sprandn(testCase.n, testCase.n, 0.6); + testCase.P = Pt' * Pt; + testCase.q = randn(testCase.n, 1); + testCase.A = sprandn(testCase.m, testCase.n, 0.8); + testCase.u = 2*rand(testCase.m, 1); + testCase.l = -2*rand(testCase.m, 1); + + % Setup solver + testCase.solver = osqp; + testCase.solver.setup(testCase.P, testCase.q, ... + testCase.A, testCase.l, testCase.u, testCase.options); + + % Solve with OSQP + results = testCase.solver.solve(); + + % Store optimal values + x_opt = results.x; + y_opt = results.y; + tot_iter = results.info.iter; + + % Warm start with zeros for primal variables + testCase.solver.warm_start('x', zeros(testCase.n, 1)); + results = testCase.solver.solve(); + testCase.verifyEqual(results.x, x_opt, 'AbsTol', testCase.tol) end end From c47c8df5996f7c8cc6d21fc48636a76cf459da80 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Fri, 16 Feb 2024 15:33:46 -0500 Subject: [PATCH 33/36] Added the following to osqp_mex.cpp: codegen, default_codegen_defines, update_codegen_defines. Added osqp_struct_codegen_defines.cpp and updated osqp_struct.h. Updated the relevant .m files (codegen.m still needs work). Minor refactoring to osqp_mex.cpp. --- @osqp/codegen.m | 266 +++++++++------------- @osqp/osqp.m | 23 +- @osqp/update_codegen_defines.m | 16 ++ c_sources/CMakeLists.txt | 1 + c_sources/osqp_mex.cpp | 177 +++++++++++++- c_sources/osqp_struct.h | 6 + c_sources/osqp_struct_codegen_defines.cpp | 28 +++ 7 files changed, 331 insertions(+), 186 deletions(-) create mode 100644 @osqp/update_codegen_defines.m create mode 100644 c_sources/osqp_struct_codegen_defines.cpp diff --git a/@osqp/codegen.m b/@osqp/codegen.m index d753762..3bd3958 100644 --- a/@osqp/codegen.m +++ b/@osqp/codegen.m @@ -1,31 +1,34 @@ %% -function codegen(this, target_dir, varargin) +function codegen(this, out, varargin) % CODEGEN generate C code for the parametric problem % % codegen(target_dir,options) % Parse input arguments p = inputParser; - defaultProject = ''; - expectedProject = {'', 'Makefile', 'MinGW Makefiles', 'Unix Makefiles', 'CodeBlocks', 'Xcode'}; - defaultParams = 'vectors'; - expectedParams = {'vectors', 'matrices'}; - defaultMexname = 'emosqp'; - defaultFloat = false; - defaultLong = true; - defaultFW = false; - - addRequired(p, 'target_dir', @isstr); - addParameter(p, 'project_type', defaultProject, ... - @(x) ischar(validatestring(x, expectedProject))); - addParameter(p, 'parameters', defaultParams, ... - @(x) ischar(validatestring(x, expectedParams))); - addParameter(p, 'mexname', defaultMexname, @isstr); - addParameter(p, 'FLOAT', defaultFloat, @islogical); - addParameter(p, 'LONG', defaultLong, @islogical); - addParameter(p, 'force_rewrite', defaultFW, @islogical); - - parse(p, target_dir, varargin{:}); + defaultPrefix = 'prob1_'; % Prefix for filenames and C variables; useful if generating multiple problems + defaultForceRewrite = true; % Force rewrite if output folder exists? + defaultParameters = 'vectors'; % What do we wish to update in the generated code? + % One of 'vectors' (allowing update of q/l/u through prob.update_data_vec) + % or 'matrices' (allowing update of P/A/q/l/u + % through prob.update_data_vec or prob.update_data_mat) + defaultUseFloat = false; % Use single precision in generated code? + defaultPrintingEnable = false; % Enable solver printing? + defaultProfilingEnable = false; % Enable solver profiling? + defaultInterruptEnable = false; % Enable user interrupt (Ctrl-C)? + defaultEnableDerivatives = false; % Enable derivatives? + + addRequired(p, 'out', @isstr); + addOptional(p, 'prefix', defaultPrefix, @isstr); + addParameter(p, 'force_rewrite', defaultForceRewrite, @isboolean); + addParameter(p, 'parameters', defaultParameters, @isstr); + addParameter(p, 'float_type', defaultUseFloat, @isboolean); + addParameter(p, 'printing_enable', defaultPrintingEnable, @isboolean); + addParameter(p, 'profiling_enable', defaultProfilingEnable, @isboolean); + addParameter(p, 'interrupt_enable', defaultInterruptEnable, @isboolean); + addParameter(p, 'derivatives_enable', defaultEnableDerivatives, @isboolean); + + parse(p, out, varargin{:}); % Set internal variables if strcmp(p.Results.parameters, 'vectors') @@ -33,41 +36,19 @@ function codegen(this, target_dir, varargin) else embedded = 2; end - if p.Results.FLOAT - float_flag = 'ON'; - else - float_flag = 'OFF'; - end - if p.Results.LONG - long_flag = 'ON'; - else - long_flag = 'OFF'; - end - if strcmp(p.Results.project_type, 'Makefile') - if (ispc) - project_type = 'MinGW Makefiles'; % Windows - elseif (ismac || isunix) - project_type = 'Unix Makefiles'; % Unix - end - else - project_type = p.Results.project_type; - end - % Check whether the specified directory already exists - if exist(target_dir, 'dir') - if p.Results.force_rewrite - rmdir(target_dir, 's'); - else - while(1) - prompt = sprintf('Directory "%s" already exists. Do you want to replace it? y/n [y]: ', target_dir); - str = input(prompt, 's'); - if any(strcmpi(str, {'','y'})) - rmdir(target_dir, 's'); - break; - elseif strcmpi(str, 'n') - return; - end + % Check whether the specified directory already exists + if exist(out, 'dir') + while(1) + prompt = sprintf('Directory "%s" already exists. Do you want to replace it? y/n [y]: ', out); + str = input(prompt, 's'); + + if any(strcmpi(str, {'','y'})) + rmdir(out, 's'); + break; + elseif strcmpi(str, 'n') + return; end end end @@ -79,20 +60,17 @@ function codegen(this, target_dir, varargin) addpath(fullfile(osqp_path, 'codegen')); % Path of osqp module - cg_dir = fullfile(osqp_path, 'codegen'); + cg_dir = fullfile(osqp_path, '..', 'codegen'); files_to_generate_path = fullfile(cg_dir, 'files_to_generate'); - % Get workspace structure - work = osqp_mex('get_workspace', this.objectHandle); - % Make target directory fprintf('Creating target directories...\t\t\t\t\t'); - target_configure_dir = fullfile(target_dir, 'configure'); - target_include_dir = fullfile(target_dir, 'include'); - target_src_dir = fullfile(target_dir, 'src'); + target_configure_dir = fullfile(out, 'configure'); + target_include_dir = fullfile(out, 'include'); + target_src_dir = fullfile(out, 'src'); - if ~exist(target_dir, 'dir') - mkdir(target_dir); + if ~exist(out, 'dir') + mkdir(out); end if ~exist(target_configure_dir, 'dir') mkdir(target_configure_dir); @@ -105,104 +83,66 @@ function codegen(this, target_dir, varargin) end fprintf('[done]\n'); - % Copy source files to target directory - fprintf('Copying OSQP source files...\t\t\t\t\t'); - cdir = fullfile(cg_dir, 'sources', 'src'); - cfiles = dir(fullfile(cdir, '*.c')); - for i = 1 : length(cfiles) - if embedded == 1 - % Do not copy kkt.c if embedded is 1 - if ~strcmp(cfiles(i).name, 'kkt.c') - copyfile(fullfile(cdir, cfiles(i).name), ... - fullfile(target_src_dir, 'osqp', cfiles(i).name)); - end - else - copyfile(fullfile(cdir, cfiles(i).name), ... - fullfile(target_src_dir, 'osqp', cfiles(i).name)); - end - end - configure_dir = fullfile(cg_dir, 'sources', 'configure'); - configure_files = dir(fullfile(configure_dir, '*.h.in')); - for i = 1 : length(configure_files) - copyfile(fullfile(configure_dir, configure_files(i).name), ... - fullfile(target_configure_dir, configure_files(i).name)); - end - hdir = fullfile(cg_dir, 'sources', 'include'); - hfiles = dir(fullfile(hdir, '*.h')); - for i = 1 : length(hfiles) - if embedded == 1 - % Do not copy kkt.h if embedded is 1 - if ~strcmp(hfiles(i).name, 'kkt.h') - copyfile(fullfile(hdir, hfiles(i).name), ... - fullfile(target_include_dir, hfiles(i).name)); - end - else - copyfile(fullfile(hdir, hfiles(i).name), ... - fullfile(target_include_dir, hfiles(i).name)); - end - end - - % Copy cmake files - copyfile(fullfile(cdir, 'CMakeLists.txt'), ... - fullfile(target_src_dir, 'osqp', 'CMakeLists.txt')); - copyfile(fullfile(hdir, 'CMakeLists.txt'), ... - fullfile(target_include_dir, 'CMakeLists.txt')); - fprintf('[done]\n'); - - % Copy example.c - copyfile(fullfile(files_to_generate_path, 'example.c'), target_src_dir); - - % Render CMakeLists.txt - fidi = fopen(fullfile(files_to_generate_path, 'CMakeLists.txt'),'r'); - fido = fopen(fullfile(target_dir, 'CMakeLists.txt'),'w'); - while ~feof(fidi) - l = fgetl(fidi); % read line - % Replace EMBEDDED_FLAG in CMakeLists.txt by a numerical value - newl = strrep(l, 'EMBEDDED_FLAG', num2str(embedded)); - fprintf(fido, '%s\n', newl); - end - fclose(fidi); - fclose(fido); - - % Render workspace.h and workspace.c - work_hfile = fullfile(target_include_dir, 'workspace.h'); - work_cfile = fullfile(target_src_dir, 'osqp', 'workspace.c'); - fprintf('Generating workspace.h/.c...\t\t\t\t\t\t'); - render_workspace(work, work_hfile, work_cfile, embedded); - fprintf('[done]\n'); - - % Create project - if ~isempty(project_type) - - % Extend path for CMake mac (via Homebrew) - PATH = getenv('PATH'); - if ((ismac) && (isempty(strfind(PATH, '/usr/local/bin')))) - setenv('PATH', [PATH ':/usr/local/bin']); - end - - fprintf('Creating project...\t\t\t\t\t\t\t\t'); - orig_dir = pwd; - cd(target_dir); - mkdir('build') - cd('build'); - cmd = sprintf('cmake -G "%s" ..', project_type); - [status, output] = system(cmd); - if(status) - fprintf('\n'); - fprintf(output); - error('Error configuring CMake environment'); - else - fprintf('[done]\n'); - end - cd(orig_dir); - end - - % Make mex interface to the generated code - mex_cfile = fullfile(files_to_generate_path, 'emosqp_mex.c'); - make_emosqp(target_dir, mex_cfile, embedded, float_flag, long_flag); - - % Rename the mex file - old_mexfile = ['emosqp_mex.', mexext]; - new_mexfile = [p.Results.mexname, '.', mexext]; - movefile(old_mexfile, new_mexfile); + %TODO: Fix the copying stuff + % % Copy source files to target directory + % fprintf('Copying OSQP source files...\t\t\t\t\t'); + % cdir = fullfile(cg_dir, 'sources', 'src'); + % cfiles = dir(fullfile(cdir, '*.c')); + % for i = 1 : length(cfiles) + % if embedded == 1 + % % Do not copy kkt.c if embedded is 1 + % if ~strcmp(cfiles(i).name, 'kkt.c') + % copyfile(fullfile(cdir, cfiles(i).name), ... + % fullfile(target_src_dir, 'osqp', cfiles(i).name)); + % end + % else + % copyfile(fullfile(cdir, cfiles(i).name), ... + % fullfile(target_src_dir, 'osqp', cfiles(i).name)); + % end + % end + % configure_dir = fullfile(cg_dir, 'sources', 'configure'); + % configure_files = dir(fullfile(configure_dir, '*.h.in')); + % for i = 1 : length(configure_files) + % copyfile(fullfile(configure_dir, configure_files(i).name), ... + % fullfile(target_configure_dir, configure_files(i).name)); + % end + % hdir = fullfile(cg_dir, 'sources', 'inc'); + % hfiles = dir(fullfile(hdir, '*.h')); + % for i = 1 : length(hfiles) + % if embedded == 1 + % % Do not copy kkt.h if embedded is 1 + % if ~strcmp(hfiles(i).name, 'kkt.h') + % copyfile(fullfile(hdir, hfiles(i).name), ... + % fullfile(target_include_dir, hfiles(i).name)); + % end + % else + % copyfile(fullfile(hdir, hfiles(i).name), ... + % fullfile(target_include_dir, hfiles(i).name)); + % end + % end + % + % % Copy cmake files + % copyfile(fullfile(cdir, 'CMakeLists.txt'), ... + % fullfile(target_src_dir, 'osqp', 'CMakeLists.txt')); + % copyfile(fullfile(hdir, 'CMakeLists.txt'), ... + % fullfile(target_include_dir, 'CMakeLists.txt')); + % fprintf('[done]\n'); + % + % % Copy example.c + % copyfile(fullfile(files_to_generate_path, 'example.c'), target_src_dir); + + % Update codegen defines + update_codegen_defines(this, 'embedded_mode', embedded, 'float_type', p.Results.float_type, 'printing_enable', p.Results.printing_enable, 'profiling_enable', p.Results.profiling_enable, 'interrupt_enable', p.Results.interrupt_enable, 'derivatives_enable', p.Results.derivatives_enable); + % Call codegen + osqp_mex('codegen', this.objectHandle, out, p.Results.prefix); + + % TODO: Do we want to keep this? + % % Make mex interface to the generated code + % mex_cfile = fullfile(files_to_generate_path, 'emosqp_mex.c'); + % make_emosqp(out, mex_cfile, embedded, float_flag, long_flag); + % + % % Rename the mex file + % old_mexfile = ['emosqp_mex.', mexext]; + % new_mexfile = [p.Results.mexname, '.', mexext]; + % movefile(old_mexfile, new_mexfile); end \ No newline at end of file diff --git a/@osqp/osqp.m b/@osqp/osqp.m index cc58bcd..ba91633 100644 --- a/@osqp/osqp.m +++ b/@osqp/osqp.m @@ -8,20 +8,21 @@ % % osqp Methods: % - % setup - configure solver with problem data - % solve - solve the QP - % update - modify problem vectors - % warm_start - set warm starting variables x and y + % setup - configure solver with problem data + % solve - solve the QP + % update - modify problem vectors + % warm_start - set warm starting variables x and y % - % default_settings - create default settings structure - % current_settings - get the current solver settings structure - % update_settings - update the current solver settings structure + % default_settings - create default settings structure + % current_settings - get the current solver settings structure + % update_settings - update the current solver settings structure % - % get_dimensions - get the number of variables and constraints - % version - return OSQP version - % constant - return a OSQP internal constant + % get_dimensions - get the number of variables and constraints + % version - return OSQP version + % constant - return a OSQP internal constant % - % codegen - generate embeddable C code for the problem + % update_codegen_defines - update the current codegen defines + % codegen - generate embeddable C code for the problem properties(SetAccess = private, Hidden = true) diff --git a/@osqp/update_codegen_defines.m b/@osqp/update_codegen_defines.m new file mode 100644 index 0000000..8f9efa0 --- /dev/null +++ b/@osqp/update_codegen_defines.m @@ -0,0 +1,16 @@ +function update_codegen_defines(this, varargin) + % UPDATE_CODEGEN_DEFINES update the current codegen defines + + % Check for structure style input + if(isstruct(varargin{1})) + newSettings = varargin{1}; + assert(length(varargin) == 1, 'too many input arguments'); + else + newSettings = struct(varargin{:}); + end + + % Write the new codegen defiens. The C-function checks for input + % validity. + osqp_mex('update_codegen_defines', this.objectHandle, newSettings); +end + diff --git a/c_sources/CMakeLists.txt b/c_sources/CMakeLists.txt index 5c42782..c552db2 100644 --- a/c_sources/CMakeLists.txt +++ b/c_sources/CMakeLists.txt @@ -69,6 +69,7 @@ matlab_add_mex( NAME osqp_mex ${CMAKE_CURRENT_SOURCE_DIR}/memory_matlab.c ${CMAKE_CURRENT_SOURCE_DIR}/osqp_struct_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/osqp_struct_settings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/osqp_struct_codegen_defines.cpp LINK_TO osqpstatic ${UT_LIBRARY} # Force compilation in the traditional C API (equivalent to the -R2017b flag) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 05f82b3..c802636 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -1,9 +1,10 @@ #include +#include #include "mex.h" #include "matrix.h" #include "osqp.h" - +#include //DELETE HERE // Mex-specific functionality #include "osqp_mex.hpp" #include "osqp_struct.h" @@ -13,15 +14,22 @@ //TODO: Check if this definition is required, and maybe replace it with: // enum linsys_solver_type { QDLDL_SOLVER, MKL_PARDISO_SOLVER }; #define QDLDL_SOLVER 0 //Based on the previous API +#define CMD_MAX_LEN 64 +#define OUTPUT_DIR_MAX_LEN 256 +#define PREFIX_MAX_LEN 128 + +using std::string; +using namespace std; //DELETE HERE // Wrapper class to pass the OSQP solver back and forth with Matlab class OsqpData { public: OsqpData() : - solver(NULL) + solver(NULL), defines(NULL) {} OSQPSolver* solver; + OSQPCodegenDefines* defines; }; @@ -33,6 +41,98 @@ static void setToNaN(double* arr_out, OSQPInt len){ } } +// This is a utility function that uses mexErrMsgTxt using std::string instead of char*. +void mexErrMsgTxt(string str) { + mexErrMsgTxt(str.c_str()); +} + +/** + * This is a utility function that assigns prhs[ind] to char. + * + * @param nrhs Number of input arguments + * @param prhs Matlab input arrays + * @param ind prhs index + * @param str Target string + * @param max_len Maximum allowed length of the string. If passed 0 (default), use sizeof(str) instead. +*/ +void setString(int nrhs, const mxArray *prhs[], int ind, char *str, int max_len){ + int str_max_len = sizeof(str); + if (max_len>0) str_max_len = max_len; + if (nrhs < (ind+1) || mxGetString(prhs[ind], str, str_max_len)){ + mexErrMsgTxt("Input #" + std::to_string(ind) + " should be less than " + std::to_string(str_max_len) + " characters long."); + } +} + +/** + * This function validates the inputs to OSQPCodegenDefines. Calls mexErrMsgTxt if a NULL or an invalid input is passed. + * + * @param defines OSQPCodegenDefines object +*/ +void validateCodegenDefines(const OSQPCodegenDefines* defines) { + if (!defines) mexErrMsgTxt("A NULL defines object has been passed."); + + if (defines->embedded_mode){ + if (defines->embedded_mode != 1 && defines->embedded_mode != 2) mexErrMsgTxt("Invalid embedded mode."); + } + + if (defines->float_type){ + if (defines->float_type != 0 && defines->float_type != 1) mexErrMsgTxt("Invalid float type."); + } + + if (defines->printing_enable){ + if (defines->printing_enable != 0 && defines->printing_enable != 1) mexErrMsgTxt("Invalid printing enable."); + } + + if (defines->profiling_enable) { + if (defines->profiling_enable != 0 && defines->profiling_enable != 1) mexErrMsgTxt("Invalid profiling enable."); + } + + if (defines->interrupt_enable) { + if (defines->interrupt_enable != 0 && defines->interrupt_enable != 1) mexErrMsgTxt("Invalid interrupt enable."); + } + + if (defines->derivatives_enable) { + if (defines->derivatives_enable != 0 && defines->derivatives_enable != 1) mexErrMsgTxt("Invalid derivatives enable."); + } +} + + + +/** + * This function updates CodegenDefines object. Calls mexErrMsgTxt if a NULL or an invalid input is passed. + * + * @param target_defines Pointer to the target OSQPCodegenDefines object. + * @param new_defines Pointer to the OSQPCodegenDefines object witht he new values. +*/ +void updateCodegenDefines(OSQPCodegenDefines* target_defines, + const OSQPCodegenDefines* new_defines) { + if (!target_defines) mexErrMsgTxt("Defines is uninitialized. No codegen defines have been configured."); + validateCodegenDefines(new_defines); + + if (new_defines->embedded_mode){ + target_defines->embedded_mode = new_defines->embedded_mode; + } + + if (new_defines->float_type){ + target_defines->float_type = new_defines->float_type; + } + + if (new_defines->printing_enable){ + target_defines->printing_enable = new_defines->printing_enable; + } + + if (new_defines->profiling_enable) { + target_defines->profiling_enable = new_defines->profiling_enable; + } + + if (new_defines->interrupt_enable) { + target_defines->interrupt_enable = new_defines->interrupt_enable; + } + + if (new_defines->derivatives_enable) { + target_defines->derivatives_enable = new_defines->derivatives_enable; + } +} // Main mex function void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) @@ -44,10 +144,12 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) OSQPInt exitflag = 0; // Get the command string - char cmd[64]; + char cmd[CMD_MAX_LEN]; - if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) - mexErrMsgTxt("First input should be a command string less than 64 characters long."); + setString(nrhs, prhs, 0, cmd, CMD_MAX_LEN); + // DELETE HERE + // if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) + // mexErrMsgTxt("First input should be a command string less than 64 characters long."); /* * First check to see if a new object was requested @@ -59,6 +161,8 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } // Return a handle to a new C++ wrapper instance osqpData = new OsqpData; + osqpData->defines = new OSQPCodegenDefines; + osqp_set_default_codegen_defines(osqpData->defines); plhs[0] = convertPtr2Mat(osqpData); return; } @@ -115,7 +219,8 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) char constant[64]; int constantLength = mxGetN(prhs[1]) + 1; - mxGetString(prhs[1], constant, constantLength); + setString(nrhs, prhs, 1, constant, constantLength); + //mxGetString(prhs[1], constant, constantLength); //DELETE HERE auto ci = intConstants.find(constant); @@ -147,9 +252,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) */ // Check for a second input, which should be the class instance handle - if (nrhs < 2) - mexErrMsgTxt("Second input should be a class instance handle."); - + if (nrhs < 2) mexErrMsgTxt("Second input should be a class instance handle."); // Get the class instance pointer from the second input osqpData = convertMat2Ptr(prhs[1]); @@ -158,6 +261,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("delete", cmd)) { osqp_cleanup(osqpData->solver); + if (osqpData->defines) c_free(osqpData->defines); destroyObject(prhs[1]); // Warn if other commands were ignored if (nlhs != 0 || nrhs != 2) @@ -169,10 +273,10 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (!strcmp("current_settings", cmd)) { // Throw an error if this is called before solver is configured if(!osqpData->solver) { - mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); } if(!osqpData->solver->settings) { - mexErrMsgTxt("Solver settings is uninitialized. No settings have been configured."); + mexErrMsgTxt("Solver settings is uninitialized. No settings have been configured."); } // Report the current settings @@ -188,7 +292,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // and for all error checking // throw an error if this is called before solver is configured if(!osqpData->solver){ - mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); + mexErrMsgTxt("Solver is uninitialized. No settings have been configured."); } OSQPSettingsWrapper settings(prhs[2]); @@ -499,6 +603,55 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) return; } + if (!strcmp("default_codegen_defines", cmd)) { + // Warn if other commands were ignored + if (nrhs > 2) + mexWarnMsgTxt("Default codegen settings: unexpected number of arguments."); + + // Create a Settings structure in default form and report the results + OSQPCodegenDefinesWrapper codegen_settings; + plhs[0] = codegen_settings.GetMxStruct(); + return; + } + + if (!strcmp("update_codegen_defines", cmd)) { + // Overwrite the current settings. Mex function is responsible + // for disallowing overwrite of selected settings after initialization, + // and for all error checking + // throw an error if this is called before solver is configured + if(!osqpData->defines){ + mexErrMsgTxt("Defines is uninitialized. No codegen defines have been configured."); + } + + OSQPCodegenDefinesWrapper defines(prhs[2]); + updateCodegenDefines(osqpData->defines, defines.GetOSQPStruct()); + return; + } + + if (!strcmp("codegen", cmd)) { + + //Check that the correct number of arguments is passed + if (nrhs != 4) mexErrMsgTxt("Codegen: unexpected number of arguments"); + cout << "Line #1" << endl; + char output_dir[OUTPUT_DIR_MAX_LEN]; + cout << "Line #2" << endl; + char prefix[PREFIX_MAX_LEN]; + cout << "Line #3" << endl; + setString(nrhs, prhs, 2, output_dir, OUTPUT_DIR_MAX_LEN); + cout << "Line #4" << endl; + setString(nrhs, prhs, 3, prefix, PREFIX_MAX_LEN); + cout << "Line #5" << endl; + + //Check that the solver was initialized + if(!osqpData->solver) mexErrMsgTxt("Solver has not been initialized."); + if(!osqpData->defines) mexErrMsgTxt("Codegen defines has not been initialized."); + + exitflag = osqp_codegen(osqpData->solver,output_dir, prefix, osqpData->defines); + + if (exitflag) mexErrMsgTxt("Codegen failed with exitflag = " + std::to_string(exitflag)); + return; + } + // Got here, so command not recognized mexErrMsgTxt("Command not recognized."); } diff --git a/c_sources/osqp_struct.h b/c_sources/osqp_struct.h index 94a432f..7971dd0 100644 --- a/c_sources/osqp_struct.h +++ b/c_sources/osqp_struct.h @@ -202,4 +202,10 @@ typedef OSQPStructWrapper OSQPSettingsWrapper; */ typedef OSQPStructWrapper OSQPInfoWrapper; +/** + * Wrapper around the OSQPCodegenDefines struct + */ +typedef OSQPStructWrapper OSQPCodegenDefinesWrapper; + + #endif \ No newline at end of file diff --git a/c_sources/osqp_struct_codegen_defines.cpp b/c_sources/osqp_struct_codegen_defines.cpp new file mode 100644 index 0000000..c13eca9 --- /dev/null +++ b/c_sources/osqp_struct_codegen_defines.cpp @@ -0,0 +1,28 @@ +#include +#include "osqp_struct.h" + +/* + * Specialization for the codegen_defines struct + */ +template<> +void OSQPStructWrapper::registerFields() { + m_struct = static_cast(c_calloc(1, sizeof(OSQPCodegenDefines))); + if(!m_struct) + mexErrMsgTxt("Failed to allocate a OSQPCodegenDefines object."); + + osqp_set_default_codegen_defines(m_struct); + + /* + * Register the mapping between struct field name and the settings memory location + */ + m_structFields.push_back(new OSQPStructField(&m_struct->embedded_mode, "embedded_mode")); + m_structFields.push_back(new OSQPStructField(&m_struct->float_type, "float_type")); + m_structFields.push_back(new OSQPStructField(&m_struct->printing_enable, "printing_enable")); + m_structFields.push_back(new OSQPStructField(&m_struct->profiling_enable, "profiling_enable")); + m_structFields.push_back(new OSQPStructField(&m_struct->interrupt_enable, "interrupt_enable")); + m_structFields.push_back(new OSQPStructField(&m_struct->derivatives_enable,"derivatives_enable")); +} + + +// Instantiate the OSQPCodegenDefines wrapper class +template class OSQPStructWrapper; \ No newline at end of file From fcc4055b49431c522d61d6609db94912d6468612 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Fri, 16 Feb 2024 15:34:11 -0500 Subject: [PATCH 34/36] Minor changes --- c_sources/osqp_mex.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index c802636..9b81121 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -4,7 +4,6 @@ #include "mex.h" #include "matrix.h" #include "osqp.h" -#include //DELETE HERE // Mex-specific functionality #include "osqp_mex.hpp" #include "osqp_struct.h" @@ -19,7 +18,6 @@ #define PREFIX_MAX_LEN 128 using std::string; -using namespace std; //DELETE HERE // Wrapper class to pass the OSQP solver back and forth with Matlab class OsqpData @@ -147,9 +145,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) char cmd[CMD_MAX_LEN]; setString(nrhs, prhs, 0, cmd, CMD_MAX_LEN); - // DELETE HERE - // if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) - // mexErrMsgTxt("First input should be a command string less than 64 characters long."); /* * First check to see if a new object was requested @@ -220,7 +215,6 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) char constant[64]; int constantLength = mxGetN(prhs[1]) + 1; setString(nrhs, prhs, 1, constant, constantLength); - //mxGetString(prhs[1], constant, constantLength); //DELETE HERE auto ci = intConstants.find(constant); @@ -632,15 +626,10 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) //Check that the correct number of arguments is passed if (nrhs != 4) mexErrMsgTxt("Codegen: unexpected number of arguments"); - cout << "Line #1" << endl; char output_dir[OUTPUT_DIR_MAX_LEN]; - cout << "Line #2" << endl; char prefix[PREFIX_MAX_LEN]; - cout << "Line #3" << endl; setString(nrhs, prhs, 2, output_dir, OUTPUT_DIR_MAX_LEN); - cout << "Line #4" << endl; setString(nrhs, prhs, 3, prefix, PREFIX_MAX_LEN); - cout << "Line #5" << endl; //Check that the solver was initialized if(!osqpData->solver) mexErrMsgTxt("Solver has not been initialized."); From a7a5e8904b2b717bbc72cfbf9161912e56e806a0 Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Tue, 20 Feb 2024 16:37:24 -0500 Subject: [PATCH 35/36] Fixed a bug where osqpData->defines would be freed twice --- c_sources/osqp_mex.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp index 9b81121..9892a1b 100755 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -145,7 +145,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) char cmd[CMD_MAX_LEN]; setString(nrhs, prhs, 0, cmd, CMD_MAX_LEN); - + /* * First check to see if a new object was requested */ @@ -253,9 +253,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // delete the object and its data if (!strcmp("delete", cmd)) { - osqp_cleanup(osqpData->solver); - if (osqpData->defines) c_free(osqpData->defines); destroyObject(prhs[1]); // Warn if other commands were ignored if (nlhs != 0 || nrhs != 2) From f354a10bb4823ab726d6ef8372ecbd23c8a6e6cd Mon Sep 17 00:00:00 2001 From: Amit Solomon Date: Thu, 4 Apr 2024 06:11:23 -0400 Subject: [PATCH 36/36] Adding SPDX-License-Identifier: Apache-2.0 to all .m, .c, .cpp, .h files that do not contain the word "License" in them. --- @osqp/build.m | 2 ++ @osqp/codegen.m | 2 ++ @osqp/osqp.m | 2 ++ @osqp/private/linsys_solver_to_string.m | 2 ++ @osqp/private/string_to_linsys_solver.m | 2 ++ @osqp/setup.m | 2 ++ @osqp/solve.m | 2 ++ @osqp/update.m | 2 ++ @osqp/update_codegen_defines.m | 2 ++ @osqp/update_settings.m | 2 ++ @osqp/validate_settings.m | 2 ++ @osqp/warm_start.m | 2 ++ c_sources/arrays_matlab.h | 2 ++ c_sources/interrupt_matlab.c | 2 ++ c_sources/memory_matlab.c | 2 ++ c_sources/memory_matlab.h | 2 ++ c_sources/osqp_mex.cpp | 2 ++ c_sources/osqp_struct.h | 2 ++ c_sources/osqp_struct_codegen_defines.cpp | 2 ++ c_sources/osqp_struct_info.cpp | 2 ++ c_sources/osqp_struct_settings.cpp | 2 ++ codegen/files_to_generate/emosqp_mex.c | 2 ++ codegen/files_to_generate/example.c | 2 ++ codegen/make_emosqp.m | 2 ++ codegen/render_workspace.m | 2 ++ examples/codegen_test.m | 2 ++ examples/easy_lasso_qp.m | 2 ++ examples/examples_old.m | 2 ++ examples/examples_osqpmatlab.m | 2 ++ examples/interrupt.m | 2 ++ examples/osqp_demo.m | 2 ++ examples/osqpmatlab.m | 2 ++ package/install_osqp.m | 2 ++ package/package_osqp.m | 2 ++ run_osqp_tests.m | 2 ++ unittests/basic_tests.m | 2 ++ unittests/codegen_mat_tests.m | 2 ++ unittests/codegen_vec_tests.m | 2 ++ unittests/dual_infeasibility_tests.m | 2 ++ unittests/feasibility_tests.m | 2 ++ unittests/non_cvx_tests.m | 2 ++ unittests/primal_infeasibility_tests.m | 2 ++ unittests/unconstrained_tests.m | 2 ++ unittests/update_matrices_tests.m | 2 ++ unittests/warm_start_tests.m | 2 ++ utils/convertProblemToMat.m | 2 ++ 46 files changed, 92 insertions(+) mode change 100755 => 100644 c_sources/osqp_mex.cpp diff --git a/@osqp/build.m b/@osqp/build.m index 0d13215..5796463 100644 --- a/@osqp/build.m +++ b/@osqp/build.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function build(varargin) % Matlab MEX makefile for OSQP. % diff --git a/@osqp/codegen.m b/@osqp/codegen.m index 3bd3958..f8e52ec 100644 --- a/@osqp/codegen.m +++ b/@osqp/codegen.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% function codegen(this, out, varargin) % CODEGEN generate C code for the parametric problem diff --git a/@osqp/osqp.m b/@osqp/osqp.m index ba91633..7d64c82 100644 --- a/@osqp/osqp.m +++ b/@osqp/osqp.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef osqp < handle % osqp interface class for OSQP solver % This class provides a complete interface to the C implementation diff --git a/@osqp/private/linsys_solver_to_string.m b/@osqp/private/linsys_solver_to_string.m index bac8047..d1ea38e 100644 --- a/@osqp/private/linsys_solver_to_string.m +++ b/@osqp/private/linsys_solver_to_string.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + % Convert linear systme solver integer to string function [linsys_solver_string] = linsys_solver_to_string(linsys_solver) switch linsys_solver diff --git a/@osqp/private/string_to_linsys_solver.m b/@osqp/private/string_to_linsys_solver.m index 51e5c99..3c4f241 100644 --- a/@osqp/private/string_to_linsys_solver.m +++ b/@osqp/private/string_to_linsys_solver.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function [linsys_solver] = string_to_linsys_solver(linsys_solver_string) linsys_solver_string = lower(linsys_solver_string); switch linsys_solver_string diff --git a/@osqp/setup.m b/@osqp/setup.m index 5317e2d..f70b662 100644 --- a/@osqp/setup.m +++ b/@osqp/setup.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% function varargout = setup(this, varargin) % SETUP configure solver with problem data diff --git a/@osqp/solve.m b/@osqp/solve.m index 2ce96cd..8165b01 100644 --- a/@osqp/solve.m +++ b/@osqp/solve.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% function varargout = solve(this, varargin) % SOLVE solve the QP diff --git a/@osqp/update.m b/@osqp/update.m index 4f27b6e..4ce0903 100644 --- a/@osqp/update.m +++ b/@osqp/update.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% function update(this,varargin) % UPDATE modify the linear cost term and/or lower and upper bounds diff --git a/@osqp/update_codegen_defines.m b/@osqp/update_codegen_defines.m index 8f9efa0..4ec8aa9 100644 --- a/@osqp/update_codegen_defines.m +++ b/@osqp/update_codegen_defines.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function update_codegen_defines(this, varargin) % UPDATE_CODEGEN_DEFINES update the current codegen defines diff --git a/@osqp/update_settings.m b/@osqp/update_settings.m index 233710d..50ec8d6 100644 --- a/@osqp/update_settings.m +++ b/@osqp/update_settings.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function update_settings(this, varargin) % UPDATE_SETTINGS update the current solver settings structure diff --git a/@osqp/validate_settings.m b/@osqp/validate_settings.m index 3ce7d6d..665673a 100644 --- a/@osqp/validate_settings.m +++ b/@osqp/validate_settings.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function currentSettings = validate_settings(this, isInitialization, varargin) % Don't allow these fields to be changed unmodifiableFields = {'scaling', 'linsys_solver'}; diff --git a/@osqp/warm_start.m b/@osqp/warm_start.m index 81d56ae..1fc9da4 100644 --- a/@osqp/warm_start.m +++ b/@osqp/warm_start.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function warm_start(this, varargin) % WARM_START warm start primal and/or dual variables % diff --git a/c_sources/arrays_matlab.h b/c_sources/arrays_matlab.h index 0d5408b..3a76fa4 100644 --- a/c_sources/arrays_matlab.h +++ b/c_sources/arrays_matlab.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #ifndef ARRAYS_MATLAB_H_ #define ARRAYS_MATLAB_H_ diff --git a/c_sources/interrupt_matlab.c b/c_sources/interrupt_matlab.c index 1a43135..e61da20 100644 --- a/c_sources/interrupt_matlab.c +++ b/c_sources/interrupt_matlab.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + /* * Implements interrupt handling using ctrl-c for MATLAB mex files. */ diff --git a/c_sources/memory_matlab.c b/c_sources/memory_matlab.c index c3b06a8..0c25bb9 100644 --- a/c_sources/memory_matlab.c +++ b/c_sources/memory_matlab.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #include void* c_calloc(size_t num, size_t size) { diff --git a/c_sources/memory_matlab.h b/c_sources/memory_matlab.h index abd48b6..b63a0cf 100644 --- a/c_sources/memory_matlab.h +++ b/c_sources/memory_matlab.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + /* Memory managment for MATLAB */ #include "mex.h" diff --git a/c_sources/osqp_mex.cpp b/c_sources/osqp_mex.cpp old mode 100755 new mode 100644 index 9892a1b..d0ad9f4 --- a/c_sources/osqp_mex.cpp +++ b/c_sources/osqp_mex.cpp @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #include #include diff --git a/c_sources/osqp_struct.h b/c_sources/osqp_struct.h index 7971dd0..4ee2a61 100644 --- a/c_sources/osqp_struct.h +++ b/c_sources/osqp_struct.h @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #ifndef OSQP_STRUCT_H_ #define OSQP_STRUCT_H_ diff --git a/c_sources/osqp_struct_codegen_defines.cpp b/c_sources/osqp_struct_codegen_defines.cpp index c13eca9..7f35a60 100644 --- a/c_sources/osqp_struct_codegen_defines.cpp +++ b/c_sources/osqp_struct_codegen_defines.cpp @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #include #include "osqp_struct.h" diff --git a/c_sources/osqp_struct_info.cpp b/c_sources/osqp_struct_info.cpp index b454a1c..5db5994 100644 --- a/c_sources/osqp_struct_info.cpp +++ b/c_sources/osqp_struct_info.cpp @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #include #include "osqp_struct.h" diff --git a/c_sources/osqp_struct_settings.cpp b/c_sources/osqp_struct_settings.cpp index 97fc357..92a8c4b 100644 --- a/c_sources/osqp_struct_settings.cpp +++ b/c_sources/osqp_struct_settings.cpp @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #include #include "osqp_struct.h" diff --git a/codegen/files_to_generate/emosqp_mex.c b/codegen/files_to_generate/emosqp_mex.c index f2d6143..36af936 100644 --- a/codegen/files_to_generate/emosqp_mex.c +++ b/codegen/files_to_generate/emosqp_mex.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #include #include #include "osqp.h" diff --git a/codegen/files_to_generate/example.c b/codegen/files_to_generate/example.c index beac16e..bc9aa7f 100644 --- a/codegen/files_to_generate/example.c +++ b/codegen/files_to_generate/example.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + #include "stdio.h" #include diff --git a/codegen/make_emosqp.m b/codegen/make_emosqp.m index 5bb87ee..8d077e3 100644 --- a/codegen/make_emosqp.m +++ b/codegen/make_emosqp.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function make_emosqp(target_dir, mex_cfile, EMBEDDED_FLAG, FLOAT_FLAG, LONG_FLAG) % Matlab MEX makefile for code generated solver. diff --git a/codegen/render_workspace.m b/codegen/render_workspace.m index e9d9a79..20ff761 100644 --- a/codegen/render_workspace.m +++ b/codegen/render_workspace.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function render_workspace( work, hfname, cfname, embedded_flag ) %RENDER_WORKSPACE Write workspace to header file. diff --git a/examples/codegen_test.m b/examples/codegen_test.m index 5734246..6918e49 100644 --- a/examples/codegen_test.m +++ b/examples/codegen_test.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% Simple problem m = 50; n = 100; diff --git a/examples/easy_lasso_qp.m b/examples/easy_lasso_qp.m index fe13dac..44523d8 100644 --- a/examples/easy_lasso_qp.m +++ b/examples/easy_lasso_qp.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% Lasso problem % Problem dimensions and sparity diff --git a/examples/examples_old.m b/examples/examples_old.m index a584c42..1fa160f 100644 --- a/examples/examples_old.m +++ b/examples/examples_old.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% OSQPtest diff --git a/examples/examples_osqpmatlab.m b/examples/examples_osqpmatlab.m index 15b4c77..ae2e246 100644 --- a/examples/examples_osqpmatlab.m +++ b/examples/examples_osqpmatlab.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% Simple problem m = 50; n = 100; diff --git a/examples/interrupt.m b/examples/interrupt.m index bb81ee8..a61e6b8 100644 --- a/examples/interrupt.m +++ b/examples/interrupt.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + %% Big lasso problem % Problem dimensions and sparity diff --git a/examples/osqp_demo.m b/examples/osqp_demo.m index da9d2eb..878e442 100644 --- a/examples/osqp_demo.m +++ b/examples/osqp_demo.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + % Demo showing the usage of OSQP from Matlab and the code generation features. % This problem is the same one that is presented in the osqp_demo.c file. diff --git a/examples/osqpmatlab.m b/examples/osqpmatlab.m index dfa2ba0..5a26e16 100644 --- a/examples/osqpmatlab.m +++ b/examples/osqpmatlab.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function [x, y, cost, status, iter] = osqpmatlab(problem, warm_start, settings) %#codegen % OSQPMATLAB Pure Matlab implementation of the OSQP solver % diff --git a/package/install_osqp.m b/package/install_osqp.m index 7df3add..4fc6109 100644 --- a/package/install_osqp.m +++ b/package/install_osqp.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function install_osqp % Install the OSQP solver Matlab interface diff --git a/package/package_osqp.m b/package/package_osqp.m index b0c39b2..cd730f3 100644 --- a/package/package_osqp.m +++ b/package/package_osqp.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function package_osqp(version) % Create OSQP matlab interface package diff --git a/run_osqp_tests.m b/run_osqp_tests.m index 2bfca1c..69e5a66 100644 --- a/run_osqp_tests.m +++ b/run_osqp_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + import matlab.unittest.TestSuite; [osqp_classpath,~,~] = fileparts( mfilename( 'fullpath' ) ); diff --git a/unittests/basic_tests.m b/unittests/basic_tests.m index c938a7a..4d21ad1 100644 --- a/unittests/basic_tests.m +++ b/unittests/basic_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef basic_tests < matlab.unittest.TestCase %TEST_BASIC_QP Solve Basic QP Problem diff --git a/unittests/codegen_mat_tests.m b/unittests/codegen_mat_tests.m index 07c3d28..5087676 100644 --- a/unittests/codegen_mat_tests.m +++ b/unittests/codegen_mat_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef codegen_mat_tests < matlab.unittest.TestCase %TEST_BASIC_QP Solve Basic QP Problem diff --git a/unittests/codegen_vec_tests.m b/unittests/codegen_vec_tests.m index f5a8f42..d9c6843 100644 --- a/unittests/codegen_vec_tests.m +++ b/unittests/codegen_vec_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef codegen_vec_tests < matlab.unittest.TestCase %TEST_BASIC_QP Solve Basic QP Problem diff --git a/unittests/dual_infeasibility_tests.m b/unittests/dual_infeasibility_tests.m index edeb680..3cc861c 100644 --- a/unittests/dual_infeasibility_tests.m +++ b/unittests/dual_infeasibility_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef dual_infeasibility_tests < matlab.unittest.TestCase properties diff --git a/unittests/feasibility_tests.m b/unittests/feasibility_tests.m index cc15f6d..b2e2801 100644 --- a/unittests/feasibility_tests.m +++ b/unittests/feasibility_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef feasibility_tests < matlab.unittest.TestCase %FEASIBILITY_TESTS Solve equality constrained feasibility problem diff --git a/unittests/non_cvx_tests.m b/unittests/non_cvx_tests.m index 53df8e9..ab9eab2 100644 --- a/unittests/non_cvx_tests.m +++ b/unittests/non_cvx_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef non_cvx_tests < matlab.unittest.TestCase %NON_CVX_TESTS Try to solve a non-convex QP diff --git a/unittests/primal_infeasibility_tests.m b/unittests/primal_infeasibility_tests.m index fdea926..a9568a8 100644 --- a/unittests/primal_infeasibility_tests.m +++ b/unittests/primal_infeasibility_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef primal_infeasibility_tests < matlab.unittest.TestCase properties diff --git a/unittests/unconstrained_tests.m b/unittests/unconstrained_tests.m index fd4c1c1..a92b1f5 100644 --- a/unittests/unconstrained_tests.m +++ b/unittests/unconstrained_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef unconstrained_tests < matlab.unittest.TestCase %UNCONSTRAINED_TESTS Solve unconstrained quadratic program diff --git a/unittests/update_matrices_tests.m b/unittests/update_matrices_tests.m index 08eeb79..e52e615 100644 --- a/unittests/update_matrices_tests.m +++ b/unittests/update_matrices_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef update_matrices_tests < matlab.unittest.TestCase %TEST_BASIC_QP Solve Basic QP Problem diff --git a/unittests/warm_start_tests.m b/unittests/warm_start_tests.m index db5ff7b..2b33375 100644 --- a/unittests/warm_start_tests.m +++ b/unittests/warm_start_tests.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + classdef warm_start_tests < matlab.unittest.TestCase %WARM_START_TESTS Warm Start problems solution diff --git a/utils/convertProblemToMat.m b/utils/convertProblemToMat.m index 614e6c0..cc9eece 100644 --- a/utils/convertProblemToMat.m +++ b/utils/convertProblemToMat.m @@ -1,3 +1,5 @@ +% SPDX-License-Identifier: Apache-2.0 + function convertProblemToMat(filename, Pdata, qdata, Adata, ldata, udata) Ptrip = load(Pdata); P = spconvert(Ptrip);