Skip to content

Commit

Permalink
Adds wrapper for python and dylib custom function (#157)
Browse files Browse the repository at this point in the history
* Adds wrapper for python and dylib custom function

* Adds unit tests and modifies customFileClass to use wrapper instead

* Addresses review comments
  • Loading branch information
StephenNneji authored Sep 21, 2023
1 parent ed1416e commit d171596
Show file tree
Hide file tree
Showing 45 changed files with 575 additions and 503 deletions.
14 changes: 2 additions & 12 deletions API/parseClassToStructs.m
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@
%% Start by removing the cell arrays
contrastLayers = inputStruct.contrastLayers;
layersDetails = inputStruct.layersDetails;
customFiles = inputStruct.files;

% If any of the contrastLayers are empty, replace the empty cells by zero
% thickness layers
Expand All @@ -108,15 +107,6 @@
layersDetails = {0};
end

% When there are custom files, we need to strip the file extension
% from the filename if it's present
for i = 1:length(customFiles)
thisCustomFileCell = customFiles{i};
[~,name,~] = fileparts(thisCustomFileCell{1});
thisCustomFileCell{1} = name;
customFiles{i} = thisCustomFileCell;
end

% Pull out all the cell arrays (except priors) into one array
problemDefCells{1} = inputStruct.contrastRepeatSLDs;
problemDefCells{2} = inputStruct.allData;
Expand All @@ -131,7 +121,7 @@
problemDefCells{11} = inputStruct.nbairNames;
problemDefCells{12} = inputStruct.nbsubNames;
problemDefCells{13} = inputStruct.resolParNames;
problemDefCells{14} = customFiles';
problemDefCells{14} = inputStruct.files;
problemDefCells{15} = cellstr(inputStruct.backgroundTypes');
problemDefCells{16} = cellstr(inputStruct.resolutionTypes');
problemDefCells{17} = inputStruct.allOilChiData;
Expand Down Expand Up @@ -181,7 +171,7 @@

% Also the custom files array..
if isempty(problemDefCells{14})
problemDefCells{14} = {{'','',''}};
problemDefCells{14} = {''};
end


Expand Down
59 changes: 41 additions & 18 deletions API/projectClass/customFileClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

% A container class for holding custom files for either
% models, backgrounds or resolutions.
properties (SetAccess = private, Hidden = true)
wrappers = {}
end

properties(Access = private, Constant, Hidden)
invalidLanguageMessage = sprintf('Language must be a supportedLanguages enum or one of the following strings (%s)', ...
strjoin(supportedLanguages.values(), ', '))
strjoin(supportedLanguages.values(), ', '))
end

methods
Expand All @@ -27,25 +30,34 @@
end
end

function delete(obj)
% Destroys the wrappers
for i=1:length(obj.wrappers)
delete(obj.wrappers{i});
end
obj.wrappers = {};
end

function obj = addCustomFile(obj, varargin)
% Add an entry to the file table.
% A custom file entry can be added with no parameters, just the
% name of the custom file entry, the name of the entry
% alongside a filename, or can be fully defined by specifying
% the name of the custom file entry, filename, language, and
% file path
% file path. For MATLAB, the provided path must be in the
% matlab path
%
% customFiles.addCustomFile()
% customFiles.addCustomFile('New Row')
% customFiles.addCustomFile('New Row', 'file.m')
% customFiles.addCustomFile('New Row', 'file.m', 'matlab', 'pwd')
% customFiles.addCustomFile('New Row', 'file.py', 'python', 'C:/stuff')
if isempty(varargin)

% Nothing supplied - add empty data row
nameVal = obj.autoNameCounter();
newName = sprintf('New custom file %d', nameVal);

newRow = {newName, '', supportedLanguages.Matlab.value, 'pwd'};
newRow = {newName, '', supportedLanguages.Matlab.value, ''};

else

Expand All @@ -62,7 +74,7 @@
throw(invalidType('Single input is expected to be a custom object name'));
end

newRow = {newName, '', supportedLanguages.Matlab.value, 'pwd'};
newRow = {newName, '', supportedLanguages.Matlab.value, ''};

case 2

Expand All @@ -71,7 +83,7 @@
newName = char(inputs{1});
newFile = char(inputs{2});

newRow = {newName, newFile, supportedLanguages.Matlab.value, 'pwd'};
newRow = {newName, newFile, supportedLanguages.Matlab.value, ''};

case 4

Expand All @@ -93,8 +105,7 @@

% Check language is valid, then add the new entry
newRow{3} = validateOption(newRow{3}, 'supportedLanguages', obj.invalidLanguageMessage).value;
obj.addRow(newRow{:});

obj.addRow(newRow{:});
end

function obj = setCustomFile(obj, row, varargin)
Expand Down Expand Up @@ -218,31 +229,43 @@ function displayTable(obj)
% Convert the custom files class to a struct
%
% customFiles.toStruct()
numberOfFiles = obj.rowCount;

fileStruct.files = {};
numberOfFiles = obj.rowCount;
if numberOfFiles > 0
filesList = cell(numberOfFiles, 1);
for i = 1:numberOfFiles
thisRow = obj.varTable{i,:};
thisFile = thisRow{2};
thisType = thisRow{3};
thisPath = thisRow{4};

if strcmpi(thisPath,'pwd')
thisPath = pwd;
thisPath = thisRow{4};
[~, functionName, ~] = fileparts(thisFile);
libpath = fullfile(thisPath, thisFile);
wrapper = [];
if i <= length(obj.wrappers)
wrapper = obj.wrappers{i};
end
filesList{i} = {thisFile, thisType, thisPath};
if isempty(wrapper) || ~strcmp(wrapper.libPath, libpath) || ~strcmp(wrapper.functionName, functionName)
if strcmpi(thisType, supportedLanguages.Python.value)
wrapper = pythonWrapper(libpath, functionName);
thisFile = wrapper.getHandle();
elseif strcmpi(thisType, supportedLanguages.Cpp.value)
wrapper = dyLibWrapper(libpath, functionName);
thisFile = wrapper.getHandle();
end
obj.wrappers{i} = wrapper;
else
thisFile = wrapper.getHandle();
end
[~, handle, ~] = fileparts(thisFile);
filesList{i} = handle;
end
fileStruct.files = filesList;
else
fileStruct.files = {};
end
end

end

methods(Access = protected)

function obj = setCustomLanguage(obj, row, language)
% Check whether a specified language is supported, and set the
% file entry if so.
Expand Down
46 changes: 46 additions & 0 deletions API/projectClass/dyLibWrapper.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
classdef dyLibWrapper < handle
% A custom function wrapper for c++ dynamic library. The library should be a .dll
% (Windows), .so (Linux), or .dylib (MacOS).
properties (SetAccess = private)
functionName
libPath
end

properties (Access = private, Hidden = true)
objectHandle % Handle to the underlying C++ class instance
mexHandle % Handle to the mex function
end

methods
function obj = dyLibWrapper(libPath, functionName)
% Creates a dyLibWrapper object. It creates a callback interface for the
% library via wrapperMex.The arguments should be full or relative path of
% the library with extension and the name of the function to call in the dynamic library
%
% wrapper = dyLibWrapper('customBilayer.so', 'customBilayer');
% wrapper = dyLibWrapper('D:/MATLAB/customBilayer.dll', 'customDomains');
obj.mexHandle = @wrapperMex;
obj.objectHandle = obj.mexHandle('new', libPath, functionName);
obj.libPath = libPath;
obj.functionName = functionName;
end

function delete(obj)
% Destroys the callback class instance in wrapperMex
if ~isempty(obj.objectHandle)
obj.mexHandle('delete', obj.objectHandle);
end
obj.objectHandle = [];
end

function handle = getHandle(obj)
% Gets the wrapper object handle
handle = obj.objectHandle;
end

function disp(obj)
% Displays libarary path and function name
fprintf('Dynamic library wrapper for %s function in %s\n', obj.functionName, obj.libPath);
end
end
end
5 changes: 5 additions & 0 deletions API/projectClass/projectClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@
obj.contrasts = contrastsClass();
end

function delete(obj)
% Destroys the wrappers
delete(obj.customFile);
end

function domainsObj = toDomainsClass(obj)
% Alias of the converter routine from projectClass to
% domainsClass.
Expand Down
59 changes: 59 additions & 0 deletions API/projectClass/pythonWrapper.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
classdef pythonWrapper < handle
% A custom function wrapper for a python file.

properties (SetAccess = private)
functionName
libPath
end

properties (SetAccess = private, Hidden = true)
tempFolder
tempName
filename
end

methods
function obj = pythonWrapper(libPath, functionName)
% Creates a pythonWrapper object. It creates a temporary function which runs
% the python code using the python executable connected to MATLAB pyenv. The
% arguments should be full or relative path of the python file with extension and
% the name of the function to call in the python file
%
% wrapper = pythonWrapper('customBilayer.py', 'customBilayer');
% wrapper = pythonWrapper('D:/MATLAB/customBilayer.py', 'customDomains');
obj.tempFolder = tempname();
obj.libPath = libPath;
obj.functionName = functionName;

fnstr = ['function [output,sub_rough] = %s(varargin)\n' ...
' [output, sub_rough] = pyRunner("%s", "%s", varargin);\n' ...
'end\n'];

mkdir(obj.tempFolder);
filename = sprintf('%s.m', tempname(obj.tempFolder));
fid = fopen(filename, 'w+');
[~, obj.tempName, ~] = fileparts(filename);
fprintf(fid, fnstr, obj.tempName, obj.libPath, obj.functionName);
fclose(fid);
addpath(obj.tempFolder);
end

function delete(obj)
% Removes the temporary runner file
rmpath(obj.tempFolder);
if exist(obj.tempFolder, 'dir')
rmdir(obj.tempFolder, 's');
end
end

function handle = getHandle(obj)
% Gets the name of the temporary runner file
handle = obj.tempName;
end

function disp(obj)
% Displays library path and function name
fprintf('Python wrapper for %s function in %s\n', obj.functionName, obj.libPath);
end
end
end
5 changes: 2 additions & 3 deletions addPaths.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

fullfile(root,'compile','DE');
eventCompilePath;
fullfile(root,'compile','customWrapper');
fullfile(root,'compile','reflectivityCalculation');
fullfile(root,'compile','simplex');
fullfile(root,'compile');
Expand All @@ -46,7 +47,6 @@
fullfile(root,'targetFunctions','common','callCustomFunction');
fullfile(root,'targetFunctions','common','callCustomFunction','callMatlabFunction');
fullfile(root,'targetFunctions','common','callCustomFunction','callCppFunction');
fullfile(root,'targetFunctions','common','callCustomFunction','callPythonFunction');

fullfile(root,'targetFunctions','common','callReflectivity');
fullfile(root,'targetFunctions','common','costFunctions','chiSquared');
Expand Down Expand Up @@ -88,8 +88,7 @@

addpath(root);
setappdata(0, 'root', root);
includedir = {fullfile(root, 'targetFunctions', 'common', 'customModelsIncludes'),...
fullfile(root, 'compile', 'events')};
includedir = {fullfile(root, 'compile', 'customWrapper'), eventCompilePath};
setappdata(0, 'includeDirs', includedir);

% Add the folder with the eventManager dynamic library to the system path so
Expand Down
10 changes: 6 additions & 4 deletions buildScript.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@
% need to change RAT paths to exclude these files from the source generation
% setCompilePaths('lib');
%
% % Now generate the source....
% reflectivityCalculationCodeOnlyScript;
% Now generate the source....
reflectivityCalculationCodeOnlyScript;

% Finally, change the paths back or the tests might fail...
% setCompilePaths('mex');

compilePath = fullfile(thisPath, 'compile', 'events');
cd(compilePath);

eventCompileScript;

compilePath = fullfile(thisPath, 'compile', 'customWrapper');
cd(compilePath);
wrapperCompileScript;

% Return to RAT root directory
cd(thisPath);
3 changes: 1 addition & 2 deletions compile/Dram_compile/makeCompileArgs.m
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@
ARG = coder.typeof('X',[1 maxArraySize],[0 1]);
ARGS_1_2{13} = coder.typeof({ARG}, [1 maxArraySize],[0 1]);
ARG = coder.typeof('X',[1 maxArraySize],[0 1]);
ARG = coder.typeof({ARG}, [1 3]);
ARGS_1_2{14} = coder.typeof({ARG}, [1 maxArraySize],[0 1]);
ARGS_1_2{14} = coder.typeof({ARG}, [1 maxArraySize], [0 1]);
ARG = coder.typeof('X',[1 maxArraySize],[1 1]);
ARGS_1_2{15} = coder.typeof({ARG}, [1 maxArraySize],[0 1]);
ARG = coder.typeof('X',[1 maxArraySize],[1 1]);
Expand Down
3 changes: 1 addition & 2 deletions compile/RAT_main_compile/makeCompileArgs.m
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@
ARG = coder.typeof('X',[1 maxArraySize],[0 1]);
ARGS_1_2{13} = coder.typeof({ARG}, [1 maxArraySize],[0 1]);
ARG = coder.typeof('X',[1 maxArraySize],[0 1]);
ARG = coder.typeof({ARG}, [1 3]);
ARGS_1_2{14} = coder.typeof({ARG}, [1 maxArraySize],[0 1]);
ARGS_1_2{14} = coder.typeof({ARG}, [1 maxArraySize], [0 1]);
ARG = coder.typeof('X',[1 maxArraySize],[1 1]);
ARGS_1_2{15} = coder.typeof({ARG}, [1 maxArraySize],[0 1]);
ARG = coder.typeof('X',[1 maxArraySize],[1 1]);
Expand Down
Loading

0 comments on commit d171596

Please sign in to comment.