2.5. Discrete Phase Model (DPM) DEFINE Macros

This section contains descriptions of DEFINE macros for the discrete phase model (DPM). Table 2.12: Quick Reference Guide for DPM-Specific DEFINE Macros provides a quick reference guide to the DPM DEFINE macros, the functions they define, and the dialog boxes where they are activated in Ansys Fluent. Definitions of each DEFINE macro are contained in the udf.h header file. For your convenience, they are listed in Appendix B: DEFINE Macro Definitions.

Table 2.12: Quick Reference Guide for DPM-Specific DEFINE Macros

Function

DEFINE Macro

Dialog Box Activated In

particle state at boundaries

DEFINE_DPM_BC

boundary condition (for example, Velocity Inlet)

body forces on particles

DEFINE_DPM_BODY_FORCE

Discrete Phase Model

drag coefficients between particles and fluid

DEFINE_DPM_DRAG

Discrete Phase Model

erosion and accretion rates

DEFINE_DPM_EROSION

Discrete Phase Model

heat and mass transfer of multicomponent particles to the gas phase

DEFINE_DPM_HEAT_MASS

Set Injection Properties

initializes injections

DEFINE_DPM_INJECTION_INIT

Set Injection Properties

custom laws for particles

DEFINE_DPM_LAW

Custom Laws

modifies what is written to the sampling plane output

DEFINE_DPM_OUTPUT

Sample Trajectories

material properties

DEFINE_DPM_PROPERTY

Create/Edit Materials

updates scalar every time a particle position is updated

DEFINE_DPM_SCALAR_UPDATE

Discrete Phase Model

particle source terms

DEFINE_DPM_SOURCE

Discrete Phase Model

particle collisions algorithm

DEFINE_DPM_SPRAY_COLLIDE

Discrete Phase Model

changes the criteria for switching between laws

DEFINE_DPM_SWITCH

Custom Laws

time step control for DPM simulation

DEFINE_DPM_TIMESTEP

Discrete Phase Model

equilibrium vapor pressure of vaporizing components of multicomponent particles

DEFINE_DPM_VP_EQUILIB

Create/Edit Materials

particle impingement regime selection criteria DEFINE_IMPINGEMENT

Discrete Phase Model

particle impingement regimes DEFINE_FILM_REGIME

Discrete Phase Model

distribution of splashed particles DEFINE_SPLASHING_DISTRIBUTION

Discrete Phase Model



Note:  If you plan to use the Hybrid parallel DPM tracking mode, the following special requirement must be considered for UDFs. Since multiple concurrent threads may be used in a single compute node (each being executed by its own CPU core, but all together forming a single process), access to most variables is shared among these concurrent threads. Therefore, every UDF that has access to a Tracked_Particle pointer variable must never change a value that is neither part of the Tracked_Particle data structure nor of any other structure or array that is entirely specific to that particular Tracked_Particle. Values in memory that must not be changed by such UDFs to which Ansys Fluent passes a Tracked_Particle pointer include (but are not limited to):

  • cell, face, and node values

  • injection properties

  • material properties

  • any global variables


2.5.1. DEFINE_DPM_BC

2.5.1.1. Description

You can use DEFINE_DPM_BC to specify your own boundary conditions for particles. The function is executed every time a particle touches a boundary of the domain, except for symmetric or periodic boundaries. You can define a separate UDF (using DEFINE_DPM_BC) for each boundary.

2.5.1.2. Usage

DEFINE_DPM_BC (name, tp, t, f, f_normal, dim)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

Thread *t

Pointer to the face thread the particle is currently hitting.

face_t f

Index of the face that the particle is hitting.

real f_normal[]

Array that contains the unit vector which is normal to the face.

int dim

Dimension of the flow problem. The value is 2 in 2D, for 2D-axisymmetric and 2D-axisymmetric-swirling flow, while it is 3 in 3D flows.

Function returns

int

There are six arguments to DEFINE_DPM_BC: name, tp, t, f, f_normal, and dim. You supply name, the name of the UDF. tp, t, f, f_normal, and dim are variables that are passed by the Ansys Fluent solver to your UDF. Your UDF will need to compute the new velocity of a particle after hitting the wall, and then return the status of the particle track (as an int), after it has hit the wall.

For the return status PATH_ACTIVE, the particle continues to track. For the return status PATH_ABORT, the particle will be stopped and considered to be aborted. For the return status PATH_END, the particle will be stopped as well, but considered to have escaped from the domain.


Important:  Pointer tp can be used as an argument to the particle-specific macros (defined in DPM Macros) to obtain information about particle properties.


2.5.1.3. Example 1

This example shows the usage of DEFINE_DPM_BC for a simple reflection at walls. It is similar to the reflection method executed by Ansys Fluent except that Ansys Fluent accommodates moving walls. The function must be executed as a compiled UDF.

The function assumes an ideal reflection for the normal velocity component (nor_coeff 1) while the tangential component is damped (tan_coeff 0.3). First, the angle of incidence is computed. Next, the normal particle velocity, with respect to the wall, is computed and subtracted from the particles velocity. The reflection is complete after the reflected normal velocity is added. The new particle velocity has to be stored in TP_VEL0 to account for the change of particle velocity in the momentum balance for coupled flows. The function returns PATH_ACTIVE for inert particles while it stops particles of all other types.

/* reflect boundary condition for inert particles */
 #include "udf.h"
 DEFINE_DPM_BC(bc_reflect,tp,t,f,f_normal,dim)
 {
    real alpha; /* angle of particle path with face normal */
    real vn=0.;
    real nor_coeff = 1.;
    real tan_coeff = 0.3;
    real normal[3];
    int i, idim = dim;
    real NV_VEC(x);
 
 #if RP_2D
    /* dim is always 2 in 2D compilation. Need special treatment for 2d
      axisymmetric and swirl flows */
    if (rp_axi_swirl)
      {
         real R = sqrt(TP_POS(tp)[1]*TP_POS(tp)[1] +
              TP_POS(tp)[2]*TP_POS(tp)[2]);
         if (R > 1.e-20)
           {
              idim = 3;
              normal[0] = f_normal[0];
              normal[1] = (f_normal[1]*TP_POS(tp)[1])/R;
              normal[2] = (f_normal[1]*TP_POS(tp)[2])/R;
           }
         else
           {
              for (i=0; i<idim; i++)
                normal[i] = f_normal[i];
           }
        }
    else
 #endif
    for (i=0; i<idim; i++)
       normal[i] = f_normal[i];

    if(TP_TYPE(tp) == DPM_TYPE_INERT)
      {
         alpha = M_PI/2. - acos(MAX(-1.,MIN(1.,NV_DOT(normal,TP_VEL(tp))/
              MAX(NV_MAG(TP_VEL(tp)),DPM_SMALL))));
         if ((NNULLP(t)) && (THREAD_TYPE(t) == THREAD_F_WALL))
               F_CENTROID(x,f,t);

         /* calculate the normal component, rescale its magnitude by
           the coefficient of restitution and subtract the change */

         /* Compute normal velocity. */
         for(i=0; i<idim; i++)
           vn += TP_VEL(tp)[i]*normal[i];

         /* Subtract off normal velocity. */
           for(i=0; i<idim; i++)
             TP_VEL(tp)[i] -= vn*normal[i];

         /* Apply tangential coefficient of restitution. */
           for(i=0; i<idim; i++)
             TP_VEL(tp)[i] *= tan_coeff;

         /* Add reflected normal velocity. */
           for(i=0; i<idim; i++)
             TP_VEL(tp)[i] -= nor_coeff*vn*normal[i];

         /* Store new velocity in TP_VEL0 of particle */
         for(i=0; i<idim; i++)
           TP_VEL0(tp)[i] = TP_VEL(tp)[i];

         return PATH_ACTIVE;
      }
  return PATH_ABORT;
 } 

2.5.1.4. Example 2

This example shows how to use DEFINE_DPM_BC for a wall impingement model. The function must be executed as a compiled UDF.

 #include "udf.h"
 #include "dpm.h"
 #include "surf.h"
 #include "random.h"
 
 /* define a user-defined dpm boundary condition routine
 * bc_reflect: name
 * tp:  the tracked particle
 * t:   the touched face thread
 * f:   the touched face
 * f_normal:  normal vector of touched face
 * dim:  dimension of the problem (2 in 2d and 2d-axi-swirl, 3 in 3d)
 *
 * return is the status of the particle, see enumeration of Path_Status
 * in dpm.h
 */
 
 #define V_CROSS(a,b,r)\
     ((r)[0] = (a)[1]*(b)[2] - (b)[1]*(a)[2],\
     (r)[1] = (a)[2]*(b)[0] - (b)[2]*(a)[0],\
     (r)[2] = (a)[0]*(b)[1] - (b)[0]*(a)[1])
  
 DEFINE_DPM_BC(bc_wall_jet, tp, thread, f, f_normal, dim)
 {
    /*
      Routine implementing the Naber and Reitz Wall
      impingement model (SAE 880107)
    */
    real normal[3];
    real tan_1[3];
    real tan_2[3];
    real rel_vel[3];
    real face_vel[3];
    real alpha, beta, phi, cp, sp;
    real rel_dot_n, vmag, vnew, dum;
    real weber_in, weber_out;
    int i, idim = dim;
    cxboolean moving = (SV_ALLOCATED_P (thread,SV_BND_GRID_V) &&
    SV_ALLOCATED_P (thread,SV_WALL_V));
 
 #if RP_2D
    if (rp_axi_swirl)
      {
         real R = sqrt(TP_POS(tp)[1]*TP_POS(tp)[1] +
            TP_POS(tp)[2]*TP_POS(tp)[2]);
         if (R > 1.e-20)
           {
              idim = 3;
              normal[0] = f_normal[0];
              normal[1] = (f_normal[1]*TP_POS(tp)[1])/R;
              normal[2] = (f_normal[1]*TP_POS(tp)[2])/R;
           }
         else
           {
              for (i=0; i<idim; i++)
                normal[i] = f_normal[i];
           }
        }
    else
 #endif

    for (i=0; i<idim; i++)
       normal[i] = f_normal[i];

      /*
        Set up velocity vectors and calculate the Weber number
        to determine the regime.
      */

    for (i=0; i<idim; i++)
      {
         if (moving)
           face_vel[i] = WALL_F_VV(f,thread)[i] + BOUNDARY_F_GRID_VV(f,thread)[i];
         else
           face_vel[i] = 0.0;
           rel_vel[i] = TP_VEL(tp)[i] - face_vel[i];
      }

    vmag = MAX(NV_MAG(rel_vel),DPM_SMALL);
    rel_dot_n = MAX(NV_DOT(rel_vel,normal),DPM_SMALL);
    weber_in = TP_RHO(tp) * SQR(rel_dot_n) * TP_DIAM(tp) /
      MAX(DPM_SURFTEN(tp), DPM_SMALL);

    /*
    Regime where bouncing occurs (We_in < 80).
    (Data from Mundo, Sommerfeld and Tropea
     Int. J. of Multiphase Flow, v21, #2, pp151-173, 1995)  */

    if (weber_in <= 80.)
      {
         weber_out = 0.6785*weber_in*exp(-0.04415*weber_in);
         vnew = rel_dot_n * (1.0 + sqrt(weber_out /
         MAX(weber_in, DPM_SMALL)));

     /*
     The normal component of the velocity is changed based
     on the experimental paper above (that is the Weber number
     is based on the relative velocity).
       */

         for (i=0; i < idim; i++)
           TP_VEL(tp)[i] = rel_vel[i] - vnew*normal[i] + face_vel[i];
      }

    if (weber_in > 80.)
      {
         alpha = acos(-rel_dot_n/vmag);

     /*
     Get one tangent vector by subtracting off the normal
     component from the impingement vector, then cross the
     normal with the tangent to get an out-of-plane vector.
     */

     for (i=0; i < idim; i++)
       tan_1[i] = rel_vel[i] - rel_dot_n*normal[i];

     UNIT_VECT(tan_1,tan_1);
     V_CROSS(tan_1,normal,tan_2);

     /*
     beta is calculated by neglecting the coth(alpha)   
     term in the paper (it is approximately right).
     */

     beta = MAX(M_PI*sqrt(sin(alpha)/(1.0-sin(alpha))),DPM_SMALL);
     phi= -M_PI/beta*log(1.0-cheap_uniform_random()*(1.0-exp(-beta)));
     if (cheap_uniform_random() > 0.5)
       phi = -phi;

     vnew = vmag;

     cp = cos(phi);
     sp = sin(phi);

     for (i=0; i < idim; i++)
       TP_VEL(tp)[i] = vnew*(tan_1[i]*cp + tan_2[i]*sp) + face_vel[i];

      }

  /*
    Subtract off from the original state.
  */
    for (i=0; i < idim; i++)
        TP_VEL0(tp)[i] = TP_VEL(tp)[i];

    if (DPM_STOCHASTIC_P(TP_INJECTION(tp)))
      {
         /* Reflect turbulent fluctuations also */
         /* Compute normal velocity. */

       dum = 0;
       for(i=0; i<idim; i++)
         dum += tp->V_prime[i]*normal[i];

           /* Subtract off normal velocity. */

        for(i=0; i<idim; i++)
           tp->V_prime[i] -= 2.*dum*normal[i];
      }
    return PATH_ACTIVE;
 } 

2.5.1.5. Example 3

This example shows how to use DEFINE_DPM_BC for a simple filter model. It illustrates the proper way to handle particles that cross the boundary zone and enter a new cell. If the particle leaves the current cell, this information is stored in TP_CCELL(tp).

#include "udf.h"
#include "cxsurf.h"


/**
 *  filter particles that are above a specified cut-off size; allow others to pass
 **/

#define CUT_OFF_SIZE 10.0e-6   /* define the cut-off size in meters */

DEFINE_DPM_BC(my_filter, tp, t, f, f_normal, dim)
{
  Thread *t0 = THREAD_T0(t);
  Thread *t1 = THREAD_T1(t);
  cxboolean in_t0;
  CX_Cell_Id neigh;
  
  /* determine what cell/thread the particle is currently in */
  if ( (P_CELL_THREAD(tp)->id == t0->id) && (P_CELL(tp) == F_C0(f, t)) )
    in_t0 = TRUE;
  else
    in_t0 = FALSE;
  
  /* filter particles above cut-off size */
  if (P_DIAM(tp) > CUT_OFF_SIZE)
    return PATH_END;

  /* particle diameter is below cut-off size - set the new cell and thread */
  if (in_t0)
    STORE_CX_CELL(&neigh, F_C1(f, t), t1);
  else
    STORE_CX_CELL(&neigh, F_C0(f, t), t0);

  COPY_CELL_ID(TP_CCELL(tp), &neigh);
  calcFaceEquations(tp);

  return PATH_ACTIVE;
}

2.5.1.6. Example 4

This example shows how to use DEFINE_DPM_BC to reflect a Lagrangian wall film particle off of a boundary for cases that use the subtet particle tracking algorithm. If your UDF modifies one of the particle macros (mainly, TP_POS(tp), TP_CCELL(tp), TP_FILM_FACE(tp), or TP_FILM_THREAD(tp)), you must ensure that the values of these particle variables are consistent with each other. For example, the particle position, TP_POS(tp), must be within the cell specified by TP_CCELL(tp), and the cell face specified by TP_FILM_FACE(tp) must be one of the faces of that cell. If the UDF does not modify any of these macros, then the current values will still be valid after the UDF is executed.

#include "udf.h"


/**
 *  reflect wall film particles off of a boundary when using subtet particle tracking
 **/


DEFINE_DPM_BC(my_reflect_bc, tp, tf, f, wall_normal, dim)
{
  int i;

  double dot_product;
  double normal[3];          /* wall film face normal */
  real edge_normal[3];       /* unit edge normal */

#if RP_2D
  normal[2] = 0.0;
#endif

  /* this UDF is only valid when using subtet tracking */
  if ( !dpm_par.use_subtet_tracking )
    return PATH_ABORT;

  /* get the wall film face normal */
  memcpy(normal, tp->gvtp.subtet->normal, 3 * sizeof(double));

  /* get the dot product of the film face normal and the reflected wall normal */
  dot_product = NV_DOT(normal, wall_normal);

  /* get the projection of the wall normal onto the wall film face normal*/
  for (i = 0; i < 3; i++)
    normal[i] *= dot_product;

  /* subtract the normal component from the original wall normal */
  for (i = 0; i < 3; i++)
    edge_normal[i] = wall_normal[i] - (real) normal[i];  /* in-plane (edge) normal */
  
  /* reflect the particle in the plane of the wall film face */
  UNIT_VECT(edge_normal, edge_normal);
  Reflect_Particle(tp, edge_normal, NULL, f, tf, NULL, -1);

  return PATH_ACTIVE;
}

2.5.1.7. Example 5

The following example shows the usage of the DEFINE_DPM_BC user-defined function (UDF) to:

  • Choose an injection from which position and velocity are obtained for every particle to be reinjected

  • Further modify the particle state

#include "udf.h"
DEFINE_DPM_BC(reinj, tp, t, f, normal, dim)
{
  /* prepare some variables used below:
   */
  cxboolean reinj_unsteady_tracking = dpm_par.unsteady_tracking;
  cxboolean reinj_update = TRUE;
  cxboolean reinj_update_summary = (((0 == c_par.skip) &&  ! reinj_unsteady_tracking) ||
                                    (reinj_unsteady_tracking && reinj_update)) &&
                                   (dpm_par.is_pathline == 0);


  /* Determine which injection to use for the reinjection --
   * in this simple example, particles whose Y coordinate (TP_POS(tp)[1])
   * exceeds 0.02_m will use injection "single-inj-no-mfr", all others "inlet-inj-no-mfr":
   */
  char *this_reinj_inj_name = TP_POS(tp)[1] > 0.02 ? "single-inj-no-mfr" : "inlet-inj-no-mfr";
  Injection *this_reinj_inj = Pick_Injection(this_reinj_inj_name);


  /* Now "this_reinj_inj" should be a pointer to the injection struct.
   * Let us check whether we've actually managed to choose one:
   */
  if (NULLP(this_reinj_inj))
  {
    DELAY_ERROR("reinj UDF: cannot find injection: %s", this_reinj_inj_name);
    return PATH_END;     /* Fluent will call "Escape_Tracked_Particle(..) */
  }


  /* OK, some injection was found -- assign it to the Tracked Particle:
   */
  tp->reinj_inj = this_reinj_inj;


  /* Add the particle to the dpm summary report for this boundary zone:
   */
  if (reinj_update_summary)
    add_to_dpm_summary(tp, FATE_BC_UDF, t);


  /* Have Fluent do some things that ought to be done
   * for every particle that escapes or is reinjected:
   */
  Escape_OR_Reinject_Tracked_Particle(tp, reinj_update_summary, f, t);


  /* EXAMPLE: Delay the reinjection by 0.5_s:
   */
  TP_TIME(tp) += 0.5;
  PP_TIME(tp->pp) += 0.5;

  return PATH_REINJECT;
}

This function performs the following:

  • Sets the pointer tp->reinj_inj to the obtained injection pointer value, thus indicating the injection from which the new particle position and velocity will be copied.

  • Calls Escape_OR_Reinject_Tracked_Particle(tp, update_summary, f, t) to make sure that DPM source terms are calculated correctly, and the internal balance of the DDPM phase content is updated.

  • Returns the status PATH_REINJECT indicating that the particle is to be reinjected.

The UDF shows how you can delay particle reinjection, thus accounting for a significant residence time of particles inside a recirculation line outside the meshed CFD domain. To achieve this, the UDF adds the time offset to both TP_TIME(tp) and PP_TIME(tp->pp). The particles will be moved to their new position and assigned their new velocity and a start time, and until the simulation reaches that time, the particles will remain still at their positions and not participate in the simulated processes. Note that you can also specify Reinjection Time Delay during particle reinjection in the Set Injection Properties dialog box (Point Properties tab). See Injection Types in the Fluent User's Guide for more information.

2.5.1.8. Hooking a DPM Boundary Condition UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_BC is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the appropriate boundary condition dialog box (for example, the Velocity Inlet dialog box) in Ansys Fluent. See Hooking DEFINE_DPM_BC UDFs for details on how to hook your DEFINE_DPM_BC UDF to Ansys Fluent.

2.5.2. DEFINE_DPM_BODY_FORCE

2.5.2.1. Description

You can use DEFINE_DPM_BODY_FORCE to specify a body force other than a gravitational or drag force on the particles.

2.5.2.2. Usage

DEFINE_DPM_BODY_FORCE (name, tp, i)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

int i

An index (0, 1, or 2) that identifies the Cartesian component of the body force that is to be returned by the function.

Function returns

real

There are three arguments to DEFINE_DPM_BODY_FORCE: name, tp, and i. You supply name, the name of the UDF. tp and i are variables that are passed by the Ansys Fluent solver to your UDF. Your UDF will need to return the real value of the acceleration due to the body force (in m/s2) to the Ansys Fluent solver.


Important:  Pointer tp can be used as an argument to the macros defined in DPM Macros to specify a body force other than a gravitational or drag force on the particles. Note that the high-resolution particle tracking option requires that forces on the particle are continuous across cell boundaries. Ansys Fluent provides the DEFINE_DPM_INTERPOLATION UDF for this purpose. This UDF permits barycentric interpolation of user-defined memory. The DEFINE_DPM_INTERPOLATION UDF is hooked using the following text command:

define/models/dpm/numerics/high-resolution-tracking/barycentric-interpolation/user-interpolation-function

This enables the interpolation of cell UDM and continuous values of the UDM across cell boundaries. This is necessary if the acceleration of the particle depends upon the UDF to avoid discontinuities in the particle forces.

See Example for an example of DEFINE_DPM_BODY_FORCE and DEFINE_DPM_INTERPOLATION usage for cases with the high high-resolution particle tracking option enabled.


2.5.2.3. Example

The following UDF computes the electrostatic force on a charged particle. It consists of two functions: one that enables the continuous interpolation of the electrostatic field to the particle position, and another that calculates the acceleration of the particle due to the electric field. This example assumes that the electric field components at the cell centroid are stored in user-defined memory.

DEFINE_DPM_BODY_FORCE is called at every particle time step in Ansys Fluent and requires a significant amount of CPU time to execute. For this reason, the UDF should be executed as a compiled UDF.

#include "udf.h"
#include "dpm_interpolation.h"

/* setup interpolation of the electrostatic field */
DEFINE_DPM_INTERPOLATION(dpm_setup_interp, tp)
{
  /* interpolate the electrostatic field to the particle position */
  if ( HIGH_RESOLUTION_TRACKING_ENABLED )
  {
    
    if (N_UDM < 7)
      return;
    
      /* specify now many and which UDM should be interpolated */
    int n_udm = 3;
    int udm_components[] = { 4, 5, 6 };

    /* interpolate UDM 4, 5, and 6 to the particle position */
    DPM_Interpolate_UDM_from_Nodes(tp, n_udm, udm_components);
  }
}

/* calculate the electrostatic force (acceleration) on charged particles */
DEFINE_DPM_BODY_FORCE(particle_E_force, tp, i)
{
  /* From Bell and Hockberg correlation at 30k RPM and 70kV */
  const real chargedensity = 3.884e-3;   /* C/kg */
  real electric_field;
  
  if (N_UDM < 7)
    return 0.0;

  if ( HIGH_RESOLUTION_TRACKING_ENABLED )
    electric_field = TP_UDMI(tp, 4 + i);  /* E-field at the particle position */
  else
  {
    Thread *t = TP_CELL_THREAD(tp);
    cell_t c  = TP_CELL(tp);

    electric_field = C_UDMI(c, t, 4 + i); /* E-field at the cell centroid */
  }
  
  /* return the acceleration due to the electric force */
  return electric_field * chargedensity;
}

2.5.2.4. Hooking a DPM Body Force UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_BODY_FORCE is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Discrete Phase Model dialog box in Ansys Fluent. See Hooking DEFINE_DPM_BODY_FORCE UDFs for details on how to hook your DEFINE_DPM_BODY_FORCE UDF to Ansys Fluent.

2.5.3. DEFINE_DPM_DRAG

2.5.3.1. Description

You can use DEFINE_DPM_DRAG to specify the drag between particles and fluid as a dimensionless group () as it appears in the drag force per unit particle mass:

(2–19)

where 
 

= viscosity.

 

= particle density

 

= particle diameter

 

= drag coefficient

 

= Reynolds number

2.5.3.2. Usage

DEFINE_DPM_DRAG (name, Re, tp)

Argument Type

Description

symbol name

UDF name.

real Re

Particle Reynolds number based on the particle diameter and relative gas velocity.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

Function returns

real

There are three arguments to DEFINE_DPM_DRAG: name, Re, and tp. You supply name, the name of the UDF. Re and tp are variables that are passed by the Ansys Fluent solver to your UDF. Your UDF will need to return the real value of the drag force on a particle. The value returned to the solver must be dimensionless and represent 18 * Cd * Re / 24.


Important:  Pointer tp can be used as an argument to the macros defined in DPM Macros to obtain information about particle properties (for example, injection properties). Note that the high-resolution particle tracking requires forces on the particle, including the drag force, to be continuous across cell boundaries. Discontinuities in the force can result in particles being lost or stuck at interior cell faces.


2.5.3.3. Example

The following UDF, named particle_drag_force, computes the drag force on a particle and is a variation of the body force UDF presented in DEFINE_DPM_BODY_FORCE . The flow is the same, but a different curve is used to describe the particle drag. DEFINE_DPM_DRAG is called at every particle time step in Ansys Fluent, and requires a significant amount of CPU time to execute. For this reason, the UDF should be executed as a compiled UDF.

/***********************************************************************
   UDF for computing particle drag coefficient (18 Cd Re/24)
   curve as suggested by R. Clift, J. R. Grace and M. E. Weber
   "Bubbles, Drops, and Particles" (1978)
 ************************************************************************/
 
 #include "udf.h"
 
 DEFINE_DPM_DRAG(particle_drag_force,Re,tp)
 {
    real w, drag_force;
    if (Re < 0.01)
    {
      drag_force=18.0;
      return (drag_force);
    }
    else if (Re < 20.0)
    {
      w = log10(Re);
      drag_force = 18.0 + 2.367*pow(Re,0.82-0.05*w) ;
      return (drag_force);
    }
    else
    /* Note: suggested valid range 20 < Re < 260 */
    {
      drag_force = 18.0 + 3.483*pow(Re,0.6305) ;
      return (drag_force);
    }
 } 

2.5.3.4. Hooking a DPM Drag Coefficient UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_DRAG is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Set Injection Properties dialog box in Ansys Fluent. See Hooking DEFINE_DPM_DRAG UDFs for details on how to hook your DEFINE_DPM_DRAG UDF to Ansys Fluent.

2.5.4. DEFINE_DPM_EROSION

2.5.4.1. Description

You can use DEFINE_DPM_EROSION to specify the erosion and accretion rates calculated as the particle stream strikes a wall surface or hits a porous jump surface. The function is called when the particle encounters a reflecting surface.

2.5.4.2. Usage

DEFINE_DPM_EROSION (name, tp, t, f, normal, alpha, Vmag, mdot)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

Thread *t

Pointer to the face thread the particle is currently hitting.

face_t f

Index of the face that the particle is hitting.

real normal[]

Array that contains the unit vector that is normal to the face.

real alpha

Variable that represents the impact angle between the particle path and the face (in radians).

real Vmag

Variable that represents the magnitude of the particle velocity (in m/s).

real mdot

Steady flow simulations: Current flow rate of the particle stream per number of stochastic tries as it hits the face (kg/s).

Transient flow simulations: Mass of the parcel hitting the face (kg).

Axial symmetric configurations: The value is per 2π.

Function returns

void

There are eight arguments to DEFINE_DPM_EROSION: name, tp, t, f, normal, alpha, Vmag, and mdot. You supply name, the name of the UDF. tp, t, f, normal, alpha, Vmag, and mdot are variables that are passed by the Ansys Fluent solver to your UDF. Your UDF will need to compute the values for the erosion rate and/or accretion rate and store the values at the faces in F_STORAGE_R_XV(f, t, SV_DPMS_EROSION, EROSION_UDF) and F_STORAGE_R (f,t,SV_DPMS_ACCRETION), respectively.


Important:  Pointer tp can be used as an argument to the macros defined in DPM Macros to obtain information about particle properties (for example, injection properties).


2.5.4.3. Example

The following is an example of a compiled UDF that uses DEFINE_DPM_EROSION to extend postprocessing of wall impacts in a 2D axisymmetric flow. It provides additional information on how the local particle deposition rate depends on the diameter and normal velocity of the particles. It is based on the assumption that every wall impact leads to more accretion, and, therefore, every trajectory is removed at its first wall impact. (This is done by setting the flag P_FL_REMOVED within DEFINE_DPM_EROSION.) User-defined memory locations (UDMLs) are used to store and visualize the following:

  • number of wall impacts since UDMLs were reset. (Resetting is typically done at the beginning of an Ansys Fluent session by the use of DEFINE_ON_DEMAND in order to avoid the use of uninitialized data fields. Resetting prevents the addition of sampled data being read from a file).

  • average diameter of particles hitting the wall.

  • average radial velocity of particles.


Note:  Before tracing the particles, you will have to reset the UDMLs and assign the global domain pointer by executing the DEFINE_ON_DEMAND UDF function, reset_UDM(), which appears at the end of this Example.


/***********************************************************************
   UDF for extending postprocessing of wall impacts
 ************************************************************************/
 #include "udf.h"
 
#define MIN_IMPACT_VELO -1000.
/* Minimum particle velocity normal to wall (m/s) to allow Accretion.*/
 
Domain *domain; /* Get the domain pointer and assign it later to domain*/
 
enum   /* Enumeration of used User-Defined Memory Locations. */
  {
    NUM_OF_HITS, /* Number of particle hits into wall face considered.*/
    AVG_DIAMETER, /* Average diameter of particles that hit the wall. */
    AVG_RADI_VELO, /* Average radial velocity of "" "" ------------ */
    NUM_OF_USED_UDM
  };
 
int UDM_checked = 0;  /* Availability of UDMLs checked? */
 
void reset_UDM_s(void); /* Function to follow below. */
 
int check_for_UDM(void)  /* Check for UDMLs availability... */
{
  Thread *t;
  if (UDM_checked)
    return UDM_checked;
 
  thread_loop_c(t,domain) /* We require all cell threads to */
  {       /* provide space in memory for UDML */
     if (FLUID_THREAD_P(t))   if (NULLP(THREAD_STORAGE(t,SV_UDM_I)))
        return 0;
  }
  UDM_checked = 1;  /* To make the following work properly... */
  reset_UDM_s();   /* This line will be executed only once, */
  return UDM_checked; /* because check_for_UDM checks for */
}      /* UDM_checked first. */
 
void reset_UDM_s(void)
{
  Thread *t;
  cell_t c;
  face_t f;
  int  i;
  if (!check_for_UDM()) /* Dont do it, if memory is not available. */
    return;
  Message("Resetting User Defined Memory...\n");
  thread_loop_f(t, domain)
    {
      if (NNULLP(THREAD_STORAGE(t,SV_UDM_I)))
        {
          begin_f_loop(f,t)
	         {
	           for (i = 0; i < NUM_OF_USED_UDM; i++)
		           F_UDMI(f,t,i) = 0.;
	         }
          end_f_loop(f, t)
        }
     else
       {
	         Message("Skipping FACE thread no. %d..\n", THREAD_ID(t));
       }
    }
  thread_loop_c(t,domain)
    {
      if (NNULLP(THREAD_STORAGE(t,SV_UDM_I)))
	       {
          begin_c_loop(c,t)
	         {
	           for (i = 0; i < NUM_OF_USED_UDM; i++)
		           C_UDMI(c,t,i) = 0.;
	         }
          end_c_loop(c,t)
	       }
      else
	       {
          Message(" Skipping CELL thread no. %d..\n", THREAD_ID(t));
	       }
    }    /* Skipping Cell Threads can happen if the user */
  /* uses reset_UDM prior to initializing. */
  Message(" --- Done.\n");
}
 
DEFINE_DPM_EROSION(dpm_accr, tp, t, f, normal, alpha, Vmag, Mdot)
{
  real A[ND_ND], area;
  int num_in_data;
  Thread *t0;
  cell_t c0;
  real imp_vel[3], vel_ortho;

#if RP_2D  
  if (rp_axi) 
    {
      real radi_pos[3], radius;
      /* The following is ONLY valid for 2d-axisymmetric calculations!!! */
      /* Additional effort is necessary because DPM tracking is done in */
      /* THREE dimensions for TWO-dimensional axisymmetric calculations. */
      
      radi_pos[0] = TP_POS(tp)[1];  /* Radial location vector. */
      radi_pos[1] = TP_POS(tp)[2];  /* (Y and Z in 0 and 1...) */
      radius = NV_MAG(radi_pos);
      NV_VS(radi_pos, =, radi_pos, /, radius);
      /* Normalized radius direction vector.*/
      imp_vel[0] = TP_VEL(tp)[0]; /* Axial particle velocity component. */
      imp_vel[1] = NVD_DOT(radi_pos, TP_VEL(tp)[1], TP_VEL(tp)[2], 0.);
    }
  else
#endif
    NV_V(imp_vel, =, TP_VEL(tp));
  
  /* Dot product of normalized radius vector and y & z components */
  /* of particle velocity vector gives _radial_ particle velocity */
  /* component */
  vel_ortho = NV_DOT(imp_vel, normal); /*velocity orthogonal to wall */

  if (vel_ortho < MIN_IMPACT_VELO) /* See above, MIN_IMPACT_VELO */
    return;
  
  if (!UDM_checked)   /* We will need some UDMs, */
    if (!check_for_UDM()) /* so check for their availability.. */
      return;    /* (Using int variable for speed, could */
  /* even just call check_for UDFM().) */
  
  c0 = F_C0(f,t);
  t0 = THREAD_T0(t);
  
  F_AREA(A,f,t);
  area = NV_MAG(A);
  F_STORAGE_R(f,t,SV_DPMS_ACCRETION) += Mdot / area;
  
  MARK_TP(tp, P_FL_REMOVED); /* Remove particle after accretion */

  /* F_UDMI not allocated for porous jumps */
  if (THREAD_TYPE(t) == THREAD_F_JUMP)
    return;

  num_in_data  = F_UDMI(f,t,NUM_OF_HITS);
  
  /* Average diameter of particles that hit the particular wall face:*/
  F_UDMI(f,t,AVG_DIAMETER) = (TP_DIAM(tp)
			      + num_in_data * F_UDMI(f,t,AVG_DIAMETER))
                              / (num_in_data + 1);
  C_UDMI(c0,t0,AVG_DIAMETER) = F_UDMI(f,t,AVG_DIAMETER);
 
  /* Average velocity normal to wall of particles hitting the wall:*/
  F_UDMI(f,t,AVG_RADI_VELO) = (vel_ortho
			       + num_in_data * F_UDMI(f,t,AVG_RADI_VELO))
                               / (num_in_data + 1);
  C_UDMI(c0,t0,AVG_RADI_VELO) = F_UDMI(f,t,AVG_RADI_VELO);
  
  F_UDMI(f, t, NUM_OF_HITS) = num_in_data + 1;
  C_UDMI(c0,t0,NUM_OF_HITS) = num_in_data + 1;

  
}
 
