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] = True

View on GitHub (pinned to f5988cc097)

Solutions

  1. Pass 0.0 to keep every point (no simplification) or a positive tolerance such as 0.5
  2. Fix the producer of epsilon: clamp with max(0.0, epsilon) only if zero is genuinely acceptable for your use case
  3. 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

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


AI-assisted analysis of TheAlgorithms/Python@f5988cc097 (2026-08-14). Data as JSON: /api/errors/1c50f7203c55ce1e. Report an issue: GitHub.