tmbb
SciEx - Scientific programming
Code here (very early stage): GitHub - tmbb/sci_ex: Scientific programming for Elixir · GitHub
I have decided to start a small project with the goal of giving real scientific computing capabilities to Elixir. The plan is to implement something like NumPy and SciPy. I plan on basing everything on bindings to the rust library ndarray and probabbly others like faer. The focus will be on “classical” scientific computing. Machine learning tools will not be on the forefront here. I won’t be using Nx because I personally don’t like it much and can’t even get it to run in my personal laptop.
The following IEx session shows what’s possible (sorry for the funky output, I’m currently reusing the normal debug functions for ndarray’s types instead of something more “elixiry”):
iex(1)> alias SciEx.Array1
SciEx.Array1
iex(2)> Array1.zeros(100)
#SciEx.Array1<[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], shape=[100], strides=[1], layout=CFcf (0xf), const ndim=1>
iex(3)> alias SciEx.Array2
SciEx.Array2
iex(4)> Array2.zeros(10, 10)
#SciEx.Array2<[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]], shape=[10, 10], strides=[10, 1], layout=Cc (0x5), const ndim=2>
iex(5)> arr2 = Array2.zeros(10, 10)
#SciEx.Array2<[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]], shape=[10, 10], strides=[10, 1], layout=Cc (0x5), const ndim=2>
iex(6)> SciEx.cos(arr2)
#SciEx.Array2<[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]], shape=[10, 10], strides=[10, 1], layout=Cc (0x5), const ndim=2>
Trending in RFCs
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
tmbb
Added a lot of functionality today. SciEx now support arrays of floats of dimension up to 6 (
SciEx.Float64.Array1, …,SciEx.Float64.Array6). Most of the rust code (and some of the elixir code) in the bindings is now programmatically generated from the Elixir code as part of a separate build step.I have added support for the most common floating point functions, including some special functions. With the new programmatic code generation tools, adding support for more functions is trivial. The functions are currently only vectorized in the first argument, but I plan on adding vectorization on the other arguments.
I have added support for drawing random variables according to the given distribution. For example, one can draw 15 values from a Cauchy distribution:
The PDF and CDF for the supported distributions is also implemented. However, they are only vectorized on the first argument.
I’m not sure on whether I’m taking the right approach regarding the rust code, though. I am writing (and compiling) separate rust functions for each supported type combination because that makes the interop with Elixir easier, although I could probably get away with generating less rust code…
I have also added support for arithmetic operators on arrays and scalars. Array types and dimensions must match, otherwise SciEx will raise a mistake. Everything is very explicit (you can
use SciEx.Operators).tmbb
Because the Rust array library I’m using (GitHub - rust-ndarray/ndarray: ndarray: an N-dimensional array with array views, multidimensional slicing, and efficient operations · GitHub) makes it so easy to parallelize computations across the array, I have added support for parallel computation. It is now automatically activated for each vectorized function (e.g.
SciEx.sin(x),SciEx.exp(x),SciEx.erf(x), etc.) if the array size exceeds a given threshold, which was determined experimentally (one doesn’t want to activate parallelism for smal arrays because it brings some overhead). One can either enable choose a different cutoff for each function on a call by call case (or for the current process) or enable or disable paralellization for all sizes (again, on a call-by-call basis or for the current process).The following benchmark shows the speed gains (this was made on a “busy” laptop with VScode and a web browser open, I plan on doing more rigurous benchmarks with everything but the benchmark turned off). The speed gain is larger for larger arrays, as is expected.
All parallelism is handled by the Rust side (using GitHub - rayon-rs/rayon: Rayon: A data parallelism library for Rust · GitHub, which is integrated as part of
ndarrayas part of a compile-time option). There are probabbly ways of controlling parallelism in a more fine grained way (i.e. try to “reserve” some CPU cores specifically for Rust or something like that), and I might think about it when I have a bit more time).The main limitation of
ndarrayis that it seems like it currently can’t work on arrays larger than the available RAM memory. There used to be some talk about making ndarray run on top of anarrowbackend (which is what polars dos (and by extension, GitHub - elixir-explorer/explorer: Series (one-dimensional) and dataframes (two-dimensional) for fast and elegant data exploration in Elixir · GitHub), but it seems like thearrowbackend doesn’t play well with multidimensional arrays, which is the whole point ofndarray. While it’s not obvious what the use of 4D+ arrays is, 2D and 3D arrays have a natural real-world usage when encoding gridded data in the real word (and 2D arrays can always be implemented as matrices, which have even more real world applications).ndarrayhas linear algebra extensions, which means one could write matrix-manipulating functions as part of SciEx while reusing the same backend, but since linear algebra usually operates exclusively on 1D and 2D arrays (matrices) it’s probably better to implement specific matrix type backed by GitHub - sarah-quinones/faer-rs: Linear algebra foundation for the Rust programming language · GitHub (it seems easy to convert from 2D arrays to faer’s matrix). I’ll leave that for later, as most of the things I plan on using SciEx for don’t require matrices for now. I do understand that if I want to gain traction (i.e. people to help develop the library) I’ll have to implement a bit more than what I intend to use myself.I wonder if in the future it could be possible to integrate SciEx with Explorer dataframes without using Elixir as a bridge, as Explorer is really useful to read columnar data, which is an important format for 1D arrays (and in some cases for 2D arrays).
tmbb
I have added some new improvements (already available in the GitHub repo).
Discrete cosine transforms (the classical demonstration is image compression or gaussian blur, but I haven’t yet implemented support for importing images as an array of pixels; there’s already an ndarray package that does it, I just havdn’t had time to bind it):
I have also added support for complex numbers. With complex numbers I can bind the general complex-to-complex FFT (and not only the rea-to-real DCT):
These complex number are not actual normal Elixir structs with a real and imaginary field. They are implemented as opaque Rustler resources (even the string representation shown here is generated by Rust!), so that I can offload all operations on complex numbers to rust instead of reimplementing them badly in Elixir. These rust complex numbers play well with arrays (that is, one can have arrays of complex numbers) and implement the FFT with that.
I’m generating most of the rustler bindings programatically from Elixir using EEx templates and some custom utilities for more advanced stuff like vectorizing functions on some but not all arguments. Vectorization is interesting because it creates a combinatorial explosion on the function arguments. For example, if you have a function of 3 arguments (let’s say, the PDF for a normal distribution -
normal(x, mu, sigma), you have2^3=8possibilities for the combinations of array and scalar arguments. This most straightforward implementation uses requires 8 different rustler bindings (i.e. rust functions), which I generate programmatically from Elixir based on the argument types. I could probabbly avoid this combinatorial explosion by being a bit smarter with the encoding adn decoding between Rust and Elixir and by doing a bit more work on the rust side, but I’m actually quite happy on how this turns out - having 8 functions instead of 1 is not such a big difference. And in any case, the Rust crate compiles in < 2min, and the extra number of functions shouldn’t cause performance problems.tmbb
If anyone wants to contribute, I believe the best way would be to make it possible to use the package with RustlerPrecompiled, because making it so that the user can use the package without a rust compiler really lowers the barrier to adoption. I’m not at all familiar with rustler precompiled.
kip
Vix implements
write_to_binary/2which returns a binary of arbitrary image data. I’d be happy to collaborate on an efficient way for that data (plus the required metadata) to be passed toSciEx.Due to the work by @akash-akya, we already have bi-drectional integration with evision and Nx so I’m confident this could be done at the NIF level without copying.
tmbb
Thanks, but I don’t think that will be necessary. You can already do that from an extension of
ndarray: ndarray_image - RustSupposedly (according to the docs), you feed it an image and it gives you the appropriate ndarray for free. I just haven’t had the time to bind it.
kip
Not necessary, agreed. However:
ndarray_imageworks on image paths. Sometimes working on image binary is useful - for example if you’ve already done a bunch of pre-processing on an image before numerical processing. Thats quite common.vix/imagemay well want to make use of your library and without a bridge thats not going to be practical (I consider having to write an image to an intermediate file so thatndarray_imagecan ready it back to be impractical - perhaps thats not fair).tmbb
Yes, you’re right, that makes sense. Feel free to contribute in any way you might find useful, thanks!
kip
Happy to take on providing a binary and the associated metadata (dimensions, data type, band order) but I’ve not got the competence to do the work on the Rust side to convert the binary to an
ndarray. I’ll work (hopefully with the support of @akash-akya) on thelibvipsside.tmbb
I have added support for loading 2D-arrays from grayscale PNG images and support for contouring “heightmaps” (represented as 2D arrays) using the marching-squares algorithm. @kip I’m sorry, but I haven’t been able to work on integrating SciEx with your image processing tools yet.
I plan on wrapping my own kernel density estimation (KDE) functions and expose them to Elixir as part of SciEx. Once this is done, I plan on using SciEx for the most computationally heavy parts of my Quartz plotting library.