Version 2 of Calling Maxima from Tcl

Updated 2004-07-23 10:42:28 by TV

TV

I've thus far tested Maxima on windows (XP), and it proved to be usefull and useable, not in the least because of it's integration with tcl, though maybe that isn't perfect from a strict programmer's point of view.

The mechanism which is used in maxima to interface between the Tcl and Tk supplied user interface (at least on windows) is that a maxima process communicates with the Tcl/Tk interface process over a socket, over which mainly text is being send back and forth containing mathematica statements/commands/formulas and the answers from the maxima 'engine', a technique succesfully used in professional software of various kinds. The possible disadvantage is that for each communication, process change is needed in the process time slicer, which probably works at at most a granularity of a millisecond or so. Most maxima commands aren't executed blazing fast, and usually don't really need to, so that in practice is probably not a granularity problem.

As stated on the maxima page, the way normally the tcl/tk + maxima 'program' maxima.exe communicates the user input to the maxima lisp based core primarily by reading from and outputting to a text widget, which from UI perspective is not a bad idea. In fact it appears the maxima.exe is a tclkit (tcl+tk+incrtcl+metakit) appearently calling some scripts, and in the same dir with as.exe, gcc.exe, and some dlls. Not very handy in my opinion, I'd prefer a few script files which simply normally start up tcl/tk of which I control the installation, which works fine for me, and would seem to require less files, and be more overseeable. Maybe I'll try, and in the process see if that way a more recent tcl/tk can be used.

The maxima tcl procedure to make user interactions work is that somehow pressing return in the .maxima.text widget invokes the routine CMeval with (normally) the window name .maxima.text as only argument. This procedure does some regexpr stuff and basically pushes the maxima input line where the cursor is through the maxima command socket stream to the engine.

The socket of this non-blocking connection has a fileevent readable routine scheduled to activate when data arrives back from the engine: maximaFilter .maxima.text sock156 (second argument depending on session of course). There seem to be various return kinds, and it could be data can be multi line and maybe even intra-line split up in seperate return packets.

I've come up with a first simple (read not too tested, subjected to the above considerations of possible failure) way to have a procedure to give a expression to maxima, and have the return value return to the tcl caller. I've changed the

 proc maximaFilter { {win} {sock} } {
    linkLocal $win  plotPending
    global pdata
 #TV added:
   global passres
 ###
    if { [eof $sock] } {
        # puts "at end"
        close $sock
        return ""
    }
    set it [read $sock]
    # puts "read=<$it>"
    if { [string first "\032\032" $it] >= 0 &&
         [regexp  -indices "\032\032(\[^:]+):(\[0-9]+):\[^\n]*\n" $it junk file line] } {

        dblDisplayFrame [getMatch $it $file] [getMatch $it $line]
        append res [string range $it 0 [expr { [lindex $junk 0] -1 } ]]
        append res [string range $it [expr { 1+[lindex $junk 1]}] end]
        set it $res
    }
    if { [string first "\032\031tcl:" $it] >= 0 &&  [regexp  -indices "\032\031tcl:(\\[^\n]*)\n" $it junk com]} {
        eval $com
        append res [string range $it 0 [expr { [lindex $junk 0] -1 } ]]
        append res [string range $it [expr { 1+[lindex $junk 1]}] end]
        set it $res
    }
    # puts it=<$it>
    if { [regexp -indices "\{plot\[d23]\[fd]" $it inds] } {
        set plotPending [string range $it [lindex $inds 0] end]
        set it ""
        if { [regexp {\(C[0-9]+\) $} $it ff] } {
            regexp "\{plot\[d23]\[df].*\}" $ff it
            #        set it $ff
        }
    }
    if { [info exists plotPending] } {
        #puts "plotPending=<$plotPending>,it=<$it>"
        append plotPending $it
        set it ""
        if { [regexp -indices "\n\\(D\[0-9\]+\\)" $plotPending  inds] } {
            set it [string range $plotPending [lindex $inds 0] end]
            set plotPending [string range $plotPending 0 [lindex $inds 0]]
            set data $plotPending
            unset plotPending
            #puts itplot=<$it>,$inds
            #puts plotdata=<$data>
            doShowPlot $win $data

        }
    }

    $win insert end $it "output"
    $win mark set  lastStart "end -1char"
 #TV added:
 if [string compare -length 2 $it "(D"] {
      set i [string first "\n(C" $it];
      if {$i <= 0} {set i end-1} {incr i -1}
      set passres [string range $it [expr [string first )\  $it]+2] $i]
   }
 ###
    if { [regexp {\(C[0-9]+\) $|\(dbm:[0-9]+\) $|([A-Z]+>[>]*)$} $it junk lisp]  } {
        #puts "junk=$junk, lisp=$lisp,[expr { 0 == [string compare $lisp {}] }]"
        #puts "it=<$it>,pdata={[array get pdata *]},[$win index end],[$win index insert]"

        if { [info exists pdata($sock,wait) ] && $pdata($sock,wait) > 0 } {
            #puts "it=<$it>,begin=$pdata($sock,begin),end=[$win index {end linestart}]"
            #puts dump=[$win dump -all "insert -3 lines" end]
            setAct pdata($sock,result) [$win get $pdata($sock,begin) "end -1char linestart" ]
            #puts result=$pdata($sock,result)
            set pdata($sock,wait) 0
        }
        $win mark set lastStart "end -1char"
        $win tag add  input "end -1char" end
        oset $win atMaximaPrompt [expr { 0 == [string compare $lisp ""] }]

    }
    $win see end
    return
 } 

Now the routine to do the maxima calling from tcl:

 proc domax {e} {
   global passres;
   .maxima.text insert insert "$e;\n" ;
   CMeval .maxima.text;
   set passres {};
   while {$passres == ""}  {vwait passres};
   return $passres
 }

And it's needed to set maxima's reply mode to 1 dimensional, so that formatting is simple formula return which can be fed back even to a new evaluation by maxima, and so that my output line parse finds the number of the result (DXX) at the beginning, and not in the middle line of the output:

 domax "display2d:false"

Now we can use:

   domax 1+5
 6
   domax "solve(x^2+x+1=0,x)"
 [x = -(SQRT(3)*%I+1)/2,x = (SQRT(3)*%I-1)/2]

Note that at this moment, there is no real error handling, and te domax can hang (leaving the maxima console running, type a ; on a new prompt to mostly make domax return...), but the principle appears to work.