はじめに
iRICの自作ソルバーを作ってみたいという方向けに、実際にソルバーを作るまでの流れをまとめてみました。
詳しい説明などは公式の開発者マニュアルがありますのでそちらを御覧ください。
今回はマニュアルの手順に従って実際にソルバーを作っていくのですが、公式ドキュメントではFortranで作っているので、せっかくですし今回はPythonを使って作成します。
今回はiRIC上でライフゲームを遊べるソルバーを作ってみましょう。
理由は計算条件などが少なくて済んでそれなりに見栄えがよいからです。
あとは私が水理公式等に疎いためです。
ライフゲームについては以下を読んでください。
今回作成したソルバーは以下のリポジトリでも公開しています。
前提条件
- iRIC v4のインストール済み
- iRICインストール時にMinicoda環境もインストールしている
- VSCodeを使用します
- 記事内ではiRICのインストールフォルダを
IRICROOT
と示します
プログラムの流れ
ざっくり説明すると、初期条件としての格子
、セルの生死
、シミュレーション終了時刻
などはiRICの機能を利用して作成・入力を行い、その条件をpythonで作成した自作プログラムに渡してシミュレーションを行い、結果をiRICの可視化機能を用いて表示します。
ソルバー完成までのロードマップ
以下にソルバー完成までのロードマップを示します。目次にもなっているので活用してください。
必須作業
任意作業
実際に作ってみる
開発用フォルダの作成
自作ソルバーを開発するためのフォルダを作成します。
iRICではIRICROOT\private\solvers
が自作ソルバーのフォルダなので、この中に自分がわかりやすい名前で自作ソルバー用のフォルダを作成してください。
今回はライフゲームを作りたいのでフォルダ名はLife_Game
にでもしておきます。この時フォルダ名に英数字以外を使うのはやめておきましょう。
フォルダを作成したらVSCodeでフォルダを開いておきます。
ソルバー定義ファイルの作成
フォルダを作成できたら、ソルバー定義ファイル(definition.xml
)を作成していきます。
このファイルはどの言語でソルバーを作る際でも必須で、iRICにソルバーを認識してもらい、ソルバーで使用する計算条件や格子属性もここで定義します。
ソルバー定義ファイルはXML形式となっています。
要素の詳細についてはこちらをごらんください。
1. definition.xmlを作成する
新しいファイルを先程のフォルダ(IRICROOT\private\solvers\Life_Game
)に作成し、definition.xml
という名前にします。
この状態ではまだ中身が空なので以下のテンプレートをまずコピペしてしまいましょう。
<?xml version="1.0" encoding="UTF-8"?>
<SolverDefinition
name="samplesolver"
caption="Sample Solver 1.0"
version="1.0"
copyright="Example Company"
release="2012.04.01"
homepage="http://example.com/"
executable="solver.exe"
iterationtype="time"
gridtype="structured2d"
>
<CalculationCondition>
</CalculationCondition>
<GridRelatedCondition>
</GridRelatedCondition>
</SolverDefinition>
definition.xml
の記述について、一部の属性は日本語を使用することもできますが、原則英数字で入力することが望ましいです。
後から辞書ファイルを作成することで多言語表示できますので、そちらの方法を使用してください。
2. ソルバーの情報を記入する
テンプレートをコピーしたらまずはSolverDefinition
という要素の属性について記入していきます。この要素がソルバーの名前やバージョンなどの基本情報を管理しています。
各属性の詳細はこちらを参照してください。
詳細
name
iRIC上でソルバーを認識するための名前です。これが一致しているとcaption
やversion
が異なっていてもiRICは同じソルバーだと認識します。
caption
iRIC上で表示されるソルバーの名前です。name
と一致している必要はありませんし好きに設定してください。
version
ソルバーのバージョンを入力します。メジャーバージョン
.マイナーバージョン
.修正番号
とすることが望ましいです。(参照: ソルバーのバージョンアップ時の注意点)
copyright
著作権者の名前です。
release
ソルバーをリリースした日を入力します。入力形式はyyyy.mm.dd
です。
homepage
ソルバーの紹介ページやマニュアルなどのURLを指定してください。別に指定しなくてもいいです。
executable
iRIC上でソルバーの実行をした際に呼び出すファイルを指定します。Fortranの場合ビルドした実行ファイル(*.exe)、pythonの場合はPythonファイル(*.py)やコンパイルした実行ファイルを指定します。バッチファイル(*.bat)等を指定することもできます。
iterationtype
計算結果出力の単位を入力します。時系列であればtime
、イテレーション毎の出力であればiteration
を入力します。
gridtype
使用する格子の種類を選択します。
記入したものがこちらです。
バージョンはとりあえずでいれてありますが、普段だと私は1.0.25031401
のように修正バージョンはyymmdd
に2桁のビルド番号をつけたものにしています。
<?xml version="1.0" encoding="UTF-8"?>
<SolverDefinition
name="LIFE_GAME_on_iRIC"
caption="LIFE GAME on iRIC"
version="1.0.25031401"
copyright="Keita Hoshino"
release="2025.03.14"
homepage=""
executable="life_game.py"
iterationtype="time"
gridtype="structured2d"
>
<CalculationCondition>
</CalculationCondition>
<GridRelatedCondition>
</GridRelatedCondition>
</SolverDefinition>
SolverDefinition
の中身を記入できたら、内容を保存してiRICに認識されているか確認してみましょう。
iRICを起動して新しいプロジェクト
を選択すると表示されるソルバーの選択ダイアログに先ほど入力した名前のソルバーが表示されていれば正しく認識されています。
3. 計算条件を定義する
ソルバーの情報の入力ができたら、続けて計算条件の定義をしていきます。
iRICではソルバー定義ファイル(definition.xml
)のCalculationCondition
要素で計算条件の定義をすることで、iRICのGUI上でソルバーに必要な計算条件の入力を行うことができます。
詳細は公式ドキュメントのこちらのページを確認してください。
この計算条件については様々な機能が使えるのですが説明すると長くなるので、サンプルのdefinition.xml
を以下で公開していますので興味があれば確認してください。
すこしクドい説明
要素の構造としてはCalculationCondition
の中に複数の計算条件をまとめた単位であるTab
要素を作成し、その中に計算条件1つ毎の単位であるItem
要素を追加します。
Item要素の中には子要素として計算条件の細かい情報を定義するDefinition
があります。
Definition
では計算条件のデータ型
、デフォルト値
、最大・最小値
などを属性として定義可能ですが、今回は最小限の設定だけを行います。詳しくは上記公式ドキュメントやサンプルファイルを是非御覧ください。
<!-- ソルバーの基本条件の定義 -->
<CalculationCondition>
<Tab name="タブ1の名前" caption="タブ1の表示名">
<Item name="計算条件1の名前" caption="計算条件1の表示名">
<Definition valueType="integer" default="10">
</Definition>
</Item>
<Item name="計算条件2の名前" caption="計算条件2の表示名">
<Definition valueType="real" default="0.1">
</Definition>
</Item>
</Tab>
<Tab name="タブ2の名前" caption="タブ2の表示名">
<Item name="計算条件3の名前" caption="計算条件3の表示名">
<Definition valueType="integer" default="10">
</Definition>
</Item>
<Item name="計算条件4の名前" caption="計算条件4の表示名">
<Definition valueType="real" default="0.1">
</Definition>
</Item>
</Tab>
</CalculationCondition>
今回の作成するライフゲームでは以下のパラメーターを入力値として使用したいと考えています。
名前 | 型 | 説明 |
---|---|---|
計算終了時刻 | 整数値 | ライフゲームのシミュレーションを終了する時刻1 |
周期境界条件 | 整数値 | 格子の境界でどのような振る舞いをするか2 0 : 無効 1: 有効 |
上記2つを定義すると以下のようになります。
<?xml version="1.0" encoding="UTF-8"?>
<!-- ソルバーの基本条件の定義 -->
<SolverDefinition
name="LIFE_GAME_on_iRIC"
caption="LIFE GAME on iRIC"
version="1.0.25031401"
copyright="Keita Hoshino"
release="2025.03.14"
homepage=""
executable="life_game.py"
iterationtype="time"
gridtype="structured2d"
>
<!-- 計算条件の定義 -->
<CalculationCondition>
<!-- タブの定義 -->
<Tab name="Condition" caption="Condition">
<!-- 計算終了時刻の定義 -->
<Item name="time_end" caption="time end">
<Definition valueType="integer" default="10"/>
</Item>
<!-- 周期境界条件の定義 -->
<Item name="periodic" caption="periodic boundary condition">
<Definition valueType="integer" default="0" checkBox="true"/>
</Item>
</Tab>
</CalculationCondition>
<GridRelatedCondition>
</GridRelatedCondition>
</SolverDefinition>
計算条件の定義が終了したら、保存して再度iRIC上で確認してみましょう。
先ほどと同様にiRICを起動後新しいプロジェクト
を選択し、ソルバーの選択ダイアログで自分のソルバーを選択し、起動します。
ソルバーが起動したらメニューバーの計算条件
->設定
を選択します。
以下のように定義した計算条件がダイアログに表示されていれば成功です。
3. 計算格子の属性を定義する
次に、計算格子が持つ属性の定義をします。
ソルバー定義ファイル(definition.xml
)のGridRelatedCondition
要素で定義を行うことで、iRIC上で作成した格子において、その属性の情報をマッピングしたり編集することができるようになります。
詳細は公式ドキュメントをご確認ください。
すこしクドい説明
要素の構造としてはGridRelatedCondition
の中に格子属性1つ毎の単位であるItem
要素を追加します。
Item要素の中には子要素として計算条件の細かい情報を定義するDefinition
があります。
Definition
要素の属性や子要素については以下の公式ドキュメントを御覧ください。
https://iric-solver-dev-manual.readthedocs.io/ja/v4_jp/05/04/definition_ga.html
今回はiRICで作成した格子のセルに初期条件として生死判定を与えたいので、セルにlife
という名前の属性を与え、この値が0
なら死、1
なら生と定義をします。
実際に定義行うと以下のようになります。
<?xml version="1.0" encoding="UTF-8"?>
<!-- ソルバーの基本条件の定義 -->
<SolverDefinition
name="LIFE_GAME_on_iRIC"
caption="LIFE GAME on iRIC"
version="1.0.25031401"
copyright="Keita Hoshino"
release="2025.03.14"
homepage=""
executable="life_game.py"
iterationtype="time"
gridtype="structured2d"
>
<!-- 計算条件の定義 -->
<CalculationCondition>
<!-- タブの定義 -->
<Tab name="Condition" caption="Condition">
<!-- 計算終了時刻の定義 -->
<Item name="time_end" caption="time end">
<Definition valueType="integer" default="10"/>
</Item>
<!-- 周期境界条件の定義 -->
<Item name="periodic" caption="periodic boundary condition">
<Definition valueType="integer" default="0" checkBox="true"/>
</Item>
</Tab>
</CalculationCondition>
<!-- 格子属性の定義 -->
<GridRelatedCondition>
<!-- セルの生死 -->
<Item name="life" caption="Life">
<Definition valueType="integer" position="cell" default="max">
<Enumeration value="0" caption="Dead"/>
<Enumeration value="1" caption="Alive"/>
</Definition>
</Item>
</GridRelatedCondition>
</SolverDefinition>
定義ができたら保存してiRIC上で確認しましょう。
ソルバーを起動して適当な格子を作成するとオブジェクトブラウザーのセルの属性
にLife
が表示されるはずです。
計算結果の表示名等を定義する
最後に、ソルバーが出力した計算結果の表示名や数値に対応した文字列を定義します。
なお、この設定は最近追加された機能で、設定するかは任意ですので別にやらなくても問題ありません。
iRICではソルバー定義ファイル(definition.xml
)のGridRelatedCondition
要素内にOutput
要素を追加することで、ソルバーの計算結果の整数値に対応した文字列や、結果の表示名を定義することができます。
Output
要素については以下の公式ドキュメントをご確認ください。
今回作成するソルバーでは計算結果として各セルの生死を出力するので、格子のセルにlife
という整数値の属性を与え、この値が0
なら死、1
なら生と定義をします。
<?xml version="1.0" encoding="UTF-8"?>
<!-- ソルバーの基本条件の定義 -->
<SolverDefinition
name="LIFE_GAME_on_iRIC"
caption="LIFE GAME on iRIC"
version="1.0.25031401"
copyright="Keita Hoshino"
release="2025.03.14"
homepage=""
executable="life_game.py"
iterationtype="time"
gridtype="structured2d"
>
<!-- 計算条件の定義 -->
<CalculationCondition>
<!-- タブの定義 -->
<Tab name="Condition" caption="Condition">
<!-- 計算終了時刻の定義 -->
<Item name="time_end" caption="time end">
<Definition valueType="integer" default="10"/>
</Item>
<!-- 周期境界条件の定義 -->
<Item name="periodic" caption="periodic boundary condition">
<Definition valueType="integer" default="0" checkBox="true"/>
</Item>
</Tab>
</CalculationCondition>
<!-- 格子属性の定義 -->
<GridRelatedCondition>
<!-- セルの生死(計算格子の属性) -->
<Item name="life" caption="Life">
<Definition valueType="integer" position="cell" default="max">
<Enumeration value="0" caption="Dead"/>
<Enumeration value="1" caption="Alive"/>
</Definition>
</Item>
<!-- セルの生死(計算結果の属性) -->
<Output name="life" caption="life">
<Definition valueType="integer" position="cell">
<Enumeration value="0" caption="Dead"/>
<Enumeration value="1" caption="Alive"/>
</Definition>
</Output>
</GridRelatedCondition>
</SolverDefinition>
ここまででソルバー定義ファイルの作成は完了です。上記の内容が最終盤なので、面倒くさがり屋の方はこれをコピペすれば問題ありません。
プログラムの作成
ソルバー定義ファイルを作ることが出来たら次はソルバーの本体であるプログラムを作成してみましょう。
pythonで作成するのであれば、iRICといっしょにインストールしたminiconda環境を利用できるので簡単です。
まずは作業用フォルダにlife_game.py
という名前のファイルを作成してください。
このとき、ファイルの名前はソルバー定義ファイルで定義した実行ファイル名と一致している必要があるので気を付けてください。
ファイルを作成したらまず下準備をします。
下準備
まずは動くか確認
まずは先ほど作成したlife_game.py
に以下の1行を追加して保存してください。
print("program start")
保存したら、iRICでLife Game on iRICを起動し、適当な格子を作ってソルバを実行します。
ソルバーコンソールが現れて以下のように表示されればiRIC上から問題なくlife_game.py
が呼び出されています。
VS CodeのインタープリターにiRICのpython環境を指定
VSCodeでpythonのコードを書くうえで、色々と便利なのでiRICのMiniconda環境をインタープリターに設定しておきましょう。
表示
->コマンドパレット
->Python: インタープリターを選択
から、Python 3.12.3('iric')
を選択してください。
選択出来たらVSCodeの右下にPython 3.12.3('iric':conda)
と表示されるはずです
プログラムの作成
下準備が済んだらコードを書いていくのですが、書き方は十人十色だと思いますので、今回は既に作成したソースコードを示して、流れを解説していきます。
プログラム全体を以下に示します。この内容を作成したファイルにコピペをして保存してください。
コードの全体はこちら
import numpy as np
import iric
import sys
def update_grid(grid, periodic=True):
"""ライフゲームのルールに基づいてグリッドを更新する関数。
Args:
grid (np.ndarray): 現在のグリッドの状態。
periodic (bool): 周期境界条件を使用するかどうか。
Returns:
np.ndarray: 更新されたグリッドの状態。
"""
if periodic:
# 周期境界条件を適用
left = np.roll(grid, 1, axis=1)
right = np.roll(grid, -1, axis=1)
up = np.roll(grid, 1, axis=0)
down = np.roll(grid, -1, axis=0)
up_left = np.roll(up, 1, axis=1)
up_right = np.roll(up, -1, axis=1)
down_left = np.roll(down, 1, axis=1)
down_right = np.roll(down, -1, axis=1)
else:
# 非周期境界条件を適用
left = np.zeros_like(grid)
right = np.zeros_like(grid)
up = np.zeros_like(grid)
down = np.zeros_like(grid)
up_left = np.zeros_like(grid)
up_right = np.zeros_like(grid)
down_left = np.zeros_like(grid)
down_right = np.zeros_like(grid)
left[:, 1:] = grid[:, :-1]
right[:, :-1] = grid[:, 1:]
up[1:, :] = grid[:-1, :]
down[:-1, :] = grid[1:, :]
up_left[1:, 1:] = grid[:-1, :-1]
up_right[1:, :-1] = grid[:-1, 1:]
down_left[:-1, 1:] = grid[1:, :-1]
down_right[:-1, :-1] = grid[1:, 1:]
# 各セルの隣接セル数をカウント
neighbors = left + right + up + down + up_left + up_right + down_left + down_right
# ライフゲームのルールを適用
new_grid = ((neighbors == 3) | ((grid == 1) & (neighbors == 2))).astype(int)
return new_grid
def open_cgns():
"""CGNSファイルを開く関数。
Returns:
int: 開いたCGNSファイルのファイルID。
"""
# コマンドライン引数でCGNSファイル名が指定されているか確認
if len(sys.argv) < 2:
print("Error: CGNS file name not specified.")
exit()
cgns_name = sys.argv[1]
print("CGNS file name: " + cgns_name)
# CGNSファイルを修正モードで開く
fid = iric.cg_iRIC_Open(cgns_name, iric.IRIC_MODE_MODIFY)
return fid
def main():
###############################################################################
# CGNSファイルを開く
###############################################################################
fid = open_cgns()
# 古い計算結果を削除
iric.cg_iRIC_Clear_Sol(fid)
###############################################################################
# 計算条件を読み込み
###############################################################################
# 計算終了時間を読み込み
time_end = iric.cg_iRIC_Read_Integer(fid, "time_end")
# 周期境界条件を読み込み
if iric.cg_iRIC_Read_Integer(fid, "periodic") == 1:
periodic = True
else:
periodic = False
# 格子サイズを読み込み
isize, jsize = iric.cg_iRIC_Read_Grid2d_Str_Size(fid)
# 初期の生死情報を読み込み
is_alive = iric.cg_iRIC_Read_Grid_Integer_Cell(fid, "life")
# 配列の形状がFortran形式の1次元配列なので2次元配列に変換
is_alive = is_alive.reshape(jsize-1, isize-1).T
###############################################################################
# 初期状態を書き込み
###############################################################################
# 初期の生死情報を書き込み
iric.cg_iRIC_Write_Sol_Start(fid)
iric.cg_iRIC_Write_Sol_Time(fid, 0.0)
iric.cg_iRIC_Write_Sol_Cell_Integer(fid, "life", is_alive.flatten(order='F'))
iric.cg_iRIC_Write_Sol_End(fid)
###############################################################################
# 時間ループ (1~time_end)
###############################################################################
for t in range(1, time_end + 1):
# 次の生死情報を計算
is_alive = update_grid(is_alive, periodic)
# 次の生死情報を書き込み
iric.cg_iRIC_Write_Sol_Start(fid)
iric.cg_iRIC_Write_Sol_Time(fid, float(t))
iric.cg_iRIC_Write_Sol_Cell_Integer(fid, "life", is_alive.flatten(order='F'))
iric.cg_iRIC_Write_Sol_End(fid)
# タイムステップを出力
print(f"Time step {t} completed.")
# 生存セル数が0なら終了
if np.sum(is_alive) == 0:
print("All cells are dead. Simulation ends.")
break
# 計算結果の再読み込みが要求されていれば出力を行う
iric.cg_iRIC_Check_Update(fid)
# 計算のキャンセルが押されていればループを抜け出して出力を終了する
canceled = iric.iRIC_Check_Cancel()
if canceled == 1:
print("Cancel button was pressed. Calculation is finishing. . .")
break
# CGNSファイルをクローズ
iric.cg_iRIC_Close(fid)
if __name__ == "__main__":
main()
以下でプログラム作成の流れを説明していますが、面倒な人は読み飛ばしてください。
なお、iRICで提供しているライブラリ(iriclib)の関数の詳細は以下のドキュメントをご確認ください。
コードの流れと説明
プログラムの流れ
プログラムのざっくりとした流れを以下に示します。
実際の動きの順番で解説をしていきます。
1. CGNSファイルを開く処理
iRICではCGNSという形式のファイルに格子、計算条件、計算結果を保存してiRICとソルバーで情報をやり取りしています。
コード内では以下の部分でCGNSファイルを開く処理をしています。
def open_cgns():
"""CGNSファイルを開く関数。
Returns:
int: 開いたCGNSファイルのファイルID。
"""
# コマンドライン引数でCGNSファイル名が指定されているか確認
if len(sys.argv) < 2:
print("Error: CGNS file name not specified.")
exit()
cgns_name = sys.argv[1]
print("CGNS file name: " + cgns_name)
# CGNSファイルを修正モードで開く
fid = iric.cg_iRIC_Open(cgns_name, iric.IRIC_MODE_MODIFY)
まず、上記コードは以下の部分で、iRICで開いているプロジェクトのCGNSファイルの名前を取得し、
if len(sys.argv) < 2:
print("Error: CGNS file name not specified.")
exit()
cgns_name = sys.argv[1]
以下の関数で、取得したCGNSファイルを開いています。
開いたCGNSファイルは以後fidに返ってきたIDを用いて指定をします。
# CGNSファイルを修正モードで開く
fid = iric.cg_iRIC_Open(cgns_name, iric.IRIC_MODE_MODIFY)
ここの処理は基本的にどのソルバーを作る場合でも共通しています。
以下の部分で開いたCGNSファイルに保存されている古い計算結果を破棄する処理をしています。
この処理はiRIC上から実行する際には不要なのですが、デバッグ作業等でiRIC以外の環境から実行する際に必要なので、こだわりがなければCGNSファイルを開く処理の直後に実行しておきましょう。
# 古い計算結果を削除
iric.cg_iRIC_Clear_Sol(fid)
2. 計算条件の読み込み
以下の部分で先程開いたCGNSファイルから計算条件と格子の情報を取得しています。
###############################################################################
# 計算条件を読み込み
###############################################################################
# 計算終了時間を読み込み
time_end = iric.cg_iRIC_Read_Integer(fid, "time_end")
# 周期境界条件を読み込み
if iric.cg_iRIC_Read_Integer(fid, "periodic") == 1:
periodic = True
else:
periodic = False
# 格子サイズを読み込み
isize, jsize = iric.cg_iRIC_Read_Grid2d_Str_Size(fid)
# 初期の生死情報を読み込み
is_alive = iric.cg_iRIC_Read_Grid_Integer_Cell(fid, "life")
# 配列の形状がFortran形式の1次元配列なので2次元配列に変換
is_alive = is_alive.reshape(jsize-1, isize-1).T
まず以下の箇所で、CGNSに保存された整数の計算条件を読み込む関数のcg_iRIC_Read_Integerを利用して計算終了条件と周期境界条件を読み込んで変数に格納しています。
それぞれ引数には先程取得したCGNSファイルのIDと、読み込みたい計算条件のdifinition.xml
で定義したname
を与えます。
# 計算終了時間を読み込み
time_end = iric.cg_iRIC_Read_Integer(fid, "time_end")
# 周期境界条件を読み込み
if iric.cg_iRIC_Read_Integer(fid, "periodic") == 1:
periodic = True
else:
periodic = False
次に以下の部分では、cg_iRIC_Read_Grid2d_Str_Sizeで格子点の数を、cg_iRIC_Read_Grid_Integer_Cellでセルの生死を情報を読み込んでいます。
# 格子サイズを読み込み
isize, jsize = iric.cg_iRIC_Read_Grid2d_Str_Size(fid)
# 初期の生死情報を読み込み
is_alive = iric.cg_iRIC_Read_Grid_Integer_Cell(fid, "life")
読み込まれたセルの情報はisize
行、jsize
列の2次元配列が列優先の1次元の配列になっており、インデックスは以下のようになっています。
1次元配列のままだとインデックスの処理が面倒なため、読み込んだis_alive
をisize
行、jsize
列の2次元配列にしたいので、以下のように配列の変換を行っています。
is_alive = is_alive.reshape(jsize-1, isize-1).T
この配列の変換についてはこちらで詳しく説明しています。
4. 初期状態の出力
上記のフロー図にはありませんでしたが、計算に必要な条件の読み込みは完了したので、メインループに入る前に以下の箇所で初期条件を出力しています。
###############################################################################
# 初期状態を書き込み
###############################################################################
# 初期の生死情報を書き込み
iric.cg_iRIC_Write_Sol_Start(fid)
iric.cg_iRIC_Write_Sol_Time(fid, 0.0)
iric.cg_iRIC_Write_Sol_Cell_Integer(fid, "life", is_alive.flatten(order='F'))
iric.cg_iRIC_Write_Sol_End(fid)
上記コードではcg_iRIC_Write_Sol_Startとcg_iRIC_Write_Sol_Endで計算結果の書き込みの開始と終了を宣言し、その間にタイムステップをcg_iRIC_Write_Sol_Time、セルの生死であるlife
の値をcg_iRIC_Write_Sol_Cell_Integerで出力しています。
なお、ソルバーで各タイムステップの計算結果を出力するときも毎回cg_iRIC_Write_Sol_Startとcg_iRIC_Write_Sol_Endで開始と終了を宣言し、その中で結果の書き込みをする必要があります。
5. メインのループ処理
以下の箇所がこのプログラムのメインループになります。
###############################################################################
# 時間ループ (1~time_end)
###############################################################################
for t in range(1, time_end + 1):
# 次の生死情報を計算
is_alive = update_grid(is_alive, periodic)
# 次の生死情報を書き込み
iric.cg_iRIC_Write_Sol_Start(fid)
iric.cg_iRIC_Write_Sol_Time(fid, float(t))
iric.cg_iRIC_Write_Sol_Cell_Integer(fid, "life", is_alive.flatten(order='F'))
iric.cg_iRIC_Write_Sol_End(fid)
# タイムステップを出力
print(f"Time step {t} completed.")
# 生存セル数が0なら終了
if np.sum(is_alive) == 0:
print("All cells are dead. Simulation ends.")
break
# 計算結果の再読み込みが要求されていれば出力を行う
iric.cg_iRIC_Check_Update(fid)
# 計算のキャンセルが押されていればループを抜け出して出力を終了する
canceled = iric.iRIC_Check_Cancel()
if canceled == 1:
print("Cancel button was pressed. Calculation is finishing. . .")
break
セルの生死状態の更新
流れとしては、まず以下の部分でライフゲームのルールのよる処理を行い、セルの生死状況を更新しています。
ソルバー作成に余り関係ないので詳しく説明しませんが、各セルに対して周囲8セルに存在する生存セルの個数をカウントしてその数に応じて次の世代の生死の判定を行っています。
# 次の生死情報を計算
is_alive = update_grid(is_alive, periodic)
def update_grid(grid, periodic=True):
"""ライフゲームのルールに基づいてグリッドを更新する関数。
Args:
grid (np.ndarray): 現在のグリッドの状態。
periodic (bool): 周期境界条件を使用するかどうか。
Returns:
np.ndarray: 更新されたグリッドの状態。
"""
if periodic:
# 周期境界条件を適用
left = np.roll(grid, 1, axis=1)
right = np.roll(grid, -1, axis=1)
up = np.roll(grid, 1, axis=0)
down = np.roll(grid, -1, axis=0)
up_left = np.roll(up, 1, axis=1)
up_right = np.roll(up, -1, axis=1)
down_left = np.roll(down, 1, axis=1)
down_right = np.roll(down, -1, axis=1)
else:
# 非周期境界条件を適用
left = np.zeros_like(grid)
right = np.zeros_like(grid)
up = np.zeros_like(grid)
down = np.zeros_like(grid)
up_left = np.zeros_like(grid)
up_right = np.zeros_like(grid)
down_left = np.zeros_like(grid)
down_right = np.zeros_like(grid)
left[:, 1:] = grid[:, :-1]
right[:, :-1] = grid[:, 1:]
up[1:, :] = grid[:-1, :]
down[:-1, :] = grid[1:, :]
up_left[1:, 1:] = grid[:-1, :-1]
up_right[1:, :-1] = grid[:-1, 1:]
down_left[:-1, 1:] = grid[1:, :-1]
down_right[:-1, :-1] = grid[1:, 1:]
# 各セルの隣接セル数をカウント
neighbors = left + right + up + down + up_left + up_right + down_left + down_right
# ライフゲームのルールを適用
new_grid = ((neighbors == 3) | ((grid == 1) & (neighbors == 2))).astype(int)
return new_grid
更新されたセルの情報の出力
上記の処理でセルの生死が更新されたので、CGNSファイルに計算結果を出力します。
初期状態の出力時と同様に、毎回cg_iRIC_Write_Sol_Startとcg_iRIC_Write_Sol_Endで計算結果の書き込みの開始と終了を宣言し、その間にタイムステップをcg_iRIC_Write_Sol_Time、セルの生死であるlife
の値をcg_iRIC_Write_Sol_Cell_Integerで出力しています。
# 次の生死情報を書き込み
iric.cg_iRIC_Write_Sol_Start(fid)
iric.cg_iRIC_Write_Sol_Time(fid, float(t))
iric.cg_iRIC_Write_Sol_Cell_Integer(fid, "life", is_alive.flatten(order='F'))
iric.cg_iRIC_Write_Sol_End(fid)
タイムステップは整数値でループを回していますが、引数は実数値を与えなければ行けないのでfloat(t)
としています。
セルの生死状態を格納したis_alive
は計算を2次元配列で行っていましたが、出力時のcg_iRIC_Write_Sol_Cell_Integerの引数が1次元配列のため変形をしています。
コンソールへ計算終了した時刻を出力
以下は計算中にコンソール画面が寂しいので計算が終了したタイムステップの時刻をコンソールに出力しています。
# タイムステップを出力
print(f"Time step {t} completed.")
条件でループを終了
以下の部分では生存セルがひとつも存在しない状態になったら終了するようにしています。
生存セルがない場合新たな生命も生まれないので計算するだ無駄だからです。
# 生存セル数が0なら終了
if np.sum(is_alive) == 0:
print("All cells are dead. Simulation ends.")
break
また、以下の部分ではiRIC上で計算のキャンセルボタンが押されたら計算を中断するための処理が書かれています。
# 計算のキャンセルが押されていればループを抜け出して出力を終了する
canceled = iric.iRIC_Check_Cancel()
if canceled == 1:
print("Cancel button was pressed. Calculation is finishing. . .")
break
計算途中で計算結果を読み込むための処理
以下の処理をループ内に入れておくと、iRIC上で計算結果の読み込みボタンを押した後この処理に到達した時点で出力されている計算結果をiRICで読み込むことができます。
# 計算結果の再読み込みが要求されていれば出力を行う
iric.cg_iRIC_Check_Update(fid)
6. CGNSファイルを閉じる処理
シミュレーション計算が終了したら開いていたCGNSファイルを閉じます。
これを閉じないでプログラムを終了したりすると下手するとファイルが破損することもあるのでしっかりプログラムの最後にはCGNSファイルを閉じる処理をしましょう。
# CGNSファイルをクローズ
iric.cg_iRIC_Close(fid)
動作確認
プログラムが出来たら動作確認をしてみます。
Fortran等で作るとコンパイルが面倒ですが、Pythonだとそのまま実行できるのがありがたいですね。
まずはiRICで新しいプロジェクト
からLIFE GAME on iRIC
を起動します。
適当な格子を作成します、矩形領域の格子を作成
を使用すると簡単だと思います。
オブジェクトブラウザーでセルの属性
とLife
にチェックを入れ、Life
を選択した状態で直接セルを選択し、右クリックメニューから値の編集
を選択し、生きているセル(赤色)が以下のような形になるようにしてください。
初期状態が設定出来たら計算条件
->設定
から計算条件ウィンドウを開き、適当な終了時間を入力します。
周期境界条件はとりあえず無効でいいです。
条件を入力したら適当な名前でプロジェクトを保存して計算を実行します。
計算終了後二次元可視化ウィンドウを開いてスカラー(セル中心)
のlife
にチェックを入れてアニメーションを再生すると、、、
こんな感じで爆速になってしまいました。
メインメニューのアニメーション
->再生速度を設定
で1コマ当たりの描画時間を0.1くらいにしてあげます。ついでにループ再生も有効にしてあげれば、、、、
バッチリですね!計算もしっかり出来ているようです!
辞書ファイルの作成
前節までの作業で、ソルバーの作成としては問題なく動くようになりました。
ここからはより使いやすくするための作業になります。
現状では計算条件などが英語のままですので、iRIC上で日本語表示するための辞書ファイルを作成しましょう。
まずはiRICを起動して、スタートページは閉じます。その後、メニューバーのオプション
->辞書ファイルの作成・更新
をクリックします。
定義ファイルの翻訳辞書 更新ウィザード
が表示されるのので、次へ
をクリックして以下の画面で対象プログラム
にプルダウンからLIFE GAME on iRIC
、言語
でJapanese
にチェックを入れます。
次へ
をクリックすると出力される辞書ファイルのパスが表示されるので完了
を押してファイルを出力します。
translation_ja_JP.ts
が作業フォルダに出力されているはずなので、VSCodeで開きます。
source
に原文が入っており、それに対応する翻訳をtranslation
に記入していきます。
<?xml version="1.0" encoding="UTF-8"?>
<TS version="2.0" language="ja_JP">
<!--これは iRIC で使用する辞書ファイルです。
<translation> と </translation> の間に、翻訳後の文字列を追記してください。
-->
<context>
<message>
<source>LIFE GAME on iRIC</source>
<translation>ライフゲーム on iRIC</translation>
</message>
<message>
<source>Condition</source>
<translation>計算条件</translation>
</message>
<message>
<source>time end</source>
<translation>計算終了時刻</translation>
</message>
<message>
<source>periodic boundary condition</source>
<translation>周期境界条件</translation>
</message>
<message>
<source>Life</source>
<translation>生死</translation>
</message>
<message>
<source>Dead</source>
<translation>死亡</translation>
</message>
<message>
<source>Alive</source>
<translation>生存</translation>
</message>
<message>
<source>life</source>
<translation>生死</translation>
</message>
</context>
</TS>
翻訳作業が終わったら辞書ファイルを保存して早速iRICを起動してみます。
先ほど翻訳したようにソルバー名が日本語で表示されているのがわかります。
計算条件や
オブジェクトブラウザーや凡例もしっかり翻訳されています。
翻訳作業はこれで完了です。
説明ファイルの作成
続けて説明ファイルを作成していきましょう。
作成ファイルとはソルバーの説明を書いたファイルで、ソルバーのフォルダの中に入れておくと、以下のようにソルバー選択画面に説明文を表示することができます。
ファイルは英語がREADME
、日本語がREADME_ja_JP
という名前のファイル名で作成されたUTF-8のテキストファイルである必要があります。(拡張子はありません)
ではさっそく作っていきましょう。VSCodeでREADME
とREADME_ja_JP
を作成し、ソルバーの説明文を入力して保存します。
# ライフゲーム on iRIC
このソルバーは、ライフゲームのルールに基づいてグリッドの状態をシミュレーションするものです。ライフゲームは、イギリスの数学者ジョン・ホートン・コンウェイによって考案されたセルオートマトンの一種です。各セルは「生」または「死」の状態を持ち、隣接するセルの状態に応じて次の状態が決まります。
## ライフゲームのルール
1. 生きているセルは、隣接する生きているセルが2つまたは3つの場合に生き続けます。それ以外の場合は死にます(過疎または過密)。
2. 死んでいるセルは、隣接する生きているセルがちょうど3つの場合に新しく生まれます。
# Life Game on iRIC
This solver simulates the state of a grid based on the rules of the Game of Life. The Game of Life is a type of cellular automaton devised by British mathematician John Horton Conway. Each cell has a state of "alive" or "dead," and the next state is determined by the states of neighboring cells.
## Rules of the Game of Life
1. A living cell continues to live if it has 2 or 3 living neighboring cells. Otherwise, it dies (due to underpopulation or overpopulation).
2. A dead cell becomes alive if it has exactly 3 living neighboring cells.
保存したらiRICを開いて確認してみましょう。しっかり表示されていればOKです。
ライセンスファイルの作成
最後にライセンスファイルの作成です。
作成しておくと先ほどの説明ファイルと同様にソルバー選択画面でライセンスの情報を表示できます。
自作のオリジナルソルバーを作った際にはライセンスを明記しておきましょう。
ライセンスファイルは英語版がLICENSE
、日本語版がLICENSE_ja_JP
という名前のUTF-8のテキストファイルとして作成し、ソルバーのフォルダに格納します。
ライセンスの内容はよくあるMITライセンス等を利用してもいいですし、独自に作成してもよいと思います。
私は以下のようにMITライセンスにしておきました。
MIT License
Copyright (c) 2025 KeitaHoshino
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
まとめ
今回はiRIC上で入力した条件をプログラムで計算し、結果をiRICで表示する簡単なソルバーを作成してみました。
難しいと思いがちなソルバーの作成ですが、一度やってみると結構簡単なことがわかったかなと思います!
ここまでに紹介したように、iriclibの関数を使うのは主に計算条件や格子の読み込みと、計算結果の書き込みの部分くらいです。
そのため、既存のプログラム等がある場合は基幹となる計算部分はほとんどいじらずに入出力部分だけにiriclibの関数を使用すれば、簡単にiRICで動くソルバーとして作成することができます。
今回作成するプログラムもupdate_grid
関数の中身を変えたりするだけで簡単に違うソルバーにすることが可能なので色々と挑戦してみてください。