Programatically creating an anonymous function that separates "Variables" and "Parameters"
4 Ansichten (letzte 30 Tage)
Ältere Kommentare anzeigen
royk
am 6 Sep. 2019
Bearbeitet: per isakson
am 24 Sep. 2019
I have a function MYFUN with several inputs.
Say,
myfun = @(a,b,c,d) a*1000 + b*100 + c*10 + d
In actuality, some of these inputs are fixed “Parameters”, and others are the actual “Variables”, so I would like to have a function “Simplify”, such that
SIMPFUN = Simplify(FUN,PAR1,PAR2,..,PARN)
gets a function handle FUN and parameters PAR1, PAR2,... and returns a simplified function SIMPFUN, such that any non-empty PARs (which are my "Parameters") are substituted into the workspace of SIMPFUN and SIMPFUN only needs to get the empty PARS (which are the "Variables").
For example,
myfun = @(a,b,c,d) a*1000 + b*100 + c*10 + d ;
myfun(1,2,3,4)
ans =
1234
simp_myfun = Simplify(myfun,1,[],3,4) ;
simp_myfun(2)
ans =
1234
simp_myfun = Simplify(myfun,1,[],3,[]) ;
simp_myfun(2,4)
ans =
1234
simp_myfun(8,9)
ans =
1839
I have an implementation that works correctly, but is based on nested functions (cumbersome) and
I wonder if there is a more elegant solution based on anonymous functions.
Here is my current implementation:
function SimplifiedFcn = Simplify(Fcn,varargin)
isEmpty = cellfun(@isempty, varargin) ;
fEmp = find(isEmpty) ;
fnEmp = find(~isEmpty) ;
nonEmptyVars = varargin(fnEmp) ;
[~,k] = sort([fEmp fnEmp]) ;
SimplifiedFcn = @getfun ;
function Val = getfun(varargin)
Vars = [varargin, nonEmptyVars] ;
Val = Fcn(Vars{k}) ;
end
end
0 Kommentare
Akzeptierte Antwort
Guillaume
am 6 Sep. 2019
You won't be able to do this with just anonymous functions, they're too limited in matlab (no multiple statements, no branching).
This is more or less similar to what you have, only simpler and not using nested functions. In addition it allows for functions that return more than one output:
function f = Simplify(fcn, varargin)
inputloc = find(cellfun(@isempty, varargin));
inputpack = varargin;
f = @(varargin) insertinputs(fcn, inputpack, inputloc, varargin);
end
function varargout = insertinputs(fcn, inputpack, inputloc, newinputs)
inputdiff = {'Not enough', '', 'Too many'};
assert(numel(inputloc) == numel(newinputs), '%s input arguments to simplified function', inputdiff{sign(numel(newinputs)-numel(inputloc))+2});
inputpack(inputloc) = newinputs;
[varargout{1:nargout}] = fcn(inputpack{:});
end
3 Kommentare
Weitere Antworten (2)
Guillaume
am 6 Sep. 2019
Bearbeitet: Guillaume
am 6 Sep. 2019
Thinking a bit more about it, metaprogramming in matlab can often only be achieved by using the dreaded eval. This is a rare case where there's no other way.
The following will work for any function regardless of the number of inputs/outputs, will give you the correct value for nargin, and only has the overhead of an anonymous function call when calling the resulting simplified function. The overhead of Simplify is of course not insignificant:
function f = Simplify(fcn, varargin) %#ok<INUSL> fcn is used inside eval
unboundidx = find(cellfun(@isempty, varargin));
boundinput = varargin; %#ok<NASGU> This is just a rename to avoid varargin appearing in the anonymous function (which could confuse the user). boundinput is used inside eval
unboundargs = compose('arg%d', unboundidx);
allargs = compose('boundinput{%d}', 1:numel(varargin));
allargs(unboundidx) = unboundargs;
%Note: you cannot use str2func in the following line as functions created by str2func are not closure (they do not capture the current workspace)
%so fcn and boundinput would not be visible. eval has to be used
f = eval(sprintf('@(%s) fcn(%s)', strjoin(unboundargs, ', '), strjoin(allargs, ', ')));
end
This is a much better solution than my original one, so I suggest you change the accepted answer.
Note that in the above I've created a variable called boundinput just the name varargin does not appear in the created anonymous function as it could confuse the user of the simplified function (the varargin would be the varargin captured by Simplify not a varargin of the anonymous function that is created)
2 Kommentare
Matt J
am 6 Sep. 2019
Bearbeitet: Matt J
am 6 Sep. 2019
I just wanted to mention that a generic simplifier like what you have asked for will not normally give you the best performance. Normally, you have to write a simplifer that is customized to the original function. In the following continuation of your example, we customize based on the knowledge that your original function is linear, and that the simplified function should also be linear. For simplicity, I demonstrate assuming only 1 free parameter.
linearfun = @(a,b,c,d) a*1000 + b*100 + c*10 + d ;
function simp_fun = simplifyLinear(linearfun, varargin)
map=cellfun('isempty',varargin);
assert(nnz(map)==1 , 'In this simple example, only one free parameter is allowed')
argcell=varargin;
argcell{map}=0;
intercept=linearfun(argCell{:});
argcell=varargin;
[argcell{~map}]=deal(0);
argcell{map}=1;
slope=linearfun(argcell{:});
simp_fun=@(X) slope*X+intercept;
end
Notice that in this implementation, the resulting simp_fun will use only 1 addition/multiplication per call, unlike with the generic simplification which will always use 4 additions/multiplications.
2 Kommentare
Guillaume
am 6 Sep. 2019
Yes, the generic simplifier has some overhead which can be significant if the function is called in a loop. It may also prevent some jit optimisation. However, it's a different concept than what Matt discusses. I think royk was looking for a generic way to bind an argument list to a function without any actual knowledge of the function implementation. Pure metaprogramming.
Matt solution relies on the knowledge of the function implementation, if you can do that, great! but it's no longer generic.
However, this talk of metaprogramming made me think of a better method. I'll post a new answer
Siehe auch
Kategorien
Mehr zu Function Creation finden Sie in Help Center und File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!