|
2.6.11 発展:評価処理のトレース
Mathematicaの標準評価処理は,ユーザの式の入力を取り込み,式を評価し,評価が完了したら結果を返す,という過程からなっている.本節では,最終的な評価結果だけでなく,中間過程でも評価がどう進行するのかを見ていく.そうすることで,評価処理の理解の助けにもなるだろう.

式評価のトレース
トレースさせると,式1 + 1が計算され,直ちに2が出力されてくる.
In[1]:= Trace[1 + 1]
Out[1]= 
足し算の前に2^3が評価されている.
In[2]:= Trace[2^3 + 4]
Out[2]= 
ベキ項が評価されると,その結果はサブリストの形で出力される.
In[3]:= Trace[2^3 + 4^2 + 1]
Out[3]= 
Trace[expr]を使えば,式exprの評価過程において中間結果として生成されるすべての中間式を列挙させることができる.ただし,もとの式が簡単なものでないと,非常に長いリストが出力されてしまい,トレースを行っても何のことか分からなくなってしまう.
複雑な式をトレースする際は,もう1つの書式,Trace[expr, form]を使った方がよい.パターンformにマッチする中間式だけを選んで出力することができる.
階乗を計算するための帰納的関係を定義しておく.
In[4]:= fac[n_] := n fac[n-1]; fac[1] = 1
Out[4]= 
階乗関数をトレースする.中間式をすべて出力したので,結果は非常に複雑なものになる.
In[5]:= Trace[fac[3]]
Out[5]= 
次に,パターンを加えてfac[n_]の形に準拠する中間式だけを出力する.
In[6]:= Trace[fac[3], fac[n_]]
Out[6]= 
実際,パターンは何でもよい.例えば,これでもよい.
In[7]:= Trace[fac[10], fac[n_/;n > 5]]
Out[7]= 
Trace[expr, form]を使うと,式exprの評価過程で生成されるすべての中間式はいったん判定され,パターンformに合った式だけが抽出される.
例えば,関数facを作ったとして,その関数が呼び出されているときだけトレースを実行したければ,パターンをfac[n_]にしTraceに与えておく.また,パターンをf[n_, 2]としておけば,引数を伴った特殊構成の関数式だけを抽出できるようになる.
Mathematica言語による典型的なプログラムでは,fac[n]等の関数呼出しの式だけでなく,変数の割当てや,制御構造体等のいろいろな要素が登場する.これらの要素はすべて一般的な「式」とみなされるので,トレースに適切なパターン指定さえしておけば,どんな要素でも個別に抽出することができる.例えば,パターンをk = _としておけば,変数kに関するどんな割当て式でも抽出できるようになる.
kに関して生成される割当て式をすべて表示させる.
In[8]:= Trace[(k=2; For[i=1, i<4, i++, k = i/k]; k), k=_]
Out[8]= 
式exprの評価において,そのどの過程で生成された中間式でもTrace[expr, form]を使えば限定抽出することができる.また,抽出すべき中間式は式exprから直接得られるものでなくてもよい.つまり,式の評価処理の一部として関数が呼び出され,その結果生成されるような式であってもよい.
トレースする関数を定義しておく.
In[9]:= h[n_] := (k=n/2; Do[k = i/k, {i, n}]; k)
関数hの評価中に生成される式を追跡 する.
In[10]:= Trace[h[3], k=_]
Out[10]= 
以上のように,トレース機能を使い中間式を追跡することができるが,追跡の対象になる式はユーザ定義の関数だけでなく,一部の組込み関数でもよい.ただし,注意点として組込み関数の場合,Mathematicaのバージョンによってはアルゴリズムの記述や最適化処理が詳細な点で違うため,評価過程の順序が違ってくる場合がある.

