Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop 1.0 codegen #56

Merged
merged 3 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading