Fixing array arguments and refactoring codegen
Right after finishing a first implementation of array arguments last week, and bragging about it in a blog post, I realized it had some embarrassing shortcomings: The generated code was not compilable. Luckily, this was actually a minor problem that could be fixed by a one-line patch. More serious was it, that the matrix-vector product was not correctly implemented. To correct it I had to change the code logic, and it was a good opportunity to start refactoring the codegen module.
The goal with the refactoring was to make the data structures representing abstract code a bit more object oriented. For instance, the Routine class that represents a general routine in any programming language, was previously used just like a struct. It was used to store data describing the routine, but no logic was implemented in instance methods. By moving and adapting existing code lines I arranged it so that any Routine instance should be capable of initializing itself. To setup a Routine instance in my development branch, you only need to supply a name for the routine and a mathematical expression.
The Routine class is a very important piece of the codegen module. Viewing the codegen utility as a translator of mathematical expressions into a set of statements in a programming language, the Routine instances are responsible for storing information about how the math has been interpreted. So, figuratively speaking, there is need for something between the math expression and the Routine instance.
By placing the code that interprets math expressions in the __init__() method of the Routine class, it will be possible to enforce a consistent interpretation of the math, and a consistent storage of the data describing the routine. A consistent storage of conclusions from the interpretation step is important, so that a code generator that implement the routine in a specific language can get the information reliably.
Combining Python with Fortran and C
This Monday I shifted my focus away from code generation, and to the task of compiling the generated Fortran and C code. I have been reading selected chapters and sections in a book by Hans Petter Langtangen. (In that book, by the way, a Sympy example session is listed on page 185 🙂 )
From reading that book, and after reading in the f2py user documentation, I have started to think that f2py can be the tool of choice for compiling SymPy expressions into python callable binary subroutines. Apparently, f2py can be used to compile and wrap both Fortran and C functions, so it covers everything I need for the GSOC project. Langtangen also states that f2py is easier to learn and less error prone than SWIG.
F2py is integrated with numpy. In fact, since 2007 it is maintained as part of the numpy project. SymPy already has some interaction with numpy, such as the symarray, so by relying on f2py I can extend the functionality of SymPy without introducing new dependencies. If the goals of my project can be realized with f2py, it looks like the perfect option.
Oh, and by the way, the code generated from the matrix-vector product in test_codegen.py now compiles, and the implementation is correct. It even compiles with f2py, after which it can be imported in and called from a python session. Using appropriate numpy arrays, you can calculate matrix vector products in compiled fortran. Check it out!