Calling Matlab from Python

I've spent the past day or so working through the details of invoking against Matlab through COM, and thought I'd pass along some tips for anyone else struggling with this.

To keep things simple, I chose to do a static compile of the Matlab typelibrary, so I ran makepy against the mlapp.tlb file that ships with Matlab, and got a file with the lovely name of "554F6052-79D4-11D4-B067-009027BA5F81x0x1x0.py", containing all the method calls and argument mappings to the Matlab COM interface. For simplicity, I renamed this file matlabCOM.py, and copied it to my test development directory.

I couldn't find much documentation on the ins and outs of this interface, but fortunately the methods are fairly simply named, so I had a good idea where to get started. Once you acquire a handle to the Matlab.Application object, you then have these methods and properties to work with (this info is copied from the Object Browser window in VB, so the syntax is VB-ish):

- Function Execute(Name As String) As Strinng
- Function GetCharArray(Name As String, Worrkspace As String) As String
- Sub GetFullMatrix(Name As String, Workspaace As String, pr() As Double, pi() As Double)
- Sub MaximizeCommandWindow()
- Sub MinimizeCommandWindow()
- Sub PutCharArray(Name As String, Workspacce As String, charArray As String)
- Sub PutFullMatrix(Name As String, Workspaace As String, pr() As Double, pi() As Double)
- Sub Quit()
- Property Visible As Long

Here are my notes:

  1. I could not get Visible or MaximizeCommandWindow to do anything. The external Matlab window does show up on the task bar until Quit() is called, and I was able to manually maximize this and enter commands for debugging.

  2. Execute() takes a command as you would type it into the Matlab window, and returns a Unicode string of the results as Matlab would display them. This is a crude way to get data into and out of Matlab, but the more efficient manner is using Get/PutFullMatrix(). The output string can also be parsed for error and warning messages.

  3. Get/PutFullMatrix provide the API to access the variable space by name in Matlab. Let's start with PutFullMatrix. Matlab is all about matrices, and provides built in support for complex numbers. Since few external languages support complex numbers, the API breaks apart the real and imaginary parts into the pr and pi arguments. These arguments will accept Python lists as follows:

    • list of numbers is interpreted as a vector of values
    • list of lists is interpreted as a list of matrix rows; all row lists in a given matrix should be the same length
    • pr and pi should have the same structure, but pi can be simply [] if there are no imaginary parts to any of the values
  4. The pr and pi arguments to GetFullMatrix should be passed as None - the actual values are returned as a (pr,pi) tuple.

  5. The default workspace name is "base".

  6. The Get/PutCharArray methods purportedly work with Unicode, but I have not had this cause me any special trouble - it looks just like a string so far.

To clean up the client-side access to this interface, I encapsulated some of these "special" characteristics into a Python class. This class takes care of converting complex data to and from split lists for real and imaginary parts, adds default values for workspace name, and passes appropriate dummy arguments on GetFullMatrix. The resulting client code is now able to work with a simplified Matlab object.

Here is the Python code for the Matlab wrapper class (note, I fixed a bug in the nested list comprehension in setComplexVar() and getComplexVar() on 3/11/04):

import matlabCOM
import pywintypes


