0
$\begingroup$

I am trying to adopt very interesting universal option check method. And I am completely lost since in one case in seems working, while in the other don't. I am looking for explanation why it fails an possible ideas for improving the code below.

These are two functions. The first checks option values. If option has suboption, it is given in the form Method->{"Method1", SubOption1->"value1",..}

ClearAll[gaOptionCheck];
gaOptionCheck::invldopt = 
  "Option `1` for function `2` received invalid value `3`. Valid pattern is `1` -> `4`.";

gaOptionCheck[testFunction_] := 
  Function[code, 
   Module[{tag, msg, catch, suggest}, 
    msg = Function[{v, t}, 
      Message[gaOptionCheck::invldopt, Sequence @@ t]; v];
    catch = Function[c, Catch[c, _tag, msg], HoldAll];
    (* add property for test function which has suboptions *)    
    testFunction[
      optName_, {valch_String, suboptions__}] := (If[
       testFunction[optName, valch], Print["Entering in suboptions"];
       TrueQ[
        AllTrue[MapAt[{valch, #} &, 
          List @@@ {suboptions}, {All, 
           1}], (If[testFunction @@ #, True, 
            Sow[suggest[optName] = testFunction@(#[[1]])]; 
            False]) &]], 
       Sow[suggest[optName] = testFunction[optName]]; False]);
    
    (*Taken from:https://mathematica.stackexchange.com/questions/
    116623/how-to-check-the-validity-of-an-option-
    value (Leonid Shifrin).*)
    
    catch@
     ReplaceAll[Unevaluated@code, 
      o : HoldPattern[OptionValue[f_, _, name_]] :> 
       With[{val = o}, 
        If[! testFunction[name, val], 
         If[! ValueQ[suggest[name]], 
          Sow[suggest[name] = testFunction[name]]]; 
         Throw[$Failed, 
          tag[name, f, val, 
           Sequence @@ Flatten[Reap[suggest[name]]]]],(*else*)val]]]],
    HoldAll];

The second one sets the provided option value to the container, for example theOption["optionName"]=value1.

Options[gaSetNamedOptionValue] = {Quiet -> True};
testOptionForsetNamedOptionValue[Quiet] = "True|False";
testOptionForsetNamedOptionValue[Quiet, val_] := 
  MatchQ[val, True | False];

gaSetNamedOptionValue[{commandName_, optionName_, containerName_}, 
   optionValue_, OptionsPattern[]] :=
  
  If[MatchQ[optionValue, $Failed],(* if option value is not valid, 
   do nothing*)Null,
   (* case of complex option: if some of suboptions were set, 
   set them together with rest of suboptions *)
   
   Module[{determinableSubOptions, complexSubOptionList =
       DeleteDuplicatesBy[
        List @@@ 
         Flatten[{Cases[optionValue, 
            HoldPattern[_Rule | _RuleDelayed]], 
           Cases[optionName /. Options[commandName], 
            HoldPattern[_Rule | _RuleDelayed]]}, 1], First]},
     Print[{"detecting option Value of", optionValue}];
     If[complexSubOptionList =!= {}, 
      determinableSubOptions = 
       MapAt[ToString, complexSubOptionList, {All, 1}]];
     Switch[optionValue,
      HoldPattern[{_String, ___?OptionQ}],
      Set @@ {containerName[ToString[optionName]], optionValue[[1]]};
      If[complexSubOptionList =!= {}, 
       Set @@@ MapAt[containerName, determinableSubOptions, {All, 1}]],
      (* case when option is simple or complex option when none of \
suboptions were set *)
      _,
      Set @@ {containerName[ToString[optionName]], optionValue};
      (* for option with suboption in addition we set all suboptions *)

            If[
       MatchQ[optionName /. Options[commandName], 
        HoldPattern[{_, __?OptionQ}]], 
       Set @@@ MapAt[containerName, 
          List @@@ Rest[optionName /. Options[commandName]], {All, 1}];
       ];
      ];
     gaOptionCheck[testOptionForsetNamedOptionValue][
      OptionValue[Quiet]];
     If[! OptionValue[Quiet],
      CellPrint[{TextCell[
          Column[{Row[{TextCell[
               "Options which are accessable to \
gaSetNamedOptionValue[ ] are"]}], 
            Row[{ExpressionCell[determinableSubOptions]}], 
            Row[{TextCell["Their values are set to"]}], 
            Row[{ExpressionCell[Definition[containerName]]}]}]]}];
      ];
     ];
   ];

Now for rather complicated case, when I test the complex option validity of different command it works as expected, for example

Options[gaProductPairExpand] = {CoefficientFunction -> Expand, 
   Quiet -> True, 
   Method -> {"RealTimePairProduct", MaxIterations -> Infinity}};
ClearAll[testOptionProductPairExpand];
(* simple option case *)

testOptionProductPairExpand[CoefficientFunction] = 
  Expand | Identity | 
   "any transformation (i.e with head Function[]) of expression";
testOptionProductPairExpand[Quiet] = "True" | "False";
(* two argument cal is used for actual check*)

testOptionProductPairExpand[CoefficientFunction, 
   val_Symbol | val_Function] := 
  MatchQ[val, Expand | Identity | _Function];
testOptionProductPairExpand[Quiet, val_] := BooleanQ[val];

(* case of option which may have suboptions *)
(* single argument \
call is used to print valid option pattern for user*)

testOptionProductPairExpand[Method] = 
  "ConvolutionPairProduct ()" | 
   "RealTimePairProduct (MaxIterations)" | 
   "PrecomputedPairProduct (OmitGrades)";
testOptionProductPairExpand[{"RealTimePairProduct", MaxIterations}] = 
 HoldForm[{"RealTimePairProduct", 
   MaxIterations -> Alternatives[Infinity, "Non negative integer"]}]; 
testOptionProductPairExpand[{"PrecomputedPairProduct", OmitGrades}] = 
 HoldForm[{"PrecomputedPairProduct", 
   OmitGrades -> Alternatives[{}, "list of nonnegative integers"]}];

(* if option has suboptions, test function should be defined for each \
suboption*)

testOptionProductPairExpand[Method, val_String | {val_String}] := 
 MatchQ[val, 
  "ConvolutionPairProduct" | "RealTimePairProduct" | 
   "PrecomputedPairProduct"]; 
testOptionProductPairExpand[{"RealTimePairProduct", MaxIterations}, 
  val_] := MatchQ[val, Alternatives[Infinity, _Integer?NonNegative]]; 
testOptionProductPairExpand[{"PrecomputedPairProduct", OmitGrades}, 
  val_] := MatchQ[val, {___Integer?NonNegative}];

Options[gaProductExpand] = {Except -> {}, 
   "IncludeProductPairExpandOptionValues" -> True};
(* simple option case *)

testOptionProductExpand["Except"] = 
  "{GeometricProduct,OuterProduct,InnerProduct,LeftContract,\
RightContract} in any combination";
testOptionProductExpand["IncludeProductPairExpandOptionValues"] = 
  "True | False";
(* two argument cal is used for actual check*)

testOptionProductExpand["Except", val_Symbol | val_List] := 
  ContainsAll[{GeometricProduct, OuterProduct, InnerProduct, 
    LeftContract, RightContract}, Flatten[{val}]];
testOptionProductExpand["IncludeProductPairExpandOptionValues", 
   val_Symbol] := BooleanQ[val];
SetAttributes[gaProductExpand, {Listable}];

gaProductExpand[expression_, 
  op : OptionsPattern[{gaProductExpand, gaProductPairExpand}]] := 
 Module[{exceptHeads = 
    gaOptionCheck[testOptionProductExpand][
     OptionValue[gaProductExpand, "Except"]], 
   includeExplicitOptionValues = 
    gaOptionCheck[testOptionProductExpand][
     OptionValue[gaProductExpand, 
      "IncludeProductPairExpandOptionValues"]], 
   allProductPairExpandOptions = 
    FilterRules[{op}, Options[gaProductPairExpand]], theOption, 
   restOpts, trueHeads, trueHeadsHeads, trueHeadsBlanks},
  (*set values of all options: exeptional usage, 
  we set options for gaProductPairExpand not gaProductExpand*)
  
  If[includeExplicitOptionValues,
    gaSetNamedOptionValue[{gaProductPairExpand, #, theOption}, 
       gaOptionCheck[testOptionProductPairExpand][OptionValue[#]], 
       Quiet -> True
       ] & /@ (First /@ allProductPairExpandOptions);
    If[Head[theOption["Method"]] === theOption, 
     theOption["Method"] = "RealTimePairProduct"];
    If[Head[theOption["MaxIterations"]] === theOption, 
     theOption["MaxIterations"] = Infinity];
    If[Head[theOption["OmitGrades"]] === theOption, 
     theOption["OmitGrades"] = {}];
    If[Head[theOption["CoefficientFunction"]] === theOption, 
     theOption["CoefficientFunction"] = Expand];
    If[Head[theOption["Quiet"]] === theOption, 
     theOption["Quiet"] = True];
    restOpts = {theOption["CoefficientFunction"], theOption["Quiet"], 
      theOption["Method"], theOption["MaxIterations"], 
      theOption["OmitGrades"]},
    (* old behaviour, 
    when gaProductPairExpand evaluates options itself *)
    
    restOpts = Sequence @@ allProductPairExpandOptions
    ];
        ]

Check:

gaProductExpand[any, Except -> {GeometricProduct, OuterProduct}, 
 Method -> {"RealTimePairProduct", MaxIterations -> 1}]

Out: Entering in suboptions {detecting option Value of,{RealTimePairProduct,MaxIterations->1}}

And for wrong input it produces what I want:

gaProductExpand[any, Except -> {GeometricProduct, OuterProduct}, 
 Method -> {"RealTimePairProduct", MaxIterations -> a}]

Out:Entering in suboptions

gaOptionCheck::invldopt: Option Method for function {gaProductExpand,gaProductPairExpand} received invalid value {RealTimePairProduct,MaxIterations->a}. Valid pattern is Method -> {RealTimePairProduct,MaxIterations->[Infinity]|Non negative integer}.

However the seemingly simple case fails and few days I cannot think why. Here is very similar code:

    Options[gaDeterminantOfMV] = {Method -> Automatic, 
   Expand -> Automatic, Quiet -> False};
(*Method\[Rule]{"Involutions",gaDeterminantFormula\[Rule]"Recursive",\
Direction\[Rule]"Left"} *)

(* case of option which may have suboptions *)
(* single argument \
call is used to print valid option pattern for user*)

testOptionDeterminantOfMV[Method] = 
  Alternatives[Automatic, 
   "Involutions (gaDeterminantFormula, Direction)", "Det()"];
testOptionDeterminantOfMV[{"Involutions", gaDeterminantFormula}] = 
  HoldForm[{"Involutions", 
    gaDeterminantFormula -> 
     Alternatives["Recursive", "Explicit", "Optimal"]}];
testOptionDeterminantOfMV[{"Involutions", Direction}] = 
  HoldForm[{"Involutions", 
    Direction -> Alternatives["Left", "Right"]}];

(* if option has suboptions, test function should be defined for each \
suboption*)

testOptionDeterminantOfMV[Method, val_] := 
  MatchQ[val, Alternatives[Automatic, "Involutions", "Det"]];
testOptionDeterminantOfMV[Method, {val_String, ___?OptionQ}] := 
  MatchQ[val, Alternatives["Involutions", "Det"]];
testOptionDeterminantOfMV[{"Involutions", gaDeterminantFormula}, 
  val_] := MatchQ[val, 
  Alternatives["Recursive", "Explicit", "Optimal"]]; 
testOptionDeterminantOfMV[{"Involutions", Direction}, val_] := 
 MatchQ[val, Alternatives["Left", "Right"]];


(* case of simple option  *)

testOptionDeterminantOfMV[Expand] = 
  Automatic | Expand | Identity | 
   "any transformation (i.e with head Function[]) of expression";
testOptionDeterminantOfMV[Quiet] = "True" | "False";
(* two argument cal is used for actual check*)

testOptionDeterminantOfMV[Expand, val_Symbol | val_Function] := 
  MatchQ[val, Automatic | Expand | Identity | _Function];
testOptionDeterminantOfMV[Quiet, val_] := BooleanQ[val];


gaDeterminantOfMV::unknownMethod = 
  "Determinant computation method Method->`1` is unknown. Possible \
values are Automatic, Involutions and Det. Method\[Rule]Det will \
first calculate matrix representation of MV. The default is to use \
Method->Involutions for algebras with p+q<=6 and Method\[Rule]Det in \
all other cases.";
gaDeterminantOfMV::OptimalMethod = 
  "Explicit optimized formulas are known for dimension \
n\[LessEqual]6. Your algebra has dimension `1`. Switching suboption \
gaDeterminantFormula->\"Explicit\".";
gaDeterminantOfMV::fail = 
  "Failed to replace quaternion entries by complex 2x2 matrices in \
`1`.";
gaDeterminantOfMV::unexpectedDim = 
  "The calculated matrix dimension `1`.";


gaDeterminantOfMV[expr_, op : OptionsPattern[{gaDeterminantOfMV}]] := 
  If[False,
   "Skip branch",
   Module[{theOption, allOptions = Options[gaDeterminantOfMV], tt},
    Print[{op}];
    
    (*gaSetNamedOptionValue[{gaDeterminantOfMV,#,theOption},
    gaOptionCheck[testOptionDeterminantOfMV][OptionValue[#]],
    Quiet\[Rule]True
    ]&/@(First/@allOptions);*)
    
    gaSetNamedOptionValue[{gaDeterminantOfMV, #, theOption}, 
       gaOptionCheck[testOptionDeterminantOfMV][OptionValue[#]], 
       Quiet -> False
       ] & /@ (Flatten[First /@ {op}]);
    
    
    (* check if main options obtained values *)
    
    If[Head[theOption["Method"]] === theOption, 
     theOption["Method"] = Automatic];
    If[Head[theOption["Expand"]] === theOption, 
     theOption["Expand"] = Automatic];
    If[Head[theOption["Quiet"]] === theOption, 
     theOption["Quiet"] = False];
    {theOption["Method"], theOption["gaDeterminantFormula"], 
     theOption["Direction"], theOption["Expand"], theOption["Quiet"]}
    ]
   ];

It even don't enter suboption checking part:

gaDeterminantOfMV[expr, 
 Method -> {"Involutions", gaDeterminantFormula -> "Explicit1", 
   Expand -> "None"}]

Out:

{Method->{Involutions,gaDeterminantFormula->Explicit1,Expand->None}}

{detecting option Value of,{Involutions,gaDeterminantFormula->Explicit1,Expand->None}}

Options which are accessable to gaSetNamedOptionValue[ ] are {{"gaDeterminantFormula","Explicit1"},{"Expand","None"}} Their values are set to Attributes[theOption$2725]={Temporary}

theOption$2725["Expand"]="None"

theOption$2725["gaDeterminantFormula"]="Explicit1"

theOption$2725["Method"]="Involutions"

Since I gave bad value gaDeterminantFormula->"Explicit1" (instead "Explicit") it should be able to detect that.

$\endgroup$
1
  • $\begingroup$ At last I think I found that problem is with not quite consistent behavior of OptionValue[ ]. In general it works well when argument is string. When argument is not a string (in my case a symbol) sometimes it works, sometimes fails. The correction then is obvious. $\endgroup$ – user18792 Sep 20 '20 at 15:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.