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) 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

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.
  • 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 (C++ logic), src/pybind11_rdp/__init__.py (Python facade), and 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. 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →