Search code examples
arraysdatabasefortrantext-files

How to read data from external .txt file, store them in arrays, filter and write them in a new file in Fortran 90?


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


Solution

  • 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