Project 5

Download the file vabirds.csv.

  1. Create the derived type bird_data in a module bird_dat as illustrated in the example. This is still a type; we do not need a class for this exercise.
  2. Add the following procedures to the constructor already written.
    • Write a stats procedure that takes only an instance of the type and returns the mean and standard deviation of the observations for that instance.
    • Write a minmax procedure that takes an instance of the type and the array of years and returns the maximum observed, the minimum observed, and the years for maximum and minimum. You may use the maxval, minval, maxloc, and minloc intrinsics.
    • Write a main program that uses your module and also uses the sorters module that you can download ( sorters.f90). This implements bubblesort. Bubblesort is simple and slow but is more than sufficient for this exercise. Note that the subprogram is destructive, i.e. it overwrites the array to be sorted, so make a copy if you don’t want that.

Remember to write an explicit interface for each subprogram in this “main” file. Do not use CONTAINS. Read the file name from the command line. First of all you will need to count the number of lines in the file. Write a function count_lines that does this and returns the number. It is up to you whether you pass it the number of header/footer lines. Count_lines can check for the existence of the file and return 0 if it is not found. Still in the read_data routine, using the number of items in the file, corrected for the header and the two footers, allocate an array of bird_data types.
Loop through this array calling your constructor for each species.
The read_data routine should return the array of years and the array of bird_data types.

Request a species name from the user. Find the species in your array of types and print its mean, standard deviation, and results from minmax. Print some appropriate message if the species is not found. Compute an array of the means for all species.
Use the pbsort routine from sorters to sort this array. This procedure also returns the permutation vector, which is an array of the indices of the original positions.
For example, if after the sort the permutation vector is (17,3,55,11,23, and so forth) that means that the element that was previously 17 is now the first in the new array, and so on.
Note that these sorters return in ascending order (smallest to largest). From the sorted mean array and the permutation index, print the names of the 10 most common (by mean) species over the years of observations.
Hint: you can use a trick to reverse a dimension of an array in Fortran:

R=A(ndim:1:-1)

Test the user input portion for
TurkeyVulture
TuftedTitmouse
ElegantTrogon

For this project you can require an exact match of the species name. (Note that no spaces are allowed and words are separated by capitalization; we would have to do more sophisticated string handling if we were to allow spaces and variations in capitalization.)

In addition to the sorters.f90 module mentioned above, the sample solution uses the file_utils module that collects some useful file-related subprograms, including the count_lines function.

Sample solution

module bird_dat
implicit none

type bird_data
   character(len=50)                  :: species
   integer, dimension(:), allocatable :: obs
end type bird_data

contains
   
subroutine constructor(bird,species,dat)
      type(bird_data),         intent(inout)          :: bird
      character(len=*),        intent(in)             :: species
      integer, dimension(:),   intent(in)             :: dat

      bird%species=species
      allocate(bird%obs(size(dat)))
      bird%obs=dat
end subroutine

subroutine stats(bird,mean,std)
      type(bird_data),         intent(inout)          :: bird
      real,                    intent(out)            :: mean,std
      real                                            :: mu2
      
      mean=sum(bird%obs)/float(size(bird%obs))
      mu2=(sum(bird%obs)/float(size(bird%obs)))**2
      std=sqrt(sum(bird%obs**2-(mu2))/float(size(bird%obs)))
end subroutine

subroutine minmax(bird,years,min_val,max_val,min_year,max_year)
      type(bird_data),       intent(inout) :: bird
      integer, dimension(:), intent(in)    :: years
      real,                  intent(out)   :: min_val,max_val
      integer,               intent(out)   :: min_year, max_year
      
      min_val=minval(bird%obs)
      max_val=maxval(bird%obs)
      min_year=years(minloc(bird%obs,1))
      max_year=years(maxloc(bird%obs,1))

end subroutine

end module