トレース機能の限定
関数Traceにより出力される式のリストは, Mathematicaの計算処理の履歴である.中間式は計算処理で実行された順序どおりに並べられる.また,トレース結果のリストは,ほとんどの場合,ネストされた形で出力されるが,これは実際に行われた計算の「構造」を表している.
基本的に,トレース結果であるリストの持つサブリストは,特定のMathematica式の評価チェーン(鎖)を表す.また,サブリストの要素はチェーンに相当し,1つの式が取るいろいろな変換形を表している.ただし,通常は,1つの式を評価するには他の式も評価しなければならないので,実際のトレースでは,従属評価の結果もサブリストに含まれることになる.
割当ての列を定義する.
In[11]:= a[1] = a[2]; a[2] = a[3]; a[3] = a[4]
Out[11]= 
トレースすると,a[i]の変換処理(i = 1〜4)の様子が評価チェーンとして現れる.
In[12]:= Trace[a[1]]
Out[12]= 
y + x + yの簡約化処理で生成される式の形が評価チェーンの連鎖要素として表示される.
In[13]:= Trace[y + x + y]
Out[13]= 
評価チェーンは引数別にあり,中間結果はサブリストの形で表示される.
In[14]:= Trace[f[1 + 1, 2 + 3, 4 + 5]]
Out[14]= 
評価チェーンは,式の要素別にサブリストで表示される.
In[15]:= Trace[x x + y y]
Out[15]= 
ネストされた式をトレースすると,トレース結果のリストもネストされた形で返される.
In[16]:= Trace[f[f[f[1 + 1]]]]
Out[16]= 
あるMathematica式の評価において従属評価を必要とする状況は基本的に2つある.1つは,式が複数の副次的な式からなる場合で,そのとき各副次式は別々に評価される必要がある.もう1つは,式の評価において規則が適用され,その適用時に別の関連式をも評価する必要がある場合である.どちらの場合も,従属評価の結果は,Traceにより返される構造体におけるサブリストとして表される.
従属的評価は,fとgの引数評価から派生する.
In[17]:= Trace[f[g[1 + 1], 2 + 3]]
Out[17]= 
条件付き関数を定義する.
In[18]:= fe[n_] := n + 1 /; EvenQ[n]
fe[6]の評価は,条件に関連した従属評価を伴う.
In[19]:= Trace[fe[6]]
Out[19]= 
再帰的処理を踏む関数の評価をトレースさせると,結果はネストされたリスト形で得られる.これは,評価の対象となる関数は1つでも,反復過程の途中で新たに生成される中間式が評価前の中間式に従属する形で生成されるからである.
例えば,fac[n_] := n fac[n-1]と定義を作り,fac[6]を評価させたとすると,最初の評価では6fac[5]が生成される.ここで,fac[5]は従属評価から派生した式である.
facが繰り返し評価され,中間結果がネストされたサブリストで表示される.
In[20]:= Trace[fac[6], fac[_]]
Out[20]= 
fp[n-1]を直接fp[n]の値とする定義を作っておく.
In[21]:= fp[n_] := fp[n - 1] /; n > 1
fp[n]の形の式は生成されることはないので,トレース結果にサブリストはない.
In[22]:= Trace[fp[6], fp[_]]
Out[22]= 
これは,Fibonacci数の再帰的定義である.
In[23]:= fib[n_] := fib[n - 1] + fib[n - 2]
最終条件を与える.
In[24]:= fib[0] = fib[1] = 1
Out[24]= 
fib[5]の再帰評価における全ステップが表示される.
In[25]:= Trace[fib[5], fib[_]]
Out[25]= 
式の評価過程で生成される中間式は,変換規則が適用された結果発生する.2.5.10で説明したように,定義済みの変換規則はすべて特定のシンボルに直接,もしくは,タグを介して間接的に関連付けられる.Trace[expr, f]を使い,式exprをトレースさせることで,シンボルf に関連付けられている変換規則がどう適用されるかを調べることができる.この場合,Traceは,規則の適用前の中間式だけでなく,適用後の結果である中間式も返す.
Trace[expr, form]を使い式exprをトレースさせると,パターンformにマッチした評価直前の中間式,または,そのパターンにマッチした,使われた規則に関連付けられたタグがすべて抽出される.

特定タグに関連した評価ステップのトレース
fac[_]にマッチする中間式だけを表示させる.
In[26]:= Trace[fac[3], fac[_]]
Out[26]= 
シンボルfacに関連付けられている変換規則を使う評価ステップをすべて表示させる.
In[27]:= Trace[fac[3], fac]
Out[27]= 
log関数の規則を定義する.
In[28]:= log[x_ y_] := log[x] + log[y]
log[a b c d]の評価過程をトレースさせ,logに関連した変換規則をすべて表示させる.
In[29]:= Trace[log[a b c d], log]
Out[29]= 

式の要素で範囲限定したトレース
Trace[expr, form]を使うと,パターンformに合った中間式だけが抽出されるが,トレース範囲としては,式exprのすべての評価ステップが対象になる.場合によっては,トレース範囲を限定して,限られたステップ中だけでトレースを行いたい.
これを行うには,TraceOn -> oformのオプション設定を使う.パターンoformにマッチした要素を持つ中間式のステップに限ってトレースが実行される.反対に,あるステップだけをトレースから排除したければ,TraceOff -> oformとすることで,対象要素を持つ中間式を除いたステップだけをトレースすることができる.
評価過程の全ステップをトレースする.
In[30]:= Trace[log[fac[2] x]]
Out[30]= 
fac関連のステップだけをトレースする.
In[31]:= Trace[log[fac[2] x], TraceOn -> fac]
Out[31]= 
fac関連以外のステップをトレースする.
In[32]:= Trace[log[fac[2] x], TraceOff -> fac]
Out[32]= 

評価中に生成される式への規則の適用
Traceが返すものをfib[5]の評価で使われるfibの引数だけに限定する.
In[33]:= Trace[fib[5], fib[n_] -> n]
Out[33]= 
Trace関数の強力な面として,この関数により返されるリストは,基本的に,他の関数で展開できる標準的なMathematica式である.ただし,リストにおかれた式にはHoldFormがかけられているので,そのままでは入力としては使うことができない.また,注意点として,HoldFormは内部で有効にはなっているが,表示が標準出力表記だと,HoldFormの記述自体は表示に現れない.
評価の全ステップをトレースさせる.
In[34]:= Trace[1 + 3^2]
Out[34]= 
トレースされた中間式にはHoldFormがかけられているため,そのままでは入力には使えない.
In[35]:= Trace[1 + 3^2] // InputForm
Out[35]//InputForm= {{HoldForm[3^2], HoldForm[9]}, HoldForm[1 + 9], HoldForm[10]}
標準の出力表記だと,どのリスト要素が評価結果のものか,または,評価前の式なのかは分かりづらい.
In[36]:= Trace[{1 + 1, 2 + 3}]
Out[36]= 
入力形で見るとはっきりする.
In[37]:= InputForm[%]
Out[37]//InputForm= {{HoldForm[1 + 1], HoldForm[2]}, {HoldForm[2 + 3], HoldForm[5]}, HoldForm[{2, 5}]}
トレースで変換規則を使うと,規則の適用結果がまず評価され,それから,HoldFormが適用される.
In[38]:= Trace[fac[4], fac[n_] -> n + 1]
Out[38]= 
複雑な計算では,トレースで得られるリストもまた複雑な構成になりがちである.Trace[expr, form]を使えば,パターンformに合った中間式だけをリストの要素として抽出することができる.しかし,どんなパターンを使おうが,最終的に得られるリストのネスト構造は変わらない.
fib[3]をトレースさせ,fib[_]のパターンに合った中間式を列挙させる.
In[39]:= Trace[fib[3], fib[_]]
Out[39]= 
今度は,fib[1]関連だけを表示する.リストのネスト構成はfib[_]のリストと同じであることに注目.
In[40]:= Trace[fib[3], fib[1]]
Out[40]= 
TraceDepth -> nのオプション設定をTraceの指定に加えると,トレース可能なネストレベルの上限はレベルnに設定される.そうすることで,長くなる解法において,細かい計算をスキップし,主要ステップだけを追うことができるため,トレースの効率を上げることができる.さらに,オプションTraceDepthとTraceOffを組み合せて使えば,追跡したいステップをさらに絞れるのでトレース作業をさらに速くすることができる.
ネストレベルが2以下のステップだけをトレースさせる.
In[41]:= Trace[fib[3], fib[_], TraceDepth->2]
Out[41]= 

