PIONEチュートリアル
PIONEチュートリアル
ここでは、PIONEのチュートリアルを行います。始める前にPIONEのインストールをしておきましょう。
基本
基本1(特定のファイルを出力する)
まず、PIONEを動かすにはルール定義書をつくる必要があります。 どんな言語でも最初に作成するHelloプログラムを作ってみましょう。この場合、出力ファイルをひとつ指定することになります。
次の内容のファイル'HelloWorld.pione'を作成してみましょう。
Rule Main output 'message.txt' Action echo "Hello PIONE world !" > message.txt End
このファイルはこちらからダウンロードできます。
この後、pione-clientを実行します。
$ pione-client HelloWorld.pione -b helloOutput
例えば、次のような出力が流れます。
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
--> Distribution: &Anonymous:Root([],{})
>>> &Anonymous:Main([],{})
==> &Anonymous:Main([],{})
SH ------------------------------------------------------------
SH echo "Hello PIONE world !" > message.txt
SH ------------------------------------------------------------
<== &Anonymous:Main([],{})
<-- Distribution: &Anonymous:Root([],{})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
その結果、helloOutputというディレクトリができます。その中に指定したファイルmessage.txtが出力されています。
$ cat helloOutput/output/message.txt
Hello PIONE world !
もう一度、実行すると今度は、実行の必要がないために次のようなものが出力されます。
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
さっきと比べると、Main Ruleが動いていないことが分かります。
さて、改めて設定したファイルを眺めてみます。
まず、最初に呼び出されるルール(Main)が定義されています。
Rule Main
Mainは、C言語などと同様に特別な意味をもつルールです。
次に、出力ファイルが定義されています。
output 'message.txt'
ここに書かれたファイルが最終的に-bで指定されたディレクトリに出力として戻ってきます。 Action以降が実際に起動するプログラムになります。
Action echo "Hello PIONE world !" > message.txt
実際にはシェルスクリプトが動きますので、どんなものも実行が可能です。
- !/bin/csh
で始めれば、cshを使って記述することもできますし、スクリプト系の言語であれば自由に記述し、実行形式を実行出来ます。
最後に、RuleをEndで終了します。
End
これが、一番シンプルなルールの書き方です。入力ファイルがないので、出力ファイルがなければ作成し、あれば、作成しないという動作をします。必要以上の動作をしないところが通常のシェルスクリプトを実行する場合と異なる点です。
基本2(特定のファイルを入力し、出力する(更新判定))
次に、入力ファイルから出力ファイルを作成する場合の定義書について作成してみましょう。
#Multiplying.pione
Rule Main
input 'test.in'
output 'test.out'
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
End
このファイルはこちらからダウンロードできます。
今回は、test.inという入力ファイルからその積算をして、test.outというファイルを作り出すルールです。入力ファイルの更新判定により、ルールを実行するかどうかが変わってきます。
awkの使い方については、別途勉強してみて下さい。ここではファイルの中にある行頭の数字を2倍して、出力することができます。
まず、
$ mkdir MultiplyingInput $ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/
として、入力ファイルがあるディレクトリ(指定しなければ、現在のディレクトリ)を指定して実行してみます。
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
入力ファイルがありませんので、何もする事がないとして終了してしまいます。先ほどとの違いに気がついたでしょうか。
次に、ファイルを作成して、実行してみます。
$ echo "3" > MultiplyingInput/test.in
$ echo "5" >> MultiplyingInput/test.in
$ cat MultiplyingInput/test.in
3
5
$ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/
==> &Anonymous:Root([test.in],{})
--> Rule Application: &Anonymous:Root([test.in],{})
--> Distribution: &Anonymous:Root([test.in],{})
>>> &Anonymous:Main([test.in],{})
==> &Anonymous:Main([test.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<-- Distribution: &Anonymous:Root([test.in],{})
<-- Rule Application: &Anonymous:Root([test.in],{})
<== &Anonymous:Main([test.in],{})
<== &Anonymous:Root([test.in],{})
$ cat MultiplyingOutput/output/test.out
6
10
となり、確かに2倍の値のファイルができあがっていることが分かります。 もう一度、実行すると、
$ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/
==> &Anonymous:Root([test.in],{})
--> Rule Application: &Anonymous:Root([test.in],{})
<-- Rule Application: &Anonymous:Root([test.in],{})
<== &Anonymous:Root([test.in],{})
となり、ここでも何も実行しません。つまり、入力ファイルに比べて出力ファイルのほうが新しいので、更新判定の結果、実行しなくてよいと判断したことになります。賢いですね。
さて、ここで、ファイルを更新してみましょう。3行目に7を付け加えます。
$ echo "7" >> MultiplyingInput/test.in
そして、実行してみましょう。 $ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/
==> &Anonymous:Root([test.in],{})
--> Rule Application: &Anonymous:Root([test.in],{})
--> Distribution: &Anonymous:Root([test.in],{})
>>> &Anonymous:Main([test.in],{})
==> &Anonymous:Main([test.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<-- Distribution: &Anonymous:Root([test.in],{})
<== &Anonymous:Main([test.in],{})
<-- Rule Application: &Anonymous:Root([test.in],{})
<== &Anonymous:Root([test.in],{})
$ cat MultiplyingOutput/output/test.out
6
10
14
今度は実行されました。これが更新判定によりルールが実行されるかどうかが判定されているということです。 もう一度実行しても、今度は実行されません。
$ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/
==> &Anonymous:Root([test.in],{})
--> Rule Application: &Anonymous:Root([test.in],{})
<-- Rule Application: &Anonymous:Root([test.in],{})
<== &Anonymous:Root([test.in],{})
さて、出力ファイルを削除してみるとどうなるでしょうか。
$ rm MultiplyingOutput/output/test.out
今度は、実行されますね。
では、内容は変えずに、touch コマンドを使って、入力ファイルの修正時刻を変えるとどうなるでしょうか。
$ touch MultiplyingInput/test.in
今回も、予想通り、動作しました。
もし、動作しないようだったら、バグです。すぐに、GITHUB/PIONEに報告しましょう。
さて、この基本2が理解できれば、まず、PIONEの動きの基本が理解できたことになります。
ところで、Actionで定義した動作の中に、見慣れない記号( {$I[1]}, {$O[1]} )が現れています。
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
End
{$I[1]}は入力ファイル、{$O[1]}は出力ファイルを表しています。それぞれの[]の中の数字1は、 input/outputで定義したそれぞれの1番目ということを意味しています。次の基本3での複数ファイルの入出力では1以外が使われることになります。 まだ、何が便利か少し分かりづらいと思いますが、例えば、Multiplying2.pioneとして、次のように記述するとどのように動作するでしょうか。
Rule Main
input 'test.in'
output '{$I[1]}.out'
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
End
少し考えてみて下さい。そして、動作させてみましょう。
$ pione-client Multiplying2.pione -b MultiplyingOutput -i MultiplyingInput/
MultiplyingOutput/outputのディレクトリにはどんなファイルができあがったでしょうか。
そうですね。test.in.outができあがっています。何故かを考えてみましょう。
基本3(複数ファイルの入力と複数ファイルの出力)
さて、更新判定が理解できたところで、複数ファイルの入出力について考えてみましょう。test1.in, test2.inから、それぞれ2倍した数が格納された、test1.out, test2.outが出力できるPIONE定義書を作成してみましょう。ここで、名前をMultiplyingFiles.pioneとして作成してみましょう。
問題1:上記のMultiplyingFiles.pioneを作成して実行してみて下さい。
解答例1:
Rule Main
input 'test1.in'
input 'test2.in'
output 'test1.out'
output 'test2.out'
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
awk '{ print $1*2 }' {$I[2]} > {$O[2]}
End
このファイルはこちらからダウンロードできます。
どうでしょうか。思ったように動きましたか。複数のファイルがあれば、複数書き並べればよいのです。
でもなんだか、同じ事をするのに何度も行を書くのは嫌だなと思いませんか。もし、2倍を5倍に変えたくなったら全ての行を書き換えなくてはなりませんね。10行ぐらいならそれでもよいけれど、100個、1000個とファイルが増えてきたらどうでしょうか。だんだん嫌になってきましたか。 では、どうすれば良いでしょうか。例えば、シェルスクリプトが得意なひとは次の様なプログラムを設定することも出来ます。
解答例2:
Rule Main
input 'test1.in'
input 'test2.in'
output 'test1.out'
output 'test2.out'
Action
for i in `ls *.in`; do
awk '{ print $1*2 }' $i > `basename $i .in`.out
done
End
これでも、ファイルが増えるたびにinput/outputを書き換える必要があります。 そこで、全てのファイルを入力し、全てのファイルを出力するように入出力を表現することも出来ます。
解答例3:
Rule Main
input '*.in'.all
output '*.out'.all
Action
for i in `ls *.in`; do
awk '{ print $1*2 }' $i > `basename $i .in`.out
done
End
どうでしょうか。うまく動作したでしょうか。ここで、
input '*.in'.all output '*.out'.all
の部分は、*は任意の文字列を表現します。つまり、*.inは、ファイルの最後が.inで終了しているファイル全て、*.outは、ファイルの最後が、.outで終了しているファイル全てを表します。.allがついていると、全てのファイルを取り扱うことを意味しています。逆に、.allが付いていないときには、.eachがついていることがdefaultとして決まっています。一つ一つのファイルを別々に実行するという意味になります。
これでずいぶんと記述しやすくなりました。
でも、これだと、*.inのどれかのファイルが更新されていると全て変更になってしまいます。試してみて下さい。なんだか無駄ですね。また、同時にできるはずのことをfor文を使って順次実行しているので、時間も無駄ですね。このくらいのタスクであれば、たいして時間がかかるわけではないのですが、もっと時間のかかるタスクだったらどうでしょうか。必要なファイルの更新だけを、できれば複数のホストや一台でも最近のPCだったら、マルチコアCPUをもっているので、同時に動かすともっと早く終了できるようになるはずです。
さて、それではどのようにすればよいのでしょうか。それが次の基本4になります。だんだんPIONEらしくなってきます。
基本4(複数ファイルの入力と複数ファイルの出力の並列処理)
次は、さっきと違ってallが付いていません。なにが起きるかを考えてみましょう。
Rule Main
input '*.in'
output '{$I[1][1]}.out'
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
End
このファイルはこちらからダウンロードできます。
下は実行した例です。
$ pione-client MultiplyingFilesEach.pione -b MultiplyingFilesEachOutput -i MultiplyingFilesInput/ -t 4
==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
>>> &Anonymous:Main([test1.in],{})
>>> &Anonymous:Main([test2.in],{})
>>> &Anonymous:Main([test3.in],{})
>>> &Anonymous:Main([test4.in],{})
>>> &Anonymous:Main([test5.in],{})
>>> &Anonymous:Main([test6.in],{})
>>> &Anonymous:Main([test7.in],{})
==> &Anonymous:Main([test1.in],{})
==> &Anonymous:Main([test2.in],{})
==> &Anonymous:Main([test3.in],{})
==> &Anonymous:Main([test4.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:Main([test2.in],{})
<== &Anonymous:Main([test1.in],{})
<== &Anonymous:Main([test3.in],{})
<== &Anonymous:Main([test4.in],{})
==> &Anonymous:Main([test5.in],{})
==> &Anonymous:Main([test6.in],{})
==> &Anonymous:Main([test7.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:Main([test6.in],{})
<== &Anonymous:Main([test5.in],{})
<== &Anonymous:Main([test7.in],{})
<-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
4つのタスクが並列に動いている様子が分かるでしょうか。確かに4つずつのタスク( ==> &Anonymous:Main([testN.in],{}))が並列に動作しているようです。 もう一度実行してみると下のようになります。
$ pione-client MultiplyingFilesEach.pione -b MultiplyingFilesEachOutput -i MultiplyingFilesInput/ -t 4
==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
今度は実行されません。そこで、ひとつファイルを更新してみます。
$ touch MultiplyingFilesInput/test3.in
$ pione-client MultiplyingFilesEach.pione -b MultiplyingFilesEachOutput -i MultiplyingFilesInput/ -t 4
==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
>>> &Anonymous:Main([test3.in],{})
==> &Anonymous:Main([test3.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:Main([test3.in],{})
<-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
となり、test3.inだけが更新されているのが分かります。これがeachとしての振る舞いです。
基本5(直列:フロールールの設定 )
さて、もう少し複雑なルールについて設定してみましょう。 まず、ふたつのルールを組み合わせて、並列計算することを考えてみます。
Rule Main
input '*.in'.all
output '*.out'.all
Flow
rule First
rule Second
End
Rule First
input '*.in'
output '{$I[1][1]}.route'
Action
awk '{ print $1*2 }' {$I[1]} > {$O[1]}
End
Rule Second
input '*.route'
output '{$I[1][1]}.out'
Action
awk '{ print $1+1 }' {$I[1]} > {$O[1]}
End
このファイルはこちらからダウンロードできます。
実行結果を以下に示します。最初に5つのタスク(First)が並列で動いているのがわかります。 その後、終了した順に、次のルールが動いています。
$ pione-client Serial2.pione -i SerialInput/ -b Serial2Output
==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
>>> &Anonymous:Main([test1.in],{})
>>> &Anonymous:Main([test2.in],{})
>>> &Anonymous:Main([test3.in],{})
>>> &Anonymous:Main([test4.in],{})
>>> &Anonymous:Main([test5.in],{})
==> &Anonymous:Main([test1.in],{})
--> Rule Application: &Anonymous:Main([test1.in],{})
--> Distribution: &Anonymous:Main([test1.in],{})
>>> &Anonymous:First([test1.in],{})
==> &Anonymous:Main([test2.in],{})
--> Rule Application: &Anonymous:Main([test2.in],{})
--> Distribution: &Anonymous:Main([test2.in],{})
>>> &Anonymous:First([test2.in],{})
==> &Anonymous:Main([test3.in],{})
--> Rule Application: &Anonymous:Main([test3.in],{})
--> Distribution: &Anonymous:Main([test3.in],{})
>>> &Anonymous:First([test3.in],{})
==> &Anonymous:Main([test4.in],{})
--> Rule Application: &Anonymous:Main([test4.in],{})
--> Distribution: &Anonymous:Main([test4.in],{})
>>> &Anonymous:First([test4.in],{})
==> &Anonymous:Main([test5.in],{})
--> Rule Application: &Anonymous:Main([test5.in],{})
--> Distribution: &Anonymous:Main([test5.in],{})
>>> &Anonymous:First([test5.in],{})
==> &Anonymous:First([test1.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:First([test1.in],{})
<-- Distribution: &Anonymous:Main([test1.in],{})
--> Distribution: &Anonymous:Main([test1.in],{})
>>> &Anonymous:Second([test1.route],{})
==> &Anonymous:First([test2.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:First([test2.in],{})
<-- Distribution: &Anonymous:Main([test2.in],{})
--> Distribution: &Anonymous:Main([test2.in],{})
>>> &Anonymous:Second([test2.route],{})
==> &Anonymous:First([test3.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:First([test3.in],{})
<-- Distribution: &Anonymous:Main([test3.in],{})
--> Distribution: &Anonymous:Main([test3.in],{})
>>> &Anonymous:Second([test3.route],{})
==> &Anonymous:First([test4.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:First([test4.in],{})
<-- Distribution: &Anonymous:Main([test4.in],{})
--> Distribution: &Anonymous:Main([test4.in],{})
>>> &Anonymous:Second([test4.route],{})
==> &Anonymous:First([test5.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:First([test5.in],{})
<-- Distribution: &Anonymous:Main([test5.in],{})
--> Distribution: &Anonymous:Main([test5.in],{})
>>> &Anonymous:Second([test5.route],{})
==> &Anonymous:Second([test1.route],{})
SH ------------------------------------------------------------
SH awk '{ print $1+1 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:Second([test1.route],{})
<-- Distribution: &Anonymous:Main([test1.in],{})
<-- Rule Application: &Anonymous:Main([test1.in],{})
<== &Anonymous:Main([test1.in],{})
==> &Anonymous:Second([test2.route],{})
SH ------------------------------------------------------------
SH awk '{ print $1+1 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:Second([test2.route],{})
<-- Distribution: &Anonymous:Main([test2.in],{})
<-- Rule Application: &Anonymous:Main([test2.in],{})
<== &Anonymous:Main([test2.in],{})
==> &Anonymous:Second([test3.route],{})
SH ------------------------------------------------------------
SH awk '{ print $1+1 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:Second([test3.route],{})
<-- Distribution: &Anonymous:Main([test3.in],{})
<-- Rule Application: &Anonymous:Main([test3.in],{})
<== &Anonymous:Main([test3.in],{})
==> &Anonymous:Second([test4.route],{})
SH ------------------------------------------------------------
SH awk '{ print $1+1 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:Second([test4.route],{})
<-- Distribution: &Anonymous:Main([test4.in],{})
<-- Rule Application: &Anonymous:Main([test4.in],{})
<== &Anonymous:Main([test4.in],{})
==> &Anonymous:Second([test5.route],{})
SH ------------------------------------------------------------
SH awk '{ print $1+1 }' {$I[1]} > {$O[1]}
SH ------------------------------------------------------------
<== &Anonymous:Second([test5.route],{})
<-- Distribution: &Anonymous:Main([test5.in],{})
<-- Rule Application: &Anonymous:Main([test5.in],{})
<-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Main([test5.in],{})
<-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
一度、終了しましたので、次は実行されません。
$ pione-client Serial2.pione -i SerialInput/ -b Serial2Output
==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
さて、ここで、ファイルをひとつ(test6.in)増やしてみましょう。
$ pione-client Serial2.pione -i SerialInput/ -b Serial2Output
==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
>>> &Anonymous:Main([test6.in],{})
==> &Anonymous:Main([test6.in],{})
--> Rule Application: &Anonymous:Main([test6.in],{})
--> Distribution: &Anonymous:Main([test6.in],{})
>>> &Anonymous:First([test6.in],{})
==> &Anonymous:First([test6.in],{})
SH ------------------------------------------------------------
SH awk '{ print $1*2 }' test6.in > test6.route
SH ------------------------------------------------------------
<== &Anonymous:First([test6.in],{})
<-- Distribution: &Anonymous:Main([test6.in],{})
--> Distribution: &Anonymous:Main([test6.in],{})
>>> &Anonymous:Second([test6.route],{})
==> &Anonymous:Second([test6.route],{})
SH ------------------------------------------------------------
SH awk '{ print $1+1 }' test6.route > test6.out
SH ------------------------------------------------------------
<== &Anonymous:Second([test6.route],{})
<-- Distribution: &Anonymous:Main([test6.in],{})
<-- Rule Application: &Anonymous:Main([test6.in],{})
<-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Main([test6.in],{})
--> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
新しくできたファイルだけが作られていることがわかります。 それでは、ファイルの更新がかかったときはどうなるでしょうか。test2.inをtouchコマンドで更新してみました。
$ pione-client Serial2.pione -i SerialInput/ -b Serial2Output2
==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
>>> &Anonymous:Main([test2.in],{})
>>> &Anonymous:Main([test6.in],{})
==> &Anonymous:Main([test2.in],{})
--> Rule Application: &Anonymous:Main([test2.in],{})
<-- Rule Application: &Anonymous:Main([test2.in],{})
<== &Anonymous:Main([test2.in],{})
==> &Anonymous:Main([test6.in],{})
--> Rule Application: &Anonymous:Main([test6.in],{})
<-- Rule Application: &Anonymous:Main([test6.in],{})
<== &Anonymous:Main([test6.in],{})
<-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
--> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
<== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
となり、新しいファイルであるtest2.in, test6.inに関しては、変更の可能性があるので起動し始めますが、何のルールも適用されないまま終了しています。
基本6(パラメータの定義)
入力データはファイルから読み取る以外にパラメータとして使用する方法があります。パラメータにはbasicとadvancedの2つがあります。処理上の違いは特にありませんが、前者を基本パラメータ、後者を上級者向けパラメータとして定義することでユーザのレベルに応じて変更可能なパラメータを区別することができます。下記のように定義します。
param $val := 123
basic param $b_val := 456
advanced param $a_val := 987
Basic Param
$b_val1 := 135
$b_val2 := 246
End
Advanced Param
$a_val1 := 975
$a_val2 := 864
End
Rule Main
output 'message.txt'
param $main_val := $val * 10
Flow
rule Sub {sub_val: $main_val}
End
Rule Sub
output 'message.txt'
param $sub_val
Action
echo "Basic Parameters:" > {$O[1]};
echo " val: {$val}" >> {$O[1]};
echo " b_val: {$b_val}" >> {$O[1]};
echo " b_val1: {$b_val1}" >> {$O[1]};
echo " b_val2: {$b_val2}" >> {$O[1]};
echo "Advanced Parameters:" >> {$O[1]};
echo " a_val: {$a_val}" >> {$O[1]};
echo " a_val1: {$a_val1}" >> {$O[1]};
echo " a_val2: {$a_val2}" >> {$O[1]};
echo "Parameters in Sub:" >> {$O[1]};
echo " sub_val: {$sub_val}" >> {$O[1]};
End
1つずつ定義するときはそれぞれbasic param XXX, advanced param YYYのように記述します。(単にparam ZZZとしたときはbasic扱いとなります。)まとめて定義したいときはBasic Param ~ End, Advanced Param ~ Endで囲みます。このとき定義する変数名の先頭には$を付けて記述します。また使用するときは入力ファイルや出力ファイルと同様に{ }で括ります。
設定したパラメータはオプション--params="{XXX:N}"で値を設定して実行します。また、定義書内でデフォルト値を記述しておけば、--params内で設定しなかったパラメータもデフォルト値で処理されます。
今回はbasicのみに値を設定して実行してみます。
/Basic6$ pione-client ParamEcho.pione -b ParamEcho --params="{val:1,b_val:3,b_val1:5,b_val2:7}"
"{val:1,b_val:3,b_val1:5,b_val2:7}"
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
--> Distribution: &Anonymous:Root([],{})
>>> &Anonymous:Main([],{main_val:(<i>10)})
==> &Anonymous:Main([],{main_val:(<i>10)})
--> Rule Application: &Anonymous:Main([],{main_val:(<i>10)})
--> Distribution: &Anonymous:Main([],{main_val:(<i>10)})
>>> &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
==> &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
SH ------------------------------------------------------------
SH echo "Basic Parameters:" > message.txt;
SH echo " val: 1" >> message.txt;
SH echo " b_val: 3" >> message.txt;
SH echo " b_val1: 5" >> message.txt;
SH echo " b_val2: 7" >> message.txt;
SH echo "Advanced Parameters:" >> message.txt;
SH echo " a_val: 987" >> message.txt;
SH echo " a_val1: 975" >> message.txt;
SH echo " a_val2: 864" >> message.txt;
SH echo "Parameters in Sub:" >> message.txt;
SH echo " sub_val1: 10" >> message.txt;
SH echo " sub_val2: 100" >> message.txt;
SH ------------------------------------------------------------
<== &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
<-- Distribution: &Anonymous:Main([],{main_val:(<i>10)})
<-- Rule Application: &Anonymous:Main([],{main_val:(<i>10)})
<-- Distribution: &Anonymous:Root([],{})
<== &Anonymous:Main([],{main_val:(<i>10)})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
/Basic6$ cat ParamEcho/output/message.txt
Basic Parameters:
val: 1
b_val: 3
b_val1: 5
b_val2: 7
Advanced Parameters:
a_val: 987
a_val1: 975
a_val2: 864
Parameters in Sub:
sub_val1: 10
sub_val2: 100
/Basic6$
設定を反映した処理が実行できました。
基本7(制御文)
今回はif文を用いて実行したいルールを制御してみましょう。
param $val := 123
Rule Main
output 'message.txt'
Flow
if $val % 2 == 0
rule Even
else
rule Odd
end
End
Rule Even
output 'message.txt'
Action
echo "{$val} is even." > message.txt;
End
Rule Odd
output 'message.txt'
Action
echo "{$val} is odd." > message.txt;
End
このファイルはこちらからダウンロードできます。
最初のパラメータvalに対して、偶数の場合と奇数の場合でそれぞれ別のルールを実行するようにしています。
Rule Mainにif文がありますが、PIONEの記述ではif ~ endと記述します。case文の場合は下記のように記述します。
case $val % 2 when 0 rule Even else rule Odd end
では、valをデフォルト値で実行してみましょう。
/Basic7$ pione-client EvenOdd.pione -b EvenOdd
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
--> Distribution: &Anonymous:Root([],{})
>>> &Anonymous:Main([],{})
==> &Anonymous:Main([],{})
--> Rule Application: &Anonymous:Main([],{})
--> Distribution: &Anonymous:Main([],{})
>>> &Anonymous:Odd([],{})
==> &Anonymous:Odd([],{})
SH ------------------------------------------------------------
SH echo "123 is odd." > message.txt;
SH ------------------------------------------------------------
<== &Anonymous:Odd([],{})
<-- Distribution: &Anonymous:Main([],{})
<-- Rule Application: &Anonymous:Main([],{})
<-- Distribution: &Anonymous:Root([],{})
<== &Anonymous:Main([],{})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
/Basic7$ cat EvenOdd/output/message.txt
123 is odd.
/Basic7$
val=123で定義されていますので、この場合は奇数のルールが動いているのが分かります。
次にvalを偶数にして実行してみます。
/Basic7$ pione-client EvenOdd.pione -b EvenOdd --params="{val:456}"
"{val:456}"
==> &Anonymous:Root([],{})
--> Rule Application: &Anonymous:Root([],{})
--> Distribution: &Anonymous:Root([],{})
>>> &Anonymous:Main([],{})
==> &Anonymous:Main([],{})
--> Rule Application: &Anonymous:Main([],{})
--> Distribution: &Anonymous:Main([],{})
>>> &Anonymous:Even([],{})
==> &Anonymous:Even([],{})
SH ------------------------------------------------------------
SH echo "456 is even." > message.txt;
SH ------------------------------------------------------------
<== &Anonymous:Even([],{})
<-- Distribution: &Anonymous:Main([],{})
<-- Rule Application: &Anonymous:Main([],{})
<-- Distribution: &Anonymous:Root([],{})
<== &Anonymous:Main([],{})
<-- Rule Application: &Anonymous:Root([],{})
<== &Anonymous:Root([],{})
/Basic7$ cat EvenOdd/output/message.txt
456 is even.
/Basic7$
偶数のルールが動きます。
以上のようにして条件に合わせてルールを制御することができます。
基本8(インタラクティブ操作)
ここまではコマンドを実行すると、結果出力までの動作を全てプログラムに委ねる一方通行の処理を記述していきました。次は処理の途中でユーザが操作できるようにして、以降の処理を制御(インタラクティブ操作)できるようにしてみましょう。実行ファイル作成のためにパッケージを、実行のためにpione-webclientを利用します。例としてはウェブページから選択した入力ファイルに対して、設定した演算子(+, *)、数値で計算したファイルを出力する処理を作成します。
まず、インタラクティブのための処理をpione定義書に記述します。
.@ PackageName :: "InteractiveCalc" Rule Main output '*.out'.all Flow rule Interaction End Rule Interaction output '*.out'.all Action # build public directory for pione-interactive mkdir public cp etc/* public cp bin/* public echo "Interruption!" > result.log.out # start interactive operation pione-interactive browser --public public cp public/* . echo "finish!" > result.log.out End
今回は.outファイルを出力ファイルとしてダウンロードできるようにしています。pione-interactive browserコマンドにてInteractiveページを開けるようにします。このとき--publicでページのために使用するhtmlファイルやcgiファイルなどがあるディレクトリを指定します。そのために事前にpublicディレクトリを用意し、etcディレクトリ内とbinディレクトリ内のファイルをそれぞれコピーしています。また、Interactiveページを終了した後は作成したファイルを処理できるようにpublicディレクトリ内のデータを全てワークスペースへコピーしています。
次に、ウェブページにて最初に呼び出されるindex.htmlを作成します。このファイルはetcディレクトリ内に置きます。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>InteractiveCalc Index Page</title> </head> <body> <form action="FileCalc.cgi" method="post" enctype="multipart/form-data"> 入力ファイル名 <input type="file" name="inputfile"/> <br> 演算子 <select name="operator"> <option value="add">+</option> <option value="mul">*</option> </select> 数値 <input type="text" name="value"/> <br> 出力ファイル名 <input type="text" name="outputfile"/> <br> <button type="submit">計算開始</button> </form> <br> <br> <a href="?pione-action=finish">終了</a> </body> </html>
そして、.cgiファイルを作成します。シェルスクリプトやEosのコマンドなどの特殊な処理はこのファイルに記述します。このファイルはbinディレクトリ内に置きます。
#!/usr/bin/env ruby require 'cgi' cgi = CGI.new HTMLstr="" # Main Process ## Query to Parameters input_file=cgi.params['inputfile'][0] output_path=cgi['outputfile'] operator=cgi['operator'] value=cgi['value'].to_i ## Calc input to output output_file = open(output_path, "w") HTMLstr += "<table>" HTMLstr += "<tr>" HTMLstr += "<td>" + input_file.original_filename.to_s + "</td>" HTMLstr += "<td>" + output_path + "</td>" HTMLstr += "</tr>" input_file.each do |input_line| HTMLstr += "<tr>" HTMLstr += "<td>" + input_line + "</td>" work_value = input_line.to_i case operator when "add" work_value += value when "mul" work_value *= value end output_line = work_value.to_s output_file.write(output_line + "\n") HTMLstr += "<td>" + output_line + "</td>" HTMLstr += "</tr>" end HTMLstr += "</table>" output_file.close # Output as html cgi.out(type: "text/html") do HTMLstr end
今回はRubyを使って記述しています。# Output as html以下のコードでhtml形式に変換するようにしています。そのためにCGIクラスが必要なので、# Main Process以上のコードが必要です。このようにすると、# Main Process内でHTMLstrに記述する文字列を加えるだけで、自由な処理を行った上でhtmlのページを表示することができます。また、## Query to ParametersにてPOST形式のクエリを変数に変換していますが、typeがfileの場合、ファイル本体はCGIクラスメンバーParamsから受け取ります。このとき受け取るデータはファイルポインタ配列ですので、取得したい要素No.を指定する必要があります。今回、入力ファイルは1つしか格納していないので、cgi.params['inputfile'][0]となります。
コードが完成したら、実行のためのパッケージを作成します。(PIONEチュートリアル-packageを参照)
/PIONE$ pione package build Basic8/ info: update the package info file: local:/Eos/tutorial/SampleCode/PIONE/Basic8/pione-package.json info: Package local:/Eos/tutorial/SampleCode/PIONE/InteractiveCalc.ppg has been built successfully.
作成したパッケージはpione-webclientにて動かすことができます。PIONE Webclientチュートリアル#インタラクティブ操作を含むコマンドを実行にて動作方法と実行結果を記載しています。
index.htmlとFileCalc.cgiで作成したページ
![]() |
-> | ![]() |
応用
さて、ここまでの基本を組み合わせて、次の問題を考えてみましょう。
応用1(フローの制御)
問題1
但し、*.parametersは以下のフォーマットで切り出しサイズ(Sx, Sy, Sz)を格納しているファイルとします。
Sx Sy Sz
応用問題1:上記のCenterGet.pioneを作成してみましょう。
解答例
Rule Main
input '*.mrc'.all
output '*.roi'
Flow
rule First
rule Second
End
Rule First
input '*.mrc'
output '{$I[1]}.info'
Action
mrcImageInfo -I -i {$I[1]} \
| head -2 | tail -1 \
| awk '{printf("%s %s %s", $3, $4, $5)}' \
| tr -c '[0-9]' ' ' \
> {$O[1]}
End
Rule Second
input '*.mrc'
input '{$I[1]}.info'
input '{$I[1][1]}.parameters'
output '{$I[1][1]}.roi'
Action
Center_x=$(awk '{printf("%s\n", $1)}' {$I[2]})
Center_y=$(awk '{printf("%s\n", $2)}' {$I[2]})
Center_z=$(awk '{printf("%s\n", $3)}' {$I[2]})
N_x=$(awk '{printf("%s\n", $1)}' {$I[3]})
N_y=$(awk '{printf("%s\n", $2)}' {$I[3]})
N_z=$(awk '{printf("%s\n", $3)}' {$I[3]})
mrcImageCenterGet -i {$I[1]} -o {$O[1]} \
-Cx $Center_x -Cy $Center_y -Cz $Center_z \
-Nx $N_x -Ny $N_y -Nz $N_z
End
このファイルはこちらからダウンロードできます。
主にmrcImageCenterGetを用いて切り出しを行っています。
Main: .mrcファイルについてFirst, Secondの処理を行います。 First: .mrcファイルの最大値の座標を得て、.mrc.infoファイルに格納します。 Second: .mrcから.mrc.infoの座標を中心に.parametersのサイズで切り出しを行います。
実行結果
入力ファイル
mrcファイル1
Min: 0 (0, 0, 0) Max: 3398.12 (23, 55, 41) Mean: 72.129 SD: 294.805 SE: 0.368507 Sum: 4.61626e+07
最大値の座標: (23, 55, 41)
Min: -2390.42 (77, 14, 69) Max: 4374.62 (17, 62, 46) Mean: 72.1487 SD: 577.82 SE: 0.722275 Sum: 4.61752e+07
最大値の座標: (17, 62, 46)
parametersファイル1, 2の両方を下記に設定して実行してみます。
40 40 50
コマンド
pione-client CenterGet.pione -i CenterGetInput/ -b CenterGet
実行結果
1VOM.roi, 1VOM-N.roiのファイルが出力されます。
mrcImageInfoを使って、最大値の座標が中心になっているか確認してみます。
/Advanced1$ mrcImageInfo -I -i CenterGet/output/1VOM.roi Min: 0 (0, 0, 0) Max: 3398.12 (19, 19, 24) Mean: 241.805 SD: 500.988 SE: 1.77126 Sum: 1.93444e+07 /Advanced1$ mrcImageInfo -I -i CenterGet/output/1VOM-N.roi Min: -1893.8 (0, 4, 33) Max: 4374.62 (19, 19, 24) Mean: 157.55 SD: 648.124 SE: 2.29147 Sum: 1.2604e+07
最大値を中心に切り出していることが分かります。
補足(ファイルの確認用コマンド)
出力したファイルと入力ファイルの違いを見るために後処理を追加します。
作成例
Rule Main
input '*.mrc'.all
output '*.roi'
output '*.tiff'
Flow
rule First
rule Second
rule Final
End
-中略-
Rule Final
input '*.mrc'
input '{$I[1][1]}.roi'
output '{$I[1]}-1.tiff'
output '{$I[2]}-1.tiff'
output '{$I[1]}-2.tiff'
output '{$I[2]}-2.tiff'
Action
mrcImageProjection -i {$I[1]} -o {$I[1]}.2d
mrc2tiff -i {$I[1]}.2d -o {$O[1]}
mrcImageProjection -i {$I[1]} -o {$I[1]}.2d1 -m 1
mrc2tiff -i {$I[1]}.2d1 -o {$O[3]}
mrcImageProjection -i {$I[2]} -o {$I[2]}.2d
mrc2tiff -i {$I[2]}.2d -o {$O[2]}
mrcImageProjection -i {$I[2]} -o {$I[2]}.2d1 -m 1
mrc2tiff -i {$I[2]}.2d1 -o {$O[4]}
End
これにより入力ファイル(mrc), 出力ファイル(roi)についてxy平面とyz平面へ投影した画像をtiffファイルで出力します。
出力ファイル
roiファイル1
roiファイル2
後処理をしたくないときはMainのoutputとFlow内をコメントアウトするだけです。
Rule Main input '*.mrc'.all output '*.roi' # output '*.tiff' Flow rule Initial rule First rule Second # rule Final End -後略-
補足(デフォルトサイズの設定)
.parametersなどのファイルがない場合にパラメータ入力で設定したい場合の前処理を追加します。
作成例
Basic Param
$Nx := 40
$Ny := 40
$Nz := 50
End
Rule Main
input '*.mrc'.all
input '*.parameters'
output '*.roi'
output '*.tiff'
Flow
rule Initial
rule First
rule Second
rule Final
End
Rule Initial
input '*.mrc'
output '{$I[1][1]}.parameters'
Action
echo "{$Nx} {$Ny} {$Nz}" > {$O[1]}
End
-後略-
このようにすると、.parametersがない場合にはオプション--paramsまたはデフォルト値(40, 40, 50)によってサイズを設定することができます。但し、入力ファイルに.parametersが1つもない場合は動きません。その場合はtouchなどを使用して.parameterを1つは用意する必要があります。
1VOM.parametersのみ下記のように設定してみます。
64 64 64
Nx=80のみをパラメータとしてコマンド入力してみます。
/Advanced1$ pione-client CenterGet.pione -i ${EOS_HOME}/tutorial/SampleData/ -b CenterGet --params="{Nx:80}"
出力ファイル
1VOM.roiはファイルに従ったサイズになっています。
1VOM-N.roiはNxがコマンドの引数、Ny, Nzはデフォルト値のパラメータによるサイズになります。
応用2(ウェブページでパラメータを設定しながらコマンドを実行)
次は#基本8(インタラクティブ操作)のようにウェブページの操作を行い、Eosのコマンドを実行する処理を作ってみましょう。
問題2
2Dファイル(mrcImage)を入力ファイルとし、ローパスフィルタ(mrcImageLowPassFilter)をウェブページの操作によって実行するコマンドを考えます。入力ファイルはファイル選択画面から自由に選べるようにし、オプションの値も画面操作で設定できるようにします。コマンド実行後は入力ファイルと出力ファイルの画像ファイルをそれぞれ表示して比較できるようにします。この画面を見ながら再設定するか、終了するかを選び、終了後に最後の変換ファイルを出力ファイルとして得られるようにします。
応用問題2:上記の機能を持つパッケージLowPassFilter.ppgを作成してみましょう。
解答例
作成するファイルは大きくpione定義書、htmlファイル、cgiファイルに分かれます。
pione定義書
メイン(Main.pione)の処理を定義します。今回は設定のほとんどをウェブページに委ねるのでここでは出力ファイル名のみ定義しています。
Rule Main output '*.lpf'.all Flow rule Interaction End
インタラクティブ(Interaction.pione)の処理を定義します。このファイルは再利用できるように拡張子が付いた全てのファイルを呼び出し元に出力できるようにしています。
Rule Interaction output '*.*'.all Action # build public directory for pione-interactive mkdir public cp etc/* public cp bin/* public # start interactive operation pione-interactive browser --public public cp public/* . End
アノテーション(Annotation.pione)を定義します。
.@ PackageName :: "LowPassFilter" .@ Editor :: "Kinoshita" .@ Tag :: "v0.1.0"
htmlファイル
Index.htmlはコマンド選択画面にしています。ここから実行したいコマンドを選択します。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>InteractiveCommand Index Page</title> </head> <body> <a href="mrcImageLowPassFilter.html">mrcImageLowPassFilter</a> <br> <br> <a href="?pione-action=finish">終了</a> </body> </html>
mrcImageLowPassFilter.htmlでコマンドや入力ファイルなどのオプションを設定できるようにします。開始ボタンでmrcImageLowPassFilterが実行されます。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>mrcImageLowPassFilter</title> </head> <body> <form action="mrcImageLowPassFilter.cgi" method="post" enctype="multipart/form-data"> <button type="submit">開始</button> <br> <table> <tr> <td> <input type="file" name="InputFile"/> </td> <td> 入力ファイル名(-i) </td> </tr> <tr> <td> <input type="text" name="OutputFile" value="outdata.lpf"/> </td> <td> 出力ファイル名(-o) </td> </tr> <tr> <td> <input type="text" name="HalfValuePoint" value="1.0"/> </td> <td> HalfValuePoint[A-1] (強度を半分に落とす空間周波数を示す)(-hvp) </td> </tr> <tr> <td> <input type="text" name="Width" value="1.0"/> </td> <td> 空間周波数のcos関数の幅を設定(-w) </td> </tr> <tr> <td> <select name="Mode"> <option value="1">1: ステップフィルタ</option> <option value="2">2: cosフィルタ</option> <option value="3">3: expフィルタ</option> <option value="4">4: ガウシアンフィルタ</option> <option value="5">5: ローレンツ型フィルタ</option> </select> </td> <td> モード(-m) </td> </tr> </table> </form> <br> <br> <a href="?pione-action=finish">終了</a> </body> </html>
cgiファイル







