−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
   応用数学II                         1995.12.14/15
   17.実践(3)−幾何学IIの二回目              飯島
        −ちょっと頑張って「構造体」も使っちゃおうか−
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

17−1 はじめに

17−2 問題は具体的に

 繰り返していることかもしれませんが,漫然と,                  何かないかな なんて考えていても何も進みません。出発点とする問題はできるだけ具体的でなければい けません。前回も幾何学IIの中から素材を取りましたが,今回も,それを例としてみまし ょう。たとえば,      テキストのp.24の4(1)  つまり,次の問題が分からないとします。(すぐに分かる人は少ないと思う)。 ・下図アの正方形ABCDが背景写像でイのA'B'C'D'に移ったという。線分EF,GH(各辺の中点  を結んだもの) の像を定規のみで描くにはどうしたらいいか?  これを何とかしたいというところに,具体的に問題を絞るわけです。最終レポートの問 題に関しても同様です。たとえば,○○の教科書のこの問題について考えたいというよう な考え方の方が,作業がスムーズに進展します。

17−3 取りかかる前に問題を少し広げてみる −一般的な定式化−

                 しかし,問題が見つかったからといって,何も考えず,それに飛びついてはいけません 。数学の問題としての取り組む場合もそうですが,プログラミングの場合にも,センスが 重要です。下手をすると,          この問題にしか使えない本当に特殊なプログラム しか得られない可能性があります。もちろん,それでも,何とか元の問題が解ければいい ですが,多くの場合は,        少し一般化して定式化した方が解きやすくなることが多い のです。また,多くの場合は,        元の問題が解けたら,関連問題まで手を伸ばしたくなる のが人間の性ですが,そのためには,              プログラムも少し大きくなる のが普通です。そういうときに,      見通しがいいプログラミングをしておかないと,どうにもならない ことになるわけで,まあ,そういういろいろな面からも,「定式化」は重要なわけです。

17−4 これまでの工夫・今回の工夫 −プログラミングの観点から−

               さて,プログラミングという観点から見ると,これまで,                  プロシージャ つまり,SUB,FUNCTIONを使ってきました。これも重要な概念であり,中規模以上のプログ ラミングには不可欠な概念です。  今回,新しく導入しようとする概念は,                   構造体 という概念です。(BASIC 方言では,「ユーザー定義型」と呼ばれる。構造体という言葉 はC などで使われるが,この概念の方が,言語全般に通用する言葉なので,これを以下で は使うことにする。)  見た目で言えば,           構造体とは,データをまとめたものである ということですが,もっと基礎にあるねらいとしては,  扱う対象そのものを,一つのまとまりとして扱うことによって,対象そのものを扱って いるような気持ちでプログラミングをすることができるような雰囲気にする。 とでもいいましょうか,まあ,そういうことです。  最近はやりの言葉で言うと,「オブジェクト指向」という流れの出発点になるものなん だけど,オブジェクト指向そのものというようも,「その匂いだけでも嗅いでみてね」と いう程度ですけどね。  説明は,次週にでもすることにして,今のところは,とりあえず,以下のコードを入力 し,使いながら,その背景にある思考の進め方を掴んでみてください。

17−5 射影平面の点

 普通の点であれば,座標(x,y)を使います。そして,たとえば,この点を表示する には, PSET(x,y) を使います。射影平面は無限遠点を追加しただけと言えば そうですが,無限遠点をどう扱うかを「特別扱い」しなければいけません。  また,射影変換は  (x,y)→((a1 x+a2 y+a3 /c1 +c2 y+c3 ),         (b1 x+b2 y+b3 /c1 +c2 y+c3 )) として表現することができますが,行列を使った方が見通しよく扱えるのは,「幾何学II 」の7章にある通りです。そして,それらをさらに見通しよく表現するためには,射影平 面の点は,3次元の点を同一視したものとして考えることが妥当であり,その位置の表示 には,「斉次座標」が便利というのは,10章で学ぶ内容です。  ということで,授業の流れで言うと,「まだこれからのこと」なのですが,取り敢えず         射影平面の点は[a1 ,a2 ,a3 ]で表すと便利 ということを押さえておきましょう。そして,           +−a11 a12 a13−+ 射影変換は,行列A=| a21 a22 a23 | を使って,通常の行列の掛け算により           +−a31 a32 a33−+                 φ(P)=AP と表すことができることを押さえておきましょう。  これを,プログラミング上で,どう扱ったらいいでしょうか。一つの方法は,                 点を配列で表す という方法です。つまり,  DIM P(3) と宣言し,これをそのようにして扱っていくという方法です。これも,一つの方法ではあ るのですが,配列というのは,いろいろな目的に使われます。特に,3次元空間の点と間 違えやすいと言えます。これに対して,例えば,  宣言  P (射影平面の点) というような使い方をすると,「射影平面の点」と明確に表示していることによって,そ れを意識化しやすくなりますし,誤解を生む危険性も減少します。それを具体的には,次 のような手続きで行います。  変数の型を定義するとき,  TYPE PrPoint2         ← 名前は自由でいい。     x as single      −+     y as single       |それぞれの要素として,x,y,z という名のを,実数     z as single      −+3つを使うことを定義した。  END TYPE  具体的な変数を作るとき,  DIM P AS PrPoint2       ← 射影平面の点としてPを定義する。  SUB などの作り方  SUB *** (P AS PrPoint2,.....) ← 入出力に射影平面の点を使うと明示する。  SUB などの使い方  (1) 射影平面の点としてまとめたまま使いたい場合  CALL *** (P,..)  (2) x 座標だけを実数変数として使いたいとき  z=P.x + P.y          ← P.x で,「P という変数のx という要素」                   を指す。