class Matlab(object):
    def __init__(self):
        "create new Matlab COM object"
        self.matlab = matlabCOM.MLApp()
        
    def __del__(self):
        "cleanup on destruct, if not already closed"
        if self.matlab:
            self.close()
            
    def setComplexVar(self, xnam, xcomplex, workspace="base"):
        """set variable in named Matlab workspace, passing in a single complex 
        value, a list of complex values, or a nested list of complex 
        values"""
        if type(xcomplex) is list:
            if type(xcomplex[0]) is list:
                xreal = [ [ c.real for c in row ] for row in xcomplex ]
                ximag = [ [ c.imag for c in row ] for row in xcomplex ]
            else:
                xreal = [ c.real for c in xcomplex ]
                ximag = [ c.imag for c in xcomplex ]
        else:
            xreal = [ xcomplex.real ]
            ximag = [ xcomplex.imag ]
        self.matlab.PutFullMatrix(xnam, workspace, xreal, ximag)
        
    def setVar(self, xnam, x, workspace="base"):
        "set real variable in named Matlab workspace"
        if type(x) is not list:
            x = [ x ]
        self.matlab.PutFullMatrix(xnam, workspace, x, [])
        
    def getComplexVar(self, xnam, workspace="base"):
        """retrieve variable from named Matlab workspace; returns 
        complex nested list"""
        try:
            xreal,ximag = self.matlab.GetFullMatrix(xnam, workspace, 
                                                     None, None)
            return [ [ complex(r,i) for r,i in zip(row[0],row[1]) ]
                        for row in zip(xreal,ximag) ]
        except pywintypes.com_error,err:
            return []
        
    def getVar(self, xnam, workspace="base"):
        """retrieve real variable from named Matlab workspace; returns real 
        nested list"""
        try:
            return self.matlab.GetFullMatrix(xnam, workspace, None, None)[0]
        except pywintypes.com_error,err:
            return []
    
    def setStringVar(self, snam, sval, workspace="base" ):
        "set the value of a string variable in Matlab"
        self.matlab.PutCharArray(snam, workspace, sval)
    
    def getStringVar(self, snam, workspace="base" ):
        "retrieve the value of a string variable from Matlab"
        return self.matlab.GetCharArray(snam, workspace)
        
    def execStmt(self, stmt):
        "executes Matlab command, returning string normally echoed from Matlab"
        return self.matlab.Execute( stmt )

    def close(self):
        "shuts down Matlab COM object"
        self.matlab.Quit()
        self.matlab = None




if __name__ == "__main__":
    matlab = Matlab()
    matlab.setComplexVar("x", 1+2j )
    print matlab.getComplexVar("x")
    print matlab.execStmt("x")
    matlab.setComplexVar("x", [ 3+4j ] )
    print matlab.getComplexVar("x")
    print matlab.execStmt("x")
    matlab.setComplexVar("x", [ [ 5+6j, 7+8j ], [ 9+10j, 11+12j ] ])
    print matlab.getComplexVar("x")
    print matlab.execStmt("x")
    print matlab.execStmt("y = inv(x)")
    print matlab.getComplexVar("y")
    
    matlab.setVar("x", 3.14159 )
    print matlab.getVar("x")
    print matlab.execStmt("x")
    matlab.setVar("x", [ 3.14159 ] )
    print matlab.getVar("x")
    print matlab.execStmt("x")
    matlab.setVar("x", [ [ 3.14159 ] ] )
    print matlab.getVar("x")
    print matlab.execStmt("x")
    
    matlab.setStringVar("greeting","Hey, man!")
    print matlab.getStringVar("greeting")
    print matlab.execStmt("greeting")
    
    
    matlab.close()

This program generates the following output:

[[(1+2j)]]

x =

   1.0000 + 2.0000i


[[(3+4j)]]

x =

   3.0000 + 4.0000i


[[(5+6j), (7+8j)], [(9+10j), (11+12j)]]

x =

   5.0000 + 6.0000i   7.0000 + 8.0000i
   9.0000 +10.0000i  11.0000 +12.0000i



y =

  -0.7500 + 0.6875i   0.5000 - 0.4375i
   0.6250 - 0.5625i  -0.3750 + 0.3125i


[[(-0.75+0.68749999999999878j), (0.49999999999999994-0.43749999999999922j)], 
 [(0.62500000000000011-0.56249999999999911j), (-0.37500000000000006+0.31249999999999944j)]]
((3.1415899999999999,),)

x =

    3.1416


((3.1415899999999999,),)

x =

    3.1416


((3.1415899999999999,),)

x =

    3.1416


Hey, man!

greeting =

Hey, man!

Created by Paul McGuire, March, 2004 -- Updated 11 March 2004

1