fourier.tcl

Go to the documentation of this file.
00001 /*  fourier.tcl --*/
00002 /*     Package for discrete (ordinary) and fast fourier transforms*/
00003 /* */
00004 /*  Author: Lars Hellstrom (...)*/
00005 /* */
00006 /*  The two top-level procedures defined are*/
00007 /* */
00008 /*           dft data-list*/
00009 /*           inverse_dft data-list*/
00010 /* */
00011 /*  which take a list of complex numbers and apply a Discrete Fourier*/
00012 /*  Transform (DFT) or its inverse respectively to these lists of numbers.*/
00013 /*  A "complex number" in this case is either (i) a pair (two element*/
00014 /*  list) of numbers, interpreted as the real and imaginary parts of the*/
00015 /*  complex number, or (ii) a single number, interpreted as the real*/
00016 /*  part of a complex number whose imaginary part is zero. The return*/
00017 /*  value is always in the first format. (The DFT generally produces*/
00018 /*  complex results even if the input is purely real.) Applying first*/
00019 /*  one and then the other of these procedures to a list of complex*/
00020 /*  numbers will (modulo rounding errors due to floating point*/
00021 /*  arithmetic) return the original list of numbers.*/
00022 /* */
00023 /*  If the input length N is a power of two then these procedures will*/
00024 /*  utilize the O(N log N) Fast Fourier Transform algorithm. If input*/
00025 /*  length is not a power of two then the DFT will instead be computed*/
00026 /*  using a the naive quadratic algorithm.*/
00027 /* */
00028 /*  Some examples:*/
00029 /* */
00030 /*    % dft {1 2 3 4}*/
00031 /*    {10 0.0} {-2.0 2.0} {-2 0.0} {-2.0 -2.0}*/
00032 /*    % inverse_dft {{10 0.0} {-2.0 2.0} {-2 0.0} {-2.0 -2.0}}*/
00033 /*    {1.0 0.0} {2.0 0.0} {3.0 0.0} {4.0 0.0}*/
00034 /*    % dft {1 2 3 4 5}*/
00035 /*    {15.0 0.0} {-2.5 3.44095480118} {-2.5 0.812299240582} {-2.5 -0.812299240582} {-2.5 -3.44095480118}*/
00036 /*    % inverse_dft {{15.0 0.0} {-2.5 3.44095480118} {-2.5 0.812299240582} {-2.5 -0.812299240582} {-2.5 -3.44095480118}}*/
00037 /*    {1.0 0.0} {2.0 8.881784197e-17} {3.0 4.4408920985e-17} {4.0 4.4408920985e-17} {5.0 -8.881784197e-17}*/
00038                                    /* */
00039 /*  In the last case, the imaginary parts <1e-16 would have been zero in*/
00040 /*  exact arithmetic, but aren't here due to rounding errors.*/
00041 /* */
00042 /*  Internally, the procedures use a flat list format where every even*/
00043 /*  index element of a list is a real part and every odd index element is*/
00044 /*  an imaginary part. This is reflected in the variable names by Re_ and*/
00045 /*  Im_ prefixes.*/
00046 /* */
00047 
00048 namespace ::math::fourier {
00049    /* ::math::constants pi*/
00050 
00051    namespace export dft inverse_dft lowpass highpass
00052 }
00053 
00054 /*  dft --*/
00055 /*      Return the discrete fourier transform as a list of complex numbers*/
00056 /* */
00057 /*  Arguments:*/
00058 /*      in_data     List of data (either real or complex)*/
00059 /*  Returns:*/
00060 /*      List of complex amplitudes for the Fourier components*/
00061 /*  Note:*/
00062 /*      The procedure uses an ordinary DFT if the number of data is*/
00063 /*      not a power of 2, otherwise it uses FFT.*/
00064 /* */
00065 ret  ::math::fourier::dft (type in_, type data) {
00066     # First convert to internal format
00067     set dataL [list]
00068     set n 0
00069     foreach datum $in_data {
00070         if {[llength $datum] == 1} then {
00071             lappend dataL $datum 0.0
00072         } else {
00073             lappend dataL [lindex $datum 0] [lindex $datum 1]
00074         }
00075         incr n
00076     }
00077 
00078     # Then compute a list of n'th roots of unity (explanation below)
00079     set rootL [DFT_make_roots $n -1]
00080 
00081     # Check if the input length is a power of two.
00082     set p 1
00083     while {$p < $n} {set p [expr {$p << 1}]}
00084     # By construction, $p is a power of two. If $n==$p then $n is too.
00085 
00086     # Finally compute the transform using Fast_DFT or Slow_DFT,
00087     # and convert back to the input format.
00088     set res [list]
00089     foreach {Re Im} [
00090         if {$p == $n} then {
00091             Fast_DFT $dataL $rootL
00092         } else {
00093             Slow_DFT $dataL $rootL
00094         }
00095     ] {
00096         lappend res [list $Re $Im]
00097     }
00098     return $res
00099 }
00100 
00101 /*  inverse_dft --*/
00102 /*      Invert the discrete fourier transform and return the restored data*/
00103 /*      as complex numbers*/
00104 /* */
00105 /*  Arguments:*/
00106 /*      in_data     List of fourier coefficients (either real or complex)*/
00107 /*  Returns:*/
00108 /*      List of complex amplitudes for the Fourier components*/
00109 /*  Note:*/
00110 /*      The procedure uses an ordinary DFT if the number of data is*/
00111 /*      not a power of 2, otherwise it uses FFT.*/
00112 /* */
00113 ret  ::math::fourier::inverse_dft (type in_, type data) {
00114     # First convert to internal format
00115     set dataL [list]
00116     set n 0
00117     foreach datum $in_data {
00118         if {[llength $datum] == 1} then {
00119             lappend dataL $datum 0.0
00120         } else {
00121             lappend dataL [lindex $datum 0] [lindex $datum 1]
00122         }
00123         incr n
00124     }
00125 
00126     # Then compute a list of n'th roots of unity (explanation below)
00127     set rootL [DFT_make_roots $n 1]
00128 
00129     # Check if the input length is a power of two.
00130     set p 1
00131     while {$p < $n} {set p [expr {$p << 1}]}
00132     # By construction, $p is a power of two. If $n==$p then $n is too.
00133 
00134     # Finally compute the transform using Fast_DFT or Slow_DFT,
00135     # divide by input data length to correct the amplitudes,
00136     # and convert back to the input format.
00137     set res [list]
00138     foreach {Re Im} [
00139         # $p is power of two. If $n==$p then $n is too.
00140         if {$p == $n} then {
00141             Fast_DFT $dataL $rootL
00142         } else {
00143             Slow_DFT $dataL $rootL
00144         }
00145     ] {
00146         lappend res [list [expr {$Re/$n}] [expr {$Im/$n}]]
00147     }
00148     return $res
00149 }
00150 
00151 /*  DFT_make_roots --*/
00152 /*      Return a list of the complex roots of unity or of -1*/
00153 /* */
00154 /*  Arguments:*/
00155 /*      n           Order of the roots*/
00156 /*      sign        Whether to use 1 or -1 (for inverse transform)*/
00157 /*  Returns:*/
00158 /*      List of complex roots of unity or -1*/
00159 /* */
00160 ret  ::math::fourier::DFT_make_roots (type n , type sign) {
00161     set res [list]
00162     for {set k 0} {2*$k < $n} {incr k} {
00163         set alpha [expr {2*3.1415926535897931*$sign*$k/$n}]
00164         lappend res [expr {cos($alpha)}] [expr {sin($alpha)}]
00165     }
00166     return $res
00167 }
00168 
00169 /*  Fast_DFT --*/
00170 /*      Perform the fast Fourier transform*/
00171 /* */
00172 /*  Arguments:*/
00173 /*      dataL       List of data*/
00174 /*      rootL       Roots of unity or -1 to use in the transform*/
00175 /*  Returns:*/
00176 /*      List of complex numbers*/
00177 /* */
00178 ret  ::math::fourier::Fast_DFT (type dataL , type rootL) {
00179     if {[llength $dataL] == 8} then {
00180         foreach {Re_z0 Im_z0 Re_z1 Im_z1 Re_z2 Im_z2 Re_z3 Im_z3} $dataL {break}
00181         if {[lindex $rootL 3] > 0} then {
00182             return [list\
00183               [expr {$Re_z0 + $Re_z1 + $Re_z2 + $Re_z3}] [expr {$Im_z0 + $Im_z1 + $Im_z2 + $Im_z3}]\
00184               [expr {$Re_z0 - $Im_z1 - $Re_z2 + $Im_z3}] [expr {$Im_z0 + $Re_z1 - $Im_z2 - $Re_z3}]\
00185               [expr {$Re_z0 - $Re_z1 + $Re_z2 - $Re_z3}] [expr {$Im_z0 - $Im_z1 + $Im_z2 - $Im_z3}]\
00186               [expr {$Re_z0 + $Im_z1 - $Re_z2 - $Im_z3}] [expr {$Im_z0 - $Re_z1 - $Im_z2 + $Re_z3}]]
00187         } else {
00188             return [list\
00189               [expr {$Re_z0 + $Re_z1 + $Re_z2 + $Re_z3}] [expr {$Im_z0 + $Im_z1 + $Im_z2 + $Im_z3}]\
00190               [expr {$Re_z0 + $Im_z1 - $Re_z2 - $Im_z3}] [expr {$Im_z0 - $Re_z1 - $Im_z2 + $Re_z3}]\
00191               [expr {$Re_z0 - $Re_z1 + $Re_z2 - $Re_z3}] [expr {$Im_z0 - $Im_z1 + $Im_z2 - $Im_z3}]\
00192               [expr {$Re_z0 - $Im_z1 - $Re_z2 + $Im_z3}] [expr {$Im_z0 + $Re_z1 - $Im_z2 - $Re_z3}]]
00193         }
00194     } elseif {[llength $dataL] > 8} then {
00195         set evenL [list]
00196         set oddL [list]
00197         foreach {Re_z0 Im_z0 Re_z1 Im_z1} $dataL {
00198             lappend evenL $Re_z0 $Im_z0
00199             lappend oddL $Re_z1 $Im_z1
00200         }
00201         set squarerootL [list]
00202         foreach {Re_omega0 Im_omega0 Re_omega1 Im_omega1} $rootL {
00203             lappend squarerootL $Re_omega0 $Im_omega0
00204         }
00205         set lowL [list]
00206         set highL [list]
00207         foreach\
00208           {Re_y0 Im_y0}       [Fast_DFT $evenL $squarerootL]\
00209           {Re_y1 Im_y1}       [Fast_DFT $oddL $squarerootL]\
00210           {Re_omega Im_omega} $rootL {
00211             set Re_y1t [expr {$Re_y1 * $Re_omega - $Im_y1 * $Im_omega}]
00212             set Im_y1t [expr {$Im_y1 * $Re_omega + $Re_y1 * $Im_omega}]
00213             lappend lowL  [expr {$Re_y0 + $Re_y1t}] [expr {$Im_y0 + $Im_y1t}]
00214             lappend highL [expr {$Re_y0 - $Re_y1t}] [expr {$Im_y0 - $Im_y1t}]
00215         }
00216         return [concat $lowL $highL]
00217     } elseif {[llength $dataL] == 4} then {
00218         foreach {Re_z0 Im_z0 Re_z1 Im_z1} $dataL {break}
00219         return [list\
00220           [expr {$Re_z0 + $Re_z1}] [expr {$Im_z0 + $Im_z1}]\
00221           [expr {$Re_z0 - $Re_z1}] [expr {$Im_z0 - $Im_z1}]]
00222     } else {
00223         return $dataL
00224     }
00225 }
00226 
00227 /*  Slow_DFT --*/
00228 /*      Perform the ordinary discrete (slow) Fourier transform*/
00229 /* */
00230 /*  Arguments:*/
00231 /*      dataL       List of data*/
00232 /*      rootL       Roots of unity or -1 to use in the transform*/
00233 /*  Returns:*/
00234 /*      List of complex numbers*/
00235 /* */
00236 ret  ::math::fourier::Slow_DFT (type dataL , type rootL) {
00237     set n [expr {[llength $dataL] / 2}]
00238 
00239     # The missing roots are computed by complex conjugating the given
00240     # roots. If $n is even then -1 is also needed; it is inserted explicitly.
00241     set k [llength $rootL]
00242     if {$n % 2 == 0} then {
00243         lappend rootL -1.0 0.0
00244     }
00245     for {incr k -2} {$k > 0} {incr k -2} {
00246         lappend rootL [lindex $rootL $k]\
00247           [expr {-[lindex $rootL [expr {$k+1}]]}]
00248     }
00249 
00250     # This is strictly following the naive formula.
00251     # The product jk is kept as a separate counter variable.
00252     set res [list]
00253     for {set k 0} {$k < $n} {incr k} {
00254         set Re_sum 0.0
00255         set Im_sum 0.0
00256         set jk 0
00257         foreach {Re_z Im_z} $dataL {
00258             set Re_omega [lindex $rootL [expr {2*$jk}]]
00259             set Im_omega [lindex $rootL [expr {2*$jk+1}]]
00260             set Re_sum [expr {$Re_sum +
00261               $Re_z * $Re_omega - $Im_z * $Im_omega}]
00262             set Im_sum [expr {$Im_sum +
00263               $Im_z * $Re_omega + $Re_z * $Im_omega}]
00264             incr jk $k
00265             if {$jk >= $n} then {set jk [expr {$jk - $n}]}
00266         }
00267         lappend res $Re_sum $Im_sum
00268     }
00269     return $res
00270 }
00271 
00272 /*  lowpass --*/
00273 /*      Apply a low-pass filter to the Fourier transform*/
00274 /* */
00275 /*  Arguments:*/
00276 /*      cutoff      Cut-off frequency*/
00277 /*      in_data     Input transform (complex data)*/
00278 /*  Returns:*/
00279 /*      Filtered transform*/
00280 /* */
00281 ret  ::math::fourier::lowpass (type cutoff , type in_, type data) {
00282     package require math::complexnumbers
00283 
00284     set res    [list]
00285     set cutoff [list $cutoff 0.0]
00286     set f      0.0
00287     foreach a $in_data {
00288        set an [::math::complexnumbers::/ $a \
00289                   [::math::complexnumbers::+ {1.0 0.0} \
00290                       [::math::complexnumbers::/ [list 0.0 $f] $cutoff]]]
00291        lappend res $an
00292        set f [expr {$f+1.0}]
00293     }
00294 
00295     return $res
00296 }
00297 
00298 /*  highpass --*/
00299 /*      Apply a high-pass filter to the Fourier transform*/
00300 /* */
00301 /*  Arguments:*/
00302 /*      cutoff      Cut-off frequency*/
00303 /*      in_data     Input transform (complex data)*/
00304 /*  Returns:*/
00305 /*      Filtered transform (high-pass)*/
00306 /* */
00307 ret  ::math::fourier::highpass (type cutoff , type in_, type data) {
00308     package require math::complexnumbers
00309 
00310     set res    [list]
00311     set cutoff [list $cutoff 0.0]
00312     set f      0.0
00313     foreach a $in_data {
00314        set ff [::math::complexnumbers::/ [list 0.0 $f] $cutoff]
00315        set an [::math::complexnumbers::/ $ff \
00316                   [::math::complexnumbers::+ {1.0 0.0} $ff]]
00317        lappend res $an
00318        set f [expr {$f+1.0}]
00319     }
00320 
00321     return $res
00322 }
00323 
00324 /* */
00325 /*  Announce the package*/
00326 /* */
00327 package provide math::fourier 1.0.2
00328 
00329 /*  test --*/
00330 /* */
00331 ret  test_dft (type points , optional real =0 , optional iterations =20) {
00332     set in_dataL [list]
00333     for {set k 0} {$k < $points} {incr k} {
00334         if {$real} then {
00335             lappend in_dataL [expr {2*rand()-1}]
00336         } else {
00337             lappend in_dataL [list [expr {2*rand()-1}] [expr {2*rand()-1}]]
00338         }
00339     }
00340     set time1 [time {
00341         set conv_dataL [::math::fourier::dft $in_dataL]
00342     } $iterations]
00343     set time2 [time {
00344         set out_dataL [::math::fourier::inverse_dft $conv_dataL]
00345     } $iterations]
00346     set err 0.0
00347     foreach iz $in_dataL oz $out_dataL {
00348         if {$real} then {
00349             foreach {o1 o2} $oz {break}
00350             set err [expr {$err + ($i-$o1)*($i-$o1) + $o2*$o2}]
00351         } else {
00352             foreach i $iz o $oz {
00353                 set err [expr {$err + ($i-$o)*($i-$o)}]
00354             }
00355         }
00356     }
00357     return [format "Forward: %s\nInverse: %s\nAverage error: %g"\
00358       $time1 $time2 [expr {sqrt($err/$points)}]]
00359 }
00360 
00361 /*  Note:*/
00362 /*  Add simple filters*/
00363 
00364 if { 0 } {
00365 puts [::math::fourier::dft {1 2 3 4}]
00366 puts [::math::fourier::inverse_dft {{10 0.0} {-2.0 2.0} {-2 0.0} {-2.0 -2.0}}]
00367 puts [::math::fourier::dft {1 2 3 4 5}]
00368 puts [::math::fourier::inverse_dft {{15.0 0.0} {-2.5 3.44095480118} {-2.5 0.812299240582} {-2.5 -0.812299240582} {-2.5 -3.44095480118}}]
00369 puts [test_dft 10]
00370 puts [test_dft 16]
00371 puts [test_dft 100]
00372 puts [test_dft 128]
00373 
00374 puts [::math::fourier::dft {1 2 3 4}]
00375 puts [::math::fourier::lowpass 1.5 [::math::fourier::dft {1 2 3 4}]]
00376 }
00377 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1