ネストレベルを限定したトレース
Trace[expr, form]を使えば,式の評価で生成される中間式のうちformに合ったものだけを抽出することができる.ただし,この方法だと式だけが列挙され,式の評価から求まる値は通常出力されない.TraceForward -> Trueのオプション指定を加えておけば,評価結果も見ることができる.
fac[_]にマッチする中間式だけでなく,これらの式の評価結果も表示される.
In[42]:= Trace[fac[4], fac[_], TraceForward->True]
Out[42]= 
Trace[expr, form]で抽出されるほとんどの式は評価チェーンの中間ステップで生成される式である.TraceForward -> Trueの指定をした場合は,評価チェーンの最終ステップで得られる式もトレース結果に入れられる.また,指定をTraceForward -> Allとすると,formにマッチした式が評価された後に評価チェーンにおいて生成されるすべての式が抽出される.
TraceForward->Allと指定したので,fac[_]にマッチする式の後に生成される評価チェーンの全要素が抽出される.
In[43]:= Trace[fac[4], fac[_], TraceForward->All]
Out[43]= 
TraceForwardを有効にしておくと,特定の形の式がどう評価されるかを追跡することができるようになる.場合によっては,式がこれからどうなるかより,どうしてこの式になったかを知りたい.そのときは,オプションTraceBackwardを有効にしておく.すると,特定の式が生成される前に評価チェーンがどうだったかを観察することができるようになる.
fac[10]をトレースする.120の値はfac[5]の評価によることが分かる.
In[44]:= Trace[fac[10], 120, TraceBackward->True]
Out[44]= 
120の値に関連した評価チェーンの全様を追跡する.
In[45]:= Trace[fac[10], 120, TraceBackward->All]
Out[45]= 
TraceForwardとTraceBackwardを使い分けると,評価チェーンを昇り降りして評価を追跡することができる.場合によっては,特定の評価チェーンから派生する子チェーンまで追跡したい.これを行うには,TraceAboveを指定する.例えば,TraceAbove -> Trueと設定しておけば,関連するすべての子チェーンの最初と最後における中間式を抽出できるようになる.また,TraceAbove -> Allとしておけば,子チェーンの全ステップにおける式をトレースすることができるようになる.
子チェーンを含む全評価チェーンの最初と最後において,数120を含む式を抽出させる.
In[46]:= Trace[fac[7], 120, TraceAbove->True]
Out[46]= 
fib[5]をトレースする.fib[2]を生成するチェーンにおける全ステップを抽出させる.
In[47]:= Trace[fib[5], fib[2], TraceAbove->True]
Out[47]= 

トレースのリストに抽出されるステップを指定するためのオプション設定
Trace[expr, ... ]の基本動作は次の通りである.まず,式exprの評価の過程で生じる中間式が抽出される.次に,中間式がパターンや設定条件に見合うものかが判断され,そうならば,リスト出力用に内部保持される.このとき,式の持つ関数の引数が評価された後にできた中間式だけが抽出の対象になる.また,TraceOriginal -> Trueの設定が有効なら,引数が評価される前の式の形も対象にされる.
このトレースでは,引数評価の前と後で生成されるfac[_]にマッチした式が抽出される.
In[48]:= Trace[fac[3], fac[_], TraceOriginal -> True]
Out[48]= 
Traceで得られるリストには,通常,非自明的な評価チェーンのステップで生成された式だけが出力される.つまり,評価後と評価前で同じ式が得られるような評価ステップは除外される.ただし,TraceOriginal -> Trueの指定が有効なときはその限りでなく,自明的かどうかにかかわらず評価過程の全ステップが出力される.
Traceが有効である場合,自明的な評価チェーンも含むすべての式がトレースさせる.
In[49]:= Trace[fac[1], TraceOriginal -> True]
Out[49]= 

