Skip to content

Commit

Permalink
Added the following to osqp_mex.cpp: codegen, default_codegen_defines…
Browse files Browse the repository at this point in the history
…, 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.
  • Loading branch information
AmitSolomonPrinceton committed Feb 16, 2024
1 parent 391fe8f commit c47c8df
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 186 deletions.
266 changes: 103 additions & 163 deletions @osqp/codegen.m
Original file line number Diff line number Diff line change
@@ -1,73 +1,54 @@
%%
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')
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
% 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
Expand All @@ -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);
Expand All @@ -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
23 changes: 12 additions & 11 deletions @osqp/osqp.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions @osqp/update_codegen_defines.m
Original file line number Diff line number Diff line change
@@ -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

1 change: 1 addition & 0 deletions c_sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit c47c8df

Please sign in to comment.