17.6 下準備

 まずは,  ・射影平面の点の定義  ・射影平面の点の描画  ・射影平面の点の射影変換 を構成しましょう。 TYPE PrPoint2   x AS SINGLE   y AS SINGLE   z AS SINGLE END TYPE COMMON SHARED pi, MaxOfX, MaxOfY       ←−−−こう宣言すると,これらの pi = ATN(1) * 4                   変数はプログラムのどこでも DIM a(3, 3)                     共有して使えるようになる。 SCREEN 12                    −−+ CLS                        | MaxOfX = 10                    |いつもの準備 MaxOfY = MaxOfX * 480 / 640            | Memori = MaxOfX / 40                | dp = MaxOfX / 300                 | WINDOW (-MaxOfX, MaxOfY)-(MaxOfX, -MaxOfY)   −−+ CALL SetMatrix(a())                  変換行列を定義 CALL DrawObject(a())                  何か対象を描画 END                      −−−−−−−以上がメイン−−−−−− SUB DrawObject (a())   DIM P AS PrPoint2              射影平面の点としてP を定義   DIM Q AS PrPoint2              変換した像としてQ を定義   FOR t = 0 TO 2 * pi STEP .01     P.x = COS(t)               P を単位円上の点として定義し     P.y = SIN(t)     P.z = 1     CALL TransForm(a(), P, Q)         射影変換する。     CALL PrPset(Q)              その像の点を描く。   NEXT END SUB SUB PrPset (P AS PrPoint2)   IF ABS(P.z) <= .001 THEN EXIT SUB   x = P.x / P.z   y = P.y / P.z   IF ABS(x) < MaxOfX AND ABS(y) < MaxOfY THEN PSET (x, y) END SUB SUB SetMatrix (a())   a(1, 1) = 2   a(1, 2) = 1   a(1, 3) = 0   a(2, 1) = 1   a(2, 2) = 2   a(2, 3) = 0   a(3, 1) = 0   a(3, 2) = 0   a(3, 3) = 1 END SUB SUB TransForm (a(), P AS PrPoint2, Q AS PrPoint2)   Q.x = a(1, 1) * P.x + a(1, 2) * P.y + a(1, 3) * P.z   Q.y = a(2, 1) * P.x + a(2, 2) * P.y + a(2, 3) * P.z   Q.z = a(3, 1) * P.x + a(3, 2) * P.y + a(3, 3) * P.z END SUB  ここでは,単位円を行列Aによって表される射影変換(上記はアフィン変換)による像 が表示されるはずです。  描画できたら,SetMatrix の中のAの定義を変えてみて,いろいろな像を観察してみま しょう。つまり,   ・楕円になることはあるか。   ・双曲線になることはあるか。   ・放物線になることはあるか。   ・直線に退化してしまうことはあるか。  というようなことを調べてみましょう。(ちょっとでいいよ。)  このように,      小さいプログラムでも,そこで実験できるはずのことはしておく という精神は大事です。

17.7 実験しやすくする。−ユーザーインターフェイスの改良−

            上記に,(ちょっとでいいよ)と書いているのに気づきましたか。どうしてでしょう。                  使いにくい ですよね。修正しては,F5を押す。ところが,画面の切り換え等に意外に時間がかかる 。思考の流れを妨げます。そういうことは,見つけたときに,                早めに手を打っておく のが大切です。たとえば,次のように修正してみましょう。これによって,行列の変更は ,かなりやりやすくなると思います。  さあ,改めて,上の問題を考えてみましょう。
でき上がりはこちら。 TYPE PrPoint2                 −+   x AS SINGLE                 |変更なし   y AS SINGLE                 |   z AS SINGLE                 | END TYPE                     |                          | COMMON SHARED pi, MaxOfX, MaxOfY         | pi = ATN(1) * 4                 | DIM a(3, 3)                   |                          | SCREEN 12                     | CLS                       | MaxOfX = 10                   | MaxOfY = MaxOfX * 480 / 640           | Memori = MaxOfX / 40               | dp = MaxOfX / 300                | WINDOW (-MaxOfX, MaxOfY)-(MaxOfX, -MaxOfY)    |                          | CALL SetMatrix(a())              −+ DO                          −+   CALL DrawObject(a())                |使いながら変更できるよ   LOCATE 1, 1                    |うに修正   PRINT "M:Matrix, X:Max of x, C:Clear, Q:Quit";   |   a$ = UCASE$(INPUT$(1))               |行列の変更   LOCATE 1, 1                    |定義域の変更   PRINT SPACE$(80);                 |画面の消去   SELECT CASE a$                   |   CASE "M": CALL EditMatrix(a())           |という3つの機能   CASE "X":                     |     LOCATE 1, 1                  |     PRINT "MaxOfX"; MaxOfX             |     INPUT "New  "; MaxOfX             |     MaxOfY = MaxOfX * 480 / 640          |     Memori = MaxOfX / 40              |     dp = MaxOfX / 300               |     WINDOW (-MaxOfX, MaxOfY)-(MaxOfX, -MaxOfY)   |     CLS                      |   CASE " ", "Q": EXIT DO               |   CASE "C": CLS                   |   END SELECT                     | LOOP                         −+ SUB DrawObject (a())                 −+変更なしなので省略 END SUB                       −+ SUB EditMatrix (a())                 −+   FOR i = 1 TO UBOUND(a, 1)             |行列の内容の修正     FOR j = 1 TO UBOUND(a, 2)           |       LOCATE i + 1, j * 5            |変えなくていい部分は       PRINT a(i, j);               |リターンキー     NEXT                      |のみでいいように配慮   NEXT                        |している。   LOCATE 1, 1                    |   FOR i = 1 TO UBOUND(a, 1)             |     FOR j = 1 TO UBOUND(a, 2)           |       COLOR 2                  |       LOCATE i + 1, j * 5            |       PRINT a(i, j);               |       COLOR 7                  |       LOCATE UBOUND(a, 1) + 3, 1         |       PRINT "             ";     |       LOCATE UBOUND(a, 1) + 3, 1         |       INPUT "New Value"; s$           |       IF s$ <> "" THEN a(i, j) = VAL(s$)     |       LOCATE i + 1, j * 5            |       PRINT a(i, j);               |     NEXT                      |   NEXT                        |   LOCATE UBOUND(a, 1) + 3, 1             |   PRINT "             ";         | END SUB                       −+                            −+ SUB PrPset (P AS PrPoint2)               |変更なしなので省略 END SUB                        |                             | SUB SetMatrix (a())                  | END SUB                        |                             | SUB TransForm (a(), P AS PrPoint2, Q AS PrPoint2)   | END SUB                       −+

17.8 射影平面の中の線分(1) −パラメータ表示をそのまま書き込む−

             ところで,元々の問題は四角形ですよね。今までの道具だてだけでも,できるので,取 り敢えず,正方形を変換してみましょう。           変える部分は,DrawObjectの部分だけでいい のですが,むしろ,次のようにする方が,具合がいいことが多いので,そうしてみましょ う。  メインの部分の  CALL DrawObject を CALL DrawObject2 に変える。  次のSUB を新規に追加する。 SUB DrawObject2 (a())   DIM P AS PrPoint2   DIM Q AS PrPoint2   FOR t = 0 TO 1 STEP .01      −+ 線分 (0,0)-(1,0)     P.x = 0              | なんだけど,その原理はいいですか。     P.y = t              |     P.z = 1              |     CALL TransForm(a(), P, Q)     |     CALL PrPset(P)          |     CALL PrPset(Q)          |   NEXT                −+   FOR t = 0 TO 1 STEP .01         同様     P.x = t     P.y = 1     P.z = 1     CALL TransForm(a(), P, Q)     CALL PrPset(P)     CALL PrPset(Q)   NEXT   FOR t = 0 TO 1 STEP .01     P.x = 1     P.y = 1 - t     P.z = 1     CALL TransForm(a(), P, Q)     CALL PrPset(P)     CALL PrPset(Q)   NEXT   FOR t = 0 TO 1 STEP .01     P.x = 1 - t     P.y = 0     P.z = 1     CALL TransForm(a(), P, Q)     CALL PrPset(P)     CALL PrPset(Q)   NEXT END SUB

