Skip to content

Aim sky_view_factor rays in ground azimuth space (#3626)#3634

Open
brendancol wants to merge 2 commits into
mainfrom
issue-3626
Open

Aim sky_view_factor rays in ground azimuth space (#3626)#3634
brendancol wants to merge 2 commits into
mainfrom
issue-3626

Conversation

@brendancol

Copy link
Copy Markdown
Contributor

Closes #3626

  • _svf_cpu and _svf_gpu now start each ray from a ground azimuth and convert it to a unit step in cell-index space (cos(phi)/cellsize_x, sin(phi)/cellsize_y, renormalized). With square cells this reduces to the old directions; a 40x40 random grid produces bit-identical output before and after.
  • On the issue's repro (planar 45-degree ramp, analytic SVF 0.75), cell aspect 1:1 / 1:2 / 2:1 / 1:4 gave 0.728 / 0.773 / 0.693 / 0.815 before and 0.728 / 0.723 / 0.723 / 0.709 after. The residual spread comes from nearest-cell ray sampling, which the issue documents as a separate, aspect-independent effect.
  • Docstrings now say azimuth spacing is uniform on the ground.

Backend coverage: the CPU and CUDA kernels carry the same change, so numpy, cupy, dask+numpy, and dask+cupy all pick it up. Verified on a CUDA host.

Test plan:

  • pytest xrspatial/tests/test_sky_view_factor.py -- 38 passed on a CUDA host (cupy and dask+cupy paths ran, not skipped)
  • new test_anisotropic_cellsize_ground_truth_consistency fails on main (diffs 0.046 / 0.035 vs 0.02 tolerance), passes here
  • new anisotropic numpy==dask and numpy==cupy parity tests
  • flat=1.0, flip/rot90/translate invariants, and NaN-holes-at-chunk-boundary parity probes all unchanged at machine epsilon

Also carries the accuracy-sweep state CSV row for this module (2026-07-03 re-audit).

Ray directions were uniform in cell-index angle, so rectangular cells
(cellsize_x != cellsize_y) sampled ground azimuths non-uniformly and
biased the SVF average. Convert each ground azimuth to a unit step in
cell-index space instead. Square-cell results are unchanged.

Also records the 2026-07-03 accuracy sweep state for the module.

@brendancol brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: Aim sky_view_factor rays in ground azimuth space (#3626)

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

  • xrspatial/sky_view_factor.py:159-166 (and the GPU twin at 213-220): the direction table (dx, dy per azimuth) is recomputed for every pixel. That was true before this PR too, and the inner radius loop dominates runtime, so this is not a merge concern. If the kernels get touched again, precomputing the n_directions unit vectors once per array would drop two divides and a sqrt per pixel-direction.

Nits (optional improvements)

  • xrspatial/sky_view_factor.py:326-329: the new docstring text explains the behavior well. A sentence noting the residual limit would round it out: at extreme aspect ratios (say 10:1) the integer-cell ray marching still quantizes the sampled azimuths, so the evenly spaced azimuths are nominal, met to within one cell of rounding. The new consistency test only covers 2:1, which is the realistic range.
  • No asv benchmark exists for sky_view_factor (pre-existing gap, and this PR adds no new function). Flagging for the benchmark sweep rather than for this PR.

What looks good

  • The fix is minimal and mathematically right: dividing the ground-azimuth unit vector by cell size and renormalizing gives a unit cell-index step, so map_overlap depth semantics (depth == max_radius) are untouched.
  • Square-cell output is bit-identical to main (verified on a 40x40 random grid), so no existing user results change.
  • test_anisotropic_cellsize_ground_truth_consistency fails on main (diffs 0.046/0.035 vs the 0.02 tolerance) and passes here with 3x headroom (0.0065/0.0061). It encodes a physical invariant (grid aspect cannot change the SVF of the same terrain), not a snapshot value.
  • The changed CUDA path gets its own parity tests (test_anisotropic_numpy_equals_dask, test_anisotropic_numpy_equals_cupy); the full suite ran on a CUDA host with cupy and dask+cupy executing, 38 passed.
  • dnorm cannot be zero: cos and sin cannot both vanish and the public wrapper floors non-positive cell sizes to 1.0 before the kernels run.

Checklist

  • Algorithm matches reference/paper (Zakšek et al. 2011 formula unchanged; only azimuth spacing corrected)
  • All implemented backends produce consistent results (CPU and CUDA kernels carry the same change; parity tests added)
  • NaN handling is correct (untouched by this PR; center-NaN and ray-truncation semantics unchanged)
  • Edge cases are covered by tests (flat surface, cell-size overrides, anisotropic aspect ratios)
  • Dask chunk boundaries handled correctly (direction depends only on scalar cell sizes, identical per chunk; depth unchanged)
  • No premature materialization or unnecessary copies
  • Benchmark exists or is not needed (no sky_view_factor benchmark; pre-existing gap, see nit)
  • README feature matrix updated (not applicable, no new function or backend change)
  • Docstrings present and accurate

@brendancol brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: follow-up after 66d805d

Disposition of the first review's findings:

  • Suggestion (per-pixel direction table): fixed for the CPU kernel. _svf_cpu now fills dxs/dys once before the pixel loops (xrspatial/sky_view_factor.py:148-159) and output is bit-identical to the previous commit on a 40x40 random grid. The CUDA kernel keeps per-thread computation: cuda.local.array needs a compile-time size and n_directions is a runtime argument, so a table there means a kernel signature change threaded through _run_cupy and _run_dask_cupy. Out of scope for an accuracy fix; per-thread trig is normal CUDA style anyway.
  • Nit (docstring): added. The public docstring now says azimuth spacing is nominal to within one cell of rounding and quantizes toward the grid axes beyond roughly 4:1 aspect (xrspatial/sky_view_factor.py:326-332).
  • Nit (missing asv benchmark): not addressed here, routed to the benchmark sweep. Pre-existing gap, no new function in this PR.

Checks on the delta itself: the hoisted table is filled with the same expressions in the same order, np.empty inside @ngjit is fine, and the full test file passes (38 tests, CUDA host). No new findings.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sky_view_factor: ray azimuths uniform in cell-index space, biasing SVF on rectangular cells

1 participant