Fluent UDF中调用变量的梯度及其注意点

Fluent UDF中有时候需要调用变量的梯度,例如温度梯度,压力梯度,VOF梯度等等,一般是在C_T,C_P,C_VOF后面加上“_G”来获取,例如C_T_G,C_VOF_G。看似简单,实际上里面有比较多的“坑”,现以如下实例来提请各位看官注意。

首先,我们利用VC++ UDF Studio插件(https://vcudfstudio.github.io)启动Fluent,然后再点击Fluent中的“启动Visual Studio”菜单,这样我们就可以在Visual Studio中输入源码并编译UDF了。

在Visual Studio项目中的udf_source.cpp文件中输入如下源码。 

#include "udf.h"
#include "SuperUdfExtension.h" //VC++ UDF Studio自带的扩展库头文件,具体参考该软件中的编程手册
#pragma comment(lib, "SuperUdfExtension.lib")  //VC++ UDF Studio自带的扩展库的lib文件 

int GetZoneIdByName(CString zoneName); //函数声明

DEFINE_ADJUST(show_gradient, domain)
{
  face_t f;
  real T_gradient[ND_ND];
  int theInletID = GetZoneIdByName("inlet"); //根据边界名字获取其ID,如果返回-1表示找不到
  if (-1 == theInletID)
  {
      Message("Cannot find the boundary name 'inlet'. Please modify!\n");
      return;
  }

  Thread * wall_thread = Lookup_Thread(domain, theInletID); //根据inlet的ID获得其Thread
  Thread *fluid_thread = THREAD_T0(wall_thread); //获得和边界face邻接的cell的thread

  begin_f_loop(f, wall_thread) //对inlet的面进行循环
  {
      cell_t c0 = F_C0(f, wall_thread); //获得和inlet面邻接的cell
      NV_V(T_gradient, =, C_T_G(c0, fluid_thread)); //将温度梯度赋值给T_gradient
      Message("c=%d, Temperature X gradient is %g\n", c0, T_gradient[0]);   //打印x方向的梯度
  }
  end_f_loop(f, wall_thread)
}

int GetZoneIdByName(CString zoneName)  //适用于所有Fluent版本
{
    int returnID = -1;
    Domain*domain = Get_Domain(1);
    CString strCurrentFluentVersion;
    strCurrentFluentVersion.Format("%d.%d", RampantReleaseMajor, RampantReleaseMinor);//格式化当前Fluent版本为字符串形式
    double fCurrentFluentVersion = atof(strCurrentFluentVersion.GetBuffer()); //当前Fluent版本转为double类型

    if (fCurrentFluentVersion <= 19.2)  // 对于Fluent6.3-19.2,只能调用VC++ UDF Studio扩展库
    {
        SuperUdf_Initialize(AfxGetInstanceHandle()); //调用VC++ UDF Studio扩展库中任何函数之前必须调用此初始化函数,具体参考该软件中的编程手册
#if !RP_NODE
        returnID = SuperUdf_GetZoneIdByName(zoneName.GetBuffer()); //调用VC++ UDF Studio扩展库中的SuperUdf_GetZoneIdByName函数,具体参考该软件中的编程手册
#endif
        host_to_node_int_1(returnID);
    }
    else // 对于Fluent version >=19.3,有直接UDF函数可以实现
    {
        Thread*tf;
        thread_loop_f(tf, domain) //对所有面的thread进行循环查找
        {
            if (0 == zoneName.CompareNoCase(THREAD_NAME(tf))) //对比名字是否相同
            {
                returnID = THREAD_ID(tf);
                break;
            }
        }
        if (-1 == returnID)  //如果面的thread中无法找到匹配名字
        {
            Thread*tc;
            thread_loop_c(tc, domain) //对所有网格的thread进行循环查找
            {
                if (0 == zoneName.CompareNoCase(THREAD_NAME(tc))) //对比名字是否相同
                {
                    returnID = THREAD_ID(tc);
                    break;
                }
            }
        }
    }

    return returnID;
}

以上程序是想要在每次迭代之前显示x方向的温度梯度的值。其中,GetZoneIdByName函数是根据边界的名字来获得其ID,可以参考博文《Fluent UDF中根据zone的名字获取ID》(https://www.cnblogs.com/SuperUDF/articles/15886289.html)。

注意:请确保有名字为inlet的入口边界,否则加载UDF的时候就会报告“Cannot find the boundary name 'inlet'. Please modify!”,说明找不到名字为inlet的边界。

 

源码输入完毕以后,点击“编译UDF”按钮就可以编译UDF了,编译成功后点击“UDF库加载到Fluent”按钮就可以加载UDF库了。

 

 

 加载成功以后,我们就需要手动将DEFINE_ADJUSThookFluent。这样才能每次迭代之前执行我们的Adjust宏。

 

  

 初始化然后开始迭代计算,但是计算第一步就直接报错,说明我们已经顺利“入坑”。

 

 

 

 然而,面对这个毫无头绪的错误提示,可能连坑在哪里都不知道,那怎么办呢?可以启用VC++ UDF Studio插件的调试功能。即在宏内第一行鼠标右键然后菜单选择“Insert Breakpoint”,此时该行前面就会出现一个圆球,表示断点已经插入。 一旦程序运行到该行,就会中断。点击三角形的按钮进入调试模式,然后重新初始化开始迭代计算。

 

 

 

 开始迭代计算后,程序马上就中断在圆球的断点处,并显示一个黄色箭头,这就说明fluent执行到该行被中断暂停了(但尚未执行该行),然后我们就可以手动一步一步跟踪后面每一行是否能正确运行。

 

 

 单击Debug菜单中的“Step Over”项,或者快捷键F10,就能手动执行一行程序,如果没有出现错误提示,那说明改行能正确运行,同时我们还可以在变量值显示区查看执行该行的变量值有什么变化,检查变量值是不是如我们所设想的那样,从而可以发现潜在错误。

 

 

 当我们一步一步执行到C_T_G这一行时,前面那个错误就跳出来了,那就说明错误就发生在这一行,需要我们仔细考虑如何修改。

 

 

首先,我们知道这个是温度梯度,可是我们连能量方程都没有开,哪来的温度呢?所以第一坑就是要对什么方程求解什么传输变量要有理解,例如取温度梯度必须开能量方程,VOF梯度也必须开多相流模型。这样,我们先把第一个坑给填平了,打开能量控制方程。

 

 

 

 重复前面的调试过程,然而错误提示依旧出现,而且还是这一行出问题。我们通过查阅UDF手册发现里面有这么一句话,“默认情况下求解器会不断移除梯度数据”。如果需要保留梯度数据,需要使用“solver/set/expert”的TUI命令回答“yes”来保留。原来,第二个坑在这里。

 OK,那我们继续填平第二个“坑”,在控制台里面输入“solver/set/expert”的TUI命令,并对于“Keep temporary solver memory from being freed?”回答yes

 

 

 

 然后重复前面的调试过程,然而错误提示依旧出现,还是定位在C_T_G这一行。这个可够让人郁闷的,怎么回事?经过笔者的研究,原来这最大的“坑”是因为迭代第一步迭代之前梯度还没有建立,而adjust恰恰是每一步迭代前调用的,所以第一步迭代之前是取不到梯度的,需要迭代第二步迭代前才有值。这样,刚开始迭代第一步就跳出错误。为了避免这个问题,我们需要写一个判断语句。 

      if(NULL!=T_STORAGE_R_NV(fluid_thread, SV_T_G))  // 如果温度梯度已经建立,一般迭代第二步以后就有梯度值了
          NV_V(T_gradient, =, C_T_G(c0, fluid_thread));
      else // 如果温度梯度尚未建立,一般是迭代第一步的开始
          NV_D(T_gradient,=,0,0,0);

其中,SV_T_G是温度梯度的存储序号。类似地,如果是VOF梯度,其序号为SV_VOF_G。整个语句结构的意思就是:对于梯度尚未建立的情况(一般是迭代第一步的开始),直接赋值零梯度。后面当梯度有存储值以后,就可以调用梯度了。正确的完整程序如下: 

#include "udf.h"
#include "SuperUdfExtension.h" //VC++ UDF Studio自带的扩展库头文件,具体参考该软件中的编程手册
#pragma comment(lib, "SuperUdfExtension.lib")  //VC++ UDF Studio自带的扩展库的lib文件 

int GetZoneIdByName(CString zoneName); //函数声明

DEFINE_ADJUST(show_gradient, domain)
{
  face_t f;
  real T_gradient[ND_ND];
  int theInletID = GetZoneIdByName("inlet"); //根据边界名字获取其ID,如果返回-1表示找不到
  if (-1 == theInletID)
  {
      Message("Cannot find the boundary name 'inlet'. Please modify!\n");
      return;
  }

  Thread * wall_thread = Lookup_Thread(domain, theInletID);
  Thread *fluid_thread = THREAD_T0(wall_thread);

  begin_f_loop(f, wall_thread)
  {
      cell_t c0 = F_C0(f, wall_thread);
      if(NULL!=T_STORAGE_R_NV(fluid_thread, SV_T_G))  // 如果温度梯度已经建立,一般迭代第二步以后就有梯度值了
          NV_V(T_gradient, =, C_T_G(c0, fluid_thread));
      else // 如果温度梯度尚未建立,一般是迭代第一步的开始
          NV_D(T_gradient,=,0,0,0);
      Message("c=%d, Temperature X gradient is %g\n", c0, T_gradient[0]);     
  }
  end_f_loop(f, wall_thread)
}

int GetZoneIdByName(CString zoneName)  //适用于所有Fluent版本
{
    int returnID = -1;
    Domain*domain = Get_Domain(1);
    CString strCurrentFluentVersion;
    strCurrentFluentVersion.Format("%d.%d", RampantReleaseMajor, RampantReleaseMinor);//格式化当前Fluent版本为字符串形式
    double fCurrentFluentVersion = atof(strCurrentFluentVersion.GetBuffer()); //当前Fluent版本转为double类型

    if (fCurrentFluentVersion <= 19.2)  // 对于Fluent6.3-19.2,只能调用VC++ UDF Studio扩展库
    {
        SuperUdf_Initialize(AfxGetInstanceHandle()); //调用VC++ UDF Studio扩展库中任何函数之前必须调用此初始化函数,具体参考该软件中的编程手册
#if !RP_NODE
        returnID = SuperUdf_GetZoneIdByName(zoneName.GetBuffer()); //调用VC++ UDF Studio扩展库中的SuperUdf_GetZoneIdByName函数,具体参考该软件中的编程手册
#endif
        host_to_node_int_1(returnID);
    }
    else // 对于Fluent version >=19.3,有直接UDF函数可以实现
    {
        Thread*tf;
        thread_loop_f(tf, domain) //对所有面的thread进行循环查找
        {
            if (0 == zoneName.CompareNoCase(THREAD_NAME(tf))) //对比名字是否相同
            {
                returnID = THREAD_ID(tf);
                break;
            }
        }
        if (-1 == returnID)  //如果面的thread中无法找到匹配名字
        {
            Thread*tc;
            thread_loop_c(tc, domain) //对所有网格的thread进行循环查找
            {
                if (0 == zoneName.CompareNoCase(THREAD_NAME(tc))) //对比名字是否相同
                {
                    returnID = THREAD_ID(tc);
                    break;
                }
            }
        }
    }

    return returnID;
}

执行结果如下,可以看到第一次迭代前的梯度因为没有存储值,所以取零值,第二步开始就有具体的值了。

 

posted @ 2022-12-01 22:37  SuperUDF  阅读(1890)  评论(0)    收藏  举报