17.9 射影平面の中の線分(2)

 何だか,同じことの繰り返しのように見えますね。しかも,その意味が分かりにくい。 そこで,この DrawObject2を次のように書き換えることを考えてみます。 SUB DrawObject2(a())   DIM P AS PrPoint2   DIM Q AS PrPoint2   CALL SetPrPointXY(P, 0, 0)        Pを(0,0) とする。   CALL SetPrPointXY(Q, 1, 0)        Qを(1,0) とする。   CALL DrawSegment(a(), P, Q)       線分 PQ を描く。   CALL SetPrPointXY(P, 1, 1)        繰り返し   CALL DrawSegment(a(), P, Q)       (Q は上記のものが使えるので省略)   CALL SetPrPointXY(Q, 0, 1)   CALL DrawSegment(a(), P, Q)   CALL SetPrPointXY(P, 0, 0)   CALL DrawSegment(a(), P, Q) END SUB この方が,「意味が分かりやすい」ですよね。これを可能にするために,次の二つのSUB を追加します。 SUB DrawSegment (a(), P AS PrPoint2, Q AS PrPoint2)   DIM P1 AS PrPoint2                 線分上の動点   DIM Q1 AS PrPoint2                 それを変換した像   FOR t = 0 TO 1 STEP .01     P1.x = t * P.x + (1 - t) * Q.x     P1.y = t * P.y + (1 - t) * Q.y     P1.z = t * P.z + (1 - t) * Q.z     CALL TransForm(a(), P1, Q1)     CALL PrPset(Q1)   NEXT END SUB SUB SetPrPointXY (P AS PrPoint2, x, y)   P.x = x   P.y = y   P.z = 1 END SUB 後の SetPrPointXY は要らないと思う人もいるかもしれないけど,結構この程度の工夫で ,プログラム自体が読みやすくなるのは事実です。

17.10 射影平面の中の線分(3)

 ところで,このプログラムは少し時間がかかりますよね。我慢ができない程度ではない けれども,少し時間がかかる。しかも,                四角形の像は四角形 だったら,       線分の描画を「点の繰り返し」にすることはないんじゃない つまり,            射影変換においては,直線は直線に写る ことが分かっているんだから,次のように修正しても,         現実には問題がなく,しかも高速化が可能になる。 SUB DrawSegment (a(), P AS PrPoint2, Q AS PrPoint2)   DIM P1 AS PrPoint2   DIM Q1 AS PrPoint2   CALL TransForm(a(), P, P1)   CALL TransForm(a(), Q, Q1)   LINE (P1.x / P1.z, P1.y / P1.z)-(Q1.x / Q1.z, Q1.y / Q1.z) END SUB  厳密には,上記のプログラムは問題がないわけではありません。つまり,  P1.z=0のときなどにはエラーが生じる  そうでなくても,  P1.x / P1.z などの値が大きくなると,エラーが生じることがある。  などの問題があり,この点は,前回の方が,対処していました。  まあ,問題が顕在化し,「なおさないといけないときには直す」という程度の扱いにし ておきましょうか。  現在のところ,高速化の効果の方が期待できますから。

17.11 問題に取り組んでみよう。

 さあ,これでテキストp.24の4(1) への準備は完成です。次のようにして,いろいろと 観察してみましょう。  DrawObject2 に,次を追加する。 SUB DrawObject2 (a())    ・・・・・・         −+   CALL SetPrPointXY(P, .5, 0)   | 追加   CALL SetPrPointXY(Q, .5, 1)   |   CALL DrawSegment(a(), P, Q)   |                    |   CALL SetPrPointXY(P, 0, .5)   |   CALL SetPrPointXY(Q, 1, .5)   |   CALL DrawSegment(a(), P, Q)  −+ END SUB  さて,紙と鉛筆で考えるときと,実際に実験をしてみたときでは,どちらがリアリティ がありますか。また,考えやすいですか。        必ずしもコンピュータがあればいいというものではない ということも分かるでしょうし,また             どの現象に注目したらいいかが重要 ということも分かるでしょう。つまり,           計算はコンピュータが正確にしてくれるが,        それがどういう意味を持つかを解釈するのは人間の役割 ということがよく分かると思います。                  ・・・・・             さて,この問題は解けましたか。 +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+ |                来週に続く                 | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+