GeoffM My derivation from Bernoulli.
I modified the basic plotf to handle multiple graphs, add a title, set colours and other options. Then added a call to RungeKutta4 to solve the damped harmonic oscillator equation (as an example).
package require Tk package require Plotchart console show # plotf -- # Convenience procedure to plot a function of one variable or # call a user defined proc with arguments: # # Arguments: # range Range over which to plot the function (x-axis) # expression Expression by which the function is defined # (independent variable: t) # proc plotf {range expression args} { # intial is the initial conditions # nosteps - number of time steps to progress. # args are arguments to be interpreted; currently # -title "XXX" # -nosteps Number of steps to use # Very simple for now: no parameters set nosteps 100 foreach {name value} $args { switch -- $name { "-nosteps" {set nosteps $value} "-initial" {set x $value} default {} } } foreach {tmin tmax} $range {break} set tstep [expr {($tmax-$tmin)/double($nosteps)}] if { ![winfo exists .c] } { pack [canvas .c] } else { .c delete all } set tvalues {} set xvalues {} set xmin {} set xmax {} for {set i 0} {$i < $nosteps+1} {incr i} { set t [expr {$tmin + $i*$tstep}] if {[info command $expression]!={}} { # Note that RungeKutta expects start of time step as time set x [$expression [expr {$t-$tstep}] $x $tstep] } else { set x [expr $expression] } lappend tvalues $t lappend xvalues $x if {$xmin=={}} {set nvalues [llength $x]} foreach xvalu $x { ;# handle vector of results if { $xmin == {} || $xvalu < $xmin } { set xmin $xvalu } if { $xmax == {} || $xvalu > $xmax } { set xmax $xvalu } } } set tscale [::Plotchart::determineScale $tmin $tmax] set xscale [::Plotchart::determineScale $xmin $xmax] set p [::Plotchart::createXYPlot .c $tscale $xscale] set i 0 foreach {name value} $args { switch -- $name { "-title" { .c create text 200 50 -text $value } "-colours" - "-colors" { set i 0 foreach col $value { $p dataconfig function$i -colour $col incr i } } default {} } } foreach t $tvalues x $xvalues { set i 0 while {$i<$nvalues} { ;# handle vector of results $p plot function$i $t [lindex $x $i] incr i } } } # now define a runge-kutta- solver package require math::calculus ;# supplies RK4 algorithm proc dampened_oscillator {t xvec} { # called by RungeKuttaStep. Returns list of derivatives of xvec. global damp global k set x [lindex $xvec 0] ;# pos set speed [lindex $xvec 1] ;# speed # Solves the damped oscillator equation: x'' = -k*x-damp*speed # where x is position, and x'' the second time derivative. # This equation is broken into 2 first order equations: # dx/dt=v # dv/dt= k.x-damp*speed # k is the 'spring constant' of the oscillator; # more negative gives higher oscillation frequency. # ::math::calculus::rungeKuttaStep expects to receive # a list of derivatives at time t. return [list $speed [expr {$k*$x-$damp*$speed}]] } proc osc {t x tstep} { ;# return the value at the time t+dt for step tstep # x is a list of values (position, speed for the moving part) return [::math::calculus::rungeKuttaStep $t $tstep $x dampened_oscillator] } set damp -0.2 set k -9 set ready 1 while {$damp<1.0} { plotf {0 10} osc -initial {0 1} -nosteps 100 -title "Damping $damp" -colours {red blue} set damp [expr {$damp+0.1}]; after 1000 {incr ready} ;# delays next solution vwait ready }
AM Interesting - I have separated the GUI component from the computational component to make sure that can be used separately. It would be possible to merge this version with mine - but note that Tclode has much more sophisticated methods than math::calculus does.