CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pybind11

Seamless operability between C++11 and Python for creating Python bindings of existing C++ code

Pending
Overview
Eval results
Files

numpy-integration.mddocs/

NumPy Integration

pybind11 provides comprehensive integration with NumPy arrays, enabling high-performance data exchange between C++ and Python numerical code. This integration supports the buffer protocol, automatic type conversion, and vectorization of C++ functions.

Capabilities

NumPy Array Wrapper

Core classes for working with NumPy arrays from C++.

class array : public object {
public:
    // Constructors
    array();
    array(const buffer_info &info);
    
    // Array properties
    ssize_t ndim() const;                    // Number of dimensions
    const ssize_t* shape() const;            // Array shape
    const ssize_t* strides() const;          // Array strides
    ssize_t size() const;                    // Total number of elements
    ssize_t itemsize() const;                // Size of each element
    ssize_t nbytes() const;                  // Total number of bytes
    
    // Data access
    void* data() const;                      // Raw data pointer
    template<typename T> T* data() const;    // Typed data pointer
    
    // Array metadata
    std::string dtype() const;               // NumPy dtype string
    bool owndata() const;                    // Whether array owns its data
    bool writeable() const;                  // Whether array is writeable
    
    // Buffer protocol
    buffer_info request(bool writable = false) const;
};

template<typename T>
class array_t : public array {
public:
    // Type-specific array wrapper
    using value_type = T;
    
    // Constructors
    array_t();
    array_t(size_t size);
    array_t(const std::vector<size_t> &shape);
    array_t(const std::vector<size_t> &shape, const std::vector<size_t> &strides);
    array_t(const buffer_info &info);
    
    // Typed data access
    T* data() const;
    T* mutable_data() const;
    
    // Element access (for 1D arrays)
    T& operator[](ssize_t index);
    const T& operator[](ssize_t index) const;
    
    // Multi-dimensional access
    template<typename... Indices>
    T& operator()(Indices... indices);
    
    template<typename... Indices>
    const T& operator()(Indices... indices) const;
    
    // Iteration
    T* begin() const;
    T* end() const;
};

Data Type Support

NumPy data type integration and conversion.

class dtype : public object {
public:
    // Get dtype for C++ type
    template<typename T>
    static dtype of();
    
    // Dtype properties
    ssize_t itemsize() const;
    std::string format() const;
    char kind() const;
    
    // Type checking
    bool is_equiv(const dtype &other) const;
};

// Supported automatic conversions:
// C++ type        -> NumPy dtype
// bool            -> bool
// int8_t          -> int8
// uint8_t         -> uint8  
// int16_t         -> int16
// uint16_t        -> uint16
// int32_t         -> int32
// uint32_t        -> uint32
// int64_t         -> int64
// uint64_t        -> uint64
// float           -> float32
// double          -> float64
// std::complex<float>  -> complex64
// std::complex<double> -> complex128

Function Vectorization

Automatically vectorize C++ functions to work with NumPy arrays.

// Vectorize a function to work element-wise on arrays
template<typename Func>
auto vectorize(Func &&f);

// Vectorize with custom function object
template<typename Return, typename... Args>
class vectorized_function {
public:
    vectorized_function(std::function<Return(Args...)> f);
    
    // Call operator for vectorized execution
    array_t<Return> operator()(const array_t<Args>&... arrays);
};

Buffer Protocol Integration

Direct integration with Python's buffer protocol for efficient data sharing.

class buffer_info {
public:
    void *ptr;                    // Pointer to buffer data
    ssize_t itemsize;             // Size of individual items in bytes
    std::string format;           // Buffer format string (struct module style)
    ssize_t ndim;                 // Number of dimensions
    std::vector<ssize_t> shape;   // Shape of buffer
    std::vector<ssize_t> strides; // Strides for each dimension
    bool readonly;                // Whether buffer is read-only
    
    buffer_info(void *ptr, ssize_t itemsize, const std::string &format,
                ssize_t ndim, std::vector<ssize_t> shape, 
                std::vector<ssize_t> strides, bool readonly = false);
};

