【问题标题】:Setting up diagnostic error messages in large Mathematica projects在大型 Mathematica 项目中设置诊断错误消息
【发布时间】:2010-11-14 07:36:20
【问题描述】:

每当我创建一个大型 Mathematica 项目时,我都会遇到这个问题:Preventing avalanche of runtime errors in Mathematica,即 Mathematica 的错误消息是不透明的、陈旧的和大量的。

然后的想法是禁用 Mathematica 自己的所有错误消息,并在每个函数和模块中实现您自己的类型检查和错误消息。但是,我还没有找到一种简单有效的方法来执行此操作并最终得到,例如,某些函数会产生错误 20 次函数调用深度,然后将一整串错误消息一直返回到主程序。

您将如何为此设置一种简单的机制,仅在遇到错误的函数处生成一条错误消息以及函数调用链的简单列表?

编辑:因为它已经出现了几个答案;我特别在寻找关于它产生的输出的轻量级的东西(否则我可以坚持使用 Mathematica 的错误消息)并且显然在计算开销方面也是轻量级的。因此,虽然 StackTrace 的开销肯定很轻,但它们在复杂项目中的输出解析速度并不快,需要做一些工作来简化它。

【问题讨论】:

  • 我喜欢简单机制的想法! Mathematica 的“警告”通常对应于不可恢复的错误,因此继续评估毫无意义。如果有一些东西会中断对第一条消息的评估,那就太好了。
  • 在第一条消息之后终止评估可能会通过与Message[] 本身混淆来完成......有趣的方法!
  • 顺便说一句,版本 7 中的内置调试器做了类似的事情——如果你选中“Break at Messages”,它会在每条消息处中断并显示堆栈
  • 重写了我的答案。它仍然需要一些工作,但我认为这是最简单的类型检查和打印堆栈的方法,而无需对现有代码进行重大重写。
  • @Samsdram 好主意,但您应该简单地 Abort[] 而不是 Return[False]。否则,如果错误涉及多个函数调用,您最终会出现巨大的错误级联。

标签: error-handling functional-programming wolfram-mathematica


【解决方案1】:

YAsI - 又一个(愚蠢的?)想法......

重新阅读您的问题...

然后的想法是禁用 Mathematica 自己的所有错误消息,并在每个函数和模块中实现您自己的类型检查和错误消息。

找到这个:

$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

结论...在第一条消息处中止并留下“合理的”堆栈跟踪。 “合理”的意思是“应该改进”。

但它是完全非侵入式的!

【讨论】:

  • 非常好!我将不得不解决这个问题,但似乎 $MessagePrePrint 上的正确格式意味着您只需要在错误/类型检查的点添加一行,即StackComplete@funcName[args],到函数中。
  • @Timo 如果您取得了一些进展,请发帖。我也将开始测试这个。我认为跟踪太线性(像 TreePlot 之类的东西可能是杀手),与我之前的尝试(你的 theStack 想法)相比,参数记录是一个很好的免费奖励。此外,设置调试标志(If[deBug,..] 可能允许相同的代码运行几乎没有性能损失。
  • 其他几个解决方案也非常好,但你会得到风格的复选标记!稍后我将尝试用一个简洁的示例编辑操作。
【解决方案2】:

让球在这里滚动是我一直在玩弄的一个想法;创建一个伪堆栈。

首先创建一个全局变量theStack={},然后在每个FunctionModule 中以AppendTo[theStack,"thisFuncName"] 开头并以theStack=Most@theStack 结尾。假设函数调用的深度适中(~几十个),这应该不会增加任何显着的开销。

然后实现您自己的打字/错误检查并在错误上使用Print@theStack;Abort[];

此方法的改进可能包括:

  1. 找出一种动态获取“thisFuncionName”的方法,以便可以将AppendTo[] 转换为对所有FunctionsModule 的相同函数调用。
  2. 使用Message[] 而不是Print[]
  3. theStack 上推送其他重要变量/状态信息。

【讨论】:

  • 尝试在我的回答中实现(第一次尝试)
  • 一个Stack 已经存在(reference.wolfram.com/mathematica/ref/…),它有许多相关的函数可以做同样的事情。
  • Stack 我认为与Trace 存在相同的问题,即您需要做很多额外的工作才能获得简单的可读输出。在编写/调试大型项目时,我发现知道哪个函数生成错误、从哪里调用以及使用什么参数几乎总是足够的。 TraceStack 都提供了这些信息,但被埋在一堆其他东西中。我想要一些我可以一眼就知道去哪里寻找错误的东西。
【解决方案3】:

尝试实现@Timo 的想法(theStack)

不完整,也许有缺陷,但只是继续思考:

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]

输出:

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

【讨论】:

  • BTW ...我没有找到一种方法让 funcDef 将函数参数存储在堆栈中。想法?
  • 我只是在考虑推送和删除列表,例如 AppendTo[theStack,{"funcName",{funArg1,..}}]。
  • @Timo 目的是通过中止和转储“theStack”来处理消息。我仍然无法输入函数参数,这显然是一个缺陷。
  • @belisarus 在使用了您的解决方案一段时间后,我明白了您的意思。非常有趣的方法!我不会想到让整个事情有效地成为一个 lambda 函数。
  • @belisarus,请参阅我对 Timo 回答的评论。
【解决方案4】:

提取堆栈的建议,可能依赖于 Trace?

下面是使用 Trace 的示例,来自 Chris Chiasson。此代码将 1 + Sin[x + y] + Tan[x + y] 的评估树保存到 ~/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]]

为了回答 Samsdram 的问题,下面的代码(也来自 Chris)给出了 Mathematica 表达式的评估树。 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]]

【讨论】:

  • 能发一下链接吗?
  • 抱歉,没有链接,这是我几年前从一些 MathGroup 邮件列表消息中复制的
  • Trace 非常好,但正如您所见,它需要大量工作才能使其输出可读:-)。此外,这感觉就像将轨道导弹指挥到刀战中。我喜欢将Trace 的输出保存到其他地方直到可能需要它的想法。
【解决方案5】:

也许我们一直在想这个。如果我们只是稍微调整一下参数的模式匹配会怎么样。例如,如果我们修改函数以检查数字数量并添加一些代码以在失败时打印错误。例如,

 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];

第 2 步:如果你有一个函数 f[x_] 需要一个数字量,只需用标准模式测试编写它就可以了

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]]

我相信这会起作用,并且它使健壮的类型检查变得像编写一两个函数一样简单。问题是这可能非常低效,因为此代码对表达式 x 求值两次,一次用于类型检查,一次用于真实。如果涉及昂贵的函数调用,这可能会很糟糕。

我还没有想出解决第二个问题的方法,并欢迎在这方面提出建议。延续是解决这个问题的方法吗?

希望这会有所帮助。

【讨论】:

  • @Samsdram 你在寻找像 TreeForm[] 这样的东西吗?
  • 为什么合同比 If,Print,Abort 检查更好?严格的类型检查可能对大型/协作项目很有用,但我个人想要一些有助于解决错误的东西,而无需我现在改变我编写 Mathematica 函数的方式
  • 顺便说一句,我在答案中添加了一个编辑,它给出了类似 TreeForm 的内容,但用于评估树而不是解析树
  • 同意@Yaroslav。对于大型项目,这似乎是可行的方法,但对于面包和黄油计算来说太麻烦了
  • Contracts 比 If/Print/Abort 等要好,因为错误通常是删除了两个或三个函数调用。例如定义 f[x_]:=x/;x>=0 然后调用 f[g[x]]。简单的工具可以在到达函数 f 时捕获错误,但是当您有很多代码时,您真正想知道的是 g[x] 是问题的原因。我不确定您是否可以使用前面建议的堆栈方法来执行此操作,因为 g[x] 将在 f[x] 中捕获错误之前完成评估,应该没有堆栈上的记录。小项目的矫枉过正,但问题是关于大项目的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-31
相关资源
最近更新 更多