NumPy Array Integration in pybind11-rdp: Row-Major vs Column-Major Layouts
pybind11-rdp requires C-contiguous (row-major) NumPy arrays for zero-copy data transfer; column-major or non-contiguous arrays must be converted using np.ascontiguousarray() to avoid performance penalties or incorrect results.
The cubao/pybind11-rdp library provides Python bindings for the Ramer-Douglas-Peucker (RDP) line simplification algorithm using pybind11 and Eigen. Understanding how this integration handles NumPy array memory layout is critical for achieving optimal performance and avoiding silent data copying when processing coordinate data.
Why Row-Major Storage Matters
The C++ implementation in src/main.cpp defines point containers using Eigen matrices with explicit row-major storage:
using RowVectors = Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::RowMajor>;
using RowVectorsNx2 = Eigen::Matrix<double, Eigen::Dynamic, 2, Eigen::RowMajor>;
(source)
pybind11’s eigen.h header automatically maps C-contiguous (row-major) NumPy arrays to these Eigen types without copying data. When a NumPy array is Fortran-contiguous (column-major), the memory layout does not match the expected row-major stride pattern. In this case, pybind11 either creates an expensive copy to rearrange the data or, in older versions, may produce undefined behavior or incorrect coordinate interpretations.
Python Wrapper Implementation
The high-level Python API in src/pybind11_rdp/__init__.py prepares inputs for the C++ binding by converting them to double-precision arrays:
points = np.asarray(points, dtype=np.float64)
(source)
The wrapper performs no explicit order conversion; it assumes the user provides a C-contiguous array. When the data reaches the C++ layer via the _rdp binding, pybind11 expects the layout to match the RowMajor template parameters defined in the Eigen typedefs.
Handling 2-D and 3-D Coordinate Overloads
The library accepts both 2-D (N × 2) and 3-D (N × 3) coordinate arrays. For 2-D inputs, the binding at lines 24-33 in src/main.cpp creates a temporary 3-D matrix, runs the simplification, and returns the 2-D result:
RowVectors xyzs(coords.rows(), 3);
xyzs.setZero();
xyzs.leftCols(2) = coords;
return douglas_simplify(xyzs, epsilon, recursive).leftCols(2);
(source)
This internal padding assumes the input coords matrix follows row-major layout. If the input were column-major, the leftCols(2) assignment would access memory incorrectly, leading to data corruption or crashes.
Practical Usage Scenarios
Use this reference to ensure your arrays meet the library's memory layout requirements:
- Standard NumPy arrays: Arrays created with
np.array([...])default to C-contiguous (row-major) layout and work without modification. - Column-major arrays: Arrays created with
order='F'must be converted:arr = np.ascontiguousarray(arr)orarr = arr.copy(order='C'). - Non-contiguous views: Slices, transposes, or strided arrays require
np.ascontiguousarray(view)before passing tordp()to ensure contiguous memory. - Mixed dimensions: Both (N, 2) and (N, 3) shapes are accepted, provided they are row-major.
Testing Layout Behavior
import numpy as np
from pybind11_rdp import rdp
# C-contiguous (default) - works out of the box
pts = np.array([[0, 0], [1, 0], [2, 0]], dtype=np.float64)
print(rdp(pts, epsilon=0.5).shape) # → (2, 2)
# Column-major - must be made contiguous
pts_f = np.array([[0, 0], [1, 0], [2, 0]], order='F')
print(pts_f.flags) # 'F_CONTIGUOUS' True, 'C_CONTIGUOUS' False
pts_c = np.ascontiguousarray(pts_f) # now C-contiguous
print(rdp(pts_c, epsilon=0.5).shape) # → (2, 2)
Performance Considerations
Avoid unnecessary memory copies by verifying array contiguity before calling rdp(). According to the cubao/pybind11-rdp source code, passing a pre-existing C-contiguous array enables zero-copy transfer directly into the Eigen RowVectors container. Only call np.ascontiguousarray() when processing slices, transposed matrices, or Fortran-ordered data imported from external libraries like MATLAB or specific scientific computing workflows.
Summary
pybind11-rdpexpects row-major (C-contiguous) NumPy arrays to match theEigen::RowMajormatrix definitions insrc/main.cpp.- The Python wrapper converts inputs to
float64but does not handle layout conversion automatically. - Column-major arrays must be converted using
np.ascontiguousarray()to prevent data copying or corruption. - 2-D data is internally padded to 3-D for algorithm execution, assuming row-major access patterns.
- Key implementation files:
src/main.cpp(C++ logic),src/pybind11_rdp/__init__.py(Python facade), andtests/test_basic.py(usage examples).
Frequently Asked Questions
Does pybind11-rdp support column-major NumPy arrays?
No, not directly. The underlying C++ code uses Eigen::RowMajor storage as defined at lines 46-48 of src/main.cpp. While you can pass a column-major array, pybind11 will either copy the data to make it row-major or fail to map it correctly. Always convert Fortran-ordered arrays using np.ascontiguousarray() before passing them to the rdp() function.
What happens if I pass a non-contiguous array slice?
Non-contiguous views—such as slices with strides or transposed arrays without .copy()—will likely cause pybind11 to create a temporary copy of the data or raise a runtime error. For maximum performance and stability, explicitly convert views with np.ascontiguousarray(slice) to ensure the memory layout matches the row-major expectations of the C++ RowVectors type.
Why does the library use Eigen with row-major storage instead of column-major?
The RDP algorithm processes points as coordinate rows (x, y, z), making row-major layout cache-friendly for row-wise operations. The RowVectors and RowVectorsNx2 typedefs in src/main.cpp enforce this layout to align with standard NumPy defaults (C-contiguous), minimizing friction for Python users while maintaining high-performance C++ execution.
How do I check if my NumPy array is compatible before calling rdp()?
Verify the array flags: arr.flags['C_CONTIGUOUS'] should return True. Alternatively, use arr.flags.f_contiguous to check for column-major layout. If either check fails or if arr.strides indicate non-standard stepping, call np.ascontiguousarray(arr, dtype=np.float64) to produce a compatible copy before simplification.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →