ANSYS Fluent の UDF についての自分用メモ

NB! Dynamic meshing については別の記事に分けている: Dynamic meshing in Fluent - dynamicsoar's log

UDFマニュアル*1は基本的に version 19.2 を参照しているが、たまに18.1も見ている(あまり変わらないと思うが…)

定義の確認

macro

個別の関数のことを function と呼ばず macro と呼んでいるようだ(とくに説明はない…)。

thread とは何か?

CPUとかそういう thread とは関係ない。 GUIでは一度も thread という言葉が出てこないので、UDFを始めて「これは何???」となった。UDFマニュアルの 1.6. Mesh Terminology によると、

A thread is a data structure in ANSYS Fluent that is used to store information about a boundary or cell zone. Cell threads are groupings of cells, and face threads are groupings of faces. Pointers to thread data structures are often passed to functions and manipulated in ANSYS Fluent to access the information about the boundary or cell zones represented by each thread. Each boundary or cell zone that you define in your ANSYS Fluent model in a boundary conditions dialog box has an integer Zone ID that is associated with the data contained within the zone. You will not see the term "thread" in a dialog box in ANSYS Fluent so you can think of a "zone" as being the same as a "thread" data structure when programming UDFs.

ゾーンと思っておけばいい、とある。具体的な例を挙げないとわからないよなぁ…。たとえば、翼まわりの流れ場の計算をするとする。そういうとき、翼表面には名前を付けて、すべりなし境界条件の設定をしたり、揚力・抗力等の算出対象として指定したりするだろう。この場合、「名前がついた翼表面」こそが face zone であり、thread となる。表面ではなく体積を対象とする場合は cell zone であり、やっぱり thread である…ということ。UDFのソースコードで thread にアクセスするときはポインタを使う。Thread の ID は、Fluent の Boundary Conditions と Cell Zone のところにあるIDというやつ…だと思う。ところでこれは Surface の ID とは違うようだ…。

Thread へのポインタは、DEFINE_GRID_MOTIONの場合は、Fluent側から渡される dt というポインタを使って、Thread *thread_pointer = DT_THREAD(dt); で得ることができる。一方で、thread ID はわかっているが dt のようなポインタが Fluent 側から渡されない場合(DEFINE_ON_DEMANDのように)は、Thread *thread_pointer = Lookup_Thread(domain_pointer, zone_ID); とすればよい。

その他によく出てくるのは、

  • Domain: 2相流では2つ、単相流なら1つとあるので、単相流だけやってる間は「ぜんぶ」と思っておけば良さそう。Domain へのポインタは Domain *domain_pointer = Get_Domain(domain_id); で得ることができるが、domain の ID はなにかというと、

    In the case of single-phase flows, domain_id is 1 and Get_Domain(1) will return the fluid domain pointer.

    とあるので、単相流だと1固定らしい。

  • SVar: まだよくわかってない

また、

Note that a face ID or cell ID by itself does not uniquely identify the face or cell. A thread pointer is always required along with the ID to identify the thread to which the face (or cell) belongs.

とのことで、あらゆる face, cell へのアクセスは thread ポインタも指定しないと行えないということのようだ。たとえば、「入口境界threadを指定して、さらにface IDを指定する」というように使うと思われる。

thread_loop_c, thread_loop_f

Domain 内のすべての cell/face をループするという便利な関数。 Section 1.7 には thread_c_loop, thread_f_loop というのがあるが、これらはここにしか出てこないのでおそらく間違い…?

SET_DEFORMING_THREAD_FLAG

UDF マニュアルにも User's Guide にも明確な記述がない。ひどい。かろうじて、マニュアル内のサンプルコードに下記の記述がある:

/* set deforming flag on adjacent cell zone */
    SET_DEFORMING_THREAD_FLAG(THREAD_T0(tf));
/**
* Set/activate the deforming flag on adjacent cell zone, which
* means that the cells adjacent to the deforming wall will also be
* deformed, in order to avoid skewness.
*/
SET_DEFORMING_THREAD_FLAG (THREAD_T0 (thread));

