Fortran read from .dat file: date, time, 42 x real_values - fortran

I have some problem with reading a file and printing in another file.
The problem is that if I use formatted file, someowhere on the second line, the format is not good, since there is a minus on some values.
So depending on the line, the format is changing. The two lines are just an example. But the date and time will remain the same format always.
My file is like this:
22/03/2015 00:00:43 2.50E+4 1.66E+3 2.22E+3 2.70E+3 3.16E+3 3.83E+3 3.58E+3 3.69E+3 3.64E+3 2.45E+2 1.67E+2 3.64E+1 1.56E+1 7.47E+0 3.21E+0 2.09E+0 1.48E+0 1.39E+0 7.75E-1 7.20E-1 6.05E-1 6.45E-1 4.90E-1 3.65E-1 3.30E-1 1.46E-1 1.09E-1 6.10E-2 4.90E-2 4.10E-2 1.00E-2 9.00E-3 4.00E-3 1.00E-3 0.00E+0 0.00E+0 0.00E+0 0.00E+0 0.00E+0 0.00E+0 0.00E+0 0.00E+0
22/03/2015 16:14:35 1.26E+4 8.36E+2 1.12E+3 1.36E+3 1.59E+3 1.92E+3 1.80E+3 1.85E+3 1.83E+3 1.23E+2 8.36E+1 2.05E+1 9.87E+0 5.02E+0 2.84E+0 1.88E+0 1.49E+0 1.72E+0 1.06E+0 8.34E-1 8.22E-1 5.72E-1 4.61E-1 2.22E-1 2.78E-1 3.59E-1 2.40E-1 1.40E-1 8.90E-2 4.80E-2 4.60E-2 2.80E-2 1.60E-2 6.00E-3 5.00E-3 -1.00E-3 1.00E-3 3.00E-3 0.00E+0 0.00E+0 2.00E-3 1.00E-3
And the code:
PROGRAM LECT
REAL tot_count(42)
CHARACTER (10) :: stringdate, date
CHARACTER (8) :: stringtime,time
CHARACTER (2) :: day, hour
CHARACTER (2) :: month,minutes, seconds
CHARACTER (4) :: year
OPEN(2,FILE='date.dat')
OPEN(6,FILE='test.txt', STATUS='UNKNOWN')
1 READ(2,11,advance='yes',end=9)
& stringdate, stringtime, (tot_count(I),I=1,42)
day=stringdate(1:2)
month=stringdate(4:5)
year=stringdate(7:10)
date=day//'/'//month//'/'//year
hour=stringtime(1:2)
minutes=stringtime(4:5)
seconds=stringtime(7:8)
time=hour//':'//minutes//':'//seconds
WRITE(6,*) date,' ', time, (tot_count(I),I=1,42)
GOTO 1
11 FORMAT(A10,1X,A8,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X)
9 CLOSE(2)
CLOSE(6)
END

I would use list-directed input for this rather than hardcoded formats. First, I would change
11 FORMAT(A10,1X,A8,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,F7.2,1X,
& F7.2,1X,F7.2,1X)
into
11 format(A10,1X,A8,1X)
This is just enough format to read your line headers to grab the date and time. Next, I would change
1 READ(2,11,advance='yes',end=9)
& stringdate, stringtime, (tot_count(I),I=1,42)
into
1 READ(2,11,advance='no',end=9) stringdate, stringtime
read(2,*,end=9) (tot_count(I),I=1,42)
This will read the first two fields using the format specifier and not advance the line and then read the next 42 values using list-directed input. Making these two changes and using your input data file produces the output:
22/03/2015 00:00:43 25000.0000 1660.00000 2220.00000 2700.00000 3160.00000 3830.00000 3580.00000 3690.00000 3640.00000 245.000000 167.000000 36.4000015 15.6000004 7.46999979 3.21000004 2.08999991 1.48000002 1.38999999 0.774999976 0.720000029 0.605000019 0.644999981 0.490000010 0.365000010 0.330000013 0.145999998 0.108999997 6.10000007E-02 4.89999987E-02 4.10000011E-02 9.99999978E-03 8.99999961E-03 4.00000019E-03 1.00000005E-03 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000
22/03/2015 16:14:35 12600.0000 836.000000 1120.00000 1360.00000 1590.00000 1920.00000 1800.00000 1850.00000 1830.00000 123.000000 83.5999985 20.5000000 9.86999989 5.01999998 2.83999991 1.88000000 1.49000001 1.72000003 1.05999994 0.833999991 0.822000027 0.572000027 0.460999995 0.222000003 0.277999997 0.358999997 0.239999995 0.140000001 8.90000015E-02 4.80000004E-02 4.60000001E-02 2.80000009E-02 1.60000008E-02 6.00000005E-03 4.99999989E-03 -1.00000005E-03 1.00000005E-03 3.00000003E-03 0.00000000 0.00000000 2.00000009E-03 1.00000005E-03

As you've noticed precisely-specifying input formats in your read statements can make them rather fragile. Fortran's * list-directed reading would be ideal, let the compiler and run-time take care of the variability in the formatting of the real values, but those pesky dates and times would muck that up.
Hmmm, here's an idea. Suppose that your lines are a maximum of 256 characters long
character(len=256) :: aline
...
read(2,*) aline
...
date = aline(1:10)
time = aline(12:19)
! decompose date and time into their elements if you want to
read(aline(20:256),*) tot_count
I've not tested this so there are almost certainly minor syntactical errors, but the approach is sound enough.

Related

Reading three specific numbers in a text file and writing them out

I have a problem how to print only specific three numbers, which are in a file with no format. I have no idea how to read it and print because if I use read from higher i, it does not start reading e.g. for i = 4, the line 4. I need only numbers 88.98, 65.50, and 30.
text
678 people
450 girls
22 old people
0 cats
0 dogs
4 girls blond
1 boy blond
1 old man
0 88.9814 xo xi
0 65.508 yo yi
0 30 zo zi
I tried this, but this is not working at all.
program souradnice
implicit none
integer :: i, k
character*100 :: yo, zo, line, name, text
real :: xo
open(10,file="text.dat", status='old')
do i=20,20
read(10,fmt='(a)') line
read(unit=line, fmt='(a100)') text
if(name=="xo") then
print *, trim(text)
endif
enddo
close(10)
end program souradnice
You need to read the whole file line by line, and check each line to see if it's the one you want, e.g. by using the index intrinsic. For example,
program souradnice
implicit none
character(100) :: line
character(5) :: matches(3)
real :: numbers(3)
character(10) :: dummy
integer :: i, ierr
! Substrings to match to find the relevant lines
matches = ["xo xi", "yo yi", "zo zi"]
open(10,file="text.dat", status='old')
do
! Read a line from the file, and exit the loop if the file end is reached.
read(10,fmt='(a)',iostat=ierr) line
if (ierr<0) then
exit
endif
do i=1,3
! Check if `line` matches any of the i'th line we want.
if (index(line, matches(i))>0) then
! If it matches, read the relevant number into `numbers`.
read(line,*) dummy, numbers(i)
endif
enddo
enddo
write(*,*) numbers
end program

Fortran: Reading and printing 2D array from text file