DEFINE_ON_DEMAND(reset_UDM)
{
  /* assign domain pointer with global domain */
  domain = Get_Domain(1);
  reset_UDM_s();
} 

2.5.4.4. Hooking an Erosion/Accretion UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_EROSION is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Discrete Phase Model dialog box in Ansys Fluent. See Hooking DEFINE_DPM_EROSION UDFs for details on how to hook your DEFINE_DPM_EROSION UDF to Ansys Fluent.

2.5.5. DEFINE_DPM_HEAT_MASS

2.5.5.1. Description

You can use DEFINE_DPM_HEAT_MASS to specify the heat and mass transfer of multicomponent particles to the gas phase.

When a DEFINE_DPM_HEAT_MASS UDF is activated, then the number of species that can be referenced and interact with the particles via the UDF is limited to those with a species index less than the maximum UDF species number, defined using the TUI command define/models/dpm/options/maximum-udf-species. The default number for maximum-udf-species is 50.

2.5.5.2. Usage

DEFINE_DPM_HEAT_MASS (name, tp, C_p, hgas, hvap, cvap_surf, Z, dydt, dzdt)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

real C_p

Particle heat capacity.

real *hgas

Enthalpies of vaporizing gas phase species.

real *hvap

Vaporization enthalpies of vaporizing components.

real *cvap_surf

Vapor equilibrium concentrations of vaporizing components.

real Z

Compressibility,

real *dydt

Source terms of the particle temperature and component masses.

dpms_t *dzdt

Source terms of the gas phase enthalpy and species masses.

Function returns

void

There are eight arguments to DEFINE_DPM_HEAT_MASS: name, tp, C_p, hgas, hvap, cvap_surf, Z, dydt, and dzdt. You supply name, the name of the UDF. tp, C_p, hgas, hvap, cvap_surf, and Z are variables that are passed by the Ansys Fluent solver to your UDF. Your UDF will need to compute the particle and gas phase source terms and store the values in dydt and dzdt, respectively.

2.5.5.3. Example

The following is an example of a compiled UDF that uses DEFINE_DPM_HEAT_MASS. It implements the source terms for the following:

Source Term

Variable

Unit

particle temperature

dydt[0]

K/s

particle component mass

dydt[1..]

kg/s

gas phase enthalpy

dzdt->energy

J/s

gas phase species mass

dzdt->species[0..]

kg/s

/***********************************************************************
   UDF for defining the heat and mass transport for
   multicomponent particle vaporization
 ***********************************************************************/
 #include "udf.h"
 #include "dpm_mem.h"
 
 DEFINE_DPM_HEAT_MASS(multivap,tp,Cp,hgas,hvap,cvap_surf,Z,dydt,dzdt)
 {
   int ns;
   Material *sp;
   real dens_total = 0.0;     /* total vapor density*/
   real P_total = 0.0;      /* vapor pressure */
   int nc = TP_N_COMPONENTS(tp);   /* number of particle components */
   Thread *t0 = TP_CELL_THREAD(tp);   /* thread where the particle is in*/
   Material *gas_mix = THREAD_MATERIAL(DPM_THREAD(t0, tp)); /* gas mixture
   material */
   Material *cond_mix = TP_MATERIAL(tp); /* particle mixture material*/
   cphase_state_t *c = &(tp->cphase[0]); /* cell information of particle location*/
   real molwt[MAX_SPE_EQNS]; /* molecular weight of gas species */
   real Tp = TP_T(tp);   /* particle temperature */
   real mp = TP_MASS(tp);   /* particle mass */
   real molwt_bulk = 0.;  /* average molecular weight in bulk gas */
   real Dp = DPM_DIAM_FROM_VOL(mp / TP_RHO(tp)); /* particle diameter */
   real Ap = DPM_AREA(Dp);      /* particle surface */
   real Pr = c->sHeat * c->mu / c->tCond;   /* Prandtl number */
   real Nu = 2.0 + 0.6 * sqrt(tp->Re) * pow(Pr, 1./3.); /* Nusselt number */
   real h = Nu * c->tCond / Dp;     /* Heat transfer coefficient*/
   real dh_dt = h * (c->temp - Tp) * Ap;  /* heat source term*/
   dydt[0] += dh_dt / (mp * Cp);
   dzdt->energy -= dh_dt;
   mixture_species_loop(gas_mix,sp,ns)
   {
      molwt[ns] = MATERIAL_PROP(sp,PROP_mwi); /* molecular weight of gas
         species */
    molwt_bulk += c->yi[ns] / molwt[ns]; /* average molecular weight */
   }

 /* prevent division by zero */
 molwt_bulk = MAX(molwt_bulk,DPM_SMALL);

 for (ns = 0; ns < nc; ns++)
   {
      int gas_index = TP_COMPONENT_INDEX_I(tp,ns);  /* gas species index of
       vaporization */
  if(gas_index >= 0)
    {
       /* condensed material */
       Material * cond_c = MIXTURE_COMPONENT(cond_mix, ns);
       /* vaporization temperature */
       real vap_temp = MATERIAL_PROP(cond_c,PROP_vap_temp);
       /* diffusion coefficient */
       real D = DPM_BINARY_DIFFUSIVITY(tp,cond_c,TP_T(tp));
       /* Schmidt number */
       real Sc = c->mu / (c->rho * D);
       /* mass transfer coefficient */
       real k = (2. + 0.6 * sqrt(tp->Re) * pow(Sc, 1./3.)) * D / Dp;
       /* bulk gas concentration (ideal gas) */
       real cvap_bulk = c->pressure / UNIVERSAL_GAS_CONSTANT / c->temp
       * c->yi[gas_index] / molwt_bulk / solver_par.molWeight[gas_index];
       /* vaporization rate */
       real vap_rate = k * molwt[gas_index] * Ap
       * (cvap_surf[ns] - cvap_bulk);
       /* no vaporization below vaporization temperature, no condensation */
       if (Tp < vap_temp || vap_rate < 0.0)
         vap_rate = 0.;

       dydt[1+ns] -= vap_rate;   
       dzdt->species[gas_index] += vap_rate;
       /* dT/dt = dh/dt / (m Cp)*/
       dydt[0] -= hvap[gas_index] * vap_rate / (mp * Cp);
       /* gas enthalpy source term */
       dzdt->energy += hgas[gas_index] * vap_rate;

       P_total += cvap_surf[ns];
       dens_total += cvap_surf[ns] * molwt[gas_index];
      }
   }
   /* multicomponent boiling */
   P_total *= Z * UNIVERSAL_GAS_CONSTANT * Tp;
   if (P_total > c->pressure && dydt[0] > 0.)
     {
        real h_boil = dydt[0] * mp * Cp;
        /* keep particle temperature constant */
        dydt[0] = 0.;
        for (ns = 0; ns < nc; ns++)
          {
             int gas_index = TP_COMPONENT_INDEX_I(tp,ns);
             if (gas_index >= 0)
               {
                  real boil_rate = h_boil / hvap[gas_index] * cvap_surf[ns] *
                     molwt[gas_index] / dens_total;
                  /* particle component mass source term */
                  dydt[1+ns] -= boil_rate;
                  /* fluid species source */
                  dzdt->species[gas_index] += boil_rate;
                  /* fluid energy source */
                  dzdt->energy += hgas[gas_index] * boil_rate;
               }
          }
     }
 }

2.5.5.4. Hooking a DPM Particle Heat and Mass Transfer UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_HEAT_MASS is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument (for example, multivap) will become visible in the Set Injection Properties dialog box in Ansys Fluent. See Hooking DEFINE_DPM_HEAT_MASS UDFs for details on how to hook your DEFINE_DPM_HEAT_MASS UDF to Ansys Fluent.

2.5.6. DEFINE_DPM_INJECTION_INIT

2.5.6.1. Description

You can use DEFINE_DPM_INJECTION_INIT to initialize a particle’s injection properties such as location, diameter, and velocity.

2.5.6.2. Usage

DEFINE_DPM_INJECTION_INIT (name, I)

Argument Type

Description

symbol name

UDF name.

Injection *I

Pointer to the Injection structure which is a container for the particles being created. This function is called twice for each Injection before the first DPM iteration, and then called once for each Injection before the particles are injected into the domain at each subsequent DPM iteration.

Function returns

void

There are two arguments to DEFINE_DPM_INJECTION_INIT: name and I. You supply name, the name of the UDF. I is a variable that is passed by the Ansys Fluent solver to your UDF.


Note:  The DEFINE_DPM_INJECTION_INIT UDF should modify only particle variables such as diameter (PP_DIAM(p)), mass (PP_MASS(p)), and/or velocity (PP_VEL(p)) of the particle. Modification of any _INIT_ variable (such as PP_INIT_DIAM(p), PP_INIT_MASS(p), or PP_INIT_VEL(p)) will have no effect. These will be computed automatically by the Fluent solver after calling DEFINE_DPM_INJECTION_INIT. Also, Fluent will automatically compute the particle density PP_RHO(p) to be consistent with the corresponding material property definition. Note that if your DEFINE_DPM_INJECTION_INIT UDF modifies the particle density PP_RHO(p), you must ensure that it matches the density definition of the injection material in the Create/Edit Materials dialog box.


2.5.6.3. Example

The following UDF, named init_bubbles, initializes particles on a surface injection due to a surface reaction. This function must be executed as a compiled UDF. Note that if you are going to use this UDF in a transient simulation to compute transient particles, you will need to replace loop(p, I->p) with loop(p, I->p_init). Transient particle initialization cannot be performed with a loop over I->p.

/**********************************************************************
   UDF that initializes particles on a surface injection due
   to a surface reaction
 ***********************************************************************/
 
 #include "udf.h"
 #include "surf.h" /* RP_CELL and RP_THREAD are defined in surf.h */
 
 #define REACTING_SURFACE_ID 2
 #define MW_H2 2
 #define STOIC_H2 1
 
 /* ARRHENIUS CONSTANTS */
 #define PRE_EXP 1e+15
 #define ACTIVE 1e+08
 #define BETA 0.0
 
 real arrhenius_rate(real temp)
 {
   return PRE_EXP*pow(temp,BETA)*exp(-ACTIVE/(UNIVERSAL_GAS_CONSTANT*temp));
 }
 
 /* Species numbers. Must match order in Ansys Fluent dialog box */
 #define HF 0
 
 /* Reaction Exponents */
 #define HF_EXP 2.0
 
 
 /* Reaction Rate Routine used in UDF */
 
 real reaction_rate(cell_t c, Thread *cthread,real mw[],real yi[])
 
 /* Note that all arguments in the reaction_rate function call in your .c source file 
   MUST be on the same line or a compilation error will occur */ 
 {
   real concenHF = C_R(c,cthread)*yi[HF]/mw[HF];
   return arrhenius_rate(C_T(c,cthread))*pow(concenHF,HF_EXP);
 }
 
 real contact_area(cell_t c,Thread *t,int s_id,int *n);
 
 
 DEFINE_DPM_INJECTION_INIT(init_bubbles,I)
 {
   int count,i;
   real area, mw[MAX_SPE_EQNS], yi[MAX_SPE_EQNS];
   /* MAX_SPE_EQNS is an Ansys Fluent constant in materials.h */
   Particle *p;
   cell_t cell;
   Thread *cthread;
   Material *mix, *sp;
   Message("Initializing Injection: %s\n",I->name);
   loop(p,I->p) /* Standard Ansys Fluent Looping Macro to get particle
          streams in an Injection */
     {
        cell = PP_CELL(p);  /* Get the cell and thread that
                             * the particle is currently in */
        cthread = PP_CELL_THREAD(p);
        /* Set up molecular weight & mass fraction arrays */
        mix = THREAD_MATERIAL(cthread);
        mixture_species_loop(mix,sp,i)
            {
             mw[i] = MATERIAL_PROP(sp,PROP_mwi);
             yi[i] = C_YI(cell,cthread,i);
            }
       area = contact_area(cell, cthread, REACTING_SURFACE_ID,&count);
       /* Function that gets total area of REACTING_SURFACE faces in
         contact with cell */
       /* count is the number of contacting faces, and is needed
         to share the total bubble emission between the faces  */
        if (count > 0) /* if cell is in contact with REACTING_SURFACE */
          {
           PP_FLOW_RATE(p) = (area *MW_H2* STOIC_H2 *
              reaction_rate(cell, cthread, mw, yi))/
              (real)count; /* to get correct total flow
               rate when multiple faces contact the same cell */
           PP_DIAM(p) = 1e-3;
           PP_RHO(p) = 1.0;
           PP_MASS(p) = PP_RHO(p)*M_PI*pow(PP_DIAM(p),3.0)/6.0;
          }
        else
           PP_FLOW_RATE(p) = 0.0;
         }
     }
 
 real contact_area(cell_t c, Thread *t, int s_id, int *n)
 {
   int i = 0;
   real area = 0.0, A[ND_ND];
   *n = 0;
   c_face_loop(c,t,i)
     {
      if(THREAD_ID(C_FACE_THREAD(c,t,i)) == s_id)
          {
          (*n)++;
         F_AREA(A,C_FACE(c,t,i), C_FACE_THREAD(c,t,i));
         area += NV_MAG(A);
          }
     } return area;
 }

2.5.6.4. Using DEFINE_DPM_INJECTION_INIT with an unsteady injection file