というわけで、どうやら「隣接するセルゾーンを自動的に Deforming に変更する」ようだ。こんな重要そうなフラグのセットをする関数なのに独立した説明がなくてサンプルコード内のコメントが唯一の情報って…。

dt と dtime

UDFの世界では、dt は「時間刻み」ではない。それは dtime と呼ばれる。dt は dynamic thread を意味する。なんて紛らわしいんだ…。

DEFINE_GEOM と DEFINE_GRID_MOTION は何が違うのか

Dynamic meshing in Fluent - dynamicsoar's log

NV_S, NV_V, NV_D など

とんでもないことに、これらいくつか(たくさん)のマクロはマニュアルに定義の説明がない。オープンソースだったとしてもそれはないでしょというところ、これ商用ソルバだよねと思うと、ちょっとどうなってんのという…。以下のマクロは、ほとんどがサンプルコードの中にだけ存在を確認できる*2。したがってここに記す説明はほぼ全て「たぶんこうだろう」と推測したもの。

  • NV_VEC(foo) は演算ではなくて宣言用で、「foo という vector 変数を宣言する」。ここでいう vector とは一般的な数学的なベクトルではなくて、3次元のベクトル(位置や速度など)。
  • NV_S は「vector 変数に対して1つの scalar でなにかの演算をする」。
  • NV_V は「vector 変数に対して1つの vector(成分指定でない)でなにかの演算をする」。
  • NV_D は「vector 変数に対して1つの vector(3成分を全て指定する)でなにかの演算をする」。
  • NV_VS は「vector 変数に対して1つの vector(成分指定でない)と1つの scalar でなにかの演算をする」。
  • NV_VV は「vector 変数に対して2つの vector でなにかの演算をする」。
  • NV_DS は「vector 変数に対して1つの vector(3成分を全て指定する)と1つの scalar でなにかの演算をする」。
  • NV_DD は「vector 変数に対して2つの vector(3成分を全て指定する)でなにかの演算をする」*3
  • NV_V_VS は「vector 変数に対して2つの vector(成分指定でない)でなにかの演算をする。ただし後者の vector には scalar 演算を先にする」*4
  • NV_VS_VS は「vector 変数に対して2つの vector(成分指定でない)でなにかの演算をする。それぞれの vector には scalar 演算を先にする」*5

このうち、NV_V, NV_VV, NV_V_VS, NV_VS_VS の4つだけは 3.4.3 で説明されている。どうやら NV_VD だとか、NV_V_DS といったものはないか、少なくともマニュアルには出てきていないようだ。

こうしてみるとどうやら傾向がわかってくる。Sはスカラー・Vはベクトルというのはいいとして、Dは「ベクトルだが3成分を(関数呼び出し時に)指定する」ということのようだ。D は direction だろうか?実際には単位ベクトル以外も使われているので違うかな…。「なにかの演算をする」というのは、基本的には単なる代入(=)が多いようだが、+=とか/=とかもされていることはあるので、要するにそういうのができるってこと。

THREAD_N_ELEMENTS_INT も定義ないなぁ…明らかに、「スレッドのポインタを引数にとって、そのスレッドの要素数を返す」だけなんだけど、おそらくセルのスレッドを食わせたらセルの個数を、表面を食わせたら face の個数を返す…のだと思う。いやいや、こんな、「だと思う」とか推測しなきゃいけないのおかしいだろ。商用ソフトが。⇒ 実際やってみたら、やはりそのとおり(セルならセルの個数、表面なら face の個数)だった。

マクロ(関数)の引数の単位

どうやら自動的に単位系は SI であると解釈するようなので、必ずSIにしてから渡さないといけない、っぽい。特に自分はミリメートルの世界を扱う事が多い(昆虫とか)ので、メートルに直すのを忘れそうで注意しないと。

I/O

UDF manual の A. 13.3. Standard I/O Functions に書いてある。fopen などが使えるが scanf は使えない、とか。また、stdio.hudf.h に含まれているので、#include "udf.h" のみで十分で、#include "stdio.h" は不要らしい。

書き方

Section 2.1 の Important というコラム:

