Sine Wave at 10Hz#

Introduction#

This example demonstrates the computation of the Discretized Fourier Transform of a sine wave at 10 Hz using the FFTW3 library in Fortran. The sine wave is generated using SciLAB and the DFFT is computed using the Fortran 2003 code. The time domain and frequency domain plots are generated using GNUPlot.

SciLAB Code#

The SciLAB code to generate the sine wave is as follows:

// Generate a sine wave at 10 Hz
// Frequency components of a signal
//----------------------------------
// build a signal sampled at 1000hz  containing a pure frequency
// at 10Hz
sample_rate=1000;
frequency=10;

// time vector which goes from 0 to 1 with a step of 1/sample_rate
t = 0:1/sample_rate:1;

// number of samples
N=size(t,'*'); //number of samples

// generate the signal
s=sin(2*%pi*frequency*t);

// compute the fft of the signal
y=fft(s);

//s is real so the fft response is conjugate symmetric and we retain only the first N/2 points
f=sample_rate*(0:(N/2))/N; //associated frequency vector

// number of samples
n=size(f,'*')

// plot the fft
h=0;
scf(h);
xset('window',h);
xtitle('DFFT of 10Hz Sine Wave in SciLAB', 'Freq [Hz]', 'Amplitude [Arb]');
plot(f,abs(y(1:n)))

// plot the time domain signal
h=1;
scf(h);
xset('window',h);
xtitle('s(t)', 'Time [s]', 'Amplitude [Arb]');
plot(t, s);

Execute the SciLAB Code#

To execute the SciLAB code, copy and paste the code into the SciLAB console and press Enter. The time domain and frequency domain plots will be displayed. The results are shown below:

Time Domain Plot#

../../../../../_images/scilab_sine_wave2_10Hz.jpeg

Frequency Domain Plot#

../../../../../_images/scilab_sine_wave2_10Hz_FFT.jpg

Saving The Time Domain Data#

The time-domain data is selected in SciLAB and saved into a CSV file.

Processing of CSV File into a Format Suitable for Input into the Fortran 2003 DFFT Code#

Since the output of SciLAB is a comma separated value (CSV) file, a method is needed that will process that file into another file, where the commas are removed and each data element is on a separate line. Additionally, a the data elements need to be reformatted into fixed floating point, since the “e” floating point format was noticed to possibly cause errors when input into the FFTW3 algorithm. These operations were done using a Python script, with its powerful object-oriented file techniques and its included CSV library. The Python script is shown below:

#!/usr/bin/python3
import sys
import csv
import os

# Assign constants for better code readability
first_arg = 1
second_arg = 2

# Call the system to get the command line arguments and pull out items from the list
input_file = sys.argv[first_arg]
intermediate_file = 'intermediate.dat'
output_file = sys.argv[second_arg]

# Simple string manipulation that traverses text file until EOF 
# and calls method to replace commas with newline characters
with open(input_file) as infile, open(intermediate_file, "w") as outfile:
  for line in infile:
      outfile.write(line.replace(",", "\n"))

# Close the input file, as it is no longer needed.
infile.close()
      
# Now go through the intermediate file (the file that contains one data element per line)
# and reformat all of the data elements into fixed floating point with 20 decimal precision.
with open(intermediate_file) as infile, open(output_file, "w") as outfile:
  for line in infile:
      outfile.write('%20.20f\n' % float(line))
            
# Close the intermediate file and final output file.
infile.close()
outfile.close()

# Clean away the intermediate file.  The next line may be commented out if the intermediate 
# file is required.
os.remove(intermediate_file)

Execute the Python Script#

Run by typing:

./convert_csv.py infile.csv outfile.dat

where infile.csv is the data file in CSV format, and outfile.dat is the processed file Obtain the line count of the processed file by typing:

wc -l outfile.dat

Fortran 2003 Code#

The Fortran 2003 code to compute the Discretized Fourier Transform of the sine wave at 10 Hz is as follows:

