Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
Beginner
11 Views

Re-dimension preserve for arrays of any data type

Version: XE Studio 2015, update 6

I'm trying to write a function with the following specification

  • Inputs:
    • array - the array to be redimensioned
    • length
      • if positive, redimension array to length
      • if negative, add ABS(length) to the length of array
    • memsize, the size of one element in memory (I'll get to this later)
  • Returns:
    • the new length of the array
  • Specification:
    • If the array is not allocated, allocate the array to ABS(length)
    • The values currently in the array are preserved during redimensioning
    • The function supports all 1D arrays of any datatype (intrinsic or user)

So obviously that last point is a tall order. As far as I can see, the typical approach is just to duplicate the function for all data types it will be used with. Coming from Python, the idea of doing this causes me great pain.

As far as I know, you can't set the kind of something like an integer at runtime. But you can set aside a block of memory equal to that occupied by the datatype by allocating array as if it were an array of strings, e.g.

allocate(character(len=memsize) :: array(abs(length)))

You can also use NO_ARG_CHECk to suppress compiler warnings when you try to feed in integer arrays etc.

I find that my function works a lot of the time, but lets say 50% of the time, it will do something like this

Test:

          !Up in definitions
          type(COTYPE), allocatable :: co(:)

          !Somewhere down in the bowels of the code
          !Part of a loop in which the first attempt is much more likely to succeed
          !The second go around, the code still has a chance but it's rare that it works
          l = ReDimPreserve(co,-1,sizeof(co(1))) !Add one to the length of this array
          if (allocated(co(ubound(co,1))%VolHistory)) then
           print "Allocated"
          else
           print "Not allocated"
          end if
          allocate(co(ubound(co,1))%VolHistory(1))

Output:

> Not allocated

forrtl: severe (151): allocatable array is already allocated
Image              PC                Routine            Line        Source
libifcoremd.dll    000000000059E478  Unknown               Unknown  Unknown
libifcoremd.dll    00000000005985A9  Unknown               Unknown  Unknown
libifcoremd.dll    0000000000584E9F  Unknown               Unknown  Unknown
libifcoremd.dll    00000000004F5198  Unknown               Unknown  Unknown
libifcoremd.dll    0000000000548600  Unknown               Unknown  Unknown
usub.exe           000000013FB8E469  JAMESMOD_mp_SETUP         684  usub.f

As can be seen, we satisfied ourselves that VolHistory was definitely not allocated, and then immediately execution failed when trying to allocate it, saying it's already allocated.

This kind of error seems to happen a lot and since it's so intermittent it obviously has something to do with not correctly managing the memory on my part . I would really like to know why it doesn't work 100% of the time, especially if it works extremely well some of the time.

On the times that it does work, you can put data in VolHistory, you can redimension co to be longer or shorter, you can also redimension VolHistory in earlier instances of COTYPE in co and I've not once seen it loose or corrupt data. When it works, it's surprisingly robust.

What is actually happening in memory when I use this approach? Is there any possible way of making it work reliably since I'm so close?

type(co):

       type COTYPE
        sequence
        integer reference
        real*8, allocatable :: VolHistory(:)
       end type

ReDimPreserve:

       integer function ReDimPreserve(array, length, memsize)
        !DEC$ATTRIBUTES NO_ARG_CHECK  :: array
        implicit none
        integer, value                :: length, memsize
        integer                       :: assignBound
        character(len=:), allocatable :: array(:)
        character(len=:), allocatable :: tempArray(:)
               
        if (not(allocated(array))) then
         allocate(character(len=memsize) :: array(abs(length)))
         ReDimPreserve = abs(length)
         return
        end if
       
        if (length < 0) then
         length = ubound(array,1) + abs(length)
        end if
        
        ReDimPreserve = length
       
        assignBound = min(length,ubound(array,1))      
        allocate(character(len=memsize) :: tempArray(1:length))
        tempArray(1:assignBound) = array(1:assignBound)
        call move_alloc(FROM=tempArray,TO=array)
        
       end function ReDimPreserve

 

 

 

0 Kudos
2 Replies
Highlighted
Beginner
11 Views

I decided to go with a different approach, using the Fortran Pre Processor,  which I'm happy with.

(On another note, I'm not sure when ';' was added to support multi-line preprocessor definitions, I couldn't find any hint of this and ultimately got it through brute force trial and error.)

#define __SupportReDimPreserve__( __TYPE__ , __ALIAS__ )      \ 
       function __ALIAS__(array, length) result (RDP)       ; \
                                                            ; \
        implicit none                                       ; \
        __TYPE__ , allocatable :: array(:)                  ; \
        __TYPE__ , allocatable :: tempArray(:)              ; \
        integer, value  :: length                           ; \
        integer         :: assignBound, RDP                 ; \
                                                            ; \
        if (not(allocated(array))) then                     ; \
         allocate(array(abs(length)))                       ; \
         RDP = abs(length)                                  ; \
         return                                             ; \
        end if                                              ; \
                                                            ; \
        if (length < 0) then                                ; \
         length = ubound(array,1) + abs(length)             ; \
        end if                                              ; \
                                                            ; \
        RDP = length                                        ; \
        assignBound = min(length,ubound(array,1))           ; \
        allocate(tempArray(1:length))                       ; \
        tempArray(1:assignBound) = array(1:assignBound)     ; \
        call move_alloc(FROM=tempArray,TO=array)            ; \
                                                            ; \
       end function __ALIAS__
       
       __SupportReDimPreserve__(Integer,       RDP_Int)
       __SupportReDimPreserve__(real(8),       RDP_Real)
       __SupportReDimPreserve__(logical,       RDP_Logical)

Then somewhere up top

       interface ReDimPreserve
        module procedure RDP_Int, RDP_Real, RDP_Logical
       end interface ReDimPreserve

I think this solution is far more compiler safe than my previous approach. Its very neat and solves the problem of maintaining several blocks of pretty much identical code.

0 Kudos
Highlighted
Valued Contributor III
11 Views

@James F.,

In your original post, you wrote, "The function supports all 1D arrays of any datatype (intrinsic or user)" - see the thread below re: possible performance hit with "user" type:

https://software.intel.com/en-us/forums/intel-visual-fortran-compiler-for-windows/topic/701985

As you have observed, there are limitations when it comes to generic programming in Fortran.  I personally prefer to avoid preprocessors but to each their own, of course.  If I needed something like this, I would consider standard Fortran with 'CLASS(*), ALLOCATABLE' dummy arguments and I would likely restrict the function to *any intrinsic data type* plus *any derived type that extends from an arbitrary abstract type' that implements certain 'MOVE' semantics based on the thread I link above.

Hope you have continued success with Fortran to build on your work with Python, 

0 Kudos