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 ビュー (過去 30 日間)
古いコメントを表示
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 件のコメント
Stephen23
2023 年 1 月 2 日
編集済み: Stephen23
2023 年 1 月 2 日
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)
採用された回答
Stephen23
2023 年 1 月 2 日
編集済み: Stephen23
2023 年 1 月 2 日
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 件のコメント
Paul
2023 年 1 月 2 日
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?
その他の回答 (2 件)
George Abrahams
2022 年 12 月 30 日
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 件のコメント
George Abrahams
2022 年 12 月 31 日
編集済み: George Abrahams
2022 年 12 月 31 日
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
2022 年 12 月 30 日
編集済み: John D'Errico
2022 年 12 月 30 日
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 件のコメント
John D'Errico
2023 年 1 月 2 日
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
参考
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!