! DFFT from Processed Data File
! Written by Mark Khusid 2015
!
! 2018
! Version 6
! Increased amount of command line arguments to 5 from 3.  The added two were sample rate and number of periods.
! Cleaned up indentation and formatting.
! Modified the write_DFFT_to_file subroutine to output not only the DFFT data element but also the corrected frequency
! on each line of the data file.  The DFFT data element and corrected frequency are separated by a space
! Obtained the corrected frequency based on the sampe rate and the next power of two of the total number of samples (i.e. 100 samples -> next power of 2 = 128.)
! Since there are num_bins_int number of data elements in FFT array, and the FFT array is symmetric, we divide the magnitude by half the num_bins_int to get the normalized magnitude.
!
! Version 5
! Commented out debug code and rearranged some inconsequential lines.
!
! 2015
! Version 4
! Numerous fixes.  Changed code to allocate memory based on the next power of 2 relative
! to the number of element in the input file.  For example, a data file with 1000 data elements
! will cause arrays to be allocated with 1024 elements.  This is done to prevent errors in the FFT.
! Extra array space above and beyond what is in data file is padded with zeros.
! Reformatted most of the debug output.
! Used ATAN2, separate variables and formatting to ensure that small values of the real part will not 
! cause phase errors.
!
! Version 3
! This version counts the number of data elements in the file automatically.
! This version is rewritten to make use of subroutines.

