首页/文章/ 详情

Fluent 并行UDF丨06 其他问题

精品
作者优秀平台推荐
详细信息
文章亮点
作者优秀
优秀教师/意见领袖/博士学历/特邀专家
平台推荐
内容稀缺
4月前浏览5930

本文摘要(由AI生成):

本文描述了一个并行UDF(用户定义函数)的设计,特别是关于数据写入的过程。在并行环境中,数据写入过程比串行环境更复杂,需要仔细设计。尽管许多串行代码在并行模式下也能运行,但并不意味着所有串行代码并行化后都能提高计算效率。并行UDF涉及到节点之间数据的加载、传递、接收和发送,以及主机节点对数据的接收和写入文件。特别地,节点0负责收集其他计算节点发送的数据,并将其直接发送给主机。主机节点则负责从节点0接收数据并将其写入文件。在数据传递和写入过程中,还需要注意内存的分配和释放。


本文描述并行UDF的一些限制、处理器标识以及并行文件读写操作。

注:本文内容来自Fluent UDF手册。

1 并行UDF宏限制

  • PRINCIPAL_FACE_P只能用于编译型UDF中
  • PRF_GRSUM1及类似的全局约简宏不能在诸如DEFINE_SOURCE和DEFINE_PROPERTY之类的宏中使用,这些宏通常在全局网格单元(或面)上调用,因此在每个计算节点上调用的次数不同的。这里提供一种变通的方法:用户可以使用在每个计算节点上只调用一次的宏,如DEFINE_ADJUST、DEFINE_ON_DEMAND和DEFINE_EXECUTE_AT_END ,如可以编写DEFINE_ADJUST UDF来计算调整函数中的全局求和值,然后将变量保存在用户定义内存(UDM)中,随后可以从用户定义的内存中检索存储的变量,并在DEFINE_SOURCE中使用它。如下所示

在下面的示例中,在DEFINE_ADJUST函数中计算spark体积,并使用C_UDMI将该值存储在用户定义的内存中。然后从用户定义的内存中检索体积并在DEFINE_SOURCE中使用。

#include "udf.h"

/* These variables will be passed between the ADJUST and SOURCE UDFs */

static real spark_center[ND_ND] = {ND_VEC(20.0e-3, 1.0e-3, 0.0)};
static real spark_start_angle = 0.0, spark_end_angle = 0.0;
static real spark_energy_source = 0.0;
static real spark_radius = 0.0;
static real crank_angle = 0.0;

