Even better would be to have the functions figure out the number of arguments so you don't have to pass N...
Functional programming: looking to create functions that map f(p1,p2,p3,...,pN,x) to g([p1 p2 p3 ... pN],x) and the reverse
2 views (last 30 days)
Show older comments
Matthias Schabel
on 30 Dec 2022
Commented: Matthias Schabel
on 4 Jan 2023
I'd like to have two functions, one that takes a function of this form:
f=@(p1,p2,...,pN,x);
and returns a function handle that takes a vector of N arguments and unpacks it:
g=fpnx_to_px(f,N);
such that
g=@(p,x)(f(p(1),p(2),...,p(N),x));
and an "inverse" function:
h=fpx_to_pnx(g,N);
such that
h=@(p1,p2,...,pN,x)(g([p1 p2 ... pN],x));
I have a couple functions that partially work:
% generates wrapper that converts calling convention for f from f(p1,...pn,x)
% to g(px,x), where p is a vector with n parameters
function g = fpnx_to_px(f,n)
pstr = '';
for idx=1:n
pstr = [pstr 'p' num2str(idx) ' '];
end
% eliminate trailing space
pstr = pstr(1:end-1);
evalstr = ['@(' strrep(pstr,' ',',') ',x)(f([' pstr '],x))'];
g = evalin('caller',evalstr);
end
% generates wrapper that converts calling convention for f from f(p,x),
% where p is a vector with n parameters, to g(p1,...,pn,x)
function g = fpx_to_pnx(f,n)
pstr = '';
for idx=1:n
pstr = [pstr 'p(' num2str(idx) '),'];
end
% eliminate trailing comma
pstr = pstr(1:end-1);
evalstr = ['@(p,x)(f(' pstr ',x))'];
g = evalin('caller',evalstr);
end
so if I do the following
f=@(p1,p2,p3,x)(p1+p2+p3*x);
g=@(p,x)(p(1)+p(2)+p(3)*x);
f(1,2,3,1:4) -> [6 9 12 15]
g([1 2 3],1:4) -> [6 9 12 15]
gg=fpx_to_pnx(f,3);
gg([1 2 3],1:4) -> [6 9 12 15]
ff=fpnx_to_px(g,3);
ff(1,2,3,1:4)
gives an error:
Matrix dimensions must agree.
Error in @(p1,p2,p3,x)(p1+p2+p3*x)
Error in fpnx_to_px>@(p1,p2,p3,x)(f([p1,p2,p3],x))
which seems to be coming from the definition of f within fpnx_to_px coming from the definition in the workspace. I think what I need is a way to get evalin to expand the argument f within the function in to the actual function handle but am clearly missing something. Perhaps there's a better way to do this that doesn't involve evalin at all?
2 Comments
Stephen23
on 2 Jan 2023
Edited: Stephen23
on 2 Jan 2023
Note that the examples are inconsistent with the explanation.
For example the function fpnx_to_px is described as "from f(p1,...pn,x) to g(px,x)". In the examples the function f does indeed have multiple p-values, however it is modified with fpx_to_pnx:
f=@(p1,p2,p3,x)(p1+p2+p3*x);
..
gg=fpx_to_pnx(f,3);
gg([1 2 3],1:4)
Accepted Answer
Stephen23
on 2 Jan 2023
Edited: Stephen23
on 2 Jan 2023
Here are two wrapper functions:
fpx_to_fpnx = @(fnh) @(varargin) fnh([varargin{1:end-1}],varargin{end});
fpnx_to_fpx = @(fnh) @(p,x) fnh(cell2struct(num2cell(p),'p').p,x);
Tested using your examples (modified by removing the input N):
f=@(p1,p2,p3,x)(p1+p2+p3*x);
g=@(p,x)(p(1)+p(2)+p(3)*x);
f(1,2,3,1:4)
g([1,2,3],1:4)
gg = fpnx_to_fpx(f);
gg([1,2,3],1:4)
ff = fpx_to_fpnx(g);
ff(1,2,3,1:4)
I recommend placing x as the first input argument, which gives simpler, cleaner, and clearer code:
fxp_to_fxpn = @(fnh) @(x,varargin) fnh(x,[varargin{:}]);
fxpn_to_fxp = @(fnh) @(x,p) fnh(x,cell2struct(num2cell(p),'p').p);
However code like this is something to play with and should be avoided in anything serious: it would get in the way of the JIT engine doing its work, and severely limits the sizes and types of data that you can call those functions with. Prefer working with arrays as much as possible.
2 Comments
Paul
on 2 Jan 2023
Di you use this:
cell2struct(num2cell(p),'p').p
because that's the only way to convert a row vector, p, into a comma-separated list formed from the elements of p that can be used in a function call?
More Answers (2)
George Abrahams
on 30 Dec 2022
g = @(f,x,p) f( x, p{:} );
h = @(f,x,varargin) f( x, cell2mat(varargin) );
g( @cat, 2, ["super" "cali" "fragil" "istic"] )
% ans = 'supercalifragilistic'
h( @max, 5, 2, 4, 6, 8 )
% ans = [5, 5, 6, 8]
You're example is a little confused but this should point you in the right direction.
2 Comments
George Abrahams
on 31 Dec 2022
Edited: George Abrahams
on 31 Dec 2022
Hi @Matthias Schabel, I strongly encourage you to define a hard-coded wrapper function for each function, like this:
oneargcat = @(dim,An) cat( dim, An{:} );
oneargcat( 2, ["super" "cali" "fragil" "istic"] )
nargmax = @(varargin) max(cell2mat(varargin));
nargmax( 3, 1, 4, 1, 6 )
It's clearer and allows flexibility of the arguments. To make that clear, consider the following, with args2vector and vector2args inspired by @Stephen23's answer below.
vector2args = @(f) @(vec) f(cell2struct(num2cell(vec),'a').a);
oneargcat = vector2args(@cat);
oneargcat( [2, 1 2 3 4 5] )
The above is effectively cat( 2, 1:5 ) and works great. However, as all input arguments must be concatenated into a vector, all functions wrapped by vector2args must be of the form f( a1, a2, a3, ..., an ), where a1,a2,a3,...,an are all scalar and the same class. Hence, neither of these are possible:
- cat( 2, "super", "cali", "fragil", "istic" )
- cat( 1, zeros(2), ones(2) )
args2vector = @(f) @(varargin) f(varargin{1},[varargin{1:end}]);
nargmax = args2vector(@max);
nargmax( 3, 1, 2, 3, 4, 5 )
The above is effectively max( 3, 1:5 ) and, again, works great. However, it is hardcoded that for all functions wrapped by args2vector must be of the form f( a, [b1,b2,...,bn] ). Hence, none of the following are possible:
- max( 1:5, 3 )
- max( [1 2; 3 4] )
- max( rand(3), [], 1 )
However, even hardcoding each function as I've suggested has its limits. One is that you can't pass matrices, without also passing/hardcoding the shape, as varargin is always a 1-by-N vector. Another is that you can only have one varargin argument. That is, you would not be able to convert N arguments to 2 vector arguments without passing/hardcoding the length of those vectors, e.g. max( 1:5, 5:-1:1 ).
John D'Errico
on 30 Dec 2022
Edited: John D'Errico
on 30 Dec 2022
I think you are only part way along in your quest, but that your quest will never be happily fulfilled.
You are asking to have a tool that will automatically generate the inverse of a potentially arbitrarily nonlinear function of multiple variables, and to do so as a function.
Remember that the inverse of a nonlinear function is generally not a simple single valued function. For example, what inverse function would you return for the trivial function
y = @(x) x.^2;
fplot(y,[-1,1])
xlabel X
ylabel Y
grid on
where x varies on the interval [-1,1]?
The inverse is not uniquely defined. This is in fact quite the expected result, where for even trivially simple functions, there is no inverse.
At best, you will now be faced with using a nonlinear rootfinder for any set of inputs for the inverse, and will be HOPING it always converges. Even if it does converge, the solution it converges to may not be in the domain of interest. Even for two points near each other in the range space of your original function, the solver may not converge to solutions that are close to each other.
Hey, good luck. But I think you are investing a lot of time in thinking how to formulate the problem in MATLAB, before you even think about whether this is a well-defined problem to solve at all.
3 Comments
John D'Errico
on 2 Jan 2023
So all you want is a vector splitter, and a combiner, each of which can act as a wrapper of sorts?
Assume we have a function that takes split arguments. Rosenbrock, here:
Mysplitfun = @(x1,x2) (1 - x1).^2 + 100*(x2 - x1.^2).^2;
Now we want to optimize it. I'll use fminsearch.
x0 = [3 3];
[X,Fval] = fminsearch(@(V) splitter(V,Mysplitfun),x0)
function obj = splitter(V,fun)
% splits a vector automatically into multiple arguments, then used by fun
Vsplit = mat2cell(V,1,ones(size(V)));
obj = fun(Vsplit{:});
end
I'm not at all sure what the use case that you see for the reverse operation is. But a combiner code might just use cell2mat.
function V = combiner(varargin)
% combines a list of scalar arguments into a single vector
V = cell2mat(varargin);
end
See Also
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!