Damien Dosimont bio photo

Damien Dosimont

Researcher at BSC

Email LinkedIn Github

We focus on the code from the start to the input reading. The corresponding files are located in the following location:

Sources
|
|------ kernel
        |
        |------ master
                |
                |------*.f90

Alya.f90

This is the main file, containing the Alya program. After some conditional initializations (Extrae, Alya DLB), call the subroutine Turnon.

Turnon.f90

Defines the subroutine Turnon. Contains the following dependencies (important: it seems that many global variables are used, with multiple side effects!):

use def_parame
use def_elmtyp
use def_master
use def_inpout
use def_domain
use def_kermod
use mod_finite_volume,        only : finite_volume_arrays

Definitions are located here:

Sources
|
|------ kernel
        |
        |------ defmode
                |
                |------*.f90

After initializing, set up mpi communicators:

! Splits the MPI_COMM_WORLD for coupling with other codes. This defines the Alya world
!
call par_code_split_universe()
!
! Splits the MPI_COMM_WORLD for coupling
!
call par_code_split_world()

And read the problem data.

! Read problem data
!
call reapro()

After doing other initializations, read the domain data.

!----------------------------------------------------------------------
!
! Read domain data
!
!----------------------------------------------------------------------

call cputim(time1)
call readom()
call cputim(time2)
cpu_start(CPU_READ_DOMAIN) = time2 - time1

readom.f90

This is the file doing calls to read the domain data (such as metadata about the mesh files, the meshfiles themselves). Here are its location.

Sources
|
|------ kernel
        |
        |------ domain
                |
                |------*.f90

It defines the readom routine.

use def_master
use def_domain
use def_kermod,       only : kfl_timeline
use mod_ker_timeline, only : ker_timeline
implicit none

if( kfl_timeline /= 0 ) call ker_timeline(0_ip,'INI_READ_MESH')
!
! Open files
!
call opfdom(1_ip)
!
! Read geometry data
!
if( kfl_examp /= 0 ) then
   call exampl()
else
   call readim()                         ! Read dimensions: NDIME, NPOIN, NELEM...
   call reastr()                         ! Read strategy:   LQUAD, NGAUS, SCALE, TRANS...
   call cderda()                         ! Derivated param: NTENS, HNATU, MNODE, MGAUS...
   call reageo()                         ! Read arrays:     LTYPE, LNODS, LNODB, LTYPB, COORD...
   call reaset()                         ! Read sets:       LESET, LBSET, LNSET
   call reabcs()                         ! Read bcs:        KFL_FIXNO, KFL_FIXBO, KFL_GEONO
end if

The two main routines that interest us for the moment are readim, which reads the problem dimensions, a kind of metadata file, and reageo, which is the routine responsible for parsing the meshfile.

reageo.f90

This is our main interest point. This file parses the mesh file. Here are the dependencies (should be taken seriously because of the multiple side effects that occur in this part):

use def_kintyp
use def_parame
use def_master 
use def_domain
use def_inpout
use mod_iofile
use mod_memory
use def_elmtyp
use def_kermod, only : multiply_with_curvature

Those are the variables:

implicit none
integer(ip)           :: ielem,ipoin,jpoin,inode,idime,iimbo
integer(ip)           :: iboun,inodb,ielty,ktype,dummi,kfl_defau
integer(ip)           :: iskew,jskew,jdime,imate,kelem,nlimi
integer(ip)           :: iblty,knode,kfl_gidsk,kfl_dontr,kpoin
integer(ip)           :: kfl_binme,ipara,iperi,imast,kfl_icgns
integer(ip)           :: pelty,ifiel,izone,kfl_ifmat,kfl_elino
integer(ip)           :: isubd,jstep,jperi
real(rp)              :: dummr
character(20)         :: chnod
integer(ip),  pointer :: lelno(:)

nullify(lelno)

They are not commented and the meaning of some of them is a bit obscure.

This instruction ensures a sequential reading, only done by the master:

if( ISEQUEN .or. ( IMASTER .and. kfl_ptask /= 2 ) ) then

Some other variables, commented this time:

!
! Initializations
!
kfl_chege  = 0                   ! Don't check geometry
kfl_naxis  = 0                   ! Cartesian coordinate system
kfl_spher  = 0                   ! Cartesian coordinate system
kfl_bouel  = 0                   ! Element # connected to boundary unknown
nmate      = 1                   ! No material
nmatf      = 1                   ! Fluid material
ngrou_dom  = 0                   ! Groups (for deflated): -2 (slaves), -1 (automatic, master), >0 (master)
kfl_gidsk  = 0                   ! General skew system
kfl_dontr  = 0                   ! Read boundaries
kfl_binme  = 0                   ! =1: Mesh must be read in binary format
nfiel      = 0                   ! Number of fields
curvatureDataField = 0           ! curvature data field id
curvatureField = 0               ! curvature data field id
kfl_elcoh  = 0                   ! Assume cohesive elements
kfl_elint  = 0                   ! Assume interface elements
!
! Local variables
!
kfl_icgns  = 0                   ! New Alya format for types of elements
kfl_ifmat  = 0                   ! Materials were not assigned
ktype      = 0                   ! Element # of nodes is not calculated
imast      = 0                   ! Master has not been read
izone      = 0                   ! Zones have not been allocated
kfl_elino  = 0                   ! Eliminate lost nodes

Now comes the memory allocation, done by memgeo. Depending of the value passed in parameter, memgeo allocates a certain amount of memory for a particular topic. This time again, careful about the side effects and the reading of variables that are not passed in parameter. This routine will have to be reworked to ensure the parallelism.

!
! Memory allocation
!
call memgeo( 1_ip)               ! Mesh arrays
call memgeo(49_ip)               ! Zones 
call memgeo(53_ip)               ! Periodicity

Now starts the reading. First, with some options that are not present in the example file which has been given to me.

!
! Read options and arrays
! 
call ecoute('REAGEO')
do while( words(1) /= 'GEOME' )
  call ecoute('REAGEO')
end do
if( exists('AXISY') ) kfl_naxis = 1
if( exists('SPHER') ) kfl_spher = 1
if( exists('CHECK') ) kfl_chege = 1
if( exists('GID  ') ) kfl_gidsk = 1

if( exists('ELIMI') ) kfl_elino = 1

The ecoute routine is responsible for reading a line of the input file. Something I don’t get is the purpose of ‘REAGEO’ string taken as argument. It is not referenced/tested in ecoute. The file that is actually readen by ecoute is actually nunit.

Interesting point: this part determines if the file is binary, which means that a binary version of the mesh file already exists. I should look for its specifications: it could be used as a starting point for defining a parallel compliant format.

!
! Binary file: read/write
!
if(exists('BINAR').or.exists('READB')) then
  call geobin(2_ip)
  do while(words(1)/='ENDGE')
     call ecoute('reageo')
  end do
  return
end if
if(exists('OUTPU').or.exists('WRITE')) kfl_binar=1

Main loop reading the whole file:

!
! ADOC[0]> $-----------------------------------------------------------------------
! ADOC[0]> $ Mesh definition
! ADOC[0]> $-----------------------------------------------------------------------
! ADOC[0]> GEOMETRY
!
do while(words(1)/='ENDGE')
  call ecoute('reageo')

Test to determine if the file is a binary, in this case calls the routine geobin.

if( words(1) == 'BINAR' ) then
  !
  ! Read geometry from binary file
  !
  call geobin(2_ip)

Part parsing the NODES section. We’ll describe more in details than the previous code section.

Determining if the line parsed contains NODES. Note the words variable, defined in def_inpout.f90, which contains the line content. The nodes section is not present in the example file, and the following code description seems to match with the type section as well. Need to ask for more information…

else if( words(1) == 'NODES' ) then
  !
  ! LTYPE: Element types
  !

I don’t understand the purpose of ktype. nelem is defined in def_domain.f90 and is the number of elements.

  ktype=nelem

Printing some informations.

call livinf(27_ip,' ',0_ip)

Iteration over the number of elements

do ielem=1,nelem

dummi’s and knode role are commented nowhere. nunit is one of the listen files defined in def_inpout.f90 I imagine that, if we take into account the mesh file structure, dummi is the node number and ktype the type. Since we already know the number of nodes, dummi is not readen after having being retrieved (doing the assumption the node are ordered).

   read(nunit,*,err=1) dummi,knode

From the knode identifier, which is a numerical value, we retrieve its real type through fintyp, that we store in ielty.

   call fintyp(ndime,knode,ielty)

lexis informs whether the type exists (is in the file, I guess) or not

   lexis(ielty)=1

Assigns the type to the corresponding element.

   ltype(ielem)=ielty
end do

Testing the section ends correctly.

call ecoute('reageo')
if(words(1)/='ENDNO')&
     call runend('REAGEO: ERROR READING NODES PER ELEMENT, POSSIBLY MISMATCH IN NUMBER OF NODES IN DOM.DAT')
call mescek(2_ip)

Here comes the types section. This time the parsing in done in a subroutine reatyp.

    else if( words(1) == 'TYPES' ) then 
       !
       ! ADOC[1]> TYPES_OF_ELEMENTS [, ALL= char]
       ! ADOC[1]>   int int                                                                     $ Element, type number
       ! ADOC[1]>   ...
       ! ADOC[1]> END_TYPES_OF_ELEMENTS
       ! ADOC[d]> TYPES_OF_ELEMENTS:
       ! ADOC[d]> This field contains the element types. At each line, the first figure is the element number and the second
       ! ADOC[d]> one the element type. If all the elements are the same (say TET04, the following option can be added to the header: 
       ! ADOC[d]> TYPES OF ELEMENTS, ALL=TET04 and then the list should be empty.
       ! ADOC[d]> The correspondance between element type and element number is the following:
       ! ADOC[d]> <ul>
       ! ADOC[d]> <li> 1D elements: </li>
       ! ADOC[d]> <ul>
       ! ADOC[d]> <li> BAR02 =    2 </li> 
       ! ADOC[d]> <li> BAR03 =    3 </li> 
       ! ADOC[d]> <li> BAR04 =    4 </li> 
       ! ADOC[d]> </ul>
       ! ADOC[d]> <li> 2D elements: </li>
       ! ADOC[d]> <ul>
       ! ADOC[d]> <li> TRI03 =   10 </li> 
       ! ADOC[d]> <li> TRI06 =   11 </li> 
       ! ADOC[d]> <li> QUA04 =   12 </li> 
       ! ADOC[d]> <li> QUA08 =   13 </li> 
       ! ADOC[d]> <li> QUA09 =   14 </li> 
       ! ADOC[d]> <li> QUA16 =   15 </li> 
       ! ADOC[d]> </ul>
       ! ADOC[d]> <li> 3D elements: </li>
       ! ADOC[d]> <ul>
       ! ADOC[d]> <li> TET04 =   30 </li> 
       ! ADOC[d]> <li> TET10 =   31 </li> 
       ! ADOC[d]> <li> PYR05 =   32 </li> 
       ! ADOC[d]> <li> PYR14 =   33 </li> 
       ! ADOC[d]> <li> PEN06 =   34 </li> 
       ! ADOC[d]> <li> PEN15 =   35 </li> 
       ! ADOC[d]> <li> PEN18 =   36 </li> 
       ! ADOC[d]> <li> HEX08 =   37 </li> 
       ! ADOC[d]> <li> HEX20 =   38 </li> 
       ! ADOC[d]> <li> HEX27 =   39 </li> 
       ! ADOC[d]> <li> HEX64 =   40 </li> 
       ! ADOC[d]> </ul>
       ! ADOC[d]> <li> 3D 2D-elements: </li>
       ! ADOC[d]> <ul>
       ! ADOC[d]> <li> SHELL =   50 </li> 
       ! ADOC[d]> </ul>
       ! ADOC[d]> </ul>
       !
       call reatyp(kfl_icgns,nelem,ktype,ltype,lexis)

mescek checks if the routine is correct. We should’nt need to intervene in this one. call mescek(2_ip)

Now, the elements section.

else if( words(1) == 'ELEME' ) then
   !
   ! ADOC[1]> ELEMENTS 
   ! ADOC[1]>   int int int ...                                                             $ Element, node1, node2, node3 ...
   ! ADOC[1]>   ...
   ! ADOC[1]> END_ELEMENTS 
   ! ADOC[d]> ELEMENTS:
   ! ADOC[d]> This field contains the element connectivity. The first figure is the element number,
   ! ADOC[d]> followed by the list of it nodes.
   !

Printing some informations

   call livinf(28_ip,' ',0_ip)

Ok, now I understand the purpose of ktype. I imagine a value equal to 0 means the information is not known and requires a manual parsing. Since the ecoute routine seems very costly (is it not possible to implement several ecoute, simpler, and more optimized, by the way?), the authors want to minimize its usage as possible.

   if( ktype == 0 ) then

Reading a line

      call ecoute('reageo')

Evaluating the end of the section

      do while( words(1) /= 'ENDEL' )

ielem is the first column element of the retrieved row, i.e. the element id

         ielem = int(param(1_ip))

Test to see if we don’t exceed the maximum element value.

         if( ielem < 0 .or. ielem > nelem ) &
              call runend('REAGEO: WRONG NUMBER OF ELEMENT '&
              //adjustl(trim(intost(ielem))))

I guess knode is the number of node associated with the element. nnpar is another variable modified by ecoute through a side-effect.

         knode = nnpar-1

Find the type as a function of knode and the dimension number and associate it with the element in the ltype table. See that this process is the same than the one done in the nodes section.

         call fintyp(ndime,knode,ielty)
         ltype(ielem) = ielty

Iterate over the nodes. The table lnods associates each node index, element couple with the corresponding node identifier.

         do inode = 1,nnode(ielty)
            lnods(inode,ielem) = int(param(inode+1))
         end do

Reads another line.

         call ecoute('reageo')
      end do
   else

Basically, does the same process, but reusing the information retrieved in the nodes section parsing. It does not need the ecoute routine and use a simple read to fill the lnods table, which is much more efficient.

      do ielem = 1,nelem
         ielty = ltype(ielem)
         read(nunit,*,err=2) dummi,(lnods(inode,ielem),inode=1,nnode(ielty))
      end do
      call ecoute('reageo')
   end if
   if( words(1) /= 'ENDEL' )&
        call runend('REAGEO: WRONG ELEMENT FIELD')

Calls the mesh checking. call mescek(3_ip)

reatyp.f90

We details here the reatyp function which parses the types.

TODO: first part of the code.

This function ltnew converts old types to new types. It is very inefficient and must be corrected, since it evaluates all the conditions event if the test is true in the previous one!

function ltnew(ityol)
  !-----------------------------------------------------------------------
  !
  ! Converts old type to new type
  !
  !-----------------------------------------------------------------------
  use def_kintyp
  integer(ip) :: ltnew,ityol

  if (ityol ==  2) ltnew= 2
  if (ityol ==  3) ltnew= 3
  if (ityol ==  4) ltnew= 4
  if (ityol ==  5) ltnew= 10
  if (ityol ==  6) ltnew= 11
  if (ityol ==  7) ltnew= 12
  if (ityol ==  8) ltnew= 13
  if (ityol ==  9) ltnew= 14
  if (ityol == 10) ltnew= 30
  if (ityol == 11) ltnew= 31
  if (ityol == 12) ltnew= 32
  if (ityol == 13) ltnew= 33
  if (ityol == 14) ltnew= 34
  if (ityol == 15) ltnew= 35
  if (ityol == 16) ltnew= 36
  if (ityol == 17) ltnew= 37
  if (ityol == 18) ltnew= 38
  if (ityol == 19) ltnew= 39

end function ltnew