Version 0 of Discrete Fourier Transform

Updated 2004-03-22 08:11:32

Arjen Markus (22 march 2004) Fourier series and transforms are a powerful mathematical technique. The script below implements the so-called discrete Fourier transform. It might grow into a complete package for dealing with Fourier transforms one day.


 # dft.tcl --
 #   Discrete Fourier transformation
 #
 #
 namespace eval ::Fourier {
    variable PI [expr {4.0*atan(1.0)}]
 }

 # dft --
 #    Perform a discrete Fourier transformation and return the
 #    results as a list
 # Arguments:
 #    data        The data to transform (a simple list)
 # Result:
 #    A list of cosine and sine amplitudes
 # Note:
 #    The base period is implicitly taken as 1, so the period
 #    of component j is 1/j (j>=1).
 #    To preserve information about the length of the original
 #    list of data, the last element (which has no meaning any
 #    other way) is set to {} for an even number of data.
 #
 proc ::Fourier::dft { data } {
   variable PI
   set result {}

   set length  [llength $data]
   set nocomps [expr {$length/2}]
   set even    [expr {$length%2==0}]

   for { set j 0 } { $j <= $nocomps } { incr j } {
      set omega  [expr {2.0*$PI*$j/double($length)}]
      set sumcos 0.0
      set sumsin 0.0
      set phi    0.0

      foreach d $data {
         set sumcos [expr {$sumcos+$d*cos($omega*$phi)}]
         set sumsin [expr {$sumsin+$d*sin($omega*$phi)}]
         set phi    [expr {$phi+1.0}]
      }

      if { $j < $nocomps || ! $even } {
         lappend result [expr {2.0*$sumcos/$length}] [expr {2.0*$sumsin/$length}]
      } else {
         lappend result [expr {2.0*$sumcos/$length}] {}
      }
   }

   return $result
 }

 # estimate --
 #    Estimate the value at a given coordinate
 # Arguments:
 #    comps        The amplitude of the components (a simple list)
 #    xval         The value of the independent coordinate
 # Result:
 #    Estimated value
 # Note:
 #    The method is numerically unstable, but it should be okay with
 #    small series
 #
 proc ::Fourier::estimate { comps xval } {
   variable PI
   set value 0.0

   set length  [llength $comps]
   set nodata  [expr {$length-1}]
   set even    [expr {[lindex $comps end] == {}}]
   if { $even } {incr nodata -1}


   set omega  [expr {2.0*$PI*$xval}]
   set sumcos 0.0
   set sumsin 0.0
   set phi    1.0

   set sumcos [expr {[lindex $comps 0]/2.0}]

   set last end
   if { $even } { set last end-2 }
   foreach {c s} [lrange $comps 2 $last] {
      set sumcos [expr {$sumcos+$c*cos($omega*$phi)}]
      set sumsin [expr {$sumsin+$s*sin($omega*$phi)}]
      set phi    [expr {$phi+1.0}]
   }
   if { $even } {
      foreach {c s} [lrange $comps end-1 end] {
         set sumcos [expr {$sumcos+$c*cos($omega*$phi)/2.0}]
      }
   }
   set value [expr {$sumcos+$sumsin}]

   return $value
 }

 # energy --
 #    Determine the "energy" density or the cumulative energy of a
 #    Fourier series
 # Arguments:
 #    option       Type of output: -density (default) or -cumulative
 #    comps        Fourier series
 # Result:
 #    Energy density or the cumulative energy
 #
 proc ::Fourier::energy { args } {
   variable PI

   if { [llength $args] == 0 || [llength $args] > 2} {
      return -code error "Should be: energy ?option? comps"
   }
   if { [llength $args] == 1 } {
      set option "-density"
      set comps  [lindex $args 0]
   } else {
      set option [lindex $args 0]
      set comps  [lindex $args 1]
   }

   set length  [llength $comps]
   set nodata  [expr {$length-1}]
   set even    [expr {[lindex $comps end] == {}}]
   if { $even } {incr nodata -1}

   set result [list [expr {$nodata*pow([lindex $comps 0]/2.0,2)}]]
   set sum    0.0
   if { $option == "-cumulative" } { set sum [lindex $result 0] }

   foreach {c s} [lrange $comps 2 end] {
      if { $s == {} } {
         set s 0.0
         set c [expr {$c/sqrt(2.0)}]
      }
      set sum    [expr {$sum+0.5*$nodata*($c*$c+$s*$s)}]
      lappend result $sum
      if { $option == "-density" } {
         set sum 0.0
      }
   }

   return $result
 }

 # truncate --
 #    Truncate the Fourier series (all components after a given one are
 #    set to zero)
 # Arguments:
 #    last         Index of the last component
 #    comps        Fourier series
 # Result:
 #    List with trailing zeroes
 #
 proc ::Fourier::truncate { last comps } {

   set length  [llength $comps]
   set nodata  [expr {$length-1}]
   set even    [expr {[lindex $comps end] == {}}]
   if { $even } {incr nodata -1}

   set result  {}
   set idx     0

   foreach {c s} $comps {
      if { $idx > $last } {
         set c 0.0
         if { $s != {} } {
            set s 0.0
         }
      }

      lappend result $c $s
      incr idx
   }

   return $result
 }

 # main --
 #    Small test of the procedures
 #
 puts [::Fourier::dft {1.0 0.0 0.0}]
 puts [::Fourier::dft {1.0 0.0 0.0 0.0}]
 puts [::Fourier::dft {1.0 1.0 1.0 1.0}]
 puts [::Fourier::dft {1.0 1.0 -1.0 -1.0}]

 puts [::Fourier::invdft [::Fourier::dft {1.0 0.0 0.0}]]
 puts "Energy:"
 puts [set series1 [::Fourier::dft {1.0 0.0 0.0 0.0 0.0 0.0 0.0}]]
 puts [set series2 [::Fourier::dft {1.0 0.0 1.0 0.0 1.0 0.0}]]
 puts "Density:    [::Fourier::energy $series1]"
 puts "Cumulative: [::Fourier::energy -cumulative $series1]"
 puts "Density:    [::Fourier::energy $series2]"
 puts "Cumulative: [::Fourier::energy -cumulative $series2]"

 puts "Truncate:"
 puts [::Fourier::truncate 2 $series1]
 puts [set series3 [::Fourier::truncate 2 $series2]]
 puts "Energy of truncated series: [::Fourier::energy -cumulative $series3]"

 puts "Estimates:"
 puts [::Fourier::estimate $series2 0.0]
 puts [::Fourier::estimate $series2 0.1]
 puts [::Fourier::estimate $series2 0.16667]
 puts [::Fourier::estimate $series2 0.2]
 puts [::Fourier::estimate $series2 0.3]
 puts [::Fourier::estimate $series2 0.33333]
 puts [::Fourier::estimate $series2 0.4]

[ Category Mathematics

Category Numerical Analysis]

]