トレースで使われる補足的なオプション
実行中のプログラムにTraceを使うとき,プログラムにあるかもしれない局所変数を的確にトレースできるだろうか,という疑問が起る.2.7.3で説明するように,モジュール等の構成体ではスコープを限定した局所変数が使えるようになっている.こうして設けられた局所変数にはx$nnnの形式で参照名が付けられる.
そこで,xについてモジュール式をトレースさせると,通常は,xだけでなくx$nnnの名前の局所変数もトレースの対象にされる.つまり,Trace[expr, x = _]と入力すると,もとのプログラムにおいて変数がxの名前で参照されている限り,それが大域定義のものでも局所定義のものでもトレースの対象にされる.

トレースからの局所変数の除外
場合によっては,名前をxとする大域変数だけをトレースの対象としたい.局所変数の記述名がxであってもそれは除外したい.そうするには,MatchLocalNames -> Falseのオプション設定を加えておく.
x$nnn形式の名前を持つ変数すべてに関連した割当て評価をトレースする.
In[50]:= Trace[Module[{x}, x = 5], x = _]
Out[50]= 
今度は,大域変数に限ってトレースする.
In[51]:= Trace[Module[{x}, x = 5], x = _, MatchLocalNames -> False]
Out[51]= 
関数Traceを使うと,まず,式の計算処理が完全に実行され,次に,計算処理の履歴を示す中間式のリストが表示される.特に,計算が長くなる場合は,計算処理の進行に並行した形で随時トレース結果を観測することができると便利である.関数TracePrintを使えばそれが可能になる.この関数は機能上Traceと同じだが,表示の仕方が違う.つまり,Traceのように抽出したすべての式を最後にリストで一括表示するのではなく,中間式が生成されるたびに式の表示を逐一行う.
fib[3]の評価で生成される式を生成時に逐一表示させる.
In[52]:= TracePrint[fib[3], fib[_]]









Out[52]= 
TracePrintにより返される一連の式は,Traceが返すリスト要素の式と同じである.TracePrintの式の表示において,画面左側のインデント幅は式のネストレベルを示している(これは,Traceリストにおけるサブリストのレベルに相当する).TracePrintの動作設定には,Traceの設定で使ったオプションTraceOn,TraceOff,TraceForwardを使うことができる.ただし,TracePrintは順次,式を出力していくので,TraceBackwardの指定は行うことができない.また,TraceOriginalの設定値は実効的に常にTrueとされる.

評価処理をトレースするための関数
fac[10]の評価で,fac[5]が生成されるとダイアログモードが開始する.
In[53]:= TraceDialog[fac[10], fac[5]]

Out[54]= 
ダイアログが始まると,スタックを参照し,現在位置を確認する.
In[54]:= Stack[ ]
Out[55]= 
ダイアログを終了する.戻り値としてfac[10]の最終評価結果が得られる.
In[55]:= Return[ ]

Out[53]= 
TraceDialogを使うと,計算処理を途中でいったん停止させ,停止時の演算環境をもとにMathematicaと対話ができるようになる.評価で一時的に使われる変数の現行値を調べたり,別の値に再設定したりすることができる.ただし,微妙な点で(特に,パターンやモジュール変数等に関連した点で),通常の割当てとは違うので注意が必要である.
TraceDialogのトレース中に一連の式が生成されると関数Dialogが呼び出される(Dialogの機能詳細に関しては2.14.2を参照のこと).Dialogを呼び出すということは,2次的な計算セッションを開始させるということで,Mathematicaとの対話は別の入出力行が使われ進行する.
式exprのトレースで抽出される中間式に任意な関数fを適用したければ,TraceScan[f, expr, ... ]の書式を使う.実際にfの適用がある式にはHoldFormが適用され,評価されないよう式が保護される.
TraceScan[f, expr, ... ]では,中間式が評価される前に関数fが適用される.ここで,TraceScan[f, expr, patt, fp]を使うと,評価の前にはfが適用され,評価の後にはfpが適用されるようになる.
|