Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
28445 Discussions

Help with pointer to array of pointers

Javier_M_
Beginner
666 Views

Hello.

I'm implementing a user subroutine for MSC Marc and I need to create a pointer to an array of pointer. I need to populate this array with data read from an external text file. This external text file is in the form of several columns, let's say:

integer SolidElement, integer SolidVertex, real*8 Sx, real*8 Sy, real*8 Sz, real*8 Sxy

I have created the following types:

type Solid2DTension_Rank
        sequence
        integer :: SolidElement
        integer :: SolidVertex
        real*8 :: Sx, Sy, Sz, Sxy
end type
    
type Solid2DTension
        type (Solid2DTension_Rank), pointer :: rank
end type

As can be seen, Solid2DTension_Rank exactly has the variables I'm reading from the text file.

The idea is that every line of the text file will be pointer to a Solid2DTension_Rank type. The whole Solid2D_Ten structure will be pointed to by the Solid2D_Ten_rank pointer.

Also I have defined several variables:

        type(Solid2DTension), dimension(:), allocatable, target :: Solid2D_Ten
        type(Solid2DTension), dimension(:), pointer :: Solid2D_Ten_common
        type(Solid2DTension_Rank), pointer :: Solid2D_Ten_rank

Solid2D_Ten_common is the pointer to the allocatable array, Solid2D_Ten. In my code, I do a Solid2D_Ten_common => Solid2D_Ten after allocating Solid2D_Ten.

After that, I get to read every line of the text file. How can I assign each of the lines I read to an element of the array?.

I know that I can use just an array of Solid2DTension_Rank (not pointers), but I want to be able to nullify the ranks as I process them later so I can speed up the search inside this structure.

Best regards.

0 Kudos
3 Replies
jimdempseyatthecove
Honored Contributor III
666 Views

Assume your array Solid2D_Ten is allocated to at least the number of input lines. And assume your READ code reads into this array (and remembers the line count).

I suggest then that you replace your array of pointers with an array of LOGICALs that contains .TRUE. for each element that has yet to be processed (non-existent entries set to .FALSE.). Then as you process elements, set the logical array entry to .FALSE.

While you can use the pointer array to the same effect, the use of the logical array is more traditional. You could also encode the "processed" by negating SolidElement or SolidVertex assuming one of them is normally +.

Jim Dempsey

 

0 Kudos
IanH
Honored Contributor II
666 Views

First, and most importantly, I don't follow what it is you are trying to do.  (Perhaps check your description to make sure you have your type/variable names and who is pointing to what around the right way, rather than saying things like "the array" be specific - there are several arrays!)  But guessing a little wildly...

I think what you are trying to do is to read each line of the file into an object of type Solid2DTension_Rank.  You then want to have an array of objects that point to each Solid2DTension_Rank object.  There are some options, which option is best, (and whether this whole approach is appropriate) depends on why you are trying to do this.

Your large array of pointers appears to be `Solid2D_Ten`.  Assuming, for simplicity, that you know how many lines are in the file, you could read each line in using an approach like:

allocate(Solid2D_Ten(num_lines))
do i = 1, num_lines
  allocate(Solid2D_Ten(i)%rank)
  call read_a_line(some_unit, Solid2D_Ten(i)%rank)
end do

...

subroutine read_a_line(unit, rank)
  integer, intent(in) :: unit
  type(Solid2DTension_Rank) :: object
  ! Perhaps...
  read(unit, some_format) object
end subroutine read_a_line

In the above, each element of the Solid2D_Ten array has a pointer component that is individually allocated, conceptually each element of Solid2D_Ten "owns" the thing that the pointer references.  If you later decide that you don't want the data for that line, you could deallocate the data for specific lines.  Because this approach allocates one relatively small structure for each line, you could be looking at a lot of allocations.

