はじめに
その計算、AiiDAで自動化してみませんか?
この連載では、第一原理計算などの計算科学の分野で注目されはじめているツール「AiiDA」について解説していきます。
AiiDAは、計算科学の現場で、複雑なワークフローの自動化やデータ管理、再現性の確保をサポートしてくれる頼もしいツールです。
これから計算に挑戦しようという初心者の方も、スクリプトを書いてガンガン計算を回してしているヘビーユーザーの方も、ぜひ一度チェックしてみてください!
第2回のこの記事では、さっそくAiiDAの使い方を紹介していきたいと思います。
今回の目標は、お試し環境を構築し、$f(x,y,z)=(x \times y)+z$ という計算をAiiDAを使って実行してみることです。
とても簡単な計算ですが、掛け算 $w=(x \times y)$ の後に足し算 $f=w+z$ を計算する、 れっきとした計算ワークフローの一種 とみることができます。
この簡単な計算を例として、AiiDAの操作を体験しながら、どのようにワークフローを実行し、データを蓄積していくのか基本的なコンセプトを見ていきましょう。
お手元のノートPCでも実行できますので、試しに動かしてみて下さいね。
来歴モデル -『計算ワークフロー』の表現形式
手を動かす前に、『計算ワークフロー』がどのようなデータ形式で表現され、Pythonオブジェクトとして操作され得るのかについて見ておきましょう。
完璧に憶えておく必要はないのですが、ここを軽く押さえておくと後のチュートリアルで行うデータ操作のイメージが付きやすくなると思います。
AiiDAは計算ワークフローを 来歴 (Provenance) グラフと呼ばれる、次の図に示すようなグラフ構造を持つデータとして表現します。
大まかには、楕円がデータで、長方形が計算処理(プロセス)を表しており、計算ワークフローはこれらが因果関係を持って連なったものとして定義されます。
つまり、 来歴グラフとは「データとプロセスの2種類のノード (頂点)」と、「ノード間の入出力関係に基づいてラベル付けされたリンク (辺)」からなる非有向巡回グラフ (DAG) である といえます。
AiiDAはこの来歴グラフモデルに基づき、計算結果を得るまでに辿った全ての処理(来歴)を管理していきます。この管理方法は計算の再現性と検索効率の向上において有利とされます。
図のグラフより、ノードが種類に応じて色分けされているのが分かるかと思います。
これらには次のような役割があります:
- DataNode (緑色の楕円) 入出力データに対応するノード (数値、文字列、辞書型など)
-
ProcessNode (長方形) 計算処理 (プロセス) に対応するノード
- WorkChainNode (橙色の長方形): 複数のProcessNodeをまとめたノード (=親プロセス)
- CalcFunctionNode (桃色の長方形): Python上で定義された計算処理に対応するノード
- CalcJobNode (赤色の長方形): 外部計算コードによる計算処理に対応するノード
また、ノード同士をつなぐ リンク (矢印) には、それぞれのノード間の因果関係(入力、関数呼び出し、戻り値、等)が保持されています。
実は、上の図は今回の目標である $f(x,y,z)=(x \times y)+z$ に対応する来歴グラフです。
例えば橙色の長方形 (親プロセス) に $2, 3, 5$ の3つの数値がそれぞれ$x, y, z$として渡され (INPUTのリンクでつながって)、 掛け算の計算ジョブ と 足し算の計算ジョブ が呼び出されて (CALLのリンクでつながって) いるのが分かるかと思います。
実際のユースケースにおいては計算課題に応じて各ノードをデザインすることになります。
特に新規のワークフローを作る際には、その "レシピ" とでもいうべきWorkChainNodeの設計が主な作業になります。
座学はこれまでにして、ここからは手を動かしていきましょう。
$f(x,y,z)=(x \times y)+z$ についてこれらのプロセスを一つずつ実装していき、最終的に同じような来歴グラフになっていることを確認していきます。
お試し環境の構築
まずはAiiDAを動かすための第一歩として、インストールと初期設定を行っていきましょう。
ここではチュートリアルのための簡易版インストールを行うことにします。実運用のための本番環境の作り方は後の連載で解説します。
使用するシステムの前提条件として、次を想定します:
- Linux OS (Ubuntu推奨, WSLも可) または MacOS X
- Python (>= 3.9)
WindowsユーザーはWSL (Winsows Subsystem for Linux) のUbuntuの使用が推奨です。
この記事では Ubuntu22.04 LTS を想定して話を進めていきます。また、ログインシェルはbashを想定します。
他の環境に構築する場合は、コマンドを適宜読み替えてください。
AiiDAのインストール
インストールを行う前に、PythonにAiiDA用の仮想環境を作りましょう。
mkdir ~/envs #仮想環境用のディレクトリをホームディレクトリ直下に作成
python -m venv ~/envs/aiida #AiiDA用の仮想環境を作成
source ~/envs/aiida/bin/activate #仮想環境を起動
仮想環境はターミナルを閉じるとリセットされるので、別なターミナルでAiiDA環境に入るには上記の
source ~/envs/aiida/bin/activate
コマンドを再度実行してください。
AiiDAのメインプログラム (aiida-core
) は通常のPythonパッケージと同様、 pip (または conda) を使って PyPI (Python Package Index) より簡単に取得できます。
pip install aiida-core[atomic_tools]
これでインストールができました。
ここでは後のチュートリアルで ASE (Atomic Simulation Environment) の機能を使うため、オプション [atomic_tools]
を追加しています。
インストール後、以下のコマンドで verdi
コマンドのヘルプが表示されれば、インストールは成功です:
verdi help
この verdi
コマンドはAiiDAのコマンドラインインタフェースである Verdi CLI を表しています。
コマンドライン上では基本的にこの verdi
コマンドを介してAiiDAを操作することになります。
各種操作を実行するコマンドは verdi <command name>
という命名になっています。憶えておくのは大変なので、次の手順で補完を有効化しましょう。
補完の有効化
Verdi CLIはTab補完に対応しているので、有効化にしておきましょう。
次のコマンドを実行するとTab補完が有効になります。
eval "$(_VERDI_COMPLETE=bash_source verdi)"
bash以外のユーザは次を実行:
zshの場合 :eval "$(_VERDI_COMPLETE=zsh_source verdi)"
fishの場合:eval (env _VERDI_COMPLETE=fish_source verdi)
これで、verdi
に続くコマンドの候補を出力してくれるようになるはずです。
試しにverdi [Tab]
と打ってみましょう。
このコマンドで有効化したTab補完は、ターミナルを閉じるとリセットされてしまいます。
立ち上げるたびに毎回手打ちで入力するのは面倒なので、~/envs/aiida/bin/activate
にこの処理を書き加えておきましょう:
echo 'eval "$(_VERDI_COMPLETE=bash_source verdi)"' >> ~/envs/aiida/bin/activate
これで、今後AiiDA環境に入るたびにTab補完が有効化されるようになります。
簡易セットアップの実行
AiiDAのセットアップに移ります。
すぐに使い始めるため、ここでは verdi presto
コマンドを利用した簡易セットアップを実行します:
verdi presto
実行すると presto
という名前で簡易版のプロファイル (設定) が作成されます。
セットアップが正常に完了したかどうかは次のコマンドで確認できます:
verdi status
通常だと、次のような出力が得られるかと思います:
✔ version: AiiDA v2.6.3
✔ config: /home/devel/.aiida
✔ profile: presto
✔ storage: SqliteDosStorage[/home/devel/.aiida/repository/sqlite_dos_aa971c5a832f42ef8f518817f8a5a803]: open,
⏺ broker: No broker defined for this profile: certain functionality not available. See https://aiida-core.readthedocs.io/en/stable/installation/guide_quick.html#quick-install-limitations
⏺ daemon: No broker defined for this profile: daemon is not available. See {URL_NO_BROKER}
環境によっては broker
と daemon
の行の出力結果が異なるかもしれませんが、現時点では問題ありません。
その他の項目すべてにチェックがついていれば、簡易セットアップが正常に完了しています。
この簡易版インストールでは 管理者権限が必要なセットアップをスキップする代わりに一部機能が制限されています。具体的には、このチュートリアルでは非同期処理は行いません。また、SQLiteを使用するのでスケーラビリティに限界があります。本番環境のセットアップは次回解説します。
チュートリアル(基礎編)
この節の内容は、AiiDAの公式チュートリアル についていくつか補足しつつ日本語に訳したものです。
では準備ができたら実際に触ってみましょう。
ここでは対話型シェル (IPython) インターフェースを使って、インタラクティブに結果を確認しながら操作を見ていきます。
対話型シェルは verdi shell
コマンドにより起動できます。
実行すると、
verdi shell
Python 3.9.10 (main, Mar 2 2022, 12:02:00)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.34.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
のようにIPythonによる対話型シェルが立ち上がり、通常のIPythonと全く同じように使うことができます。
verdi shell
で起動すると、AiiDAの機能を利用するための環境設定や主要モジュールがあらかじめインポートされた状態で起動するので便利です。
Step 1: 入力データを作成しよう ― DataNodeの生成
対話型シェルを起動できたら、まずはワークフロー作成の第一歩として、入力データ $x=2, y=3, z=5$ に対応するDataNodeを作成してみましょう。
手始めに整数 2 を保持するDataNodeを生成してみます。
次のコマンドを実行してみましょう。
# Operation-Relation Mapper (ORM) ライブラリをインポート。
# ORMとは、Pythonオブジェクトとデータベースを相互に変換するライブラリ。
from aiida import orm
# 整数2を保持するDataNodeを作成
node = orm.Int(2)
# DataNodeをデータベースに保存
node.store()
整数値 $2$ を保持するDataNodeが生成されました。
orm.Int(n)
は文字通り整数 (Integer) 型の数値 n
を格納するDataNodeを生成するクラスです。
名前が紛らわしいですが、ここで生成したのはあくまで"整数値に対応するDataNode型"であり、Pythonの組込み型の int
とは異なります。
実際、node
の型をtype(node)
で見てみると、aiida.orm.nodes.data.int.Int
というオブジェクトであることが確認できます。
すぐ後に見るように、数値だけではなくデータ管理のための様々なメタデータも一緒に保持しています。
node
の中身を確認してみましょう。
# nodeの中身を表示
node
<Int: uuid: e3d8c5d6-8f55-40cd-813c-8a0f1d132ce4 (pk: 1) value: 2>
一番左に型 (Int)、一番右に value: 2
とあり、整数が格納されているのが分かります。他のフィールドは識別子です。
DataNodeの各フィールドの説明
-
Int
: 整数型のDataNodeであることを意味します。他にもFloat
やStr
、Dict
など様々な型が用意されていて、また自分でデータ構造を定義することもできます。 -
uuid
: Universally Unique Identifierと呼ばれる文字列で、インターネット上でデータ共有を行う際に、データを一意に決めるためのグローバルな識別子として利用されます1。
-
(pk: 1)
: pkはPrimary Key、つまり主キーのことで、いわばこのデータベースの中だけで一意なローカルな識別子です。 pkにはノードがデータベースに登録される度に順番に整数値が割り振られます。ちなみに、データベースに格納する前のノードには(unstored)
と表示されます。 -
value
: 格納されている値です。一度データベースに格納された値は変更できません。 (store()
時にImmutable属性が自動で付与されています)。
これを踏まえて、改めて先ほどお見せした来歴グラフを見直してみると、DataNodeを表す緑楕円の中に書かれている文字列や数は、これらの各要素と対応していることが分かります。例えば、
を例に挙げると、これはInt型で、UUID 6c9114f2...
で識別され、値 2
を持つDataNodeであることを示しています。
また、DataNode型のオブジェクトは値と識別子の他にも様々なメタデータを持っています:
Node型がもつメタデータの説明
-
ctime
: このノードが作成された時刻です。 -
mtime
: このノードが最後に変更された時刻です。 -
label
: ノードにつけられたラベルです。クエリの際に参照できるため便利です。 -
description
: ノードの説明(自由記述)です。 - etc.
DataNode型に限らず、Nodeと名の付くクラス (抽象クラス
Node
の派生クラス) は全て上記のメタデータを持ちます。
value
や識別子 (pk
,uuid
) は一度データベースにstore()
で格納してしまうと変更不可能でしたが、label
やdescription
はユーザが自由に設定でき、store()
後でも設定や変更が可能です:
node.label = 'value_x' # 生成したDataNode (store済)にラベルを設定
node.description = 'input for multiply' # 生成したDataNodeに説明を設定
ここまででDataNodeを作成してデータベースに格納する方法について解説しました。
では、DataNodeをデータベースから呼び出すにはどうすれば良いでしょうか?
データベースに格納したNode型のオブジェクトは、load_node
関数を使うことでアクセスが可能です。
x = orm.load_node(1) # 1は先ほど作成したDataNodeのpk (またはUUID)
x2=orm.load_node(label='value_x') # labelを使って呼び出すことも可能
load_node
の引数にNodeの識別子 (pk
または uuid
)を指定することで、対応するNodeを読み込むことができます。
また、ラベルを付与している場合はラベルを使って呼び出すこともできるため、繰り返し用いる可能性のあるデータには適当なラベルを設定しておくと便利です。
以上がDataNodeの作成および操作にかかる基本的な操作です。
Step 2: 関数を実行しよう ― CalcFunctionの定義
入力データが準備できたら、次は計算 $(x \times y)+z$ を実行します。
ここで"計算を実行する"といったとき、次の二通りの手続きのうちどちらかを実行することになります: (1) Pythonで定義された関数を実行する手続き (CalcFunction) 、もしくは (2) 外部で定義された何らかの計算コードを呼び出す手続き (CalcJob) です。
実際のユースケースでは、(1) はデータの前処理や後処理のようなPythonを使って行うことが多い計算、 (2) は電子状態計算のような外部コードを使った複雑かつ高パフォーマンスな計算が対応します。
今回の例のような簡単な計算では (1) だけで実装できてしまいますが、練習のためにあえてかけ算を(1) CalcFunction 、たし算を (2) CalcJob で実装してみましょう。
まずはかけ算 $x \times y$ を行う関数をPythonで定義してAiiDA上で利用してみます。
そのためには、かけ算関数 multiply
を次のように定義してみましょう:
from aiida import engine # AiiDA engine: 計算ジョブを管理するエンジンとのやり取りを行うためのモジュール。
@engine.calcfunction # CalcFunctionであることを宣言
def multiply(x, y): # 通常のかけ算関数
return x * y
通常の関数定義との違いは、def
の前にデコレーター @engine.calcfunction
がついていることです。
@engine.calcfunction
を付けて関数を定義することでAiiDAエンジンがその関数をCalcFunctionとして認識し、計算評価時に自動で ProcessNode を生成してデータベースに格納してくれるようになります。
関数を定義した段階ではデータベースには格納されない点に注意しましょう。"来歴"という言葉に象徴されるように、Nodeとして登録されるのは関数そのものではなく、実際に実行された計算処理なのです。
では、入力データのDataNodeを渡して関数 multiply
を実行してみましょう。
x = load_node(label='value_x') # xのロード
y = orm.Int(3) # yのDataNode作成
y.store() # yをデータベースに格納
multiply(x,y) # x,yを引数としてmultiply実行
<Int: uuid: 09af58b8-26c1-4ac3-8d82-532d9352d3ff (pk: 4) value: 6>
出力DataNode (uuid='09af58b8...'
) が生成され、計算結果 $2\times 3=6$ がデータベースに登録されました。
同時に、かけ算に対応する ProcessNode も登録されたはずです。どのような処理が行われたのかを確認してみましょう。次のコマンドを実行してみて下さい。
!verdi process list -a
verdi process list
は実行した計算プロセスを出力するためのVerdi CLIのコマンドです。verdi
は本来シェルで実行するコマンドですが、先頭に!
をつけて!verdi <コマンド名>
とIPython環境からでも実行できます。マジックコマンドといいます。
結果を見ると、
PK Created Process label ♻ Process State Process status
---- --------- ------------------------ --- ---------------- -----------------------------------
3 5s ago multiply ⏹ Finished [0]
のように、multiply
に対応するプロセスが実行され、既に終了している (Finished [0]
) 様子が見えます。
この処理に対応するProcessNodeには pk=3
が与えられているのが分かります。
プロセスの内容を確認するコマンドは !verdi process show <pk>
です。実行してみましょう。
!verdi process show 3
Property Value
----------- ------------------------------------
type multiply
state Finished [0]
pk 3
uuid 8c8258d4-bf84-47a2-b9fb-0fbd56dc1dd1
label multiply
description
ctime 2024-09-19 07:23:23.408558+00:00
mtime 2024-09-19 07:43:13.546392+00:00
Inputs PK Type
-------- ---- ------
x 1 Int
y 2 Int
Outputs PK Type
--------- ---- ------
result 4 Int
x
, y
を入力にとり、結果を pk=4
のDataNodeに格納しているのが分かります。
また、このProcessNodeにもDataNodeのときのように uuid
や pk
といった識別子が付与されていることが分かります。したがって、DataNodeと同様にload_node()
で呼び出すことができます。
load_node(3) # multiplyのNodeを読み込み
<CalcFunctionNode: uuid: 4e458eba-8d0e-44c1-9bc6-c588617e7c5d (pk: 3) (__main__.multiply)>
このノードは CalcFunctionNode
クラスのインスタンスであることが分かりますが、これはその名の通りCalcFunction、つまりPython関数の来歴を格納するクラスで、ProcesssNode
の派生クラスのひとつです。
また、DataNodeは値をvalue
として持っていましたが、ProcessNodeは関数名 (__main__.multiply
) を持っていることが分かるかと思います。
ProcessNodeの各フィールドの説明
-
CalcFunctionNode
: Nodeの型です。他のProcessNodeとしては、後に出てくるCalcJobNodeやWorkChainNodeがよく使用されます。 -
uuid, pk
: DataNodeと同様、ノードを特定するための識別子です。 -
(__main__.multiply)
: 実行された計算処理の名前です。この例では対話シェル上 (__main__
) で定義したmultiply
という関数を呼び出していることが分かります。
ProcessNodeのメタデータの説明
-
ctime
,mtime
,label
,description
: DataNodeと同様です。 -
process_state
: プロセスの状態。実行中ならrunning
、終了していればfinished
となります。詳細は下の折り畳みをご参照ください。 -
exit_status
: プロセスの実行結果です。正常終了の場合は0
となり、異常終了の場合は対応するエラーコードが返されます。 -
process_label
: プロセス名を表すラベルです。通常のlabel
と異なり、ユーザ側での変更は不可能です。
process_stateの一覧
Process state | 実行状態 | 説明 |
---|---|---|
created |
実行中 (active) | プロセスが生成され、実行前の状態 |
running |
実行中 (active) | プロセス実行中 |
waiting |
実行中 (active) | 待機中 (キュー待ちなど) |
killed |
終了 (terminated) | 割込み命令により強制終了された状態 |
excepted |
終了 (terminated) | 例外により途中で終了した状態 |
finished |
終了 (terminated) | プロセスが全て終了した状態 |
さて、これでひとつの計算が完了しました。
せっかくなのでこれを来歴グラフとして出力してみましょう。
Verdi CLIのコマンド'verdi node graph generate 'を使うと簡単にできます。
再びIPythonを閉じ、このコマンドを実行してみましょう。
exit() # IPythonを終了
# multipleの来歴グラフをpng形式で、multiply.pngというファイル名で出力
verdi node graph generate 3 -f png -O multiply.png
eog multiply.png # 適当なビューワーで描画
なお、次のようにするといちいちIPythonとコマンドラインを切り替えなくても来歴グラフを出力できます。
IPythonfrom aiida.tools.visualization import Graph graph = Graph() calc_node = orm.load_node(3) # multyplyを呼び出す graph.add_incoming(calc_node, annotate_links="both") graph.add_outgoing(calc_node, annotate_links="both") graph.graphviz.view() # graph.graphviz # Jupyter Notebookの場合はview()は不要
計算がちゃんと進行していれば、次のような来歴グラフが出力されるかと思います。
このグラフは入力値 2
と 3
に対応するDataNodeをかけ算に対応する CalcFunction である multiply
に渡し、計算結果 6
に対応する1つのDataNodeを出力として生成する様子を表しています。
四角形はCalcFunctionNodeの名前と状態を表しており、 multiply
について、実行時にUUID 8c8258d4-...
が付与され、正常終了(Exit Code: 0, State: finished
)していることを表しています。
Link (矢印) のラベルにも注目してみて下さい。2
と 3
と multiply
の間はINPUT_CALC
の関係で結ばれている、すなわち2
と 3
は計算プロセス multiply
の入力データであり、それぞれx
, y
の引数に対応することが読み取れます。一方で、multiply
の出力として6
のDataNodeがresult
として生成されましたが、こちらにはCRATE
、すなわち新たなデータを生成したことを表すラベルがついています。
この図がAiiDAにおける一回の計算の基本形です。
どんな複雑な計算であっても入出力データが異なるだけで、基本的に 「入力DataNodeとCalcFunction (またはCalcJob) を定義し、出力として新しいDataNodeを得る」 という構造に変わりはありません。
Step 3:計算コードを実行しよう - CalcJobの定義
Python上で完結するような処理であればCalcFunctionを実装すれば済みますが、前節でも触れたように実際の科学計算では外部で用意した何らかの計算コード-FortranやCのような高パフォーマンスな言語で実装しコンパイルして作成した実行形式ファイル-を高性能計算機上で実行することがほとんどでしょう。
このような外部の計算コードによる計算はAいiDAではCalcJobというプロセスとして定義されます。
ここではこれを実行する手順について見ていきましょう。簡単な例として、この節では足し算 $x+y$ を行うシェルスクリプトをCalcJobとして定義し、呼び出して計算してみます。
CalcJobを実行するにはプラグインと計算コードのふたつの要素を与える必要があります。これらを順に解説していきます。
ここからしばらく Verdi CLI を多用するため、IPythonはいったん閉じておきましょう。
exit()
プラグイン
CalcJobは全てプラグイン(拡張プログラム)として用意されたものを呼び出して使います。
プラグインには外部コードとのインタフェース、例えば入力ファイルの作り方や出力ファイルのパーサーが定義されています。
ある計算パッケージをAiiDAに組み込む場合、まずはプラグインがあるかどうかを探す(なければ自作する)ことになります。
インストール済みのプラグインの一覧は verdi plugin list aiida.calculations
で確認することができます。
verdi plugin list aiida.calculations
Registered entry points for aiida.calculations:
* core.arithmetic.add
* core.shell
* core.templatereplacer
* core.transfer
Report: Pass the entry point as an argument to display detailed information
出力結果に core.arithmetic.add
というプラグインがあるかと思います。これはAiiDAにデフォルトで用意されているbashでたし算を行うためのチュートリアル用プラグインで、今回はこれを使います。
プラグインの使い方は、verdi plugin list aiida.calculations <プラグイン名>
で確認できます。
verdi plugin list aiida.calculations core.arithmetic.add
長くなるので表示は省略しますが、入力と出力、そしてExit codesの一覧を確認できます。
補足:Calculationsプラグインのおおまかな仕組み
今回はプラグインの中身についての説明は省略しますが、大まかには次のような処理を行っていると思ってください:
- 入力データ (Int, Float, Dict, Str, …)を受け取り、インプットファイルを作成する。
- 計算コードを適切なコマンドラインパラメータで実行する。
- プロセスが終了したら出力結果をパースする。
- 結果に応じて、適切なExit codeを返す。
従って、プラグインを改変・自作する場合はこれら各ステップに対応する要素を設計することになります:
- 入力ファイルのフォーマット
- コマンドラインパラメータのフォーマット
- 出力結果のパーサー
- Exit code
詳細はまた回を改めて解説します。
計算機と外部計算コードの登録
次に、計算コードについて解説します。
外部計算コードを実行する場合、そのコードの実行ファイルとインストールされている計算機の情報をCodeNodeとしてデータベースに登録する必要があります。(その名の通り、Nodeとして入力データの一部とみなされます)。
プラグインはコードを動かすための設計図(論理構造)で、CodeNodeがインスタンスだと思っておいてください。
CodeNodeの作成は次の手順で行います:
- 計算機をデータベースに登録 (登録済みの場合省略)
- 計算機にインストールされている計算コードをCodeNodeとして登録
まずは計算機を確認して、必要であればデータベースに登録しましょう。
verdi computer list
で登録済みの計算機一覧を表示できます。
verdi computer list
Report: List of configured computers
Report: Use 'verdi computer show COMPUTERLABEL' to display more detailed information
* localhost
デフォルトではAiiDAを実行しているコンピュータがlocalhost
という名前で登録されています。
導入の手順によっては存在しない場合もあるので、何も表示されない場合は次のコマンドを実行して登録してください。
verdi computer setup -L localhost -H localhost -T core.local -S core.direct -w `echo $PWD/aiida_workdir` -n
verdi computer configure core.local localhost --safe-interval 1 -n
ここで使ったコマンドの詳細については、リモート計算機サーバーを計算機として登録する手順と併せて別の回に改めて解説します。
登録された計算機は次のコマンドで確認できます:
verdi computer show localhost
--------------------------- ------------------------------------
Label localhost
PK 1
UUID f4fd16f8-...
Description
Hostname localhost
Transport type core.local
Scheduler type core.direct
Work directory /home/hogehoge/work
Shebang #!/bin/bash
Mpirun command mpirun -np {tot_num_mpiprocs}
Default #procs/machine 1
Default memory (kB)/machine 8388608
Prepend text
Append text
--------------------------- ------------------------------------
計算機は専用のデータベースに保存され、Nodeとは別に管理されます。
ここで表示されたPKは計算機DB内の識別子であり、これまでのNode用DBのPKとは独立に割り当てられます。
登録した計算機について、適切に設定されているかどうか verdi computer test <label>
でテストすることができます。
verdi computer test localhost
Report: Testing computer<localhost> for user<yuta-yahagi@nec.com>...
* Opening connection... [OK]
* Checking for spurious output... [OK]
* Getting number of jobs from scheduler... [OK]: 27 jobs found in the queue
* Determining remote user name... [OK]: yahagi
* Creating and deleting temporary file... [OK]
* Checking for possible delay from using login shell... [OK]
Success: all 6 tests succeeded
エラーが出ていなければOKです。エラーが出た場合は設定を見直してみてください。
次はたし算を行う計算コードを登録しましょう。
ここでは シェルの算術式 echo $((x+y))$
を一種の計算コードだと思うことにして、これをCalcJobとして登録します。
基本的には、 (1) コードがインストールされている計算機、 (2) インストールされている場所への絶対パス、 (3) コードのプラグインの情報を登録することになります。
verdi code create core.code.installed
と実行して、画面の指示に従って次のように入力してください:
verdi code create core.code.installed
Report: enter ? for help.
Report: enter ! to ignore the default and set no value.
Computer: localhost
Filepath executable: /bin/bash
Label: add
Description []:
Default `CalcJob` plugin: core.arithmetic.add
Escape using double quotes [y/N]:
Success: Created InstalledCode<5>
これにより、たし算 (core.arithmetic.add) を行うlocalhost 上のコード (/bin/bash
) がadd
というラベルでCodeNodeとして登録されました。
CodeNodeの詳細情報は verdi code show <label>
で確認できます。
今しがた追加したコードの情報を表示してみましょう。
verdi code show add@localhost
-------------------- ------------------------------------
PK 5
UUID 394a527f-...
Label add
Description
Default plugin core.arithmetic.add
Type remote
Remote machine localhost
Remote absolute path /bin/bash
Prepend text
Append text
-------------------- ------------------------------------
設定したとおりになっているか確認してみてください。
独自のデータベースを持っていた計算機と違い、こちらはNodeデータベースに格納されることに注意して下さい。
CodeNodeは実はDataNodeの派生クラスの一つで、インスタンスはNodeのDBで管理されています。
AiiDAの来歴モデルにおいては、計算コードの実行環境のメタデータ (例えばビルドバージョンやコア数、メモリサイズなど) の情報も入力データの一種とみなされるためです。
CodeNodeについても正常に動作するかどうか確認しましょう。
これはverdi code test <label>
で行えます。
verdi code test add@localhost
Success: all tests succeeded.
Successと出れば正常です。
これで外部コードを用いてたし算を計算する準備が整いました。
入力データの作成と外部計算コードの実行
コンピュータとコードの設定が完了したら、再びIPythonインタフェースを起動しましょう。
verdi shell
前節で登録した add
コードをCodeNodeとして呼び出します。
from aiida import orm, engine
code = orm.load_code(label='add@localhost') # コードラベル@計算機ラベル で呼び出せる。
code # codeノードの情報を表示
<Code: Remote code 'add' on localhost, pk: 5, uuid: 394a527f-705b-4cac-95a6-56d3d2befafb>
出力を見ると、コンピュータ localhost
上で実行される add
という名前のCodeNodeが呼び出されていることが分かります。
入力データの作成には、コードノードの中の get_builder
というメソッドを用いるのが便利です。
builder = code.get_builder() # builderの作成
builder.x = orm.load_node(4) # 前節のmultiplyの結果
builder.y = orm.Int(5)
builder # builderの中身を表示
Process class: ArithmeticAddCalculation
Inputs:
code: add@localhost
metadata:
options:
stash: {}
monitors: {}
x: 6
y: 5
builder
はCalcJobNodeに入力データを引き渡すためのインタフェースクラスで、端的にいうと 計算ジョブの内容と入力をひとまとめにしたもの です。
例えば add
が x
という引数をもつ関数だった場合、builder.x = orm.Int(1)
のように、入力に対応するDataNodeをアサインしていきます。
出力からは、$x=6$ (前節のmultiply
の結果)と $y=5$ がアサインしていることが分かると思います。
計算ジョブは入力がアサインされたbuilderをengine.run()
関数に渡すことによって実行できます。
engine.run(builder) # 計算を実行
{'sum': <Int: uuid: e5d7850d-c25f-449c-99f8-40c66ea73905 (pk: 10) value: 11>,
'remote_folder': <RemoteData: uuid: 94be139f-6d54-4535-98aa-a1c0583aad29 (pk: 8)>,
'retrieved': <FolderData: uuid: 469e36fe-45c4-457a-b861-1a3014a30221 (pk: 9)>}
出力結果を見ると、いくつかのNodeがdict形式で返ってきていることに気づきます。
このうち 'sum'
に格納されているのが出力データ (6+5=11
) に対応するDataNodeです。
'remote_folder'
は作業ディレクトリのパスが、'retrived'
には作業ディレクトリから回収した出力ファイルが入っています。
どのようなプロセスが実行されたのか、multiply
のときと同様に!verdi process list -a
で確認します。
!verdi process list -a
PK Created Process label ♻ Process State Process status
---- --------- ------------------------ --- --------------- ----------------
3 1h ago multiply ⏹ Finished [0]
7 1s ago ArithmeticAddCalculation ⏹ Finished [0]
Total results: 2
Report: ♻ Processes marked with check-mark were not run but taken from the cache.
Report: Add the option `-P pk cached_from` to the command to display cache source.
Report: Last time an entry changed state: 0s ago (at 05:56:12 on 2024-11-06)
Warning: This profile does not have a broker and so it has no daemon. See https://aiida-core.readthedocs.io/en/stable/installation/guide_quick.html#quick-install-limitations
Total results: 2
(...)
のようになっているかと思います。
前回の計算 (multiply
) に加え、新たに ArithmeticAddCalculation
の計算プロセスが実行され終了していることが読み取れます。
計算プロセスはPK=7のNodeとして保存されているので、中身を確認してみましょう。
今回はIPythonではなく、Verdi CLIの機能であるverdi process show <pk>
コマンドを使ってみます:
!verdi process show 7
$ verdi -p tutorial process show 7
Property Value
----------- ------------------------------------
type ArithmeticAddCalculation
state Finished [0]
pk 7
uuid 183186bc-afd8-4f9c-9d7b-c5d59fee7cc9
label
description
ctime 2024-09-19 10:07:20.150300+00:00
mtime 2024-09-19 10:07:25.142707+00:00
computer [2] localhost
Inputs PK Type
-------- ---- -------------
code 5 InstalledCode
x 4 Int
y 6 Int
Outputs PK Type
------------- ---- ----------
remote_folder 8 RemoteData
retrieved 9 FolderData
sum 10 Int
Log messages
---------------------------------------------
There are 2 log messages for this calculation
Run 'verdi process report 7' to see them
となり、CalcJobNodeに関するプロセスの詳細が確認できます。
CalcFunctionNodeの場合も同様のコマンドで内容を確認できます。試しに
verdi process show 3
と打ってみるとよいでしょう。
前節同様、計算の結果を来歴グラフで確認してみましょう。
# AddのCalcJobNode (pk=7とする) の来歴グラフを出力
verdi node graph generate 7 -f png -O add.png
eog add.png # グラフを描画
次のような図が出力されるかと思います。
ArithmeticAddCalculation
がたし算に相当するCalcJobNodeです。
また、左上に注目すると、前節で生成した multiply
の来歴グラフが含まれていることが分かるかと思います。これはmultiply
の結果をadd
の入力として使っていることを反映しています。
このように、 ひとつの計算ワークフローは個々のプロセスノードの連結として構成されます。
計算ワークフローを実行しよう ― WorkChainの定義
ここまでは $f(x,y,z)=(x \times y)+z$ を計算するために、かけ算とたし算を個別に実行してきました。
より洗練された書き方として、WorkChainによるワークフロー化を練習してみましょう。これにより、 一連の計算プロセスをひとつの親プロセスにまとめる ことができ、 複数の計算を含むワークフローをあたかも単一のプロセスのように実行できる ようになります。
ここではいよいよ大詰めとして、このような計算ワークフローを実装してみましょう。すなわち、複数のプロセス (CalcFunction, CalcJob) からなる親プロセス (WorkChainと呼びます) を作成し、実行していきます。
前の節でIPythonを閉じていた方は、再びIPythonを起動しましょう:
verdi shell
WorkChainは端的にいうと プロセスをどのような順序で、どのようなロジックで実行するかを定義した設計図 です。
自動化したい計算タスクに合わせて自分で定義するか、プラグインとして提供されているものを呼び出して使います。
自分で定義する方法は後で説明するとして、まずはプラグインから呼び出す方法を試してみましょう。利用可能な WorkChain プラグインの一覧はverdi plugin list aiida.workflows
で表示できます。
!verdi plugin list aiida.workflows
Registered entry points for aiida.workflows:
* core.arithmetic.add_multiply
* core.arithmetic.multiply_add
Report: Pass the entry point as an argument to display detailed information
core.arithmetic.multiply_add
はデフォルトで用意されているチュートリアル用プラグインです。内容を確認してみましょう。
!verdi plugin list aiida.workflows core.arithmetic.multiply_add
Description:
WorkChain to multiply two numbers and add a third, for testing and demonstration purposes.
Inputs:
code AbstractCode
x Int
y Int
z Int
metadata
Required inputs are displayed in bold red.
Outputs:
result Int
(...)
入力 x, y, z
について$f(x,y,z)=(x \times y)+z$を実行するWorkChainであることが分かります。
Pythonコード内でプラグインを読み込むには WorkflowFactory(<プラグイン名>)
という関数を使用します。
from aiida import engine, orm
wc = WorkflowFactory('core.arithmetic.multiply_add')
builder = wc.get_builder() # 定義したWorkChainをインスタンス化
builder.code = orm.load_code(label='add@localhost') # たし算コードを指定
builder.x = orm.Int(2) # 入力 x をアサイン
builder.y = orm.Int(3) # 入力 y をアサイン
builder.z = orm.Int(5) # 入力 z をアサイン
engine.run(builder) # 実行
Report: [14|MultiplyAddWorkChain|add]: Submitted the `ArithmeticAddCalculation`: uuid: f9b505c8-3e8c-467a-aeff-983b5c9058e5 (pk: 17) (aiida.calculations:core.arithmetic.add)
Out[6]: {'result': <Int: uuid: 50af4a7b-6ed5-4576-b325-112a6c8a6ef1 (pk: 20) value: 11>}
プロセスを確認しましょう。
!verdi process list -a
PK Created Process label ♻ Process State Process status
---- --------- ------------------------ --- ---------------- -----------------------------------
3 31m ago multiply ⏹ Finished [0]
7 30m ago ArithmeticAddCalculation ⏹ Finished [0]
14 10s ago MultiplyAddWorkChain ⏹ Finished [0]
15 11s ago multiply ⏹ Finished [0]
17 11s ago ArithmeticAddCalculation ⏹ Finished [0]
MultiplyAddWorkChain
(PK=14) が完了しているのが分かります。詳細を見てみましょう。
!verdi process show 14
Property Value
----------- ------------------------------------
type MultiplyAddWorkChain
state Finished [0]
pk 14
uuid c4636146-2c65-46ee-81dc-5957ebcb85b5
label
description
ctime 2025-04-15 14:30:05.616450+00:00
mtime 2025-04-15 14:30:11.086354+00:00
Inputs PK Type
-------- ---- -------------
code 5 InstalledCode
x 11 Int
y 12 Int
z 13 Int
Outputs PK Type
--------- ---- ------
result 20 Int
Called PK Type
-------- ---- ------------------------
CALL 15 multiply
CALL 17 ArithmeticAddCalculation
Log messages
---------------------------------------------
There are 1 log messages for this calculation
Run 'verdi process report 14' to see them
入力、出力、そしてMultiplyAddWorkChain
から呼び出された子プロセスがCalled
以下に表示されていることが分かります。
子プロセスの情報を確認する場合は子プロセスのPKについて
verdi process show <pk>
を実行してください。
既存のプラグインを使うだけで $f(x,y,z)=(x \times y)+z$ が実行できてしまいましたが、ではWorkChainの中身はどのようになっているのでしょうか?
次はこのMultiplyAddWorkChain
を自作してみましょう。
MultiplyAddWorkChain
は下のコードのように定義されます(長いのでコピペ推奨です):
# WorkChainの詳細な説明は回を改めて行うので、今回はコードを読む必要はありません。
# 一応コメントで説明は書いたので、気になる方は読んでみて下さい。
from aiida.engine import ToContext, WorkChain, calcfunction
from aiida.orm import AbstractCode, Int
from aiida.plugins.factories import CalculationFactory
# かけ算関数をCalcFunctionとして定義 (前節と同じ)
@calcfunction
def multiply(x, y):
return x * y
# 抽象クラス 'WorkChain' を継承して個別のワークフローを作る
class MultiplyAddWorkChain(WorkChain):
"""WorkChain to multiply two numbers and add a third, for testing and demonstration purposes."""
# ワークフローの定義をクラスメソッド 'define' で行う
# 入力、計算の順番、出力、(必要なら)エラーコードの定義を行う
@classmethod
def define(cls, spec):
"""Specify inputs and outputs."""
super().define(spec) # はじめに必ず親クラスのdefine(spec)を呼び出す。定型文。
spec.input('x', valid_type=Int) # 入力 x
spec.input('y', valid_type=Int) # 入力 y
spec.input('z', valid_type=Int) # 入力 z
spec.input('code', valid_type=AbstractCode) # 入力 'add'のCodeNode
spec.outline( # 計算の実行順序 (アウトライン)
cls.multiply, # かけ算 (multiply)
cls.add, # たし算 (add)
cls.validate_result, # 計算結果の検証
cls.result, # 計算結果を返す
)
spec.output('result', valid_type=Int) # 出力 'result'
# エラーコードの設定 (Optional)
spec.exit_code(400, 'ERROR_NEGATIVE_NUMBER', message='The result is a negative number.')
# かけ算の呼び出し
def multiply(self):
"""Multiply two integers."""
# 入力データは self.inputs."変数名" から取得できます。
x = self.inputs.x
y = self.inputs.y
# multiplyの結果を"Context"に保存します。
# Contextとは、WorkChainのoutlineを通してデータを保持する変数です。
# self.ctx.hogehoge = fugafuga のように、Attribute dictとして使用します。
self.ctx.product = multiply(x, y)
# たし算の呼び出し
def add(self):
"""Add two numbers using the `ArithmeticAddCalculation` calculation job plugin."""
# codeを入力データから読み込み、builderを作ります。
code = self.inputs.code
builder = code.get_builder()
# 直前に実行したmultiplyの結果がContextに保存されているはずなので、それをxとします。
builder.x = self.ctx.product
# 入力 z をaddの入力 y として使います
builder.y = self.inputs.z
# 計算を実行します。たし算の計算を行うサブプロセスが生成されます。
future = self.submit(builder)
# 注意:engine.submitではなくクラスメソッドのself.submitを使いましょう
# ToContext関数を使って結果をContextに保存します。少々特殊な関数なので詳しく説明します。
# ここではサブプロセスが完了するまで (=futureのprocess_stateがterminatedになるまで) 待機し、
# 完了したら結果を'self.ctx.addition'に保存してメインプロセスに制御を渡します。
# このような制御処理を行うのが、ToContext関数です。
return ToContext(addition=future)
# 計算結果の検証の一例として、結果が正かどうかを判別する手続きを設けてみます。
def validate_result(self):
"""Make sure the result is not negative."""
result = self.ctx.addition.outputs.sum
if result.value < 0:
# 異常終了する場合は、defineで定義したexit_codesのいずれかを返せばよいです。
return self.exit_codes.ERROR_NEGATIVE_NUMBER
# 結果の収集
def result(self):
"""Add the result to the outputs."""
# 計算結果として、additionの出力'sum'を返します。
self.out('result', self.ctx.addition.outputs.sum)
これまでと比べると少々複雑な定義に見えますが、やっていること自体は単純で、
- インタフェースクラス
WorkChain
の継承 - 入力の定義
- アウトライン (=計算プロセスの実行順序) の定義
- 出力の定義
- エラーコードの定義
- 各計算プロセスで行う処理の定義
というように計算ワークフローの構成要素を順に定義しているだけです。
そうはいっても
WorkChain
インタフェースの使い方に若干の慣れは必要なので、とりあえずは「こういう定型文がある」くらいの認識で使ってもらえばいいと思います。
WorkChainの機能の全容についてはチュートリアルの範疇を越えるので、次回以降で解説したいと思います。例えば、アウトラインの定義において繰返し処理 (for) や分岐処理 (if) を使った高度な制御が実装可能です。
では、自作版の MultiplyAddWorkChain
を実行してみましょう。
これまでと同様 builder
を用いてデータを入力し、実行することができます。使い方はプラグイン版と全く同じです:
from aiida import orm, engine
builder = MultiplyAddWorkChain.get_builder() # 定義したWorkChainをインスタンス化
builder.code = orm.load_code(label='add@localhost') # たし算コードを指定
builder.x = orm.Int(2) # 入力 x をアサイン
builder.y = orm.Int(3) # 入力 y をアサイン
builder.z = orm.Int(5) # 入力 z をアサイン
engine.run(builder) # 実行
結果の表示は省略しますが、プラグイン版と同じ結果になることを確認してみてください。
では満を持してこの計算ワークフローの来歴グラフを見てみましょう。
# MultiplyAddWorkChain (PK=14) の来歴グラフを出力
verdi node graph generate 14 -f png -O mult_add.png
# 出力した図を表示
eog mult_add.png
上手くいっていれば、次のような図が出力されるはずです。
MultiplyAddWorkChain
と書かれたオレンジ色のProcessNodeがWorkChainNodeです。
ここから multiply
や add
などの子ProcessNodeが定められた順序で呼び出され、最終結果としてresult=11
を出力します。
これにて$f(x,y,z)=(x \times y)+z$の計算ワークフローが完成しました。
ここまでフォローして頂ければ、今回のチュートリアルの目標は達成です。
今回は単純な例を用いて説明しましたが、 どんな複雑な計算ワークフローであっても基本的には今回と同じように実装出来ます。
また、発展形として親WorkChainからさらに子WorkChainを呼び出すような階層的な設計も可能であり、これを利用すれば全体で見れば複雑な計算ワークフローであってもパーツごとに実装することができます。
今回の例は簡単すぎてイメージがつかないかもしれませんが、例えば電子状態計算の結果を使ってポストプロセス計算を立て続けに行う場合など、複数の計算コードが絡むような処理を行う場合に非常に効果的です。
以上、MultiplyAddWorkChain
についてプラグインを使う方法と自作する方法の2種類を紹介しました。
このように、 一度CalcJobやWorkChainをプラグイン化しておくと簡単に呼び出して再利用することができます。
デフォルトで用意されているものの他にも様々な計算コードやワークフローについてプラグインが開発されており、AiiDA公式プラグインレジストリ から確認できます。
気になるコードがあったら是非使ってみて下さい。既存のプラグインだけでも様々な計算タスクがこなせるかと思います。
もちろん、自分でプラグインを作ることも可能です。
自作コードや計算手法をハイスループット化する際には必然的に自作することになります。
プラグインはPythonパッケージを作成するのと同じ要領で実装できます。
具体的な作り方については別の回で改めて紹介します。
まとめ
少々長めのチュートリアルでしたが、お疲れさまでした。
今回の内容をまとめます。
- AiiDAは計算ワークフローをデータとプロセスの2種類のノードからなるグラフデータ (来歴グラフ) として表現し、管理します。
- ノード (Node) はデータまたはプロセスと、ノードの識別子 (pk, uuid)、そして各種メタデータからなるオブジェクトとして実装されています。
- ProcessNodeはDataNodeを入力として受け取り、出力としてDataNodeを生成します。これがAiiDAにおける計算プロセスの最小単位です。
- 計算プロセスとしては、Pythonで実装された計算処理 (CalcFunction) だけではなく外部計算コードを呼び出して行う計算処理 (CalcJob) も利用可能です。
- WorkChainを設計することで、複数のプロセスを一つの計算ワークフローとしてまとめることができます。
- プラグインシステムを使用すると、既存のCalcJobやWorkChainを手軽に再利用できます。
終わりに、次回予告を兼ねて今回のチュートリアルで触れなかった大事な機能について紹介して締めましょう。
実は今回の簡易セットアップでは、 非同期処理に関する機能 (AiiDAデーモン) が有効化されていません。しかしながら、今回解説した計算ワークフローはデーモンによる制御と組み合わせることで真価を発揮します。
次回『(その3)環境構築してみよう!』では、本格的な環境構築のやり方について解説し、今回お見せしなかったAiiDAのもう一つの側面-計算ジョブの非同期的な制御-について解説します。お楽しみに!
参考文献
おわりに
この連載ではAiiDAの基本的な使い方をご紹介していきますが、実際にハイスループット計算を運用する際には、 取り組みたい計算課題に応じた運用設計が重要になります。
NECでは研究コンサルティングの一環として自動計算環境の構築・運用に関するご相談を受け付けています。
計算課題に応じたワークフロー設計や運用支援、レクチャーの依頼など、こちらのページから気軽にお問い合わせください。
-
UUIDが偶然同じ値になるためには2^61もの試行が必要であり、重複する可能性は実用上ゼロとみなせるという事実に基づいています。 ↩