UDF Moving Wall Boundary Conditions
ExaGOOP supports spatially- and temporally-varying velocity boundary conditions at any domain face through a user-defined function (UDF) supplied as a runtime-loaded shared library. This mechanism allows arbitrary wall motions such as linearly translating walls, rotating wall etc. to be specified without recompiling the solver.
Overview
A UDF boundary condition is attached to a single named momentum BC face
(e.g. mpm.bc_zhi_mom) by providing two additional sub-namespace
parameters in the input file: the path to the shared library and the name
of the function to call. At every time step, ExaGOOP calls the UDF on
the CPU for every node on that face, stores the resulting velocity field
on a device-resident array, and uses that array when enforcing nodal and
particle boundary conditions on the GPU.
UDF interface
The shared library must export a single C-linkage function with the following signature:
void func_name(double x, double y, double z, double t, double vel[3]);
where:
x,y,z— physical coordinates of the boundary node (m)t— current simulation time (s)vel[3]— output velocity vector{vx, vy, vz}to be written by the function
The function must be declared extern "C" (or compiled as plain C) so
that the symbol name is not mangled. A minimal example for a
solid-body rotation about the \(z\)-axis:
#define OMEGA 0.5
void wall_vel_twist(double x, double y, double z,
double t, double vel[3])
{
vel[0] = -OMEGA * y;
vel[1] = OMEGA * x;
vel[2] = 0.0;
}
The function is called once per boundary node per time step on a single CPU thread; it must be thread-safe and must not call any AMReX or GPU routines.
Linking the solver against libdl
The solver must be linked against the dynamic loader library. Add the
following line to the test-specific GNUmakefile before the
include Make.rules line:
LDFLAGS += -ldl
Input file parameters
UDF wall velocity is specified per face. The face namespace follows the
same pattern as the existing momentum BC keys (mpm.bc_xlo_mom,
mpm.bc_xhi_mom, etc.). Two sub-keys are added under the face
namespace:
Parameter |
Type |
Description |
|---|---|---|
|
|
Path to the shared library (relative to the run directory or absolute). If omitted, no UDF is loaded for this face. |
|
|
Name of the exported C function in the library. Must match exactly (case-sensitive); no default. |
Both keys must be present together — specifying only one is an error.
The UDF is only activated for the face where both keys appear; all other
faces continue to use their static mpm.wall_vel_lo /
mpm.wall_vel_hi values.
Example — no-slip rotating top wall in the \(z\)-direction:
mpm.bc_zhi_mom = noslip
mpm.bc_zhi_mom.udf_lib = "./UDF/libwall_twist.dylib"
mpm.bc_zhi_mom.udf_func = "wall_vel_twist"
The face BC type (noslip, slip, partialslip) continues to
control the treatment of the normal and tangential velocity components;
the UDF replaces the constant wall velocity that would otherwise be read
from mpm.wall_vel_hi.
Implementation details
The CPU–GPU split is necessary because UDF function pointers cannot be called inside GPU kernels. ExaGOOP resolves this as follows.
Per-timestep CPU pre-computation (compute_udf_wall_vel_at_nodes):
For each face that has a UDF loaded, the function is called once per
boundary node with the node’s physical coordinates and the current time.
The results are stored in a host-side amrex::Vector<Real> and then
copied to a Gpu::DeviceVector<Real> via
Gpu::copy(Gpu::hostToDevice, ...). The device array is indexed as:
2D:
[j * SPACEDIM + c]wherejis the node index along the single perpendicular direction3D:
[(j * (ncells[p1]+1) + k) * SPACEDIM + c]wherejandkare node indices in the two perpendicular directions (p0 = (dir==0) ? 1 : 0,p1 = (dir==2) ? 1 : 2)
Nodal velocity enforcement (apply_udf_nodal_bcs): A GPU
ParallelFor over the nodal MultiFab overwrites the velocity at
each boundary node using a direct lookup into the device array. For a
noslip face, all velocity components are set to the UDF value; for a
slip face, only the normal component is set.
Particle boundary conditions (moveParticles): When a particle
crosses a UDF face, its wall velocity is computed by bilinear
interpolation (3D) or linear interpolation (2D) of the pre-computed
device array at the particle’s transverse position. This interpolated
value replaces the constant wall_vel used by the non-UDF path and is
then passed to the standard applybc kernel.
Supported face labels
Any of the six domain-face labels may carry a UDF:
xlo, xhi, ylo, yhi, zlo, zhi. Multiple faces
can each have an independent UDF. The same library file can export
multiple functions, each assigned to a different face.
Limitations
The UDF is called on a single CPU thread. For large 3D grids with many boundary nodes the call overhead is proportional to the number of nodes on the face; the cost is typically negligible compared to the particle and grid operations.
The UDF must be stateless (no global mutable state) because the order in which nodes are visited is not guaranteed between time steps. Time dependence must be expressed solely through the
targument.Shared libraries must be in a format compatible with the OS on which the solver runs (Mach-O
.dylibon macOS, ELF.soon Linux). Cross-compiled libraries will fail to load.