Fluent UDF: NODE_POS_UPDATED で付与された MOVED フラグは NODE_POS_MOVABLE コマンドでリセットできる

問題編

DEFINE_GRID_MOTION を使って板とかを曲げたりしたいとき、メッシュ内の各 node は複数の face にまたがっている可能性があるので、UDF マニュアルのサンプルにあるように、

#include "udf.h"

DEFINE_GRID_MOTION(my_dyn_mesh, domain, dt, time, dtime)
{
  Thread *tp = DT_THREAD(dt); // tp = thread pointer
  face_t f; // face index
  Node *np; // np = node pointer

  begin_f_loop(f,tp)
  {
    f_node_loop(f,tp,n)
    {
      np = F_NODE(f,tp,n);

      if ( NODE_POS_NEED_UPDATE(np) )
      {
        NODE_POS_UPDATED(np); // Update the flag
        
        do_something;
      }
    }
  }
  end_f_loop(f,tp);
}

というように、

  1. if( NODE_POS_NEED_UPDATE(np) ) で「既に訪れたかどうか」のフラグを調べて、
  2. NODE_POS_UPDATED(np); コマンドで「訪れた」という情報に書き換える。

しかし、「計算開始直後など、時間進行を伴わずに全ノードをループして何かをしたい」ということがある(自分はあった)。このとき、何も考えずにループ構造をコピペしてしまうと、1回目のループで既にフラグが「既に訪れた」ことになっているので、2回目のループでは「全ての」node をスキップしてしまう。つまり何も起きない。この問題にしばらく悩んでいた。つまり、

DEFINE_GRID_MOTION(my_dyn_mesh, domain, dt, time, dtime)
{
  Thread *tp = DT_THREAD(dt); // tp = thread pointer
  face_t f; // face index
  Node *np; // np = node pointer

  // First loop
  begin_f_loop(f,tp)
  {
    f_node_loop(f,tp,n)
    {
      np = F_NODE(f,tp,n);

      if ( NODE_POS_NEED_UPDATE(np) )
      {
        NODE_POS_UPDATED(np);
        
        do_something;
      }
    }
  }
  end_f_loop(f,tp);

  // Another loop
  begin_f_loop(f,tp)
  {
    f_node_loop(f,tp,n)
    {
      np = F_NODE(f,tp,n);

      if ( NODE_POS_NEED_UPDATE(np) )
      {
        NODE_POS_UPDATED(np);
        
        do_something_else; // This doesn't work!
      }
    }
  }
  end_f_loop(f,tp);
}

ということだ。…と思っていた。調査編でわかるように、実際には単に「訪れたかどうか」ではなかったのだが…。

調査編

まず、フラグの実体はなんなのか。 適当なフォルダ(C:\Program Files\ANSYS Inc\v191\fluent\fluent19.1.0\ とか)において、Devas で NODE_POS_NEED_UPDATENODE_POS_UPDATED を検索すると、dynamesh_tools.h というヘッダファイルにヒットする。

/* node marks */
#define NODE_NULL       0x0000
#define NODE_FIXED      0x0001
#define NODE_MOVABLE    0x0002
#define NODE_CONTACT    0x0004
#define NODE_MOVED      0x0008
#define NODE_ADAPT      0x0010
#define NODE_DEFORMED   0x0020
<中略>
#define NODE_POS_MOVABLE(v) \
  (NODE_CLEAR_MARK(v, NODE_MOVED | NODE_FIXED | NODE_ADAPT), \
   NODE_SET_MARK(v, NODE_MOVABLE))
#define NODE_POS_NEED_UPDATE(v) (NODE_MARK(v) & NODE_MOVABLE)
#define NODE_POS_UPDATED(v) \
  (NODE_CLEAR_MARK(v, NODE_MOVABLE | NODE_FIXED | NODE_ADAPT), \
   NODE_SET_MARK(v, NODE_MOVED))
#define NODE_POS_UPDATED_P(v) (NODE_MARK(v) & NODE_MOVED)
<略>

とある。 まず、フラグは TURE/FALSE のような2値ではなく、複数の値をとる。それがソース冒頭の NODE_NULL, NODE_FIXED, ... というやつだ。 そしてこのフラグに関連するコマンドは「現在のフラグ状況を調べるコマンド」と「フラグをセットするコマンド」の2種類に分かれる。 ここでは MOVABLE と MOVED 関連のみを示したが、実際には他のフラグに対応するコマンドもある。

現在のフラグ状況を調べるコマンド: - NODE_POS_NEED_UPDATE: MOVABLE かどうかを調べる - NODE_POS_UPDATED_P: MOVED かどうかを調べる

フラグをセットするコマンド: - NODE_POS_MOVABLE: MOVED 等のフラグを CLEAR_MARK して、MOVABLE をセットする - NODE_POS_UPDATED: MOVABLE 等のフラグを CLEAR_MARK して、MOVED をセットする

となっている。調べるコマンドは基本的に suffix として _P が付くのだが、なぜか MOVABLE だけは MOVABLE_P ではなく NEED_UPDATE となっていて一貫性に欠ける。

解決編

したがって、解決策としては、最初のループの後でフラグリセット用のループを回して、そこで NODE_POS_MOVABLE を実行する、ということになる。つまり、

DEFINE_GRID_MOTION(my_dyn_mesh, domain, dt, time, dtime)
{
  Thread *tp = DT_THREAD(dt); // tp = thread pointer
  face_t f; // face index
  Node *np; // np = node pointer

  // First loop
  begin_f_loop(f,tp)
  {
    f_node_loop(f,tp,n)
    {
      np = F_NODE(f,tp,n);

      if ( NODE_POS_NEED_UPDATE(np) ) // Check the MOVED flag
      {
        NODE_POS_UPDATED(np); // Set MOVED flag, clear any other flags
        
        do_something;
      }
    }
  }
  end_f_loop(f,tp);

  // Loop for resetting the MOVED flag
  begin_f_loop(f,tp)
  {
    f_node_loop(f,tp,n)
    {
      np = F_NODE(f,tp,n);
      NODE_POS_MOVABLE(np); // Clear MOVED flag (and other flags if any), set the MOVABLE flag
    }
  }
  end_f_loop(f,tp);

  // Another loop
  begin_f_loop(f,tp)
  {
    f_node_loop(f,tp,n)
    {
      np = F_NODE(f,tp,n);

      if ( NODE_POS_NEED_UPDATE(np) ) // Check the MOVED flag
      {
        NODE_POS_UPDATED(np); // Set MOVED flag, clear any other flags
        
        do_something_else; // This works!
      }
    }
  }
  end_f_loop(f,tp);
}

ということだ。

ところで、こうやってループを複数書いてると、end_f_loop(f,tp); を忘れてハマりがちなので注意したい(ハマった)。