module MKDynamics_FFT

    use, intrinsic      :: iso_c_binding
      include 'fftw3.f03'
      
      type(C_PTR)         			:: plan
      
      ! Input file name, output file name and number of bins, sample rate, number of periods
      integer, parameter                                      :: num_expected_args = 5
      
      ! FFTW input array
      real(C_DOUBLE), dimension(:), allocatable			    :: in
      
      ! FFTW output array
      complex(C_DOUBLE_COMPLEX), dimension(:), allocatable	:: out
      
      ! Holds the data read from the input file
      real(C_DOUBLE), dimension(:), allocatable   			                :: temp_real_array
      
      ! Used to perform complex math
      real(C_DOUBLE)                                                                      :: magnitude, phase, temp_real
      
      ! For checking if EOF reached
      integer                                                                             :: EOF_marker
      
      ! For checking if files opened successfully
      integer                                     			                :: file_good1, file_good2, file_good3
      
      ! For checking if files written successfully
      integer                                                                             :: good_write1, good_write2			
      
      ! Counter used to traverse arrays
      integer                     							:: i
      
      ! Contains number of arguments for verification of proper command line call
      integer                     							:: num_args
      
      ! Holds obtained number of bins
      integer                     							:: num_bins_int
      
      ! Holds memory allocation status of arrays
      integer						                           	:: status1, status2, status3
      
      ! Holds file names
      character(len=80)               						:: input_file, output_mag_file
      character(len=80)				                   		:: output_phase_file
      
      ! Holds sample rate argument string that will be converted to an integer
      character(len=80)                                       :: sample_rate_string
      
      ! Holds number of periods argument string that will be converted to an integer
      character(len=80)                                       :: num_periods_string
      
      ! Used to calculate the next power of 2 above input file size.  i.e. 1000 element array is increased to 1024 elements
      real			                         				:: num_log2_bins_real
      integer							                        :: num_bins_int_pow2
      
      ! Used to hold the sample rate of the input data file
      integer                                                 :: sample_rate
      
      ! Used to hold the number of periods of the input data file
      integer                                                 :: num_periods
      
      ! Used to hold the total number of samples within the input data file
      integer                                                 :: num_samples
         
    contains
      subroutine close_files_and_exit
        close(1)
        close(10)
        close(20)
        print *, 'Files closed.....'
        print *, 'Exiting.....'
        stop
      end subroutine close_files_and_exit
      
      subroutine close_files_deallocate_and_exit(state)
        integer :: state
        close (1)
        close (10)
        close (20)
        deallocate (in)
        deallocate (out)
        deallocate (temp_real_array)
          if (state .eq. -1) then
              print *, 'Files closed and memory deallocated'
              print *, 'Exiting.....'
          stop
          else
              print *, 'Files closed and memory deallocated'
          end if
      end subroutine close_files_deallocate_and_exit
      
      ! From stack overflow
      elemental subroutine str2int(str,int)
          implicit none
          ! Arguments
          character(len=*),intent(in) :: str
          integer,intent(out)         :: int
  
          read(str,*)  int
      end subroutine str2int
      
      subroutine get_args
      ! Verify that the number of arguments is correct.  Should be 5, otherwise display
        ! error and terminate program
        num_args = command_argument_count()
          if (num_args .ne. num_expected_args) then
              print *, 'Improper number of arguments'
              print *, 'Argument list:'
              print *, '<input file name> <output magnitude file name> <output phase file name> <sample rate> <number of periods>' 
              call close_files_and_exit	! Graceful exit, no cleanup  
          end if
        
        ! Call subroutines that obtain the command line arguments and load the appropriate variables
        call get_command_argument (1, input_file)
        call get_command_argument (2, output_mag_file)
        call get_command_argument (3, output_phase_file)
        call get_command_argument (4, sample_rate_string)
        call get_command_argument (5, num_periods_string)
        
        ! Debug code that ensures command line arguments were passed in properly
        print *, 'The first argument is (input file name) ->'
        print *, input_file
        
        print *, 'The second argument is (output magnitude file name) ->'
        print *, output_mag_file
        
        print *, 'The third argument is (output phase file name) ->'
        print *, output_phase_file
        
        print *, 'The fourth argument is (sample rate) as string ->'
        print *, sample_rate_string
        call str2int(sample_rate_string, sample_rate)
        print *, 'The fourth argument is (sample rate) as integer ->'
        print *, sample_rate
        
        print *, 'The fifth argument is (number of periods) as string ->'
        print *, num_periods_string
        call str2int(num_periods_string, num_periods)
        print *, 'The fifth argument is (number of periods) as integer ->'
        print *, num_periods
        
      end subroutine get_args
    
      subroutine open_files
      ! Begin to open files
        open (unit=1, file=TRIM(input_file), status="old", IOSTAT=file_good1, form="formatted", access="sequential", action="read")
        !open (unit=1, file=TRIM(input_file), IOSTAT=file_good1)
        if (file_good1 .ne. 0) then
          print *, 'Error opening input file!'
          call close_files_and_exit	! Graceful exit, no cleanup  
        else if (file_good1 .eq. 0) then
          print *, 'Input file opened successfully...'
        end if
        
        open (unit=10, file=TRIM(output_mag_file), status="replace", IOSTAT=file_good2, form="formatted", access="sequential")
        if (file_good2 .ne. 0) then
          print *, 'Error creating output magnitude file!'
          call close_files_and_exit	! Graceful exit, no cleanup  
        else if (file_good2 .eq. 0) then
          print *, 'Output magnitude file created successfully...'
        end if
  
        open (unit=20, file=TRIM(output_phase_file), status="replace", IOSTAT=file_good3, form="formatted", access="sequential")
        if (file_good3 .ne. 0) then
          print *, 'Error creating output phase file!'
          call close_files_and_exit	! Graceful exit, no cleanup  
        else if (file_good3 .eq. 0) then
          print *, 'Output phase file created successfully...'
        end if
  
      !   Debug code follows.  Tests to see if file opened properly.
        read (unit=1, fmt='(f20.10)', IOSTAT = EOF_marker) temp_real
        print *, 'The status of EOF_marker is'
        print *, EOF_marker
        print *, 'The first element read was'
        print *, temp_real
      !   Debug code ends
        end subroutine open_files
      
      subroutine get_num_data_elements
        ! Traverse through data file and count number of data elements in file
        ! Get first data element from file to see whether it is the EOF character
          print *, 'Entering get_num_data_elements procedure'
          print *
          i = 1
          rewind(1)
          read (1, '(f30.20)', IOSTAT = EOF_marker) temp_real
      !	Debug code starts here
          write (*, '(a, i4)') 'The counter is ->', i
          write (*, '(a, f30.20)') 'The first data element was ->', temp_real
          write (*, '(a, i4)') 'The EOF marker is ->', EOF_marker
      !	print *
      ! 	print *
      !	Debug code ends here
  
          if (EOF_marker < 0) then
              print *, 'ERROR: Input file is empty'
              call close_files_and_exit	! Graceful exit, no cleanup  
          end if
  
        ! Establish loop that will count number of elements in file
          count_loop: do
              if (EOF_marker < 0) then
                  print *, 'EOF reached'
                  exit count_loop
              else
                  i = i + 1
                  read (1, '(f30.20)', IOSTAT = EOF_marker) temp_real
                  !write (*, '(a, i4, a, f30.20)'), 'The data element at', i, ' was ->', temp_real
                  !write (*, '(a, i4)'), 'The EOF_marker is ->', EOF_marker
              end if
          end do count_loop
  
          ! Copy over the number of counted elements into the num_bins_int variable less one for the EOF character
          num_bins_int = i - 1
          write (*, '(a, i10)') 'The number of lines in the input file is ->', num_bins_int
          print *, 'Exiting get_num_data_elements procedure'
          print *
      end subroutine get_num_data_elements
    
      subroutine allocate_memory
      
      ! Allocate memory based on the number of bins
      ! The number of bins will be used to find the next power of 2.
      ! Display success status of memory allocation
      ! If there is a problem with memory allocation, display error and terminate
      
          print *, 'Entering allocate_memory procedure'
          print *
          num_log2_bins_real = log(real(num_bins_int))/log(real(2))
          write (*, '(a, f20.4)') 'The closest log base 2 bins is (as real)->', num_log2_bins_real
          num_bins_int_pow2 = 2**(ceiling(num_log2_bins_real))
          write (*, '(a, i8)') 'The closest number of power of 2 bins is (as integer)->', num_bins_int_pow2
      
          allocate (in(num_bins_int_pow2), stat=status1)
          print *
          write (*, '(a, l)') 'Was memory allocated successfully for input array? ->', allocated(in)  
          if (status1 .ne. 0) then
              print *, 'Memory allocation error on input array'
              call close_files_and_exit	! Graceful exit, no cleanup  
          end if
          write (*, '(a, i10)') 'Input Array allocated with size ->', size(in)
      
          allocate (out(num_bins_int_pow2), stat=status2)
          print *
          write (*, '(a, l)') 'Was memory allocated successfully for output array? ->', allocated(out)  
          if (status2 .ne. 0) then
              print *, 'Memory allocation error on output array'
              deallocate (in)
              call close_files_and_exit	! Graceful exit, no cleanup  
          end if
          write (*, '(a, i10)') 'Output Array allocated with size ->', size(out)
      
          allocate (temp_real_array(num_bins_int_pow2), stat=status3)
          print *
          write (*, '(a, l)') 'Was memory allocated successfully for scratchpad array? ->', allocated(temp_real_array)  
          if (status3 .ne. 0) then
              print *, 'Memory allocation error on scratchpad array'
              deallocate (in)
              deallocate (out)
              call close_files_and_exit	! Graceful exit, no cleanup  
          end if
          write (*, '(a, i10)') 'Scratchpad Array allocated with size ->', size(temp_real_array)
          print *, 'Exiting allocate_memory procedure'
          print *
      end subroutine allocate_memory
        
      subroutine read_from_file_into_array
      ! Establish a loop that reads the file from 1 to num_bins_int
      ! Bring file pointer for input file back to first element 
          print *, '***** Subroutine read_from_file_into_array ******'
          rewind(1)
          do i=1, num_bins_int
              read (1, '(f30.20)', IOSTAT = EOF_marker) temp_real_array(i)		! read data element from file
              ! Debug code starts here
              !write (*, '(a, i4)'), 'The EOF marker is ->', EOF_marker
              !write (*, '(a, i4)') 'The counter is ->', i
              !write (*, '(a, 30f30.20)') 'The data element was -> ', temp_real_array(i)
              !print *
              !print *
              ! Debug code ends here
          end do
      
          print *, 'Padding remainder of array with zeros to fill up to size of next power of 2....'
          do i=num_bins_int+1, num_bins_int_pow2
              temp_real_array(i) = 0.0
              !write (*, '(a, i4)') 'The counter is ->', i
              !write (*, '(a, 30f30.20)') 'The data element was -> ', temp_real_array(i)
          end do
      
          print *, 'Exiting read_from_file_into_array procedure'
          print *
      end subroutine read_from_file_into_array
        
      subroutine compute_DFFT
          ! *************** FFT COMPUTATION SECTION **********************
          
          ! Establish array of reals that is the input the FFTW3 function
          print *, 'Entering compute_DFFT procedure'
          print *
          
          in = temp_real_array
          print *, 'num_bins_int_pow2 ->', num_bins_int_pow2
          ! Use built in fucntion to create FFTW3 plan.  Tells FFTW3 what to do.
          plan = fftw_plan_dft_r2c_1d((num_bins_int_pow2), in, out, FFTW_ESTIMATE)
          !print *, 'The FFTW3 plan is ->'
          !call fftw_print_plan(plan)
          
          print *
          print *
              
          ! Debug code starts here
          !print *, "The contents of the input array is ->"
      ! 	do i = 1,num_bins_int_pow2			 
      ! 	  write (*, '(f30.20)'), in(i)
      ! 	end do
          ! Debug code ends here
          
          ! Execute FFTW3 to produce DFFT on data from file stored in array
          print *, "Executing DFFT on input array..."
          call fftw_execute_dft_r2c(plan, in, out)
          print *, "DFFT execution successfull"
          
  
          ! Debug code starts here
          print *, "The contents of the output array is ->"
  !     	do i = 1,num_bins_int_pow2			 
  !     	  write (*, '(f30.20, f30.20)') out(i)
  !     	end do
          ! Debug code ends here
          print *, 'Exiting compute_DFFT procedure'
          print *
      end subroutine compute_DFFT
        
      subroutine write_DFFT_to_file
          real                    :: real_float, imag_float, corrected_frequency
          real                    :: pi
          !integer(kind=8)         :: real_int, imag_int
  
          print *, 'Entering write_DFFT_to_file procedure'
          print *
      
          pi = 4*atan(1.0)
  
          num_samples = sample_rate * num_periods
          write (*, '(a, i8)') 'The number of total number of samples is -> ', num_samples
      
          ! Traverse through output array that contains the complex DFFT, calculate magnitude and
          ! write results to output file.  Since output is symmetric, we only want half of the output array.
          ! The magnitude and phase will have each data element along side its corresponding corrected frequency
          ! in two columns, separated by a space, i.e. DFFT_magnitude corrected_frequency, on each line in the output file.
          !do i = 1, 100
          do i = 1,(num_bins_int_pow2/2)
          !do i = 1, (num_bins_int/2)
  
              ! Lines below for debug
              !write (*, '(a, i4)'), 'The counter i is ->', i
              !write (*, '(a, f30.20, f30.20)'), 'The DFFT data element is -> ', out(i)
              
              ! Obtain the real part of the complex data stored in output array as float
              real_float = real(out(i))
              !write (*, '(a, f30.20)'), 'The value of real_float is ->', real_float
                  
              ! Obtain the imaginary part of the complex data stored in output array as float
              imag_float = aimag(out(i))
              !write (*, '(a, f30.20)'), 'The value of imag_float is ->', imag_float
              
              ! Obtain the corrected frequency based on the sampe rate and the next power of two of the total number of samples (i.e. 100 samples -> next power of 2 = 128.)
              !corrected_frequency = floor(float(i) * (float(sample_rate)/float(num_bins_int_pow2)))
              corrected_frequency = (float(i) * (float(sample_rate)/float(num_bins_int_pow2)))
              
              ! Since there are num_bins_int number of data elements in FFT array, and the FFT array is symmetric, we divide the magnitude by half the num_bins_int to get the normalized magnitude.
              magnitude = (sqrt((real_float**2) + (imag_float**2)))/float(num_bins_int/2)
              
              phase = (atan2(imag_float,real_float))*(180/pi)
              !write (*, '(a, i8, a, f30.20, a, f30.5)') 'Counter -> ', i, &
              !' Magnitude -> ', magnitude, ' Frequency -> ', corrected_frequency
              !write (*, '(a, f30.20)'), 'The phase is -> ', phase
              
              !print *
              
              ! Write out the DFFT magnitude, a space character and the corrected frequency
              write (10, '(f30.20, a, f30.20)', IOSTAT = good_write1) magnitude, ' ', corrected_frequency
              
              ! Write out the DFFT phase, a space character and the corrected frequency
              write (20, '(f30.20, a, f30.20)', IOSTAT = good_write2) phase, ' ', corrected_frequency
              
              if (good_write1 .ne. 0) then
                  !print *, 'The state of good_write1 is:'
                  !print *, good_write1
                  print *, 'Error writing to magnitude file!'
                  call close_files_deallocate_and_exit(-1)
              else if (good_write2 .ne. 0) then
                  !print *, 'The state of good_write2 is:'
                  !print *, good_write2
                  print *, 'Error writing to phase file!'
                  call close_files_deallocate_and_exit(-1)
              end if
  
      end do
  
      ! Tell user that output file was written successfully
      if (good_write1 .eq. 0) then
        print *, 'Output magnitude file written successfully....'
      endif
      
      if (good_write2 .eq. 0) then
        print *, 'Output phase file written successfully....'
      endif
      
      ! Deallocate memory held by plan
      call fftw_destroy_plan(plan)
      print *, 'Plan destroyed successfully....'
      ! 
      ! !   The following debug code checks to see if the Inverse DFFT will produce the original input data  
      ! !   print *, "Output file written successfully!"
      ! !     
      ! !   Swapping arrays to see if I can retrieve signal in time domain from signal in frequency domain
      ! !   in = out
      ! !     
      ! !   plan = fftw_plan_dft_1d(n, in, out, FFTW_BACKWARD, FFTW_ESTIMATE)
      ! !     
      ! !   print *, "The contents of the input array is ->"
      ! !   do i = 1,num_bins_int
      ! !     print *, in(i)
      ! !   end do
      ! !     
      ! !   print *
      ! !     
      ! !   print *, "Executing DFT on input array..."
      ! !   call fftw_execute_dft(plan, in, out)
      ! !     
      ! !   print *, "The contents of the output array is ->"
      ! !   do i = 1,num_bins_int
      ! !     print *, out(i)
      ! !   end do
      ! !     
      ! !   print *
      ! !     
      ! !   call fftw_destroy_plan(plan)
          print *, 'Exiting write_DFFT_to_file procedure'
          print *
  
    end subroutine write_DFFT_to_file
  
  end module MKDynamics_FFT
  
  program module_test
    
    use MKDynamics_FFT
    implicit none
    
    print *, 'MKDyanmics_FFT Program Version 5.'
  
    call get_args
    call open_files
    call get_num_data_elements
    call allocate_memory
    call read_from_file_into_array
    call compute_DFFT
    call write_DFFT_to_file
    call close_files_deallocate_and_exit(0)
    
    print *, 'Program completed successfully'
    print *, 'Exiting.....'  
  
  end program module_test
  
  

