The discussion here applies to both Linux and Windows operating systems and it is only applicable to shared memory MAPDL jobs. Mechanical APDL allows more than one user routine in a single run. You can issue multiple /UPF commands in the input file to activate the routines.
Using multiple routines simultaneously may require sharing data generated by one routine with another. The easiest method for doing so is to use common-block (or global) variables.
In Linux, a single shared library contains all compiled user routines and data sharing is straightforward. In Windows, however, each user routine is built into a separate dynamic link library (.dll); to share data, functions and data must be explicitly exported and imported.
The userdata subroutine enables
you to store the common-block functionality and data. You can edit usercm (included in userdata) to add
common-block variables. The compiler uses !DEC$ ATTRIBUTES DLLEXPORT
and !DEC$ ATTRIBUTES DLLIMPORT to indicate which functions and
common-block variables to export or import, respectively. Both commands are valid for the
supported Intel Fortran and C compilers.
Example 1.1: Creating and Exporting Functions and Common Block Variables
c usercm.inc
*comdeck,usercm USERDISTRIB
!DEC$ ATTRIBUTES DLLEXPORT :: /usercm/
common /usercm/ userdatsz,userdatptr
pointer (userdatptr,userdataarray)
integer userdatsz
double precision userdataarray(*)
c
c userdat.F . Sample function exported for use in other subroutines
function getusercmvals(iloc,sz,outdata)
!DEC$ ATTRIBUTES DLLEXPORT :: getusercmvals
#include "usercm.inc"
integer iloc,sz,getusercmvals,iX
double precision outdata(*)
double precision userdataloc(*)
pointer (userdatptr,userdataloc)
if (iloc.lt.1.or.iloc+sz-1.gt.userdatsz) then
getusercmvals = 0
else
do iX=iloc,iloc+sz-1,1
outdata(iX)= userdataloc(iX)
enddo
getusercmvals = 1
endif
return
end
Example 1.2: Importing and Using Functions
Imported functions are added to the interface section. Common blocks are also imported as needed.
INTERFACE
FUNCTION getusercmvalsz ()
!DEC$ ATTRIBUTES DLLIMPORT :: getusercmvalsz
integer getusercmvalsz
END
return
end
END INTERFACE
!DEC$ ATTRIBUTES DLLIMPORT :: /usercm/
common /usercm/ userdatsz,userdatptr
pointer (userdatptr,userdataarray)
integer userdatsz
double precision userdataarray(*)
Using common-block variables in shared memory requires care. Multiple threads started by the executable access and share the same memory location and can overwrite each other's values. To minimize conflict, allocate enough space for each thread and avoid writing to the same location at the same time.
Example 1.3: Safely Allocating Separate Data for Each Thread
One double-precision location exists in the common block. Enough space is allocated for
numberofprocessors * 1 locations. Functions
pplock and ppunlock are used when initializing
the memory or values.
When setting the value, write to the iy memory location only (used for that specific thread). Even if other threads access the same common block, they do not modify that memory location.
c -----------------------
c Initialize common-block values in user routine 1 such as USolBeg.F
c
call pplock(LOCKUPF)
inumprocs = ppinqr(PP_NPROCS)
call initusercmvals(inumprocs)
val1(:) = 0.0d0
iy = setusercmvals(ix, inumprocs,val1)
call ppunlock(LOCKUPF)
c
c -----------------------
c Set common-block value to time in user routine 2
c
isize = 1
c Go to location allocated for the iy the thread
iy = ppproc()+1
val1 = time
c Set just the iy-th value.
iy = setusercmvals(iy,isize,val1)
c
c -----------------------
c Get saved value from common block in user routine 3
c
iproc = ppproc()+1
isize = 1
call getusercmvals(iproc,isize,dFldTime)For information about the pplock, ppinqr, ppunlock, and ppproc subroutines used in Example 1.3: Safely Allocating Separate Data for Each Thread, see Subroutines for Your Convenience.