Complete Course On UDF by Ansys Fluent1
Complete Course On UDF by Ansys Fluent1
Introduction to Fluent
from the Inside
14. 0 Release
Advanced Fluent
User-Defined Functions
1 © 2012 ANSYS, Inc. March 14, 2012 Release 14.0
Introduction to UDF Programming Customer Training Material
– Initialization
– Custom boundary conditions (both space or time dependent)
– Material properties
– Source terms into any of the flow equations
– Chemical reaction rates
– Post processing and reporting of modified data
– Adding extra flow equations
– Mesh motion
– Discrete phase model modification
– And many more...
There are many places within the solution cycle where a UDF
can be added:
Segregated PBCS DBCS
User-
Initialize Begin Loop defined Solver? Solve U-Momentum Source terms
ADJUST
Solve Mass,
Source terms Solve V-Momentum Momentum,
Solve Mass Source terms
User Defined Energy,
& Momentum
INITIALIZE Species
Solve W-Momentum
Repeat
Solve Mass Continuity;
Update Velocity
• A cell has information about its bounding faces and a face has access to
its neighbouring cells.
Introduction to the C
Programming Language
14. 0 Release
Advanced Fluent
User-Defined Functions
8 © 2012 ANSYS, Inc. March 14, 2012 Release 14.0
C Programming (1) Customer Training Material
• A Pointer is a special kind of variable that contains an address in memory, not content, of
another variable
BUT:
An array name cannot be set to a new value.
It is fixed to the value set when it is defined as an array.
– Operators
= (assignment)
+, -, *, /, % (modulus)
<, >, >=, <= , ==, !=
++ increment; i++ is “post‐increment”: use the current value of i in the
expression, then increment i by 1 (i=i+1)
+= addition assignment:
agg += single; /* means: agg = agg + single; */
*= multiplication assignment,
-= subtraction assignment,
/= division assignment
12 © 2012 ANSYS, Inc. March 14, 2012 Release 14.0
C Programming (5) Customer Training Material
Conditional Operator (? : )
if ( …)
( condition ? operand a : operand b )
<statement>; example:
else if ( … ) real At = (rp_axi ? At*2*M_PI : At );
<statement>;
– Structures
• Structures are collections of data that are of different types.
struct car_struct
{
char model_name[25];
int number_of_wheels;
int engine_cc;
real top_speed;
}; /* Need a ‘;’ as variables can be defined here */
Introduction to UDF
Implementation Using C
14. 0 Release
Advanced Fluent
User-Defined Functions
17 © 2012 ANSYS, Inc. March 14, 2012 Release 14.0
Why UDFs are written in the C Customer Training Material
Language
Fluent is written in C because it is a very powerful language. It provides access to high
level operations such as graphics and networking and low level capabilities such as
mathematical functions and memory operations.
It enables the linking of extra functionality using “shared objects” in Unix and Dynamic
Linked Libraries (DLLs) in windows systems.
This is a very convenient mechanism for linking in UDFs which allows a seamless
connection between the main program and the user functions
Users familiar with other “procedural” languages such as FORTRAN will be familiar with
most of the ideas and syntax used in C
• There are several Fluent‐specific data types that are associated with mesh
components.
• Some of the more commonly used ones are:
− Node – stores data associated with a grid point
− face_t – identifies a face within a face thread
− cell_t – identifies a cell within a cell thread
− Thread – stores data that is common to a collection of cells or faces
− Domain – stores data associated with a collection of face and cell threads in a mesh
• Each individual cell can be accessed by using a cell index of type cell_t and the
cell thread (the zone which contains the cell)
• Similarly, each individual face (boundary or internal) can be accessed by using
the face index of type face_t and the face thread (the zone which contains the
face)
• Some mesh or solver data types have a capital letter. These are actually data
structures in the C language and are usually passed between functions as
pointers to these structures.
• Examples of how these data types are defined are shown below:
• Every Zone is associated to a single ID available in the BC panel or the TUI using:
/grid/modify-zones/list-zones
• The cells either side of a face (f,ft) can be accessed using the macros:
ct0 = THREAD_T0(ft); C0
ct1 = THREAD_T1(ft);
c0 = F_C0(f,ft);
C1
c1 = F_C1(f,ft);
• Boundary zones such as inlets will connect to a cell zone on one side with
THREAD_T0(ft) but THREAD_T1(ft) will return NULL , a special pointer
which signifies there is no zone on the other side.
C0
real A[3];
f = F_AREA(A,f,ft); C1
• C_R(c,t) Density
• C_P(c,t) Pressure
• C_U(c,t)
• C_V(c,t) X, Y & Z Velocity Components
• C_W(c,t)
• C_T(c,t) Temperature
• C_H(c,t) Enthalpy
• C_K(c,t) Turbulent Kinetic Energy
• C_D(c,t) Turbulent Energy Dissipation
• C_YI(c,t,i) i‐th Species Mass Fraction
• C_UDSI(c,t,i) i‐th User Defined Scalar
• C_UDMI(c,t,i) i‐th User Defined Memory
t is a cell‐thread pointer, c is a cell index, and i is an integer for indexing which
species, UDS and UDM require.
27 © 2012 ANSYS, Inc. March 14, 2012 Release 14.0
Macros used for Control Customer Training Material
• Data_Valid_P()
Equals 1 if data is available 0 if not.
if (!Data_Valid_P()) return;
• FLUID_THREAD_P(t0)
Checks to see if thread t0 is a fluid thread
• NULLP(ct)
NULLP checks if a pointer is NULL
• NNULLP(ct)
NNULLP checks if a pointer is NOT NULL
if(NNULLP(THREAD_T1(ft)))
{ ... Do things on neighbouring cell thread.}
• All UDFs that will be used in Fluent must be defined using a specific DEFINE_
macro
• Some examples are:
Profiles : DEFINE_PROFILE
Source terms : DEFINE_SOURCE
Properties : DEFINE_PROPERTY
User-defined Scalars : DEFINE_UNSTEADY
DEFINE_FLUX
DEFINE_DIFFUSIVITY
Initialization : DEFINE_INIT
Global Functions : DEFINE_ADJUST
DEFINE_ON_DEMAND
DEFINE_RW_FILE
flux discretisation
variable reconstruction and clipping
• The UDF macros are defined in the ‘udf.h’ file, for instance:
#define DEFINE_PROFILE(name, t, i) void name(Thread *t, int i)
#define DEFINE_PROPERTY(name,c,t) real name(cell_t c, Thread *t)
#define DEFINE_SOURCE(name, c, t, dS, i) \
real name(cell_t c, Thread *t, real dS[], int i)
#define DEFINE_INIT(name, domain) void name(Domain *domain)
#define DEFINE_ADJUST(name, domain) void name(Domain *domain)
…
• There are many more header files stored in the same directory which
can be browsed by users but most are already included automatically
within udf.h
31 © 2012 ANSYS, Inc. March 14, 2012 Release 14.0
Customer Training Material
Chapter 2
Advanced Fluent
User-Defined Functions
1 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
How to use UDFs Customer Training Material
• Compiled
– The UDF files are compiled and linked to the main code
– Full C language and all standard libraries
– Access to full FLUENT function set
– Needs a compiler for every OS to be used on
• On Windows to recompile you will need to unload a previously compiled UDF library
using:
– Define → User Defined → Func on → Manage
– Select the library and then click Unload
Appendix
14. 0 Release
Advanced Fluent
User-Defined Functions
6 © 2012 ANSYS, Inc. March 14, 2012 Release 14.0
Directory Tree for a Compiled UDF Library Customer Training Material
Machine dependent
Unix Tree irix6r10 Windows Tree
ultra
libudf hp700 libudf
2d 3d 2d 3d
Composing UDFs
14. 0 Release
Advanced Fluent
User-Defined Functions
1 © 2012 ANSYS, Inc. March 14, 2012 Release 14.0
Using the DEFINE Macros
All UDFs are defined using a macro that starts with “DEFINE_”
The general format of a DEFINE_ macro is:
Example:
DEFINE_PROFILE(inlet_x_velocity, thread, index)
• Defines boundary profile name “inlet_x_velocity”
• thread is the boundary thread which will be changed
• index identifies which boundary condition value is to be set
Note that the BCs are applied at the faces of boundary, not the adjacent cells.
• Specify the x‐velocity component on each face using F_PROFILE(f, ft, var) = …
#include "udf.h"
Average
Velocity
Magnitude
Flow Time
Adiabatic Wall
Cold Inflow
Heated Wall
Energy source term UDFs may also be defined for solid zones
The source term macro is different from the previous macro because it is called for
each cell separately.
So, there is no need to perform a cell loop!
The units of all source terms are expressed in terms of the volumetric generation
rate. (total cell source / m3)
A DEFINE_SOURCE UDF returns the value for the total source, S, but we can also
specify B if required using the dS and eqn variables passed to the UDF:
dS[eqn] = dS_dPhi;
Setting this term is usually not required and can be set to 0.0, but, if the source terms
are large and causing convergence problems, then if dS_dPhi is negative,
setting this term will help stabilise the solution.
#include "udf.h"
#define C2 100.0
#define HEIGHT 0.01
Porous Plug
Inlet
Outlet
DEFINE_INIT(init_sphere_temp, domain)
The macro NV_D can be used to set {
a vector cell_t c; /* Cell index has its own type too */
Thread *ct; /* Threads are pointers to a structure */
The macro NV_VV can be used for real pos[ND_ND];
real centre[ND_ND];
vector arithmetic
NV_D(centre,=,0.0,0.5,0.5); /* Set a vector */
The macro NV_MAG computes the
length of a vector /* Loop through all the cell threads in the domain */
thread_loop_c(ct,domain)
{/* Loop through the cells in each cell thread */
(0.0, 0.5, 0.5) begin_c_loop(c,ct)
{
C_CENTROID(pos,c,ct);
NV_VV(pos,=,pos,-,centre); /* Vector arithmetic */
#include "udf.h"
DEFINE_ADJUST(total_dissipation, domain)
{
cell_t c;
Thread *ct;
real total_diss;
thread_loop_c(ct,domain)
{
begin_c_loop(c,ct)
{
total_diss += C_D(c,ct) * C_VOLUME(c,ct); /* Use += to sum up values */
}
end_c_loop(c,ct)
}
#include "udf.h"
DEFINE_EXECUTE_AT_END(final_total_dissipation)
{
/* The domain is NOT passed as a parameter to EXECUTE_AT_END UDF. */
/* So a local domain variable must be defined and set internally. */
Domain *domain;
total_dissipation(domain);
}
DEFINE_EXECUTE_AT_END(increment_run_count)
{
run_count++; /* Increment the global counter here */
}
DEFINE_RW_FILE(write_count, file_pointer)
{
Message("Writing run counter to data file.\n");
DEFINE_RW_FILE(read_count, file_pointer)
{
Message("Reading run counter from data file.\n");
The property UDF is called on a cell‐by‐cell basis so, like source term UDFs
does not require a loop.
DEFINE_PROPERTY(freezing_viscosity, c, ct)
{
real temp, mu_laminar;
The viscosity is high when
temp = C_T(c,ct);
the flow is below T_COLD if (temp > T_HOT)
mu_laminar = MU_HOT;
return mu_laminar;
}
#include "udf.h"
DEFINE_DELTAT(start_up_time_steps, domain)
{
real flow_time;
real time_step;
flow_time = CURRENT_TIME;
else
time_step = MAIN_TIMESTEP;
return time_step;
}
14. 0 Release
Advanced Fluent
User‐Defined Functions
1 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
User Defined Memory
Sometimes it is useful to define extra field variables to store and retrieve
values defined by the user.
User Defined Memories (UDMs) are defined in:
Define → User Defined → User Defined Memory
• C_UDMI(c,ct,i) = my_cell_value;
• F_UDMI(f,ft,i) = my_face_value; (Only on boundary faces)
2 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
User Defined Scalars
In addition to the Continuity, Momentum Equations and Energy
Equations, up to 50 user defined generic scalar transport
equations can be solve.
Each scalar follows the generic transport equation:
#define C_UDS_U(C,T)C_UDMI((C),(T),0)
#define C_UDS_V(C,T)C_UDMI((C),(T),1)
#define C_UDS_W(C,T)C_UDMI((C),(T),2)
t0 = F_C0_THREAD(f,ft);
c0 = F_C0(f,ft);
NV_D(av_vel,=,C_UDS_U(c0,t0),C_UDS_V(c0,t0),C_UDS_W(c0,t0));
t1 = F_C1_THREAD(f,ft);
if(NNULLP(t1))
{
/* Use velocity on the other side of face to get an average */
c1 = F_C1(f,ft);
F_AREA(A,f,ft);
flux = NV_DOT(av_vec, A); /* Dot velocity with face area to get flux */
return flux; /* Fluent does any specified upwinding with this flux value */
}
Note that the value of the flux is not multiplied by a UDS value as
Fluent does this according to the sign of flux and the Spatial
Discretization scheme selected in Solve → Methods...
9 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
Example
The following UDF modifies User-Defined Scalar time derivatives
using DEFINE_UDS_UNSTEADY
The unsteady UDF actually sets a volumetric source term (= −V.dφ/dt ) with
an implicit part and an explicit part:
− V.dφ/dt ≈ (–V/∆t) .φnew + V.φold/∆t
#include "udf.h"
/* The second order time derivative can be set using C_UDSI_M2 & PREVIOUS_TIMESTEP */
}
DEFINE_ADJUST(set_uds0_gradient_magnitude, domain)
{
Thread *ct;
cell_t c;
int index;
thread_loop_c(ct,domain)
{
begin_c_loop(c,ct)
{
/* The gradient vector of many calculated values can be got */
/* by adding _G to the standard variable name: C_T_G(c,ct) */
C_UDMI(c,ct,index) = NV_MAG(C_UDSI_G(c,ct,index));
}
end_c_loop(c,ct)
}
}
14. 0 Release
Advanced Fluent
User‐Defined Functions
1 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
DPM Macros (1)
The most important datatype for DPM model is Tracked_Particle
Tracked_Particle *p;
DPM tracks particles by performaing “Lagrangian” tracking
Particle data at current position
• P_DIAM(p) Particle diameter
• P_VEL(p)[I] Particle Velocity
• P_T(p) Particle Temperature
• P_RHO(p) Particle density
• P_MASS(p) Particle mass
• P_TIME(p) Current time for particle
• P_DT(p) Particle time step
• P_LF(p) Particle liquid fraction
• P_VFF(p) Particle volatile fraction
DEFINE_DPM_LAW(Evapor_Swelling_Law, p, ci)
{
real swelling_coeff = 1.1;
/* First, call standard evaporation routine
to calculate mass and heat transfer */
Vaporization_Law(p);
14. 0 Release
Advanced Fluent
User‐Defined Functions
1 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
The Multi‐Domain Architecture
• In multiphase flows each phase has its set of material properties and may have
its own set of variables
– For VOF model: k , Yki
– For Mixture model: k , U k , Yki
– For Eulerian model: k , U k , Tk , k k , k , Yki
Superdomain
Superthreads
• The advantage of this is that the same value for cell c corresponds to the
same cell on different duplicated threads, so for a multiphase problem
with 3 phases there will be three phase threads: t_primary, t_secondary1,
and t_secondary2.
• The data is not repeated, but instead each thread points to the same
data.
– The most common example is the geometry thread.
• There is a new set of macros to get this information about the hierarchy.
These are discussed next.
Domain *d_p0;
d_p0 = DOMAIN_SUB_DOMAIN(superdomain, p_index);
where p_index is 0 for primary phase; 1 for the first secondary phase, 2 for the
second etc.
Thread *t_phase;
t_phase = THREAD_SUB_THREAD(superthread, p_index);
• To get a sub thread for a secondary phase we can use a similar macro and specify
the phase index, p_index.
Domain *d;
int d_id;
d_id = DOMAIN_ID(d);
• To see the correspondence between the phases and Domain IDs, look at
the Phases panel.
P_index
0
1
2 The corresponding
Domain ID is shown here
phase_domain = Get_Domain(WATER_ID);
thread_loop_c(phase_ct, phase_domain)
{
mt = THREAD_SUPER_THREAD(phase_t);
all_phase_threads = THREAD_SUB_THREADS(mt);
Scalar
Mass Momentum Energy
Model / (Turbulence
(Volume (Velocity (Temperature,
equation fraction) variables,
components) enthalpy)
scalars)
Eulerian Phase Phase Phase Phase/Mixture
Mixture Phase Mixture Mixture Mixture
VOF Phase Mixture Mixture Mixture
mass_transfer = 0.0;
if(T_gas < T_SAT) /* Gas Condenses, mass transfer positive from gas to liquid */
mass_transfer += BOIL_CONST*C_VOF(c,gas)*C_R(c,gas)*(T_SAT - T_gas)/T_SAT;
return(mass_transfer);
}
25 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
Phase Interaction – Define Heat Transfer Rate
• The heat transfer rates between phases as well as drag and lift coefficient
functions can be set : DEFINE_EXCHANGE_PROPERTY
• Eulerian model – net heat transfer rate, drag and lift coefficients
• Mixture model – drag coefficient only.
DEFINE_EXCHANGE_PROPERTY(heat_exchange, c, mix_ct, i, j)
{
Thread *ti, *tj;
#define PR_NUMBER(CP,MU,K)((CP)*(MU)/(K))
#define IP_HEAT_COEFF(VOF,K,NU,D)(6.0*(VOF)*(K)*(NU)/((D)*(D)))
return IP_HEAT_COEFF(C_VOF(c,tj),k,Nu,d);
}
27 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
Chapter 7
14. 0 Release
Advanced Fluent
User‐Defined Functions
1 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
Parallel Fluent Architecture
• Host process is labeled
999999
• Cortex is the front end
process which connects
only to the host process
• Compute Node processes
are labeled consecutively
starting at 0
• Each compute node is
virtually connected to
every other compute
node by the Message
Passing (MP) interface
“(%iterate 5)”
“(%iterate 5)”
Compute-Node-0 Compute-Node-2
• To parallelize a UDF we need to modify a serial code such that it will run
successfully on both serial and parallel modes
#if RP_HOST
/* Coding here only performed on HOST process */
#endif
#if RP_NODE
/* Coding here only performed on NODE processes */
#endif
#if PARALLEL
/* Coding here only performed on HOST & NODE processes */
#endif
#if !PARALLEL
/* Coding here only performed on SERIAL process */
#endif
#if !RP_HOST
/* Coding here only performed on NODE & SERIAL processes */
#endif
#if !RP_NODE
/* Coding here only performed on HOST & SERIAL processes */
#endif
Interior Faces
Exterior Cell
Interior Cells
begin_c_loop(c,t)
{ }
end_c_loop(c,t)
• In parallel, this serial form loops through both interior and exterior cells
• Use begin_c_loop_int(c,t) in UDFs that are to be parallelized:
begin_c_loop_int(c,t)
{
}
end_c_loop_int(c,t)
• This loop excludes the exterior cells so they don’t get visited twice.
• Another loop construct loops through the exterior cells only :
begin_c_loop_ext(c,t)
{
}
end_c_loop_ext(c,t)
but it is rarely used in UDFs and does nothing if compiled in the serial version
9 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
Partitioned Thread Loop (2)
• Similar loops exist for faces:
begin_f_loop_all(f,t)
{…}
end_f_loop_all(f,t)
begin_f_loop_int(f,t)
{…}
end_f_loop_int(f,t)
• But these have a slightly different use so instead, use the standard loop and check to see if
the face is allocated to this node by using PRINCIPAL_FACE_P(f,t):
begin_f_loop (f,t)
{
if(PRINCIPAL_FACE_P(f,t))
{…Only gets done once per face…}
}
end_f_loop(f,t)
Serial Version
#include “udf.h“
DEFINE_INIT(init_temp, domain)
{
real init_temp; /* Variable declaration */
Thread *ct;
cell_t c;
DEFINE_INIT(init_temp, domain)
{
real init_temp; /* Variable declaration. Some variables used in all versions. */
#if !RP_HOST
Thread *ct; /* These variable only used on the nodes and in serial */
cell_t c;
#endif
#if !RP_NODE
init_temp = RP_Get_Real(“my-init-temp“); /* Scheme communication on host and serial */
#endif
#if PARALLEL
host_to_node_real_1(init_temp); /* Host sends data to nodes (Does nothing in serial) */
#endif
#if !RP_HOST
thread_loop_c(ct,domain) /* Solution data handling only on nodes and serial */
begin_c_loop(c,ct)
{
C_T(c,ct) = init_temp;
}
end_c_loop(c,ct)
#endif
}
12 © 2011 ANSYS, Inc. March 14, 2012 Release 14.0
Inter-Process Communication (3)
• The macro host_to_node_real_1(init_temp)is defined as
a Send command in the Host version and a Receive command in
the Compute Nodes. It does nothing in the Serial version so the
#if PARALLEL directive is not really needed in this case.
• The reverse node to host data transfer macro for 1 real number is
node_to_host_real_1(report_temp)
Note however that this command only sends the value of
report_temp from Node‐0 to the Host.
(It does nothing in the serial version)
So, some way of gathering data onto Node‐0 before it is sent is
required.
• There are different macros depending on what data type you’re sending: