Setting up diagnostic error messages in large Mathematica projects

前端 未结 5 1871
春和景丽
春和景丽 2020-12-10 02:12

Whenever I create a large Mathematica project I run into this problem: Preventing avalanche of runtime errors in Mathematica, i.e., Mathematica\'s error message are opaque,

相关标签:
5条回答
  • 2020-12-10 02:30

    A suggestion for extracting stack, maybe something that relies on Trace?

    An example of using Trace below, from Chris Chiasson. This code saves evaluation tree of 1 + Sin[x + y] + Tan[x + y] into ~/temp/msgStream.m

    Developer`ClearCache[];
    SetAttributes[recordSteps, HoldAll];
    recordSteps[expr_] :=
    
      Block[{$Output = List@OpenWrite["~/temp/msgStream.m"]}, 
       TracePrint[Unevaluated[expr], _?(FreeQ[#, Off] &), 
        TraceInternal -> True];
       Close /@ $Output;
       Thread[
        Union@Cases[
          ReadList["~/temp/msgStream.m", HoldComplete[Expression]], 
          symb_Symbol /; 
            AtomQ@Unevaluated@symb && 
             Context@Unevaluated@symb === "System`" :> 
           HoldComplete@symb, {0, Infinity}, Heads -> True], 
        HoldComplete]
       ];
    recordSteps[1 + Tan[x + y] + Sin[x + y]]
    

    To answer Samsdram's question, the code below (also from Chris) gives evaluation tree of a Mathematica expression. Here is the post from MathGroup with source code and examples.

    (Attributes@# = {HoldAllComplete}) & /@ {traceToTreeAux, toVertex, 
      HoldFormComplete, getAtoms, getAtomsAux}
    MakeBoxes[HoldFormComplete[args___], form_] := 
     MakeBoxes[HoldForm[args], form]
    edge[{head1_, pos1_, xpr1_}, {head2_, pos2_, xpr2_}] := 
     Quiet[Rule[{head1, vertexNumberFunction@pos1, xpr1}, {head2, 
        vertexNumberFunction@pos2, xpr2}], {Rule::"rhs"}]
    getAtomsAux[atom_ /; AtomQ@Unevaluated@atom] := 
     Sow[HoldFormComplete@atom, getAtomsAux]
    getAtomsAux[xpr_] := Map[getAtomsAux, Unevaluated@xpr, Heads -> True]
    getAtoms[xpr_] := Flatten@Reap[getAtomsAux@xpr][[2]]
    toVertex[traceToTreeAux[HoldForm[heldXpr_], pos_]] := toVertex[heldXpr]
    toVertex[traceToTreeAux[HoldForm[heldXprs___], pos_]] := 
     toVertex@traceToTreeAux[Sequence[], pos]
    (*this code is strong enough to not need the ToString commands,but \
    some of the resulting graph vertices give trouble to the graphing \
    routines*)
    toVertex[
      traceToTreeAux[xpr_, pos_]] := {ToString[
       Short@Extract[Unevaluated@xpr, 0, HoldFormComplete], StandardForm],
       pos, ToString[Short@First@originalTraceExtract@{pos}, StandardForm]}
    traceToTreeAux[xpr_ /; AtomQ@Unevaluated@xpr, ___] := Sequence[]
    traceToTreeAux[_HoldForm, ___] := Sequence[]
    traceToTreeAux[xpr_, pos_] := 
     With[{lhs = toVertex@traceToTreeAux[xpr, pos], 
       args = HoldComplete @@ Unevaluated@xpr}, 
      Identity[Sequence][
       ReleaseHold[
        Function[Null, edge[lhs, toVertex@#], HoldAllComplete] /@ args], 
       ReleaseHold@args]]
    traceToTree[xpr_] := 
     Block[{vertexNumber = -1, vertexNumberFunction, 
       originalTraceExtract}, 
      vertexNumberFunction[arg_] := 
       vertexNumberFunction[arg] = ++vertexNumber; 
      originalTraceExtract[pos_] := 
       Extract[Unevaluated@xpr, pos, HoldFormComplete]; {MapIndexed[
        traceToTreeAux, Unevaluated@xpr, {0, Infinity}]}]
    TraceTreeFormPlot[trace_, opts___] := 
      Block[{$traceExpressionToTree = True}, 
       Through@{Unprotect, Update}@SparseArray`ExpressionToTree; 
       SparseArray`ExpressionToTree[trace, Infinity] = traceToTree@trace; 
       With[{result = ToExpression@ToBoxes@TreeForm[trace, opts]}, 
        Through@{Unprotect, Update}@SparseArray`ExpressionToTree; 
        SparseArray`ExpressionToTree[trace, Infinity] =.; 
        Through@{Update, Protect, Update}@SparseArray`ExpressionToTree; 
        result]];
    
    TraceTreeFormPlot[Trace[Tan[x] + Sin[x] - 2*3 - 55]]
    
    0 讨论(0)
  • 2020-12-10 02:31

    One attempt to implement @Timo's idea (theStack)

    Incomplete and perhaps flawed, but just to keep thinking about it:

    Clear["Global`*"];
    funcDef = t_[args___]  \[CircleMinus] a_ :>
       {t["nude", args] := a,
        ReleaseHold[Hold[t[args] :=
           (If[! ValueQ[theStack], theStack = {}];
            AppendTo[theStack, ToString[t]];
            Check[ss = a, Print[{"-TheStack->", Evaluate@theStack}]; 
             Print@Hold[a]; Abort[]];
            theStack = Most@theStack;
            Return[ss])
          ]]};
    v[x_, y_]\[CircleMinus]  (Sin@ g[x, y]) /. funcDef;
    g[x_, y_]\[CircleMinus]  x/y /. funcDef;
    v[2, 3]
    v[2, 0]
    

    Output:

    Out[299]= Sin[2/3]
    
    During evaluation of In[295]:= Power::infy: Infinite expression 1/0 encountered. >>
    
    During evaluation of In[295]:= {-TheStack->,{v,g}}
    
    During evaluation of In[295]:= Hold[2/0]
    
    Out[300]= $Aborted
    
    0 讨论(0)
  • 2020-12-10 02:36

    To get the ball rolling here is one idea that I've been toying with; the creation of a pseudo stack.

    First make a global variable theStack={} and then in every Function or Module start with AppendTo[theStack,"thisFuncName"] and end with theStack=Most@theStack. Assuming moderate (~a few tens) depth of function calls, this should not add any significant overhead.

    Then implement your own typing/error checking and use Print@theStack;Abort[]; on errors.

    Refinements of this method could include:

    1. Figuring out a way to dynamically get "thisFuncionName" so that the AppendTo[] can be made into an identical function call for all Functions and Module.
    2. Using Message[] Instead of Print[].
    3. Pushing other important variables / stateful information on theStack.
    0 讨论(0)
  • 2020-12-10 02:39

    Perhaps we have been over thinking this. What if we just tweaked the pattern matching on the arguments a little. For instance, if we modified the function to check for a numeric quantity and added some code to print an error if it fails. For instance,

     TypeNumeric[x_] :=   If[! NumericQ[Evaluate[x]],
     Print["error at "]; Print[Stack[]];    Print["Expression "]; Print[x];    Print["Did   
     not return a numeric value"];Return[False], 
     (*Else*)
     Return[True];] 
     SetAttributes[TypeNumeric, HoldAll];
    

    Step 2: If you have a function, f[x_] that requires a numeric quantity, just write it with the standard pattern test and all should be well

    Input:
    f[x_?TypeNumeric] := Sqrt[x]
    f[Log[y]]
    f[Log[5]]
    Output:
    error at 
    {f}
    Expression 
    Log[y]
    Did not return a numeric value
    f[Log[y]]
    
    Sqrt[Log[5]]
    

    I believe this will work and, it makes robust type checking as simple as a writing a function or two. The problem is that this could be hugely inefficient because this code evaluates the expression x twice, once for the type checking and once for real. This could be bad if an expensive function call is involved.

    I haven't figured out the way around this second problem and would welcome suggestions on that front. Are continuations the way out of this problem?

    Hope this helps.

    0 讨论(0)
  • 2020-12-10 02:40

    YAsI - Yet Another (silly?) Idea ...

    Re-reading your question ...

    The idea then is to disable all of Mathematica's own error messages and implement type checking and error messages of your own in every Function and Module.

    Found this:

    $MessagePrePrint = ( #; Print[Stack[_][[;; -5]]]; Abort[]) &  
    
    v[x_, y_] := w[x, y];
    w[x_, y_] := x/y;
    
    StackComplete@v[1, 0];
    
    During evaluation of In[267]:= {StackComplete[v[1,0]];,
              StackComplete[v[1,0]], v[1,0], w[1,0], 1/0, 1/0, Message[Power::infy,1/0]}
    
    Out[267]= $Aborted
    

    conclusion ... Aborts at first message and leaves a "reasonable" stack trace. "Reasonable" means "Should be improved".

    But it is completely non-intrusive!

    0 讨论(0)
提交回复
热议问题