Experimenting with numPy

Arjen Markus (16 october 2020) In the Tclers' chat the other day, Steve Blinkhorn mentioned an article in Nature on the numPy package that is used for scientific and technical calculation in Python. The question he posed was whether it would be an idea to build an interface to numPy, so that we can use it from a Tcl program. One reason for this: numPy is a well-respected and well-known package and it would potentially make it easier for Tclers to get their work accepted.

Steve Landers told me of the tclpy package that enables you to call Python from within Tcl and vice versa (see Accessing Tcl and Python from one another for more information, as there are solutions too). So, this provided me a first step towards using numPy in a Tcl program. Well, "program" is a bit of a big word, as I have not tried anything serious yet. Still, it is a useful first step to investigate how this package can be used.

The preparations were simple: build the package and load it into Tcl. My Cygwin environment presented a few problems, though, as the makefile required a file python-config to provide the Python include files and libraries. After some experimenting I used the following command to build it:

gcc -o libtclpy0.3.so -shared generic/tclpy.c -I/usr/include/tcl8.6 -I/usr/include/python2.7 -ltcl8.6 -lpython2.7 -Wl,--export-all-symbols

and introduced a few missing macros in the source code:

#define PACKAGE_VERSION "0.3"
#define PY_LIBFILE "libpython2.7.dll"

Then it was a matter of trying a few commands. Like from the documentation:

package require tclpy

py eval {import numpy as np}
py eval {def xx(x,y): return x+y}
puts [py call xx string1 string2]

which diligently prints string1string2

Defining an array in the Python interpreter is a trifle more demanding:

set a [list 4 2 3]
py eval "a = np.array(\[[join $a ,]\])"

to convert the data in the list into the right format for Python.

Now, however, the limitation: the tclpy package currently passes strings, not variables. This makes it a trifle difficult to manipulate variables on the Python whose names we pass from the Tcl side. For instance: to sort the numbers in the array we just created, this does not work:

puts [py call np.sort a]

Instead we need to convert the string "a" into the Python variable "a". Luckily Python does offer that possibility via the globals() and locals() function.

So what we can do:

py eval {def tolist(array): var = globals().get(array); return var.tolist()}
puts [py call tolist a]

This works also with two-dimensional arrays

set a {[[4,2,3],[1,2,3]]}
py eval "z = np.array($a)"
puts [py call tolist z]

prints:

{4 2 3} {1 2 3}

Of course, this is not very efficient - with a large array or matrix you would need to convert everything to a long string, pass it to the Python interpreter, do the computations and pass the result back.

But the fact that it is so easy to do these first few steps makes it possible to think about the next steps: provide more direct transfer methods to pass large amounts of numerical data to and fro.