(In the above, there really isn't any reason for the component of Solid2D_Ten to be a pointer - it could also be an allocatable, which can simplify lifetime management of the thing addressed by the component of each element.  As a general principle (exceptions exist), pointer components are only used in modern (F2003+) Fortran when you want to reference something, use allocatables otherwise.

Another approach is to have a large storage array for the line data, that the pointer components in the individual elements of Solid2D_Ten then reference, rather than own:

type(Solid2DTension_Rank), allocatable, target :: storage(:)

allocate(storage(num_lines))
allocate(Solid2D_Ten(num_lines))
do i = 1, num_lines
  ! Read the line of data in.
  call read_a_line(some_unit, storage(i))
  ! Associate a pointer with the line of data.
  Solid2D_Ten(i)%rank => storage(i)
end do

Now there are only two allocations.  You can't deallocate the data for individual lines, but you can dissociate (nullify) individual pointer components.  Deallocating the single storage array deallocates storage for all lines, at that stage all remaining undissociated pointer components in Solid2D_Ten become undefined.

The above snippets don't use Solid2D_Ten_common and Solid2D_Ten_rank, because I don't understand what they are for.  Note that Solid2D_Ten_rank is a scalar, so at best it is only going to be pointing to the data for one line, not all lines.

Whether all this pointer malarkey is worth it depends, you may be better off just having an array of logical that indicates whether a particular line's worth of data has been processed/is required or not.

(Rather than real*8, use the standard syntax.  Also, why is the type a sequence type?

0 Kudos
Javier_M_
Beginner
666 Views

Thanks both for the great suggestions and detailed explanations.

I kept trying to solve the problem after I wrote the post and managed to get it working in a very similar way as lanh has suggested. More on this later.

To explain the actual problem with some depth: I need to pass a list of elements with their tensions that are listed in a txt file to a MSC Marc routine called UINSTR. This routine gets called twice per point, so it can be called several million times just in a medium sized problem. I need to read this data and put it into memory, but as this routine gets called so many times, MSC Marc provides another user routine that is called USDATA that can be used for just this purpose. This routine is called twice and it has a variable stating if you are on the first or second call. In order to transfer this data to other routines of MARC, a COMMON block is provided and must be used in order to be able to access the data read in USDATA. So the process is:

- USDATA: Read the text file in the first call of the subroutine and store it in some kind of structure that will be stored in the named COMMON block used by MARC.

- UINSTR: Inside this routine you have access to the element, vertex, layer, and several other FEM data that are passed using the routine arguments, and an output argument that is the stresses you want to use that were defined in the text file. The stresses should be stored in the COMMON block using the designed structure.

So, my initial failed approach was to read the file and count the number of lines create an allocatable array of types. This first approach doesn't work as allocatable arrays can't be passed using COMMON blocks. What can be passed using a COMMON block is a pointer, hence I created a Solid2D_Ten target and a Solid2D_Ten_common type that is going to point to Solid2D_Ten after allocation and is the pointer that is going to be passed using the COMMON block.

Just to clarify, my first succesful implementation was:

type Solid2DTension
	        integer :: SolidElement
	        integer :: SolidVertex
	        real*8 :: Sx, Sy, Sz, Sxy
end type

...

type(Solid2DTension), dimension(:), allocatable, target :: Solid2D_Ten
type(Solid2DTension), dimension(:), pointer :: Solid2D_Ten_common
common /name_of_common_block/ Solid2D_Ten_common
...

allocate(Solid2D_Ten(number_lines))

...

Solid2D_Ten_common => Solid2D_Ten

This works.

I was suggested though, that I could implement this as a pointer to an array of pointers, with the benefit (I'm not sure about this) of being able to nullify the pointers when the actual element is read and no longer needed. I don't really know if this approach frees memory:

type Solid2DTension_rank
	        integer :: SolidElement
	        integer :: SolidVertex
	        real*8 :: Sx, Sy, Sz, Sxy
end type

type Solid2DTension
            type (Solid2DTension_Rank), pointer :: rank
end type
...

type(Solid2DTension), dimension(:), allocatable, target :: Solid2D_Ten
type(Solid2DTension), dimension(:), pointer :: Solid2D_Ten_common
type(Solid2DTension_rank), pointer :: rank
common /name_of_common_block/ Solid2D_Ten_common

...

allocate(Solid2D_Ten(number_lines))

...

Solid2D_Ten_common => Solid2D_Ten

...

! I'm writing from memory here but this is the idea of how I solved it
! Some DO loop
allocate(rank)
Solid2D_Ten(element)%rank => rank
...

About the real*8 thing, it's the nomenclature we need to use when programming the MSC MARC subroutines, don't ask me why. I use real(8) or the more modern "kinds" in my own code, but this is part of a whole project that has to have a homogeneous "look".

ONE QUESTION: What happens with the allocated Solid2D_Ten array I allocated in USDATA?. When can I deallocate it?. Once inside UINSTR I have access to the COMMON block only.

Thank you very much and best regards.

0 Kudos
Reply