You must place all of the arguments to a DEFINE macro on the same line in your source code. Splitting the DEFINE statement onto several lines will result in a compilation error.

こんなことするやついるの?って思うかもしれないが、自分は Fortran のときにこれをやるので、「ダメだよ」って書いてなかったらハマったかもしれない。

Make sure that there are no spaces between the macro (such as DEFINE_PROFILE) and the first parenthesis of the arguments, as this will cause an error in Windows.

マクロ名はいいとして、「マクロ名直後の最初のカッコの前にも半角スペースを置いてはいけない」のは割とハマりそう。

Do not include a DEFINE macro statement (such as DEFINE_PROFILE) within a comment in your source code. This will cause a compilation error.

コメントでも書いてはいけないとか普通思わないだろ…まじか…。

DEFINE_ON_DEMAND を使う

おそらくこれが最もプレーンというか一般的なマクロと思われる。Compile → Load までは他のマクロの場合と同じだが、メニューの Execute on Demand... から直接(1回だけ)実行する点が他と違う。したがって、テストなどに便利と思われる。ただし注意事項として、Fluent 側から何も情報が渡されない。多くのマクロでは domain のポインタや、場合によっては現在時刻・スレッド(たとえば「翼表面」とか)などが渡されることが多いが、DEFINE_ON_DEMAND は何も渡されないので、全部自分で取得する必要がある。具体的には、ドメインのポインタを取得するには Get_Domain(domain_ID) を使い、スレッドのポインタを取得するには Lookup_Thread(domain_pointer, zone_ID) を使う。たとえば、コードの冒頭はこんな感じになる:

#include "udf.h"

DEFINE_ON_DEMAND(this_is_udf_name)
{
  int domain_ID = 1; // Always 1 for single phase domain
  Domain *domain_pointer; // Declare domain pointer
  domain_pointer = Get_Domain(domain_ID); // Get the pointer to the domain

  int zone_ID = 6; // or whatever you want to use. Happened to be 6 is the wing surface in my case.
  Thread *thread_pointer; // Declare thread pointer
  thread_pointer = Lookup_Thread(domain_pointer, zone_ID); // Get the pointer to the thread
  //---------------------------------------

  Message0 ("Test DEFINE_ON_DEMAND\n");
  Message0 ("domain_ID      = %i\n", domain_ID);
  Message0 ("domain_pointer = %p\n", domain_pointer);
  Message0 ("zone_ID        = %i\n", zone_ID);
  Message0 ("thread_pointer = %p\n", thread_pointer);
}

…というわけでこれ便利だな、と思っていた時期がおれにもありました。しかし致命的な欠点が… NODE_POS_NEED_UPDATE(node_pointer)DEFINE_GRID_MOTION でしか使えない!(と思われる。他にも使えないマクロがありそう)。結局、メッシュ関係の作業をする場合は DEFINE_GRID_MOTION を使った方がいいですねという結論。まぁこういうのを使わないなら ON_DEMAND でいいんだけど。

Journal での実行
  • /define/user-defined/execute-on-demand コマンドを実行すると、Execute on demand function name ["none"] と聞かれるので…
  • "udf name::library name" と入力する。ダブルクォートとコロン2つは必須。
    • udf name は、自分でつけたUDF名。具体的には、Cコードの DEFINE_ON_DEMAND(ここ) ←ここに書いた名前。
    • library name は、コンパイル後のディレクトリ名。デフォルトでは libudf になっている。
  • すると DEFINE_ON_DEMAND の内容が1度だけ実行される。

*1:正式には ANSYS Fluent Customization Manual だが、長いので個人的に勝手にUDFマニュアルと呼んでいる。まぁわかるでしょ…。

*2:というか、サンプルコードにすらなかったらユーザとしては存在すら知れないわけで、実はそういう「仕様上は存在するけどアンシスの人しか知らないマクロ」がたくさんあっても、もう驚かないわ…。

*3:なんと704ページあるPDFの中で、サンプルコードのたった1箇所にしか出てこない。

*4:サンプルコードには出てこない。

*5:サンプルコードには出てこない。