For the past week, I’ve been working on adding support for LFortran to SymPy’s code generation capabilities. My current goal is to emulate SymPy’s
fcode, which converts a SymPy expression to an equivalent expression in Fortran, utilizing only LFortran as a backend. This post is an outline of what I’ve done (and learned) over last week.
LFortran is a Fortran (with some extensions) to LLVM compiler. One advantage that this design provides is that it enables interactive execution of Fortran code. LFortran can also be used as a Jupyter kernel, which means it can be used in a Jupyter notebook environment (you can even find an online interactive demo here).
In addition to being able to parse code, LFortran also provides the functionality of traversing a parse tree and generating the equivalent Fortran code. This means that if we want to generate Fortran code from a SymPy expression, the only work that we have to do is convert the SymPy expression tree to its LFortran equivalent.
LFortran provides a number of convenience functions for building a Fortran AST. Since LFortran is still in early alpha, there are currently only about a dozen builder functions. However, these few basic functions are enough for constructing simple expressions in the Fortran AST. As an example, if we wanted to construct the expression represented by
c = a + b, where each variable involved is an integer, we could do something like:
>>> import lfortran.asr.builder as builder >>> import lfortran.asr.asr as asr >>> integer = builder.make_type_integer() >>> a = asr.Variable(name="a", type=integer) >>> b = asr.Variable(name="b", type=integer) >>> c = asr.Variable(name="c", type=integer) >>> sum = builder.make_binop(a, asr.Add(), b) >>> expr = asr.Assignment(c, sum)
LFortran also provides functionality to visualize what the expression tree looks like:
>>> import lfortran.asr.pprint as pprint >>> pprint.pprint_asr(expr) stmt.Assignment ├─target=c ╰─value=expr.BinOp ├─left=a ├─op=operator.Add ├─right=b ╰─type=ttype.Integer ├─kind=4 ╰─dims=
Sympy to LFortran Converter
I’ve started with the implementation of a basic SymPy to LFortran converter utilizing the AST builder described above, with the current pull request available on the SymPy GitHub. The converter follows the same node visitor class structure as all of the other code printers (it even inherits the
CodePrinter class, despite the methods not producing strings but rather AST nodes). Here’s a simple example that demonstrates the conversion of a simple expression to an equivalent in LFortran:
There are two things to notice here. The first is that I had to replace all the newlines in the generated expression, since a bug in LFortran causes too many newlines to be printed. The second is that there are a number of redundant parentheses in the printed expression. While this isn’t an outright bug, it’s another aspect of LFortran that is currently being improved upon.
I’ve also add another function,
sympy_to_lfortran_wrapped, which wraps an expression in a function definition, (poorly) emulating the wrapping part of
>>> from sympy.codegen.lfort import sympy_to_lfortran_wrapped >>> e_wrapped = sympy_to_lfortran_wrapped(e) >>> print(lfortran.ast_to_src(lfortran.asr_to_ast(e_wrapped))) integer function f(x) result(ret) integer, intent(in) :: x ret = 1 + x end function
Since LFortran can directly compile the AST to an LLVM intermediate representation, a future implementation of
autowrap might be implemented by compiling the output of this function (instead of first completely generating the code and then feeding it to
gfortran as it’s done right now).
For the next couple of days, I will try to extend the types of SymPy expressions that may be converted. One thing to note is that there isn’t a perfect correspondence between SymPy and LFortran AST nodes. LFortran supports nodes for operations like unary subtraction and division, which SymPy converts into multiplication and division respectively. On top of this, I’ll also add some tests for the functionality that I have implemented so far. After that, I’ll start with work on SymPy’s matrix expression code generation (the second part of my GSoC project) and pick LFortran up again close to the end of the summer.