Skip to content

[BUG] HashGrid truncation error causes missed neighbors with negative coordinates #1256

Closed

Description

@daedalus5

Bug Description

Summary

hash_grid_query fails to find valid neighbors when query bounds or point coordinates are negative. The root cause is float-to-int conversion using C++ int() cast (truncation toward zero) instead of floor() (toward negative infinity) in the native HashGrid implementation.

Affected code

warp/native/hashgrid.h — three locations:

  1. Point cell assignment (line 86): When building the grid, points with negative fractional cell coordinates are binned into the wrong cell.
  2. Query lower bounds (lines 150-152): The start of the cell search range is computed incorrectly when it falls in negative space.
  3. Query upper bounds (lines 155-157): Same issue for the end of the search range.

Root cause

C++ int() truncates toward zero, while floor() rounds toward negative infinity. They agree for positive values but diverge for negative fractions:

Value int(value) floor(value)
0.7 0 0
-0.3 0 -1
-1.5 -1 -2

For example, with cell_width = 1.0, a point at position -0.3 should be assigned to cell -1 but int(-0.3) = 0 places it in cell 0. Similarly, a query centered at +0.2 with radius 0.6 has a lower bound of -0.4, which should search cell -1 but int(-0.4) = 0 skips it entirely.

The downstream hash_grid_index(grid, x, y, z) function correctly handles negative integer cell indices via a large offset and modulo wrapping — the bug is purely in the upstream float-to-int conversion that feeds it.

Impact

  • Neighbor queries that cross the zero boundary in any axis silently miss valid neighbors (false negatives).
  • Points with negative fractional coordinates are assigned to the wrong grid cell during build(), making them invisible to queries searching the correct cell.
  • Affects any simulation using negative coordinate space or relying on HashGrid's toroidal wrapping (e.g., periodic boundary conditions in example_particle_repulsion.py).
  • All precision types (fp16, fp32, fp64) are affected.

Fix

Replace int(expr) with int(floor(Type(expr))) at all three locations. This matches the pattern used by other Warp native code (noise.h, volume.h, texture.h) which correctly applies floor() before integer conversion.

System Information

No response

Activity

  1. added this to the 1.12.1 milestone on Feb 25, 2026
  2. self-assigned this
    on Feb 25, 2026
  3. added theissue type on Feb 25, 2026
  4. added a commit that references this issue on Mar 10, 2026
    59394a8
  5. shi-eric commented on Mar 11, 2026

    @shi-eric
    Contributor

    Clarification on when this bug causes missed neighbors:

    The issue correctly identifies the root cause (int() truncating toward zero instead of floor() rounding toward negative infinity), but the impact is more nuanced than described.

    Because both point insertion (hash_grid_index) and query range computation (hash_grid_query) use the same int() truncation, there is a cancellation effect: points are stored in the "wrong" cell, but queries also search the "wrong" cells in the same way. For large grids, this means neighbors are still found despite incorrect cell assignment.

    The cancellation breaks down when grid wrapping is involved. The int() truncation shifts negative fractional cell indices by +1 toward zero, but positive indices are unaffected. After the grid's modulo operation (% dim), this asymmetric shift can map to a completely different physical bucket. For example, with grid_dim=4 and cell_width=1.0:

    • Point at -0.3: int(-0.3) = 0 → physical cell 0. But floor(-0.3) = -1 → physical cell 3.
    • Point at 3.9: cell 3 in both cases (positive, no disagreement).

    These two points are periodic neighbors (distance 0.2 in a domain of size 4), but the bug places them in different physical cells (0 vs 3). The query from -0.3 only searches cell 0 and misses the point at 3.9.

    In practice, this primarily affects:

    • Simulations with periodic boundary conditions and small grid dimensions relative to the coordinate range
    • Any scenario where the +1 cell shift from int() wraps to a different physical bucket than floor() would produce

    Purely positive coordinates are unaffected since int() and floor() agree for non-negative values.

  6. added a commit that references this issue on Mar 12, 2026
    94119ea
  7. shi-eric commented on Mar 16, 2026

    @shi-eric
    Contributor

    This was addressed in e0ec2d0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions