/*
  【並列処理テストプログラム】
                                          2009.10.22 T.Inaba SOFNEC.CO.LTD 
                                          2009.12.01 Pentominoなどを追加

   注意：  1)$(AZ-Prolog)\bin　にＰａｔｈが設定されていること。
           2)当然のことながら、シングルコアＣＰＵでは、並列処理のほうが遅い場合が多い。
           3)子プロセスの生成、ストリーム通信のコストなどがあるので、これらを無視できる
             程度の分散処理効率がないとあまり意味はない。

    ・データ並列：       data_parallel       例題：12queen,prime(100000)
          数値のみからなるリストの全要素を二乗したリストを生成することを考えてみよう。
           double([],[]):-!.
           double([A|B],[AA|BB]):- AA is A*A,double(B,BB).
          全要素を同時に二乗できれば５分の1のコストになるはず。
          [1,2,3,4,5]
           ↓↓↓↓↓
          [1,4,9,16,25]

    ・処理並列：         task_parallel       例題：ack(3,10)+fibo(35)
          依存しない複数のサブゴールが並列で処理できれば最長部分のみの処理時間で済む。

                      3sec + 20sec + 10sec=33sec
          逐次: ....a(A,AA),b(B,BB),c(C,CC),   d(AA,BB,CC),....

                      20sec
                  +- a(A,AA)-+
          並列: --+- b(B,BB)-+---d(AA,BB,CC,Ans),
                  +- c(C,CC)-+
 
    ・アルゴリズム並列： argorizum_parallel  例題：send+more=money 
           Prolog のＯＲ節は、異なるアルゴリズムを並列であらわしていると考えることもできる。
           複数のOR節のいずれかが成功すればよい場合、これらを並列で実行してひとつの結果を得れればよい。

                  55sec
             .....p(X,Y).....

             p(X,Y):- Argorizum1,..     20sec でFail 
             p(X,Y):- Argorizum2,..      5sec でFail
             p(X,Y):- Argorizum3,..     30sec で Succ  !!
              :
             p(X,Y):- ArgorizumN,..      1sec でSucc

             Argorizum1〜ArgrizumNを並列で試せれば、1secで結果がでる！

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ここではすべて、次のパターンからなる。これらはPrologでかかれたコンパイル述語であり、
詳細は mlt_parent.pl mlt_child.pl を読んでください。
子プロセスへのゴールの与え方と結果の受け取り方で目的の違う処理が構築できます。

   1)複数の子プロセスの生成          mlt_proc/2       生成するPrologプロセスの個数を指定して生成された一覧リストを第二引数にユニファイ
                                     mlt_proc/5       プロセスの個数、EXE、引数１(領域設定)、引数２(読込みPG)を指定してプロセスを生成
   2)子プロセスにゴールを与える      mlt_send/2       ゴール:         結果は status(exe,Status) Statusはsucc,fail,エラー番号のいずれか
                                                      [ゴール,戻し値] ゴールが成功したとき返す戻し値を指定。エラー、failでは上と同じ
                                                      {ゴール,戻し値} 上と同じだが、戻したあと強制バックトラックされ、別解が取れる
   3)子プロセスから結果を受け取る    mlt_receive/2   （<fail,error>を含む全ての結果を起動順に読み込む） 
                                     mlt_receive/3   （<fail,error>を含む全ての結果を早い順に読み込む,Arg2でスキャンインターバルを指定する）
                                     mlt_scan/3      （<fail,error>を含む最速の結果のみを読み込む）
                                     mlt_scan2/3     （<fail,error>を除く最速の結果を読み込む。すべて<err,fail>なら述語そのものがFail）
                                     mlt_scan_each/2  (<fail,error>を除く結果を早い順に受け取る。バックトラックで次の結果を取れる）
   4)子プロセスを強制終了する        mlt_kill/1

*/

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Top Level is "?-test." すべてのテストプログラムを逐次、並列ともおこないます.
% :-b_load(pentomino).

comp_all([]).
comp_all([A|L]):- 
	atom_appends(['azpc -p /byte ',A,'.pl /NOM'],Com),system(Com),
	b_load(A),comp_all(L).

test:- 
	comp_all([queen,prime,fibo_ack,sendmore]),
	sep,data_parallel,
	sep,task_pararellel,
	sep,argorizum_parallel.
%	sep,write(＜ペントミノパズル全解＞),nl,test_all.

% 逐次処理のテストのみおこないます. たいていの処理系でこのまま動くでしょう。
single_test:- sep,queen_test1,sep,prime_test1,sep,task_test1,sep,sendmore_test1.

data_parallel:-      queen_test,prime_test.
task_pararellel:-    task_test.
argorizum_parallel:- sendmore_test,sep2,para_test.

