TheAlgorithms/Python · error · ValueError
epsilon must be non-negative, got {epsilon!r}
Error message
epsilon must be non-negative, got {epsilon!r} What it means
Raised by ramer_douglas_peucker (geometry/ramer_douglas_peucker.py:133) when the epsilon parameter is negative. Epsilon is the maximum perpendicular distance a point may deviate from the simplification line; negative tolerances are geometrically meaningless, so the function validates epsilon >= 0 up front.
Source
Thrown at geometry/ramer_douglas_peucker.py:133
[]
>>> ramer_douglas_peucker([(0.0, 0.0)], epsilon=1.0)
[(0.0, 0.0)]
>>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.0)], epsilon=1.0)
[(0.0, 0.0), (1.0, 0.0)]
>>> # middle point is within epsilon - it is discarded
>>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.1), (2.0, 0.0)], epsilon=0.5)
[(0.0, 0.0), (2.0, 0.0)]
>>> # middle point exceeds epsilon - it is kept
>>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)], epsilon=0.5)
[(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)]
>>> ramer_douglas_peucker([(0.0, 0.0), (1.0, 0.5), (2.0, 0.0)], epsilon=-1.0)
Traceback (most recent call last):
...
ValueError: epsilon must be non-negative, got -1.0
"""
if epsilon < 0:
msg = f"epsilon must be non-negative, got {epsilon!r}"
raise ValueError(msg)
if len(pts) < 3:
return list(pts)
# ---------------------------------------------------------------------------
# Iterative, stack-based implementation.
#
# The naive recursive approach copies sublists at every level via slicing
# (pts[:max_index+1] / pts[max_index:]), which is O(n) per call and makes
# the overall algorithm O(n²) in memory even for well-balanced splits. An
# explicit stack operating on index ranges avoids all copying and also
# eliminates the risk of hitting Python's recursion limit for long polylines.
# ---------------------------------------------------------------------------
n = len(pts)
# keep[i] is True when pts[i] must appear in the output.
keep: list[bool] = [False] * n
keep[0] = TrueView on GitHub (pinned to f5988cc097)
Solutions
- Pass 0.0 to keep every point (no simplification) or a positive tolerance such as 0.5
- Fix the producer of epsilon: clamp with max(0.0, epsilon) only if zero is genuinely acceptable for your use case
- Validate config values at load time so a bad epsilon fails early with your own error message
Example fix
# before simplified = ramer_douglas_peucker(pts, epsilon=base - margin) # can be negative # after epsilon = abs(base - margin) simplified = ramer_douglas_peucker(pts, epsilon=epsilon)
Defensive patterns
Strategy: validation
Validate before calling
if not isinstance(epsilon, (int, float)) or epsilon < 0:
raise ValueError(f"epsilon must be a non-negative number, got {epsilon!r}")
simplified = ramer_douglas_peucker(points, epsilon=epsilon) Try / catch
try:
simplified = ramer_douglas_peucker(pts, epsilon=eps)
except ValueError:
simplified = list(pts) # fall back: keep all points Prevention
- Treat epsilon as an absolute distance; never derive it from a subtraction that can go negative
- Validate tolerance values when loading configuration
When it happens
Trigger: ramer_douglas_peucker(points, epsilon=-1.0), or epsilon arriving as a computed value (e.g. -tolerance due to a sign flip, or 0.0 - margin from floating-point subtraction).
Common situations: Config/UI code computing epsilon as a difference that can go negative; parsing epsilon from config where a minus sign was mistyped; unit tests sweeping epsilon values across a range that includes negatives.
Related errors
- degrees must be a numeric value between 0 and 360.
- length must be a positive numeric value.
- num_cuts must be a positive numeric value.
- surface_area_cube() only accepts non-negative values
- surface_area_cuboid() only accepts non-negative values
AI-assisted analysis of TheAlgorithms/Python@f5988cc097 (2026-08-14).
Data as JSON: /api/errors/1c50f7203c55ce1e.
Report an issue: GitHub.