Manipulateの高度な機能
自動再評価の制御 | Manipulate内部でのDynamicの使用 |
Manipulateのネスト | コントロールエリアでのDynamicオブジェクト |
相互制御 | コントロールエリアでのコントロールの任意のレイアウト |
遅い評価の対処 | コントロール外観のカスタム化 |
このチュートリアルでは,Manipulateコマンドの上級機能を扱う.読者はすでに「Manipulateチュートリアル」を読んでおり,コマンドの使用目的や概してどのように働くかが分かっているものと仮定する.
また,このチュートリアルは「Dynamicチュートリアル」,「Dynamicの高度な機能」で論じている低レベルの動的メカニズムが既知であるとして話を進める.
Manipulateの例題の中には,スライダーが動いていなくてもコンテンツが継続的に再評価され「回転」するものがある.この動作が意図したものである場合もある.例えばこの垂れ下がった三角形は,常に中心が垂れている.スライダーを使うと垂れた部分を引き上げることができるが,動かすのをやめると再び垂れてくる.
これが起るのは,変数yが最初の引数のコードにより変更されると,yの値が変更されたからコンテンツを再評価して表示する必要があるということをシステムが正確に感知してくれるためである.それが再びyの値を変更する,ということが延々と続き,この場合は安定点のy=-1に達するまで続く.yの値がもう変化しないところにきたら,再びスライダーを動かすまでコンテンツは継続的な再描画を実行しない.システムのCPU動作モニターがあれば,三角形が垂れ下がる動作の最中ではWolframシステムはCPU時間を使っているが,下まで達するとWolframシステムのCPU使用はなくなることが検証できる.
もちろん中止することのない例題を構築することは可能である.ここではゼロに初期化された2つの変数を宣言し,その一方の値に基づいて他方の値が連続的に更新されるように,Manipulateのボディにコードを入れてみる.
刻み幅がゼロを離れると常にコンテンツエリアは連続的に更新されCPUモニターにはWolframシステムのCPU時間の使用が表示される.これはいつまでも続く.これはCPU時間を完全に使い果たすものではないので,フロントエンドの他のタスクはこれにより妨害されることはない.このプログラムが実行中でも編集したり評価したりすることはできる.これはWebブラウザで起動している動画あるいはアプレットのようなものだと考えることができる.
しかし,連続的な再評価が意味のない意図せぬものである場合もある.次のやや不自然にも思えるような例を考えてみる.この例は,スクリーン上にある(開いたウィンドウ内にあり,完全に見えなくなるようにスクロールオフされていない)ときは常に,連続的に再評価し,何も変更がなくてもCPU時間を使う.
これは,nの値が変更されていなくても(毎回2つの異なる値に再設定されるということ),評価中に変数tempの値が変更されるから起るのである.この回転は無意味である.というのは,tempの値が使われる前に設定されるからである.
意味のない回転は,以下の例のようにManipulateのボディに関数定義または複雑な割当てをすることでも生じる.
どちらの場合も,問題の変数をModuleの内部の局所変数にすることで解決できる.これは,意味のない更新を避けようとすることを別にしても,よいプログラミングのやり方である.
Moduleの局所変数にどのような操作を行っても,その操作により値が更新されることはない.というのは,値が1回起動されると,次の起動まで生き残らないというということがModuleの定義の一部だからである.従って,現在のサイクルで局所変数の値に施されたことがあるからといって,結果が次回は異なることはない.
別の解決方法は,ManipulateでTrackedSymbolsオプションを使い,どの変数が更新動作を起こすことができるようにするかを制御することである.デフォルト値のFullは,最初の引数に明示的(語彙的)に現れるシンボルが追跡されるということを意味する.つまり,Manipulateの例題で使用する関数定義の内部の一時変数の問題や他の類似した問題は,第1引数に明示的に現れることはなく,呼び出す関数を通して間接的に現れるため,無限に再評価されるという問題を生じないということである.
先ほどの2つ目の例を使って,もしfを局所的なModule変数にしたくなく,その定義をManipulateの外側に動かすことができない(もっと複雑な場合は,これら両方の状態が起る理由がある)ならば,TrackedSymbolsを使いfにより引き起される更新を無効にすることができる.
この例では,スライダーを動かした結果としてnが変更されたときだけ,内容エリアの内容が更新される.
指定された動的式が厳密にいつ更新されるかという問題は複雑であり,2つのチュートリアル(「Dynamicチュートリアル」および「Dynamicの高度な機能」)で取り上げられている.これらのチュートリアルを読むとき,ManipulateはDynamic内の第1引数をラップして,そのTrackedSymbolsオプションの値を,その中のRefreshに渡すだけだということを覚えておく必要がある.更新に関することはすべてDynamicとRefreshが行うのである.
Manipulateをどのように深くネストすることもできるし,それで動作もするが,それが最も便利な機能とはいえない.1度ネストすることで,実際にパラメータ化されたユーザインターフェース構築インターフェースを作成したことになる.外側のManipulateにより,内側のManipulateにより提示されたユーザインターフェースを決定するパラメータが制御できる.上の例よりもわずかに複雑なプログラミングを使うと,驚くようなことができる.
Manipulateの1つのスライダーの範囲を,別のスライダーの位置に依存するようにすることができる.例えば,関数Binomial[n,m]はm<=nのときのみ意味をなすので,範囲が1からnの現在の値までであるmスライダーを作った方がよいかもしれない.これは,下のようにmに対する変数指定の中にnを使って実行することができる.
両方のスライダーを右に向かっていくらか動かし,nスライダーを左に動かすと,mスライダーは自動的に右に動く.これは最大値が小さくなっているためである.nをもっと左に動かし,現行のmの値よりも小さくなる点まで行くと,mスライダーは許されている最大値よりも大きくなるので,「範囲外」を示す赤い部分が表示される.
最大値が現行の値よりも低く設定してあるときに,mはなぜ現行の最大値に自動的に再設定されないのだろうか.それは,値をそのままにしておいた方がよい場合もあり,自動的に再設定したければ,手動で簡単にできる.例えば,第1引数のコードにIf文を加えるとよい.
一般的に,Manipulate変数を他の変数の定義の中で制約なしに使うことができる.しかし,このように役に立つというよりもややこしい奇妙な相互作用を生成することもある.
以下の例は,スライダーの範囲を制御するチェックボックスを使った,別の変形である.このようなものは細かい進行範囲を提供したい場合等に便利である.
Manipulateは,スライダーが動くことで到達する可能な出力値すべてを前計算しない.そのようなことは非常にわずかな場合を除いて大変非実践的である.つまりManipulateは,各スライダーがドラッグされるたびに現行の値を計算し,フォーマットし,表示しなければならい. 使用中のコンピュータがどんなに速くても,限られた時間内に計算できる量には限度があり,Manipulateの第1引数に使っている式の評価に1秒以上かかるならば,Manipulateの使用に満足できないであろう.
非常に面白くパワフルな計算の多くが1秒以内で実行でき,コンピュータが速くなるに従って範囲は増加の一途をたどる(人間はこれ以上速くはならないので,計算が遅すぎるように思える例題に使用できる時間は,これかもらしばらくは変わらないはずである).しかし,計算の中にはそれほど速くできないものもあり,それをManipulate内部で使用したい場合は修正が必要となる.遅い評価に対処するよい方法がいくつかある.
このセクションでは,Pauseを使って遅い評価をシミュレートしてみる.その理由は,実際の計算はそれぞれのユーザのコンピュータでさまざまな速さで実行されるので,ひとつの例で要点を例示するのが難しいからである.Pauseコマンドがある部分では,非常に難しく,また面白いことが起っており,その結果細部にわたるすばらしい出力が得られるものと想像していただきたい.
問題の趣旨を理解するために,下のスライダーをドラッグしてみる.この例題は許されないものではないが,ほとんど操作する価値のないものにかなり近い.遅延が数秒にまで増えると,非常に意味のないものになる(5秒を超えると,数の代りに$Abortedが表示される.これは,システムが意味のない長い評価を避けようとするためであり,この場合フロントエンドの活動もできなくなるからである).
最も簡単な修正方法は,ManipulateにオプションContinuousAction->Falseを加えるというものである.
この例では,スライダーはドラッグされるたびに即座にスムーズに動く.しかし出力エリアの値はリアルタイムで動かない.スライダーが解放されたときだけに更新される.
これよりも細かい相違点であるが,この例で値が更新されるとき,フロントエンドの他の活動を阻止しない.スライダーが解放されるたびにセルブラケットが数秒ハイライトされ,その間にでもフロントエンドでのタイプやその他の動作が継続できる.このようなブロックされない評価には5秒制限はないので,ContinuousAction->Falseオプションを使うと任意に長い評価を使うことができる(しかし1分もかかるような評価は,Manipulate内部で行うよりも通常のShift+Return評価にした方がよいであろう).
より高度な方法に,ControlActive関数を使い,スライダーがドラッグされている間にもっと簡単で早い表示を使用し,スライダーが解放されたときだけ長い計算を実行するというものがある.
ControlActiveは引数を2つ取る.最初の引数は,コントロール(スライダー等)がマウスでドラッグされている間に式が評価されると返される.2つ目の引数は現在アクティブなコントロールがない場合に返される(厳密にいつどちらの引数が返されるかは,ControlActiveのドキュメントを参照のこと).
この例では,スライダーがドラッグされている間のプレビューとして,周囲にボックスを持ち,1秒遅延するxだけを使う.スライダーが解放されると最終表示が与えられる.ここでは上の例題で使用したContinuousAction->Falseオプションは使わない.
セルブラケットはスライダーが解放されたときだけにアウトライン(ブロックしない評価を意味する)される.スライダーがドラッグされている間は,評価は他の動作をブロックし,インタラクティブなパフォーマンスを最大限にしている.
以下はControlActiveが使えるもっと現実的な例である.以下の例は,DensityPlotのデフォルト動作ではスライダーがドラッグされている間に使われるサンプル点の方が少ないことを示している.
しかし,スライダーが解放された後に使用されるより多い数でさえも,満足のいくプロットを生成するには十分とはいえない.固定された多数のプロット点を使うと,結果は美しくはなるが,インタラクティブなパフォーマンスはあまりよくはない.
(PlotPointsだけでなく他のオプションにもデフォルトでControlActiveに依存するものがあるので,アクティブ形式と非アクティブ形式にはまだ違いがある.)
スピードと質の最高の組合せは,PlotPointsオプションの値で明示的にControlActiveを使うことで達成することができる.
その結果の例題は,荒削りではあるが実質的に即時にグラフィックスのプレビューを表示し,スライダーが解放されたときに何秒もかけて高解像度のグラフィックスを構築する例である.
次のセクションでは,パラメータの変更で遅い評価が必要な部分もあればずっと速く表示が更新できる部分もある,という場合に使えるかなり複雑な解決策を説明する.
「Dynamicチュートリアル」には,このチュートリアルでは説明されていない明示的なDynamic式の使用について言及されているので,このセクションを終了する前に読んでおいた方がよい.
Manipulateの中のスライダー(あるいは他のコントロール)を動かすと,第1引数で与えられた式が各新しいパラメータ値に対して最初から再評価される.「遅い評価の対処」では,この第1引数の評価が遅すぎてManipulateのスムーズでインタラクティブなパフォーマンスが不可能な場合にどうすればよいかという一般的な事項を説明している.しかし,評価を遅い部分と速い部分とに分け,それによりずっとよいパフォーマンスを得ることができる場合もある.
1つのスライダーが3Dプロットの内容を制御し,もうひとつがそのビューポイントを制御する以下の例を考えてみる.
nスライダーが動かされると,3Dプロットの形が実際に変化するため,関数を再計算することが必要であるということは明らかである.プロットはスライダーがドラッグされている間はぎざぎざになり,解放するとすぐにきれいになる.この動作は正しく,予想された通りのものである.これとは異なり,vスライダーが動かされると,ビューポイントが変化するだけなので,関数を再計算する必要はない.しかし,Manipulateにはこのことが分からない(また,もっと複雑な場合,自動的にこのような判別を行うことは純粋に不可能である)ため,vが変化するたびにプロット全体が最初から再生成される.
これでvスライダーが動いてもプロットはぎざぎざには戻らず,前よりも速く回転するようになった.これはプロットが各動きにより再生成されなくなったためである.
なぜこのような動作が可能かを正確に説明するためには,「Dynamicチュートリアル」および「Dynamicの高度な機能」で説明してあるDynamicメカニズムの内部を理解する必要がある.要するに,Manipulateは常に第1引数で与えられた式をDynamicでラップし,第1引数に使われる変数の変更は通常そのDynamicの更新を引き起す.しかし,変数がManipulateにより暗示的に生成されたDynamic内部にネストされた明示的なDynamicの内部にしかない場合,Dynamicは外側のDynamicの更新は引き起さずそれらが含まれている内側のDynamicの更新を引き起す.
Manipulate内部で明示的にDynamicを使うことでできることをすべて説明することはこのドキュメントの趣旨に反するが,計算の遅い部分に入力変数のいくつかだけがかかわっている場合を見ておく価値はあろう.
次の例では,大きい数値の表を構築する.この場合はRandomRealを使うが,実際にはもっと複雑で遅い計算であったり,ネットワークからの外部データの読込みを含むものであったりすることさえあるかもしれない.データを構築した後,かなり単純で高速な関数を使い,それを表示する(ここでは単に座標値をベキ乗することで例示している).
nスライダーが動かされると,毎回新しい乱数集合が生成されるので,点の数が変化し飛び跳ね回る.しかしpスライダーが動かされると,更新はスムーズで点は飛び回らない.これはpの使用をラップする内側のDynamicが第1引数全体を再評価されないようにしているからである.したがって,新しい乱数点は生成されず,既存の点の提示だけが更新されるのである.
オプションSynchronousUpdating->Falseを使うと,外側のDynamic(Manipulateにより暗示的に生成されたもの)が非同期に更新されるようになる(nスライダーが動いたときにセルブラケットがアウトラインされるので分かる). 非同期の更新はそれほどスムーズなものではないが,評価に時間がかかる場合でもフロントエンドの活動をブロックしない.
内側のDynamicではデフォルトの同期の更新が使われるため,pスライダーが動かされたときの更新はスムーズで速い.
ここで示したテクニックを使って,ひとつのスライダーが変化したときの反応に数秒あるいは数分かかっても,長い計算の反復を必要としないほかのコントロールが変化したときに敏速でインタラクティブなパフォーマンスが維持できるような例題を作成するとよい.
Manipulate内部でDynamicを使って,出力がManipulateのコントロール変数の値以外のものに動的に反応するようにすることもできる.次は,既出のセクションからの例であるが,異なる点は現在のマウス位置に動的に反応するようにした点である.
マウスがプロットの領域上にあると,線の中心が(クリックなしで)それに従う.詳細はMousePositionのドキュメントを参照のこと.
ここでManipulateだけがWolfram言語でインタラクティブなユーザインターフェースを生成する方法ではないことを覚えておいていただきたい.Manipulateは非常に高いレベルのユーザインターフェースを定義するための簡単でパワフルなツールであることを意図したものである.しかし,コントロールのレイアウト,更新の動作,外部システムとのインタラクションという点でその限界に達したとき,DynamicやEventHandler等の関数を使い,低レベルのインターフェースプログラミングへと低下させることが常に可能(そして通常あまり難しくない)である.
「Manipulateチュートリアル」で述べたが,以下の例題のようにタイトルやデリミタ等の多様な要素をManipulateのコントロールエリアに加えることができる.
実際のところ,コントロールエリアに置くことのできるものに制約はない.任意のフォーマッティングコンストラクトや動的オブジェクトの他,Manipulateのコントロール指定の一部ではないコントロールでさえ置くことができる.変数列に置かれる文字列,あるいは頭部Dynamic,Style,ExpressionCellを持つものは何でも,コントロールエリアに挿入される注釈であると自動的に解釈される.
「Dynamicチュートリアル」には,このチュートリアルでは説明されていない明示的なDynamic式の使い方が記載してあるので,このセクションを終える前に読んでおいた方がよい.
実際この提示にはよい点がたくさんある.しかし,今度はリサージュ図形自体を大きく目立つように提示したメイン出力エリアをそのままにして,それぞれの正弦関数を各方向のコントロールに関連付けてずっと小さく表示したいとしよう.このためには,以下のように動的プロットオブジェクトをコントロールエリアに置く.
このセクションの最初の例題のヘッディングが,単に変数の指定列にコントロールをリストすることによりコントロールに統合されたように,ここでは変数の指定列に動的に更新されるプロットを置いた.Dynamicはこれらの部分プロットでは明示的に使われるので,コントロールが動かされたときに更新される(メイン出力エリアには明示的なDynamicは必要ない.Manipulateが自動的に第1引数をDynamicでラップするからである).
ここでこのような例がきちんと動作する理由を述べたい.これは,Manipulateの出力がコントロールの集合と単独の出力エリアとを接続するだけの特別な固定されたオブジェクトではないからである.代りにManipulateの出力は,「Dynamicチュートリアル」で説明したテクニックを使って低レベルでアクセスできる同じフォーマッティング,レイアウト,ユーザインターフェース,動的インタラクティビティを使い積み上げられる(これには手動でManipulateを簡単に積み上げる方法の例が含まれている).ある意味で,Manipulateと低レベルインタラクティブ機能との関係は,PlotとGraphicsの関係に似ている.高レベルPlotコマンドの評価の結果は低レベルのGraphicsオブジェクトであり,Plotで指定のグラフィックスが生成できないならば,直接Graphicsを使うことができる.Plotに任意のグラフィカル要素を加えるためにPrologとEpilogを使うことができる.さらにPlotを使って得られるグラフィカル出力でGraphicsを使って得られないものはない.Plotには低レベルでは利用できないようなWolframシステムの機能への特別なアクセスはない.
同様に,Manipulateにも低レベル関数で利用できないような関数への特別なアクセスはない.DynamicでできないがManipulateではできる,というようなことなどないのである.Manipulateの方が高レベルで,あるスタイルのインターフェースの構築に便利なだけである.
上の例のように,コントロールラベルで動的オブジェクトを使うとき,Manipulateコマンドの出力を構成するすでにかなり複雑なPanelオブジェクト,Gridオブジェクト,Dynamicオブジェクト,DynamicModuleオブジェクトの集合体に,あといくつかのDynamicオブジェクトを加えることになる.本当に異なることはないので,新しい動的要素が他の動的要素とスムーズに相互運用されても驚くことはない.
これが常に賢明であるとは限らないが,Manipulateのコントロールエリアだけで完全にインタラクティブな動的ユーザインターフェースを構築することが可能である.
前のセクションで,Manipulateのコントロールエリアに任意の動的コンテンツを加える方法について説明したが,それでもこれまでのコントロールエリア内のレイアウトはすべて簡単なものであった.それは,ControlPlacementオプションでは各コントロールあるいはアニメーションのどちら側かしか選べないからである.配置を決めたら,コントロールと注釈は単純にその列に収まるのである.
デフォルトのレイアウトが簡単であるということは,Manipulateの重要な設計原理である.Manipulateを使うことで,コントロールや注釈の細かい配置といったような,インターフェースプログラマーが通常頭を抱えるような事柄をほとんど無視し,インターフェースの高レベルの記述を与え,利用可能なインターフェースを迅速に作成することに集中できるのである.
このようにサポートされる関数はStyleだけではない.Wolfram言語のレイアウトおよびスタイルのコンストラクトはManipulateの引数として直接サポートされる.Grid,Row,Framed,Item,Panel,Text等のレイアウト関数も,TabView,OpenerView,PaneSelector,その他のビュー関連のコンストラクトと同様にサポートされている.このような便利なオブジェクトすべてに関する情報は「レイアウトと表」で見ることができる.
Manipulateのコントロールでの任意のレイアウトを考えるときに理解しておかなければならない別のコンストラクトがControlである.Controlは変数やコントロールを指定するためのコンパクトなManipulateシンタックスを使いながらも,Manipulateでのこれらのレイアウトコンストラクトが使えるようにしてくれる.
次の簡単な形式では,Controlラッパーは完全に冗長なものに見える.
実際,この場合はControlラッパーはオプショナルである.これら2つのインターフェースの動作および表示には何の差もない.ここでControlの引数が局所的変数とその領域を含む見慣れたリストであることに注目されたい.これはよく使われるManipulateの変数と同じである.
しかし,Controlの本当に便利なところは,Manipulateの引数の内部どこででも使えるという点である.上で出たレイアウトの内部とかスタイルコンストラクトの内部でも使えるのである.コントロール一式を縦ではなく横にしたい場合,Control式のリストを含むRowを構築して実行することができる.そしてそれぞれのControl式はそのコントロールに対するコンパクトで高レベルな指定を使い続けるのである.
Controlラッパーはこの場合唯一のシンタックスシュガーというわけではない.これらのラッパーがなければ,Manipulateは{u,{True,False}}がコントロール指定であるということが分からないであろう.また,Row内部に{u,{True,False}}が見付かった場合,ManipulateはRowの他の要素のようにただそれを評価して表示するだけであり,u はManipulateの局所的変数ではなく大域的変数として扱われていたであろう.
もとの例題に戻り,OpenerViewを使ってみる.これはColumnおよびColumnとともに,コントロールの集合をグループ化したり隠したりするために使われる.また,プロットの軸と大きさを調整するための一列のコントロールも加えられる.
作業後のコードは,もとと比べると2倍以上の大きさになっている.これではもはやインターフェースの軽量で高レベルな記述とはいえない.これは通常のManipulateなら完全に無視するような詳細を指定している.このようにすることで必ずその犠牲になるものがあるのである.
実際に,インターフェースのレイアウトの必要性を考えるとき,このような犠牲も十分に考慮する必要がある.ManipulateでControlおよび他のレイアウトコンストラクトを使う方が,一からDynamicインターフェースを構築するよりも便利であるかもしれない.あるいは,カスタムインターフェースを構築する際でも,コントロールの作成のためにこのコンパクトなシンタックスを使うために,Manipulateの外でControlを使うのが便利なことすらあるかもしれない.
このセクションには,このチュートリアルでは説明していない明示的なDynamic式の使用を扱うので,「Dynamicチュートリアル」を読んでおくことをお勧めする.
グラフィックスとダイナミックスを使って自分で構築したコントロール等,Manipulateでサポートされない種類のコントロールが使いたいとする.以下はつまみの位置で値を表示するカスタムスタイルのスライダーを定義するコードである.これは正しい位置で所望の要素を描画する詳細を超えるようなかなり難しいものではないが,このコードがどのように動作するかが詳しく分からなくても心配する必要はない.
以下がこの新しいコントロールの外観である.通常のスライダーのように,どこかをクリックしてつまみを動かす.
Manipulateでカスタムコントロールを使用するためには,変数指定の一部としてコントロールオブジェクトを生成するために使われる純関数を使う.この関数が,変数(Dynamic内部の)を第1引数とし,範囲を第2引数として,すべての組込みコントロール関数で使用される慣例に従っていれば,その関数名を使うことができ,Manipulateにより適切な引数が自動的にそれに渡される.ここで,簡単なManipulateで使われるカスタムコントロールを見てみる.
(##という表記は,第1引数だけでなくすべての引数が関数に渡されることを意味する.)
純関数に必要な情報を与えると,変数指定の一部として最大最小を指定する必要はない.
しかし,最大最小を指定すると,Manipulateはスライダーで使うよう選択した範囲を認識しない.これは非常に優れた自動起動機能(Manipulateのドキュメントを参照)が動作できないということである.したがって,一般に変数指定に範囲を入れ,コントロール関数がそれを継承するようにした方がよい.
標準コントロールとカスタムコントロールを自由に組み合わせることももちろん可能である.ここでは新しい2つのスライダーとManipulateにより自動的に与えられるSetterBarを一緒に使う.
また,カスタムコントロールとコントロールエリアの他の動的要素を組み合せること(前セクションで説明)もできる.
この例題は,複雑なインターフェースを作成するために,どれだけManipulateを使うことができるかを示している.しかしManipulateだけがWolfram言語でインターフェースを作成する方法ではないことを覚えておくことが大切である.「Dynamicチュートリアル」には,Manipulateにより提供されるモデルに限らず,自由形式のインターフェースを作成する方法についての追加情報および例題が記載されている.
Manipulate内部でこのようにインターフェースを構築するよい点のひとつは,理にかなった補間パターンにより各変数を変化させ,例題をそのペースで試す自動実行(パネルの右上隅のプラスアイコンをクリックし,自動実行を選ぶ)が使えるという点である.
その一方,Manipulateではレイアウトと動作が制限される.これは非常に柔軟で拡張可能であるが,「Dynamicチュートリアル」で記述されている低レベル機能を使ってできることに比べれば,それでも限られている.