4.7 Creating a new Pseudo-tty Interface

You can create Sage pseudo-tty interfaces that allow Sage to work with almost any command-line program, and which don't require any modification or extensions to that program. They are also surprisingly fast and flexible (given how they work!), because all IO is buffered, and because interaction between Sage and the command line program can be non-blocking (asynchronous); this is because they all derive from the Sage class Expect, which handles the communication between Sage and the external process.

For example, here is part of the file SAGE_ROOT/devel/sage/sage/interfaces/octave.py, which defines an interface between Sage and Octave, an open-source program for doing numerical computations, among other things.

import os
from expect import Expect, ExpectElement

class Octave(Expect):
The first two lines import the library os, which contains operating system routines, and also class Expect, which is the basic class for interfaces. The third line defines the class Octave: it derives from Expect. After this comes a docstring, which we omit here - see the file for details. Next comes:
    def __init__(self, maxread=100, script_subdirectory="", logfile=None, 
                 server=None, server_tmpdir=None):
        Expect.__init__(self,
                        name = 'octave',
                        prompt = '>',
                        command = "octave --no-line-editing --silent",
                        maxread = maxread,
                        server = server, 
                        server_tmpdir = server_tmpdir,
                        script_subdirectory = script_subdirectory,
                        restart_on_ctrlc = False,
                        verbose_start = False,
                        logfile = logfile,
                        eval_using_file_cutoff=100)
This uses the class Expect to set up the Octave interface.
    def set(self, var, value):
        """
        Set the variable var to the given value.
        """
        cmd = '%s=%s;'%(var,value)        
        out = self.eval(cmd)
        if out.find("error") != -1:
            raise TypeError, "Error executing code in Octave\nCODE:\n\t%s\nOctave ERROR:\n\t%s"%(cmd, out)

    def get(self, var):
        """
        Get the value of the variable var.
        """
        s = self.eval('%s'%var)
        i = s.find('=')
        return s[i+1:]

    def console(self):
        octave_console()
These let users type octave.set('x', 3), after which octave.get('x') returns ' 3'. Running octave.console() dumps the user into Octave interactive shell.
    def solve_linear_system(self, A, b):
        """
        Use octave to compute a solution x to A*x = b, as a list. 

        INPUT:
            A -- mxn matrix A with entries in QQ or RR
            b -- m-vector b entries in QQ or RR (resp)

        OUTPUT:
            An list x (if it exists) which solves M*x = b

        EXAMPLES:
            sage: M33 = MatrixSpace(QQ,3,3)
            sage: A   = M33([1,2,3,4,5,6,7,8,0])
            sage: V3  = VectorSpace(QQ,3)
            sage: b   = V3([1,2,3])
            sage: octave.solve_linear_system(A,b)    # requires optional octave
            [-0.33333299999999999, 0.66666700000000001, -3.5236600000000002e-18]

        AUTHOR: David Joyner and William Stein
        """
        m = A.nrows()
        n = A.ncols()
        if m != len(b):
            raise ValueError, "dimensions of A and b must be compatible"
        from sage.matrix.all import MatrixSpace
        from sage.rings.all import QQ
        MS = MatrixSpace(QQ,m,1) 
        b  = MS(list(b)) # converted b to a "column vector"
        sA = self.sage2octave_matrix_string(A)
        sb = self.sage2octave_matrix_string(b)
        self.eval("a = " + sA )
        self.eval("b = " + sb )
        soln = octave.eval("c = a \\ b")
        soln = soln.replace("\n\n ","[")
        soln = soln.replace("\n\n","]")
        soln = soln.replace("\n",",")
        sol  = soln[3:]
        return eval(sol)
This code defines the method solve_linear_system, which works as documented.

These are only excerpts from octave.py; check that file for more definitions and examples. Look at other files in the directory SAGE_ROOT/devel/sage/sage/interfaces/ for examples of interfaces to other software packages.

See About this document... for information on suggesting changes.