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
Asked Answered
R

2

5

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?

Rational answered 17/12, 2013 at 23:2 Comment(0)
C
5

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;
Coroneted answered 17/12, 2013 at 23:19 Comment(5)
+1 using strcmp solves the issue. Personally I don't like "optional arguments", I'd rather use a name-value parameter argument: p.addParameter('imag', false, @(x)validateattributes(x, {'logical'}, {'scalar'})). Then p.Result.imag would either be true or false.Barta
That's a good way to clean up the comparison later in code too. It might as well be a param-value argument with a logical value.Coroneted
@Coroneted Thanks for the help. I really like using the name-value pair instead of an optional argument so I'll probably use that for new code. Unfortunately the code Im working on now is legacy stuff for an API that isnt allowed to change.Rational
@Coroneted Also addParamValue isnt recommended. Your answer uses addParameter, but your link is to addParamValue. I fixed it.Rational
@user30588 Thanks for fixing the link!Coroneted
B
2

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.

Barta answered 17/12, 2013 at 23:21 Comment(5)
Have you seen the R2014a prerelease release notes? Is seems an empty string now matches. It's as if MathWorks reads SO. :)Coroneted
I haven't seen the prerelease, but that is good news. Thanks for letting us know.. Out of curiosity, are there any major changes in the prerelease? HG2 graphics perhaps?Barta
HG2 is not the default, yet. There is a very annoying new pop-up command history thing when you hit the up key at the command line. MEX setup is easier now, but with separate C and C++ configurations (I see mass confusion ahead). The flipud, fliplr, and rot90 commands now work with multi-dimensional arrays, but the syntax has changed. Some new test functions such as issymmetric, isdiag, etc.Coroneted
@chappjc: thanks for the summary. It is nice to see that Mathworks is working on improving the MEX experience (IMO they spend too much time on the IDE and the interface!). Hopefully they also update the MEX API to include some much needed functionality with proper and official documentation: mathworks.com/matlabcentral/answers/79046-mex-api-wish-listBarta
It seems that MathWorks is actively working on the MEX API, as they have moved several of the undocumented functions, including mxCreateSharedDataCopy (see my other comment), into a C++-style namespace organization. Have a look at the exported symbols in the new libmx.lib/.dll when R2014a comes out and compare to R2013b -- big changes. Although I had no trouble importing the new decorated functions.Coroneted

© 2022 - 2024 — McMap. All rights reserved.