I’m looking for resources for introducing Julia to people as a possible solution to challenges experienced in a python-based scientific-computing project. The challenges arise from inefficiency and lack of composability (this is actually part of my question, how to characterize and articulate the challenges of the current system as well as the opportunities offered by Julia). The format is an hour long presentation. The audience are relatively experienced scientists/engineers. My plan is to scroll through a Jupyter notebook. But, if people know of other notebooks or slides I can use, please let me know.
I don’t plan to dwell too much on the execution speed of Julia (no plots of microbenchmarks). I guess this is the easiest thing for people to understand and appreciate. I’ll integrate execution times in the story of other features.
The harder part is understanding why Julia is more productive (in many situations, at least). Regarding composability and multiple dispatch: Is the best thing to pull examples out of “The unreasonable effectiveness of multiple dispatch.” ? IIRC Chris R and Lyndon may have some related blog posts or presentations.
I want to use the situation in Python frequently as a counterpoint. I know we support the notion that all languages are beautiful in their own special way, which is laudable. My goal here is not to be antagonistic, but rather to explain specific technical benefits to using Julia in this kind of project, as well as the social benefit of happier, more enthusiastic developers (not least myself); some are quite happy using Python, others not so much. This is important, because you need to give a very compelling reason to justify adding another language to your stack.
Some topics are:
Specialization on input types, inference, optimization.
Heavy use of functions as first-class objects, devirtualizing/inlining functions passed as arguments because each has it’s own type. For example, adding 1 and then calling a passed function that subtracts one will be compiled into a no-op.
Many ways to compute over an array that are roughly equivalent for efficiency. Explicit loops (with nice iterators, like enumerate
). Array comprehensions. Fused broadcast with dot notation: No functions are vectorized, and all functions are vectorized. (How much engineering goes into vectorization in the Python world ? I don’t have a detailed understanding: elementwise_grad
, numpy.vectorize
, etc.
Interop. @ccall
, PyCall
, @cfunction
, PackageCompiler.jl, pyjulia.
Ways to illustrate the two-language problem. One story is the following about AD (which I know nothing about) I wrote LambertW.jl
, which I chose only because I know it. Maybe it’s a good example in a way because the LambertW function is a bit obscure and is less likely to get the library support that cos
does. With very little, almost no, effort, I ensured that lambertw is compatible with FowardDiff and Zygote. Not by supporting them specifically, but by making sure it’s written in a generic way. In the Python world, there is mpmath.lambertw
which is orders of magnitude slower, and returns numbers that are not python floats or complexes, but something else. The complex one mpmath.ctx_mp_python.mpc
fails isinstance(., complex)
There is also scipy.special.lambertw
which is written in cython. You can find the source online after some digging, it’s not installed by pip. It can accept a real number, but always returns a complex type. The type is, again, not a Python complex
, but something else numpy.complex128
, which nonetheless does satisfy isinstance(., complex)
. The latter function is as efficient as the Julia lambertw
, but only when operating on a numpy array (it is vectorized). mpmath.lambertw
will not accept an array. As far as I can tell, neither of the Python lambertw implementations is compatible with any AD package, because that would require explicit support. The wrapper (or reimplementation, or whatever) autograd.scipy.special
does not implement lambertw
. I don’t know about JAX. Furthermore, even if autograd
did support lambertw
, it would be as a separate function, in a hierarchy separated from other implementations of lambertw
. There is more to the story, but you get the idea. In contrast, a single source LambertW.jl works efficiently with AD (and IntervalArithmetic.jl, etc) with no engineering effort and, in contrast to Python, incurring no complexity in multiple libraries. I don’t understand all the details of this story; maybe someone would like to clarify. But, that’s part of the point. I ensured LambertW works (and efficiently) with AD even though I know very little about it.
I should add that if someone has another story illustrating the same point, but with a less-obscure function, I’d be happy to hear it. Of course the lesson is not about lambertw, and only partly about AD. It’s that similar engineering hurdles will appear repeatedly in one language and either not appear at all or be much easier to clear in the other.