I am trying to read a 3x3 array and print it out but I am getting an end of line error:
The text file contains the following array:
1 2 3
4 5 6
7 8 9
Here is my code:
program myfile
implicit none
! Declare Variables
integer i,j
!real, dimension(1:3,1:3) :: A
integer, parameter :: M = 3, N =3
real, dimension(1:M,1:N) :: A
! Open and read data
open(unit=10, file = 'test_file_cols.txt', status = 'old')
do i =1,M
do j =1,N
read(unit=10,FMT=*) A(i,j)
print *,A(i,j)
end do
end do
end program myfile
The error I am getting is below:
1.000000
4.000000
7.000000
forrtl: severe (24): end-of-file during read, unit 10, file C:\Users\M42141\Documents\mean_flow_file\test_file_cols.txt
As discussed briefly in the comments by default all I/O in Fortran is record based. This is true for both formatted and unformatted files. What happens is the file is viewed as a set of records - and you can think of a record as a line in the file. Now these lines may be very, very long, especially in an unformatted files, but the default Fortran I/O methodology still views it as a set of lines.
Now the important thing is that by default every time you perform an I/O statement (read, write, print) the last thing it does is move from the record it is on to the next record - a write statement will write an end of record marker. This is why you automatically get a newline after a write statement, but it also means that for a read statement any remaining data in the record (on the line) will get skipped over. This is what is happening to you. The first read reads record 1, and so you get 1.0, and then moves to record 2. Your program then reads record 2 and so you get 4.0, and it automatically moves to record 3. this is then read (9.0) and the file pointer moves onto record 4. You then try to read this, but there isn't a record 4, so you get an end of file error.
Record structure is a bit strange when you first encounter it, but when you get used to it it is very powerful and convenient - I'll show an example below, and another one might be that you could leave a comment at the end of each line saying what it does, the end of the read statement will mean you move to the next record, so skipping the comment and needing to take no special action in you code to deal with such a case.
Anyway how to solve your case. Three possible ways
Read a whole record at a time - the comment suggests an implied do loop but I think in this case an array section is much easier and more intuitive
You can simply read the whole array in one go. This works because when a read statement finishes a record and finds it still "needs" more data it will carry onto the next record and keep reading. But note the end of line comment idea won't work here - can you work out why?
Non-Advancing I/O. I don't recommend this at all in this case, but for completeness this allows you to perform a read or write without moving onto the next record
There may be others, you could probably use so called stream I/O but personally I prefer record based whenever possible, I find it more convenient and powerful. Anyway here is a program illustrating the 3 methods. Note I have also changed your input file, getting the original to work with non-advancing I/O is a pain, but not the other 2 - another reason not to use it here.
ian#eris:~/work/stack$ cat readit.f90
Program readit
Implicit None
Real, Dimension( 1:3, 1:3 ) :: a
Integer :: i, j
! one line per read
Write( *, * ) 'Line at a time'
Open( 10, file = 'in' )
Do i = 1, 3
Read ( 10, * ) a( i, : )
Write( *, * ) a( i, : )
End Do
Close( 10 )
! All in one go
Write( *, * ) 'All in one go'
Open( 10, file = 'in' )
Read ( 10, * ) a
Write( *, * ) a
Close( 10 )
! Non advancing I/O
Write( *, * ) 'Non-advancing'
Open( 10, file = 'in' )
Do i = 1, 3
Do j = 1, 3
! Non advancing I/O requires a 'proper' format
Read ( 10, '( f3.1, 1x )', Advance = 'No' ) a( i, j )
Write( *, '( f3.1, 1x )', Advance = 'No' ) a( i, j )
End Do
! Move to next records (lines)
Read ( 10, * )
Write( *, * )
End Do
Close( 10 )
End Program readit
ian#eris:~/work/stack$ gfortran-8 -Wall -Wextra -pedantic -std=f2008 -fcheck=all -O readit.f90
ian#eris:~/work/stack$ cat in
1.0 2.0 3.00
4.0 5.0 6.00
7.0 8.0 9.00
ian#eris:~/work/stack$ ./a.out
Line at a time
1.00000000 2.00000000 3.00000000
4.00000000 5.00000000 6.00000000
7.00000000 8.00000000 9.00000000
All in one go
1.00000000 2.00000000 3.00000000 4.00000000 5.00000000 6.00000000 7.00000000 8.00000000 9.00000000
Non-advancing
1.0 2.0 3.0
4.0 5.0 6.0
7.0 8.0 9.0

Call a subroutine for a list of points instead of a single point

