Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use MATLAB's inputParser with optional string inputs? The documentation says "use a validation function" but it's unclear how to do that

I have a MATLAB file that contains a single top-level function, called sandbox. That function in turn contains two nested functions, mysum and myprod, which are identical in functionality and what parameters they allow except that one uses @sum internally and the other uses @prod internally. My goal is to create a wrapper function to use in both mysum and myprod that takes care of all the validation and input parsing. This function is called applyFunc.

Here's where it gets tricky. mysum and myprod come in two forms:

  1. mysum(v) returns sum(v, 1).
  2. mysum(v, 'imag') returns sum(v, 1) + 1i

Any other combinations of input should throw an error.

I'm having trouble using inputParser to parse these various combinations of input, specifically the optional string input. Here's the code:

function sandbox()
%% Data
v = [1 4; 3 3];

%% Calculations
s = mysum(v);
si = mysum(v, 'imag');
p = myprod(v);
pi = myprod(v, 'imag');

%% Accuracy tests
assert(isequal(s, [4 7]))
assert(isequal(si, [4+1i 7+1i]))
assert(isequal(p, [3 12]))
assert(isequal(pi, [3+1i 12+1i]))

    function x = mysum(varargin)
        x = applyFunc(@sum, varargin{:});
    end

    function x = myprod(varargin)
        x = applyFunc(@prod, varargin{:});
    end
end

function x = applyFunc(func, varargin)

p = inputParser();
p.addRequired('func', @(x) validateattributes(x, {'function_handle'}, {'scalar'}));
p.addRequired('v', @(x) validateattributes(x, {'double'}, {}, 'applyFunc:msg', 'v'));
p.addOptional('imag', '', @(x) validatestring(x, {'imag', ''})); % THIS LINE IS THE PROBLEM
p.parse(func, varargin{:});

f = p.Results.func;
v = p.Results.v;
strflag = p.Results.imag;

x = f(v);
if ~isempty(strflag)
    validatestring(strflag, {'imag'});
    x = x + 1i;
end
end

The line that's causing the problem is this one (as marked in the code above):

p.addOptional('imag', '', @(x) validatestring(x, {'imag', ''}));

The documentation for inputParser says that:

For optional string inputs, specify a validation function. Without a validation function, the input parser interprets valid string inputs as invalid parameter names and throws an error.

Unfortunately I don't have any idea how to do this. Is there something simple Im missing or what? If the 'imag' argument isn't passed at all (as in the assignment of s and p), the code works fine, but if I do pass it, I get this error:

Error using sandbox>applyFunc (line 32)
The value of 'imag' is invalid. It must satisfy the function:
@(x)validatestring(x,{'imag',''}).
Error in sandbox/mysum (line 18)
        x = applyFunc(@sum, varargin{:});
Error in sandbox (line 7)
si = mysum(v, 'imag'); 

Any help?

like image 260
Michael A Avatar asked Dec 17 '13 23:12

Michael A


2 Answers

The problem is that validatestring returns the matching string from the cell argument ({'imag',''}) rather than a Boolean indicating if it passes validation. Instead, use strcmp and any:

@(x) any(strcmp(x,{'imag', ''}))

Also, with validatestring, if the input string did not match either 'imag' or '' (actually just 'imag' since empty strings only match in R2014a+), it would throw an error rather than returning false so that the inputParser could return the appropriate error.

Another nice way to fix the problem is to change the syntax of applyFunc entirely so that instead of just 'imag' as an optional string input argument, use a Parameter-Value with 'imag' as the parameter and a validated boolean as the input.

The input definition suggested by Amro in the comments:

p.addParameter('imag', false, @(x)validateattributes(x, {'logical'}, {'scalar'}))

The usage:

mysum(x,'imag',true)
mysum(x)               % default is equivalent to mysum(x,'imag',false)

This would simplify the rest of the code with p.Result.imag being a logical scalar. I would suggest:

x = f(v) + p.Result.imag*1i;
like image 166
chappjc Avatar answered Sep 22 '22 20:09

chappjc


The problem is not inputParser, I think the issue is with validatestring.

1) First it does not match on empty strings:

>> x = ''
x =
     ''

>> validatestring(x, {'imag',''})
Expected input to match one of these strings:

imag,

The input did not match any of the valid strings.
Caused by:
    Error using validatestring>checkString (line 85)
    Expected input to be a row vector. 

2) Second, if it successfully matches, it returns the resolved string (from one of the valid choice), instead of true/false. inputParser requires that the validation function either return a boolean, or nothing but throws error on failure.

like image 34
Amro Avatar answered Sep 22 '22 20:09

Amro