Compile the Fortran 2003 Code#

To compile the Fortran 2003 code, type the following command:

gfortran -o dfft_fftw3 dfft_fftw3.f08 -lfftw3

Execute the Fortran 2003 Code#

To execute the Fortran 2003 code, type the following command:

./dfft_fftw3 outfile.dat DFFT_mag_file.dat DFFT_phase_file.dat

where outfile.dat is the output of the Python script, DFFT_mag_file.dat is the DFFT magnitude DFFT_phase_file.dat is the DFFT phase (if applicable).

Visualize the DFFT Results#

The DFFT results are visualized using GNUPlot. The GNUPlot script is as follows:

set term wxt 0
set xrange [0:50]
set title "DFFT Magnitude of 10Hz Sine Wave"
set xlabel "Frequency [Hz]"
set ylabel "Arbitrary Units [Arb]"
plot "DFFT_mag_file.dat" using 1 with lines

set term wxt 1
set xrange [0:50]
set title "DFFT Phase of 10Hz Sine Wave"
set xlabel "Frequency [Hz]"
set ylabel "Phase [degrees]"
plot "DFFT_phase_file.dat" using 1 with lines

To execute the GNUPlot script, type the following command:

gnuplot -persist plot_dfft.gnu

or start GNUPlot and type the following to load the script:

load 'plot_dfft.gnu'

Results#

The magnitude and phase plots are shown below:

DFFT Magnitude Plot#

../../../../../_images/DFFT_mag_sine_wave2_10Hz1.jpeg

DFFT Phase Plot#

../../../../../_images/DFFT_phase_sine_wave2_10Hz.jpeg

Discussion#

The DFFT of the sine wave at 10 Hz was successfully computed using the FFTW3 library in Fortran. The results were visualized using GNUPlot. The magnitude and phase plots are shown above.

The phase plot above displays overall quantization noise and computation noise of FFTW3, since pure frequency sine wave with zero phase should have been a straight line at 0 degrees. This plot gives a feel for the accuracy of the computational engine.

There appears to be good correlation between the DFFT magnitude plot generated by SciLAB and the DFFT magnitude plot generated by the Fortran 2003 code. Of note are is the anomalous phase results in the output of the Fortran 2003 code. It is unknown at this time what is causing it. It would be interesting to see the results on a signal with known phase shift.

Conclusion#

The Discretized Fourier Transform of a sine wave at 10 Hz was successfully computed using the FFTW3 library in Fortran. The results were visualized using GNUPlot. The magnitude and phase plots are shown above.