# NumPy Array Integration in pybind11-rdp: Row-Major vs Column-Major Layouts

> Learn how pybind11-rdp handles NumPy array integration discussing row-major vs column-major layouts. Ensure C-contiguous arrays for optimal performance.

- Repository: [cubao/pybind11-rdp](https://github.com/cubao/pybind11-rdp)
- Tags: internals
- Published: 2026-02-28

---

**`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`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp) defines point containers using **Eigen matrices with explicit row-major storage**:

```cpp
using RowVectors   = Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::RowMajor>;
using RowVectorsNx2 = Eigen::Matrix<double, Eigen::Dynamic, 2, Eigen::RowMajor>;

```

([source](/src/main.cpp#L46-L48))

**pybind11’s [`eigen.h`](https://github.com/cubao/pybind11-rdp/blob/main/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`](https://github.com/cubao/pybind11-rdp/blob/main/src/pybind11_rdp/__init__.py) prepares inputs for the C++ binding by converting them to double-precision arrays:

```python
points = np.asarray(points, dtype=np.float64)

```

([source](/src/pybind11_rdp/__init__.py#L37-L38))

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`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp) creates a temporary 3-D matrix, runs the simplification, and returns the 2-D result:

```cpp
RowVectors xyzs(coords.rows(), 3);
xyzs.setZero();
xyzs.leftCols(2) = coords;
return douglas_simplify(xyzs, epsilon, recursive).leftCols(2);

```

([source](/src/main.cpp#L25-L31))

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)` or `arr = arr.copy(order='C')`.
- **Non-contiguous views**: Slices, transposes, or strided arrays require `np.ascontiguousarray(view)` before passing to `rdp()` to ensure contiguous memory.
- **Mixed dimensions**: Both (N, 2) and (N, 3) shapes are accepted, provided they are row-major.

### Testing Layout Behavior

```python
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-rdp` expects row-major (C-contiguous) NumPy arrays** to match the `Eigen::RowMajor` matrix definitions in [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp).
- **The Python wrapper converts inputs to `float64`** but 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`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp) (C++ logic), [`src/pybind11_rdp/__init__.py`](https://github.com/cubao/pybind11-rdp/blob/main/src/pybind11_rdp/__init__.py) (Python facade), and [`tests/test_basic.py`](https://github.com/cubao/pybind11-rdp/blob/main/tests/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`](https://github.com/cubao/pybind11-rdp/blob/main/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`](https://github.com/cubao/pybind11-rdp/blob/main/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.