5
$\begingroup$

I have two functions with options, for example f and g

ClearAll[f,g,h]

h::ioppf = "Value of option MaxIterations should be a positive integer.";
g::method = "The Method option should be \"one\" or \"two\".";
h::fun = "The fun option should be \"f\" or \"g\".";

f[x_, opts : OptionsPattern[MaxIterations -> 5]] :=
  ConstantArray[x, OptionValue[MaxIterations]] /;
   If[OptionValue[MaxIterations] > 0, True, Message[h::ioppf]; False]

g[x_, opts : OptionsPattern[{Method -> "one", MaxIterations -> 5}]] :=
  ConstantArray[
    OptionValue[Method] /.{"one" -> Sqrt[x], "two" -> x^2},   
    OptionValue[MaxIterations]
  ] /;
  If[MemberQ[{"one","two"},OptionValue[Method]], True, Message[g::method]; False] &&
  If[OptionValue[MaxIterations] > 0, True, Message[h::ioppf]; False]

In function h we can select which function (f or g) we want to use (option fun)

h[x_, opts : OptionsPattern[{fun -> "f", Method->"one", MaxIterations->3}]]:=
Module[{func, arg},
  FilterRules[{opts},Options[h]];
  func = ToExpression@OptionValue[fun];
  arg = OptionValue[fun]/.
        {"f" -> Join[{x}, {MaxIterations -> OptionValue[MaxIterations]}],
         "g" -> Join[{x}, {Method -> OptionValue[Method], 
                           MaxIterations -> OptionValue[MaxIterations]}]};
  func@@arg
]/;
 If[MemberQ[{"f","g"}, OptionValue[fun]], True, Message[h::fun]; False] &&
 If[OptionValue[MaxIterations] > 0, True, Message[h::ioppf]; False] &&
 If[MemberQ[{"one","two"}, OptionValue[Method]], True, Message[g::method]; False]

Examples:

h[x] (* {x,x,x} *)
h[x,fun->"f"] (*  {x,x,x} *)
h[x,fun->"g"] (* {Sqrt[x],Sqrt[x],Sqrt[x]} *)
h[x,fun->"g",Method->"two"] (* {x^2,x^2,x^2} *)
h[x,fun->"g",MaxIterations->5] (* {Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x]} *)
h[x,fun->"g",Method->"two",MaxIterations->5] (* {x^2,x^2,x^2,x^2,x^2} *)

with messages:

h[x,fun->"u"]
h[x,fun->"f",MaxIterations->0]
h[x,fun->"g",Method->"2"]

That's what I need. But I want to make the input form with particular options:

h[x, fun->"f"] (* { x,x,x} *)
h[x, fun->{"f", MaxIterations->10}] (* {x,x,x,x,x,x,x,x,x,x} *)
h[x, fun->{"g", Method->"one", MaxIterations->10}] 
(*    
 {Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x]} *)

How to call these functions with particular options? And can you suggest me more elegant way to write h function? Maybe I can use Check instead of If?

$\endgroup$
1

1 Answer 1

10
$\begingroup$

You can use my OptionsValidation framework to add options validation to your functions.

We start by loading the package:

Import["https://raw.githubusercontent.com/jkuczm/MathematicaOptionsValidation/master/NoInstall.m"]

Now definitions of your f, g and h functions. Option validation will be performed by special ValidOptionsPattern provided by the framework.

ClearAll[f, g, h]

Options[f] = {MaxIterations -> 5};
f[x_, ValidOptionsPattern[f]] :=
    ConstantArray[x, OptionValue[MaxIterations]]

Options[g] = {Method -> "one", MaxIterations -> 5};
g[x_, ValidOptionsPattern[g]] :=
    ConstantArray[
        OptionValue[Method] /. {"one" -> Sqrt[x], "two" -> x^2},
        OptionValue[MaxIterations]
    ]

Options[h] = {fun -> f, Method -> "one", MaxIterations -> 3};
h[x_, opts : ValidOptionsPattern[h]] :=
    Module[{func = OptionValue[fun], funcOpts = {}},
        If[ListQ[func],
            funcOpts = Rest[func];
            func = First[func]
        ];
        func[x, FilterRules[{funcOpts, opts, Options[h]}, Options[func]]]
    ]

And now definitions of option value tests. We "register" them by defining CheckOption for functions and options matching given patterns.

f::iopp = g::iopp = h::iopp = 
    "Value of option `1` -> `2` should be a positive integer.";
CheckOption[func : f | g | h, MaxIterations][val : Except[_Integer?Positive]] := 
    Message[func::iopp, MaxIterations, HoldForm@val]

