Commit de61e262 authored by Sebastian Kehl's avatar Sebastian Kehl
Browse files

Merge branch 'feature_numpy' into 'main'

Feature numpy

See merge request !1
parents 1fcfcbd8 23487996
_skbuild
hello_world.egg-info
*.egg-info
......@@ -2,66 +2,111 @@
## Introduction
With the popularity and success of the Python programming language, it has become increasingly important for researchers to be able to make software developments available to the Python eco-system by means of extension packages. To this end, the [Pybind11](https://github.com/pybind/pybind11) library provides a convenient approach to generate Python bindings for (existing) C++ code. In this context, the [sciki-build](https://github.com/scikit-build/scikit-build) project can be used to bridge Python's `setuptools` with [CMake](https://cmake.org/). As a result, CMake's features such as, e.g., choice of build-generators, dependency management or cross-compilation, can easily be exploited from within the installation process of the developed Python extension.
With the rising popularity of the Python programming language it has become increasingly important for computational scientists to be able to make their software easily available to the Python ecosystem and community.
Historically, exposing compiled extensions from C/C++ to Python has often been cumbersome, error prone and technically challenging, given the plethora of compilers, libraries and relevant target platforms developers have to deal with.
The present article introduces a combination of two Python packages that promise to make this daunting task much easier and more stable.
First, the [Pybind11](https://github.com/pybind/pybind11) header-only library provides a convenient approach to generate Python bindings for existing or newly developed C++ code.
Second, the [scikit-build](https://github.com/scikit-build/scikit-build) package can be used to bridge Python's `setuptools` with [CMake](https://cmake.org/), leveraging the power of CMake for the build process of the Python extension.
As a result, CMake's native features such as, e.g., discovering and linking of numerical libraries, dependency management, support of various build-generators, or even cross-compilation can easily be taken advantage of during the build process of the Python extension.
A key advantage is that the file 'setup.py' stays minimal and simple, instead the aforementioned complexities are handled by CMake.
## Basic Usage
The basic usage of pybind11 in combination with scikit-build will be demonstrated in the following using a 'hello-world' Python extension package that can be accessed [here](sebak/pybind11-hello-world).
The basic usage of pybind11 in combination with scikit-build is demonstrated below by means of a simple Python extension package.
The code example can be obtained from [MPCDF GitLab](https://gitlab.mpcdf.mpg.de/sebak/pybind11-hello-world).
## pybind11 hello-world
### Interfacing Python/NumPy with C++ using pybind11
pybind11 is a header-only library that provides conversion from C++ types to Python. The following C++ file demonstrates its use
pybind11 is a header-only library that provides conversion from C/C++ types to Python, and vice versa.
The following C++ Python extension module 'cumsum' demonstrates its use in combination with NumPy arrays.
```c++
#include <iostream>
// Python example module 'cumsum'
#include <numeric>
#include <functional>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
void hello() {
std::cout << "Hello, World!" << std::endl;
// numpy-like cumulative sum, taking a NumPy array as input and returning a NumPy array
py::array_t<double> cumsum(py::array_t<double> a)
{
// obtain information about the n-d input array
auto shape = a.request().shape;
size_t count = std::accumulate(shape.begin(), shape.end(), 1, std::multiplies<size_t>());
// create output NumPy array
py::array_t<double> b(count);
// obtain raw pointers
double * a_p = (double*) a.request().ptr;
double * b_p = (double*) b.request().ptr;
// compute cumulative sum into b
double cs = 0.0;
for (size_t i = 0; i<count; ++i) {
cs += a_p[i];
b_p[i] = cs;
}
return b;
}
PYBIND11_MODULE(_hello, m) {
m.doc() = "Package to say hi";
m.def("hello", &hello, "Prints \"Hello, World!\"");
PYBIND11_MODULE(cumsum, m) {
m.doc() = "pybind11 cumulative sum example"; // module docstring
m.def("cumsum", &cumsum, // third parameter is the function docstring
"return the cumulative sum of a double-precision numpy array");
}
```
In order for this example to be compiled, the pybind11 headers (as any other potential dependency) must be available. pybind11 can be installed in [several ways](https://pybind11.readthedocs.io/en/stable/installing.html) and it also natively support various [build systems](https://pybind11.readthedocs.io/en/stable/compiling.html#compiling). However using scikit-build provides a particularly easy approach which is shown in the following.
The 'cumsum' example module could finally be used and tested as follows:
## build with scikit-learn
```python
import numpy as np
import cumsum
a = np.random.rand(20)
b = cumsum.cumsum(a)
c = np.cumsum(a)
assert(np.allclose(b, c))
```
In order for this example to be compiled, the pybind11 headers (as any other potential dependency) must be available. pybind11 can be installed in [several ways](https://pybind11.readthedocs.io/en/stable/installing.html), and it natively supports various [build systems](https://pybind11.readthedocs.io/en/stable/compiling.html#compiling).
However using scikit-build provides a particularly easy approach which is shown in the following.
### build with scikit-learn
scikit-build provides a drop-in replacement for the `setuptools.setup` function that can be used in a project's `setup.py` via
The package scikit-build provides a drop-in replacement for the `setuptools.setup` function that can be used in a project's `setup.py` via
```python
from skbuild import setup
```
Beside the standard setuptools options, it provides [extra options](https://scikit-build.readthedocs.io/en/latest/usage.html#scikit-build-options) to control the CMake build. In addition, a `CMakeLists.txt` file must be available in the top-level directory of the project such as, e.g.,
Beside the standard setuptools options, it provides [extra options](https://scikit-build.readthedocs.io/en/latest/usage.html#scikit-build-options) to control the CMake build.
In addition, a minimal `CMakeLists.txt` file must be available in the top-level directory of the project, e.g.:
```cmake
cmake_minimum_required(VERSION 3.18)
project(hello-world VERSION "1.0")
project(pybind11-hello-world VERSION "1.0")
find_package(pybind11)
pybind11_add_module(_hello MODULE src/hello/hello.cpp)
install(TARGETS _hello DESTINATION .)
pybind11_add_module(_cumsum MODULE src/cumsum/cumsum.cpp)
install(TARGETS _cumsum DESTINATION .)
```
Build-system dependencies have to be specified via the project's `pyproject.toml` file:
```
```toml
[build-system]
requires = [
"setuptools>=42",
"wheel",
"pybind11[global]~=2.6.0",
"pybind11[global]>=2.6.0",
"cmake>=3.18",
"scikit-build",
]
build-backend = "setuptools.build_meta"
```
With modern Python packaging, it is thus not necessary to manually install scikit-build, but all build-dependencies will be installed in an isolated build-environment. Like this, also dependencies like pybind11 or CMake can be made available with an up-to-date version of `pip`. Please note that the `[global]` feature of the pybind11 requirement, which installs headers and libraries, does not apply to the used Python installation or environment but to the dedicated build environment and can thus be used safely here.
Now the Python module can be compiled and installed by running the command `pip install --user .` in the root directory of the project. Similarly Wheel archives can be created for distribution.
Note that with modern Python packaging tools it is not necessary to manually install pybind11 and scikit-build, instead all build dependencies will be installed into an isolated build environment by `pip`.
The '[global]' feature of the pybind11 requirement is necessary to install the include and cmake files correctly into the dedicated build environment, it does not affect the Python installation or environment in use and can thus be used safely.
*Sebastian Kehl, Klaus Reuter*
cmake_minimum_required(VERSION 3.18)
project(hello-world VERSION "1.0")
project(pybind11-hello-world VERSION "1.0")
find_package(pybind11)
pybind11_add_module(_hello MODULE src/hello/hello.cpp)
install(TARGETS _hello DESTINATION .)
pybind11_add_module(_cumsum MODULE src/cumsum/cumsum.cpp)
install(TARGETS _cumsum DESTINATION .)
\ No newline at end of file
# pybind11-hello-world
This is a minimal 'hello-world' example demonstrating the use of the [pybind11](https://github.com/pybind/pybind11) binding library using [scikit-build](https://github.com/scikit-build/scikit-build). This example and examples using different python bindings can be found [here](https://github.com/scikit-build/scikit-build-sample-projects).
This minimal Python/NumPy-based example demonstrates the use of the [pybind11](https://github.com/pybind/pybind11) binding library in concert with [scikit-build](https://github.com/scikit-build/scikit-build).
## Installation
`pip install .`
Run `pip install --user .` to build and install locally.
## Usage
`python -c "import hello; hello.hello()"`
Please see 'test/test_cumsum.py' for details.
......@@ -2,10 +2,8 @@
requires = [
"setuptools>=42",
"wheel",
"pybind11[global]~=2.6.0",
"pybind11[global]>=2.6.0",
"cmake>=3.18",
"scikit-build",
]
build-backend = "setuptools.build_meta"
build-backend = "setuptools.build_meta"
\ No newline at end of file
import sys
from skbuild import setup
setup(
name="hello-world",
name="pybind11-hello-world",
version="1.0",
description="hello world example",
description="pybind11/skbuild/numpy cumulative sum example package",
author='MPCDF',
license="MIT",
packages=['hello'],
packages=['cumsum'],
package_dir={'': 'src'},
cmake_install_dir='src/hello'
cmake_install_dir='src/cumsum'
)
from ._cumsum import cumsum
\ No newline at end of file
// pybind11 example module '_cumsum'
#include <numeric>
#include <functional>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
// numpy-like cumulative sum, taking a NumPy array as input and returning a NumPy array
py::array_t<double> cumsum(py::array_t<double> a)
{
// obtain information about the nd input array
auto shape = a.request().shape;
size_t count = std::accumulate(shape.begin(), shape.end(), 1, std::multiplies<size_t>());
// create output NumPy array
py::array_t<double> b(count);
// obtain raw pointers
double * a_p = (double*) a.request().ptr;
double * b_p = (double*) b.request().ptr;
// compute cumulative sum into b
double cs = 0.0;
for (size_t i = 0; i<count; ++i) {
cs += a_p[i];
b_p[i] = cs;
}
return b;
}
// Python binary module cumsum, exposing the cumsum C++ function to Python
PYBIND11_MODULE(_cumsum, m) {
m.doc() = "pybind11 cumulative sum example"; // module docstring
m.def("cumsum", &cumsum, // third parameter is the function docstring
"return the cumulative sum of a double-precision numpy array");
}
from ._hello import hello
#include <iostream>
#include <pybind11/pybind11.h>
namespace py = pybind11;
void hello() {
std::cout << "Hello, World!" << std::endl;
}
PYBIND11_MODULE(_hello, m) {
m.doc() = "Package to say hi";
m.def("hello", &hello, "Prints \"Hello, World!\"");
}
#!/usr/bin/env python3
def test_prereq():
import numpy
def test_import():
import cumsum
def test_cumsum():
import numpy as np
import cumsum
a = np.random.rand(80)
ap = a.reshape(20,4)
b = cumsum.cumsum(ap)
c = np.cumsum(ap)
assert(np.allclose(b, c))
print("OK!")
if __name__ == "__main__":
test_prereq()
test_import()
test_cumsum()
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment