技術資料htp://www2.toyo.ac.jp/~asahi/research/simulation/docs/recurrence.html
first upload 09.02.01, update 09.02.27, 09.12.04
(モデル理論アプローチによるシミュレーション)
言語CASTで漸化式を解く
例題として,漸化式
f(0)=100,
f(n)=0.9×f(n-1) (n≧1)
で定義される数列 f(1),f(2),f(3),... を求めるためのCAST言語のユーザ関数を示す.
1 初等再帰的関数
教科書 p.161の初等再帰的関数として定義すると,
func([f]);
f(n)=y <-> (n=0) -> (y:=100) otherwise ( y:=0.9*f(n-1) );
となる.実は,同じ関数を次のようにも記述できる.
func([f]);
f(0)=100;
f(n)=0.9*f(n-1);
数式をそのまま書いているので分かりやすい.
しかし,これで求められるのはf(28)までである.
n > 28に対してはシステムがフリーズする(Segmentation fault).
原因は不明である.
2 関数defListによる解法
関数defListを使って数列のリストL(n) = [f(1), f(2),..., f(n)]を求める方法を示す.
func([L]);
L(n)=Ps <-> y.g:=100, Ps:=defList(p(z,x,[]), member(genIndex(1,n)));
p(z,x,[]) <-> z:=0.9*y.g, y.g:=z;
したがって,リストの最後の要素をとりだせば f(n) が得られる.
fn:=project(L(n),0);
これなら,nが大きくても計算できる.f(10000)が計算できることを確認した.
教科書p.162の非再帰的な方法がこれである.
【解説】defList ---------------------------------------------------------
defList(p(z,x,[補助変数]),member(x,Xs)) は,
述語pを満足するzを並べてリストを作成する関数である.
Xsをインデックス集合と呼ぶ.
述語p(z,x,[補助変数])はインデックス集合の要素xと並べるべき要素zを関係づける述語である.
たとえば,教科書p. 156のように,
Ps:= defList(peven(z,x,[]),member(x,[1,2,3,4,5,6]))
peven(z,x,[]) <-> x%2=0, z:=x;
と定義すれば,偶数のリストPs=[2,4,6]が得られる.システム内部では,
peven(z,1,[])を満足するz = [],
peven(z,2,[])を満足するz = [2],
peven(z,3,[])を満足するz = [],
peven(z,4,[])を満足するz = [4],
peven(z,5,[])を満足するz = [],
peven(z,6,[])を満足するz = [6],
を連接(append)してリストPs=[2,4,6]を得ている.
集合Xsの要素xを「先頭から順に代入して」性質pevenが成り立つzを求めているのである.
以下では,教科書には未記載の「裏技」を紹介する.ここで注目したいことは,
「defLIstはインデックス集合の要素の数|Xs|だけ仕事をする」という点である.
したがって,極端に言えば,xとzが無関係であっても|Xs|回の仕事をさせることができる.
たとえば,
Qs:=defList(p1(z,x,[]),member(x,[6,7,8]))
p1(z,x,[]) <-> xwriteln(0,"Hallo");
と定義した場合,Dialogウインドウに"Hallo"が3回表示される.
述語 xwriteln(0,"Hallo") は,x=6,7,8とまったく無関係であるうえに,変数zの定義さえしていない.
しかしながら,3回の仕事「真偽チェック」をしているのである.
(xwriteln(0,"Hallo")のような入出力命令は,それが成功する限り常に「真」である.)
したがって,手続型言語の「繰り返し命令」のような仕事をさせることができる.
その他,裏技としてグローバル変数を使う例を紹介する.
例えば,
y.g:=100,
Qs:=defList(p1(z,x,[]),member(x,[6,7,8]))
p1(z,x,[]) <-> z:=0.9*y.g, y.g:=z;
と定義した場合,zはy.gのみに依存して決定されるので,x=6,7,8とzは互いに無関係である.
しかしながら,3回の真偽チェックを行ない,
初期値はy.g=100,
p1(z,6,[])を満足するz = [90], そのとき y.g=90,
p1(z,7,[])を満足するz = [81], そのとき y.g=81,
p1(z,8,[])を満足するz = [72.9]
であるから,Qs=[90,81,72.9]が得られる.
述語p1は並べるべき値z:=0.9*y.gを決定し,
ついでにグローバル変数をy.g:=zに書き換える仕事をしている.したがって,
f(0)=100,
f(n)=0.9*f(n-1)
という漸化式を初期値から順番に計算し,
リストQs=[f(1),f(2),f(3)]を作成していると考えることができる.
さらに,もしもRs=[f(11),f(12),f(13)] を計算したいときは,次のように定義すればよい.
y.g:=100,
Rs:=defList(p2(z,x,[]),member(x,genIndex(1,13)))
p2(z,x,[]) <-> z:=0.9*y.g, y.g:=z, x>10;
述語p2ではインデックスを使ってx>10と指定しているので,
11回目の計算結果からRsの要素に採用されていく.
しかし,10回目まで計算は行なわれていることに注意したい.
10回目まで「計算は行なわれている」がRsの要素として採用されないことに特徴がある.
###
ただし x>10 は右端に記述する必要がある.
例えば x>10, z:=0.9*y.g, y.g:=z と書くと,10回までの計算は実行されない.
その理由は,システムは,偽である x>10 を見つけると,それより右の項の真偽チェックをしないからである.
記述の順序に気をつける必要がある.
###
その他,
defListの結果にリスト演算子sumを適用すると,数列の和(シグマ:Σ)を計算できる.
defListを2つ使って,行列(リストのリスト)を作成することもできる.
また,リストではなく集合を作る関数としてdefSetがあるが,
これはdefListを行なったあとに「集合」化したもの,
つまり,defSet(...) = distinct(defList(...))と考えることができる.
東洋大学 旭貴朗