// Enable buffer protocol for custom classes
class MyClass {
public:
    buffer_info get_buffer_info();  // Implement this method
};

// In binding code:
py::class_<MyClass>(m, "MyClass")
    .def_buffer(&MyClass::get_buffer_info);

Memory Management

Control memory allocation and lifetime for NumPy arrays.

// Memory management flags for array creation
enum class array_c_style     { f = detail::npy_api::NPY_ARRAY_C_CONTIGUOUS_ };
enum class array_f_style     { f = detail::npy_api::NPY_ARRAY_F_CONTIGUOUS_ };
enum class array_forcecast   { f = detail::npy_api::NPY_ARRAY_FORCECAST_ };

// Create array with specific memory layout
template<typename T>
array_t<T> make_array(const std::vector<size_t> &shape, 
                      const T* data = nullptr,
                      handle base = handle());

Eigen Integration

Integration with the Eigen linear algebra library (requires #include <pybind11/eigen.h>).

// Automatic conversion between Eigen matrices and NumPy arrays
// Eigen::Matrix<T, Rows, Cols> <-> numpy.ndarray
// Eigen::VectorXd <-> numpy.ndarray (1D)  
// Eigen::MatrixXd <-> numpy.ndarray (2D)

// Eigen types are automatically supported in function signatures
Eigen::MatrixXd process_matrix(const Eigen::MatrixXd& input);
Eigen::VectorXd solve_system(const Eigen::MatrixXd& A, const Eigen::VectorXd& b);

Usage Examples

Basic Array Operations

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

// Function that works with NumPy arrays
py::array_t<double> square_array(py::array_t<double> input) {
    // Get buffer info
    py::buffer_info buf = input.request();
    
    // Check that we have a 1-D array
    if (buf.ndim != 1)
        throw std::runtime_error("Input array must be 1-dimensional");
    
    // Create output array
    auto result = py::array_t<double>(buf.size);
    py::buffer_info res_buf = result.request();
    
    // Perform computation
    double *input_ptr = static_cast<double*>(buf.ptr);
    double *output_ptr = static_cast<double*>(res_buf.ptr);
    
    for (size_t i = 0; i < buf.shape[0]; i++) {
        output_ptr[i] = input_ptr[i] * input_ptr[i];
    }
    
    return result;
}

PYBIND11_MODULE(example, m) {
    m.def("square_array", &square_array, "Square all elements in array");
}

Vectorization Example

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

// Simple scalar function
double compute_value(double x, double y) {
    return x * x + y * y;
}

PYBIND11_MODULE(example, m) {
    // Vectorize the function automatically
    m.def("compute_vectorized", py::vectorize(compute_value),
          "Vectorized computation of x^2 + y^2");
}

// Python usage:
// import numpy as np
// x = np.array([1, 2, 3])
// y = np.array([4, 5, 6]) 
// result = compute_vectorized(x, y)  # Returns array([17, 29, 45])

Multi-dimensional Array Processing

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

py::array_t<double> process_2d_array(py::array_t<double> input) {
    py::buffer_info buf = input.request();
    
    if (buf.ndim != 2)
        throw std::runtime_error("Input array must be 2-dimensional");
    
    int rows = buf.shape[0];
    int cols = buf.shape[1];
    
    // Create output array with same shape
    auto result = py::array_t<double>({rows, cols});
    py::buffer_info res_buf = result.request();
    
    double *input_ptr = static_cast<double*>(buf.ptr);
    double *output_ptr = static_cast<double*>(res_buf.ptr);
    
    // Process each element (example: apply smoothing filter)
    for (int i = 1; i < rows - 1; i++) {
        for (int j = 1; j < cols - 1; j++) {
            double sum = 0.0;
            for (int di = -1; di <= 1; di++) {
                for (int dj = -1; dj <= 1; dj++) {
                    sum += input_ptr[(i + di) * cols + (j + dj)];
                }
            }
            output_ptr[i * cols + j] = sum / 9.0;
        }
    }
    
    return result;
}

PYBIND11_MODULE(example, m) {
    m.def("process_2d_array", &process_2d_array);
}

Custom Class with Buffer Protocol

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <vector>

namespace py = pybind11;

class DataContainer {
    std::vector<double> data_;
    std::vector<size_t> shape_;
    
public:
    DataContainer(const std::vector<size_t>& shape) : shape_(shape) {
        size_t size = 1;
        for (auto dim : shape) size *= dim;
        data_.resize(size, 0.0);
    }
    
    // Enable buffer protocol
    py::buffer_info get_buffer_info() {
        return py::buffer_info(
            data_.data(),                          // Pointer to data
            sizeof(double),                        // Size of one scalar
            py::format_descriptor<double>::format(), // Python struct-style format
            shape_.size(),                         // Number of dimensions
            shape_,                               // Buffer dimensions
            calculate_strides(shape_)             // Strides for each index
        );
    }
    
private:
    std::vector<size_t> calculate_strides(const std::vector<size_t>& shape) {
        std::vector<size_t> strides(shape.size());
        size_t stride = sizeof(double);
        for (int i = shape.size() - 1; i >= 0; i--) {
            strides[i] = stride;
            stride *= shape[i];
        }
        return strides;
    }
};

PYBIND11_MODULE(example, m) {
    py::class_<DataContainer>(m, "DataContainer", py::buffer_protocol())
        .def(py::init<const std::vector<size_t>&>())
        .def_buffer(&DataContainer::get_buffer_info);
}

// Python usage:
// container = DataContainer([10, 20])
// array = np.array(container, copy=False)  # Direct access to C++ data

Integration with Eigen

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <Eigen/Dense>

namespace py = pybind11;

// Automatic conversion between Eigen and NumPy
Eigen::MatrixXd matrix_multiply(const Eigen::MatrixXd& A, const Eigen::MatrixXd& B) {
    return A * B;
}

Eigen::VectorXd solve_linear_system(const Eigen::MatrixXd& A, const Eigen::VectorXd& b) {
    return A.colPivHouseholderQr().solve(b);
}

PYBIND11_MODULE(example, m) {
    m.def("matrix_multiply", &matrix_multiply);
    m.def("solve_linear_system", &solve_linear_system);
}

// Python usage:
// import numpy as np
// A = np.random.random((5, 5))
// B = np.random.random((5, 3))
// C = matrix_multiply(A, B)  # Automatic conversion

Performance Considerations

Memory Layout and Copying

// Avoid unnecessary copying
py::array_t<double> process_inplace(py::array_t<double> array) {
    // Request mutable buffer to modify in-place
    py::buffer_info buf = array.request(/* writable = */ true);
    
    double* ptr = static_cast<double*>(buf.ptr);
    // Modify data in-place...
    
    return array;  // Return modified array
}

// Control memory layout
auto create_c_contiguous() {
    return py::array_t<double>(
        {100, 100},  // shape
        {100 * sizeof(double), sizeof(double)}  // C-style strides
    );
}

auto create_f_contiguous() {
    return py::array_t<double>(
        {100, 100},  // shape  
        {sizeof(double), 100 * sizeof(double)}  // Fortran-style strides
    );
}

Types

namespace pybind11 {
    // Core NumPy types
    class array;
    template<typename T> class array_t;
    class dtype;
    class buffer_info;
    
    // Vectorization
    template<typename Func> auto vectorize(Func &&f);
    template<typename Return, typename... Args> class vectorized_function;
    
    // Memory layout flags
    enum class array_c_style;
    enum class array_f_style; 
    enum class array_forcecast;
    
    // Format descriptors for buffer protocol
    template<typename T> struct format_descriptor;
    
    // Buffer protocol utilities
    template<typename T> 
    py::buffer_info get_buffer_info(T *ptr, ssize_t size);
}

Install with Tessl CLI

npx tessl i tessl/pypi-pybind11

docs

advanced-features.md

core-binding.md

exception-handling.md

index.md

numpy-integration.md

python-package.md

stl-integration.md

type-system.md

tile.json