{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Jupyter and multiple Languages" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook is [based on an oldnotebook of mine](https://matthiasbussonnier.com/posts/23-Cross-Language-Integration.html), and the supporting material behing a [Jupyter blog post](https://blog.jupyter.org/i-python-you-r-we-julia-baf064ca1fb6) talking about cross language integration.\n", "This will quite short on narrative, and dive a bit more into technical details than the blog post does. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "An often requested feature for the [Jupyter Notebook](https://jupyter.org) is the ability to have multiple kernels, often in many languages, for a single notebook. \n", "\n", "While the request in spirit is a perfectly valid one, it is often a misunderstanding of what having a single kernel means. In particular having multiple language is often easier if you have a single process which handle the dispatching of various instructions to potentially multiple underlying languages. It is possible to do that in a _Single Kernel_ which does orchestrate dispatching instruction and moving data around.\n", "\n", "Whether the multiple languages that get orchestrated together are remote processes, or simply library calls or more complex mechanisms becomes an implementation detail. \n", "\n", "[Python](https://python.org) is known to be a good \"glue\" language, and over the year the [IPython kernel](https://ipython.org) have seen a growing number of extensions showing that dynamic cross language integration can be seamless form the point of view of the user.\n", "\n", "In the following we only scratch the surface of what is possible across a variety of languages. The approach shown here is one among many. The [Calysto](https://github.com/Calysto) organisation for example has several projects taking different approaches on the problem.\n", "\n", "In the following I will show a quick overview on how you can in single notebook interact with many languages, via Common Foreign function interface (C, Rust, Fortran, ...), or even crazier approaches (Julia).\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# IPython and cross language integration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The rest of this is mostly a demo on how cross-language integration works in a Jupyter notebook by using the features of the _Reference IPython Kernel implementation_. These features are completely handled in the kernel so need to be reimplemented on a per-kernel basis. Though they **also** work on pure terminal IPython, nbconvert or any other programmatic use of IPython.\n", "\n", "Most of what you will see here are _just_ thin wrappers around already existing libraries. These libraries (and their respective authors) do all the heavy lifting. I just show how seamless a cross language environment can be from the user point of view. The installation of these library might not be easy either and getting all these language to play together can be complex task. It is though becoming easier and easier.\n", "\n", "The term _just_ does not imply that the wrappers are simple, or easy to write. It indicate that the wrappers are far from being complete. What is shown here is completely doable using standard Python syntax and bit of manual work. SO what you'll see here is mostly _convenience_. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The good old example of Fibonacci" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Understanding the multiple languages themselves is not necessary; most of the code here should self explanatory and straightforward. We'll define many function that compute the nth `Fibonacci` number more or less efficiently. We'll define them either using the classic recursive implementation, or sometime using an unrolled optimized version. As a reminder the Fibonacci sequence is defines a the following:\n", "\n", "$$ F_n = \\begin{cases} 1 &\\mbox{if } n \\leq 2 \\\\ \n", "F_{n-1}+F_{n-2} & \\mbox{otherwise }\\end{cases}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The fact that we calculate the Fibonacci sequence as little importance, except that the value of $F_n$ can grow _really_ fast in $O(e^n)$ if I remember correctly. And the recursive implementation will have a hard time getting beyond (n=100) as the number of call will be greater than $O(e^n)$ as well. Be careful especially if you calculate $F_{F_n}$ or more composition. Remembering that n=5 is stable via $F$ might be useful. \n", "\n", "Here are the first terms of the Fibonacci sequence:\n", "\n", " 1. 1\n", " 1. 1\n", " 1. 1+1 = 2\n", " 1. 2+1 = 3\n", " 1. 3+2 = 5\n", " 1. 5+3 = 8\n", " 1. 8+5 = 13\n", " ...\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Basic Python cross-language integration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's define the `fibonacci` function in python:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "def fib(n):\n", " \"\"\"\n", " A simple definition of fibonacci manually unrolled\n", " \"\"\"\n", " if n<2:\n", " return 1\n", " x,y = 1,1\n", " for i in range(n-2):\n", " x,y = y,x+y\n", " return y" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 2, 3, 5, 8, 13, 21, 34]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[fib(i) for i in range(1,10)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Store the value from 1 to 30 in `Y`, and graph it." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0.5,1,'The Fibonacci sequence grows fast !')" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "import numpy as np\n", "X = np.arange(1,30)\n", "Y = np.array([fib(x) for x in X])\n", "import matplotlib.pyplot as plt\n", "fig, ax = plt.subplots()\n", "ax.scatter(X, Y)\n", "ax.set_xlabel('n')\n", "ax.set_ylabel('fib(n)')\n", "ax.set_title('The Fibonacci sequence grows fast !')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It may not surprise you, but this looks like an exponential, so if we were to look at $log(fib(n))$ × $n$ it would look approximately like a line. We can try to do a linear regression using this model. R is a language many people use to do statistics. So, let's use R. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's enable integration between Python and R using the [__`RPy2`__](https://rpy2.readthedocs.io/en/version_2.8.x/) python package developed by Laurent Gautier and the rest of the rpy2 team." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import rpy2.rinterface\n", "\n", "%load_ext rpy2.ipython" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Following will \"Send\" the X and Y array to R." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "%Rpush Y X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now let's try to fit a linear model ($ln(Y) = A.X + B$) using R. I'm not a R user myself, so don't take this as idiomatic R." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%R\n", "my_summary = summary(lm(log(Y)~X))\n", "val <- my_summary$coefficients\n", "\n", "plot(X, log(Y))\n", "abline(my_summary)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\n", "Call:\n", "lm(formula = log(Y) ~ X)\n", "\n", "Residuals:\n", " Min 1Q Median 3Q Max \n", "-0.183663 -0.013497 -0.004137 0.006046 0.296094 \n", "\n", "Coefficients:\n", " Estimate Std. Error t value Pr(>|t|) \n", "(Intercept) -0.775851 0.026173 -29.64 <2e-16 ***\n", "X 0.479757 0.001524 314.84 <2e-16 ***\n", "---\n", "Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1\n", "\n", "Residual standard error: 0.06866 on 27 degrees of freedom\n", "Multiple R-squared: 0.9997,\tAdjusted R-squared: 0.9997 \n", "F-statistic: 9.912e+04 on 1 and 27 DF, p-value: < 2.2e-16\n", "\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%R\n", "my_summary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Good, we have now the some statistics on the fit, which also looks good. __And__ we were able to not only send variable to R, but to plot directly from R !" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are happy as $F_n = \\left[\\frac{\\phi^n}{\\sqrt 5}\\right]$, where `[]` is closest integer and $\\phi = \\frac{1+\\sqrt 5}{2}$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also look at the variables more carefully" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ " Estimate Std. Error t value Pr(>|t|)\n", "(Intercept) -0.7758510 0.026172673 -29.64355 3.910319e-22\n", "X 0.4797571 0.001523832 314.83597 1.137181e-49\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%R\n", "val" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or even the following that _looks_ more like python" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-7.75850975e-01, 2.61726725e-02, -2.96435519e+01,\n", " 3.91031947e-22],\n", " [ 4.79757090e-01, 1.52383191e-03, 3.14835966e+02,\n", " 1.13718145e-49]])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%R val" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can even get the variable back from R as Python objects:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([-7.75850975e-01, 2.61726725e-02, -2.96435519e+01, 3.91031947e-22]),\n", " array([4.79757090e-01, 1.52383191e-03, 3.14835966e+02, 1.13718145e-49]))" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coefs = %Rget val\n", "y0,k = coefs[0:2]\n", "y0,k" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's all from the R part. I hope this shows you some of the power of IPython, both in notebook and command line. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# CFFI" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great! We were able to send data back and forth! If does not works for all objects, but at least for the basic ones. It requires quite some work from the authors of the underlying library to allow you to do that. Though we are still limited to data. We can't (yet) send functions over which limits the utility." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Mix and Match : C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One of the critical point of any code may at some point be performance. Python is known to not be the most performant language, though it is convenient and quick to write and has a large ecosystem. Most of the function you requires are probably available in a package, battle tested and optimized. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You might still need here and there the raw power of an ubiquitous language which is known for its speed when you know how to wield it well: C. \n", "\n", "Though one of the disadvantage of C is the (relatively) slow iteration process due to the necessity of compilation/run part of the cycle. Let see if we can improve that by leveraging the excellent [CFFI project](https://cffi.readthedocs.io/), using my own small [cffi_magic](https://pypi.python.org/pypi/cffi_magic) wrapper. " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "import cffi_magic" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "rm -rf *.o *.c *.so Cargo.* src target" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ls: *.c: No such file or directory\r\n", "ls: *.h: No such file or directory\r\n", "ls: *.o: No such file or directory\r\n" ] } ], "source": [ "ls *.c *.h *.o" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using the `%%cffi` magic we can define in the middle of our python code some C function:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "%%cffi int cfib(int);\n", "\n", "int cfib(int n)\n", "{\n", " int res=0;\n", " if (n <= 1){\n", " res = 1;\n", " } else {\n", " res = cfib(n-1)+cfib(n-2);\n", " }\n", " return res;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first line take the \"header\"/\"signature\" of the function we declare, and the rest of the cell takes the body of this function. The `cfib` function will automatically be made available to you in the main python namespace." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cfib(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Oops there is a mistake as we should have `fib(5) == 5`. Luckily we can redefine the function on the fly. I could edit the above cell, but here as this will be rendered statically for the sake of demo purpose, I'm going to make a second cell:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "%%cffi int cfib(int);\n", "\n", "int cfib(int n)\n", "{\n", " int res=0;\n", " if (n <= 2){ /*mistake was here*/\n", " res = 1;\n", " } else {\n", " res = cfib(n-1)+cfib(n-2);\n", " }\n", " return res;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(you may need to run above cell twice... I don't know why)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cfib(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great ! Let's compare the timing." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "287 ns ± 10.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" ] } ], "source": [ "%timeit cfib(10)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "902 ns ± 61 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" ] } ], "source": [ "%timeit fib(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Not so bad considering the C implementation is recursive, and the Python version is manually hand-rolled. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Implementation detail" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So how do we do that magically under the hood? The knowledgeable reader is aware that CPython extensions cannot be reloaded. Though here we redefine the function... how come? \n", "\n", "Using the user provided code we compile a shared object with a random name, import this as a module and alias using a user friendly name in the `__main__` namespace. If the user re-execute we just get a new name, and change the alias mapping. \n", "\n", "If one wan to optimize you can use a hash of the codecell string to not recompile if the user hasn't changed the code. " ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "_cffi_5c004d54c0a603cba22b0cb2067ddbe4.c\r\n", "_cffi_5c004d54c0a603cba22b0cb2067ddbe4.o\r\n", "_cffi_dcac8fc76ec118571b0ae4bda71fb975.c\r\n", "_cffi_dcac8fc76ec118571b0ae4bda71fb975.o\r\n" ] } ], "source": [ "ls *.o *.c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With this in mind you can guess the same can be done for any language which can be compiled to a shared object, or a dynamically loadable library. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Mix and Match : rust" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `cffi` module also allows you to do the same with [Rust](https://www.rust-lang.org), a new language designed by Mozilla, which provide the same C-like level of control, while incorporating more recent understanding of programming and provide better memory safety. Let's see how we would do the same with Rust:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We need to put rustc on `$PATH`:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "import cffi_magic\n", "from os import environ as E\n", "if 'cargo' not in E['PATH']:\n", " E['PATH'] = E['HOME']+'/.cargo/bin:'+E['PATH']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fetching crates registry should take only 30sec or so... (if you know how to tell gargo not to hit the network). If it get stuck restart the notebook and \"Run all above\"." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "injecting `rfib` in user ns\n" ] } ], "source": [ "%%rust int rfib(int);\n", "\n", "#[no_mangle]\n", "pub extern fn rfib(n: i32) -> i32 {\n", " match n {\n", " 0 => 1,\n", " 1 => 1,\n", " 2 => 1,\n", " _ => rfib(n-1)+rfib(n-2)\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 2, 3, 5, 8, 13, 21, 34]" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[rfib(x) for x in range(1,10)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I'm not a Rustacean, but the above seem pretty straightforward to me. Again this might not be idiomatic Rust but you should be able to decipher what's above. The same than for C applies. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Still in development" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Both the C and Rust example shown above use the `cffi_magic` on which I spent roughly 4 hours total, so the functionalities can be really crude and the documentation minimal at best. Feel free to send PRs if you are interested. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fortran" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [fortran magic](https://pypi.python.org/pypi/fortran-magic) does the same as above, but has been developed by [mgaitan](https://github.com/mgaitan/fortran_magic) and is slightly older. Again no surprise except you are supposed to mark fortran variable that are used to return the values. " ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "scrolled": true }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/bussonniermatthias/anaconda/lib/python3.6/site-packages/fortranmagic.py:147: UserWarning: get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.\n", " self._lib_dir = os.path.join(get_ipython_cache_dir(), 'fortran')\n" ] }, { "data": { "application/javascript": [ "$.getScript(\"https://raw.github.com/marijnh/CodeMirror/master/mode/fortran/fortran.js\", function () {\n", "IPython.config.cell_magic_highlight['magic_fortran'] = {'reg':[/^%%fortran/]};});\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%load_ext fortranmagic" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "%%fortran\n", "RECURSIVE SUBROUTINE ffib(n, fibo) \n", " IMPLICIT NONE\n", " INTEGER, INTENT(IN) :: n\n", " INTEGER, INTENT(OUT) :: fibo\n", " INTEGER :: tmp\n", " IF (n <= 2) THEN \n", " fibo = 1\n", " ELSE\n", " CALL ffib(n-1,fibo)\n", " CALL ffib(n-2,tmp)\n", " fibo = fibo + tmp\n", " END IF\n", "END SUBROUTINE ffib" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 2, 3, 5, 8, 13, 21, 34]" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[ffib(x) for x in range(1,10)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "No surprise here, you are well aware of what we are doing." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cython" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "IPython used to ship with the Cython magic that is now part of [Cython](http://cython.org/) itself.\n", "Cython is a superset of Python that compiles to C and importable from Python. You should be a ble to take your python code as is, type annotate it, and get c-like speed.\n", "The same principle applies:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "import cython" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "%load_ext cython" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "%%cython\n", "\n", "def cyfib(int n): # note the `int` here\n", " \"\"\"\n", " A simple definition of fibonacci manually unrolled\n", " \"\"\"\n", " cdef int x,y # and the `cdef int x,y` here\n", " if n < 2:\n", " return 1\n", " x,y = 1,1\n", " for i in range(n-2):\n", " x,y = y,x+y\n", " return y" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 2, 3, 5, 8, 13, 21, 34]" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[cyfib(x) for x in range(1,10)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### benchmark" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "651 ns ± 25 ns per loop (mean ± std. dev. of 3 runs, 100 loops each)\n" ] } ], "source": [ "%timeit -n100 -r3 fib(5)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The slowest run took 15.29 times longer than the fastest. This could mean that an intermediate result is being cached.\n", "1.82 µs ± 2.07 µs per loop (mean ± std. dev. of 3 runs, 100 loops each)\n" ] } ], "source": [ "%timeit -n100 -r3 cfib(5)" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "170 ns ± 8.86 ns per loop (mean ± std. dev. of 3 runs, 100 loops each)\n" ] } ], "source": [ "%timeit -n100 -r3 ffib(5)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "206 ns ± 204 ns per loop (mean ± std. dev. of 3 runs, 100 loops each)\n" ] } ], "source": [ "%timeit -n100 -r3 cyfib(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The benchmark result can be astonishing, but keep in mind that the Python and Cython version use manually unrolled loop. Main point being that we reached our goal and used Fortran, Cython, C (and Rust) in the middle of our Python program." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " > let's skip the Rust fib version, it tends to segfault, and it would be sad to segfault now :-) If you konw why I would be happy to include a fix!" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "# %timeit rfib(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Cake is not a lie!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So can we do a layer cake? Can we call rust from Python from Fortran from Cython? Or Cython from C from Fortran? Or Fortron from Cytran from Rust?" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Pray the demo-gods it wont segfault even without rust...\n" ] } ], "source": [ "import itertools\n", "lookup = {'c':cfib,\n", " # 'rust': rfib, # as before Rust may segfault, but I dont' know why ...\n", " 'python': fib,\n", " 'fortran': ffib,\n", " 'cython': cyfib\n", " }\n", "\n", "print(\"Pray the demo-gods it wont segfault even without rust...\")" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "for function in lookup.values():\n", " assert function(5) == 5, \"Make sure all is correct or will use 100% CPU for a looong time.\"" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "c -> python -> fortran -> cython : 5\n", "c -> python -> cython -> fortran : 5\n", "c -> fortran -> python -> cython : 5\n", "c -> fortran -> cython -> python : 5\n", "c -> cython -> python -> fortran : 5\n", "c -> cython -> fortran -> python : 5\n", "python -> c -> fortran -> cython : 5\n", "python -> c -> cython -> fortran : 5\n", "python -> fortran -> c -> cython : 5\n", "python -> fortran -> cython -> c : 5\n", "python -> cython -> c -> fortran : 5\n", "python -> cython -> fortran -> c : 5\n", "fortran -> c -> python -> cython : 5\n", "fortran -> c -> cython -> python : 5\n", "fortran -> python -> c -> cython : 5\n", "fortran -> python -> cython -> c : 5\n", "fortran -> cython -> c -> python : 5\n", "fortran -> cython -> python -> c : 5\n", "cython -> c -> python -> fortran : 5\n", "cython -> c -> fortran -> python : 5\n", "cython -> python -> c -> fortran : 5\n", "cython -> python -> fortran -> c : 5\n", "cython -> fortran -> c -> python : 5\n", "cython -> fortran -> python -> c : 5\n" ] } ], "source": [ "for order in itertools.permutations(lookup):\n", " t = 5\n", " for f in order:\n", " t = lookup[f](t)\n", " \n", " print(' -> '.join(order), ':', t)" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "It worked ! I can run all the permutations !\n" ] } ], "source": [ "print('It worked ! I can run all the permutations !')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# The Cherry on the Layer Cake, with Julia" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you have a small idea about how the above layer-cake is working you'll understand that there is (still) a non-negligible overhead as between each language switch we need to go back to Python-land. And the scope in which we can access function is still quite limited. The following is some really _Dark Magic_ concocted by Fernando Perez and Steven Johnson using the [Julia](http://julialang.org/) programming language. I can't even pretend to understand how this possible, but it's really impressive to see. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try to handwave what's happening. I would be happy to get corrections." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The crux is that the Python and Julia interpreters can be started in a way where they each have access to the other process memory. Thus the Julia and Python interpreter can share live objects. You then \"just\" need to teach the Julia language about the structure of Python objects and it can manipulate these as desired, either directly (if the memory layout allow it) or using proxy objects that \"delegate\" the functionality to the python process.\n", "\n", "The result being that Julia can import and use Python modules (using the Julia `PyCall` package), and Julia functions are available from within Python using the `pyjulia` module. \n", "\n", "Let's see how this look like." ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that if you're using Julia version `0.7` or greater (any recent Julia), we need to work around this issue:\n", "https://pyjulia.readthedocs.io/en/latest/troubleshooting.html#your-python-interpreter-is-statically-linked-to-libpython\n", "\n", "The easiest way to do that is to initialize Juila with `compiled_modules=False`, as we do in this next cell:" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "from julia.api import Julia\n", "jl = Julia(compiled_modules=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now we can load and start using Julia:" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Initializing Julia runtime. This may take some time...\n" ] } ], "source": [ "%load_ext julia.magic" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "julia_version = %julia VERSION\n", "julia_version # you can see this is a wrapper" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "He we tell the _julia_ process to import the _python_ matplotlib module, as well as numpy." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [], "source": [ "%julia @pyimport matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "%julia @pyimport numpy as np" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "%%julia\n", " # Note how we mix numpy and julia:\n", "t = range(0, stop=2*pi, length=1000); # use the julia `range` and `pi`\n", "s = sin.(3*t + 4*np.cos(2*t)); # use the numpy cosine and julia sine\n", "fig = plt.gcf() # **** WATCH THIS VARIABLE ****\n", "plt.plot(t, s, color=\"red\", linewidth=2.0, linestyle=\"--\", label=\"sin(3t+4.cos(2t))\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All the above block of code is Julia, where, `range`,`pi`,`sin` are builtins of Julia. `np.*` and `plt.*` are referencing Python function and methods.\n", "\n", "We see that `t` is a Julia \"Array\" (technically a `1000-element StepRangeLen{Float64}`), which can get sent to `numpy.cos`, multiply by a Julia int, (..etc) and end up being plotted via matplotlib (Python), and displayed inline.\n", "\n", "Let's finish our graph in Python" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "fig = %julia fig\n", "fig.axes[0].plot(X[:6], np.log(Y[:6]), '--', label='fib')\n", "fig.axes[0].set_title('A weird Julia function and Fib')\n", "fig.axes[0].legend()\n", "\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above we get the reference to our previously defined figure (in Julia), plot the log of our `fib` function.\n", "The key value here is that we get the _same_ object from within Python and Julia. But let's push even further." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above we had _explicit_ transition between the Julia code and the Python code. Can we be more sneaky?\n", "\n", "One toy example is to define the Fibonacci function using the recursive form and _explicitly_ pass the function with which we recurse.\n", "\n", "We'll define such a function both on the Julia and Python side, ask the Julia function to recurse by calling the Python one, and the Python one to recurse using the Julia one.\n", "\n", "Let's print `(P` when we enter Python Kingdom, `(J` when we enter Julia Realm, and close the parenthesis accordingly:" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function\n", "\n", "\n", "# julia fib function\n", "jlfib = %julia _fib(n, pyfib) = n <= 2 ? 1 : pyfib(n-1, _fib) + pyfib(n-2, _fib)\n", "\n", "\n", "def pyfib(n, _fib):\n", " \"\"\"\n", " Python fib function\n", " \"\"\"\n", " print('(P', end='')\n", " if n <= 2:\n", " r = 1\n", " else:\n", " print('(J', end='')\n", " # here we tell julia (_fib) to recurse using Python\n", " r = _fib(n-1, pyfib) + _fib(n-2, pyfib)\n", " print(')',end='')\n", " print(')',end='')\n", " return r" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(P(J(P(J(P(J(P(J(P)(P)))(P(J))(P(J))(P)))(P(J(P(J))(P)(P)(P)))(P(J(P(J))(P)(P)(P)))(P(J(P)(P)))))(P(J(P(J(P(J))(P)(P)(P)))(P(J(P)(P)))(P(J(P)(P)))(P(J))))(P(J(P(J(P(J))(P)(P)(P)))(P(J(P)(P)))(P(J(P)(P)))(P(J))))(P(J(P(J(P)(P)))(P(J))(P(J))(P)))))" ] }, { "data": { "text/plain": [ "55" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fibonacci = lambda x: pyfib(x, jlfib)\n", "\n", "fibonacci(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Cross language is Easy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I hope you enjoyed that, I find it quite interesting and useful when you need to leverage the tools available across multiple domains. I'm sure there are plenty of other tools that allow this kind of things and a host of other languages that can interact with each other in this way.\n", "\n", "From the top of my head I know of a few magics (SQL, Redis...) that provide such integration. Every language has its strong and weak points, and knowing what to use is often hard. I hope I convinced you that mixing languages is not such a daunting task.\n", "\n", "The other case when this is useful is when you are learning a new language, you can leverage your current expertise temporarily and get something that work before learning the idiomatic way and available libraries.\n", "\n", "You can head back to the [Jupyter blog post](https://blog.jupyter.org/i-python-you-r-we-julia-baf064ca1fb6) to read the end ! \n", "\n", "Happy coding.\n" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" } }, "nbformat": 4, "nbformat_minor": 1 }