tags:

views:

398

answers:

4

Purpose: Create a program that takes two separate files, opens and reads them, assigns their contents to arrays, do some math with those arrays, create a new array with product numbers, print to a new file. Simple enough right?

My input files have comment characters at the beginning. One trouble is, they are '#' which are comment characters for most plotting programs, but not FORTRAN. What is a simple way to tell the computer not to look at these characters? Since I have no previous FORTRAN experience, I am plowing through this with two test files. Here is what I have so far:

PROGRAM gain
  IMPLICIT NONE
  REAL, DIMENSION (1:4, 1:8)     :: X, Y, Z
  OPEN(1, FILE='test.out', &
        STATUS='OLD', ACTION='READ')            ! opens the first file
  READ(1,*), X
  OPEN(2, FILE='test2.out', &
    STATUS='OLD', ACTION='READ')            ! opens the second file
  READ(2,*), Y
  PRINT*, X, Y

  Z = X*Y
!  PRINT*, Z
  OPEN(3, FILE='test3.out', STATUS='NEW', ACTION='WRITE')   !creates a new file
  WRITE(3,*), Z
  CLOSE(1)
  CLOSE(2)
  CLOSE(3)
END PROGRAM

PS. Please do not overwhelm me with a bunch of code monkey gobblety gook. I am a total programming novice. I do not understand all the lingo, that is why I came here instead of searching for help in existing websites. Thanks.

+1  A: 

Write a subroutine that puts this logic into one spot for you so you can call it for both files. You'll need to read each line as a string and add an IF test to check whether a given line starts with a "#" or not. If the line starts with a "#", just read the next line. If not, convert the string to a value and add it to the array of values you're returning.

duffymo
A: 

Thanks for the suggestions. I understand the theory of what to do, it is how to write the correct commands that trips me up. Like...learning a new language! Lol. I wrote the subroutine, but I get this error: Unexpected array reference

REAL FUNCTION myfile(unit, file, rows, columns)
  IMPLICIT NONE
  INTEGER, INTENT(IN)               :: unit, rows, columns
  CHARACTER(LEN=*)      :: file
  REAL, DIMENSION (1:columns, 1:rows)  ::X
  OPEN(unit, FILE='file', STATUS='OLD', ACTION='READ')
  READ(unit,*), X
  PRINT*, X         
  CLOSE(unit)
END FUNCTION myfile

PROGRAM gain
  myfile(1, test.out, 8, 4)
END PROGRAM

Why?

lollygagger
It would be better to edit your original question to include the new information, rather than post it down here as an answer where people aren't as likely to find it.
Jim Lewis
A: 

I'm not real familiar with anything beyond FORTRAN 77 but here are a few pointers (and a working version of what you posted in your answer). First the working code (I added line numbers):

1   REAL FUNCTION myfile(unit, file, rows, columns)
2   IMPLICIT NONE
3   INTEGER, INTENT(IN) :: unit, rows, columns
4   CHARACTER(LEN=*) :: file
5   REAL, DIMENSION (1:columns, 1:rows) ::X
6   OPEN(unit, FILE=file, STATUS='OLD', ACTION='READ')
7   READ(unit,*), X
8   PRINT*, X         
9   CLOSE(unit)
10  myfile= 0
11  END FUNCTION myfile
12
13  PROGRAM gain
14  errno = myfile(1, "test.out", 8, 4)
15  END PROGRAM

The differences are:

  • line 6 - You needed to remove the quotes around 'file' in the FILE='file' assignment. As written you were using a file named file instead of the name passed in as the file parameter.
  • line 10 - since you declared this as a function you need to assign a return value to the function name before you leave the function. I just assigned it a value of 0 to allow it to compile. I think what you want here is to pass the array you read in from the file back out of the routine. In that case you'll need to modify the type of the function (and assign X to myfile) or pass the array in as a parameter and allow it to be modified in the function. (in the old FORTRAN 77 world this was done with common blocks or pointers to the array, not sure how you do it in later versions of Fortran).
  • line 14 - you need to assign the return value of the function to a variable. At least you did with my gfortran compiler. It wouldn't let me compile the program otherwise.
  • line 14 (again) - the function call needs the name of the file (test.out) in quotes. You had it without quotes and so was having problems (this might be where your array reference error was coming from, I got different errors with my compiler.)

