1.10. Sharing Data Between User Routines

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.