はじめに
その計算、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 (緑色の楕円) 入出力データに対応するノード (数値、文字列、辞書型など)
- CodeNode (青色の楕円): 外部計算コードの実行方法を記述する特殊な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 bcd2e4bf...
で識別され、値 5
を持つ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$ がデータベースに登録されました。
どのような処理が行われたのかを追跡してみましょう。
一度IPythonを閉じ、コマンドラインシェルで次のコマンドを実行してみて下さい:
exit() # IPythonを一旦閉じる
verdi process list -a
これは実行した計算プロセスを出力するためのVerdi CLIのコマンドです (IPython中では%verdi
というマジック関数として呼び出されます)。
そうすると次のような出力が得られます:
PK Created Process label ♻ Process State Process status
---- --------- --------------- --- --------------- ----------------
3 0s ago multiply ⏹ Finished [0]
Total results: 1
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: 1
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
'multiply' に対応するプロセスが実行され、既に終了している (Finished [0]
) 様子が見えます。
この処理に対応するProcessNodeには pk=3
が与えられているのが分かるので、再度IPythonを開いてこれの中身を見てみましょう:
verdi shell # IPythonを立上げ
load_node(3) # multiplyのNodeを読み込み
出力は
<CalcFunctionNode: uuid: 4e458eba-8d0e-44c1-9bc6-c588617e7c5d (pk: 3) (__main__.multiply)>
となっています。DataNodeは値をvalue
として持っていましたが、ProcessNodeは関数名 (__main__.multiply
) を持っていることが分かるかと思います。pk
やuuid
といった識別子を持つ点はDataNodeと共通です。
ProcessNodeの各フィールドの説明
-
CalcFunctionNode
: Nodeの型です。CalcFunctionNodeの場合、@engine.calcfunction
で修飾された関数がPython上で実行されたことを意味します。他の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のような高パフォーマンスな言語で実装しコンパイルして作成した実行形式ファイル-を高性能計算機上で実行することがほとんどでしょう。
このような外部の計算コードによる計算はAIiDAではCalcJobというプロセスとして定義されます。
ここではこれを実行する手順について見ていきましょう。簡単な例として、この節では足し算 $x+y$ を行うシェルスクリプトをCalcJobとして定義し、呼び出して計算してみます。
計算機と外部計算コードの登録
外部の計算コードを利用するためには、使用する計算コードおよびコード実行環境となる計算機の情報をAiiDAに登録する必要があります。
これらはVerdi CLIで行うため、IPythonは閉じておきましょう。
exit()
まずは計算機を登録しましょう。
今回はAiiDAを実行しているローカルマシンを計算機として登録します。
少々天下りですが、これは次のコマンドで行えます:
verdi computer setup -L tutor -H localhost -T core.local -S core.direct -w `echo $PWD/aiida_workdir` -n
verdi computer configure core.local tutor --safe-interval 1 -n
これにより、ローカルマシンがtutor
というラベルで、利用可能な計算資源として登録されました。
コマンドの詳細については、リモート計算機サーバーを計算機として登録する手順と併せて別の回に改めて解説します。
登録された計算機は次のコマンドで確認できます:
verdi computer show tutor
--------------------------- ------------------------------------
Label tutor
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が1から振られているのは、ここでいうPKは計算機データベース内の識別子であり、これまでのNodeデータベースのPKとは独立なためです。
念のため注意ですが、Nodeとは来歴グラフの頂点のことであり、コンピュータノードのことではありません。紛らわしいので混同しないように注意してください。
次はたし算を行う計算コードを登録しましょう。
ここでは シェルの算術式 $echo $((x+y))$ を一種の計算コードだと思うことにして、これをCalcJobとして登録します。これまた天下りですが、次のコマンドを実行してください:
verdi code create core.code.installed --label add --computer=tutor --default-calc-job-plugin core.arithmetic.add --filepath-executable=/bin/bash -n
これにより、たし算を行うコードの実行パス (/bin/bash
) が add
という名前で登録され、tutor
という計算機上で実行されるように設定されました。
登録された計算コードは、次のように確認できます:
verdi code show add@tutor
-------------------- ------------------------------------
PK 5
UUID 394a527f-...
Label add
Description
Default plugin core.arithmetic.add
Type remote
Remote machine tutor
Remote absolute path /bin/bash
Prepend text
Append text
-------------------- ------------------------------------
このように、計算コードはNodeの一種であるCodeNodeとしてPKとUUIDが割り振られてNodeデータベースに保存されます。独自のデータベースを持っていた計算機と違い、こちらはDataNodeの一種としてNodeデータベースに格納されることに注意して下さい。
AiiDAの来歴グラフモデルにおいては、使用する計算コードもまた入力データの一種と見なされます。
人によっては、計算コードがDataNodeとして扱われることに違和感を感じるでしょうか。計算コードには、単なる計算プログラムだけではなく、それがインストールされている計算機環境のメタデータ、例えばビルドバージョンやコア数、メモリサイズなどの情報も含まれていると思えば、少し納得できるかもしれません。
出力からはこのコードは core.arithmetic.add
というプラグインを使用し、コマンドラインシェル (/bin/bash
) を実行するものであることが分かります。
ここでは備え付けのbashとデフォルトのプラグインを使用していますが、一般にはここで使用したい計算コードに応じたプラグインを選択することになります。これもまた別の回に解説したいと思います。
とりあえずは、これで外部コードを用いてたし算を計算する準備が整いました。
入力データの作成と外部計算コードの実行
コンピュータとコードの設定が完了したら、再びIPythonインタフェースを起動しましょう。
verdi shell
前節で登録した add
コードをCodeNodeとして呼び出します。
from aiida import orm, engine
code = orm.load_code(label='add@tutor') # コードラベル@計算機ラベル で呼び出せる。
code # codeノードの情報を表示
<Code: Remote code 'add' on tutor, pk: 5, uuid: 394a527f-705b-4cac-95a6-56d3d2befafb>
出力を見ると、コンピュータ tutor
上で実行される add
という名前のCodeNodeが呼び出されていることが分かります。
入力データの作成には、コードノードの中の get_builder
というメソッドを用いるのが便利です。
builder = code.get_builder() # builderの作成
builder.x = orm.load_node(4) # 前節のmultiplyの結果
builder.y = orm.Int(5)
builder # builderの中身を表示
builder
はCalcJobNodeに入力データを引き渡すためのインタフェースクラスで、端的にいうと 計算ジョブの内容と入力をひとまとめにしたもの です。
例えば add
が x
という引数をもつ関数だった場合、builder.x = orm.Int(1)
のように、入力に対応するDataNodeをアサインしていきます。実例を見た方が分かりやすいでしょう:
builder.x = orm.load_node(4) # 前節のmultiplyの結果を入力xとする
builder.y = orm.Int(5) # 整数5を入力yとする
builder # builderの中身を表示
Process class: ArithmeticAddCalculation
Inputs:
code: add@tutor
metadata:
options:
stash: {}
monitors: {}
x: 6
y: 5
出力からは、$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'
には作業ディレクトリから回収した出力ファイルが入っています。
どのようなプロセスが実行されたのか確認します。
一旦IPythonを閉じましょう:
exit()
前と同様に、
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
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
のようになっているかと思います。
前回の計算 (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] tutor
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$ を計算するために、かけ算とたし算を個別に実行してきました。
これらを計算ワークフローとして実行する場合、素朴にはmultiply
のプロセスの後にArithmeticAddCalculation
のプロセスを呼び出すようにPythonスクリプトを書けば実装はできるのですが、より洗練された書き方として、 一連の計算プロセスをひとつの親プロセスにまとめる ことができ、これにより 複数の計算を含むワークフローをあたかもひとつの関数のように実行できる ようになります。
ここではいよいよ大詰めとして、このような計算ワークフローを実装してみましょう。すなわち、複数のプロセス (CalcFunction, CalcJob) からなる親プロセス (WorkChainと呼びます) を作成し、実行していきます。
前の節でIPythonを閉じていた方は、再びIPythonを起動しましょう:
verdi shell
WorkChain作成で行うことは、基本的には プロセスをどのような順序で、どのようなロジックで実行するかの定義 です。
例として、$f(x,y,z)=(x \times y)+z$を実行するWorkChainである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
を実行してみましょう。
CalcJobと同様 builder
を用いてデータを入力し、実行することができます:
# ORMとEngineをインポート
from aiida import orm, engine
builder = MultiplyAddWorkChain.get_builder() # 定義したWorkChainをインスタンス化
builder.code = orm.load_code(label='add@tutor') # たし算コードを指定
builder.x = orm.Int(2) # 入力 x をアサイン
builder.y = orm.Int(3) # 入力 y をアサイン
builder.z = orm.Int(5) # 入力 z をアサイン
engine.run(builder) # 実行
終了したら、プロセスの結果を確認してみましょう。またIPythonを閉じます。
exit()
前節までと同様、verdi process list -a
でプロセスを確認してみます:
verdi process list -a
PK Created Process label ♻ Process State Process status
---- --------- ------------------------ --- --------------- ----------------
3 2h ago multiply ⏹ Finished [0]
7 1h ago ArithmeticAddCalculation ⏹ Finished [0]
14 1s ago MultiplyAddWorkChain ⏹ Finished [0]
Total results: 3
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: 11h ago (at 23:30:11 on 2025-04-15)
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: 3
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: 11h ago (at 23:30:11 on 2025-04-15)
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
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
入力、出力、そしてMultiplyAddWorkChain
から呼び出された子プロセスがCalled
以下に表示されていることが分かります。
子プロセスの情報を確認する場合は子プロセスのPKについて
verdi process show
を実行してください。
では満を持してこの計算ワークフローの来歴グラフを見てみましょう。
# 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
をベタ打ちで実装しましたが、毎回コピペするのは大変です。
AiiDAではCalcJobやWorkChainをあたかもライブラリのようにインポートできる機能が備わっています。それがプラグインシステムです。
実は、このチュートリアルの中で既にプラグインが使われています。 Step 3:計算コードを実行しよう - CalcJobの定義 でArithmeticAddCalculation
を用意する際に
verdi code create core.code.installed --label add --computer=tutor --default-calc-job-plugin core.arithmetic.add --filepath-executable=/bin/bash -n
というコマンドを実行しましたが、ここで --default-calc-job-plugin core.arithmetic.add
、 つまりcore.arithmetic.add
というプラグインを使用します、という宣言をしています。
このプラグインはAiiDAがデフォルトで用意している、bashでたし算を計算するためのチュートリアル用プラグインです。
このプラグインの中ではPythonとbashの間のインタフェースが定義されており、たし算を計算するための入力ファイル (といっても$echo $((x+y))$
の1行ですが)と、出力の解析 (標準出力の取得) が実装されていたのです。
実は MultiplyAddWorkChain
についても、上のワークフローと全く同じ内容のデフォルトのチュートリアル用プラグインが用意されています。
試しにこちらのやり方でも実行してみましょう。再度IPythonを立上げ、
verdi shell
MultiplyAddWorkChain
を自分で定義する代わりに次のコードを実行してみて下さい:
# orm, engineに加え、pluginsもインポートする
from aiida import plugins, orm, engine
# プラグインからMultiplyAddWorkChainを読み込み
MultiplyAddWorkChain = plugins.WorkflowFactory('core.arithmetic.multiply_add')
# 実行方法は先ほどと同じ
builder = MultiplyAddWorkChain.get_builder() # WorkChainをインスタンス化
builder.code = orm.load_code(label='add@tutor') # たし算コードを指定
builder.x = orm.Int(2) # 入力 x をアサイン
builder.y = orm.Int(3) # 入力 y をアサイン
builder.z = orm.Int(5) # 入力 z をアサイン
engine.run(builder) # 実行
以降の解説は省略しますが、自分で実装したときと全く同じ計算ワークフローが実行されたかと思います。
このように、 一度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もの試行が必要であり、重複する可能性は実用上ゼロとみなせるという事実に基づいています。 ↩