program bird_obs
use bird_dat
use sorters
implicit none
!******************************************************************
!Mean observations, standard deviation, max and min of a set of 
!bird observations 
!Author:    Katherine Holcomb
!Changelog: Initial version 2015-03-4		
!******************************************************************

  type(bird_data),dimension (:), allocatable :: bird_list
  real,           dimension(:),  allocatable :: means,stds,oldmeans
  character(len=50)                          :: filename
  character(len=50)                          :: species,my_species,lc_species
  integer                                    :: n, nargs, nbirds
  logical                                    :: found_it
  real                                       :: std,mean,min_val,max_val
  integer                                    :: min_year, max_year
  integer, dimension(:),allocatable          :: years, pvec

  interface
     subroutine read_data(bird_list,filename,years)
        use bird_dat
        use file_utils
        implicit none
        type(bird_data), dimension(:), allocatable, intent(out) :: bird_list
        character(len=*),                           intent(in)  :: filename
        integer,         dimension(:),allocatable,  intent(out) :: years
     end subroutine
  end interface

  nargs=command_argument_count() 
  if ( nargs .ne. 2 ) then
     stop "Usage: <file> species"
  else
     call get_command_argument(1,filename)
     call get_command_argument(2,my_species)
  endif

  call read_data(bird_list,filename,years)
  nbirds=size(bird_list)

  found_it=.false.
  allocate(means(nbirds),stds(nbirds))
  do n=1,nbirds
     call stats(bird_list(n),means(n),stds(n))
     if (my_species==bird_list(n)%species) then
        mean=means(n)
        std=stds(n)
        call minmax(bird_list(n),years,min_val,max_val,min_year,max_year)
        found_it=.true.
     endif
  enddo

  if (found_it) then
     write(*,*) "Statistics for "//my_species
     write(*,'(a,f8.2)',advance='no') "The minimum value of observations is:",min_val
     write(*,'(a,i5)') " in year",min_year
     write(*,'(a,f8.2)',advance='no') "The maximum value of observations is:",max_val
     write(*,'(a,i5)') " in year",max_year
     write(*,'(a,f0.2)') "The mean value of the observations is:",mean
     write(*,'(a,f0.2)') "The standard deviation of the observations is:",std
  else
     write(*,*) "Requested species not found in the data file."
  endif

  allocate(pvec(nbirds))
  allocate(oldmeans(size(means)))
  oldmeans=means
  call pshellsort(means,pvec,nbirds)

  write(*,*)
  write(*,*) 'The 10 most common birds are'

  do n=nbirds,nbirds-9,-1
      write(*,'(a,f8.2)') bird_list(pvec(n))%species(1:len_trim(species)),oldmeans(pvec(n))
  enddo

End program 

subroutine read_data(bird_list,filename,years)
  use bird_dat
  use file_utils
  implicit none
  type(bird_data), dimension(:), allocatable, intent(out) :: bird_list
  character(len=*),                           intent(in)  :: filename
  integer,         dimension(:), allocatable, intent(out) :: years
  integer,         dimension(:), allocatable :: obs
  integer,         parameter                 :: nobs=47
  character(len=6),dimension(nobs)           :: cyears
  integer                                    :: inunit
  integer                                    :: nheaders,nbirds
  character(len=50)                          :: species
  character(len=1024)                        :: line
  character(len=:),dimension(:),allocatable  :: line_vals
  integer                                    :: num_vals
  integer                                    :: n

  inunit=open_file(filename,'r')

  if (inunit .ne. 0) then
      open(unit=inunit,file=filename)
  else
      stop "Unable to open specified data file."
  endif

  nheaders=3
  nbirds=count_records(inunit,nheaders)

  allocate(bird_list(nbirds))
  allocate(years(nobs))
  read(inunit,*) species,cyears(:)
  do n=1,nobs
     read(cyears(n),'(i4)') years(n)
  enddo

  allocate(obs(nobs))
     
  do n=1,nbirds
     read(inunit,*) species, obs
     call constructor(bird_list(n),species,obs)
  end do

end subroutine read_data

Previous
Next