DEFINE_ADJUST(adjust, domain)
{

   #if !RP_HOST

      const int FLUID_CHAMBER_ID = 2;

      real cen[ND_ND], dis[ND_ND];
      real crank_start_angle;
      real spark_duration, spark_energy;
      real spark_volume;
      real rpm;

      cell_t c;
      Thread *ct;


      rpm = RP_Get_Real("dynamesh/in-cyn/crank-rpm");
      crank_start_angle = RP_Get_Real(
"dynamesh/in-cyn/crank-start-angle");
      spark_start_angle = RP_Get_Real(
"spark/start-ca");
      spark_duration = RP_Get_Real(
"spark/duration");
      spark_radius = RP_Get_Real(
"spark/radius");
      spark_energy = RP_Get_Real(
"spark/energy");

      /* Set the global angle variables [deg] here for use in the SOURCE UDF */
      crank_angle = crank_start_angle + (rpm * CURRENT_TIME * 6.0);
      spark_end_angle = spark_start_angle + (rpm * spark_duration *
6.0);

      ct = Lookup_Thread(domain, FLUID_CHAMBER_ID);
      spark_volume =
0.0;

      begin_c_loop_int(c, ct)
      {
              C_CENTROID(cen, c, ct);
              NV_VV(dis,=,cen,-,spark_center);


       if (NV_MAG(dis) < spark_radius)
       {
              spark_volume += C_VOLUME(c, ct);
       }
       }
      end_c_loop_int(c, ct)

      spark_volume = PRF_GRSUM1(spark_volume);
      spark_energy_source = spark_energy/(spark_duration*spark_volume);

      Message0("
\nSpark energy source = %g [W/m3].\n", spark_energy_source);
   #endif
}

DEFINE_SOURCE(energy_source, c, ct, dS, eqn)
{

     /* Don't need to mark with #if !RP_HOST as DEFINE_SOURCE is only executed
        on nodes as indicated by the arguments "c" and "ct" */

     real cen[ND_ND], dis[ND_ND];

     if((crank_angle >= spark_start_angle) && (crank_angle < spark_end_angle))
       {
           C_CENTROID(cen, c, ct);
           NV_VV(dis,=,cen,-,spark_center);


           if (NV_MAG(dis) < spark_radius)
           {

             return spark_energy_source;
           }
       }


     return 0.0;
}

注:解释udf不能与infiniband互连一起使用。这种情况下应该使用编译UDF方法。

2 处理器标识

并行ANSYS Fluent中的每个计算节点都有一个唯一的整数标识符,该标识符存储为全局变量myid。当在并行UDF中使用myid时,它将返回当前计算节点(包括主机)的整数ID。host节点的ID为host(=999999),并存储为全局变量host。node-0节点的ID为0,并被分配给全局变量node_zero。下面是并行ANSYS Fluent中的全局变量列表。

int node_zero = 0;
int host = 999999;
int node_one = 1;

int node_last; /* 返回最后一个计算节点的ID值*/
int compute_node_count; /* 返回计算节点的数量 */
int myid; /*返回当前节点的ID值 */

myid通常用于并行UDF代码中的条件-if语句。下面是一些使用全局变量myid的示例代码。在本例中,首先通过累加计算面thread中面的总数。然后,如果myid不是node-0节点,则使用传递宏PRF_CSEND_INT的消息将面数从所有计算节点传递到node-0节点。

int noface=0;
begin_f_loop(f, tf)
/* loops over faces in a face thread and computes number of faces */
{
   noface++;
}
end_f_loop(f, tf)


/* Pass the number of faces from node 1,2, ... to node 0 */

#if RP_NODE if(myid!=node_zero)
{
   PRF_CSEND_INT(node_zero, &noface,
1, myid);
}

#endif

3 并行UDF中的文件读写

计算当Fluent以并行模式运行时,计算节点可以同时对数据执行计算。但当数据从一个���通文件���取或写入该文件时,操作必须是连续的,文件必须由访问文件系统的处理器打开和读写。通常情况下,计算节点在没有磁盘空间的专用并行计算机上运行,这意味着所有的数据都必须从host节点读取和/或写入,host节点总是在具有文件系统访问权限的机器上运行,因为其需要读取和写入cas和dat文件。这意味着,现在必须将数据从所有计算节点传递给node-0节点,然后node-0节点将数据传递给host节点,再利用host节点将数据写入文件。这个过程称为“marshalling”。

3.1 读取文件

在并行UDF中读取文件之前, 要将文件从host节点复 制到计算节点,可以使用以下函数:

host_to_node_sync_file(const char* filename)

这可以处理当前工作路径在host与node之间不共享时。对于host节点,输入参数filename为要复 制到node节点的文件路径,而对于node节点,输入参数是复 制文件的节点上的路径。函数执行完成后,host_to_node_sync_file()返回复 制的字节数,否则返回-1。

在下面的示例中,Windows上的host节点将文件从其本地目录e:\udfs\test.bat复制到远程Linux节点上的目录/tmp。

DEFINE_ON_DEMAND(host_to_node_sync)
{

   #if RP_HOST
       int total_bytes_copied = host_to_node_sync_file("e:\\udfs\\test.dat.h5");
   #endif
   #if RP_NODE
       int total_bytes_copied = host_to_node_sync_file("/tmp");
   #endif
   printf("Total number of bytes copied is %d\n", total_bytes_copied);
}

3.2 写入文件

在并行模式下写入文件过程较为复杂,其基本步骤为:

  • 计算节点将数据发送给node-0节点
  • node-0节点将数据发送给host节点
  • host节点打开文件
  • host节点将数据写入文件
  • host节点关闭文件

下面的示例描述了文件写入过程。

#include "udf.h"

# define FLUID_ID 2

DEFINE_ON_DEMAND(pressures_to_file)
{

   /* Different variables are needed on different nodes */
#if !RP_HOST
  Domain *domain=Get_Domain(1);
  Thread *thread;

  cell_t c;
#else
  int i;
#endif

#if !RP_NODE
  FILE *fp = NULL;
  char filename[]="press_out.txt";
#endif

  int size; /* data passing variables */
  real *array;
  int pe;

#if !RP_HOST
  thread=Lookup_Thread(domain,FLUID_ID);
#endif
#if !RP_NODE
  if ((fp = fopen(filename, "w"))==NULL)
     Message
("\n Warning: Unable to open %s for writing\n",filename);
  else
     Message("\nWriting Pressure to %s...",filename);
#endif
/* UDF Now does 2 different things depending on NODE or HOST */

#if RP_NODE
  /* Each Node loads up its data passing array */
  size=THREAD_N_ELEMENTS_INT(thread);
  array = (real *)malloc(size * sizeof(real));
  begin_c_loop_int(c,thread)
    array[c]= C_P(c,thread);
  end_c_loop_int(c,thread)


  /* Set pe to destination node */
  /* If on node_0 send data to host */
  /* Else send to node_0 because */
  /*  compute nodes connect to node_0 & node_0 to host */
  pe = (I_AM_NODE_ZERO_P) ? host : node_zero;
  PRF_CSEND_INT(pe, &size,
1, myid);
  PRF_CSEND_REAL(pe,
array, size, myid);
  free(array);/* free array on nodes after data sent */

  /* node_0 now collect data sent by other compute nodes */
  /*  and sends it straight on to the host */

if (I_AM_NODE_ZERO_P)
  compute_node_loop_not_zero (pe)
  {
     PRF_CRECV_INT(pe, &size,
1, pe);
     array = (real *)malloc(size * sizeof(real));
     PRF_CRECV_REAL(pe,
array, size, pe);
     PRF_CSEND_INT(host, &size,
1, myid);
     PRF_CSEND_REAL(host,
array, size, myid);
     free((char *)array);
  }

#endif /* RP_NODE */

#if RP_HOST
  compute_node_loop (pe) /* only acts as a counter in this loop */
    {
       /* Receive data sent by each node and write it out to the file */
       PRF_CRECV_INT(node_zero, &size, 1, node_zero);
       array = (real *)malloc(size * sizeof(real));
       PRF_CRECV_REAL(node_zero,
array, size, node_zero);

  for (i=0; i<size; i++)
     fprintf(fp, "%g\n", array[i]);

  free(array);
}

#endif /* RP_HOST */


#if !RP_NODE
  fclose(fp);
  Message(
"Done\n");
#endif

}


并行UDF的��写要远比串行程序复杂,在工程应用时一定要仔细设计。大多数串行代码在并行模式下也能够很好的工作,千万不要以为把串行代码改造成了并行代码就一定能够提高计算效率。


Fluent其他专业通用科普
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2020-08-11
最近编辑:4月前
CFD之道
博士 | 教师 探讨CFD职场生活,闲谈CFD里外
获赞 2565粉丝 11293文章 732课程 27
点赞
收藏
作者推荐
未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习 福利任务 兑换礼品
下载APP
联系我们
帮助与反馈