Search code examples
fortranabaqus

Read values into a Fortran array from the next line after a string match


I am trying to parse an Abaqus input file and read the desired nodes into an array. I first try to compare a string and if it matches then I want to read in an unknown number of integer values from the next line.

An example of the text file is shown below. I want to read the values under nset=bf1:

** PARTS

*Part, name=Beam

*Nset, nset=bf1, instance=Beam-1

    1,    2,    5,   43,   45,   48,   49,   50,  105,  106, 1189, 1190, 1191, 1192, 1193, 1194

    5275, 5276, 5277, 5278

*Elset, elset=_Surf-3_SNEG, internal, instance=Beam-1

    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,   16

    17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,   32

What I am doing right now is this

program nset_parser
    implicit none

    integer, parameter :: rk = selected_real_kind(10,40)
    real(kind=rk), dimension(:), allocatable :: values
    integer ::  ios,l,val, n, nlines
    character(len=80) :: file_name = 'code_check.inp'
    character (len=*), parameter :: search_str = "bf1"
    character(len=5096) :: input_line, line
    character(len=10) :: keyword, get_keyword
    logical :: h_nset=.false.

     
    l = len(search_str)
     
    open(unit=10, file=file_name, status="old", action='read') ! open file
    do 
     read(10, '(a)',iostat=ios) input_line   ! read the lines
     if (ios /=0) exit
     input_line = adjustl(input_line)   
    
     if (input_line(1:1) == '*' .and. input_line(2:2) /= '*') then  ! check if it is a comment line or data line
      keyword=get_keyword(input_line)
      if (trim(keyword) == 'Nset') then
          read(input_line,'(a)') line
          val = index(line, search_str)
          if (val /= 0) then
              h_nset=.true.
              print*, line(val:val+l-1)
          end if
      end if
     end if
    end do

  close(10)
  end program nset_parser

but after this I am sort of stumped.


Solution

  • I've rewritten my answer to be more general. This code will handle multiple lines of integers between a line with "nset=bf1" and any other line beginning '*'.

    Because the lines of ints can vary in length, a 2D int array is inappropriate. Instead I am storing all the ints in a 1D array, and storing the array index of each new line.

    To avoid reading everything twice, I am increasing the size of arrays on the fly if necessary.

    Note that if your compiler doesn't support MOVE_ALLOC (Fortran 2003), then you'll just need to replace each call with a 'deallocate, allocate, deallocate' set.

    program nset_parser
        implicit none
    
        INTEGER ::  ios,l,val, i, n, nlines, comma_idx, next_idx
        INTEGER, ALLOCATABLE :: my_ints(:), line_starts(:), work_ints(:)
        character(len=80) :: file_name = 'code_check.inp'
        character (len=*), parameter :: search_str = "bf1"
        character(len=5096) :: input_line
        character(len=10) :: keyword
        logical :: h_nset=.false.
    
        l = len(search_str)
    
        ALLOCATE(line_starts(10), my_ints(1000))
        next_idx = 1
        nlines = 0
    
        open(unit=10, file=file_name, status="old", action='read') ! open file
        DO 
    
          ! Read and adjust the line
          read(10, '(a)',iostat=ios) input_line
          if (ios /=0) exit
          input_line = adjustl(input_line)   
    
          ! Ignore blank lines
          IF(LEN(TRIM(input_line))==0) CYCLE
    
          ! This section picks up any line beginning *
          ! and either sets or unsets h_nset
          IF (input_line(1:1) == '*' .AND. input_line(2:2) /= '*') THEN  ! check if it is a comment line or data line
    
            ! Use of keyword below is a little unsafe if
            ! keywords can be longer than 10 chars (the length of 'keyword')
            ! Instead, better to check if input_line(2:5) == Nset
            ! I left it like this in case you want to be able to check multiple
            ! keywords or something.
            comma_idx = INDEX(input_line, ',')
            keyword = input_line(2:comma_idx-1)
    
            IF (TRIM(keyword) == 'Nset') THEN
              val = INDEX(input_line, search_str)
              IF (val /= 0) THEN
                h_nset=.TRUE.  ! Switch on when nset=bf1 line is found
                CYCLE
              END IF
            END IF
            h_nset = .FALSE.  ! Only reach this point if we are in a * line which isn't nset=bf1
          END IF
    
          IF(h_nset) THEN
            n = COUNT(TRANSFER(input_line, 'a', LEN(input_line)) == ",") + 1 !cast to chars and count occurrence of comma
            nlines = nlines + 1
    
            ! Double size of arrays on the fly if necessary
            IF(nlines > SIZE(line_starts)) THEN
              ALLOCATE(work_ints(nlines * 2))
              work_ints(1:SIZE(line_starts)) = line_starts
              CALL MOVE_ALLOC(work_ints, line_starts)
            END IF
            IF(next_idx+n > SIZE(my_ints)) THEN
              ALLOCATE(work_ints(2*SIZE(my_ints)))
              work_ints(1:SIZE(my_ints)) = my_ints
              CALL MOVE_ALLOC(work_ints, my_ints)
            END IF
    
            line_starts(nlines) = next_idx
            READ(input_line, *) my_ints(next_idx:next_idx+n-1)
            next_idx = next_idx + n
          END IF
        END DO
        close(10)
    
        ! This helps with the printing below
        line_starts(nlines+1) = line_starts(nlines) + n
    
        DO i=1, nlines
          PRINT *, my_ints(line_starts(i):line_starts(i+1)-1)
        END DO
      end program nset_parser