[PYTHON] Extend NumPy with Rust

Benefits of Python

--Interactive data processing, analysis and visualization on Jupyter --Glue language usage --The Python interface is generally prepared by official and informal --Easy to build environment with pip / wheel or anaconda

Python problems

--Python itself is slow


Efforts to speed up


Python extensions and NumPy extensions

--Python C-API (CPython extension) - http://docs.python.jp/3/c-api/ --Define structures and functions that can be used in Python in C

You need to use both of the two C-APIs


In what language do you want to extend NumPy?


Why Rust?

--Rust is a modern system language suitable for numerical calculations (I think) --Borrow Checker eliminates data races --C ++-like memory model-move semantics (no GC) --Typeclass generics --Powerful type inference - Native Thread --Optimized with LLVM -** I want to write in Rust **


rust-cpython

https://github.com/dgrunwald/rust-cpython

--Python C-API Rust wrapper --Manage reference counts --GIL abstraction --Launch Python interpreter --Publishing functions to Python


Call Python from Rust

extern crate cpython;

use cpython::{Python, PyDict, PyResult};

fn main() {
    let gil = Python::acquire_gil();
    hello(gil.python()).unwrap();
}

fn hello(py: Python) -> PyResult<()> {
    let sys = py.import("sys")?;
    let version: String = sys.get(py, "version")?.extract(py)?;

    let locals = PyDict::new(py);
    locals.set_item(py, "os", py.import("os")?)?;
    let user: String = py.eval("os.getenv('USER') or os.getenv('USERNAME')", None, Some(&locals))?.extract(py)?;

    println!("Hello {}, I'm Python {}", user, version);
    Ok(())
}

Calling Rust functions from Python

#[macro_use] extern crate cpython;

use cpython::{PyResult, Python};

// add bindings to the generated python module
// N.B: names: "librust2py" must be the name of the `.so` or `.pyd` file
py_module_initializer!(librust2py, initlibrust2py, PyInit_librust2py, |py, m| {
    try!(m.add(py, "__doc__", "This module is implemented in Rust."));
    try!(m.add(py, "sum_as_string", py_fn!(py, sum_as_string_py(a: i64, b:i64))));
    Ok(())
});

// logic implemented as a normal rust function
fn sum_as_string(a:i64, b:i64) -> String {
    format!("{}", a + b).to_string()
}

// rust-cpython aware function. All of our python interface could be
// declared in a separate module.
// Note that the py_fn!() macro automatically converts the arguments from
// Python objects to Rust values; and the Rust return value back into a Python object.
fn sum_as_string_py(_: Python, a:i64, b:i64) -> PyResult<String> {
    let out = sum_as_string(a, b);
    Ok(out)
}

rust-numpy

https://github.com/termoshtt/rust-numpy

--Made in GW (/ ・ ω ・) / --Published NumPy C-API to Rust based on rust-cpython --Introduce PyArray corresponding to numpy.ndarray --Convert to ʻArray` in rust-ndarray


rust-ndarray

https://github.com/bluss/rust-ndarray

--Linear algebra library in Rust --Stride method ndarray similar to NumPy

extern crate ndarray;
use ndarray::*;

// immutable example
fn axpy(a: f64, x: ArrayViewD<f64>, y: ArrayViewD<f64>) -> ArrayD<f64> {
    a * &x + &y
}

// mutable example (no return)
fn mult(a: f64, mut x: ArrayViewMutD<f64>) {
    x *= a;
}

rust-numpy

#[macro_use]
extern crate cpython;
extern crate numpy;

use numpy::*;
use cpython::{PyResult, Python, PyObject};

// wrapper of `axpy`
fn axpy_py(py: Python, a: f64, x: PyArray, y: PyArray) -> PyResult<PyArray> {
    let np = PyArrayModule::import(py)?;
    let x = x.as_array().into_pyresult(py, "x must be f64 array")?;
    let y = y.as_array().into_pyresult(py, "y must be f64 array")?;
    Ok(axpy(a, x, y).into_pyarray(py, &np))
}

// wrapper of `mult`
fn mult_py(py: Python, a: f64, x: PyArray) -> PyResult<PyObject> {
    let x = x.as_array_mut().into_pyresult(py, "x must be f64 array")?;
    mult(a, x);
    Ok(py.None()) // Python function must returns
}

/* Define module "_rust_ext" */
py_module_initializer!(_rust_ext, init_rust_ext, PyInit__rust_ext, |py, m| {
    m.add(py, "__doc__", "Rust extension for NumPy")?;
    m.add(py, "axpy", py_fn!(py, axpy_py(a: f64, x: PyArray, y: PyArray)))?;
    m.add(py, "mult", py_fn!(py, mult_py(a: f64, x: PyArray)))?;
    Ok(())
});

setuptools-rust

--rust-A library for handling cpython extensions with setuptools

setup.py
extensions/Cargo.toml
           src/lib.rs
rust_ext/__init__.py

setup.py


from setuptools import setup
from setuptools_rust import RustExtension

setup(name='rust_ext',
      version='1.0',
      rust_extensions=[
          RustExtension('rust_ext._rust_ext', 'extensions/Cargo.toml')],
      packages=['rust_ext'],
      zip_safe=False)

rust_ext/__init__.py


from ._rust_ext import *

Call rust-numpy

python setup.py install
import rust_ext
import numpy as np

x = np.array([1.0, 2.0])
y = np.array([2.0, 3.0])
rust_ext.axpy(3, x, y)

(*'▽') It worked!


Finally: When Rust is painful

Recommended Posts

Extend NumPy with Rust
Moving average with numpy
[Rust / Python] Handle numpy with PyO3 (August 2020 version)
Getting Started with Numpy
Matrix concatenation with Numpy
Hamming code with numpy
Regression analysis with NumPy
Kernel regression with Numpy only
I wrote GP with numpy
CNN implementation with just numpy
Artificial data generation with numpy
Try matrix operation with NumPy
Diffusion equation animation with NumPy
Debt repayment simulation with numpy
Implemented SMO with Python + NumPy
Stick strings together with Numpy
Handle numpy arrays with f2py
Use OpenBLAS with numpy, scipy
Python3 | Getting Started with numpy
Implementing logistic regression with NumPy
Use Python-like range with Rust
Perform least squares fitting with numpy.
Draw a beautiful circle with numpy
Implement Keras LSTM feedforward with numpy
Extract multiple elements with Numpy array
Read and write csv files with numpy
Create bins with NumPy, get data-bin correspondence
Graph trigonometric functions with numpy and matplotlib
I made a life game with Numpy
Read a character data file with numpy
Handle numpy with Cython (method by memoryview)
Use multithreaded BLAS / LAPACK with numpy / scipy