When you are using a DPM particle sampling file or a VOF-to-DPM lump conversion transcript file as an unsteady injection file in a separate simulation, you can add more injection property data to such a file in extra columns. To do this, you can, for example, use a DEFINE_DPM_OUTPUT UDF during either sampling or VOF-to-DPM lump conversion (see Sampling of Trajectories in the Fluent User's Guide and DEFINE_DPM_OUTPUT for details). You can then use a DEFINE_DPM_INJECTION_INIT UDF to read that additional information from the file’s extra columns and initialize the particles that Ansys Fluent generates from the same unsteady injection file.

In the context of parallel processing, different particles generated from the unsteady injection file may be located in different partitions. Therefore, the DEFINE_DPM_INJECTION_INIT UDF must first obtain the contents of the file on all compute-node processes and then, for each new particle found in any partition, identify the corresponding line in the unsteady injection file. Only then can the additional information from the file be used, for example, to initialize user-defined particle properties.

The easiest way to obtain the contents of the file on every compute-node process is to read the file independently on every process, as shown in the example below. Note that Ansys Fluent itself reads the file only on compute-node 0 and then uses inter-process communication to automatically distribute the standard column data across all compute-node processes. You could implement a similar approach in your UDF for reading and sending information from the extra columns to other compute-node processes, but it involves extra programming that will not be further discussed here.

The following example can be used as a template for many purposes. The injudf UDF first reads the particle id (after the colon near the end of each line in the injection file) and then uses this id to initialize the first DPM particle user real in the new particle. For a typical purpose, there would be additional columns in the file, and you would use some extra fscanf(...) statement in this UDF to read them from the file.

#define USE_MY_OWN_FILE _NT

#if USE_MY_OWN_FILE
# define USE_FLUENT_IO_API  0
# define USE_UDF_PAR_IO  0
#endif

#include "udf.h"
#include "dpm_injections.h"

#define  REL_RESOL_IN_INJ_FILE 0.0001
/* If, for example, the file format is...:
 *  (( 4.9821e+00  -1.0061e+00  4.2000e+00  -1.6938e-05 ...
 * then there are five significant digits -->
 * the "relative resolution" is 0.0001 = 1.e-4
 */

/* Following definition divides by EPSILON to cancel
 * that from the definition of REAL_EQUAL_SCALED:
 */
#define MY_REAL_EQUAL(a, b)  \
   REAL_EQUAL_SCALED((a), (b), (REL_RESOL_IN_INJ_FILE / EPSILON))

DEFINE_DPM_INJECTION_INIT(injudf, I)
{
  Particle *p = NULL;
  char rest[1024] = "";
  int n_times_rewound = 0;
  FILE *my_inj_file_ptr = I->inj_fil_ptr;

  if ( ! I_DO_DPM)
    return;

  if (dpm_par.n_user_reals < 1)
  {
    DELAY_ERROR("node-%d, UDF '%s', Injection '%s', file '%s':\n"
                "Need at least one DPM user-real.",
                myid, __FUNCTION__, I->name, I->injection_file);
    return;
  }

#if USE_MY_OWN_FILE
  my_inj_file_ptr = fopen(I->injection_file, "r");
  /* Cannot use the open file pointer provided
   * by the Fluent executable.
   */
#endif

  if (NULLP(my_inj_file_ptr))
  {
    DELAY_ERROR("node-%d, UDF '%s', Injection '%s', file '%s':\n"
                "Cannot read from the injection file.",
                myid, __FUNCTION__, I->name, I->injection_file);
    return;
  }

#if USE_MY_OWN_FILE
  /* "read away" the file header: */
  fgets(rest, 1023, my_inj_file_ptr);
  fgets(rest, 1023, my_inj_file_ptr);
#elif 00
  rewind(I->inj_fil_ptr);
#endif

  int rewind_counter = 0;
  DPM_FPOS inj_file_start_pos = DPM_FTELL(my_inj_file_ptr);

  loop (p, I->p_init)
  {
    while (TRUE)
    {
      real file_x, file_y, file_z,
           file_u, file_v, file_w,
           file_d, file_T, file_M,
           file_m, file_n, file_t, file_f = 0.;

      int one = fgetc(my_inj_file_ptr);
      int two = fgetc(my_inj_file_ptr);

      if (feof(my_inj_file_ptr))
      {
        if (2 < rewind_counter)
          break;
        else
        {
          DPM_FSEEK(my_inj_file_ptr, inj_file_start_pos, SEEK_SET);
          ++rewind_counter;
          continue;   /* start over for this particle... */
        }
      }

      if ((one != '(') ||
          (two != '('))
      {
        DELAY_ERROR("node-%d, UDF '%s', Injection '%s', file '%s':\n"
                    "Cannot read from the file: '((' not found.",
                    myid, __FUNCTION__, I->name, I->injection_file);
        rewind_counter = 3;
        break;
      }

      int nmatch = fscanf(my_inj_file_ptr,
                      "%lg%lg%lg%lg%lg%lg%lg%lg%lg%lg%lg%lg%lg",
                      &file_x, &file_y, &file_z,
                      &file_u, &file_v, &file_w,
                      &file_d, &file_T, &file_M,
                      &file_m, &file_n, &file_t, &file_f);

      if (13 != nmatch)
      {
        DELAY_ERROR("node-%d, UDF '%s', Injection '%s', file '%s':\n"
                    "Cannot read at least 13 values from a line.",
                    myid, __FUNCTION__, I->name, I->injection_file);
        rewind_counter = 3;
        break;
      }

      /* Read additional numerical columns here... */

      /* Read the rest of the line into a string buffer: */
      fgets(rest, 1023, my_inj_file_ptr);

      /* if ( ! Check_Inj_File_Type(I, FALSE)) --- may change the file read pointer..! */
      /* The following block is relevant for *unsteady* injection files only...(!!)..: */
      {
        file_f -= I->start_flow_time_in_unst_file;

        if (file_f < 0.)   /* Before the start time, */
        {
          char dummyline[1024] = "";
          /* advance to end of line */
          do ;  /* nothing */
          while (( ! NULLP(fgets(dummyline, 1024, I->inj_fil_ptr))) &&
                 *dummyline &&   /* At least one character read? */
                 NULLP(strchr(dummyline, '\n'))); /* no LF read? */

          continue;   /* go on searching the file for the right entry */
        }

        if (0. < I->repeat_intrvl_from_unst_file)
        {
          if (file_f > I->repeat_intrvl_from_unst_file)
          {
            if (2 < ++n_times_rewound)
            {
              DELAY_ERROR("node-%d, UDF '%s', Injection '%s', file '%s':\n"
                          "Cannot find all expected entries in the file.",
                          myid, __FUNCTION__, I->name, I->injection_file);
              rewind_counter = 3;
              break;
            }

#if USE_MY_OWN_FILE
            rewind(my_inj_file_ptr);
            /* "read away" the file header: */
            fgets(rest, 1023, my_inj_file_ptr);
            fgets(rest, 1023, my_inj_file_ptr);
#else
            DPM_FSEEK(I->inj_fil_ptr, I->inj_fil_loc_bfr_rpt_intvl, SEEK_SET);
#endif
            file_f = 0.;
            continue;   /* go on searching the file for the right entry */
          }
        }

        file_f += I->unsteady_start;

        if (0. < I->repeat_intrvl_from_unst_file)   /* periodically repeating? */
        {
          /* file_f += I->repeat_intrvl_from_unst_file * (real) I->inj_fil_repeat_count; */
          while (file_f < dpm_par.time)
            file_f += I->repeat_intrvl_from_unst_file;
        }

        if (PP_INJ_FLOWTIME(p) > (dpm_par.time + dpm_par.time_step))
        {
          ASSERT(NULLP(p->next));   /* last particle -- the one that has been "read ahead" */
          break;  /* done for this particle from I->p_init, break out of loop through file */
        }
      }

      if (MY_REAL_EQUAL(PP_POS(p)[0],         file_x) &&
          MY_REAL_EQUAL(PP_POS(p)[1],         file_y) &&
          MY_REAL_EQUAL(PP_POS(p)[2],         file_z) &&
          MY_REAL_EQUAL(PP_VEL(p)[0],         file_u) &&
          MY_REAL_EQUAL(PP_VEL(p)[1],         file_v) &&
          MY_REAL_EQUAL(PP_VEL(p)[2],         file_w) &&
          MY_REAL_EQUAL(PP_DIAM(p),           file_d) &&
          MY_REAL_EQUAL(PP_T(p),              file_T) &&
          MY_REAL_EQUAL(PP_FLOW_RATE(p),      file_M) &&
       /* The following PP_...(p) values aren't yet known in
        * parallel and will be calculated from P_FLOW_RATE(p)
        * later:
        * MY_REAL_EQUAL(PP_MASS(p) * PP_N(p), file_M) &&
        * MY_REAL_EQUAL(PP_MASS(p),           file_m) &&
        * MY_REAL_EQUAL(PP_N(p),              file_n) &&
        */
          MY_REAL_EQUAL(PP_INJ_DURATIME(p),   file_t) &&
          MY_REAL_EQUAL(PP_INJ_FLOWTIME(p),   file_f))
      {
        /* From the rest of the line, determine some old part_id,
         * add 7000 and assign to the new particle's user real:
         */
        sscanf(strchr(rest, ':') + 1, "%lg", &(PP_USER_REAL(p, 0)));
        PP_USER_REAL(p, 0) += 7000.;
	/* Message("Value of scalar 0 \t %lg\n",PP_USER_REAL(p,0)); */
        break;  /* done for this particle from I->p_init, break out of loop through file */
      }
    }   /* while(TRUE) */

    /* End of file, but yet another particle to do? */
    if (2 < rewind_counter &&  ! NULLP(p))
    {
      DELAY_ERROR("node-%d, UDF '%s', Injection '%s', file '%s':\n"
                  "Could not process all new particles.",
                  myid, __FUNCTION__, I->name, I->injection_file);
      break;
    }
  }

#if USE_MY_OWN_FILE
  fclose(my_inj_file_ptr);
#endif
}

As mentioned above, in a parallel environment, the UDF must identify for every particle on each compute-node process which line from the injection file describes the particle. Therefore, the UDF reads all information from the file into every compute-node process and then compares the values from the standard columns against the properties of each particle in order to find the matching file entry for every particle. Once the correct line has been found, the UDF processes additional particle property information and assigns it to the particle.

The preprocessor macro _NT, which is used in the definition of the macro USE_MY_OWN_FILE, is defined to TRUE (1) by udf.h on Windows only and not on Linux. This is needed because on Windows, the UDF must read the file independently of the file handle already established by the Fluent executable. The reason for this is that, as long as you compile your UDF with the recommended compiler, it cannot use the file handle data structure provided by the Fluent executable.

2.5.6.5. Hooking a DPM Initialization UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_INJECTION_INIT is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Set Injection Properties dialog box in Ansys Fluent.

See Hooking DEFINE_DPM_INJECTION_INIT UDFs for details on how to hook your DEFINE_DPM_INJECTION_INIT UDF to Ansys Fluent.

2.5.7. DEFINE_DPM_LAW

2.5.7.1. Description

You can use DEFINE_DPM_LAW to customize laws for particles. For example your UDF can specify custom laws for heat and mass transfer rates for droplets and combusting particles. Additionally, you can specify custom laws for mass, diameter, and temperature properties as the droplet or particle exchanges mass and energy with its surroundings.

When a DEFINE_DPM_LAW UDF is activated, then the number of species that can be referenced and interact with the particles via the UDF is limited to those with a species index less than the maximum UDF species number, defined using the TUI command define/models/dpm/options/maximum-udf-species. The default number for maximum-udf-species is 50.

2.5.7.2. Usage

DEFINE_DPM_LAW (name, tp, ci)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

int ci

Variable that indicates whether the continuous and discrete phases are coupled (equal to if coupled with continuous phase, if not coupled).

Function returns

void

There are three arguments to DEFINE_DPM_LAW: name, tp, and ci. You supply name, the name of the UDF. tp and ci are variables that are passed by the Ansys Fluent solver to your UDF.


Important:  Pointer tp can be used as an argument to the macros defined in DPM Macros to obtain information about particle properties (for example, injection properties).


2.5.7.3. Example

The following UDF, named Evapor_Swelling_Law, models a custom law for the evaporation swelling of particles. The source code can be interpreted or compiled in Ansys Fluent. See Example DEFINE_DPM_LAW usage.

/**********************************************************************
   UDF that models a custom law for evaporation swelling of particles
 ***********************************************************************/
 
 #include "udf.h"
 
 DEFINE_DPM_LAW(Evapor_Swelling_Law,tp,ci)
 {
    real swelling_coeff = 1.1;

    /* first, call standard evaporation routine to calculate
    the mass and heat transfer       */
    VaporizationLaw(tp);

    /* compute new particle diameter and density */
    TP_DIAM(tp) = TP_INIT_DIAM(tp)*(1. + (swelling_coeff - 1.)*
   (TP_INIT_MASS(tp)-TP_MASS(tp))/(TP_VOLATILE_FRACTION(tp)*TP_INIT_MASS(tp)));
    TP_RHO(tp) = TP_MASS(tp) / (3.14159*TP_DIAM(tp)*TP_DIAM(tp)*TP_DIAM(tp)/6);
    TP_RHO(tp) = MAX(0.1, MIN(1e5, TP_RHO(tp)));
 } 

2.5.7.4. Hooking a Custom DPM Law to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_LAW is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Custom Laws dialog box in Ansys Fluent. See Hooking DEFINE_DPM_LAW UDFs for details on how to hook your DEFINE_DPM_LAW UDF to Ansys Fluent.

2.5.8. DEFINE_DPM_OUTPUT

2.5.8.1. Description

You can use DEFINE_DPM_OUTPUT to modify what is written to the sampling device output. This function allows access to the variables that are written to a file as a particle passes through a sampler (see Sampling of Trajectories in the Fluent User's Guide for details).

2.5.8.2. Usage

DEFINE_DPM_OUTPUT (name, header, fp, tp, t, plane)

Argument Type

Description

symbol name

UDF name.

int header

Variable that is equal to 1 at the first call of the function before particles are tracked and set to 0 for subsequent calls.

FILE *fp

Pointer to the file to which you are writing.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

Thread *t

Pointer to the thread that the particle is passing through if the sampler is represented by a mesh zone. If the sampler is not defined as a mesh zone, then the value of t is NULL.

Plane *plane

Pointer to the Plane structure (see dpm_types.h) if the sampling device is defined as a plane surface (line in 2D). If a mesh zone is used by the sampler, then plane is NULL.

Function returns

void

There are six arguments to DEFINE_DPM_OUTPUT: name, header, fp, tp, t, and plane. You supply name, the name of the UDF. header, fp, tp, t, and plane are variables that are passed by the Ansys Fluent solver to your UDF. The output of your UDF will be written to the file indicated by fp.


Important:  Pointer tp can be used as an argument to the macros defined in DPM Macros to obtain information about particle properties (for example, injection properties).


When using DEFINE_DPM_OUTPUT to write sample files, certain special file operations must be performed by Ansys Fluent. Therefore, the usual C output function fprintf cannot be used. Instead, you must use the functions par_fprintf and par_fprintf_head. For details on the use of these functions, refer to The par_fprintf_head and par_fprintf Functions and the following example. In particular, note that the first two arguments passed to par_fprintf must always be given as indicated, because they are used to sort the contents of the file but they will not appear in the final output.

2.5.8.3. Example 1 - Sampling and Removing Particles

The following UDF named discrete_phase_sample samples the size and velocity of discrete phase particles at selected planes downstream of an injection. For 2D axisymmetric simulations, it is assumed that droplets/particles are being sampled at planes (lines) corresponding to constant . For 3D simulations, the sampling planes correspond to constant .

To remove particles from the domain after they have been sampled, change the value of REMOVE_PARTICLES to TRUE. In this case, particles will be deleted following the time step in which they cross the plane. This is useful when you want to sample a spray immediately in front of an injector and you do not want to track the particles further downstream.


Important:  This UDF works with unsteady and steady simulations that include droplet break-up or collisions. Note that the discrete phase must be traced in an unsteady manner.


#include "udf.h"

/*******************************************************************/
/* UDF that samples discrete phase size and velocity distributions */
/* within the domain.                                              */
/*******************************************************************/

#define REMOVE_PARTICLES FALSE

DEFINE_DPM_OUTPUT(discrete_phase_sample,header,fp,tp,t,plane)
{
#if RP_2D
  real y;

  if(header)
  {
    par_fprintf_head(fp," #Time[s]  R [m]  X-velocity[m/s]");
    par_fprintf_head(fp," W-velocity[m/s] R-velocity[m/s] ");
    par_fprintf_head(fp,"Drop Diameter[m] Number of Drops  ");
    par_fprintf_head(fp,"Temperature [K] Initial Diam [m] ");
    par_fprintf_head(fp,"Injection Time [s] \n");
  }

  if(NULLP(tp))
    return;

  if (rp_axi && (sg_swirl || GVAR_TURB(rp, ke)))
    y = MAX(sqrt(SQR(TP_POS(tp)[1]) + SQR(TP_POS(tp)[2])),DPM_SMALL);
  else
    y = TP_POS(tp)[1];

  /* Note: The first two arguments to par_fprintf are used internally and */
  /* must not be changed, even though they do not appear in the final output.*/
  par_fprintf(fp, "%d %" int64_fmt " %e %f %f %f %f %e %e %f %e %f \n",
              P_INJ_ID(TP_INJECTION(tp)),
              TP_ID(tp),
              TP_TIME(tp),
              y,
              TP_VEL(tp)[0],
              TP_VEL(tp)[1],
              TP_VEL(tp)[2],
              TP_DIAM(tp),
              TP_N(tp),
              TP_T(tp),
              TP_INIT_DIAM(tp),
              TP_TIME_OF_BIRTH(tp));
#else
  real r, x, y;

  if(header)
  {
    par_fprintf_head(fp," #Time[s] R [m] x-velocity[m/s] ");
    par_fprintf_head(fp,"y-velocity[m/s] z-velocity[m/s]  ");
    par_fprintf_head(fp,"Drop Diameter[m]  Number of Drops ");
    par_fprintf_head(fp,"Temperature [K]  Initial Diam [m] ");
    par_fprintf_head(fp,"Injection Time [s] \n");
  }

  if(NULLP(tp))
    return;

  x = TP_POS(tp)[0];
  y = TP_POS(tp)[1];
  r = sqrt(SQR(x) + SQR(y));

  /* Note: The first two arguments to par_fprintf are used internally and */
  /* must not be changed, even though they do not appear in the final output.*/
  par_fprintf(fp,"%d %" int64_fmt " %e %f %f %f %f %e %e %f %e %f \n",
              P_INJ_ID(TP_INJECTION(tp)),
              TP_ID(tp),
              TP_TIME(tp),
              r,
              TP_VEL(tp)[0],
              TP_VEL(tp)[1],
              TP_VEL(tp)[2],
              TP_DIAM(tp),
              TP_N(tp),
              TP_T(tp),
              TP_INIT_DIAM(tp),
              TP_TIME_OF_BIRTH(tp));
#endif

#if REMOVE_PARTICLES
  MARK_TP(tp, P_FL_REMOVED);
#endif
}

2.5.8.4. Example 2 - Source Code Template

The following example provides the source code used in Ansys Fluent simulations when you do not use a DEFINE_DPM_OUTPUT UDF. You can modify this template to adapt it to your needs.

#include "udf.h"

/*****************************************************************/
/* DPM sampling output UDF that does what Fluent does by default */
/*****************************************************************/

#define REMOVE_PARTICLES FALSE

DEFINE_DPM_OUTPUT(my_dpm_out, header, fp, tp, thread, plane)
{
  if (header)
  {
    char *sort_name;
    char sort_fn[4096];

    if (NNULLP(thread))
      sort_name = THREAD_HEAD(thread)->dpm_summary.sort_file_name;
    else if ( ! NULLP(plane))
      sort_name = plane->sort_file_name;
    else   /* This is not expected to happen for regular particle sampling.. */
    {
      if (dpm_par.unsteady_tracking)
        sort_name = "parcels";
      else
        sort_name = "tracks";
    }

    /* sort_name may contain "/" (Linux)
     * or ":" and "\" (Windows) --
     * replace them all by "_":
     */
    strcpy(sort_fn, sort_name);
    replace_path_chars_in_string(sort_fn);
    if (dpm_par.unsteady_tracking)
      par_fprintf_head(fp, "(%s %d)\n", sort_fn, 13);
    else
      par_fprintf_head(fp, "(%s %d)\n", sort_fn, 12);

#if RP_2D
    if (rp_axi_swirl)
      par_fprintf_head(fp, "(x    r    theta    u    v    w");
    else
#endif
      par_fprintf_head(fp, "(x    y    z    u    v    w");

    if (dpm_par.unsteady_tracking)
      par_fprintf_head(fp, "  diameter    t    parcel-mass  "
                           "  mass    n-in-parcel   time   flow-time   name)\n");
    else
      par_fprintf_head(fp, "  diameter    t    mass-flow  "
                           "  mass    frequency    time    name)\n");
  }
  else if ( ! NULLP(tp))
  {
    /* Do some preparatory calculations for later use:
     */
    real flow_rate = 0.;
    real V_vel = TP_VEL(tp)[1];
    real W_vel = TP_VEL(tp)[2];
    real Y = TP_POS(tp)[1];
    real Z = TP_POS(tp)[2];
    real strength = 0.;
    real mass = 0.;

    if (TP_INJECTION(tp)->type != DPM_TYPE_MASSLESS)
    {
      mass = TP_MASS(tp);

      if (dpm_par.unsteady_tracking)
        strength = TP_N(tp);
      else
      {
        strength = TP_FLOW_RATE(tp) / TP_INIT_MASS(tp);
        if (TP_STOCHASTIC(tp))
          strength /= (real)TP_STOCHASTIC_NTRIES(tp);
      }

      flow_rate = strength * mass;
    }

#if RP_2D
    if (rp_axi_swirl)
    {
      Y = MAX(sqrt(TP_POS(tp)[1] * TP_POS(tp)[1] + TP_POS(tp)[2] * TP_POS(tp)[2]), DPM_SMALL);
      V_vel = (TP_VEL(tp)[1] * TP_POS(tp)[1] + TP_VEL(tp)[2] * TP_POS(tp)[2]) / Y;
      W_vel = (TP_VEL(tp)[2] * TP_POS(tp)[1] - TP_VEL(tp)[1] * TP_POS(tp)[2]) / Y;
      if (Y > 1.e-20) Z = LIMIT_ACOS(TP_POS(tp)[1] / Y);
    }
#endif

    if ( ! dpm_par.unsteady_tracking)
      par_fprintf(fp, /* Note: The first two arguments to par_fprintf are */
                      /* used internally and must not be changed, even */
                      /* though they do not appear in the final output.*/
                  "%d %" int64_fmt " ((%e  %e  %e  %e  %e  %e "
                  " %e  %e  %e  %e  %e  %e) %s:%" int64_fmt ")\n",
                  P_INJ_ID(TP_INJECTION(tp)), tp->part_id,
                  TP_POS(tp)[0],
                  Y,
                  Z,
                  TP_VEL(tp)[0],
                  V_vel,
                  W_vel,
                  TP_DIAM(tp),
                  TP_T(tp),
                  flow_rate,
                  mass,
                  strength,
                  TP_TIME(tp) - tp->time_of_birth,
                  TP_INJECTION(tp)->name,
                  tp->part_id);
    else
      par_fprintf(fp, /* Note: The first two arguments to par_fprintf are */
                      /* used internally and must not be changed, even */
                      /* though they do not appear in the final output.*/
                  "%d %" int64_fmt " ((%e  %e  %e  %e  %e  %e "
                  " %e  %e  %e  %e  %e  %e  %e) %s:%" int64_fmt ")\n",
                  (int) TP_TIME(tp),
                  (int64_t) ((TP_TIME(tp) -
                              (real) ((int) TP_TIME(tp))) *
                             (real) 1000000000000.),
                  TP_POS(tp)[0],
                  Y,
                  Z,
                  TP_VEL(tp)[0],
                  V_vel,
                  W_vel,
                  TP_DIAM(tp),
                  TP_T(tp),
                  flow_rate,
                  mass,
                  strength,
                  TP_TIME(tp) - tp->time_of_birth,
                  TP_TIME(tp),
                  TP_INJECTION(tp)->name,
                  tp->part_id);

#if REMOVE_PARTICLES
    MARK_TP(tp, P_FL_REMOVED);
#endif
  }
}

2.5.8.5. Using DEFINE_DPM_OUTPUT in VOF-to-DPM Simulations

For a VOF-to-DPM model transition simulation, if you use the option to write information about every liquid lump converted into DPM particle parcels to a file (as described in Setting up the VOF-to-DPM Model Transition in the Fluent User's Guide), you can use a DEFINE_DPM_OUTPUT function to define the file content. After you hook the DEFINE_DPM_OUTPUT function as described in Hooking a DPM Output UDF to Ansys Fluent, that function will be also called multiple times every time the VOF-to-DPM model transition mechanism is triggered.


Note:  Once you hook DEFINE_DPM_OUTPUT into Ansys Fluent, it will be used for both particle sampling and VOF-to-DPM lump conversion transcript.


  1. For the first call, the four last arguments fp, tp, t, and plane will be NULL. The UDF can be used to perform preparatory steps, such as looping over cells, findings lumps by the C_LUMP_ID(c, t) value, and collecting per-lump information that is not collected by default.

  2. Starting with the second call, fp will contain a valid value to give an access to the file. In the second call, the header argument will be set to 1 to indicate that the UDF is supposed to write the file header.

  3. The third call is similar to the first call, but with the correctly assigned fp argument. You can use it to finalize the collection of per-lump information. Note that only the first and third calls will be done synchronously on all compute-node processes so that global reductions can be used to correctly characterize liquid lumps that are spread across multiple partitions. To exclude exterior cells from the looping, use the begin_c_loop_int() statement.

  4. The fourth call will provide a pointer to a fully allocated and initialized Tracked_Particle data structure in the tp argument. This call happens once for every lump elected for conversion by the criteria specified and in parallel processing, it is done on node-0 only. You can use this call for multiple purposes:

    • Write information to the file.

      Note that, if the DEFINE_DPM_OUTPUT UDF has been hooked, no information will be written to the lump conversion transcript file, other than by the UDF.

    • Modify the initial conditions of the particle parcel.

      Make sure to keep particle properties such as TP_MASS(tp), TP_MASS0(tp), TP_INIT_MASS(tp), etc. consistent for all particle or parcel properties that you choose to modify.

    • Suppress the lump conversion by setting TP_MASS(tp) to a negative value.

    • Have the lump converted and eliminated from the VOF solution and then immediately discard the resulting DPM particle parcels.

      For that purpose, use the following statement:

      MARK_TP(tp, P_FL_REMOVED);

    At the time of these UDF calls, the solver has not determined yet whether the lump will be converted into single or multiple particle parcels. Any changes that the UDF applies to the Tracked_Particle data structure in the fourth call will apply automatically to the set of particle parcels into which the liquid lump will be split.

  5. For more control over the DPM particle parcels, you can use a fifth call to the DEFINE_DPM_OUTPUT UDF for every particle parcel in the aforementioned set of parcels. The fourth and fifth calls can be distinguished by the expression REAL_EQUAL(1.0, TP_N(tp)), which will be TRUE in the fourth call and FALSE in the fifth.

You can also use the DEFINE_DPM_OUTPUT function to control whether a liquid droplet is represented by a single particle parcel or split into multiple particle parcels as follows:

  1. In the VOF-to-DPM Transition Parameters dialog box, set Split any DPM Parcel that Exceeds the Cell Volume by Factor to a very large number.

  2. For each droplet that you want to split into multiple particle parcels, each representing a fraction of the droplet that is not larger than the volume of the hosting cell, use the following statements in your UDF:

    • In the fourth call:

      MARK_TP(tp, P_FL_REMOVED);

    • In the fifth call:

      UNMARK_TP(tp, P_FL_REMOVED);

2.5.8.5.1. Example

The following sample UDF is similar to the one shown in Example 2 - Source Code Template, but it contains a few extra clauses demonstrating some of the options outlined above. If you need more advice on using the DPM Output UDF for VOF-to-DPM model transition simulations, contact your technical support engineer for assistance.

#include "udf.h"
#include "vof_to_dpm.h"

/*****************************************************************/
/* DPM sampling output UDF that does what Fluent does by default */
/*****************************************************************/

DEFINE_DPM_OUTPUT(my_dpm_out, header, fp, tp, thread, plane)
{
  if (header)
  {
    char *sort_name;
    char sort_fn[4096];

    if (NNULLP(thread))
      sort_name = THREAD_HEAD(thread)->dpm_summary.sort_file_name;
    else if ( ! NULLP(plane))
      sort_name = plane->sort_file_name;
    else   /* This is not expected to happen for regular particle sampling.. */
    {
      if ( ! NULLP(convert_lump_args.myldps.injection))
       sort_name = convert_lump_args.myldps.injection->name;
      else
      if (dpm_par.unsteady_tracking)
        sort_name = "parcels";
      else
        sort_name = "tracks";
    }

    /* sort_name may contain "/" (Linux)
     * or ":" and "\" (Windows) --
     * replace them all by "_":
     */
    strcpy(sort_fn, sort_name);
    replace_path_chars_in_string(sort_fn);
    if (dpm_par.unsteady_tracking)
      par_fprintf_head(fp, "(%s %d)\n", sort_fn, 13);
    else
      par_fprintf_head(fp, "(%s %d)\n", sort_fn, 12);

#if RP_2D
    if (rp_axi_swirl)
      par_fprintf_head(fp, "(x    r    theta    u    v    w");
    else
#endif
      par_fprintf_head(fp, "(x    y    z    u    v    w");

    if (dpm_par.unsteady_tracking)
      par_fprintf_head(fp, "  diameter    t    parcel-mass  "
                           "  mass    n-in-parcel   time   flow-time  name)\n");
    else
      par_fprintf_head(fp, "  diameter    t    mass-flow  "
                           "  mass    frequency    time    name)\n");
  }
  else if ( ! NULLP(tp))
  {
    /* Do some preparatory calculations for later use:
     */
    real flow_rate = 0.;
    real V_vel = TP_VEL(tp)[1];
    real W_vel = TP_VEL(tp)[2];
    real Y = TP_POS(tp)[1];
    real Z = TP_POS(tp)[2];
    real strength = 0.;
    real mass = 0.;

    if (TP_INJECTION(tp)->type != DPM_TYPE_MASSLESS)
    {
      mass = TP_MASS(tp);

      if (dpm_par.unsteady_tracking)
        strength = TP_N(tp);
      else
      {
        strength = TP_FLOW_RATE(tp) / TP_INIT_MASS(tp);
        if (TP_STOCHASTIC(tp))
          strength /= (real)TP_STOCHASTIC_NTRIES(tp);
      }

      flow_rate = strength * mass;
    }

#if RP_2D
    if (rp_axi_swirl)
    {
      Y = MAX(sqrt(TP_POS(tp)[1] * TP_POS(tp)[1] + TP_POS(tp)[2] * TP_POS(tp)[2]), DPM_SMALL);
      V_vel = (TP_VEL(tp)[1] * TP_POS(tp)[1] + TP_VEL(tp)[2] * TP_POS(tp)[2]) / Y;
      W_vel = (TP_VEL(tp)[2] * TP_POS(tp)[1] - TP_VEL(tp)[1] * TP_POS(tp)[2]) / Y;
      if (Y > 1.e-20) Z = LIMIT_ACOS(TP_POS(tp)[1] / Y);
    }
#endif

    if ( ! NULLP(convert_lump_args.domain))
    {
      /* This is definitely VOF-to-DPM calling,
       * so demonstrate some things we can do:
       */
      if (TP_POS(tp)[0] > 0.07)   /* Silly condition for demonstration purposes. */
      {
        TP_N(tp) = -1.;           /* Tell the code not even to convert the lump. */
        return;                   /* Do not go on to write anything to the file. */
      }
      else
      if (TP_POS(tp)[0] > 0.04)   /* Silly condition for demonstration purposes. */
      {
        MARK_TP(tp, P_FL_REMOVED);    /* Drop the particle, never track it. */
        return;              /* Do not go on to write anything to the file. */
      }
      else
      {
        /* Just for demonstration that it works,
         * mirror the particle at the XY plane,
         * or set its temperature to 777:
         */
        /* TP_POS(tp)[2] *= -1.; */
        TP_T(tp) = 777.;
      }
    }

    if (NULLP(convert_lump_args.domain) ||  /* For future use: Not VOF-to-DPM, but...: */
        ( ! NULLP(thread)) ||               /* ...thread AND plane may still be NULL... */
        ( ! NULLP(plane))  ||  /* When VOF-to-DPM calls this, "thread" and "plane" are NULL */
#if RP_NODE
        I_AM_NODE_ZERO_P   ||  /* and only ONE compute-node process must write to the file: */
#endif
        I_AM_NODE_HOST_P   ||  /* (This line for shared-memory parallel DPM tracking only) */
        I_AM_NODE_SERIAL_P)    /* (This line for SERIAL ["-t0"] execution [unsupported]) */
      if ((1. == strength) ||  /* VOF-to-DPM: want to record one single entry per lump! */
          (NULLP(convert_lump_args.domain)))   /* Otherwise, write every particle.. */
      {
        if ( ! dpm_par.unsteady_tracking)
          par_fprintf(fp, /* The first two arguments to par_fprintf are used internally and */
                          /* must not be changed, although they do not appear in the output.*/
                      "%d %" int64_fmt " ((%.6e  %.6e  %.6e "
                      " %.6e  %.6e  %.6e  %.6e  %.6e  %.6e "
                      " %.6e  %.6e  %.6e) %s:%" int64_fmt ")\n",
                      P_INJ_ID(TP_INJECTION(tp)), tp->part_id,
                      TP_POS(tp)[0],
                      Y,
                      Z,
                      TP_VEL(tp)[0],
                      V_vel,
                      W_vel,
                      TP_DIAM(tp),
                      TP_T(tp),
                      flow_rate,
                      mass,
                      strength,
                      TP_TIME(tp) - tp->time_of_birth,
                      TP_INJECTION(tp)->name,
                      tp->part_id);
        else
          par_fprintf(fp, /* The first two arguments to par_fprintf are used internally and */
                          /* must not be changed, although they do not appear in the output.*/
                      "%d %" int64_fmt " ((%.6e  %.6e  %.6e "
                      " %.6e  %.6e  %.6e  %.6e  %.6e  %.6e "
                      " %.6e  %.6e  %.6e  %.6e) %s:%" int64_fmt ")\n",
                      (int) TP_TIME(tp),
                      (int64_t) ((TP_TIME(tp) -
                                  (real) ((int) TP_TIME(tp))) *
                                 (real) 1000000000000.),
                      TP_POS(tp)[0],
                      Y,
                      Z,
                      TP_VEL(tp)[0],
                      V_vel,
                      W_vel,
                      TP_DIAM(tp),
                      TP_T(tp),
                      flow_rate,
                      mass,
                      strength,
                      TP_TIME(tp) - tp->time_of_birth,
                      TP_TIME(tp),
                      TP_INJECTION(tp)->name,
                      tp->part_id);
      }
  }
  else
  {
    /* tp is NULL: VOF-to-DPM calls us TWICE synchronously on all compute-node proc.
     * so that we have an opportunity e.g. to do our own lump characterisation (i.e.
     * calculate some lump properties that have not been calculated by Fluent yet),
     * or can interfere with the lump properties Fluent has determined so far:
     */
    Message0("\nmy_dpm_out: last_lump_id:  %d  =="
  "==  file yet opened? -- %s  ====\n",
          convert_lump_args.last_lump_id, NULLP(fp) ? "NO" : "yes!");
  }
}

2.5.8.6. Hooking a DPM Output UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_OUTPUT is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Sample Trajectories dialog box in Ansys Fluent. See Hooking DEFINE_DPM_OUTPUT UDFs for details on how to hook your DEFINE_DPM_OUTPUT UDF to Ansys Fluent.

2.5.9. DEFINE_DPM_PROPERTY

2.5.9.1. Description

You can use DEFINE_DPM_PROPERTY to specify properties of discrete phase materials. You can model the following dispersed phase properties with this type of UDF:

  • particle emissivity

  • vapor pressure

  • vaporization temperature

  • thermophoretic coefficient

  • particle scattering factor

  • boiling point

  • particle viscosity

  • particle density

  • particle surface tension

  • binary diffusivity

  • swelling coefficient

  • latent heat

  • specific heat


Important:
  • When you are using the DEFINE_DPM_PROPERTY macro to specify the density property for a combusting particle material, all other model-specific density calculations, such as the swelling calculation during particle devolatilization, or the composition dependent char density will be ignored and the density calculated by the UDF will always be used. Similarly when you are using the DEFINE_DPM_PROPERTY macro to specify the specific heat property for a combusting particle material, the composition dependent char specific heat option will be ignored.

  • When you are using either the non-premixed or the partially-premixed combustion model in the continuous phase calculation together with DEFINE_DPM_PROPERTY for particle specific heat, the DEFINE_DPM_PROPERTY UDF will be used for the specific heat and enthalpy calculations of the non-volatile/non-reacting particle mass. 


2.5.9.2. Usage

DEFINE_DPM_PROPERTY (name, c, t, tp, T)

Argument Type

Description

symbol name

UDF name.

cell_t c

Index that identifies the cell where the particle is located in the given thread.

Thread *t

Pointer to the thread where the particle is located.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

real T Temperature. The appropriate temperature will be passed to your UDF by the solver. Depending on the DPM model you are using and the physical property type, it may be the temperature of the particle, the film, or the continuous phase at the current, previous, or initial location of the particle being tracked.

Function returns

real

There are five arguments to DEFINE_DPM_PROPERTY: name, c, t, tp, and T. You supply name, the name of the UDF. c, t, tp, and T are variables that are passed by the Ansys Fluent solver to your UDF. Your UDF will need to compute the real value of the discrete phase property and return it to the solver.

If you are using DEFINE_DPM_PROPERTY to specify the specific heat for particle materials, your UDF will also need to set the value of the particle enthalpy in the Tracked_Particle *tp, tp->enthalpy, to the particle sensible enthalpy, which should be calculated as the temperature integral of the specific heat function from the reference temperature, T_REF, to the temperature, T.


Important:
  • Pointer tp can be used as an argument to the macros defined in DPM Macros to obtain information about particle properties (for example, injection properties).

  • In some situations, when Ansys Fluent calls DEFINE_DPM_PROPERTY, tp may point to a dummy Tracked_Particle structure. If that is the case, your UDF code must not use any data from that structure, except TP_INJECTION(tp), which is always available. You can add the following condition into your UDF code to determine whether Tracked_Particle that tp points to is a dummy structure:

        if (NULLP(tp->pp) ||
            NULLP(TP_CELL_THREAD(tp)))
          {
            ... /* Do something withOUT using tp,
                 * except for accessing the Injection
                 * data structure through TP_INJECTION(tp).
                 */
            tp->enthalpy = ...;   /* needed for specific heat only */
            return ...;
          }
        else
          {
            ... /* Do the regular computation --
                 * can use tp in all possible ways.
                 */
            tp->enthalpy = ...;   /* needed for specific heat only */
            return ...;
          }

2.5.9.3. Example

In the following example, three discrete phase material property UDFs (named coal_emissivity , coal_scattering, and coal_cp, respectively) are concatenated into a single C source file. These UDFs must be executed as compiled UDFs in Ansys Fluent.

/*********************************************************************
 *    UDF that specifies discrete phase material properties
 *********************************************************************
 */

#include "udf.h"

DEFINE_DPM_PROPERTY(coal_emissivity, c, t, tp, T)
{
  real mp0;
  real mp;
  real vf, cf;

  if (NULLP(tp->pp) ||
      NULLP(TP_CELL_THREAD(tp)))
    return 1.0;                    /* initial value */

  mp0 = TP_INIT_MASS(tp);
  mp  = TP_MASS(tp);

  /* get the material char and volatile fractions and
   * store them in vf and cf:
   */
  vf = TP_VOLATILE_FRACTION(tp);
  cf = TP_CHAR_FRACTION(tp);

  if (!(((mp / mp0) >= 1) ||
        ((mp / mp0) <= 0)))
    {
      if ((mp / mp0) < (1 - vf - cf))
        {
          /* only ash left  */
          /* vf = cf = 0    */

          return .001;
        }
      else if ((mp / mp0) < (1 - vf))
        {
          /* only ash and char left               */
          /* cf = 1 - (1 - vf - cf) / (mp / mp0)  */
          /* vf = 0                               */

          return 1.0;
        }
      else
        {
          /* volatiles, char, and ash left   */
          /* cf =          cf  / (mp / mp0)  */
          /* vf = 1 - (1 - vf) / (mp / mp0)  */

          return 1.0;
        }
    }

  return 1.0;
}


DEFINE_DPM_PROPERTY(coal_scattering, c, t, tp, T)
{
  real mp0;
  real mp;
  real cf, vf;

  if (NULLP(tp->pp) ||
      NULLP(TP_CELL_THREAD(tp)))
    return 1.0;                    /* initial value */


  mp0 = TP_INIT_MASS(tp);
  mp  = TP_MASS(tp);

  /* get the original char and volatile fractions
  /* and store them in vf and cf:
   */
  vf = TP_VOLATILE_FRACTION(tp);
  cf = TP_CHAR_FRACTION(tp);

  if (!(((mp / mp0) >= 1) ||
        ((mp / mp0) <= 0)))
    {
      if ((mp / mp0) < (1 - vf - cf))
        {
          /* only ash left  */
          /* vf = cf = 0    */

          return 1.1;
        }
      else if ((mp / mp0) < (1 - vf))
        {
          /* only ash and char left               */
          /* cf = 1 - (1 - vf - cf) / (mp / mp0)  */
          /* vf = 0                               */

          return 0.9;
        }
      else
        {
          /* volatiles, char, and ash left   */
          /* cf =          cf  / (mp / mp0)  */
          /* vf = 1 - (1 - vf) / (mp / mp0)  */

          return 1.0;
        }
    }

  return 1.0;
}


DEFINE_DPM_PROPERTY(coal_cp, c, t, tp, T)
{
  real mp0;
  real mp;
  real cf;
  real vf;
  real af;
  real Cp;

  if (NULLP(tp->pp) ||
      NULLP(TP_CELL_THREAD(tp)))
    {
      Cp = 1600.;                       /* initial value */
    }
  else
    {
      mp0 = TP_INIT_MASS(tp);
      mp  = TP_MASS(tp);

      cf  = TP_CF(tp);                  /* char fraction */
      vf  = TP_VF(tp);                  /* volatiles fraction */
      af  = 1. - TP_VF(tp) - TP_CF(tp); /* ash fraction */

      Cp  = 2000. * af +
            1100. * vf +
            1300. * cf;
    }

  tp->enthalpy = Cp * (T - T_REF);

  return Cp;
}

2.5.9.4. Hooking a DPM Material Property UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_PROPERTY is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Create/Edit Materials dialog box in Ansys Fluent. See Hooking DEFINE_DPM_PROPERTY UDFs for details on how to hook your DEFINE_DPM_PROPERTY UDF to Ansys Fluent.

2.5.10. DEFINE_DPM_SCALAR_UPDATE

2.5.10.1. Description

You can use DEFINE_DPM_SCALAR_UPDATE to update scalar quantities every time a particle position is updated. The function allows particle-related variables to be updated or integrated over the life of the particle. Particle values can be stored in an array associated with the Tracked_Particle (accessed with the macro TP_USER_REAL(tp,i)). Values calculated and stored in the array can be used to color the particle trajectory.

During Ansys Fluent execution, the DEFINE_DPM_SCALAR_UPDATE function is called at the start of particle integration and then after each time step for the particle trajectory integration. A value of 1 for initialize will be passed to the UDF when the particle is first injected or each time the particle tracker is called for unsteady particle tracking.

2.5.10.2. Usage

DEFINE_DPM_SCALAR_UPDATE (name, c, t, initialize, tp)

Argument Type

Description

symbol name

UDF name.

cell_t c

Index that identifies the cell that the particle is currently in.

Thread *t

Pointer to the thread the particle is currently in.

int initialize

Variable that has a value of when the function is called at the start of the particle integration, and thereafter.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

Function returns

void

There are five arguments to DEFINE_DPM_SCALAR_UPDATE: name, c, t, initialize, and tp. You supply name, the name of the UDF. c, t, initialize, and tp are variables that are passed by the Ansys Fluent solver to your UDF.


Important:  Pointer tp can be used as an argument to the macros defined in DPM Macros to obtain information about particle properties (for example, injection properties). Also, the real array user is available for storage. The size of this array should be set in the Discrete Phase Model dialog box in the Number of Scalars field.


2.5.10.3. Example

The following compiled UDF computes the melting index along a particle trajectory. The DEFINE_DPM_SCALAR_UPDATE function is called at every particle time step in Ansys Fluent and requires a significant amount of CPU time to execute.

The melting index is computed from

(2–20)

Also included in this UDF is an initialization function DEFINE_INIT that is used to initialize the scalar variables. DPM_OUTPUT is used to write the melting index at sample planes and surfaces. The macro NULLP(p), which expands to ((p) == NULL), checks if its argument p is a null pointer.

/*********************************************************************
   UDF for computing the melting index along a particle trajectory
 **********************************************************************/
 #include "udf.h"
 
 DEFINE_INIT(melt_setup,domain)
 {
    /* if memory for the particle variable titles has not been
   * allocated yet, do it now */
    if (NULLP(user_particle_vars)) Init_User_Particle_Vars();
      /* now set the name and label */
      strcpy(user_particle_vars[0].name,"melting-index");
      strcpy(user_particle_vars[0].label,"Melting Index");
      strcpy(user_particle_vars[1].name,"melting-index-0");
      strcpy(user_particle_vars[1].label,"Melting Index 0");
 }
 
 /* update the user scalar variables */
 DEFINE_DPM_SCALAR_UPDATE(melting_index,cell,thread,initialize,tp)
 {
    cphase_state_t *c = &(tp->cphase[0]);
    if (initialize)
      {
     /* this is the initialization call, set:
     * TP_USER_REAL(tp,0) contains the melting index, initialize to 0
     * TP_USER_REAL(tp,1) contains the viscosity at the start of a time step*/
     TP_USER_REAL(tp,0) = 0.;
     TP_USER_REAL(tp,1) = c->mu;
      }
    else
      {
       /* use a trapezoidal rule to integrate the melting index */
       TP_USER_REAL(tp,0) += TP_DT(tp) * .5 * (1/TP_USER_REAL(tp,1) + 1/c->mu);
       /* save current fluid viscosity for start of next step */   
      TP_USER_REAL(tp,1) = c->mu;
      }
   }
 
 /* write melting index when sorting particles at surfaces */
 DEFINE_DPM_OUTPUT(melting_output,header,fp,tp,thread,plane)
 {
    char name[100];
    if (header)
    {
     if (NNULLP(thread))
       par_fprintf_head(fp,"(%s %d)\n",THREAD_HEAD(thread)->
         dpm_summary.sort_file_name,11);
     else
       par_fprintf_head(fp,"(%s %d)\n",plane->sort_file_name,11);
       par_fprintf_head(fp,"(%10s %10s %10s %10s %10s %10s %10s"
        " %10s %10s %10s %10s %s)\n",
        "X","Y","Z","U","V","W","diameter","T","mass-flow",
        "time","melt-index","name");
      }
    else
      {
       sprintf(name,"%s:%d",TP_INJECTION(tp)->name,TP_ID(tp));
       /* Note: The first two arguments to par_fprintf are used internally and     */
       /* must not be changed, even though they do not appear in the final output. */
       par_fprintf(fp,
          "%d %d ((%10.6g %10.6g %10.6g %10.6g %10.6g %10.6g "
          "%10.6g %10.6g %10.6g %10.6g %10.6g) %s)\n",
        P_INJ_ID(TP_INJECTION(tp)), TP_ID(tp),
        TP_POS(tp)[0], TP_POS(tp)[1], TP_POS(tp)[2],
        TP_VEL(tp)[0], TP_VEL(tp)[1], TP_VEL(tp)[2],
        TP_DIAM(tp), TP_T(tp), TP_FLOW_RATE(tp), TP_TIME(tp),
        TP_USER_REAL(tp,0), name);
      }
 } 

2.5.10.4. Hooking a DPM Scalar Update UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_SCALAR_UPDATE is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Discrete Phase Model dialog box in Ansys Fluent.

See Hooking DEFINE_DPM_SCALAR_UPDATE UDFs for details on how to hook your DEFINE_DPM_SCALAR_UPDATE UDF to Ansys Fluent.

2.5.11. DEFINE_DPM_SOURCE

2.5.11.1. Description

You can use DEFINE_DPM_SOURCE to specify particle source terms. The function allows access to the accumulated source terms for a particle in a given cell before they are added to the mass, momentum, and energy exchange terms for coupled DPM calculations.

When a DEFINE_DPM_SOURCE UDF is activated, then the number of species that can be referenced and interact with the particles via the UDF is limited to those with a species index less than the maximum UDF species number, defined using the TUI command define/models/dpm/options/maximum-udf-species. The default number for maximum-udf-species is 50.

2.5.11.2. Usage

DEFINE_DPM_SOURCE (name, c, t, S, strength, tp)

Argument Type

Description

symbol name

UDF name.

cell_t c

Index that identifies the cell that the particle is currently in.

Thread *t

Pointer to the thread the particle is currently in.

dpms_t *S

Pointer to the source structure dpms_t, which contains the source terms for the cell.

real strength

Particle number flow rate in particles/second (divided by the number of tries if stochastic tracking is used).

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

Function returns

void

There are six arguments to DEFINE_DPM_SOURCE: name, c, t, S, strength, and tp. You supply name, the name of the UDF. c, t, S, strength, and tp are variables that are passed by the Ansys Fluent solver to your UDF. The modified source terms, after they have been computed by the function, will be stored in S.


Important:  Pointer tp can be used as an argument to the macros defined in DPM Macros to obtain information about particle properties (for example, injection properties).


2.5.11.3. Example

For an example of DEFINE_DPM_SOURCE usage with the species model, see Example.

For using DEFINE_DPM_SOURCE with the non-premixed model, see the example below.

The DEFINE_DPM_LAW named nonpremixed_example and the DEFINE_DPM_SOURCE named dpm_source provide a simplistic model for droplet vaporization for education purposes only. The DEFINE_DPM_LAW UDF also demonstrates how to define the additional terms required to include source term linearization in the DPM UDFs.

#include "udf.h"
DEFINE_DPM_LAW(nonpremixed_example, tp, coupled)
{
  real vapor_conc_bulk, vapor_conc_surf;
  real mp_dot = 0., remaining_mass=0.;

  const cphase_state_t *c = &(tp->cphase[0]);
  Material *m = TP_MATERIAL(tp);
  real area_old = DPM_SURFACE_AREA(TP_DIAM(tp));
  real Tp_old   = TP_T(tp);
  real dt = TP_DT(tp);

  real diffusion_coeff = DPM_BINARY_DIFFUSIVITY(tp, m, TP_T(tp));
  real Pr = (c->sHeat)*(c->mu)/(c->tCond);
  real Sc = (c->mu)/((c->rho)*(diffusion_coeff));
  real Nu = 2. + 0.6*sqrt(tp->Re)*pow(Pr,1./3.);
  real Sh = 2. + 0.6*sqrt(tp->Re)*pow(Sc,1./3.);
  real mass_transfer_coeff = Sh * diffusion_coeff / TP_DIAM(tp);

  real h_lat = DPM_LATENT_HEAT(tp, m);
  real p_saturation = DPM_VAPOR_PRESSURE(tp, m, TP_T(tp));

  vapor_conc_bulk = c->pressure / UNIVERSAL_GAS_CONSTANT
    / c->temp * c->yi[TP_EVAP_SPECIES_INDEX(tp)] * c->mol_wt_bulk
    / solver_par.molWeight[TP_EVAP_SPECIES_INDEX(tp)];
 
  if (p_saturation > c->pressure)
    p_saturation = c->pressure;
  else if (p_saturation < 0.0)
    p_saturation = 0.;
 
  vapor_conc_surf = p_saturation / UNIVERSAL_GAS_CONSTANT / TP_T(tp);
 
  mp_dot = mass_transfer_coeff * solver_par.molWeight[TP_EVAP_SPECIES_INDEX(tp)]
    * area_old * (vapor_conc_surf - vapor_conc_bulk);
 
  if (mp_dot < 0.)
    mp_dot = 0.0;
 
/* additional terms required for using DPM source term linearization */

  if (dpm_par.linearized_source_terms)
    tp->htcA = Nu * c->tCond * TP_DIAM(tp)* M_PI;
 
  if (dpm_par.linearize_mixture_fraction_source_terms)
    tp->source.pdfc[0]  = c->rho * area_old * mass_transfer_coeff;
 
  UpdateTemperature(tp, -mp_dot * h_lat, TP_MASS(tp), 1.);
 
  remaining_mass = TP_MASS(tp) - mp_dot * dt;
 
  /* if the particle is very small, or the particle temperature
     exceeds the boiling temperature dump the remaining mass */
  if ((remaining_mass < TP_DPM_MINIMUM_MASS(tp))||( TP_DIAM(tp) <= DPM_LOWEST_DIAMETER)
      || (TP_T(tp)> DPM_BOILING_TEMPERATURE(tp, m)))
  {
    TP_DIAM(tp) = 0.0;
    TP_T(tp) = Tp_old;
    TP_MASS(tp) = 0.;
  }
  else
  {
    TP_MASS(tp) -= mp_dot * dt;
    TP_DIAM(tp) = DPM_DIAM_FROM_VOL(TP_MASS(tp) / TP_RHO(tp));
  }
}

DEFINE_DPM_SOURCE(dpm_source, c, t, S, strength, tp)
{
   /* delta_m is the mass source to the continuous phase
    * (Difference in mass between entry and exit from cell)
    * multiplied by strength (Number of particles/s in stream)
    */
  real delta_m = (TP_MASS0(tp) - TP_MASS(tp)) * strength;

  if (TP_CURRENT_LAW(tp) == DPM_LAW_USER_1)
  {
       /* Sources relevant to the user law 1:
        *
        * add the mixture fraction source and adjust the energy source by
        * adding the latent heat at reference temperature
        *
        */
    S->pdf_s[0] += delta_m;
    S->energy = -delta_m * TP_INJECTION(tp)->latent_heat_ref;
  }
}

2.5.11.4. Hooking a DPM Source Term UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_SOURCE is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Discrete Phase Model dialog box in Ansys Fluent. See Hooking DEFINE_DPM_SOURCE UDFs for details on how to hook your DEFINE_DPM_SOURCE UDF to Ansys Fluent.

2.5.12. DEFINE_DPM_SPRAY_COLLIDE

2.5.12.1. Description

You can use DEFINE_DPM_SPRAY_COLLIDE to side-step the default Ansys Fluent spray collision algorithm. When droplets collide they may bounce (in which case their velocity changes) or they may coalesce (in which case their velocity is changed, as well as their diameter and number in the DPM parcel). A spray collide UDF is called during droplet tracking after every droplet time step and requires that Droplet Collision is enabled in the Discrete Phase Model dialog box.

2.5.12.2. Usage

DEFINE_DPM_SPRAY_COLLIDE (name, tp, p)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

Particle *p

Pointer to the Particle data structure where particles p are stored in a linked list.

Function returns

void

There are three arguments to DEFINE_DPM_SPRAY_COLLIDE: name, tp, and p. You supply name, the name of the UDF. tp and p are variables that are passed by the Ansys Fluent solver to your UDF. When collision is enabled, this linked list is ordered by the cell that the particle is currently in. As particles from this linked list are tracked, they are copied from the particle list into a Tracked_Particle structure.

2.5.12.3. Example

The following UDF, named mean_spray_collide, is a simple (and non-physical) example that demonstrates the usage of DEFINE_SPRAY_COLLIDE. The droplet diameters are assumed to relax to their initial diameter over a specified time t_relax. The droplet velocity is also assumed to relax to the mean velocity of all droplets in the cell over the same time scale.

/***********************************************************
   DPM Spray Collide Example UDF
 ************************************************************/
 #include "udf.h"
 #include "dpm.h"
 #include "surf.h"
 DEFINE_DPM_SPRAY_COLLIDE(mean_spray_collide,tp,p)
 {
    /* non-physical collision UDF that relaxes the particle */
    /* velocity and diameter in a cell to the mean over the */
    /* specified time scale t_relax */
    const real t_relax = 0.001; /* seconds */

    /* get the cell and Thread that the particle is currently in */
    cell_t c = TP_CELL(tp);
    Thread *t = TP_CELL_THREAD(tp);
    /* Particle index for looping over all particles in the cell */
    Particle *pi;

    /* loop over all particles in the cell to find their mass */
    /* weighted mean velocity and diameter */
    int i;
    real u_mean[3]={0.}, mass_mean=0.;
    real d_orig = TP_DIAM(tp);
    real decay = 1. - exp(-t_relax);
    begin_particle_cell_loop(pi,c,t)
      {
         mass_mean += PP_MASS(pi);
         for(i=0;i<3;i++)
           u_mean[i] += PP_VEL(pi)[i]*PP_MASS(pi);
      }
    end_particle_cell_loop(pi,c,t)  /* relax particle velocity to the mean and diameter to the */
    /* initial diameter over the relaxation time scale t_relax */
    if(mass_mean > 0.)
      {
       for(i=0;i<3;i++)
         u_mean[i] /= mass_mean;
       for(i=0;i<3;i++)
         TP_VEL(tp)[i] += decay*(u_mean[i] - TP_VEL(tp)[i]);
         TP_DIAM(tp) += decay*(TP_INIT_DIAM(tp) - TP_DIAM(tp));
         /* adjust the number in the droplet parcel to conserve mass */
         TP_N(tp) *= CUB(d_orig/TP_DIAM(tp));
      }
 } 

2.5.12.4. Hooking a DPM Spray Collide UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_SPRAY_COLLIDE is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Discrete Phase Model dialog box in Ansys Fluent.

See Hooking DEFINE_DPM_SPRAY_COLLIDE UDFs for details on how to hook your DEFINE_DPM_SPRAY_COLLIDE UDF to Ansys Fluent.

2.5.13. DEFINE_DPM_SWITCH

2.5.13.1. Description

You can use DEFINE_DPM_SWITCH to modify the criteria for switching between laws. The function can be used to control the switching between the user-defined particle laws and the default particle laws, or between different user-defined or default particle laws.

2.5.13.2. Usage

DEFINE_DPM_SWITCH (name, tp, ci)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

int ci

Variable that indicates if the continuous and discrete phases are coupled (equal to if coupled with continuous phase, if not coupled).

Function returns

void

There are three arguments to DEFINE_DPM_SWITCH: name, tp, and ci. You supply name, the name of the UDF. tp and ci are variables that are passed by the Ansys Fluent solver to your UDF.


Important:  Pointer tp can be used as an argument to the macros defined in DPM Macros to obtain information about particle properties (for example, injection properties).


2.5.13.3. Example

The following is an example of a compiled UDF that uses DEFINE_DPM_SWITCH to switch between DPM laws using a criterion. The UDF switches to DPM_LAW_USER_1 which refers to condenshumidlaw since only one user law has been defined. The switching criterion is the local humidity which is computed in the domain using a DEFINE_ON_DEMAND function, which again calls the function myHumidity for every cell. In the case where the humidity is greater than , condensation is computed by applying a simple mass transfer calculation. Otherwise, Ansys Fluent’s standard law for Inert Heating is applied. The UDF requires one UDML and needs a species called h2o to compute the local humidity.

/**********************************************************************
   Concatenated UDFs for the Discrete Phase Model including
   an implementation of a condensation model
   an example for the use of DPM_SWITCH
 ***********************************************************************/
 
 #include "udf.h"
 #include "dpm.h"

 #define UDM_RH 0           /* no. of UDM holding relative humidity */
 #define N_REQ_UDM 1        /* 1 more than UDM_RH */
 #define CONDENS 1.0e-4     /* a condensation rate constant */
 
 int h2o_index=0;           /* index of water vapor species in mixture material */
 real mw_h2o=18.;           /* molecular weight of water */
 
 real H2O_Saturation_Pressure(real T)
 {
   real ratio, aTmTp;
   T = MAX(T, 273);
   T = MIN(T, 647.286);
   aTmTp = .01 * (T - 338.15);
   ratio = (647.286 / T - 1.) *
           (-7.419242 + aTmTp * (.29721 +
                        aTmTp * (-.1155286 +
                        aTmTp * (8.685635e-3 +
                        aTmTp * (1.094098e-3 +
                        aTmTp * (-4.39993e-3 +
                        aTmTp * (2.520658e-3 -
                        aTmTp * 5.218684e-4)))))));
  return (22.089e6 * exp(MIN(ratio, 35.)));
 }
 
 real myHumidity(cell_t c, Thread *t)
 {
   int i;
   Material *m = THREAD_MATERIAL(t), *sp;
   real yi_h2o = 0;      /* water mass fraction */
   real r_mix = 0.0;     /* sum of [mass fraction / mol. weight] over all species */
   real humidity;

   if ((MATERIAL_TYPE(m) == MATERIAL_MIXTURE) && (FLUID_THREAD_P(t)))
     {
       yi_h2o = C_YI(c, t, h2o_index);     /* water vapor mass fraction */
       mixture_species_loop(m, sp, i)
         {
           r_mix += C_YI(c,t,i) / MATERIAL_PROP(sp, PROP_mwi);
         }

       humidity = op_pres * yi_h2o / (mw_h2o * r_mix) /
                  H2O_Saturation_Pressure(C_T(c,t));
       return humidity;
     }
   else
     return 0.;
 }
 
 DEFINE_DPM_LAW(condenshumidlaw, tp, coupled)
 {
   real area;
   real mp_dot;

   /* Get Cell and Thread from Particle Structure */
   cell_t c = TP_CELL(tp);    
   Thread *t = TP_CELL_THREAD(tp); 

   area = M_PI * (TP_DIAM(tp) * TP_DIAM(tp));

   /* Note This law only used if Humidity > 1.0 so mp_dot always positive*/
   mp_dot = CONDENS * sqrt(area) * (myHumidity(c, t) - 1.0);
   if (mp_dot > 0.0)
     {
       TP_MASS(tp) += mp_dot * TP_DT(tp); 
       TP_DIAM(tp) = pow(6.0 * TP_MASS(tp) / (TP_RHO(tp) * M_PI), 1./3.);
     }
   /* Assume condensing particle is in thermal equilibrium with fluid in cell */
   TP_T(tp) = C_T(c,t);
 }
 
 DEFINE_DPM_SOURCE(dpm_source, c, t, S, strength, tp) 
 { 
   real mp_dot; 

   /* mp_dot is the mass source to the continuous phase
    * (Difference in mass between entry and exit from cell)
    * multiplied by strength (Number of particles/s in stream)
    */
   mp_dot = (TP_MASS0(tp) - TP_MASS(tp)) * strength;

   if (TP_CURRENT_LAW(tp) == DPM_LAW_USER_1)
     {
       /* Sources relevant to the user law 1:
        * add the source to the condensing species
        * equation and adjust the energy source by 
        * adding the latent heat at reference temperature
        */
       S->species[h2o_index] += mp_dot;
       S->energy -= mp_dot * TP_INJECTION(tp)->latent_heat_ref;
     }
 } 
  
 DEFINE_DPM_SWITCH(dpm_switch, tp, coupled)
 {
   cell_t c = TP_CELL(tp);
   Thread *t = TP_CELL_THREAD(tp);
   Material *m = TP_MATERIAL(tp); 

   /* If the relative humidity is higher than 1 
    * and the particle temperature below the boiling temperature 
    * switch to condensation law
    */
   if ((C_UDMI(c,t,UDM_RH) > 1.0) && (TP_T(tp) < DPM_BOILING_TEMPERATURE(tp, m)))
     TP_CURRENT_LAW(tp) = DPM_LAW_USER_1;
   else
     TP_CURRENT_LAW(tp) = DPM_LAW_INITIAL_INERT_HEATING;
 }

 DEFINE_ADJUST(adj_relhum, domain)
 {
   cell_t cell;
   Thread *thread;

   if(N_UDM < N_REQ_UDM)
      Message("\nNot enough user defined memory allocated. %d required.\n",
      N_REQ_UDM);
   else
     {
       real humidity, min, max;
       min = 1e10;
       max = 0.0;
       thread_loop_c(thread, domain)
         {
           /* Check if thread is a Fluid thread and has UDMs set up on it */
           if (FLUID_THREAD_P(thread) && NNULLP(THREAD_STORAGE(thread, SV_UDM_I)))
             {
               Material *m = THREAD_MATERIAL(thread), *sp;
               int i;
               /* Set the species index and molecular weight of water */ 
               if (MATERIAL_TYPE(m) == MATERIAL_MIXTURE)
                 mixture_species_loop (m,sp,i)
                   {
                     if (0 == strcmp(MIXTURE_SPECIE_NAME(m,i),"h2o") ||
                        (0 == strcmp(MIXTURE_SPECIE_NAME(m,i),"H2O")))
                       {
                         h2o_index = i;
                         mw_h2o = MATERIAL_PROP(sp,PROP_mwi);
                       }
                   }

               begin_c_loop(cell,thread)
                 {
                   humidity = myHumidity(cell, thread);
                   min = MIN(min, humidity);
                   max = MAX(max, humidity);
                   C_UDMI(cell, thread, UDM_RH) = humidity;
                 }
               end_c_loop(cell, thread)
             }
         }
       Message("\nRelative Humidity set in udm-%d", UDM_RH);
       Message(" range:(%f,%f)\n", min, max);
     }/* end if for enough UDSs and UDMs */
 }
 
 DEFINE_ON_DEMAND(set_relhum)
 {
   adj_relhum(Get_Domain(1));
 } 

2.5.13.4. Hooking a DPM Switching UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_SWITCH is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible in the Custom Laws dialog box in Ansys Fluent. See Hooking DEFINE_DPM_SWITCH UDFs for details on how to hook your DEFINE_DPM_SWITCH UDF to Ansys Fluent.

2.5.14. DEFINE_DPM_TIMESTEP

2.5.14.1. Description

You can use DEFINE_DPM_TIMESTEP to change the time step for DPM particle tracking based on user-specified inputs. The time step can be prescribed for special applications where a certain time step is needed. It can also be limited to values that are required to validate physical models.

2.5.14.2. Usage

DEFINE_DPM_TIMESTEP (name, tp, ts)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

real ts

Time step.

Function returns

real

There are three arguments to DEFINE_DPM_TIMESTEP: name, tp, and ts. You supply the name of your user-defined function. tp and ts are variables that are passed by the Ansys Fluent solver to your UDF. Your function will return the real value of the DPM particle timestep to the solver.

2.5.14.3. Example 1

The following compiled UDF named limit_to_e_minus_four sets the time step to a maximum value of 1e-4. If the time step computed by Ansys Fluent (and passed as an argument) is smaller than 1e-4, then Ansys Fluent’s time step is returned.

/* Time step control UDF for DPM */
 
 #include "udf.h"
 #include "dpm.h"
 
 DEFINE_DPM_TIMESTEP(limit_to_e_minus_four,tp,dt)
 {
   if (dt > 1.e-4)
    {
      /* TP_NEXT_TIME_STEP(tp) = 1.e-4; */
      return 1.e-4;
    }
 return dt;
 } 

2.5.14.4. Example 2

The following compiled UDF named limit_to_fifth_of_prt computes the particle relaxation time based on the formula:

(2–21)

where

(2–22)

The particle time step is limited to a fifth of the particle relaxation time. If the particle time step computed by Ansys Fluent (and passed as an argument) is smaller than this value, then Ansys Fluent’s time step is returned.


Important:  The high-resolution particle tracking option requires forces on the particle and the particle time step to be continuous across cell boundaries. Discontinuities in the forces or the time step can result in particles being lost or stuck at interior cell faces.


When executing the below UDF with high-resolution particle tracking, it is recommended that the continuous phase viscosity be interpolated to the particle position if it varies with position. This can be enabled with the following text command:

define/models/dpm/numerics/high-resolution-tracking/barycentric-interpolation/interpolate-flow-viscosity? yes

/* Particle time step control UDF for DPM */
 
 #include "udf.h"
 #include "dpm.h"
 
 DEFINE_DPM_TIMESTEP(limit_to_fifth_of_prt,tp,dt)
 {
   real drag_factor = 0.;
   real p_relax_time;
   cphase_state_t *c = &(tp->cphase[0]);
   /* compute particle relaxation time */
   if (TP_DIAM(tp) != 0.0)
      drag_factor = DragCoeff(tp) * c->mu / (TP_RHO(tp) * TP_DIAM(tp) * TP_DIAM(tp));
   else
      drag_factor = 1.;
   p_relax_time = 1./drag_factor;
   /* check the condition and return the time step */
   if (dt > p_relax_time/5.)
    {
      return p_relax_time/5.;
    }
   return dt;
 } 

2.5.14.5. Hooking a DPM Timestep UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_TIMESTEP is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible and selectable for DPM Timestep in the Discrete Phase Model dialog box in Ansys Fluent. See Hooking DEFINE_DPM_TIMESTEP UDFs for details on how to hook your DEFINE_DPM_TIMESTEP UDF to Ansys Fluent.

2.5.15. DEFINE_DPM_VP_EQUILIB

2.5.15.1. Description

You can use DEFINE_DPM_VP_EQUILIB to specify the equilibrium vapor pressure of vaporizing components of multicomponent particles.

2.5.15.2. Usage

DEFINE_DPM_VP_EQUILIB (name, tp, T, cvap_surf, Z)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

real T Temperature. The appropriate temperature will be passed to your UDF by the solver and may be equal to the particle, the film, or the multi-component droplet saturation temperature.

real *cvap_surf

Array that contains the equilibrium vapor concentration over the particle surface

real *Z

Pointer to the compressibility factor, Z

Function returns

void

There are five arguments to DEFINE_DPM_VP_EQUILIB: name, tp, T, cvap_surf, and Z. You supply the name of your user-defined function. tp and T are passed by the Ansys Fluent solver to your UDF. Your UDF will need to compute the equilibrium vapor concentration and the compressibility factor and store their values in cvap_surf and Z, respectively.

2.5.15.3. Example

The following UDF named raoult_vpe computes the equilibrium vapor concentration of a multicomponent particle using the Raoult law. The vapor pressure in the law is proportional to the molar fraction of the condenses material. DEFINE_VP_EQUILIB is called several times every particle time step in Ansys Fluent and requires a significant amount of CPU time to execute. For this reason, the UDF should be executed as a compiled UDF.

/***********************************************************************
   UDF for defining the vapor particle equilibrium
   for multicomponent particles
 ***********************************************************************/
 #include <udf.h>
 DEFINE_DPM_VP_EQUILIB(raoult_vpe,tp,Tp,cvap_surf,Z)
 {
    int is;
    real molwt[MAX_SPE_EQNS];
    Thread *t0 = TP_CELL_THREAD(tp);  /* cell thread of particle location */
    Material *gas_mix = THREAD_MATERIAL(t0); /* gas mixture material */
    Material *cond_mix = TP_MATERIAL(tp); /* particle mixture material */
    int nc = TP_N_COMPONENTS(tp);    /* number of particle components */
    real molwt_cond = 0.;  /* reciprocal molecular weight of the particle */
    for (is = 0; is < nc; is++)
      {
         int gas_index = TP_COMPONENT_INDEX_I(tp,is); /* index of vaporizing
            component in the gas phase */
         if (gas_index >= 0)
           {
            /* the molecular weight of particle material */
            molwt[gas_index] =
           MATERIAL_PROP(MIXTURE_COMPONENT(gas_mix,gas_index),PROP_mwi);
            molwt_cond += TP_COMPONENT_I(tp,is) / molwt[gas_index];
           }
        }
    /* prevent division by zero */
    molwt_cond = MAX(molwt_cond,DPM_SMALL);
    for (is = 0; is < nc; is++)  {
     /* gas species index of vaporization */
     int gas_index = TP_COMPONENT_INDEX_I(tp,is);
     if(gas_index >= 0)
       {
        /* condensed material */
        Material * cond_c = MIXTURE_COMPONENT(cond_mix, is);
        /* condensed component molefraction */
        real xi_cond = TP_COMPONENT_I(tp,is)/(molwt[gas_index]*molwt_cond);
        /* particle saturation pressure */
        real p_saturation = DPM_vapor_pressure(tp, cond_c, Tp);
        if (p_saturation < 0.0)
          p_saturation = 0.0;
        /* vapor pressure over the surface, this is the actual Raoult law */
        cvap_surf[is] = xi_cond * p_saturation / UNIVERSAL_GAS_CONSTANT / Tp;
       }
    }
  /* compressibility for ideal gas */
  *Z = 1.0;
 } 

2.5.15.4. Hooking a DPM Vapor Equilibrium UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_DPM_VP_EQUILIB is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name of the argument that you supplied as the first DEFINE macro argument will become visible and selectable in the Create/Edit Materials dialog box in Ansys Fluent. Note that before you hook the UDF, you’ll need to create particle injections in the Injections dialog box with the type Multicomponent chosen. See Hooking DEFINE_DPM_VP_EQUILIB UDFs for details on how to hook your DEFINE_DPM_VP_EQUILIB UDF to Ansys Fluent.

2.5.16. DEFINE_IMPINGEMENT

2.5.16.1. Description

You can use DEFINE_IMPINGEMENT to customize the impingement regime selection criteria.

2.5.16.2. Usage

DEFINE_IMPINGEMENT (name, tp, rel_dot_n, f, t, y_s, E_imp)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

real real_dot_n

The particle impingement velocity magnitude

face_t f

Index of the face that the particle is currently hitting

Thread *t

Pointer to the face thread the particle is currently hitting

real *y_s

Pointer to the fraction of particle mass that remains in the free stream after impact

real *E_imp

Pointer to the particle impingement parameter that is used as a regime selection criterion.

Function returns

int

There are seven arguments to DEFINE_IMPINGEMENT: name, tp, real_dot_n, f, t, y_s, and E_imp. You supply the name of your user-defined function. tp, real_dot_n, f, and t are passed by the Ansys Fluent solver to your UDF. Your function will:

  • Compute the fraction of the particle mass that remains in the free stream after impact and store it in y_s

  • Compute the impingement parameter and store it in E_imp

    The variable E_imp calculated in your UDF replaces the impingement energy calculated by Equation 12–208 and will be used also in Equation 12–216 for the peak diameter of the splashed droplets.

  • Return the impingement regime

The impingement regime that you return can either be one of the four predefined impingement regimes in Ansys Fluent, described in Interaction During Impact with a Boundary in the Fluent Theory Guide:

FILM_STICK
FILM_REBOUND
FILM_SPREAD
FILM_SPLASH

or one of the following user-defined regimes defined in a DEFINE_FILM_REGIME UDF:

FILM_USER_0
FILM_USER_1
FILM_USER_2
FILM_USER_3
FILM_USER_4
FILM_USER_5
FILM_USER_6
FILM_USER_7
FILM_USER_8
FILM_USER_9

2.5.16.3. Example

The following UDF, named dry_impingement, returns one of the pre-defined regimes, or the user-defined regime FILM_USER_0 for high wall temperature and high impact energy conditions. This function must be run as a compiled UDF.

#include "udf.h"
#define E_crit_0 16.
#define E_crit_1 3329.
#define E_crit_2 7500.
#define T_crit 1.5
DEFINE_IMPINGEMENT(dry_impingement, tp, rel_dot_n, f, t, y_s, E_imp)
{
  real one = 1.0, zero = 0.0;
  real fh = F_WALL_FILM_HEIGHT(f,t);
  real Re, denom;
  real abs_visc  = MAX(DPM_MU(tp),DPM_SMALL);
  real sigma = MAX(DPM_SURFTEN(tp),DPM_SMALL);
  real p_rho = MAX(TP_RHO(tp),DPM_SMALL);
  real d_0   = TP_DIAM(tp);
  real T_w = rf_energy ? F_T(f,t) : 300.0;
  real T_b = dpm_par.Tmax;
  int regime = FILM_STICK;
  
  *y_s = zero;
  Re = p_rho * d_0 * rel_dot_n / abs_visc;
  denom = sigma * (MIN(fh/d_0,1.0) + pow(Re,-0.5));
  *E_imp = SQR(rel_dot_n) * d_0 * p_rho / MAX(DPM_SMALL, denom);

  /* rf_energy is defined if Energy model is enabled. It is possible to use inert particle
  with isothermal conditions (Energy off). In that case the UDF returns FILM_STICK. */
  if (rf_energy && TP_LIQUID(tp))
    {
      Material *mb;
      if (TP_WET_COMBUSTION(tp))
        mb = TP_WET_COMB_MATERIAL(tp);
      else
        mb = TP_MATERIAL(tp);
      T_b = DPM_Boiling_Temperature(tp,mb);
    }
  else
    return FILM_STICK;
  
  if (T_w <= T_crit*T_b)
    {
      if (*E_imp <= E_crit_0)
        regime = FILM_STICK;
      else if (*E_imp > E_crit_0 && *E_imp <=  E_crit_1)
        regime = FILM_SPREAD;
      else if (*E_imp > E_crit_1)
        regime = FILM_SPLASH;
    }
  else
    {
      if (*E_imp < E_crit_1)
        regime = FILM_REBOUND;
      else
        regime = FILM_USER_0;
    }
  
  if ((regime == FILM_SPREAD) || (regime == FILM_STICK))
    *y_s = zero;
  else if ((regime == FILM_REBOUND)|| (regime == FILM_USER_0))
    *y_s = one;
  else if (regime == FILM_SPLASH)
    {
      if (*E_imp < E_crit_2)
        *y_s = MAX(0., 1.8e-4*(*E_imp - E_crit_1));
      else
        *y_s = 0.7;
    }
  return regime;
}

2.5.16.4. Hooking an Impingement UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_IMPINGEMENT is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name that you supplied as the first DEFINE macro argument will become visible and selectable in the Discrete Phase Model dialog box in Ansys Fluent. See Hooking DEFINE_IMPINGEMENT UDFs for details on how to hook your DEFINE_IMPINGEMENT UDF to Ansys Fluent.

2.5.17. DEFINE_FILM_REGIME

2.5.17.1. Description

You can use DEFINE_FILM_REGIME to set the particle variables for user-defined regimes of particle impingement. Fluent supports up to 10 user-defined regimes:

FILM_USER_0
FILM_USER_1
FILM_USER_2
FILM_USER_3
FILM_USER_4
FILM_USER_5
FILM_USER_6
FILM_USER_7
FILM_USER_8
FILM_USER_9

2.5.17.2. Usage

DEFINE_FILM_REGIME (name, regime, tp, pp, f, t, f_normal, update)

Argument Type

Description

symbol name

UDF name.

int regime

The particle impingement regime.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

Particle *pp

Pointer to the Particle data structure where particles pp are stored in a linked list.

face_t f

Index of the face that the particle is currently hitting

Thread *t

Pointer to the face thread the particle is currently hitting

real f_normal[]

Array that contains the unit vector which is normal to the face
int update Variable indicating whether the particle performs a sub-iteration, or a full tracking step. The value is 0 for sub-iteration and 1 for a full tracking step.

Function returns

void

There are eight arguments to DEFINE_FILM_REGIME: name, regime, tp, pp, f, t, f_normal, and update. You supply the name of your user-defined function. regime, tp, pp, f, t, f_normal, and update are passed by the Ansys Fluent solver to your UDF. Your function will need to compute the particle parameters and variables corresponding to the user-defined regime and modify the Tracked_Particle *tp and Particle *pp structures accordingly.


Note:  If you are defining multiple user-defined regimes, they must all be defined within a single DEFINE_FILM_REGIME UDF.


2.5.17.3. Example

The following UDF, named dry_breakup, describes a user-defined impingement regime where the impinging droplet particle bounces off the wall surface and breaks up into smaller droplets without any film formation. The ratio of the initial droplet diameter to the diameter after reflection is defined by the parameter D_RATIO. The UDF uses the Ansys Fluent function Reflect_Particle to compute the particle velocities after impingement. The reflected droplet’s new diameter and mass, as well as the number in parcel and flowrate are updated in the Tracked_Particle *tp structure. Note that the mass and diameter of the current and previous time steps, as well as the initial values must be updated. The initial mass and diameter must also be adjusted in Particle structure *pp at the end of the tracking step.

#include "udf.h"
#define D_RATIO 5
#define DIAM_FROM_VOL(V) ((real)pow((6.0 * (V) / M_PI), 1./3.))

DEFINE_FILM_REGIME(dry_breakup, regime, tp, pp, f, t, f_normal, update)
{
  real d1 = TP_DIAM(tp);
  real d2 = MAX(TP_DIAM(tp)/D_RATIO,dpm_par.lowest_diam);
  real drat_cubed;

  if (regime == FILM_USER_0)
    { 
      UNMARK_TP(tp, P_FL_ON_WALL);
      tp->tracking_scheme = tp->free_stream_tracking_scheme;
      TP_FILM_FACE(tp) = NULL_FACE;
      TP_FILM_THREAD(tp) = NULL;
      tp->gvtp.n_rebound ++ ;
      Reflect_Particle(tp,f_normal,ND_ND,f,t, NULL, NULL_FACE);
      drat_cubed = CUB(d2/d1);      
      TP_DIAM(tp) = d2;
      TP_N(tp) /= drat_cubed;
      TP_MASS(tp) *= drat_cubed;
      TP_FLOW_RATE(tp) /= drat_cubed;
      /* after diameter change all relevant variables in previous and initial states must be consistent */
      /* initialize the previous state mass and diameter to the current */
      TP_MASS0(tp) = TP_MASS(tp);
      TP_DIAM0(tp) = TP_DIAM(tp);
      /* initialize the initial state mass and diameter to the current */
      TP_INIT_MASS(tp) *= drat_cubed;
      TP_INIT_DIAM(tp) = DIAM_FROM_VOL(  TP_INIT_MASS(tp) /  TP_INIT_RHO(tp) );
      /* at the end of the iteration step update the initial mass
         and diameter in the stored particle list */
      if (update)
        {
          PP_INIT_MASS(pp) *= drat_cubed;
          PP_INIT_DIAM(pp) = DIAM_FROM_VOL(  PP_INIT_MASS(pp) /  PP_INIT_RHO(pp) );      
        }
    }
  else
    {
      Error("User defined impingement regime not implemented: %d.\n",regime);  
    }
}

2.5.17.4. Hooking a Film Regime UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_FILM_REGIME is interpreted (Interpreting UDFs) or compiled (Compiling UDFs), the name that you supplied as the first DEFINE macro argument will become visible and selectable in the Discrete Phase Model dialog box in Ansys Fluent. See Hooking DEFINE_FILM_REGIME UDFs for details on how to hook your DEFINE_FILM_REGIME UDF to Ansys Fluent.

2.5.18. DEFINE_SPLASHING_DISTRIBUTION

2.5.18.1. Description

You can use DEFINE_SPLASHING_DISTRIBUTION to customize the diameter, number in parcel, and velocity distribution for the splashed particles.

2.5.18.2. Usage

DEFINE_SPLASHING_DISTRIBUTION (name, tp, rel_dot_n, f_normal, n_splash, splashing_distribution_t *s)

Argument Type

Description

symbol name

UDF name.

Tracked_Particle *tp

Pointer to the Tracked_Particle data structure which contains data related to the particle being tracked.

real real_dot_n

The particle impingement velocity magnitude

real f_normal[]

Array that contains the unit vector which is normal to the face
int n_splash Number of splashed particle parcels.
splashing_distribution_t *s Array of pointers to structure s of size n_splash, containing the splashed particle variables distribution defined by the following macros:
SPLASH_DIAM(s,i) diameter of splashed particle i
SPLASH_N(s,i) number in parcel of splashed particle i
SPLASH_VEL(s,i)[3] velocity components of splashed particle i

Function returns

void

There are six arguments to DEFINE_SPLASHING_DISTRIBUTION: name, tp, real_dot_n, f_normal, n_splash, and s. You supply the name of your user-defined function. tp, real_dot_n, f_normal, and n_splash are passed by the Ansys Fluent solver to your UDF. Note that n_splash is the value entered for Number of Splashed Drops in the DPM tab of the boundary condition dialog box when Discreate Phase BC Function is set to wall-film. The output of your function is the array of pointers to the structure *s containing the distribution of the n_splash particle parcels.

2.5.18.3. Example

The following UDF applies the O’Rourke & Amsden splashing model [10]. This function must be run as a compiled UDF.

#include "udf.h"
#define NUKIYAMA_TANASAWA_TABLE_SIZE 15

static double nukiyama_tanasawa_table[NUKIYAMA_TANASAWA_TABLE_SIZE][2] =
  {
    {0.000000000000, 0.000000000000},
    {0.081108621634, 0.500000000000},
    {0.193908811137, 0.700000000000},
    {0.266112345561, 0.800000000000},
    {0.345136495805, 0.900000000000},
    {0.427593287415, 1.000000000000},
    {0.510077497807, 1.100000000000},
    {0.589500744183, 1.200000000000},
    {0.663337543595, 1.300000000000},
    {0.729766711799, 1.400000000000},
    {0.787709698251, 1.500000000000},
    {0.877181682854, 1.700000000000},
    {0.934793421924, 1.900000000000},
    {0.953988295926, 2.000000000000},
    {1.000000000000, 4.000000000000},
  };

double get_interpolated_value(double table[][2], int table_size, double inval)
{
  /* table is table[tableSize][1] with table[i][0] being the lookup values
     and table[i][1] the return values */

  int lowerindex, upperindex, midindex, jump;
  double lowerval, upperval, midval, ratio;

  lowerindex = 0;
  upperindex = table_size-1;

  lowerval = table[lowerindex][0];
  upperval = table[upperindex][0];

  jump = (upperindex-lowerindex)/2;

  /* Find values for Interpolation */
        
  while (jump)
    {
      midindex = lowerindex + jump;
      midval = table[midindex][0];

      if (inval > midval)
	{
	  lowerval = midval;
	  lowerindex = midindex;
	}
      else
	{
	  upperval = midval;
	  upperindex = midindex;
	}
      jump /= 2;
    }

  ratio = (inval-lowerval)/(upperval - lowerval);
  lowerval = table[lowerindex][1];
  upperval = table[upperindex][1];
        
  return lowerval + ratio*(upperval - lowerval);
}

double nukiyama_tanasawa_random()
{
  return get_interpolated_value(nukiyama_tanasawa_table, NUKIYAMA_TANASAWA_TABLE_SIZE, uniform_random());
}

double nabor_reitz_random(double beta)
{
  double exp_beta;
  double rand;
  double angle_range;

  angle_range = M_PI;
  rand = 1.0 - 2.0*uniform_random(); /* -1.0 < rand <= 1.0 */

  if (beta == 0.0)
    return angle_range * rand;
  
  if (rand < 0.0)
    {
      angle_range = -M_PI;
      rand = -rand;    /* 0.0 <= rand <= 1.0 */
    }
  exp_beta = exp(beta);
  
  return angle_range*(beta - log(exp_beta + rand*(1.0 - exp_beta)))/beta;
}

/*  O'Rourke & Amsden splashing model BC definition */

#define E_2_CRIT 3329.29 /* O'Rourke & Amsden critical energy squared (57.7^2) */

DEFINE_SPLASHING_DISTRIBUTION(splash, tp, rel_dot_n, norm, n_splashes, s)
{
  int i;
  real p_diam, p_v_mag; /* Particle properties */
  real w0, v0, v_t;     /* Particle condition  */
  real E_2;
  real sin_alpha;
  
  real e_t[ND_ND], e_p[ND_ND];
  /* Splashed particle properties */
  real weber;
  real d_max, w_dash, v_dash;                 
  real beta, psi, component;
  real total_volume = 0.;
      
  p_diam = TP_DIAM(tp);

  /* norm into wall and TP_VEL should be so w0 >= 0.0 */
  w0 = NV_DOT(TP_VEL(tp),norm);
  
  /* Particle impingement energy */
  E_2 = TP_IMPINGEMENT_PARAMETER(tp);
  
  /* Calculate new splash stream droplet diameters */ 
  weber = TP_WEBER_IMP(tp);
  
  d_max = MAX(MAX((E_2_CRIT/E_2),(6.4/weber)),0.06)*p_diam;
  
  for (i=0;i<n_splashes;i++)
    {
      SPLASH_DIAM(s,i) = d_max * nukiyama_tanasawa_random();
    }
  
  /* Calculate droplet velocities */
  NV_CROSS(e_p,TP_VEL(tp),norm);
  p_v_mag = NV_MAG(TP_VEL(tp));
  
  NV_S(e_p,/=,p_v_mag);
  sin_alpha = NV_MAG(e_p);

  /* If particle hits exactly normal to surface tilt slightly */ 
  sin_alpha = MAX(1.E-6, sin_alpha);
  NV_S(e_p,/=,sin_alpha);
  
  beta = M_PI*sqrt(sin_alpha/MAX(DPM_SMALL,(1.0-sin_alpha)));
  
  NV_CROSS(e_t,norm,e_p);
  
  v0 = NV_DOT(TP_VEL(tp),e_t); /* Tangential to wall */
  v_t = 0.8*v0;
  
  for (i=0;i<n_splashes;i++)
    {
      w_dash = -0.2 * w0 * nukiyama_tanasawa_random();
      NV_VS(SPLASH_VEL(s,i),=,norm,*,w_dash);
      
      v_dash = 0.1 * w0 * fabs(gauss_random()); /* O'Rourke and Amsden: delta = 0.1 w0 */
      v_dash += 0.12*w0;
      psi = nabor_reitz_random(beta);
      component = v_dash * cos(psi);
      NV_VS(SPLASH_VEL(s,i),+=,e_t,*,component);
      component = v_dash * sin(psi);
      NV_VS(SPLASH_VEL(s,i),+=,e_p,*,component);
      
      NV_VS(SPLASH_VEL(s,i),+=,e_t,*,v_t);
    }
  
  /*  The flow rate for each diameter will be proportional to its mass (=volume as density equal) */
  for (i=0;i<n_splashes;i++)
    {
      total_volume += CUB( SPLASH_DIAM(s,i));
    }
  for (i=0;i<n_splashes;i++)
    {
      SPLASH_N(s,i) = TP_SPLASHED_FRACTION(tp)*CUB(SPLASH_DIAM(s,i))/total_volume*TP_N(tp);
    }
} 

2.5.18.4. Hooking a Splashing Distribution UDF to Ansys Fluent

After the UDF that you have defined using DEFINE_SPLASHING_DISTRIBUTION is compiled (Compiling UDFs) the name that you supplied as the first DEFINE macro argument will become visible and selectable in the Discrete Phase Model dialog box in Ansys Fluent. See Hooking DEFINE_SPLASHING_DISTRIBUTION UDFs for details on how to hook your DEFINE_SPLASHING_DISTRIBUTION UDF to Ansys Fluent.