Mathematical functions and Snit

Arjen Markus (11 November 2008) Here is an experiment with Snit where I want to provide a set of methods to examine mathematical functions. It is a fairly stragihtforward exercise in wrapping procedures from Tcllib and Tklib.

One particular aspect is noteworthy: in interactive use, it would be nice to invoke the function as [f 1.0], rather than [f value 1.0], as the first form is more concise and seems more natural. You need to supply an implicit method name then and this is achieved by the UnknownMethod method - the delegation mechanism in Snit works very nice.


# function_obj.tcl --
#     Mathematical functions as a Snit object
#

package require snit
package require math::calculus
package require math::optimize

# function --
#     Define the "function" type
#
::snit::type function {
    typevariable top 0
    delegate method * using {%s UnknownMethod %m}
    option -steps -default 100

    constructor {arglist body args} {
        proc ${selfns}::F $arglist $body
        $self configurelist $args
    }

    method value {x} {
        ${selfns}::F $x
    }

    method integral {a b} {
    #
    # Romberg returns the value and an error estimate. Just return the
    # value of the integral.
    #
        lindex [::math::calculus::romberg [list $self value] $a $b] 0
    }

    method table {a b} {
        set dx [expr {($b-$a)/double($options(-steps))}]
        set table {}
        for {set i 0} {$i <= $options(-steps)} {incr i} {
            set x [expr {$a + $i*$dx}]
            if { abs($x) < 0.5*$dx } {
                set x 0.0   ;# Make sure x obtains the exact value zero,
                             # if required
            }
            set r [$self value $x]
            lappend table [list $x $r]
        }
        return $table
    }

    method print {a b {format {%10.4f %10.4f}}} {
        set table [$self table $a $b]
        set result ""

        foreach row $table {
            foreach {x r} $row {break}
            append result "[format $format $x $r]\n"
        }
        return $result
    }

    method zero {a b} {
        return [::math::calculus::regula_falsi $self $a $b]
    }

    method minimum {a b} {
        return [::math::optimize::minimum $a $b $self]
    }

    method maximum {a b} {
        return [::math::optimize::maximum $a $b $self]
    }

    #
    # Not finished yet:
    # TODO: scaling, determining the right widget
    #
    method plot {a b} {
        package require Tk
        package require Plotchart

        set table [$self table $a $b]

        set ymin {}
        set ymax {}
        foreach row $table {
            foreach {x y} $row {break}
            if { $ymin == {} || $ymin > $y } { set ymin $y }
            if { $ymax == {} || $ymax < $y } { set ymax $y }
        }

        incr top
        toplevel .plot$top
        pack [canvas .plot$top.c]
        set p [::Plotchart::createXYPlot .plot$top.c \
                  [::Plotchart::determineScale $a $b] \
                  [::Plotchart::determineScale $ymin $ymax]]

        foreach row $table {
            foreach {x y} $row {break}
            $p plot graph $x $y
        }
    }

    #
    # For interactive use, the abbreviation "f 1.0" instead of
    # "f value 1.0" would be very useful ...
    #
    method UnknownMethod {subcommand args} {
        if {[string is double -strict $subcommand]} {
            ${selfns}::F $subcommand
        } else {
            error "Unknown subcommand: $subcommand"
        }
    }

    # More to come:
    # - view, deriv, create-deriv, create-integral
}

# main --
#     Testing this
#

function f {x} {expr {$x==0.0? 1.0 : sin($x)/$x}}

puts "Value at x=0: [f 0.0]"
#catch {puts [f 0.0]} msg;puts $msg; puts $errorInfo
# Alternative: [f value 1.0]

puts "Integral from 0 to pi: [f integral 0 [expr {acos(-1.)}]]"
puts "Zero between 0 and 4: [f zero 0 4]"
puts "Minimum between 0 and 10: [f minimum 0 10]"
puts "Maximum between 0 and 10: [f maximum 0 10]"
f plot -10 10

f configure -steps 20
puts [f print 0 8] 

MB: The core of your idea is to define the command in the constructor, which leads to the very natural definition of the function f, which in fact creates the object f as an instance of the class function. This is a clever use of SNIT, indeed. My simpler idea for that problem would have been based on a callback instead, with a client such as :

proc myfunc {x} {expr {$x==0.0? 1.0 : sin($x)/$x}}
function f
f configure -function myfunc

What is missing, still, is a more detailed definition of the input and output parameters, that is what is the size of x and the size of y. That may lead to different behavior of the component with respect to the numerical method applied, or how the function can be plot. The following is a brief sketch for such a class, as an extension to your example.

::snit::type function {
    option -function
    option -inputnb -default 1
    option -outputnb -default 1

    method value {x} {
        set cmd $options(-function)
        if {$cmd==""} then {
          error "Option -function is mandatory."
        }
        lappend cmd $x
        return [eval $cmd]
    }

    method minimum {a b} {
      if {$options(-outputnb)!=1} then {
        error "Multi-objective optimization is not available"
      } elseif {$options(-inputnb)==1} then {
        set result [::math::optimize::minimum $a $b $self]
      } else {
        # More complex optimization, for example a fictive SQP method
        set sqp [sqp create %AUTO%]
        $sqp configure -nbparams $options(-inputnb)
        $sqp configure -function $options(-function)
        set result [$sqp find]
        $sqp destroy
      }
      return $result
    }
}