※ 説明が難しい関数

[2006/07/16]

 機能は簡単なのだが一言で説明できない関数。単に私のボキャブラリーが乏しいだけなのだが。こんなときほど文科系の方が羨ましくなる時は無い。

 実は mc (matrix calculator) の時から使っていた grEdit 関数の事なんです。MacOSX に移行するとき、これを残すべきかどうか悩みペンディングになっていた関数です。何故に悩んだか、それを説明する前にどんな機能なのかを紹介しましょう。

● 注目する部分のデータが欲しい

 例えば連続して計測した記録データを考えてみて下さい。トレンド図にして眺めると、そのトレンド図のある特定の時間だけ拡大して見たいという場合があり ます。普通、そのような時は selectIf 関数を使い selectIf(data, time>=xxx and time<=yyy); として時間xxxからyyyまでのデータを選択しグラフにします(推薦)。

 例えば下図に人工調節換気中の気道内圧(肺に空気を送り込む時の圧力)と気道内流量(空気の量)を計測した計測結果を示します。横軸は時間(秒)で縦軸 はセンサーから出力された電圧です。実際にはキャリブレーションをし、電圧を気道内圧と気道内流量の単位に変換するのですが、とりあえずこのままで使って みます。

data = getClipData(0);
parametricLinePlot(data[#,1],data[#,3|4])+0;

 黒色が気道内圧で赤色のグラフが気道内流量です。このデータでは大体4回分の呼吸データが含まれているのが判ります。このデータから一呼吸分だけ選びた いとしたらどうしますか? 上で紹介した selectIf 関数を使えば出来ますが、肝心の時間範囲(xxx,yyy)が判りません。こんなとき使えるようにと考えたのが grEdit関数です。

d = grEdit(data,1,4);    ~ dataの第1列をx軸に第4列をy軸にした散布図でデータを選択

parametricLinePlot(d[#,1],d[#,3|4],|4,9,1,-2,1,0.25|);

 これが grEdit関数です。この関数は非常に便利なんですがとても怖い関数なんです。上の黒いグラフにスパイク状のノイズのような波形が見えています。これをノイズと考え grEdit関数で .............

e = grEdit(d,1,3);    ~ dataの第1列をx軸に第3列をy軸にした散布図でデータを選択
parametricLinePlot(e[#,1],e[#,3|4],|4,9,1,-2,1,0.25|);

 grEdit関数を使うとこのような事が出来てしまうのです。実に、まあ奇麗で如何にも実測値のようなグラフができました。これって詐欺です よね。この気道内圧に乗っているスパイク状のノイズらしきものは実は心臓の拍動が肺を圧迫し気道内圧に反映されていたものなのです。ここで使っている調節 換気装置は吸気の流量を一定にするサーボ機構を持ち圧を調節しているので、このような波形になってしまいます(このような現象はあまり無いのですが、いや たまに見れます)。

● 制限する必要は無いのでは

 このような事から、CalendarMemoに移行する時点でこの関数は私意が入るデータが作れてしまうのでペンディングにしていた訳なんです。でも最 近になり、「このような操作は私意は入るが、これをどう使うのかは研究者・使用者が考えるべきであり私が考える事ではない」と思うようになりました。そん な訳でCalendarMemoでもこの機能が使えるようにしました。次回のバージョンアップで利用できるようになります。

 grEdit 関数はマウス操作で領域を決めるのでいつも同じにできる訳ではありません(再現性が無い→科学ではない)。できるなら selectIf 関数を使って下さい。selectIf関数なら何故それが選択されたかが明白で実行には再現性があります。韓国の黄禹錫(ファン・ウソク)前ソウル大教授 のようにPhotoShopを使うのは科学ではありません!

● ぐち!

 このような機能、他のアプリケーションでありますかね? 私の施設の研究者の一人は mc (matrix calculator) を使うメリットはこの機能にあり、この機能がない(CalendarMemo)なら使わないとまで言っていました(Kさん、あんただよ!)。

● おまけ

 この機能、どのようにして実現しているか興味ありますか? 実は簡単でいて奥が深いというか面白いというか、寝る前に考えると睡眠薬のかわりになります。色々面白い失敗もあるので紹介しましょう。

 この機能を実現するに必要なアルゴリズムは、あるデータが閉曲線(ポリゴン)の中にあるのか外にあるのかを解決する事です。一番簡単な方法は Macintoshのシステム自体にこの機能を実現するルーチンがないか探す事です。実際、簡単に見つける事ができました。それは PtInRgn 関数です。これは昔、確かQuickDrawのルーチンで、最近ではQuickTimeのフレームワークで定義されています。リージョンハンドルにポリゴ ンを定義し PtInRgn 関数で指定した点がこのリージョンに入っているか否かを判別できます。このルーチンを使えば簡単に実現できる訳です。実際、 CalendarMemo のgrEdit関数はこのルーチンを使っています。

第一案:PtInRgn には問題あり

 実はこの関数には問題があります。元々、この関数はグラフィック用に考えられたルーチンのようで、指定できる座標は整数だけです。 grEdit関数のようにグラフ上でポリゴンを指定する場合には問題は無いのですが、グラフを使わないで任意の浮動小数点座標を持つデータで同じ事をしよ うとすると色々と面倒な事に巻き込まれまます(region関数)。それにこの機能はMacintoshでしか使えないというハンディもあります。

第二案:ジョルダンの閉曲線定理

 そこで考えたのは、実はこれも私が考えた訳ではなく単に思い出しただけですが、ジョルダンの閉曲線定理です(大学生の時代に読んでいたブルーバックスに 書いていた事を思い出した)。インターネットで調べると簡単にヒットするので興味があれば探してみて下さい。ウィキペディアでは「ジョルダン曲線定理」で 載っています。難しすぎて私には全ては理解できませんが、「内と外が定義されているとき内から外に向かう場合には必ずその境界を通らなければならない」と いう事のようです。簡単に言えば内から一回境界をまたげば外になり、内から二回境界をまたげばそこはまだ内である。要は確実に外と判っている点から調べた い点まで、境界をまたいだ回数が奇数であればその点は閉曲線の内側に有り、偶数であれば外側にある、ということです。これ、絵を描けば簡単に理解できま す。

 赤い点から外に向かう直線を考えたとき、境界をまたぐ回数は9回なので奇数。すなわち赤い点はポリゴンの内部にあるということが判ります。この原理を使えば指定した点が閉曲線の内部に有るか否かが判定できます。

現実は厳しい

 原理は判ったが、実際にこの機能を実現するには色々と問題が有ります。その一つは指定する閉曲線は実際には多角形:ポリゴンであり、それには頂点という ものがあるからです。たまたま引いた直線がポリゴンの頂点と接触していたら一回と数えるのでしょうか、それとも二回と数えるのでしょうか。

 この図で多角形の内にある赤い点の直線は頂点で接続しているので一回と数えればいいのでしょうか。でも青い点は外にあるのに頂点には一回しか接続してい ないことになるのでこのルールから考えると青い点も内にあることになってしまいます。また黄色の点は多角形の一つの辺に平行になっています。これは何回と 数えるのでしょうか。これらを全て満たすようなルールはあるのでしょうか。

 残念ながら私には解決できませんでした。たぶんあるのでしょうね。これを考えるのは非常に面白いと思います。でも、私は考えるより先にこの機能を実現しなければならないという現実的な目的があります(アカデミック?な興味より実際問題ですね)。

 そこでこのような特異な状況は果たして普通に頻繁に起こりえるのだろうかという疑問がありました。無限とも言える実数の世界でたまたま多角形の頂点に接 続するようなことが頻繁に発生するのか。私はたぶん発生しないだろうと考えました。また仮に頂点に接続するような事があっても上記図の[X]にあたる点を 少し移動するだけで多角形の頂点に接続する事は無くなるはずです。なにせ点[X]は外部に位置すればどこでも良いのですから。

 そんな訳で滅多に起こらない現象がもし発生しても外部点に相当する[X]を移動することで解決するようにしました。実際に乱数で10万点の点を作り発生するかを何回か調べてみました。思った通り何回か試してもいまだ発生した事はありません。ではその例を

data = nRand(|100000,2,1,0|);
r = |-1,-1||0,0||1,-1||0.5,0||0,-0.5||1,1||0,0||-1,1|;
a = region(r,data);
pointMode(1);
parametricPointPlot(a[#,1],a[#,2]);

第三案:ポリゴンの面積→失敗

 実はもう一つアイデアがありました。ジョルダンの閉曲線定理では確実に外部、または内部が判っている点[X]が必要です。これはちょっとエレガントでないな〜と思い、別の方法が無いか考えてみました。

 それは面積を利用する方法です。閉曲線:多角 形の面積を計算します。次にその多角形に調べたい点を追加した新しい多角形の面積を計算します。もし調べたい点が多角形の内部にあれば元の多角形の面積よ り小さくなるはずです。逆に調べたい点が多角形の外部にあれば元の多角形の面積より大きくなるはずです。この原理を使えばジョルダンの閉曲線定理を使わなくても点が内部に有るか否かが判るはず。

 考えは悪くはないと思うのですが、調べる点を多角形の何処の辺に追加するのか色々(最短にある2つの頂点の間)と試してみたのですが何故か上手く実行できません。特に上記グラフのように閉曲線が交わった場合には全然だめでした。何か良い方法があるとは思うのですが。誰か考えて見てくれませんかね〜


面積を使って試した例。領域は |-1,-1||0,0||1,-1||1,1||0,0||-1,1|です。

 変なゴミが楕円状に偏在しているのが判ります。このゴミ点は確実に外部点、何処かバグがあるようです。考えは悪くないと思うのですが。

● 本題は

 このメモを書いているうち、本題の「説明が難しい関数」から脱線してしまいました。実はここで紹介した grEdit 関数ですが、関数名を付けるとき悩みました。関数名は、名前でその機能が類推できるようにしたほうが良いと思っているのですが。肝心のその機能を簡単に説 明できる言葉が出てこないのです。これには困りました。いまだに困っています。で、 grEditという名前にしたおですが。この命名は何も考えず適当に付けたので意味は全く有りません。たまたまプログラムを作る時に適当に書いた関数の識 別子がそのまま関数名になっただけです。

 誰かこの関数に良い名前を付けてくれませんか!


Nishimura Hiromi