sep:- write('******************************'),nl.
sep2:- write('  **********************'),nl.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% データ並列テストプログラム（１）
%% 12クイーン全解、逐次処理と並列処理の実行時間を比較する。出力結果は膨大なので、時間のみ表示


queen_test:- queen_test1,queen_test2.

queen_test1:-
    write('【12クイーン全解、逐次処理と並列処理の実行時間を比較】'),nl,
    List = [1,2,3,4,5,6,7,8,9,10,11,12],
    Ts is cputime, 
        qtest(List,[],Vol),
    Tse is cputime-Ts,
    write(' 12queen 逐次処理 全解の個数'=Vol),write(' Seconds'=Tse),nl.

queen_test2:-
    List = [1,2,3,4,5,6,7,8,9,10,11,12],
    bagof([qtest(Else,[F],Vol),Vol],select(List,F,Else),Pattern),     % 一列目におくQueenの位置１２とおりを生成
    mlt_proc(12,prolog_c,'-l 10 -g 100 -h 100 -a 10',b_load(queen),Proc),                % 12のプロセスを生成
    Tp is cputime,
        mlt_send(Proc,Pattern),                                       % おのおののプロセスにパターンを割り振る
        mlt_receive(Proc,Vols),                                       % 全プロセスが計算終了まで待つ
    Tpe is cputime-Tp,
    mlt_kill(Proc),                                                   % プロセスを消去
    add_all(Vols,0,Volx),
    write(' 12queen 並列処理 全解の個数'=Volx),write(' Seconds'=Tpe),nl,nl.

add_all([],N,N):-!.
add_all([A|L],N,Ans):- NN is N+A,add_all(L,NN,Ans).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% データ並列テストプログラム（2）
%% ２から１0万までの素数抽出を逐次処理と５プロセス並列処理で実行時間を比較する
%% あまり並列効果がでないのは、膨大な結果を親に返すプロセス間通信にコストがかかるためと思われる。


prime_test:- prime_test1,prime_test2.

prime_test1:-
    write('【2-100000までの間の素数抽出を逐次処理と並列処理で時間比較】'),nl,
    Ts is cputime, 
        genPrime(2,100000,SAns-[]),
        length(SAns,SL),                                      % 結果を表示すると膨大なので、抽出数のみ表示
    Tse is cputime-Ts,
    write(['素数計算(2-100000)　逐次処理 ',Tse,' seconds   ',volume=SL]),nl.

prime_test2:-
    mlt_proc(5,prolog_c,'',b_load(prime),Proc),
    Tp is cputime,
        mlt_send(Proc,                                         % ５つのデータ領域に分けて並列計算
            [[genPrime(2,     20000,LT),LT],
             [genPrime(20001, 40000,LT),LT],
             [genPrime(40001, 60000,LT),LT],
             [genPrime(60001, 80000,LT),LT],
             [genPrime(80001,100000,LT),LT]] ),
        mlt_receive(Proc,[PAns-A1,A1-A2,A2-A3,A3-A4,A4-[]]),   % 戻り値を差分リストにしているので容易に結合できる
    Tpe is cputime-Tp,
    mlt_kill(Proc),
    length(PAns,PL),
    write(['素数計算(2-100000)　並列処理 ',Tpe,' seconds   ',volume=PL]),nl,nl.
       
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 処理並列(タスク並列）テストプログラム
%% ほとんど意味のないプログラムだが、処理並列の効率計測を行うため、並列と逐次で計算時間を比較する


task_test:- task_test1,task_test2.

task_test1:-
    write('【フィボナッチ数列 fibo(35)とアッカーマン関数 ack(3,10)の計算結果を加算する】'),nl,
    write('逐次処理で計算。。。'),
    Xs is cputime,
       fibo(35,X),
       ack(3,10,Y),
       Ans is X+Y,
    Ys is cputime-Xs,
    write(['Ans'=Ans,'     ',Ys='seconds']),nl.

task_test2:-
    write('並列処理で計算。。。'),
    mlt_proc(2,prolog_c,'',b_load(fibo_ack),Proc),             % fibo.pl を起動時に読込むことを指定し、子プロセスを二つ生成
    Xp is cputime,
       mlt_send(Proc,[[fibo(35,X),X],[ack(3,10,Y),Y]]),              % ２つのプロセスの入力ストリームに、異なる[ゴール,返し値] を送信
       mlt_receive(Proc,[X,Y]),                                      % 出力ストリームから計算結果を受け取る
       Ans is X+Y,                                                   % 両方の計算結果を加算する
    Yp is cputime-Xp,
    mlt_kill(Proc),                                                  % 子プロセスを殺す
    write(['Ans'=Ans,'     ',Yp='seconds']),nl,nl.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% アルゴリズム並列テストプログラム
%% ここでは、send+more+monyの解を複数のパターンのリストの並べ替えで並列計算してみる


sendmore_test :- sendmore_test1,sendmore_test2,sendmore_test3.

sendmore_test1:-
    write('【send+more=money を[0..9]の昇順リストを基に生成テスト法で解く】'),nl,
        Xs is cputime,
            sendmore(S,[0,1,2,3,4,5,6,7,8,9]),
        Ys is cputime-Xs,
    write(['Ans'=S,'     ',Ys='seconds']),nl,nl.

sendmore_test2:-
    write('【send+more=money を4パターンのリストで並列に解き、最速分のみ抽出】'),nl,
    mlt_proc(4,prolog_c,'',b_load(sendmore),Proc),
    s_sleep(1000),                                % コンパイルが終わらないうちに殺されるプロセスが発生してしまうので一拍おく
    Xp is cputime,
      mlt_send(Proc,[ [(L=[0,1,2,3,4,5,6,7,8,9],sendmore(X,L)),  L>>X],          % 昇順パターン
                      [(L=[9,8,7,6,5,4,3,2,1,0],sendmore(X,L)),  L>>X],          % 降順パターン
                      [(L=[0,9,2,7,4,6,8,5,3,1],sendmore(X,L)),  L>>X],          % ランダムパターン1
                      [(L=[5,6,1,4,8,9,7,0,3,2],sendmore(X,L)),  L>>X] ]),       % ランダムパターン2
      mlt_scan2(Proc,0,ID=Ans),
    Yp is cputime-Xp,
    mlt_kill(Proc),
    write(['fast is ',Ans,' ',Yp='seconds']),nl,nl.

sendmore_test3:-
    write('【send+more=money を4パターンのリストで並列に解き速度順,全結果を時間付きで取得】'),nl,
    Xp is cputime,
    mlt_proc(4,prolog_c,'',b_load(sendmore),Proc),
      mlt_send(Proc,[ [(L=[0,1,2,3,4,5,6,7,8,9],sendmore(X,L)),  L>>X],          % 昇順パターン
                      [(L=[9,8,7,6,5,4,3,2,1,0],sendmore(X,L)),  L>>X],          % 降順パターン
                      [(L=[0,9,2,7,4,6,8,5,3,1],sendmore(X,L)),  L>>X],          % ランダムパターン1
                      [(L=[5,6,1,4,8,9,7,0,3,2],sendmore(X,L)),  L>>X] ]),       % ランダムパターン2
      mlt_receive(Proc,0,Ans),
    mlt_kill(Proc),
    Yp is cputime-Xp,
    write(['All solution   ',Yp='seconds']),nl,
    write_each(1,Ans),nl.

write_each(_,[]):-!.
write_each(N,[_=[Ans,Tm]|L]):- write([N,'番 ',Ans,' ',Tm,' Seconds']),nl,NN is N+1,write_each(NN,L).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% アルゴリズム並列テストプログラム
%%  １００節の最後で成功するパターンの実験

:- ['ptest.pl'].         % 時間計測が目的なのでコンサルト。コンパイルしてしまうと計れない。

para_test:- para_test1,nl,para_test2.

para_test1:-
    write('【100節あるＯＲの最後で解を得る場合の逐次処理（各節0.01秒かかるように細工）】'),nl,nl,
    Xs is cputime,
       ptest(1,100,Ans),
    Ys is cputime-Xs,
    write(ptest(1,100,Ans)),tab(2),write(Ys),write(seconds),nl.

para_test2:-
    write('【100節あるＯＲの最後で解を得る場合の並列処理（各節0.01秒かかるように細工）】'),nl,
    write(' このようなパターンはProcess生成に時間がかかるので、はじめに生成し繰り返し利用することを前提にするべき。'),
    Pb is cputime,
        bagof([prolog_c,'-l 1 -g 1 -h 1 -a 2',assert((ptest(X,Y,Z):-B))], clause(ptest(X,Y,Z),B), L),
        mlt_proc2(L,Proc),
    Pe is cputime-Pb,
    length(L,Lng),
    CMD=[[ptest(1,100,Ans),Ans]|CMD],         % 子プロセスに同じゴールを設定するために循環リストにしておく
    tab(2),write(Lng),write('プロセス生成の時間＝'),write(Pe),write(seconds),nl,nl,
    Xp is cputime,
       mlt_send(Proc,CMD),
       mlt_scan2(Proc,0,ID=Ans),
    Yp is cputime-Xp,
    write(ptest(1,100,Ans)),tab(2),write(Yp),write(seconds),nl, %assert(pp(Proc)).
    mlt_kill(Proc).