Instead of using a function, you could define this your myfile routine as a subroutine instead. In this case you would definitely need to pass in the array you want filled as a parameter. You wouldn't need line 10 and instead of assigning the return value to a variable in the main program you would 'call' the routine. i.e. line 14 would look like this:

call myfile(1, "test.out",8,4)

EDIT: I posted this and then realized I forgot to answer the original questions. I did so and then for some reason couldn't connect to SO to upload the edits. So here they are finally.

That gets your routine compiling. To actually deal with the comment lines, you have several options (at least these are the ones that come to mind initially). These are in order from simplest/most brittle to more robust/general:

  1. If you know exactly how many comment lines are in the data file (and it is the same for all the files) you could just read in that many lines, throw away what was read in and then read in the array from that point. This will advance you past the comments and then read in the array. However if the number of comment lines is different in the different input files, this will not work. Likewise if it changes in the future it will require changing the code. This option is probably not the best.
  2. Make a pass through the file read a line at a time as a string and checking to see if it starts with a # mark. If so increment a counter. When you find the first non-comment line, stop, reset the file to the beginning and then follow the steps in #1 above where you use the counter value as the number of lines to skip. This is more flexible than #1 in that it can handle an arbitrary number of comment lines but they still have to be at the beginning of the file. Any comments in the middle of the data will mess you up.
  3. Read in each line as a string, look for the # symbol and if it is not there, parse the line and populate the array manually. This is the most complex but gives the most flexibility for the input file format. It allows you to have (and ignore) comments anywhere in the data file.

Which method you choose (and other people may have other suggestions) depends on your particular application. Good luck.

dagorym
Your comments and suggestions are MUCH appreciated. Thanks so much =)
lollygagger
A: 

If you mean that the comments are only at the beginning of the file, it is fairly simple -- no need to count the comment lines or rewind file -- you can read the lines into a string and test whether they are a comment. Then you will eventually encounter a non-comment line. Problem: it will have been read into a string and thus not available for a regular read ... solution ... use "backspace" to unread one record so that you can now use normal file reads to read the rest of the file. A slightly more complicated solution would be necessary if comment lines were interspersed throughout the file -- as already stated, read the lines into a string, then read from the string.

Here is a worked example ... I have assumed that the "#" is in the first column and various other simplifying assumptions. Some recommendations: put your subroutines and functions into a module and "use" that module -- this will allow the compiler to check the interfaces. As you are developing your programs, use as many code checking and warning options as possible -- especially subscript bounds checking -- it will save time in the end.

P.S. It is officially "Fortran" since Fortran 90 -- it was "FORTRAN" for FORTRAN 77 and earlier.

module read_file_module

   implicit none

contains

   subroutine read_file (UnitNum, FileName, NumRows, NumCols, Array )

      integer, intent (in) :: UnitNum
      character (len=*), intent (in) :: FileName
      integer, intent (in) :: NumRows, NumCols
      real, dimension (1:NumRows, 1:NumCols), intent (out) :: Array

      character (len=300) :: line
      integer :: i, j

      open (unit=UnitNum, file=FileName, status='old', action='read' )

      ReadComments: do
         read (UnitNum, '(A)') line
         if (line (1:1) /= "#") exit ReadComments
      end do ReadComments

      backspace (UnitNum)

      do i=1, NumRows
         read (UnitNum, *) (Array (i, j), j=1,NumCols)
      end do

      close (UnitNum)

      return

   end subroutine read_file

end module read_file_module 




program test_prog

use read_file_module

implicit none

real, dimension (1:8, 1:4) :: Array
integer :: i, j

call read_file (66, 'TestFile.txt', 8, 4, Array)

do i=1, 8
  write (*, '( 4(2X, F7.3) )' ) (Array (i, j), j=1,4)
end do

end program test_prog

And some test data to show how flexible the input data can be:

#  comment one
#  comment two
1.1   2.0  3.0  4.1
1.2   2.0  3.0  4.2
1.3   2.0  3.0  4.3
1.4   
  2.0  3.0  4.4
1.5   2.0  3.0  4.5
1.6   2.0  3.0  4.6


1.7   2.0  3.0  4.7
1.8   2.0  3.0  4.8
M. S. B.
Thanks for all of your suggestions, and getting me started. It is very kind of you to help a lowly beginner like me ;)Can you please explain this line in more detail? read (UnitNum, *) (Array (i, j), j=1,NumCols)I understand the read statement, and that the second part is the iolist. This in not well defined in any documentation I have found. Thanks in advance.
lollygagger