Choosing Between `algo='iter'` and `algo='rec'` in pybind11-rdp
Use algo='iter' for large or degenerate datasets to avoid stack overflow risks, and algo='rec' for small-to-moderate datasets where minimal overhead matters.
The pybind11-rdp library from cubao implements the Ramer-Douglas-Peucker (RDP) line simplification algorithm with two distinct computational back-ends. Understanding when to choose between the iterative (algo='iter') and recursive (algo='rec') parameters ensures optimal performance and prevents crashes on large geometric datasets.
Algorithm Implementations in the Source Code
The C++ implementation in src/main.cpp provides two variants of the RDP algorithm that share the same core pivot-selection logic but differ in traversal strategy.
The Iterative Back-End (douglas_simplify_iter)
The iterative implementation resides in src/main.cpp at lines 86-126 within the douglas_simplify_iter function. This version uses an explicit std::queue to process line-segment intervals in a breadth-first manner. Instead of recursive function calls, it repeatedly pops an interval from the queue, finds the farthest point (the pivot), and pushes the two new sub-intervals back onto the queue.
This approach guarantees linear space complexity and eliminates deep recursion, making it safe for very long or highly degenerate point sequences.
The Recursive Back-End (douglas_simplify)
The recursive implementation occupies lines 50-84 in src/main.cpp inside the douglas_simplify function. This is the classic divide-and-conquer approach: after locating the pivot point with maximum distance, the function calls itself recursively on the left and right sub-intervals. While this offers simpler control flow and slightly lower constant-time overhead, the recursion depth grows with the number of points and may hit the C++ stack limit on pathological inputs.
How the Python API Maps to C++
In src/pybind11_rdp/__init__.py (lines 71-94), the rdp function maps the Python algo parameter to a C++ boolean flag using the logic recursive = "iter" != algo. When you specify algo='iter', the wrapper calls the iterative C++ function; any other value (including algo='rec') triggers the recursive path.
Both implementations use the identical distance-to-segment computation via the LineSegment::distance2 method, ensuring mathematical equivalence regardless of traversal method.
When to Use Each Algorithm
Select the appropriate algorithm based on your data characteristics and performance requirements:
-
algo='iter'(default) – Recommended for typical datasets with thousands of points and mandatory for very large or highly degenerate data (e.g., 10,000+ points on a grid). The queue-based approach prevents stack overflow while maintaining fast execution through breadth-first processing. -
algo='rec'– Suitable for small-to-moderate datasets where you prefer the simplest control flow. The lack ofstd::queueheap allocations may provide marginal speed gains on tiny inputs, though the risk of stack overflow makes this unsafe for large sequences.
The library's own regression test in tests/test_basic.py (lines 42-46) demonstrates this choice explicitly:
ret = rdp(coords, 2e-15, algo="recursive")
assert len(ret) == len(coords)
This test validates that both algorithms handle degenerate cases correctly, though only the iterative version guarantees safety at scale.
Practical Code Examples
Basic usage with the default iterative algorithm:
import numpy as np
from pybind11_rdp import rdp
pts = np.array([[0, 0], [1, 0.1], [2, 0], [3, 0.2], [4, 0]])
simplified = rdp(pts, epsilon=0.15) # uses algo='iter' implicitly
print(simplified.shape) # → (3, 2)
Explicitly selecting the recursive implementation:
simplified_rec = rdp(pts, epsilon=0.15, algo="rec")
print(np.allclose(simplified, simplified_rec)) # True
Performance comparison with large degenerate data (14,000 points):
import time
import numpy as np
from pybind11_rdp import rdp
coords = np.tile([[0,0], [1,0], [1,1], [0,1]], 3500).astype(float)
t0 = time.time()
_ = rdp(coords, epsilon=2e-15, algo="iter")
print("Iterative:", time.time() - t0, "s")
t0 = time.time()
_ = rdp(coords, epsilon=2e-15, algo="rec") # Risk of crash here
print("Recursive:", time.time() - t0, "s")
On most systems, the iterative version completes the 14,000-point degenerate grid in approximately 4 seconds, while the recursive version may exceed the C++ stack limit and terminate abnormally.
Summary
- The
algo='iter'parameter invokesdouglas_simplify_iterinsrc/main.cpp, using astd::queuefor breadth-first traversal that guarantees stack safety on large inputs. - The
algo='rec'parameter invokesdouglas_simplifyinsrc/main.cpp, employing classic recursion that risks stack overflow on pathological datasets but offers marginally lower overhead on small data. - Both algorithms produce mathematically identical results through the same
LineSegment::distance2calculations. - The Python wrapper in
src/pybind11_rdp/__init__.pydefaults to the iterative algorithm to prioritize stability over micro-optimizations.
Frequently Asked Questions
What is the default algorithm if I don't specify the algo parameter?
The rdp function defaults to algo='iter' as implemented in src/pybind11_rdp/__init__.py. This ensures safe execution across all input sizes without requiring users to understand C++ stack limitations.
Can algo='rec' cause a crash or segmentation fault?
Yes. The recursive implementation in src/main.cpp relies on the C++ call stack for each subdivision. Processing tens of thousands of points—especially degenerate cases where minimal simplification occurs—can exhaust stack memory and cause the Python process to crash.
Do both algorithms return exactly the same simplified coordinates?
Yes. Both douglas_simplify and douglas_simplify_iter use identical pivot-selection logic and the same LineSegment::distance2 calculation. The only difference is traversal order (depth-first vs. breadth-first), which does not affect the final geometric result.
Is there a performance difference between the two algorithms?
For small datasets (hundreds of points), the recursive algorithm may show slightly lower latency due to avoiding std::queue allocations. However, for large datasets, the iterative algorithm often performs better in practice because it avoids stack frame overhead and operates with predictable memory locality. Benchmark both on your specific data if performance is critical.
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 →