I have a certain piece of code in fortran. The code takes 'pq' as an input from the user and is a single point. Instead of doing this I want to read a set of points 'pq' from a file points.txt and run it for those number of points instead of just one single user input. Is it possible? The code is as follows:
program prop
use module
implicit none
character(len=80) :: ErrorMsg
character(2) :: xy
real(8) :: Conc(20) = 0.d0
character(len=20) :: fn, fl
real(8) :: Mmolar, Tcritical, Pcritical, Tmininimum, Tmaximum, x, y
call Init_module()
write(*,*) 'Insert the gas name:'
read(*,*) fn
write(*,*) 'Insert the gas library:'
read(*,*) fl
write(*,*) 'Insert the copule pq:'
read(*,*) pq
write(*,*) 'Insert the value of ', pq(1:1)
read(*,*) x
write(*,*) 'Insert the value of ', pq(2:2)
read(*,*) y
write(*,*) 'Pres = ', Pres( pq, x, y, ErrorMsg)
write(*,*) 'Temp = ', Temperature( pq, x, y, ErrorMsg)
call ReleaseObjects()
end program prop
Instead of reading pq as a single point x,y from the user in the above code, I want to read a set of points from file.txt, for example 50 points and then run subroutines Pres and Temperature.
Each line of the file contains one point x,y and x and y in each line are separated by a few space characters.
The first few lines of file.txt are:
Ts
500
0.04781564 159.81587875
0.20396084 165.46398084
0.08159885 166.81382894
0.03879184 164.17497877
0.12585959 165.37000305
0.09895530 165.95997769
0.10389518 170.74235496
It must be noted that the length and the sign of the floating numbers can vary. The file.txt is originally written through python with the formatting for x, y being '%-12.8f %-12.8f\n'%. I have the following code to try and read the file but am not able to read from the 3rd line onwards:
real, allocatable :: x(:),y(:)
integer :: np
open(12,file=trim('file.txt'),status='old', &
access='sequential', form='formatted', action='read' )
read(12,*)pq
write(*,*)'pq:', pq
read(12,*)np
write(*,*)'number of points:',np
allocate (x(np))
allocate (y(np))
do i=1,np
read(12,*)x(i),y(i)
write(*,*)x(i),y(i)
enddo
Instead of using the READ statement with the asterisk (*) as the first argument asking for an user input, use a file identifier. You need to OPEN your file containing the set of points, assuming it is ASCII :
OPEN(UNIT=10,FILE=file.txt,ACTION='read',STATUS='old')
I think the arguments of this command are quite explanatory.
Then assuming your file contains multiple lines with x and y values, you can read each line of your file by doing :
READ(10,*) x,y
If you have multiple points to read, just use a DO if you know the number of points to read, a DO WHILE otherwise. To take your example with 50 points, something like this should work :
OPEN(UNIT=10,FILE=file.txt,ACTION='read',STATUS='old') ! Open file
DO i=1,50
READ(10,*) x,y
write(*,*) 'Pres = ', Pres( pq, x, y, ErrorMsg)
write(*,*) 'Temp = ', Temperature( pq, x, y, ErrorMsg)
END DO
CLOSE(10) ! Close file
EDIT
Your suggestion is almost correct. You forgot to declare pq as a character(len=2). You should not have been able to pass line 1 because of that.
As I said, there is a space separator that is naturally treated by a asterisk as a format. Anyway, if you want to exactly match the format, use the same with which you wrote your data. Reading your format Python, I assume you wrote two floats with a space separator, and indeed if you count the number of character of your digits :
0.04781564 159.81587875
^^^^^^^^^^^^|^^^^^^^^^^^^
1 12|1 12
|
space
which gives the following format in Fortran :
read(12,'(f12.8,1X,f12.8)') x(i),y(i)
X means a space separator in Fortran formats.
Then you can write you data onscreen with the same format to check :
write(*,'(f12.8,1X,f12.8)') x(i),y(i)
It gives :
pq:Ts
number of points: 500
0.04781564 159.81587219
0.20396084 165.46397400
0.08159885 166.81382751
0.03879184 164.17497253
0.12585959 165.37001038
0.09895530 165.95997620
0.10389518 170.74235535
You may have noticed that you lost precision on the last digits. It is because you have declared a simple real (4 bytes). Switch your real to 8 bytes with real(kind=8) or real*8 according to your compiler (be aware, not the right way to do it, not portable but sufficient in your case)
Do not forget to close your file when you are done dealing with it :
close(12)

Fortran-- reading columns with specific time stamps

I have this data set and I would like to read it using Fortran and apply a few operations . I have presented the data set below. Here's a short description of the data set. The data set starts of with a time stamp and the subsequent lines (around 60 or 120)are the data points recorded at that time, which is again followed by a time stamp and data and so on so forth. Each file has around 30 observations with 15 different time stamps(i.e two adjacent observations have the same time stamps). I now want to read this file and perform operations such as addition or subtraction of columns on the data points with the same time stamps.
Here the '6:31: 5', '6:27: 9' are the time stamps followed by the data.
Please help as I am unable to figure out a way to do this.
Thank you very much.
Your question leaves a lot of ambiguity. The way I understand it is that you want to read a file where the contents of a line might be one of two different things: a value, or a time stamp.
And you don't know which one it is before you read it.
The best way, I think, is to first read the line into a string.
character(len=20) :: line
read(1001, '(A)') line
Then, based on the first character in that string, you can deduce which one it is.
if (line(1:1) == "'") then
! It's a time stamp
else
! It's a new value
end if
If it's a value, you can read it straight out of the line variable:
read(line, *) val
Reading the time stamp is more complicated, because I don't quite know where the colons are inside it. If the hours are all single digits, you could get away with something like this:
read(line, '(X, I1, X, I2, X, I2)') hour, minute, second
But because there are hours that can't be written with a single digit, I rather doubt that.
So I suggest that you create a subroutine that uses the INDEX function to locate the colons, and then slice the line to read each value separately.
hm = index(line, ':', back=.FALSE.) ! hour-minute separator
ms = index(line, ':', back=.TRUE.) ! minute-second separator
read(line(2:hm-1), *) hour
read(line(hm+1:ms-1), *) minute
read(line(ms+1:), *) second
All put together:
program timestamp
implicit none
integer, parameter :: runit = 1001
integer :: io_err
character(len=20) :: line
real :: val
integer :: hour, minute, second
open(unit=runit, file='data.txt', action='READ', status='OLD', iostat=io_err)
if (io_err /= 0) then
write(*, *) "Unable to open file"
stop 1
end if
do
read(runit, '(A)', iostat=io_err) line
if (io_err == -1) then
write(*, *) "End of file"
exit
end if
if (line(1:1) == "'") then
call read_timestamp(line, hour, minute, second)
write(*, '(A, 2(I2.2, ":"), I2.2)') "New timestamp: ", hour, minute, second
else
read(line, *) val
write(*, *) "Data: ", val
end if
end do
close(runit)
contains
subroutine read_timestamp(line, hour, minute, second)
implicit none
character(len=*), intent(in) :: line
integer, intent(out) :: hour, minute, second
integer :: hm, ms ! Indices of separators
hm = index(line, ':', back=.FALSE.)
ms = index(line, ':', back=.TRUE.)
read(line(2:hm-1), *) hour
read(line(hm+1:ms-1), *) minute
read(line(ms+1:), *) second
return
end subroutine read_timestamp
end program timestamp

