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 件のコメント
Matthias Schabel
Matthias Schabel 2022 年 12 月 30 日
Even better would be to have the functions figure out the number of arguments so you don't have to pass N...
Stephen23
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
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)
ans = 1×4
6 9 12 15
g([1,2,3],1:4)
ans = 1×4
6 9 12 15
gg = fpnx_to_fpx(f);
gg([1,2,3],1:4)
ans = 1×4
6 9 12 15
ff = fpx_to_fpnx(g);
ff(1,2,3,1:4)
ans = 1×4
6 9 12 15
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
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
George Abrahams 2022 年 12 月 30 日
You're so close! You just need to understand Comma-Separated Lists and varargin.
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 件のコメント
Matthias Schabel
Matthias Schabel 2022 年 12 月 31 日
Hi George,
Thanks for your answer. I think this is really close to what I'm looking for, and works great, but I want to get a function handle that wraps the call to the function argument completely so that I don't have to pass the handle to the original function around everywhere. That is, I want something like:
newfun = @(fun)(@(p,x)fun(p{:},x));
so that, if I define
g = @(p,x)(p(1)+x.*(p(2)+x.*p(3)));
and
gg = newfun(g);
then
gg(1,2,3,1:4)
is equivalent to
g([1 2 3],1:4)
George Abrahams
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"] )
ans = 'supercalifragilistic'
nargmax = @(varargin) max(cell2mat(varargin));
nargmax( 3, 1, 4, 1, 6 )
ans = 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] )
ans = 1×5
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 )
ans = 1×6
3 3 3 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
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
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)
X = 1×2
1.0000 1.0000
Fval = 1.0423e-09
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
Matthias Schabel
Matthias Schabel 2023 年 1 月 4 日
Yes, this is exactly what I was looking for, and more or less the same use case...thanks!

サインインしてコメントする。

カテゴリ

Help Center および File ExchangeProgramming についてさらに検索

製品


リリース

R2022a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by