g::method = h::method = "The Method option should be \"one\" or \"two\".";
CheckOption[func : g | h, Method][val : Except["one" | "two"]] := 
    Message[func::method, Method, HoldForm@val]

h::funOpts = "Invalid `1` options given in fun -> `2` option.";
h::fun = "The fun option should be f, g, {f, fOptions}, or {g, gOptions}. \
`1` given instead.";
CheckOption[h, fun][
    val : {func : f | g, opts : OptionsPattern[]} /; 
        Not@ValidOptionsQ[func][opts]
] := 
    Message[h::funOpts, func, HoldForm@val];
CheckOption[h, fun][
    val : Except[f | g | {f, OptionsPattern[]} | {g, OptionsPattern[]}]
] := 
    Message[h::fun, HoldForm@val]

SetDefaultOptionsValidation[{f, g, h}]

Now some tests. h called with correct option values:

h[x] (*{x,x,x}*)
h[x, fun -> f] (*{x,x,x}*)
h[x, fun -> g] (*{Sqrt[x],Sqrt[x],Sqrt[x]}*)
h[x, fun -> g, Method -> "two"] (*{x^2,x^2,x^2}*)
h[x, fun -> g, MaxIterations -> 5] (*{Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x],Sqrt[x]}*)
h[x, fun -> g, Method -> "two", MaxIterations -> 5] (*{x^2,x^2,x^2,x^2,x^2}*)

With invalid option values:

h[x, fun -> u]
(* h::fun: The fun option should be f, g, {f, fOptions}, or {g, gOptions}. u given instead. *)
(* h[x, fun -> u] *)

h[x, fun -> f, MaxIterations -> 0]
(* h::iopp: Value of option MaxIterations -> 0 should be a positive integer. *)
(* h[x, fun -> f, MaxIterations -> 0] *)

h[x, fun -> g, "MaxIterations" -> -5, Method -> "2"]
(* h::iopp: Value of option MaxIterations -> -5 should be a positive integer. *)
(* h::method: The Method option should be "one" or "two". *)
(* h[x, fun -> g, "MaxIterations" -> -5, Method -> "2"] *)

And with valid and invalid special version of fun option:

h[x, fun -> {f, MaxIterations -> 10}]
(* {x, x, x, x, x, x, x, x, x, x} *)

h[x, fun -> {g, "Method" -> "one", MaxIterations -> 10}]
(* {Sqrt[x], Sqrt[x], Sqrt[x], Sqrt[x], Sqrt[x], Sqrt[x], Sqrt[x], Sqrt[x], Sqrt[x], Sqrt[x]} *)

h[x, fun -> {g, Method -> "three"}]
(* g::method: The Method option should be "one" or "two". *)
(* h::funOpts: Invalid g options given in fun -> {g,Method->three} option. *)
(* h[x, fun -> {g, Method -> "three"}] *)

Setting default options is also protected from wrong values:

SetOptions[h, fun -> 5]
(* h::fun: The fun option should be f, g, {f, fOptions}, or {g, gOptions}. 5 given instead. *)
(* $Failed *)
$\endgroup$
7
  • $\begingroup$ Interesting... Thank you for such a detailed answer! $\endgroup$
    – luck_liv
    Feb 9, 2016 at 12:24
  • 1
    $\begingroup$ I wasn't aware of this code, +1. Quite a lot of work done here. I see that you transform the code at definition-time, which may be a good idea indeed. Will have to study this more carefully, pretty interesting. $\endgroup$ Jun 4, 2016 at 12:40
  • $\begingroup$ Actually, I misunderstood the first time. You attach the checks to the OptionsPattern, replacing it by a custom one. Makes total sense. The only problem I can see here is ValidOptionsQ executes in all cases, when testing the pattern, even for non-options - unless I misunderstood (which could be since I didn't spend enough time with you code). $\endgroup$ Jun 4, 2016 at 12:50
  • 1
    $\begingroup$ @jkuczm Yes, you are right, and I was wrong. Your framework is more general than my recent answer from today. OTOH, since I test at run-time on the r.h.s., I will only test those options that are actually used in the code (some options may never be used, if they are in some conditional branches), and then stop right after the first such option is encountered during code execution. Anyway, this is an interesting approach that I didn't think about. It is always good to explore all possibilities. $\endgroup$ Jun 4, 2016 at 13:12
  • 1
    $\begingroup$ Hi, @jkuczm, the code: InvalidOptionsPattern[ fs : _Symbol | {__Symbol}] := (opts : OptionsPattern[fs] /; ! ValidOptionsQ[fs, AllOptions -> True][opts]) /. opts -> Unique[opts], the color of last part opts -> Unique[opts] is blue, is it suitable? $\endgroup$
    – xyz
    Jun 4, 2016 at 14:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.