How to write an anonymous function with a variable number of output arguments?
Asked Answered
G

2

6

Using deal we can write anonymous functions that have multiple output arguments, like for example

minmax = @(x)deal(min(x),max(x));
[u,v] = minmax([1,2,3,4]); % outputs u = 1, v = 4

But if you want to provide a function with its gradient to the optimization function fminunc this does not work. The function fminunc calls the input function sometimes with one and sometimes with two output arguments. (EDIT: This is not true, you just have to specify whether you actually want to use the gradient or not, using e.g. optimset('SpecifyObjectiveGradient',true). Then within one call it always asks for the same number of arguments.)

We have to provide something like

function [f,g] = myFun(x)
 f = x^2; % function
 g = 2*x; % gradient

which can be called with one or two output arguments.

So is there a way to do the same inline without using the function keyword?

Gasconade answered 25/1, 2018 at 12:16 Comment(1)
Side note: A function similar to your minmax was recently (R2017a) added to MATLAB under the name bounds.Glottis
G
5

The OP's solution is good in that it's concise and useful in many cases.

However, it has one main shortcoming, in that it's less scalable than otherwise possible. This claim is made because all functions ({x^2,2*x,2}) are evaluated, regardless of whether they're needed as outputs or not - which results in "wasted" computation time and memory consumption when less than 3 outputs are requested.

In the example of this question this is not an issue because the function and its derivatives are very easy to compute and the input x is a scalar, but under different circumstances, this can be a very real issue.

I'm providing a modified version, which although uglier, avoids the aforementioned problem and is somewhat more general:

funcs_to_apply = {@(x)x.^2, @(x)2*x, @(x)2};
unpacker = @(x)deal(x{:});
myFun = @(x)unpacker(cellfun(@(c)feval(c,x),...
                             funcs_to_apply(1:evalin('caller','nargout')),...
                             'UniformOutput',false)...
                    );

Notes:

  1. The additional functions I use are cellfun, evalin and feval.
  2. The 'UniformOutput' argument was only added so that the output of cellfun is a cell (and can be "unpacked" to a comma-separated list; we could've wrapped it in num2cell instead).
  3. The evalin trick is required since in the myFun scope we don't know how many outputs were requested from unpacker.
  4. While eval in its various forms (here: evalin) is usually discouraged, in this case we know exactly who the caller is and that this is a safe operation.
Glottis answered 25/1, 2018 at 14:5 Comment(0)
G
9

Yes there is, it involves a technique used in this question about recursive anonymous functions. First we define a helper function

helper = @(c,n)deal(c{1:n});

which accepts a cell array c of the possible outputs as well as an integer n that says how many outputs we need. To write our actual function we just need to define the cell array and pass nargout (the number of expected output arguments) to helper:

myFun = @(x)helper({x^2,2*x,2},nargout);

This now works perfectly when calling fminunc:

x = fminunc(myFun,1);
Gasconade answered 25/1, 2018 at 12:16 Comment(0)
G
5

The OP's solution is good in that it's concise and useful in many cases.

However, it has one main shortcoming, in that it's less scalable than otherwise possible. This claim is made because all functions ({x^2,2*x,2}) are evaluated, regardless of whether they're needed as outputs or not - which results in "wasted" computation time and memory consumption when less than 3 outputs are requested.

In the example of this question this is not an issue because the function and its derivatives are very easy to compute and the input x is a scalar, but under different circumstances, this can be a very real issue.

I'm providing a modified version, which although uglier, avoids the aforementioned problem and is somewhat more general:

funcs_to_apply = {@(x)x.^2, @(x)2*x, @(x)2};
unpacker = @(x)deal(x{:});
myFun = @(x)unpacker(cellfun(@(c)feval(c,x),...
                             funcs_to_apply(1:evalin('caller','nargout')),...
                             'UniformOutput',false)...
                    );

Notes:

  1. The additional functions I use are cellfun, evalin and feval.
  2. The 'UniformOutput' argument was only added so that the output of cellfun is a cell (and can be "unpacked" to a comma-separated list; we could've wrapped it in num2cell instead).
  3. The evalin trick is required since in the myFun scope we don't know how many outputs were requested from unpacker.
  4. While eval in its various forms (here: evalin) is usually discouraged, in this case we know exactly who the caller is and that this is a safe operation.
Glottis answered 25/1, 2018 at 14:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.