Adapting a Fortran Code to write a file, run an executable and read in arrays from a file

I am new to Fortran but I am trying to adapt a Fortran code and I am having trouble doing something which I think is probably quite simple.
I want to adapt a Fortran file called original.f so that it makes an input file called input.inp and populates it with 4 integers calculated earlier in original.f so that input.inp looks like, for example:
&input
A = 1
B = 2
C = 3
D = 4
&end
I know how to write this format:
OPEN(UNIT=10,FILE='input.inp')
WRITE (10,00001) 1,2,3,4
...
...
...
00001 Format (/2x,'&input',
& /2x,'A = ',i4,
& /2x,'B = ',i4,
& /2x,'C = ',i4,
& /2x,'D = ',i4,
& /2x,'&end')
(or something like this that I can fiddle with when I get it working) but I am not sure how to create the input.inp file write this into it and then use this input file.
The input file needs to be used to run an executable called "exec". I would run this in bash as:
./exec < input.inp > output.out
Where output.out contains two arrays called eg(11) and ai(11,6,2) (with dimensions given) like:
eg(1)= 1
eg(2)= 2
...
...
...
eg(11)= 11
ai(1,1,1)= 111
ai(1,2,1)= 121
...
...
...
ai(11,6,2)=1162
Finally I need to read these inputs back into original.f so that they can be used further down in file. I have defined these arrays at the beginning of original.f as:
COMMON /DATA / eg(11),ai(11,6,2)
But I am not sure of the Fortran to read data line by linw from output.out to populate these arrays.
Any help for any of the stages in this process would be hugely appreciated.
Thank you very much
James
Since you have shown how you create the input file, I assume the question is how to read it. The code shows how "a" and "b" can be read from successive lines after skipping the first line. On Windows, if the resulting executable is a.exe, the commands a.exe < data.txt or type data.txt | a.exe will read from data.txt.
program xread
implicit none
character (len=10) :: words(3)
integer, parameter :: iu = 5 ! assuming unit 5 is standard input
integer :: a,b
read (iu,*) ! skip line with &input
read (iu,*) words ! read "a", "=", and "1" into 3 strings
read (words(3),*) a ! read integer from 3rd string
read (iu,*) words ! read "b", "=", and "1" into 3 strings
read (words(3),*) b ! read integer from 3rd string
print*,"a =",a," b =",b
end program xread
If I understand the expanded question correctly, you have to work with an output file, produced by some other code you did not write, with lines like eg(1) = ....
For the simplest case where you know the number of elements and their ordering beforehand, you can simply search each line for the equals sign from behind:
program readme
implicit none
character(100) :: buffer
integer :: i, j, k, pos, eg(11), ai(11,6,2)
do i = 1,11
read*, buffer
pos = index(buffer, '=', back = .true.)
read(buffer(pos+1:), *) eg(i)
enddo
! I have assumed an arbitrary ordering here
do k = 1,2
do i = 1,11
do j = 1,6
read*, buffer
pos = index(buffer, '=', back = .true.)
read(buffer(pos+1:), *) ai(i,j,k)
enddo
enddo
enddo
end program
Assuming here for simplicity that the data are provided to standard input.