0001 classdef osqp < handle
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027 properties (SetAccess = private, Hidden = true)
0028 objectHandle
0029 end
0030 methods
0031
0032 function this = osqp(varargin)
0033
0034 this.objectHandle = osqp_mex('new', varargin{:});
0035 end
0036
0037
0038 function delete(this)
0039
0040 osqp_mex('delete', this.objectHandle);
0041 end
0042
0043
0044 function out = current_settings(this)
0045
0046 out = osqp_mex('current_settings', this.objectHandle);
0047
0048
0049 out.linsys_solver = linsys_solver_to_string(out.linsys_solver);
0050
0051 end
0052
0053
0054 function update_settings(this,varargin)
0055
0056
0057
0058
0059 newSettings = validateSettings(this,false,varargin{:});
0060
0061
0062
0063 osqp_mex('update_settings', this.objectHandle, newSettings);
0064
0065 end
0066
0067
0068 function [n,m] = get_dimensions(this)
0069
0070
0071 [n,m] = osqp_mex('get_dimensions', this.objectHandle);
0072
0073 end
0074
0075
0076 function update(this,varargin)
0077
0078
0079
0080
0081 allowedFields = {'q','l','u','Px','Px_idx','Ax','Ax_idx'};
0082
0083 if(isempty(varargin))
0084 return;
0085 elseif(length(varargin) == 1)
0086 if(~isstruct(varargin{1}))
0087 error('Single input should be a structure with new problem data');
0088 else
0089 newData = varargin{1};
0090 end
0091 else
0092 newData = struct(varargin{:});
0093 end
0094
0095
0096 newFields = fieldnames(newData);
0097 badFieldsIdx = find(~ismember(newFields,allowedFields));
0098 if(~isempty(badFieldsIdx))
0099 error('Unrecognized input field ''%s'' detected',newFields{badFieldsIdx(1)});
0100 end
0101
0102
0103
0104 try q = double(full(newData.q(:))); catch q = []; end
0105 try l = double(full(newData.l(:))); catch l = []; end
0106 try u = double(full(newData.u(:))); catch u = []; end
0107 try Px = double(full(newData.Px(:))); catch Px = []; end
0108 try Px_idx = double(full(newData.Px_idx(:))); catch Px_idx = []; end
0109 try Ax = double(full(newData.Ax(:))); catch Ax = []; end
0110 try Ax_idx = double(full(newData.Ax_idx(:))); catch Ax_idx = []; end
0111
0112 [n,m] = get_dimensions(this);
0113
0114 assert(isempty(q) || length(q) == n, 'input ''q'' is the wrong size');
0115 assert(isempty(l) || length(l) == m, 'input ''u'' is the wrong size');
0116 assert(isempty(u) || length(u) == m, 'input ''l'' is the wrong size');
0117 assert(isempty(Px) || isempty(Px_idx) || length(Px) == length(Px_idx), ...
0118 'inputs ''Px'' and ''Px_idx'' must be the same size');
0119 assert(isempty(Ax) || isempty(Ax_idx) || length(Ax) == length(Ax_idx), ...
0120 'inputs ''Ax'' and ''Ax_idx'' must be the same size');
0121
0122
0123
0124 if (~isempty(Px_idx))
0125 Px_idx = Px_idx - 1;
0126 end
0127 if (~isempty(Ax_idx))
0128 Ax_idx = Ax_idx - 1;
0129 end
0130
0131
0132 if (~isempty(u))
0133 u = min(u, osqp.constant('OSQP_INFTY'));
0134 end
0135 if (~isempty(l))
0136 l = max(l, -osqp.constant('OSQP_INFTY'));
0137 end
0138
0139
0140
0141 osqp_mex('update', this.objectHandle, ...
0142 q, l, u, Px, Px_idx, length(Px), Ax, Ax_idx, length(Ax));
0143
0144 end
0145
0146
0147 function varargout = setup(this, varargin)
0148
0149
0150
0151
0152 nargin = length(varargin);
0153
0154
0155
0156 assert(nargin >= 5, 'incorrect number of inputs');
0157 [P,q,A,l,u] = deal(varargin{1:5});
0158
0159
0160
0161
0162
0163
0164 if (isempty(P))
0165 if (~isempty(q))
0166 n = length(q);
0167 else
0168 if (~isempty(A))
0169 n = size(A, 2);
0170 else
0171 error('The problem does not have any variables');
0172 end
0173 end
0174 else
0175 n = size(P, 1);
0176 end
0177
0178
0179 if (isempty(A))
0180 m = 0;
0181 else
0182 m = size(A, 1);
0183 end
0184
0185
0186
0187
0188
0189 if (isempty(P))
0190 P = sparse(n, n);
0191 else
0192 P = sparse(P);
0193 end
0194 if (~istriu(P))
0195 P = triu(P);
0196 end
0197 if (isempty(q))
0198 q = zeros(n, 1);
0199 else
0200 q = full(q(:));
0201 end
0202
0203
0204 if (isempty(A) && (~isempty(l) || ~isempty(u))) || ...
0205 (~isempty(A) && (isempty(l) && isempty(u)))
0206 error('A must be supplied together with at least one bound l or u');
0207 end
0208
0209 if (~isempty(A) && isempty(l))
0210 l = -Inf(m, 1);
0211 end
0212
0213 if (~isempty(A) && isempty(u))
0214 u = Inf(m, 1);
0215 end
0216
0217 if (isempty(A))
0218 A = sparse(m, n);
0219 l = -Inf(m, 1);
0220 u = Inf(m, 1);
0221 else
0222 l = full(l(:));
0223 u = full(u(:));
0224 A = sparse(A);
0225 end
0226
0227
0228
0229
0230
0231
0232 assert(length(q) == n, 'Incorrect dimension of q');
0233 assert(length(l) == m, 'Incorrect dimension of l');
0234 assert(length(u) == m, 'Incorrect dimension of u');
0235
0236
0237
0238
0239 u = min(u, osqp.constant('OSQP_INFTY'));
0240 l = max(l, -osqp.constant('OSQP_INFTY'));
0241
0242
0243
0244
0245
0246
0247 theSettings = validateSettings(this,true,varargin{6:end});
0248
0249 [varargout{1:nargout}] = osqp_mex('setup', this.objectHandle, n,m,P,q,A,l,u,theSettings);
0250
0251 end
0252
0253
0254
0255
0256 function warm_start(this, varargin)
0257
0258
0259
0260
0261
0262
0263
0264
0265
0266 [n, m] = get_dimensions(this);
0267
0268
0269 allowedFields = {'x','y'};
0270
0271 if(isempty(varargin))
0272 return;
0273 elseif(length(varargin) == 1)
0274 if(~isstruct(varargin{1}))
0275 error('Single input should be a structure with new problem data');
0276 else
0277 newData = varargin{1};
0278 end
0279 else
0280 newData = struct(varargin{:});
0281 end
0282
0283
0284 newFields = fieldnames(newData);
0285 badFieldsIdx = find(~ismember(newFields,allowedFields));
0286 if(~isempty(badFieldsIdx))
0287 error('Unrecognized input field ''%s'' detected',newFields{badFieldsIdx(1)});
0288 end
0289
0290
0291
0292 try x = double(full(newData.x(:))); catch x = []; end
0293 try y = double(full(newData.y(:))); catch y = []; end
0294
0295
0296 assert(isempty(x) || length(x) == n, 'input ''x'' is the wrong size');
0297 assert(isempty(y) || length(y) == m, 'input ''y'' is the wrong size');
0298
0299
0300
0301 if (~isempty(x) && isempty(y))
0302 osqp_mex('warm_start_x', this.objectHandle, x);
0303 return;
0304 end
0305
0306 if (isempty(x) && ~isempty(y))
0307 osqp_mex('warm_start_y', this.objectHandle, y);
0308 end
0309
0310 if (~isempty(x) && ~isempty(y))
0311 osqp_mex('warm_start', this.objectHandle, x, y);
0312 end
0313
0314 if (isempty(x) && isempty(y))
0315 error('Unrecognized fields');
0316 end
0317
0318 end
0319
0320
0321 function varargout = solve(this, varargin)
0322
0323
0324 nargoutchk(0,1);
0325 [out.x, out.y, out.prim_inf_cert, out.dual_inf_cert, out.info] = osqp_mex('solve', this.objectHandle);
0326 if(nargout)
0327 varargout{1} = out;
0328 end
0329 return;
0330 end
0331
0332
0333 function codegen(this, target_dir, varargin)
0334
0335
0336
0337
0338
0339 p = inputParser;
0340 defaultProject = '';
0341 expectedProject = {'', 'Makefile', 'MinGW Makefiles', 'Unix Makefiles', 'CodeBlocks', 'Xcode'};
0342 defaultParams = 'vectors';
0343 expectedParams = {'vectors', 'matrices'};
0344 defaultMexname = 'emosqp';
0345 defaultFloat = false;
0346 defaultLong = true;
0347 defaultFW = false;
0348
0349 addRequired(p, 'target_dir', @isstr);
0350 addParameter(p, 'project_type', defaultProject, ...
0351 @(x) ischar(validatestring(x, expectedProject)));
0352 addParameter(p, 'parameters', defaultParams, ...
0353 @(x) ischar(validatestring(x, expectedParams)));
0354 addParameter(p, 'mexname', defaultMexname, @isstr);
0355 addParameter(p, 'FLOAT', defaultFloat, @islogical);
0356 addParameter(p, 'LONG', defaultLong, @islogical);
0357 addParameter(p, 'force_rewrite', defaultFW, @islogical);
0358
0359 parse(p, target_dir, varargin{:});
0360
0361
0362 if strcmp(p.Results.parameters, 'vectors')
0363 embedded = 1;
0364 else
0365 embedded = 2;
0366 end
0367 if p.Results.FLOAT
0368 float_flag = 'ON';
0369 else
0370 float_flag = 'OFF';
0371 end
0372 if p.Results.LONG
0373 long_flag = 'ON';
0374 else
0375 long_flag = 'OFF';
0376 end
0377 if strcmp(p.Results.project_type, 'Makefile')
0378 if (ispc)
0379 project_type = 'MinGW Makefiles';
0380 elseif (ismac || isunix)
0381 project_type = 'Unix Makefiles';
0382 end
0383 else
0384 project_type = p.Results.project_type;
0385 end
0386
0387
0388 if exist(target_dir, 'dir')
0389 if p.Results.force_rewrite
0390 rmdir(target_dir, 's');
0391 else
0392 while(1)
0393 prompt = sprintf('Directory "%s" already exists. Do you want to replace it? y/n [y]: ', target_dir);
0394 str = input(prompt, 's');
0395
0396 if any(strcmpi(str, {'','y'}))
0397 rmdir(target_dir, 's');
0398 break;
0399 elseif strcmpi(str, 'n')
0400 return;
0401 end
0402 end
0403 end
0404 end
0405
0406
0407 [osqp_path,~,~] = fileparts(which('osqp.m'));
0408
0409
0410 addpath(fullfile(osqp_path, 'codegen'));
0411
0412
0413 cg_dir = fullfile(osqp_path, 'codegen');
0414 files_to_generate_path = fullfile(cg_dir, 'files_to_generate');
0415
0416
0417 work = osqp_mex('get_workspace', this.objectHandle);
0418
0419
0420 fprintf('Creating target directories...\t\t\t\t\t');
0421 target_configure_dir = fullfile(target_dir, 'configure');
0422 target_include_dir = fullfile(target_dir, 'include');
0423 target_src_dir = fullfile(target_dir, 'src');
0424
0425 if ~exist(target_dir, 'dir')
0426 mkdir(target_dir);
0427 end
0428 if ~exist(target_configure_dir, 'dir')
0429 mkdir(target_configure_dir);
0430 end
0431 if ~exist(target_include_dir, 'dir')
0432 mkdir(target_include_dir);
0433 end
0434 if ~exist(target_src_dir, 'dir')
0435 mkdir(fullfile(target_src_dir, 'osqp'));
0436 end
0437 fprintf('[done]\n');
0438
0439
0440 fprintf('Copying OSQP source files...\t\t\t\t\t');
0441 cdir = fullfile(cg_dir, 'sources', 'src');
0442 cfiles = dir(fullfile(cdir, '*.c'));
0443 for i = 1 : length(cfiles)
0444 if embedded == 1
0445
0446 if ~strcmp(cfiles(i).name, 'kkt.c')
0447 copyfile(fullfile(cdir, cfiles(i).name), ...
0448 fullfile(target_src_dir, 'osqp', cfiles(i).name));
0449 end
0450 else
0451 copyfile(fullfile(cdir, cfiles(i).name), ...
0452 fullfile(target_src_dir, 'osqp', cfiles(i).name));
0453 end
0454 end
0455 configure_dir = fullfile(cg_dir, 'sources', 'configure');
0456 configure_files = dir(fullfile(configure_dir, '*.h.in'));
0457 for i = 1 : length(configure_files)
0458 copyfile(fullfile(configure_dir, configure_files(i).name), ...
0459 fullfile(target_configure_dir, configure_files(i).name));
0460 end
0461 hdir = fullfile(cg_dir, 'sources', 'include');
0462 hfiles = dir(fullfile(hdir, '*.h'));
0463 for i = 1 : length(hfiles)
0464 if embedded == 1
0465
0466 if ~strcmp(hfiles(i).name, 'kkt.h')
0467 copyfile(fullfile(hdir, hfiles(i).name), ...
0468 fullfile(target_include_dir, hfiles(i).name));
0469 end
0470 else
0471 copyfile(fullfile(hdir, hfiles(i).name), ...
0472 fullfile(target_include_dir, hfiles(i).name));
0473 end
0474 end
0475
0476
0477 copyfile(fullfile(cdir, 'CMakeLists.txt'), ...
0478 fullfile(target_src_dir, 'osqp', 'CMakeLists.txt'));
0479 copyfile(fullfile(hdir, 'CMakeLists.txt'), ...
0480 fullfile(target_include_dir, 'CMakeLists.txt'));
0481 fprintf('[done]\n');
0482
0483
0484 copyfile(fullfile(files_to_generate_path, 'example.c'), target_src_dir);
0485
0486
0487 fidi = fopen(fullfile(files_to_generate_path, 'CMakeLists.txt'),'r');
0488 fido = fopen(fullfile(target_dir, 'CMakeLists.txt'),'w');
0489 while ~feof(fidi)
0490 l = fgetl(fidi);
0491
0492 newl = strrep(l, 'EMBEDDED_FLAG', num2str(embedded));
0493 fprintf(fido, '%s\n', newl);
0494 end
0495 fclose(fidi);
0496 fclose(fido);
0497
0498
0499 work_hfile = fullfile(target_include_dir, 'workspace.h');
0500 work_cfile = fullfile(target_src_dir, 'osqp', 'workspace.c');
0501 fprintf('Generating workspace.h/.c...\t\t\t\t\t\t');
0502 render_workspace(work, work_hfile, work_cfile, embedded);
0503 fprintf('[done]\n');
0504
0505
0506 if ~isempty(project_type)
0507 fprintf('Creating project...\t\t\t\t\t\t\t\t');
0508 orig_dir = pwd;
0509 cd(target_dir);
0510 mkdir('build')
0511 cd('build');
0512 cmd = sprintf('cmake -G "%s" ..', project_type);
0513 [status, output] = system(cmd);
0514 if(status)
0515 fprintf('\n');
0516 fprintf(output);
0517 error('Error configuring CMake environment');
0518 else
0519 fprintf('[done]\n');
0520 end
0521 cd(orig_dir);
0522 end
0523
0524
0525 mex_cfile = fullfile(files_to_generate_path, 'emosqp_mex.c');
0526 make_emosqp(target_dir, mex_cfile, embedded, float_flag, long_flag);
0527
0528
0529 old_mexfile = ['emosqp_mex.', mexext];
0530 new_mexfile = [p.Results.mexname, '.', mexext];
0531 movefile(old_mexfile, new_mexfile);
0532
0533 end
0534
0535 end
0536 methods(Static)
0537
0538 function out = default_settings()
0539
0540 out = osqp_mex('default_settings', 'static');
0541
0542
0543 out.linsys_solver = linsys_solver_to_string(out.linsys_solver);
0544
0545 end
0546
0547
0548 function out = constant(constant_name)
0549
0550
0551 out = osqp_mex('constant', 'static', constant_name);
0552 end
0553
0554
0555 function out = version()
0556
0557 out = osqp_mex('version', 'static');
0558 end
0559
0560 end
0561 end