PowerExpand/InverseTrig snafu
- To: mathgroup at christensen.cybernetics.net
- Subject: [mg830] PowerExpand/InverseTrig snafu
- From: Dave Wagner <wagner at bullwinkle.cs.Colorado.EDU>
- Date: Mon, 24 Apr 1995 02:54:14 -0400
>To: Jack Goldberg <jackgold at math.lsa.umich.edu> >Subject: Re: Reply to my plea >In-reply-to: Your message of "Wed, 19 Apr 95 11:35:02 EDT." <Pine.SUN.3.91.950419113115.7364A-100000 at jack.math.lsa.umich.edu> -------- My reply to Jack Goldberg's question about introducing a new option (InverseTrig) to a built-in function (PowerExpand) had several problems. I feel culpable for several of these, but one of themis something that I think very few people could have foreseen. First, let me point out two minor problems with the expression (InverseTrig /. opts /. Options[PowerExpand]) == True The first "problem" is that the comparison is unnecessary; if a condition evaluates to anything other than True, the rule will not fire. The second problem is that, in general, opts should be surrounded by list braces: (InverseTrig /. {opts} /. Options[PowerExpand]) == True This will work even if there is more than one option in the command. But the SERIOUS problem is that with a definition of this form: PowerExpand[expr_, opts___Rule] /; (InverseTrig /. {opts} /. Options[PowerExpand]) == True := Module[... ...PowerExpand[expr]... ] you should enter an infinite recursion if you ever do a SetOptions[PowerExpand, InverseTrig->True] because the option-extraction condition will always return True! (I say "should" because, in this particular case it doesn't happen, for reasons that will become clearer below.) The fix to this problem in general is to use a global variable as a flag, as in intercept = True; PowerExpand[expr_, opts___Rule] /; intercept && (InverseTrig /. opts /. Options[PowerExpand]) := Block[{intercept = False}, Module[... ...PowerExpand[expr]... ] ] The Block command turns off the flag temporarily so that recursive calls are prevented. The true advantage of using Block for this purpose is that, even if the computation is aborted, the flag will be reset before returning. (Thanks to Todd Gayley for this insight.) Now, it turns out that I have opened a tremendous can of worms here, as the following transcript will show. In[1]:= Unprotect[PowerExpand] Out[1]= {PowerExpand} Here is a rule for PowerExpand with some strategically-placed Print statements: In[2]:= PowerExpand[expr_, opts___Rule] /; With[{it = InverseTrig /. {opts} /. Options[PowerExpand]}, Print["in condition, intercept = ", intercept, ", InverseTrig = ", it]; intercept && it ] := Block[{intercept = False}, Print["in body"]; PowerExpand[expr] ] In[3]:= Options[PowerExpand] = Append[Options[PowerExpand], InverseTrig->False] Out[3]= {InverseTrig -> False} In[4]:= intercept = True Out[4]= True Here, the built-in rule fires. In[5]:= PowerExpand[Sqrt[a^2]] Out[5]= a Here, our rule fires. In[6]:= PowerExpand[Sqrt[a^2], InverseTrig->True] in condition, intercept = True, InverseTrig = True in body Out[6]= a The question arises, why didn't the "in condition..." print statement show up during evaluation of In[5], and twice during In[6]? We attempt to make our defined behavior the default: In[7]:= SetOptions[PowerExpand, InverseTrig->True] Out[7]= {InverseTrig -> True} It doesn't work! In[8]:= PowerExpand[Sqrt[a^2]] Out[8]= a Alright, now the worms start to squirm. It turns out that there are some system-defined DownValues for PowerExpand. Before we can see them, we have to clear the ReadProtected attribute: In[9]:= ClearAttributes[PowerExpand, ReadProtected] In[10]:= First /@ DownValues[PowerExpand] Out[10]= {Literal[PowerExpand[System`Private`expr_]], Literal[PowerExpand[System`Private`expr_, {}]], Literal[PowerExpand[System`Private`expr_, {System`Private`a_}]], Literal[PowerExpand[System`Private`expr_, {System`Private`a_, System`Private`r__}]], Literal[PowerExpand[expr_, opts___Rule] /; With[{it = InverseTrig /. {opts} /. Options[PowerExpand]}\ , Print[in condition, intercept = , intercept\ , , InverseTrig = , it]; intercept && it]], Literal[PowerExpand[System`Private`expr_, System`Private`a_]], Literal[PowerExpand[System`Private`args___]]} As you can see, our rule for PowerExpand does NOT take precedence over all of the built-in rules. Thus, unless the InverseTrig->True is manifested in the command, our rule will not fire! To fix this problem, we can re-order the DownValues manually. Here's the technique: In[11]:= {a,b,c,d}[[{3,1,2,4}]] Out[11]= {c, a, b, d} Now we go for it: In[12]:= DownValues[PowerExpand] = DownValues[PowerExpand][[{5,1,2,3,4,6,7}]]; In[13]:= First /@ DownValues[PowerExpand] Out[13]= {Literal[PowerExpand[expr_, opts___Rule] /; With[{it = InverseTrig /. {opts} /. Options[PowerExpand]}\ , Print[in condition, intercept = , intercept\ , , InverseTrig = , it]; intercept && it]], Literal[PowerExpand[System`Private`expr_]], Literal[PowerExpand[System`Private`expr_, {}]], Literal[PowerExpand[System`Private`expr_, {System`Private`a_}]], Literal[PowerExpand[System`Private`expr_, {System`Private`a_, System`Private`r__}]], Literal[PowerExpand[System`Private`expr_, System`Private`a_]], Literal[PowerExpand[System`Private`args___]]} Our rule is now at the top. Note that the condition is evaluated twice below; once successfully, and once unsuccessfully: In[14]:= PowerExpand[Sqrt[a^2]] in condition, intercept = True, InverseTrig = True in body in condition, intercept = False, InverseTrig = True Out[14]= a This is the kind of stuff that rears its ugly head when you start trying to override the behavior of built-in functions. In general, the numerical built-in functions don't have explicit DownValues (they are implemented completely internal to the kernel), but quite a few of the algebraic simplification commands do have external DownValues. You have to assume the worst in all cases. Also, NEVER do the following: Unprotect[foo]; foo[my arguments] := my body (* Damn, I screwed up *) Clear[foo] If you do, you wipe out the built-in rules and the function ceases to do anything. (Guess how I found this out? :-)). If you find yourself in a situation like this, BEFORE doing the Clear, you can either use UnSet to remove your rule or surgically modify the DownValues. AFTER doing the Clear, you either restart the kernel or you scrounge through all of the files in Packages/StartUp looking for those definitions that you wiped out! (Not recommended!) Dave Wagner Principia Consulting (303) 786-8371 princon at csn.net http://www.csn.net/princon