Ioannis Nompelis
  personal pages  
  Examples and mini tutorial on using the tecio library

  Formatting structured data for plotting with AMTEC's TecPlot
When plotting data, one typically formats the data according to what software package will be used for visualization. Many use in-house programs, or programs that are freely distributable and widely available, such as GNUplot. In the CFD community, it is common practice to use TecPlot, a very nice software package made by AMTEC. It is easy to format data for plotting with TecPlot. Typically one "dumps" the data as ASCII text and TecPlot reads them accordingly.

However, using text files for output can be heavy on disk usage -- because there are several characters (bytes) used to represent one number that has the precision of just a few bytes (4 for floats, 8 for doubles, etc). If you add to this all the delimiter symbols (commas or spaces) and newline characters, there is a lot of wasted disk space! Additionally, writing and reading large datafiles takes a long time, and in some cases TecPlot has to go through a conversion process from text to its native format before loading the data, which forces the use of temporary data files, and surely introduces extra steps to the visualization process.

A solution to all these problems was provided by AMTEC. A library was issued which can be used at compile time and creates native TecPlot binary files directly from your own program. The library is called "tecio.a" and comes with a standard installation of TecPlot. Although I am not aware of how TecPlot and the library deal with the "endian-ness" of the machine that is used, it has worked flawlessly for me, so here is an example of how to use it!

Here is the "naive" FORTRAN example for writing structured datasets (code.f), which can be compiled as:

f90 code.f -o code /path_where_library_resides/tecio.a
In this example a Cartesian grid is created and the variable u and v are filled with integers for each of the nodes, with increasing u and v for increasing i and j respectively. The plotting routine is by no means general, and should only serve as an example for this case (but you have figured that already). I do not think there is a need to disect this code, so here it is:



      program main

      parameter(ni=10,nj=50)
      Real*8 x(ni,nj),y(ni,nj)
      Real*8 u(ni,nj),v(ni,nj)


      do i = 1,ni
      do j = 1,nj
         x(i,j) = dble(i-1)/dble(ni-1)
         y(i,j) = dble(j-1)/dble(nj-1)

         u(i,j) = dble(i-1)
         v(i,j) = dble(j-1)
      enddo
      enddo

      call plot_tecplot(ni,nj,x,y,u,v)

      stop
      end

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
C *** Subroutine to write a .plt file (uses TecPlot tecio.a library)
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
      subroutine plot_tecplot(ni,nj, x,y,u,v)
      implicit none

      integer ni,nj
      Real*8 x(ni,nj),y(ni,nj)
      Real*8 u(ni,nj),v(ni,nj)

      integer  i,j,imax,jmax,kmax
      integer  debug,ier,itot
      integer  tecini,tecdat,teczne,tecnod,tecfil,tecend
      integer  visdouble,disdouble
      character*1 nulchar


      nulchar = char(0)
      debug   = 0
      visdouble = 0
      disdouble = 1
      imax = ni
      jmax = nj
      kmax = 1

      !
      ! Open the file and write the tecplot datafile header information.
      !
      ier = tecini('flowfield'//nulchar,
     &             'x,y,u,v'//nulchar,
     &             'output.plt'//nulchar,
     &             '.'//nulchar,
     &             debug,visdouble)

      !
      ! Write the zone header information.
      !
      ier = teczne('Flowfield'//nulchar,
     &             imax,jmax,kmax,
     &             'BLOCK'//nulchar,nulchar)

      !
      ! Write out the field data.
      !
      itot = imax*jmax*kmax
      ier = tecdat(itot,x,disdouble)
      ier = tecdat(itot,y,disdouble)
      ier = tecdat(itot,u,disdouble)
      ier = tecdat(itot,v,disdouble)

      !
      ! close file
      !
      ier = tecend()

      return
      end


  Now that you know
It is my understanding that the tecio library works like a "state machine." People who are familiar with OpenGL (a library used widely for interactive computer graphics) are very familiar with the idea. From the examples that come with tecio.a from AMTEC, it seems like one can open multiple .plt files and write different data to the different files in no particular order by putting the library in the appropriate state. Although there must be some benefit for having that functionality, the programmer must be cautioned that if multiple files are to remain open simultanuously, (s)he must keep track of the state... In short,... study the examples!

  Using tecio.a for writing unstructured data files
Yes! Of course you can do that! It is a lot like writing structured data, only some of the arguments that are passed to different calls have different meanings.

Below is a subroutine that creates TecPlot .plt files with unstructured data. It is a lot like the structured .plt file creation procedure, only the data is arranged in a way that nothing is really "implied," as is the case with structured data. For structured data, if I were to specify that a particular zone will have "imax" "jmax" and "kmax" points in the i,j, and k directions respectivelly, I could stream the data to a file -- or any stream for that matter. Knowing just the maxima of each of the i,j,k and also the convention of how those indexes "unroll" when the data is dumped into the stream, I could easily recover the data from the stream (or file) by reading them in the same order. By "unroll" I mean the nesting order of the i,j, and k loops. Tecio.a does this in each own internal way when imax, jmax and kmax are specified by simply accepting a pointer to the first data point.

Unstructured data are specified on a "per-point" basis, exactly like structured data, but the connectivity of each data point is not implied, and therefore, unless connectivity information is also provided, the data simply exist as a "cloud of points." To connect the points in this set, and therefore represent it as a mesh, we need to provide the "per-element connectivity." But this would be really ambiguous if we had not specified first what type of elements make up the mesh; for example, the "simplex" for each of the zero, one, two and three dimensions are a point, a line segment, a triangle and a tetrahedron respectivelly. Note that if the data is represented with a structured mesh, the number of dimensions and number of points in each of the directions automatically determine what the element type is.

Therefore, tecio.a expects all of this information and imposes its own restrictions on what kind of data it can accept. To my knowledge, it can accept line-segements, triangles, tetrahedra and "bricks" which is what I chose for this example. So, first you need to open the file and specify the number of variables that will be outputed, the element type, the number of nodes and the number of elements. Here is what a subroutine would look like:



cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
c *** demo subroutine to write a TecPlot .plt file with the real*8 data.
c *** Written by Ioannis Nompelis  16.12.2005
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
       subroutine field_output(ne,nno,nel,ien,xyz,q)

       Implicit none

       Integer itp,ierr
       Integer ne
       Integer nno,nel
       Integer, dimension(nel,0:8) :: ien
       Real*8, dimension(nno,3) :: xyz
       Real*8, dimension(nno,ne) :: q

       Integer n
       Integer  tecini,tecdat,teczne,tecnod,tecfil,tecend
       Integer,allocatable,dimension(:,:) :: icon

       Integer debug,visdouble,disdouble
       Character*1 NULCHAR


c
c *** setup futility contants
c
       debug   = 0
       visdouble = 0
       disdouble = 1
       NULCHAR = char(0)

c
c *** create the .plt file
c
       ierr = TECINI('flowfield'//NULCHAR,
     &               'x y z q1 q2'//NULCHAR,
     &               'output.plt'//NULCHAR,
     &               '.'//NULCHAR,
     &               debug,visdouble)

c
c *** make sure your are working on the first open file
c
       ierr = TECFIL(1)
c
c *** Set up the zone and declare it as finite element data
c
       ierr = TECZNE('Flowfield'//NULCHAR,
     &               nno,
     &               nel,
     &               3,   ! Brick
     &               'FEBLOCK'//NULCHAR,
     &               NULCHAR)

c
c *** dump node data sequentially, grid first, then field variables
c
       ierr = TECDAT(nno,xyz(1,1),1)
       ierr = TECDAT(nno,xyz(1,2),1)
       ierr = TECDAT(nno,xyz(1,3),1)
       do n = 1,ne
          ierr = TECDAT(nno,q(1,n),1)
       enddo
c
c *** allocate memory for and assemble connectivity data
c
       allocate(icon(8,nel))

       do n = 1,nel
          Select Case(ien(n,0))

          Case(4)
             icon(1:3,n) = ien(n,1:3)
             icon(4,n)   = ien(n,3)
             icon(5,n)   = ien(n,4)
             icon(6,n)   = ien(n,4)
             icon(7,n)   = ien(n,4)
             icon(8,n)   = ien(n,4)

          Case(5)
             icon(1:4,n) = ien(n,1:4)
             icon(5,n)   = ien(n,5)
             icon(6,n)   = ien(n,5)
             icon(7,n)   = ien(n,5)
             icon(8,n)   = ien(n,5)

          Case(6)
             icon(1:3,n) = ien(n,1:3)
             icon(4,n)   = ien(n,3)
             icon(5:7,n) = ien(n,4:6)
             icon(8,n)   = ien(n,6)
          Case(8)
             icon(1:8,n) = ien(n,1:8)
          End Select
       enddo
c
c *** dump node connectivity data and deallocate memory
c
       ierr = TECNOD(icon)

       deallocate(icon)
c
c *** close the working file(s)
c
       ierr = TECEND()

       return
       end


Now let's disect it piece by piece. Notice that in this example I have hardwired the subroutine to use 2 variables only! But I have allowed for a "variable number of variables" to be used. We will see how this can be changed. Here we go:


c
c *** create the .plt file
c
       ierr = TECINI('flowfield'//NULCHAR,
     &               'x y z q1 q2'//NULCHAR,
     &               'output.plt'//NULCHAR,
     &               '.'//NULCHAR,
     &               debug,visdouble)

In this part I initialize the file by giving a filename, default path, a name for the dataset and a string of 5 (five only!) variables. This is how I hardwired the number of variables -- I have assumed that we have 3D data (x,y,z) and q1 q2 as field variables.

c
c *** make sure your are working on the first open file
c
       ierr = TECFIL(1)

Remember what I said about the "stateness" of the tecio.a library!

c
c *** Set up the zone and declare it as finite element data
c
       ierr = TECZNE('Flowfield'//NULCHAR,
     &               nno,
     &               nel,
     &               3,   ! Brick
     &               'FEBLOCK'//NULCHAR,
     &               NULCHAR)

Now tecio.a knows that we will be giving out unstructured data in bricks. How? Notice the FEBLOCK statement and also notice the "3" that is in there instead of "kmax." (Yes, I know there is some redundancy with the FEBLOCK thing, but I can live with it and it is not my fault.)

c
c *** dump node data sequentially, grid first, then field variables
c
       ierr = TECDAT(nno,xyz(1,1),1)
       ierr = TECDAT(nno,xyz(1,2),1)
       ierr = TECDAT(nno,xyz(1,3),1)
       do n = 1,ne
          ierr = TECDAT(nno,q(1,n),1)
       enddo

It does exactly what it claims; dumps streams of variables corresponding to nodes 1,2,3,......,nno for each of the x,y,z and q1, q2 variables. Be careful here that there is a loop of the variable "n" that can be anything that is passed to the subroutine, but tecio.a knows that we will be giving it a maximum of 5 variables.

Here comes the tricky part. In the following fragment of FORTRAN code we provide tecio.a with the node connectivity for bricks. WIth this example I am illustrating how to use mixed element types and output them in the same zone. You simply create only bricks, but you have duplicate node numbers in the "per-element" connectivity. Notice that I have an original connectivity array with an extra slot (the zero slot) which holds information for the element type. An example would be something like this:

...
4    158   234   235   602    -1    -1    -1    -1
8    162   345   346   347   348   447   123   512
8    174   654   345   657   658   659   732   245
5     66   100   123   432   112    -1    -1    -1
6    266   569   246   245   123   987    -1    -1
...
In this example, the element types in the order they appear are tetrahedron, brick, brick, pyramid, prism. The numbers that are "-1" are obviously _invalid_ because you may not have a negative number correspond to any node -- unless you have some specific reason to have this in your data structure and in which case you will have to deal with it. In this case the "-1" indicates that the numbers in those slots of memory are meaningless.

c
c *** allocate memory for and assemble connectivity data
c
       allocate(icon(8,nel))

       do n = 1,nel
          Select Case(ien(n,0))

          Case(4)
             icon(1:3,n) = ien(n,1:3)
             icon(4,n)   = ien(n,3)
             icon(5,n)   = ien(n,4)
             icon(6,n)   = ien(n,4)
             icon(7,n)   = ien(n,4)
             icon(8,n)   = ien(n,4)

          Case(5)
             icon(1:4,n) = ien(n,1:4)
             icon(5,n)   = ien(n,5)
             icon(6,n)   = ien(n,5)
             icon(7,n)   = ien(n,5)
             icon(8,n)   = ien(n,5)

          Case(6)
             icon(1:3,n) = ien(n,1:3)
             icon(4,n)   = ien(n,3)
             icon(5:7,n) = ien(n,4:6)
             icon(8,n)   = ien(n,6)
          Case(8)
             icon(1:8,n) = ien(n,1:8)
          End Select
       enddo
c
c *** dump node connectivity data and deallocate memory
c
       ierr = TECNOD(icon)

       deallocate(icon)

In the above fragment of code we allocate a new array which has numbers assigned to its memory slots based on how we want to represent the data. We are clever in collapsing nodes on each brick that is sent to tecio.a such that we do not run into visualization problems. Notice how the treatment of the prism and the brick are similar; the former has two pairs collapsed while the latter has four nodes collapsed into one. You can think of this visually and you will realize that it makes a lot of sense! Finally, we dump the node connectivity with a single call.

I hope this is helpful and not too confusing. Again, you will have to do some more sophisticated programming in order to get a variable number of field variables and the corresponding string for the variable labels to work together in a general way, but this is beyond the scope of this tutorial.

Your feedback and possible corrections to this tutorial are always welcome.
 

Copyright © 2004-2017 Ioannis Nompelis

Last updated: (08.11.2017) by I.N.