ompc

Examples (Supplemental data to the article OMPC: An Open-source MATLAB® -to-Python Compiler.)

OMPC is a platform that makes it easy to move from MATLAB® to Python. MATLAB® can cost considerable amount of money but for somebody using only its basic functionality without toolboxes this seems like a waste. As we would like to prove with OMPC, MATLAB® is not irreplaceable, and usually the reason to stick with it is the reuse of previously developed code. OMPC should allow people to use their old MATLAB® code transparently from Python. This means that you don't have to rewrite all previously written modules to start learning and developing in Python.

There are other applications like Octave and Scilab that allow you to enjoy freedom while keeping your old MATLAB® code running with only small modifications. OMPC aspires for 100% syntax compatibility. The more difficult tasks is the make the OMPC supporting library fully compatible in the sense of format of the input and output parameters. We are not allowed to reverse engineer MATLAB® and its code. This is not necessary. There are packages for Python that offer the same feature. The only work that needs to be done by OMPC is to act as a bridge between the MATLAB® way and the Python way.

You can download the current examples in a zip file.



>>> import ompc
>>> addpath('mfiles')
>>> import add 
>>> add(1,2) 
(3,)

The source code of the m-file imported is in the mfiles/add.m:

function out = add(a, b)
% adds values of a and b
out = a + b;

The OMPC version is automatically generated next to the original m-file and named add.pym

# This file was automatically translated by OMPC (http://ompc.juricap.com)

from ompc import *

@mfunction("out")
def add(a=None, b=None):
    # adds values of a and b
    out = a + b

This small code snippet actually runs only thanks to a number of concepts that are described in their own examples. 
  1. import ompc - demonstrated on its own in ompc_ihooks.py, read a section below called Automatic translation of ".m" files after Python import statement.
  2. addpath('mfiles') - mfiles is the location of the add.m file within the examples.
  3. import add - besides being imported thanks to to import hook installed by the preceding import statement the add function is automatically compiled. This compilation involves translation described in the section names OMPC translator. Next the mfunction decorator modifies the function translated by OMPC. The mfunction decorator is demonstrated in The poor man's implementation of the mfunction decorator and Special variables nargin, nargout, varargin, varargout.
  4. add(1,2) - at this point the function is ready and should behave like a MATLAB(R) function. Notice that the OMPC translation does not contain the return a statement, however the function returns it's result anyway. This is because the mfuction decorator has inserted the byte-code for the correct return statement upon functions import.
All the described functionality is implemented in the examples archive under the ompc/ directory.

Growing cell-array

Example file: growing_array.py 

The cell array of MATLAB(R) is very similar to Python's list. One of the special features of MATLAB(R)'s arrays is that they are growable on demand. This means that one can assign to a non-existent index, the array will be first extended to contain the requested element  and the position will be subsequently filled with a new value. The following demonstrates such feature in MATLAB(R):

>>> import ompc

>>> class mcellarray(list):
    def __setitem__(self,i,v):
        if i >= len(self):
            self.extend([None]*(i-len(self)) + [v])

>>> m = mcellarray()
>>> tic()
>>> for i in xrange(100000): m[i] = 12
>>> toc()
Elapsed time is 0.372690 seconds. 

The time measurement is there for a reason. Let's look at MATLAB(R)'s performance on a similar example:

>> m = {}; tic, for i=1:100000, m{i} = 12; end, toc
Elapsed time is 9.637410 seconds.

Python's implementation of list is very good.

Online OMPC compiler

Example files: appengine/*
This is a Google Appengine application that demonstrating the OMPC compiler. This example shows how Python can be used for online demonstrations. A running instance of the small application is located at http://ompclib.appspot.com/ . 
This application saves all submited m-files in the database and will serve as a bug-submission system.

OMPC translator

Example file: ompc/ompcply.py

OMPC parser is written using the PLY package (http://www.dabeaz.com/ply/). The grammar was implemented to the best knowledge of the author. It is very possible that the compiler fails in some untested cases. If you find an incompatible statement please use the online compiler at http://ompclib.appspot.com/ . The submitted source file will be compiled and in case it fails it will be stored in the database, so the incompatibilities can be addressed.

Run the "ompcply.py" from the same directory where it resides.

a = 1;
a = 1
disp 12
for x = 1:10,
a(12:14) = [ class(a) rand(3).' ; 'test']

OMPC console will print the equivalent MATLAB® statements. The captured session should look as follows:

Welcome to OMPC compiler test console!

ompc> a = 1;
a = 1
ompc> disp 12
disp("12")
ompc> a = 1
a = 1; print a
ompc> for x = 1:10,
for x in mslice[1:10]:
ompc> a(12:14) = [ class(a) rand(3).' ; 'test']
    a(mslice[12:14]).lvalue = mcat([mclass(a), rand(3).T, OMPCSEMI, mstring('test')]); print a
ompc>

You can test your own statements. You can see that the parser obeys the semicolon ';' verbosity rules. Also MATLAB(R) names that are a reserved word in Python have to be translated, for example class -> mclass and print -> _print.
The parser is a set of syntactical rules like the following:

def p_statement_list(p):
    '''statement_list : statement
                      | statement COMMA
                      | statement SEMICOLON'''
    p[0] = _print_statement(p[1], len(p) > 2 and p[2] or None, p[0])

def p_expression_lambda(p):
    '''expression : LAMBDA LPAREN name_list RPAREN expression'''
    p[0] = 'lambda %s: %s'%(p[3], p[5])

To implement an interpreter, instead of a translator, it is only necessary to put the Python exec statement after a successful match of a full statement.

Special variables nargin, nargout, varargin, varargout

Example file: arguments.py 

This example demonstrates the how special variables nargin, nargout, varargin, varargout can be emulated. At first the following code is tested:

@mfunction_arguments("out", "varargout")
def example1(a=None, b=None, *varargin):
    print "NIN: %d, NOUT: %d"%(nargin, nargout)
    print "VARARGIN", varargin, "VARARGOUT:", varargout
    out = 12
    return nargout > 0 and range(nargout) or None

The test cases and their corresponding output are here:

>>> example1()
NIN: 0, NOUT: 0
VARARGIN () VARARGOUT: []
>>> example1(1)
NIN: 1, NOUT: 0
VARARGIN () VARARGOUT: []
>>> example1(1,2)
NIN: 2, NOUT: 0
VARARGIN () VARARGOUT: []
>>> example1(1,2,4)
NIN: 3, NOUT: 0
VARARGIN (4,) VARARGOUT: []
>>> example1(1,2,4,'a')
NIN: 4, NOUT: 0
VARARGIN (4, 'a') VARARGOUT: []

>>> a = example1(1,2,4,'a')
NIN: 4, NOUT: 0
VARARGIN (4, 'a') VARARGOUT: []
>>> [a, b] = example1(1,2,4,'a')
NIN: 4, NOUT: 2
VARARGIN (4, 'a') VARARGOUT: [None]
>>> [a, b, c] = example1(1,2,4,'a')
NIN: 4, NOUT: 3
VARARGIN (4, 'a') VARARGOUT: [None, None]
>>> [a, b, c, d] = example1(1,2,4,'a')
NIN: 4, NOUT: 4
VARARGIN (4, 'a') VARARGOUT: [None, None, None]
>>> print 'OUT:', a
OUT: 12

Finally an example from the MATLAB online help on the varargin/varargout was used:

@mfunction_arguments("s", "varargout")
def mysize(x=None):
    nout = max(nargout, 1) - 1
    s = size(x)
    for k in m_[1:nout]:
        varargout(k).lvalue = mcellarray([s(k)])

The output is equivalent to the output in the web page:

>>> [s,rows,cols] = mysize(rand(4,5))
>>> print 's = %s, rows = %r, cols = %r'%(s, rows, cols)
s = [4, 5], rows = [4], cols = [5]

Calling mexFunctions from Python

Example file: mex/mex.py

This is an example of calling mexFunction from Python. Only a very small subset of the mex API is implemented. It is a proof of concept example.

The file mex.py contain a function called mexFunc. This Python function wraps any dynamic-link library that exports an extern mexFunction (according to mex API). This function will determine the number of input and output arguments and will automatically create C-arrays emulating a regular mexFunction call. This example does not need MATLAB®! The comiled dynamic-link libraries can be called even from C or FORTRAN.

First run the batch file compile in the directory. This will compile the mexlib.lib and the example mexFunctions in the same directory. So far I have made available only batch files for Windows and Visual Studio. If you would like to try on Linux on Mac, contact me (Peter Jurica), I can provide you with these files given I have time to spare.

Run the file mex.py to see. 

You can try your own mexfiles given that they use only the small subset of mex API implemented for the example.

Emulating nargin/nargout, assigning to a function call

Example file: ompc_narginout.py 

This example shows that Python allows emulation of the nargin/nargout variables. It also shows how values can be assigned to a function call. The following function is used for testing

def a(b=None,c=None):
    out1, out2 = None, None
    nargin, nargout = _get_narginout()
    print '  nargin = %s, nargout = %d'%(nargin, nargout)
    if nargin == 2:
        out1 = b + c
    elif nargin == 1:
        k = marray()
        out2 = '---'
    k = locals().get('k', marray())
    k(10).lvalue = 12
    return (out1, out2)[:nargout]

The following object is used to show how is it possible to assign to a function call.

class marray:
    def __init__(self, val=[]):
        self.val = []
    def __call__(self, *args):
        print "  return elements %r"%args
        return self
    def __setattr__(self, name, val):
        if name == "lvalue":
            print "  I am an L-value ('%s') being set to %r."%(name, val)
            self.val = val

A set of tests are performed on this function. First the function a  pint the number of submitted in an out parameters. The statement k = locals().get('k', marray()) shows how to check if a variable is initialized in the current scope. Then we assign to this variable k through a function call. Finally only the requested number of arguments is returned. The output should look like the following:

>>> a()
  nargin = 0, nargout = 1
  return elements 10
  I am an L-value ('lvalue') being set to 12.
(None,)
>>> a(1)
  nargin = 1, nargout = 1
  return elements 10
  I am an L-value ('lvalue') being set to 12.
(None,)
>>> b = a(1)
  nargin = 1, nargout = 2
  return elements 10
  I am an L-value ('lvalue') being set to 12.
None ---
>>> b, c = a(12, 11)
  nargin = 2, nargout = 2
  return elements 10
  I am an L-value ('lvalue') being set to 12.
23 None
>>> a, b, c, d = a('++', '--')
  nargin = 2, nargout = 4
  return elements 10
  I am an L-value ('lvalue') being set to 12.
Traceback (most recent call last):
  File "c:\devel\hg\ompc\src\ompc_narginout.py", line 60, in <module>
    a, b, c, d = a('++', '--')
ValueError: need more than 2 values to unpack


Automatic translation of ".m" files after Python import statement.

This example shows how the OMPC compiler implemented in ompcply.py can be invoked automatically on every Python import statement. This example also demostrates the use of documentation strings that are contained in the first comment after the function declaration (read in the next section).

Example file: ompc_ihooks.py 

The __main__ section of this file contains:

import sys
sys.path += ['mfiles']
import add
print add
help(add)

The class implemented within the same file implement an import hook. On the command import add, the Python interpreter looks besides the regular Python modules also for files with the extension .m. The file mfiles/add.m is imported and a file add.pyc is generated. The .pyc file is created from a Python source code generated by ompc (saved as add.pym) by the line:

m_compile.compile(filename, cfile)    # m-file compilation

The result if running this example should contain the following:

<function add at 0x00C22CF0>
Help on function add in module ompc:

add(*args)
    adds values of a and b

The import add results in a function object, this is different from the default Python import and can be only done, because m-files contain only 1 function callable from outside.

Doc-strings from comments


Example file: doc_comments.py 

This file contains a function called coinflip, with documenation in the first comment after the function declaration. This example shows how it is possible to extract this comments and convert them to a function doc-string.

Input:

@mfunction("x")
def coinflip(ndraws=None, p=None):
    # Generate a list x of zeros and ones according to coin flipping (i.e.
    # Bernoulli) statistics with probability p of getting a 1.
    # 1/20/97  dhb  Delete obsolet rand('uniform').
    # 7/24/04  awi  Cosmetic.   
   
    # Generate ndraws random variables on the real interval [0,1).
    unif = rand(ndraws, 1)
    # Find all of the ones that are less than p.
    # On average, this prop
...

Output:

Help on function coinflip in module __main__:

coinflip(*args)
    Generate a list x of zeros and ones according to coin flipping (i.e.
    Bernoulli) statistics with probability p of getting a 1.
    1/20/97  dhb  Delete obsolet rand('uniform').
    7/24/04  awi  Cosmetic.

Poor man's implementation of the mfunction decorator


Example file: ompc_mfunction_poormans.py 

This example demonstrates the full functionality of the mfunction decorator implemented in an un-Pythonic way. Maybe this way interpreted lanuages were misused 10 years ago. The decorator looks up the function's source code and modfies it. Then this new source code is compiled and a new function object is created. This approach would fail without a source file available.

The function add in this example is executed at the end of the program.

Input:

@mfunction("out1, out2")
def add(a=None, b=None):
    # adds 2 numbers
    if nargin == 2:
        out2 = (a, b)
    out1 = a + b

Output:

def add(a, b):
    """adds 2 numbers"""
    nargin, nargout = _get_narginout()
    if nargout > 2:
        error("Too manu output arguments!")
    if nargin == 2:
        out2 = (a, b)
    out1 = a + b

    return (out1, out2,)[:nargout]

The output shows what the actual source code of a decorated function looks like. The real mfunction decorator however works directly with bytecode. It does not require the source code of the function.

Element-wise operations


Example file: elementwise_operations.py

MATLAB(R) offers a number of operators that are unique to the language. The operators are .*, ./, .^ and .\, they are used for element-wise operations on arrays while their simpler version *, /, ^ and \ are used for matrix operations. Python does support these special operations. Operator overloading allows one to choose only one of the two possible operations. However with the help of a third object whose presence implies an element-wise operation we can emulate a number of new operators.

For example MATLAB(R)'s a*b can be preserved while the a.*b will be translated into Python's a*elmul*b. Run the example and look inside to see how this can be implemented. The result of running should look like the following:

-----------------------------------------------------------------------
-----------------------------------------------------------------------
SCALAR*SCALAR
>>> 1.*2
times 1 2
2
>>> 1*2
2
-----------------------------------------------------------------------
VECTOR*SCALAR
>>> a = [1,2,3,4]
>>> a.*2
times [1, 2, 3, 4] 2
[2, 4, 6, 8]
>>> a*2
times [1, 2, 3, 4] 2
[2, 4, 6, 8]
>>> 2*a
times 2 [1, 2, 3, 4]
[2, 4, 6, 8]
-----------------------------------------------------------------------
VECTOR*VECTOR
>>> b = [2,3,4,5]
>>> a.*b
times [1, 2, 3, 4] [2, 3, 4, 5]
[2, 6, 12, 20]
>>> a*b
mtimes [1, 2, 3, 4] [2, 3, 4, 5]
40
-----------------------------------------------------------------------



Attachments (1)

Recent site activity