Prepend Information to Warning Messages

30

21

I have a function running within a Do loop that sometimes issues a warning. I'd like to prepend the warning with the loop ctr so that I can go back and debug that instance later.
Basically, I would like to modify the following line,

Do[i^0, {i, -1, 1}]

so that instead of displaying the warning:

Power::indet: Indeterminate expression 0^0 encountered. >>

it displays:

i=0, Power::indet: Indeterminate expression 0^0 encountered. >>

Where i==0 is the iteration that i^0 issues the warning.

Thanks

Chris Roth

Posted 2012-07-11T18:44:28.353

Reputation: 547

Answers

32

Here is my proposal for tagging messages with (the value of) an arbitrary expression at the time of message generation. The tag is placed inside the the message itself.

ClearAll[withTaggedMsg]
SetAttributes[withTaggedMsg, HoldAll]

withTaggedMsg[exp_, label_: "When"] := Function[,
   Internal`InheritedBlock[{MessagePacket},
     Unprotect @ MessagePacket;
     mp : MessagePacket[__, _BoxData] /; !TrueQ[$tagMsg] :=
   Block[{$tagMsg = True},
         Style[Row[{label, HoldForm[exp], "=", exp, " "}, "  "], "SBO"] /. tag_ :>
           MapAt[RowBox[{ToBoxes @ tag, #}] &, mp, {-1, 1}] // Identity
       ];
     #
   ],
  HoldAll]

Usage:

Do[i^0, {i, -1, 1}] // withTaggedMsg[i]

enter image description here

Do[i^0, {i, -1, 1}] // withTaggedMsg[i, "At iteration"]

enter image description here


Note: this only works with variables that are either globally accessible or are scoped using Block. For example,

f[x_] := Message[f::brains, x]
f[5] // withTaggedMsg[x]
(* At iteration x = x f::brains: -- Message text not found -- (5) *)

Module[{x = 5},
 Message[f::brains, x]
] // withTaggedMsg[x]
(* At iteration x = x f::brains: -- Message text not found -- (5) *)

With[{x = 5},
 Message[f::brains, x]
] // withTaggedMsg[x]
(* At iteration x = x f::brains: -- Message text not found -- (5) *)

Block[{x = 5},
 Message[f::brains, x]
] // withTaggedMsg[x]
(* At iteration x = 5 f::brains: -- Message text not found -- (5) *)

This means that any variable that is scoped using Block can be used to tag a message. So, loop variables from Do and Table are accessible via this method, in addition to any Block variable. This makes it indispensable as a debugging tool.

Mr.Wizard

Posted 2012-07-11T18:44:28.353

Reputation: 259 163

Argh, I hadn't thought about what happens to the other fields. I do like your second method better, though; it is more readable and closer to what the OP wants. Although, the two cell output isn't to my liking, all that much, it is very nice, overall. +1 – rcollyer – 2012-07-12T00:54:20.877

1

I added a fix per Rojo.

– rcollyer – 2012-07-13T14:23:27.003

@rcollyer thanks! – Mr.Wizard – 2012-07-13T15:10:18.357

This is why I participate here: I'm having to make use of this to debug some code to load a file, and this allows me to spit out the line numbers with the errors. I'd give you another +1 if I could. – rcollyer – 2012-10-11T18:34:00.910

@rcollyer Glad to be of help, as always. Thanks for letting me know, I value that a lot more than points. – Mr.Wizard – 2012-10-11T19:06:23.527

You're welcome. I think that is the primary reason most of us are here. Anyway, very useful piece of code, now if I can only get my software to behave ... :P – rcollyer – 2012-10-11T19:21:52.550

@Mr.Wizard, suppose instead of Do[i^0, {i, -1, 1}] the iteration is via Map, eg (1/#) & /@ Range[-10, 10]. How to preserve message tagging w/o the named iterator? – alancalvitti – 2012-12-20T22:22:29.193

@alan I guess that depends on what you're trying to do. How do you envision using this, and with what syntax? You could artificially introduce a global symbol, e.g. Block[{i = #}, (1/#)] & /@ Range[-10, 10] // withTaggedMsg[i]. You could possibly use Check, e.g. Check[1/#, Print["...happened with argument: ", #]] & /@ Range[-10, 10]; (It may be possible collapse the printed line and the message into one.) It seems to me that Do is always going to be cleaner. – Mr.Wizard – 2012-12-21T01:05:10.473

18

I cannot seem to make it do exactly what you want do to how messages are created, but here is a serviceable alternative using $MessagePrePrint. $MessagePrePrint formats the variables specified in the message string, and in your example, the message has the form

General::indet = "Indeterminate expression `1` encountered."

where the `1` will be replaced by $0^0$, or whatever else you pass to it. It is that argument that $MessagePrePrint operates on, and we can change it to suit us, as follows

Block[{$MessagePrePrint},
     $MessagePrePrint := ToString[StandardForm[#]] <> " at i = " <> ToString[i] &;
 Do[i^0, {i, -1, 1}]
]

where the output will now read "$0^0\text{ at i} =1$".


(Edit from Mr.Wizard)

The code above fails in Mathematica 7. It seems that v7 doesn't like the fact that

InputForm @ ToString @ StandardForm @ HoldForm[0^0]
 "\!\(\*TagBox[\(0\^0\), HoldForm]\)"

Here is a variation that is compatible with version 7:

Block[{$MessagePrePrint},
      $MessagePrePrint := Row@{#, " at i = ", i} ~ToString~ StandardForm &;
  Do[i^0, {i, -1, 1}]
]

This is still not ideal however, as every field in the message gets this tag. For example, if you enter the spurious Inner[f, {{1, 2}}, {3}] you get:

Inner::incom: Length 2 at i = 1 of dimension 2 at i = 1 in {{1,2}} at i = 1 is incommensurate with length 1 at i = 1 of dimension 1 in {3} at i = 1. >>

rcollyer

Posted 2012-07-11T18:44:28.353

Reputation: 32 561

1It seems that v7 doesn't like the fact that InputForm@ToString@StandardForm@HoldForm[0^0] yields "\!\(\*TagBox[\(0\^0\), HoldForm]\)". Either the form I show in my answer or ToBoxes[ToString[StandardForm@#] <> " at i = " <> ToString[i]] & appear to work. – Mr.Wizard – 2012-07-11T22:25:24.433

15

rcollyer has a nice solution. Here's another possibility using Check and printing the list of messages generated at the current evaluation.

Quiet@Block[{$OldMessages = 0}, 
        Do[Check[#^#/# &@Mod[i, 2], 
            Print@StringForm["At i=``, ``", i, $MessageList[[$OldMessages + 1 ;;]]]; 
            $OldMessages = Length@$MessageList;], 
        {i, 0, 5}]
   ];

(* At i=0, {Power::indet,Power::infy}
   At i=2, {Power::indet,Power::infy}
   At i=4, {Power::indet,General::stop,Power::infy,General::stop} *)

Remove the Quiet@ if you want to see the actual messages generated.

rm -rf

Posted 2012-07-11T18:44:28.353

Reputation: 85 395