Describe the bug
The dask "Tier C" out-of-core fallback in viewshed() produces visibility masks that diverge substantially from the exact numpy sweep, but the docstring at xrspatial/viewshed.py:1670 only describes "minor visibility differences near the boundary of occluded regions." On random terrain the divergence is far larger than that, and there is no runtime warning. Large dask rasters can feed downstream visibility logic with silently wrong results.
Tier C (starting at xrspatial/viewshed.py:2227) is a horizon-profile distance sweep that discretizes occlusion into angular bins. It is a different, approximate visibility model from the exact GRASS r.viewshed event sweep used by the CPU/Tier-B path. The error is geometric, not a resolution artifact: raising the angular bin count does not reduce the mismatch count.
Reproduce
Force Tier C on a small random terrain (call _viewshed_distance_sweep directly, or shrink the memory budget so _viewshed_dask skips Tier B) and diff the visibility mask against _viewshed_cpu.
Measured mask mismatches vs the exact sweep (observer at grid center, integer-coordinate grid, no observer/target elevation offset):
- 20x20, seed 0: 81 / 400 cells (20.2%)
- 20x20, seed 42: 25 / 400 cells (6.2%)
- 40x40, seed 42: 199 / 1600 cells (12.4%)
Mismatches include both false positives and false negatives. Raising n_angles from 360 to 500000 does not change the count.
Expected behavior
Either Tier C matches the exact sweep, or the documentation states the real accuracy contract and the code emits a runtime warning when the Tier C path runs, so users do not silently receive approximate visibility from large dask inputs.
Additional context
Reimplementing the exact event sweep out-of-core is a large undertaking, and it is the reason Tier C exists: the exact algorithm's working set does not fit when the grid exceeds memory. The horizon-profile model cannot be made exact without becoming the exact algorithm.
Affected: dask+numpy and dask+cupy backends on grids large enough to skip Tier B and without max_distance set.
Describe the bug
The dask "Tier C" out-of-core fallback in
viewshed()produces visibility masks that diverge substantially from the exact numpy sweep, but the docstring atxrspatial/viewshed.py:1670only describes "minor visibility differences near the boundary of occluded regions." On random terrain the divergence is far larger than that, and there is no runtime warning. Large dask rasters can feed downstream visibility logic with silently wrong results.Tier C (starting at
xrspatial/viewshed.py:2227) is a horizon-profile distance sweep that discretizes occlusion into angular bins. It is a different, approximate visibility model from the exact GRASS r.viewshed event sweep used by the CPU/Tier-B path. The error is geometric, not a resolution artifact: raising the angular bin count does not reduce the mismatch count.Reproduce
Force Tier C on a small random terrain (call
_viewshed_distance_sweepdirectly, or shrink the memory budget so_viewshed_daskskips Tier B) and diff the visibility mask against_viewshed_cpu.Measured mask mismatches vs the exact sweep (observer at grid center, integer-coordinate grid, no observer/target elevation offset):
Mismatches include both false positives and false negatives. Raising
n_anglesfrom 360 to 500000 does not change the count.Expected behavior
Either Tier C matches the exact sweep, or the documentation states the real accuracy contract and the code emits a runtime warning when the Tier C path runs, so users do not silently receive approximate visibility from large dask inputs.
Additional context
Reimplementing the exact event sweep out-of-core is a large undertaking, and it is the reason Tier C exists: the exact algorithm's working set does not fit when the grid exceeds memory. The horizon-profile model cannot be made exact without becoming the exact algorithm.
Affected: dask+numpy and dask+cupy backends on grids large enough to skip Tier B and without
max_distanceset.