# fourier.tcl -- # Package to manipulate Fourier series # # Author: Arjen Markus (arjen.markus@wldelft.nl) # # Notes on the implementation: # 1. The Fourier series is represented as a tagged list, where # the tag is either FOURIER or FOURIER-HALF. Aftre the tag # follow two values, the amplitudes for the cosine and sine # components. # 2. The interval of interest is always [-1,1] # 3. The function is supposed to be periodic on an interval [-1,1] # or on the interval [-2,2], indicated as FOURIER-HALF # # math::fourier -- # Namespace for the commands # namespace eval ::math::fourier { namespace export sineFourier toTimeseries # Possible extension: plenty! } # sineFourier -- # Construct a Fourier series based on sines only from an expression # for the amplitudes (so an antisymmetric function results) # # Arguments: # ncomps Number of components # var Name of the variable in the expression (the index of # the component) # expression Expression to determine the amplitudes # option Whether the interval [-1,1] covers the whole # period or only half of it (-half). Optional # Return value: # Tagged list # proc ::math::fourier::sineFourier { ncomps var expression {option {}} } { if { $option == "-half" } { set series "FOURIER-HALF" } else { set series "FOURIER" } # # TODO: properly evaluate other variables in caller's scope # lappend series 0.0 0.0 for { set $var 0 } { [set $var] < $ncomps } { incr $var } { set ampl [expr $expression] lappend series 0.0 $ampl } return $series } # toTimeseries -- # Return the values for equally spaced points # # Arguments: # series Fourier series to use # npoints Number of points # Return value: # List of values # proc ::math::fourier::toTimeseries { series npoints } { set tag [lindex $series 0] if { $tag == "FOURIER-HALF" } { set period -0.5 } elseif { $tag == "FOURIER" } { set period 0.0 } else { error "Series is not a Fourier series" } if { $npoints < 2 } { error "Number of points must be at least 2" } set ncomps [expr {([llength $series]-1)/2}] set delx [expr {2.0/($npoints-1)}] set a0 [lindex $series 1] for { set j 0 } { $j < $npoints } { incr j } { lappend result $a0 } set elem 3 for { set i 1 } { $i < $ncomps } { incr i } { set acos [lindex $series $elem] incr elem set asin [lindex $series $elem] incr elem set period [expr {$period+1.0}] set newresult {} for { set j 0 } { $j < $npoints } { incr j } { set x [expr {-1.0+$j*$delx}] set resx [lindex $result $j] set acosx [expr {$acos * cos(3.1415926*$period*$x)}] set asinx [expr {$asin * sin(3.1415926*$period*$x)}] set resx [expr {$resx + $acosx + $asinx}] lappend newresult $resx } set result $newresult } return $result } # plotTimeseries -- # Plot a timeseries # # Arguments: # series Fourier series to use # npoints Number of points # colour Which colour to use # Return value: # List of values # proc ::math::fourier::plotTimeseries { series npoints colour } { set yvalues [toTimeseries $series $npoints] set x0 -1.0 set dx [expr {2.0/($npoints-1)}] set xvalues {} set x1 $x0 set y1 [lindex $yvalues 0] for { set i 1 } { $i < $npoints } { incr i } { set x2 [expr {$x0+$dx*$i}] set y2 [lindex $yvalues $i] foreach {sx1 sy1} [scaleToCanvas $x1 $y1] {break} foreach {sx2 sy2} [scaleToCanvas $x2 $y2] {break} .c create line $sx1 $sy1 $sx2 $sy2 -fill $colour set x1 $x2 set y1 $y2 } } # scaleToCanvas -- # Scale points for drawing on the canvas # # Arguments: # x X coordinate # y Y coordinate # Return value: # List of scaled x and y # proc scaleToCanvas { x y } { global scale set sx [expr {$scale(pxmin)+($x-$scale(xmin))*$scale(xfactor)}] set sy [expr {$scale(pymax)-($scale(ymax)-$y)*$scale(yfactor)}] return [list $sx $sy] } # setCanvasScales -- # Set the drawing area and the extreme coordinates for drawing # # Arguments: # area List of pixel coordinates: xmin, ymin, xmax, ymax # coords List of world coordinates: xmin, ymin, xmax, ymax # Return value: # None # Side effect: # Calculation of coordinate transformation # proc setCanvasScale { area coords } { global scale set scale(pxmin) [lindex $area 0] set scale(pymax) [lindex $area 1] ;# Because of inversion of y-coordinate set scale(xmin) [lindex $coords 0] set scale(ymax) [lindex $coords 3] set xmin [lindex $coords 0] set xmax [lindex $coords 2] set pxmax [lindex $area 2] set xfactor [expr {($pxmax-$scale(pxmin))/($xmax-$xmin)}] set ymin [lindex $coords 1] set ymax [lindex $coords 3] set pymin [lindex $area 3] set yfactor [expr {($scale(pymax)-$pymin)/($ymax-$ymin)}] set scale(xfactor) $xfactor set scale(yfactor) $yfactor return } # # Some simple tests # catch { console show } if {[file tail $::argv0] == [file tail [info script]]} { namespace import ::math::fourier::* # This Fourier series converges to the function: # f(x) = -1/2 if x < 0 # f(x) = 1/2 if x >= 0 set f [sineFourier 20 k {1.0/($k+0.5)/3.1415926} -half] puts $f set values [toTimeseries $f 41] puts $values if { [info exists tk_version] } { canvas .c -width 400 -height 300 pack .c -fill both setCanvasScale {50 50 380 280} {-1.0 -1.0 1.0 1.0} foreach {xaxis1 yaxis1} [scaleToCanvas -1.0 -1.0] {break} foreach {xaxis2 yaxis2} [scaleToCanvas 1.0 1.0] {break} foreach {xaxis3 yaxis3} [scaleToCanvas 0.0 0.0] {break} .c create line $xaxis1 $yaxis3 $xaxis2 $yaxis3 -fill black .c create line $xaxis3 $yaxis1 $xaxis3 $yaxis2 -fill black foreach {ncomps colour} { 1 blue 2 green 3 yellow 4 red 5 magenta 20 black} { set series [sineFourier $ncomps k {1.0/($k+0.5)/3.1415926} -half] plotTimeseries $series 100 $colour } } }
TV Time for some graphics?AM That would surely improve the script :) But then I was thinking also of procs to manipulate the Fourier series (simulate the effect of low- and high-pass filters for instance, do basic arithmetic and so on). Well, it is but a first exercise ...Okay, I enhanced the script with a colourful graph.
AM I have continued the experiment with a Discrete Fourier Transform