Project description:
I have the coordinates of McDonalds in cities and the coordinates of city centers. They are divided to text files by city. For example,
=== atlanta_mc.txt === === houston_mc.txt === ... (for 200 cities)
[latitude] [longitude] [latitude] [longitude] <-- The headers are not written in the files.
33.5431140 -84.5766910 29.8044570 -95.3990780
34.0489350 -84.0928960 30.0051170 -95.2834550
33.7853660 -84.1018980 29.7371140 -95.5352550
34.0903210 -84.2040180 29.7280160 -95.4188230
34.1606520 -84.1781010 29.7851700 -95.3609770
.... ....
My goal is to calculate the distance between the city center and each McDonald for each city. The city center data file has [cityname],[latitude],[longitude] fiels. It looks like:
== city_centers.txt ==
[city name] [latitude] [longitude] <-- The headers are not written in the file.
atlanta 33.79526 -84.326528
la 34.057453 -118.413842
ny 40.759347 -73.980202
houston 29.733215 -95.430824
....
(for 200 cities in the US.)
Fortran code:
For one city (say, Atlanta), I can do the job as follows:
program mcdonalds
implicit none
interface
function distkm(deglat1,deglon1,deglat2,deglon2)
real :: distkm
real, intent(in) :: deglat1,deglon1,deglat2,deglon2
end function distkm
end interface
integer, parameter :: maxnr = 200
integer :: nr, i, j, ios
character(len=1) :: junkfornr
real, dimension(:), allocatable :: lat, lon
! Obtain the total number of observations
nr=0
open(10, file='atlanta_mc.txt', status='old')
do i=1,maxnr
read(10,*,iostat=ios) junkfornr
if (ios/=0) exit
if (i == maxnr) then
stop
endif
nr = nr + 1
end do
allocate(lat(nr))
allocate(lon(nr))
rewind(10)
do i=1,nr
read(10,*) lat(i), lon(i)
end do
open(20,file='RESULT_atlanta_mc.txt')
do i=1,nr
write(20,100) "atlanta",lat(i),lon(i),distkm(lat(i),lon(i),33.79526,-84.326528)
100 format(A,3F12.6)
end do
end program mcdonalds
function distkm(deglat1,deglon1,deglat2,deglon2)
implicit none
real, intent(in) :: deglat1,deglon1,deglat2,deglon2
real :: pi,dlat,dlon,lat1,lat2,a,distkm
pi = 4*atan(1.0)
dlat = (deglat2-deglat1)*pi/180
dlon = (deglon2-deglon1)*pi/180
lat1 = deglat1*pi/180
lat2 = deglat2*pi/180
a = (sin(dlat/2))**2 + cos(lat1)*cos(lat2)*(sin(dlon/2))**2
distkm = 6372.8*2*atan2(sqrt(a),sqrt(1-a))
end function distkm
Problem:
I have to do this for the all cities. So, I have to change (1) the McDonalds data file, (2) the city center coordinate; and (3) the result file for each city accordingly. How do I have to write a program to iterate this process for the whole 200 cities? I use Intel Fortran (ifort) to compile it.
EDIT (This code worked -- Friday, April 11, 2014):
! calculate distances from the city center to each McDonald
! Try this for all the cities
program mcdonalds
implicit none
interface
function distkm(deglat1,deglon1,deglat2,deglon2)
real :: distkm
real, intent(in) :: deglat1,deglon1,deglat2,deglon2
end function distkm
end interface
integer, parameter :: maxnr = 200
integer :: nr, i, j, ios
character(len=1) :: junkfornr
character(len=25) :: fn
real, dimension(:), allocatable :: lat, lon
real, dimension(5) :: clat, clon
character(len=15), dimension(5) :: filename
character(len=6), dimension(5) :: code
character(len=7), dimension(5) :: cityname
! I have a text file that contains the list of data file names, city names and the (lat/lon)s of city centers.
! The name of that file: datafilelistcitycenter.txt
! === The content of the file looks like: ===
! atlanta_mc.txt 11111 atlanta 33.79526 -84.326528
! houston_mc.txt 22222 houston 29.733215 -95.430824
! la_mc.txt 33333 la 34.057453 -118.413842
! ny_mc.txt 44444 ny 40.759347 -73.980202
! philly_mc.txt 55555 philly 29.733215 -95.430824
! Let's assume we know the number of the cities.
! In this case, it is 5.
open(10, file='datafilelistcitycenter.txt', status='old')
rewind(10)
do i=1,5
read(10,*) filename(i), cityname(i), clat(i), clon(i)
end do
close(10)
! Obtain the total number of observations for each city
do j=1,5
open(j, file=filename(j), status='old')
nr=0
do i=1,maxnr
read(j,*,iostat=ios) junkfornr
if (ios/=0) exit
if (i == maxnr) then
write(*,*) "Error: Maximum number of records exceeded..."
write(*,*) "Exiting the program..."
stop
endif
nr = nr + 1
end do
allocate(lat(nr),lon(nr))
rewind(j)
do i=1,nr
read(j,*) lat(i), lon(i)
end do
close(j)
write(fn, '("RESULT_",A15)') filename(j)
open(j,file=fn)
do i=1,nr
write(j,100) code(j),cityname(j),lat(i),lon(i),distkm(lat(i),lon(i),clat(j),clon(j))
100 format(A6,A7,3F12.6)
end do
close(j)
deallocate(lat,lon)
end do
end program mcdonalds
function distkm(deglat1,deglon1,deglat2,deglon2)
implicit none
real, intent(in) :: deglat1,deglon1,deglat2,deglon2
real :: pi,dlat,dlon,lat1,lat2,a,distkm
pi = 4*atan(1.0)
dlat = (deglat2-deglat1)*pi/180
dlon = (deglon2-deglon1)*pi/180
lat1 = deglat1*pi/180
lat2 = deglat2*pi/180
a = (sin(dlat/2))**2 + cos(lat1)*cos(lat2)*(sin(dlon/2))**2
distkm = 6372.8*2*atan2(sqrt(a),sqrt(1-a))
end function distkm
No luck. I end up with very weird messages: severe <66>: output statement overflows record, unit -5, file Internal Fomatted Write.
EDIT: I think I got it right. Thanks.
Instead of opening one specific city file such as atlanta_mc.tx, create a file that a list of the filenames of the city. Open that file, add a new loop to your program and read a name of a city file, open and process it as you do now, then close the file. Next iteration of new loop will process the next city file, etc. This is assuming that the cities are in separate files. If they are in one big file, then create arrays to hold information for all of the cities, read all the information into the array, then have a new loop to iterate over the cities.
Also, if you place your function distkm into a module you won't have to bother with having an interface for it. Using modules and easier and less error prone. An example: distkm
Related
I have a program in Fortran that saves the results to a file. At the moment I open the file using
OPEN (1, FILE = 'Output.TXT')
However, I now want to run a loop, and save the results of each iteration to the files 'Output1.TXT', 'Output2.TXT', 'Output3.TXT', and so on.
Is there an easy way in Fortran to constuct filenames from the loop counter i?
you can write to a unit, but you can also write to a string
program foo
character(len=1024) :: filename
write (filename, "(A5,I2)") "hello", 10
print *, trim(filename)
end program
Please note (this is the second trick I was talking about) that you can also build a format string programmatically.
program foo
character(len=1024) :: filename
character(len=1024) :: format_string
integer :: i
do i=1, 10
if (i < 10) then
format_string = "(A5,I1)"
else
format_string = "(A5,I2)"
endif
write (filename,format_string) "hello", i
print *, trim(filename)
enddo
end program
A much easier solution IMHO ...................
character(len=8) :: fmt ! format descriptor
fmt = '(I5.5)' ! an integer of width 5 with zeros at the left
i1= 59
write (x1,fmt) i1 ! converting integer to string using a 'internal file'
filename='output'//trim(x1)//'.dat'
! ====> filename: output00059.dat
Well here is a simple function which will return the left justified string version of an integer:
character(len=20) function str(k)
! "Convert an integer to string."
integer, intent(in) :: k
write (str, *) k
str = adjustl(str)
end function str
And here is a test code:
program x
integer :: i
do i=1, 100
open(11, file='Output'//trim(str(i))//'.txt')
write (11, *) i
close (11)
end do
end program x
I already showed this elsewhere on SO (How to use a variable in the format specifier statement? , not an exact duplicate IMHO), but I think it is worthwhile to place it here. It is possible to use the techniques from other answers for this question to make a simple function
function itoa(i) result(res)
character(:),allocatable :: res
integer,intent(in) :: i
character(range(i)+2) :: tmp
write(tmp,'(i0)') i
res = trim(tmp)
end function
which you can use after without worrying about trimming and left-adjusting and without writing to a temporary variable:
OPEN(1, FILE = 'Output'//itoa(i)//'.TXT')
It requires Fortran 2003 because of the allocatable string.
For a shorten version.
If all the indices are smaller than 10, then use the following:
do i=0,9
fid=100+i
fname='OUTPUT'//NCHAR(i+48) //'.txt'
open(fid, file=fname)
!....
end do
For a general version:
character(len=5) :: charI
do i = 0,100
fid = 100 + i
write(charI,"(A)"), i
fname ='OUTPUT' // trim(charI) // '.txt'
open(fid, file=fname)
end do
That's all.
I've tried #Alejandro and #user2361779 already but it gives me an unsatisfied result such as file 1.txt or file1 .txt instead of file1.txt. However i find the better solution:
...
integer :: i
character(len=5) :: char_i ! use your maximum expected len
character(len=32) :: filename
write(char_i, '(I5)') i ! convert integer to char
write(filename, '("path/to/file/", A, ".dat")') trim(adjustl(char_i))
...
Explanation:
e.g. set i = 10 and write(char_i, '(I5)') i
char_i gives " 10" ! this is original value of char_i
adjustl(char_i) gives "10 " ! adjust char_i to the left
trim(adjustl(char_i)) gives "10" ! adjust char_i to the left then remove blank space on the right
I think this is a simplest solution that give you a dynamical length filename without any legacy blank spaces from integer to string conversion process.
Try the following:
....
character(len=30) :: filename ! length depends on expected names
integer :: inuit
....
do i=1,n
write(filename,'("output",i0,".txt")') i
open(newunit=iunit,file=filename,...)
....
close(iunit)
enddo
....
Where "..." means other appropriate code for your purpose.
To convert an integer to a string:
integer :: i
character* :: s
if (i.LE.9) then
s=char(48+i)
else if (i.GE.10) then
s=char(48+(i/10))// char(48-10*(i/10)+i)
endif
Here is my subroutine approach to this problem. it transforms an integer in the range 0 : 9999 as a character. For example, the INTEGER 123 is transformed into the character 0123. hope it helps.
P.S. - sorry for the comments; they make sense in Romanian :P
subroutine nume_fisier (i,filename_tot)
implicit none
integer :: i
integer :: integer_zeci,rest_zeci,integer_sute,rest_sute,integer_mii,rest_mii
character(1) :: filename1,filename2,filename3,filename4
character(4) :: filename_tot
! Subrutina ce transforma un INTEGER de la 0 la 9999 in o serie de CARACTERE cu acelasi numar
! pentru a fi folosite in numerotarea si denumirea fisierelor de rezultate.
if(i<=9) then
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+0)
filename4=char(48+i)
elseif(i>=10.and.i<=99) then
integer_zeci=int(i/10)
rest_zeci=mod(i,10)
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=100.and.i<=999) then
integer_sute=int(i/100)
rest_sute=mod(i,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+0)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=1000.and.i<=9999) then
integer_mii=int(i/1000)
rest_mii=mod(i,1000)
integer_sute=int(rest_mii/100)
rest_sute=mod(rest_mii,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+integer_mii)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
endif
filename_tot=''//filename1//''//filename2//''//filename3//''//filename4//''
return
end subroutine nume_fisier
I've read similar solved questions on this website but they do to help me! So, I'm sorry to make a similar question.
I've the following .txt file named "Asteroids_Numbered.txt" (the file has lots of rows, i.e. 607013, but I put a lot less for simplicity):
Num Name Epoch a e i w Node M H G Ref
------ ----------------- ----- ---------- ---------- --------- --------- --------- ----------- ----- ----- ----------
1 Ceres 59600 2.7660431 0.07850100 10.58769 73.63704 80.26860 291.3755993 3.54 0.12 JPL 48
2 Pallas 59600 2.7711069 0.22999297 34.92530 310.69725 172.91657 272.4799259 4.22 0.11 JPL 49
3 Juno 59600 2.6687911 0.25688702 12.99186 247.94173 169.84780 261.2986327 5.28 0.32 JPL 123
4 Vesta 59600 2.3612665 0.08823418 7.14172 151.09094 103.80392 7.0315225 3.40 0.32 JPL 36
5 Astraea 59600 2.5751766 0.19009936 5.36762 358.74039 141.57036 160.9820880 6.99 0.15 JPL 125
6 Hebe 59600 2.4256657 0.20306151 14.73873 239.50547 138.64097 347.4991368 5.65 0.24 JPL 100
7 Iris 59600 2.3866161 0.22949924 5.51768 145.34355 259.52553 47.6423152 5.61 0.15 JPL 119
8 Flora 59600 2.2017319 0.15606719 5.88872 285.55022 110.87251 136.2585358 6.54 0.28 JPL 127
9 Metis 59600 2.3852921 0.12356142 5.57695 6.16423 68.89958 184.5626181 6.37 0.17 JPL 128
10 Hygiea 59600 3.1418676 0.11162598 3.83093 312.49331 283.18419 328.8968591 5.55 0.15 JPL 105
11 Parthenope 59600 2.4532814 0.09954681 4.63165 195.59824 125.52829 175.4211548 6.60 0.15 JPL 118
12 Victoria 59600 2.3337809 0.22074254 8.37333 69.66955 235.36878 49.7506630 7.31 0.22 JPL 131
13 Egeria 59600 2.5765835 0.08544364 16.53450 80.14708 43.20673 66.2442983 6.84 0.15 JPL 103
14 Irene 59600 2.5859176 0.16587880 9.12082 97.71349 86.11601 42.0351479 6.53 0.15 JPL 96
15 Eunomia 59600 2.6440754 0.18662534 11.75200 98.63169 292.92610 152.5002319 5.41 0.23 JPL 85
16 Psyche 59600 2.9244847 0.13392662 3.09684 229.21980 150.03218 125.1275316 6.06 0.20 JPL 90
17 Thetis 59600 2.4706187 0.13286003 5.59276 135.80674 125.54282 197.5734224 7.76 0.15 JPL 125
18 Melpomene 59600 2.2957889 0.21790920 10.13249 228.11923 150.36173 190.3739342 6.53 0.25 JPL 116
19 Fortuna 59600 2.4429040 0.15701789 1.57276 182.47214 211.04422 95.0887535 7.38 0.10 JPL 142
20 Massalia 59600 2.4088126 0.14306413 0.70880 257.55922 205.97388 20.5136762 6.56 0.25 JPL 118
21 Lutetia 59600 2.4351916 0.16354177 3.06364 250.15544 80.85386 243.3813245 7.52 0.11 JPL 118
22 Kalliope 59600 2.9102024 0.09838131 13.70049 357.60063 65.99349 33.4836574 6.51 0.21 JPL 111
How can I create a program that reads this file, stores data in 1-D arrays (one for every type of data, so I want to get 12 arrays) and then filter them according some criteria, for example for a value of inclination (i) less then 2deg?
At the end, how can I store the filtered data in a new file with the same formatting of original file?
Here my code (it contains only the reading part):
program Read_write_ephemerides_Main
implicit none
!Declarations
character*100 :: input_path,input_filename, output_path, output_filename
double precision, dimension(:,:), allocatable :: Epoch_TDB, a_AU, e, i_deg, w_deg, Node_deg, M_deg, H_mag, G
character*30, dimension(4) :: str_output
character, dimension (:,:), allocatable :: Name, Ref
integer :: i,iu, i_counter
integer, dimension (:,:), allocatable :: Number
logical :: bContinue
! Definition of constants, paths names and file names
iu = 10
input_path = 'D:\MSc_Aerospace_Eng\Thesis\Fortran_projects\Read_write_ephemerides\InputFiles\'
input_filename = 'Asteroids_Numbered.txt'
!output_path = 'D:\MSc_Aerospace_Eng\Thesis\Fortran_projects\Read_write_ephemerides\OutputFiles\'
!output_filename = 'prova_ast_num.txt'
! Reading of Asteroids_numbered file
open(unit = iu, file = trim(input_path) // trim(input_filename), status='old', &
access = 'sequential',form = 'formatted', action='read')
read(iu,'(//)') ! skip first 2 lines
read(iu,'(i10,a25,f10.0,6(f12.8),2(f5.4),f5.4)') Number, Name, Epoch_TDB, a_AU, e, i_deg, w_deg, Node_deg, M_deg, H_mag, G, Ref
close(unit = iu,status='keep')
! Creation of output file
!open(unit = iu, file = trim(output_path) // trim(output_filename1), status = 'unknown', action = 'write')
!write(iu,'(i10,a25,f10.0,6(f12.8),2(f5.4),f5.4)') Number, Name, Epoch_TDB, a_AU, e, i_deg, w_deg, Node_deg, M_deg, H_mag, G, Ref
!close(unit = iu,status='keep')
!
stop
end program Read_write_ephemerides_Main
EDIT: Code updated
USEFUL NOTE: I use Intel Fortran compiler within Microsoft Visual Studio 2022
Let's get one thing out of the way before moving on to the next part: if this is simply a "filtering" task, treat it as a filtering task.
In Fortran 2018 this could be as simple as
implicit none
character(1234) line
integer iostat, nchars
do
read (*,'(A)',iostat=iostat,size=nchars) line
if (iostat.lt.0) exit
if (KEEP_LINE) print *, line(:nchars) ! Implement conditional
end do
end program
(If your compiler isn't a Fortran 2018 compiler you will need to complicate this.) This program acts as a filter in the Unix-sense:
./program < input_file > output_file
For this question the filter would be something like "pass first two lines; pass later lines where the sixth column as numeric is less than 2". I'll leave the exact specification as an exercise, noting that we can do the job with
awk 'NR<3||$6<2' < input_file > output_file
Note that you can simply extract the sixth column without creating variables for each column - or you can note that it's the first column of line(52:).
That's the filtering out of the way. Let's look at how we can create a data structure and do something with it in the Fortran program.
As High Performance Mark commented, and veryreverie expanded on we can create a derived type for this "data table" (if all columns are the same data type we can possibly get away with just a rank-2 intrinsic type, although even in such cases a derived type helps):
type asteroids_t
integer :: num
character(18) :: name
integer :: epoch
real :: a, e, i, w, node, m, h, g
character(10) :: ref
end type asteroids_t
(set the kind parameter of each component as desired, but probably double precision for the reals)
We have a format for the input and output:
character(*), parameter :: FMT='(i6,a,i7,f10.7,f11.8,3f10.5,f12.7,2f6.2,a)'
(Note that we can't use list-directed formatting for the input, because the final column has a space in the character. Again, working around this is an exercise.)
Assuming we have an array appropriately sized (see general questions about reading files with unknown number of rows, or veryreverie's answer here for detail) we're good to go. For clarity here I'm going to use an explicit size.
type(asteroids_t) asteroids(NUMBER_OF_ASTEROIDS)
integer, allocatable :: pass(:)
read FMT, asteroids
... ! Work, including setting pass for filter
print FMT, asteroids(pass)
Putting that all together for a quick-and-dirty program:
implicit none
type asteroids_t
integer :: num
character(18) :: name
integer :: epoch
real(KIND(0d0)) :: a, e, i, w, node, m, h, g
character(10) :: ref
end type asteroids_t
type(asteroids_t) :: asteroids(22)
character(118) :: header(2)
character(*), parameter :: FMT='(i6,a,i7,f10.7,f11.8,3f10.5,f12.7,2f6.2,a)'
integer :: i
read '(A)', header
print '(A)', header
read FMT, asteroids
print FMT, asteroids(PACK([(i,i=1,SIZE(asteroids))], asteroids%i<2))
end program
The key point to note is that we can process our derived type with "normal" input/output: the item asteroids is expanded as array elements and each array element is then expanded as components. Because our derived type has no private, pointer or allocatable component we can use this simple form of processing.
As more advanced material, a note on the "magic numbers" in the example here. We already know how to remove the magic asteroid count (22) and the magic number and length of the header lines (2 and 118). But maybe we're worried about the lengths of those character components (18 and 10).
Our data structure is tightly coupled to the form of the input data file, but what if we have two datasets where the names differ in length? It will indeed be a pain to rewrite or duplicate our derived type to handle this. Let's solve that problem:
type asteroids_t(len_name, len_ref)
integer, len :: len_name=18, len_ref=10
integer :: num
character(len_name) :: name
integer :: epoch
real(KIND(0d0)) :: a, e, i, w, node, m, h, g
character(len_ref) :: ref
end type asteroids_t
type(asteroids_t) asteroids_set_1(22)
type(asteroids(25,8)) asteroids_set_2(22)
! There's no magic character length in FMT
read FMT, asteroids_set_1
read FMT, asteroids_set_2
The column widths can even be deferred to be resolved at run-time (not shown). You can read about these parameterized derived types in more detail elsewhere.
To expand on #HighPerformanceMark's comments, the best thing to do is to define an Asteroid type which holds all of the information about an asteroid, and then to create an array of Asteroids.
The Asteroid type
The Asteroid type should initially just contain the data about an asteroid,
type :: Asteroid
integer :: num
character(:), allocatable :: name
integer :: epoch
real(dp) :: a
real(dp) :: e
real(dp) :: i
real(dp) :: w
real(dp) :: node
real(dp) :: m
real(dp) :: h
real(dp) :: g
character(:), allocatable :: ref_name
integer :: ref_number
end type
where dp defines double precision.
This allows you to have an array of Asteroids, e.g.
type(Asteroid) :: asteroids(22)
asteroids(1) = Asteroid(1, "Ceres", ..., "JPL", 48)
...
asteroids(22) = Asteroid(22, "Kalliope", ..., "JPL", 111)
write(*,*) asteroids(1)%name ! Writes "Ceres".
Reading and Writing Asteroids
You want to be able to read and write asteroids to and from file, and you can do this using user defined input/output. For this you need a subroutine to read an Asteroid, e.g.
subroutine read_Asteroid(this, unit, iotype, v_list, iostat, iomsg)
class(Asteroid), intent(inout) :: this
integer, intent(in) :: unit
character(*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(*), intent(inout) :: iomsg
character(100) :: name
character(100) :: ref_name
read(unit, *, iostat=iostat, iomsg=iomsg) &
& this%num, &
& name, &
& this%epoch, &
& this%a, &
& this%e, &
& this%i, &
& this%w, &
& this%node, &
& this%m, &
& this%h, &
& this%g, &
& ref_name, &
& this%ref_number
this%name = trim(name)
this%ref_name = trim(ref_name)
end subroutine
and another to write an Asteroid, e.g.
subroutine write_Asteroid(this, unit, iotype, v_list, iostat, iomsg)
class(Asteroid), intent(in) :: this
integer, intent(in) :: unit
character(*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(*), intent(inout) :: iomsg
write(unit, *, iostat=iostat, iomsg=iomsg) &
& this%num, &
& this%name, &
& this%epoch, &
& this%a, &
& this%e, &
& this%i, &
& this%w, &
& this%node, &
& this%m, &
& this%h, &
& this%g, &
& this%ref_name, &
& this%ref_number
end subroutine
You also need to add bindings to the Asteroid type so that it knows to use read_Asteroid and write_Asteroid for reading and writing. This looks like
type :: Asteroid
integer :: num
...
integer :: ref_number
contains
! `read` binding.
generic :: read(formatted) => read_Asteroid
procedure :: read_Asteroid
! `write` binding.
generic :: write(formatted) => write_Asteroid
procedure :: write_Asteroid
end type
N.B. because the Asteroid type has allocatable components (name and ref_name), which are not allocated by read statements, care must be taken when writing read_Asteroid. This method can be used to read allocatables; first reading to an over-large buffer, and then copying the data to the allocatable variable. (Thanks #francescalus for pointing out previous problems with my code here).
It's now possible to read and write asteroids directly, e.g.
character(1000) :: line
type(Asteroid) :: Ceres
line = "1 Ceres 59600 2.766 0.07850 10.58 73.63 80.26 291.3 3.54 0.12 JPL 48"
read(line, *) Ceres
write(*, *) Ceres
An example code
Putting this all together, here is an example code which reads a file full of asteroids and then writes those with i < 2:
module asteroid_module
implicit none
! Define `dp`, which defines double precision.
integer, parameter :: dp = selected_real_kind(15, 307)
! Define the `Asteroid` type.
type :: Asteroid
integer :: num
character(:), allocatable :: name
integer :: epoch
real(dp) :: a
real(dp) :: e
real(dp) :: i
real(dp) :: w
real(dp) :: node
real(dp) :: m
real(dp) :: h
real(dp) :: g
character(:), allocatable :: ref_name
integer :: ref_number
contains
! `read` binding.
generic :: read(formatted) => read_Asteroid
procedure :: read_Asteroid
! `write` binding.
generic :: write(formatted) => write_Asteroid
procedure :: write_Asteroid
end type
contains
! Define how to `read` an `Asteroid`.
subroutine read_Asteroid(this, unit, iotype, v_list, iostat, iomsg)
class(Asteroid), intent(inout) :: this
integer, intent(in) :: unit
character(*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(*), intent(inout) :: iomsg
character(100) :: name
character(100) :: ref_name
read(unit, *, iostat=iostat, iomsg=iomsg) &
& this%num, &
& name, &
& this%epoch, &
& this%a, &
& this%e, &
& this%i, &
& this%w, &
& this%node, &
& this%m, &
& this%h, &
& this%g, &
& ref_name, &
& this%ref_number
this%name = trim(name)
this%ref_name = trim(ref_name)
end subroutine
! Define how to `write` an `Asteroid`.
subroutine write_Asteroid(this, unit, iotype, v_list, iostat, iomsg)
class(Asteroid), intent(in) :: this
integer, intent(in) :: unit
character(*), intent(in) :: iotype
integer, intent(in) :: v_list(:)
integer, intent(out) :: iostat
character(*), intent(inout) :: iomsg
write(unit, *, iostat=iostat, iomsg=iomsg) &
& this%num, &
& this%name, &
& this%epoch, &
& this%a, &
& this%e, &
& this%i, &
& this%w, &
& this%node, &
& this%m, &
& this%h, &
& this%g, &
& this%ref_name, &
& this%ref_number
end subroutine
end module
program example
use asteroid_module
implicit none
character(1000) :: line
integer :: iostat
integer :: file_length
type(Asteroid), allocatable :: asteroids(:)
integer :: i
! Count the number of lines in the file.
file_length = 0
open(10, file="input.txt")
do
read(10, '(A)',iostat=iostat) line
if (iostat/=0) then
exit
endif
file_length = file_length + 1
enddo
close(10)
! Allocate the array to hold the asteroids.
allocate(asteroids(file_length-2))
! Read the asteroids into the array.
open(10, file="input.txt")
read(10, '(A)') line
read(10, '(A)') line
do i=1,size(asteroids)
read(10, '(A)') line
read(line, *) asteroids(i)
enddo
close(10)
! Write the asteroids with `i` < 2 to a file.
open(10, file="output.txt")
do i=1,size(asteroids)
if (asteroids(i)%i < 2.0_dp) then
write(10,*) asteroids(i)
endif
enddo
close(10)
end program
I have a program in Fortran that saves the results to a file. At the moment I open the file using
OPEN (1, FILE = 'Output.TXT')
However, I now want to run a loop, and save the results of each iteration to the files 'Output1.TXT', 'Output2.TXT', 'Output3.TXT', and so on.
Is there an easy way in Fortran to constuct filenames from the loop counter i?
you can write to a unit, but you can also write to a string
program foo
character(len=1024) :: filename
write (filename, "(A5,I2)") "hello", 10
print *, trim(filename)
end program
Please note (this is the second trick I was talking about) that you can also build a format string programmatically.
program foo
character(len=1024) :: filename
character(len=1024) :: format_string
integer :: i
do i=1, 10
if (i < 10) then
format_string = "(A5,I1)"
else
format_string = "(A5,I2)"
endif
write (filename,format_string) "hello", i
print *, trim(filename)
enddo
end program
A much easier solution IMHO ...................
character(len=8) :: fmt ! format descriptor
fmt = '(I5.5)' ! an integer of width 5 with zeros at the left
i1= 59
write (x1,fmt) i1 ! converting integer to string using a 'internal file'
filename='output'//trim(x1)//'.dat'
! ====> filename: output00059.dat
Well here is a simple function which will return the left justified string version of an integer:
character(len=20) function str(k)
! "Convert an integer to string."
integer, intent(in) :: k
write (str, *) k
str = adjustl(str)
end function str
And here is a test code:
program x
integer :: i
do i=1, 100
open(11, file='Output'//trim(str(i))//'.txt')
write (11, *) i
close (11)
end do
end program x
I already showed this elsewhere on SO (How to use a variable in the format specifier statement? , not an exact duplicate IMHO), but I think it is worthwhile to place it here. It is possible to use the techniques from other answers for this question to make a simple function
function itoa(i) result(res)
character(:),allocatable :: res
integer,intent(in) :: i
character(range(i)+2) :: tmp
write(tmp,'(i0)') i
res = trim(tmp)
end function
which you can use after without worrying about trimming and left-adjusting and without writing to a temporary variable:
OPEN(1, FILE = 'Output'//itoa(i)//'.TXT')
It requires Fortran 2003 because of the allocatable string.
For a shorten version.
If all the indices are smaller than 10, then use the following:
do i=0,9
fid=100+i
fname='OUTPUT'//NCHAR(i+48) //'.txt'
open(fid, file=fname)
!....
end do
For a general version:
character(len=5) :: charI
do i = 0,100
fid = 100 + i
write(charI,"(A)"), i
fname ='OUTPUT' // trim(charI) // '.txt'
open(fid, file=fname)
end do
That's all.
I've tried #Alejandro and #user2361779 already but it gives me an unsatisfied result such as file 1.txt or file1 .txt instead of file1.txt. However i find the better solution:
...
integer :: i
character(len=5) :: char_i ! use your maximum expected len
character(len=32) :: filename
write(char_i, '(I5)') i ! convert integer to char
write(filename, '("path/to/file/", A, ".dat")') trim(adjustl(char_i))
...
Explanation:
e.g. set i = 10 and write(char_i, '(I5)') i
char_i gives " 10" ! this is original value of char_i
adjustl(char_i) gives "10 " ! adjust char_i to the left
trim(adjustl(char_i)) gives "10" ! adjust char_i to the left then remove blank space on the right
I think this is a simplest solution that give you a dynamical length filename without any legacy blank spaces from integer to string conversion process.
Try the following:
....
character(len=30) :: filename ! length depends on expected names
integer :: inuit
....
do i=1,n
write(filename,'("output",i0,".txt")') i
open(newunit=iunit,file=filename,...)
....
close(iunit)
enddo
....
Where "..." means other appropriate code for your purpose.
To convert an integer to a string:
integer :: i
character* :: s
if (i.LE.9) then
s=char(48+i)
else if (i.GE.10) then
s=char(48+(i/10))// char(48-10*(i/10)+i)
endif
Here is my subroutine approach to this problem. it transforms an integer in the range 0 : 9999 as a character. For example, the INTEGER 123 is transformed into the character 0123. hope it helps.
P.S. - sorry for the comments; they make sense in Romanian :P
subroutine nume_fisier (i,filename_tot)
implicit none
integer :: i
integer :: integer_zeci,rest_zeci,integer_sute,rest_sute,integer_mii,rest_mii
character(1) :: filename1,filename2,filename3,filename4
character(4) :: filename_tot
! Subrutina ce transforma un INTEGER de la 0 la 9999 in o serie de CARACTERE cu acelasi numar
! pentru a fi folosite in numerotarea si denumirea fisierelor de rezultate.
if(i<=9) then
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+0)
filename4=char(48+i)
elseif(i>=10.and.i<=99) then
integer_zeci=int(i/10)
rest_zeci=mod(i,10)
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=100.and.i<=999) then
integer_sute=int(i/100)
rest_sute=mod(i,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+0)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=1000.and.i<=9999) then
integer_mii=int(i/1000)
rest_mii=mod(i,1000)
integer_sute=int(rest_mii/100)
rest_sute=mod(rest_mii,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+integer_mii)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
endif
filename_tot=''//filename1//''//filename2//''//filename3//''//filename4//''
return
end subroutine nume_fisier
I have a program in Fortran that saves the results to a file. At the moment I open the file using
OPEN (1, FILE = 'Output.TXT')
However, I now want to run a loop, and save the results of each iteration to the files 'Output1.TXT', 'Output2.TXT', 'Output3.TXT', and so on.
Is there an easy way in Fortran to constuct filenames from the loop counter i?
you can write to a unit, but you can also write to a string
program foo
character(len=1024) :: filename
write (filename, "(A5,I2)") "hello", 10
print *, trim(filename)
end program
Please note (this is the second trick I was talking about) that you can also build a format string programmatically.
program foo
character(len=1024) :: filename
character(len=1024) :: format_string
integer :: i
do i=1, 10
if (i < 10) then
format_string = "(A5,I1)"
else
format_string = "(A5,I2)"
endif
write (filename,format_string) "hello", i
print *, trim(filename)
enddo
end program
A much easier solution IMHO ...................
character(len=8) :: fmt ! format descriptor
fmt = '(I5.5)' ! an integer of width 5 with zeros at the left
i1= 59
write (x1,fmt) i1 ! converting integer to string using a 'internal file'
filename='output'//trim(x1)//'.dat'
! ====> filename: output00059.dat
Well here is a simple function which will return the left justified string version of an integer:
character(len=20) function str(k)
! "Convert an integer to string."
integer, intent(in) :: k
write (str, *) k
str = adjustl(str)
end function str
And here is a test code:
program x
integer :: i
do i=1, 100
open(11, file='Output'//trim(str(i))//'.txt')
write (11, *) i
close (11)
end do
end program x
I already showed this elsewhere on SO (How to use a variable in the format specifier statement? , not an exact duplicate IMHO), but I think it is worthwhile to place it here. It is possible to use the techniques from other answers for this question to make a simple function
function itoa(i) result(res)
character(:),allocatable :: res
integer,intent(in) :: i
character(range(i)+2) :: tmp
write(tmp,'(i0)') i
res = trim(tmp)
end function
which you can use after without worrying about trimming and left-adjusting and without writing to a temporary variable:
OPEN(1, FILE = 'Output'//itoa(i)//'.TXT')
It requires Fortran 2003 because of the allocatable string.
For a shorten version.
If all the indices are smaller than 10, then use the following:
do i=0,9
fid=100+i
fname='OUTPUT'//NCHAR(i+48) //'.txt'
open(fid, file=fname)
!....
end do
For a general version:
character(len=5) :: charI
do i = 0,100
fid = 100 + i
write(charI,"(A)"), i
fname ='OUTPUT' // trim(charI) // '.txt'
open(fid, file=fname)
end do
That's all.
I've tried #Alejandro and #user2361779 already but it gives me an unsatisfied result such as file 1.txt or file1 .txt instead of file1.txt. However i find the better solution:
...
integer :: i
character(len=5) :: char_i ! use your maximum expected len
character(len=32) :: filename
write(char_i, '(I5)') i ! convert integer to char
write(filename, '("path/to/file/", A, ".dat")') trim(adjustl(char_i))
...
Explanation:
e.g. set i = 10 and write(char_i, '(I5)') i
char_i gives " 10" ! this is original value of char_i
adjustl(char_i) gives "10 " ! adjust char_i to the left
trim(adjustl(char_i)) gives "10" ! adjust char_i to the left then remove blank space on the right
I think this is a simplest solution that give you a dynamical length filename without any legacy blank spaces from integer to string conversion process.
Try the following:
....
character(len=30) :: filename ! length depends on expected names
integer :: inuit
....
do i=1,n
write(filename,'("output",i0,".txt")') i
open(newunit=iunit,file=filename,...)
....
close(iunit)
enddo
....
Where "..." means other appropriate code for your purpose.
To convert an integer to a string:
integer :: i
character* :: s
if (i.LE.9) then
s=char(48+i)
else if (i.GE.10) then
s=char(48+(i/10))// char(48-10*(i/10)+i)
endif
Here is my subroutine approach to this problem. it transforms an integer in the range 0 : 9999 as a character. For example, the INTEGER 123 is transformed into the character 0123. hope it helps.
P.S. - sorry for the comments; they make sense in Romanian :P
subroutine nume_fisier (i,filename_tot)
implicit none
integer :: i
integer :: integer_zeci,rest_zeci,integer_sute,rest_sute,integer_mii,rest_mii
character(1) :: filename1,filename2,filename3,filename4
character(4) :: filename_tot
! Subrutina ce transforma un INTEGER de la 0 la 9999 in o serie de CARACTERE cu acelasi numar
! pentru a fi folosite in numerotarea si denumirea fisierelor de rezultate.
if(i<=9) then
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+0)
filename4=char(48+i)
elseif(i>=10.and.i<=99) then
integer_zeci=int(i/10)
rest_zeci=mod(i,10)
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=100.and.i<=999) then
integer_sute=int(i/100)
rest_sute=mod(i,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+0)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=1000.and.i<=9999) then
integer_mii=int(i/1000)
rest_mii=mod(i,1000)
integer_sute=int(rest_mii/100)
rest_sute=mod(rest_mii,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+integer_mii)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
endif
filename_tot=''//filename1//''//filename2//''//filename3//''//filename4//''
return
end subroutine nume_fisier
I am having an issue with the subroutine in this program, which is at the end of the program. I am getting an error for the division of the arrays in the subroutine, which says "unclassifiable statement." Hope someone can help!
PROGRAM subroutine1
IMPLICIT NONE
INTEGER:: err, ierr, counter, y ,i ,j
!INTEGER, ALLOCATABLE:: gamenum(:)
CHARACTER(30):: fname
REAL, ALLOCATABLE:: AB(:), H(:), TB(:), BA (:), SP(:)
100 Format (A)
200 Format (I2)
300 Format (F9.3)
! 1.Open file
WRITE(*,100)"Please enter a filename:"
READ(*,*) fname
OPEN (UNIT=10, FILE=fname, STATUS="OLD", ACTION="READ", IOSTAT=err)
IF(err.NE.0) STOP "An error occured when opening the file."
! 2.Count Lines
READ(10,*)
counter=0
DO
READ(10,*,IOSTAT=ierr)
IF(ierr .NE. 0) EXIT
counter=counter+1
END DO
!WRITE(*,200) counter
! 3. allocate array
ALLOCATE(AB(counter))
ALLOCATE(H(counter))
ALLOCATE(TB(counter))
ALLOCATE(BA (counter))
ALLOCATE(SP(counter))
! 4. read in data
REWIND(10)
READ(10,*)
DO i=1,counter
READ(10,*) AB(i), H(i), TB(i)
END DO
REWIND(10)
! 5. Call subroutine
CALL arraycalc(counter,AB,H,TB,BA,SP)
! 6. Write out
WRITE(*,100)"Game AB H TB BA SP"
DO i=1,counter
WRITE(*,200,ADVANCE="NO") i
WRITE(*,'(5F9.3)') AB(i), H(i), TB(i), BA(i), SP(i)
END DO
END PROGRAM subroutine1
!HERE IS THE PART WHERE I'M HAVING TROUBLE
SUBROUTINE arraycalc(counter,AB,H,TB,BA,SP)
IMPLICIT NONE
INTEGER, INTENT(IN)::counter
INTEGER::i
REAL,INTENT(INOUT)::AB,H,TB
REAL,INTENT(INOUT):: BA,SP
DO i=1,counter
BA(i)=H(i)/AB(i)
END DO
DO i=1,counter
SP(i)=TB(i)/AB(i)
END DO
END SUBROUTINE
Inside the subroutine, AB, H, TB, BA, and SP are not declared as arrays, so the statement that gives the error indeed makes no sense. They are scalars, so can not be indexed.