The Dynaccess Library

Using Dynamic Allocation in MCTDH

Allocation

Use variables of type
integer pointer(2)
to store pointers (can be shared in common blocks).

call alloc(pointer, n)
where n is the number of bytes to allocate to allocate a block of memory. The pointer is stored in pointer.

Use the functions complexsize(), realsize(), integersize() and logicalsize() from lib/utilities/platform.F as prefactors to create portable code, e.g. n=realsize()*len to allocate a double precision array that can hold len numbers.

Resizing

call resize(pointer, m)
to resize the array to the new size in bytes m, data already present in the array is copied into the new location.

If pointer is set to zero prior to calling realloc, this routine can also be used instead of alloc. This is useful when an array is to grow in a loop along with the arrival of data.

Writing to Arrays

Data is stored in an array via
call set****(value, pointer, index)
where **** stands for character, short, long, lli, float, double, scpx, dcpx.
These can be used to access elements of arrays that store integer*1 (bytes), integer*2 (words), integer*4, integer*8, real*4, real*8 (double precision), complex*8 (single complex), and complex*16 (double complex) variables.

The set**** call can be envisaged as corresponding to the Fortran statement
array(index) = value

Reading from Arrays

For most types, the elements in a dynamic array are accessed via
variable = get****(pointer, index)
where **** are as before and the statement is equivalent to Fortran's
variable = array(index).

Note that the getlli and the get?cpx calls for long long int and complex numbers cannot be called this way.
Instead, they need to be called procedurally, i.e. the above Fortran call corresponds to
call getdcpx(variable, pointer, index)

This has become necessary due to a shortcoming in the specification of the Fortran standard which allowed different Fortran compilers to implement passing of complex function results differently.

All other functions get**** can be used just like any ordinary Fortran function, e.g. in a debug statement of the form
write(0,'(e16.8)')(getdouble(buffer, i),i=1,buflen)

Note also that with setlli you can actually store pointer variables, so you can create arrays of arrays (i.e. two-dimensional arrays). This was done with arrays of arrays of double precision for in source/opfuncs/linint.F. Have a look to see how it was done.

Freeing Memory

One of the advantages of using dynamic memory allocation is that it allows to free memory, so that more memory is available for other sections of the program.

Memory allocated can be deallocated by calling either of
call dealloc(pointer)
call resize(pointer, 0)

Block Access

Random access to data is almost as fast with the dynaccess library as on Fortran 77 arrays. However, sequential data access is much faster with Fortran 77 arrays.
In order to achieve better performance for sequential access of large blocks of data, blockwise operations on dynamical arrays can be performed by
call setblock(buffer, pointer, offset, length)
call getblock(buffer, pointer, offset, length)

where buffer(*) is a Fortran array of any type that is used to store data to or read it from the dynamic array, integer pointer(2) is a Fortran pointer placeholder which has been initialised with alloc or resize, offset is the offset in bytes from the start of the memory designated by pointer and length is the length in bytes of the data block to copy.

Use the functions complexsize(), realsize(), integersize() and logicalsize() to translate array indices and lengths into byte offsets and lengths.

Note that copying from the beginning of the memory block is done by using offset=0, i.e. we are counting from zero instead of counting from one, which would have been the usual Fortran way (and the way it is done in all other get**** and set**** routines to make them less confusing to use from Fortran).
This convention is more natural when operating with complexsize(), realsize() etc. and hence has been used for the ***block subroutines.

The speed advantage of the ***block subroutines stems from the fact that large data sets are transferred in one function call.

Dynamic Allocation of Arrays Containing Character Strings

An array containing strings is an array of arrays containing characters, which is a common problem in programming, so special routines were devised for this case. Allocation:
call newstring(pointer, n)
where n is the number of stings to be stored.

Resizing is done via
call resizestring(pointer, n, o)
where n is the new allocation size and o is the old one. Freeing all memory occupied by the array of string pointers and the strings themselves is done with either of
call freestring(pointer)
call releasestring(pointer, n)

Array elements (i.e. strings) are set with
call setstring(string, pointer, index)
which corrsponds to
array(index) = string
and read out with
call getstring(string, pointer, index)
which corresponds to
string = array(index)

Note that the latter is not a function but a procedure because we are copying an array of characters into the character*(*) variable string.
This routine is fail-safe, i.e. if the string variable is shorter than what was stored in the array element, only the first few characters are retrieved; if it is longer, it is filled up with white blanks (as is the Fortran way).

Note that setstring allocates enough memory to hold the string string, i.e. calling it via
call setstring(string(start:end), pointer, index)
will save memory if you only need a part of a string.

These techniques were used in source/opfuncs/linint.F. Have a look to see how it was done.