xarray-spatial 0.9.4__tar.gz → 0.9.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. xarray_spatial-0.9.6/.claude/accuracy-sweep-state.json +9 -0
  2. xarray_spatial-0.9.6/.claude/commands/accuracy-sweep.md +158 -0
  3. xarray_spatial-0.9.6/.claude/commands/sweep-performance.md +494 -0
  4. xarray_spatial-0.9.6/.claude/performance-sweep-state.json +47 -0
  5. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.github/workflows/benchmarks.yml +2 -2
  6. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/CHANGELOG.md +31 -0
  7. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/PKG-INFO +40 -26
  8. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/README.md +39 -25
  9. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/setup.cfg +11 -0
  10. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xarray_spatial.egg-info/PKG-INFO +40 -26
  11. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xarray_spatial.egg-info/SOURCES.txt +9 -0
  12. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/__init__.py +4 -0
  13. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/_version.py +3 -3
  14. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/accessor.py +12 -0
  15. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/aspect.py +12 -4
  16. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/balanced_allocation.py +34 -6
  17. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/classify.py +2 -2
  18. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/cost_distance.py +33 -6
  19. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/dasymetric.py +16 -0
  20. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/diffusion.py +90 -28
  21. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/erosion.py +22 -0
  22. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/normalize.py +22 -33
  23. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/preview.py +3 -1
  24. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/rasterize.py +4 -1
  25. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/__init__.py +13 -0
  26. xarray_spatial-0.9.6/xrspatial/sieve.py +480 -0
  27. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/surface_distance.py +14 -0
  28. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_balanced_allocation.py +32 -0
  29. xarray_spatial-0.9.6/xrspatial/tests/test_dask_laziness.py +180 -0
  30. xarray_spatial-0.9.6/xrspatial/tests/test_sieve.py +502 -0
  31. xarray_spatial-0.9.6/xrspatial/tests/test_visibility.py +276 -0
  32. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_zonal.py +37 -1
  33. xarray_spatial-0.9.6/xrspatial/visibility.py +256 -0
  34. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/zonal.py +33 -13
  35. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/backend-parity.md +0 -0
  36. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/bench.md +0 -0
  37. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/dask-notebook.md +0 -0
  38. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/efficiency-audit.md +0 -0
  39. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/new-issues.md +0 -0
  40. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/release-major.md +0 -0
  41. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/release-minor.md +0 -0
  42. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/release-patch.md +0 -0
  43. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/review-pr.md +0 -0
  44. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/rockout.md +0 -0
  45. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/user-guide-notebook.md +0 -0
  46. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.claude/commands/validate.md +0 -0
  47. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.gitattributes +0 -0
  48. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  49. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.github/ISSUE_TEMPLATE/feature-proposal.md +0 -0
  50. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.github/labeler.yml +0 -0
  51. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.github/pull_request_template.md +0 -0
  52. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.github/workflows/labeler.yml +0 -0
  53. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.github/workflows/pypi-publish.yml +0 -0
  54. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.github/workflows/test.yml +0 -0
  55. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.gitignore +0 -0
  56. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/.readthedocs.yml +0 -0
  57. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/CODE_OF_CONDUCT.md +0 -0
  58. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/CONTRIBUTING.md +0 -0
  59. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/Citation-styles.md +0 -0
  60. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/LICENSE.txt +0 -0
  61. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/MANIFEST.in +0 -0
  62. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/RELEASE.md +0 -0
  63. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/codecov.yml +0 -0
  64. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/pyproject.toml +0 -0
  65. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/setup.py +0 -0
  66. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xarray_spatial.egg-info/dependency_links.txt +0 -0
  67. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xarray_spatial.egg-info/entry_points.txt +0 -0
  68. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xarray_spatial.egg-info/not-zip-safe +0 -0
  69. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xarray_spatial.egg-info/requires.txt +0 -0
  70. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xarray_spatial.egg-info/top_level.txt +0 -0
  71. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/__main__.py +0 -0
  72. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/analytics.py +0 -0
  73. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/bilateral.py +0 -0
  74. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/bump.py +0 -0
  75. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/contour.py +0 -0
  76. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/convolution.py +0 -0
  77. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/corridor.py +0 -0
  78. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/curvature.py +0 -0
  79. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/dataset_support.py +0 -0
  80. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/datasets/__init__.py +0 -0
  81. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/datasets/sentinel-2/blue_band.nc +0 -0
  82. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/datasets/sentinel-2/green_band.nc +0 -0
  83. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/datasets/sentinel-2/nir_band.nc +0 -0
  84. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/datasets/sentinel-2/red_band.nc +0 -0
  85. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/datasets/sentinel-2/swir1_band.nc +0 -0
  86. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/datasets/sentinel-2/swir2_band.nc +0 -0
  87. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/diagnostics.py +0 -0
  88. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/edge_detection.py +0 -0
  89. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/emerging_hotspots.py +0 -0
  90. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/experimental/__init__.py +0 -0
  91. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/experimental/min_observable_height.py +0 -0
  92. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/fire.py +0 -0
  93. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/flood.py +0 -0
  94. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/focal.py +0 -0
  95. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geodesic.py +0 -0
  96. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/__init__.py +0 -0
  97. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/_compression.py +0 -0
  98. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/_dtypes.py +0 -0
  99. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/_geotags.py +0 -0
  100. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/_gpu_decode.py +0 -0
  101. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/_header.py +0 -0
  102. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/_reader.py +0 -0
  103. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/_vrt.py +0 -0
  104. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/_writer.py +0 -0
  105. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/__init__.py +0 -0
  106. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/bench_vs_rioxarray.py +0 -0
  107. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/conftest.py +0 -0
  108. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_accessor_io.py +0 -0
  109. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_accuracy_1081.py +0 -0
  110. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_cog.py +0 -0
  111. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_compression.py +0 -0
  112. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_compression_level.py +0 -0
  113. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_dtype_read.py +0 -0
  114. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_edge_cases.py +0 -0
  115. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_features.py +0 -0
  116. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_geotags.py +0 -0
  117. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_header.py +0 -0
  118. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_jpeg.py +0 -0
  119. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_jpeg2000.py +0 -0
  120. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_lerc.py +0 -0
  121. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_lz4.py +0 -0
  122. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_reader.py +0 -0
  123. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_streaming_write.py +0 -0
  124. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_vrt_write.py +0 -0
  125. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/geotiff/tests/test_writer.py +0 -0
  126. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/glcm.py +0 -0
  127. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/gpu_rtx/__init__.py +0 -0
  128. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/gpu_rtx/cuda_utils.py +0 -0
  129. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/gpu_rtx/hillshade.py +0 -0
  130. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/gpu_rtx/mesh_utils.py +0 -0
  131. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/gpu_rtx/viewshed.py +0 -0
  132. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hillshade.py +0 -0
  133. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/__init__.py +0 -0
  134. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/_boundary_store.py +0 -0
  135. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/basin_d8.py +0 -0
  136. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/fill_d8.py +0 -0
  137. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_accumulation_d8.py +0 -0
  138. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_accumulation_dinf.py +0 -0
  139. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_accumulation_mfd.py +0 -0
  140. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_direction_d8.py +0 -0
  141. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_direction_dinf.py +0 -0
  142. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_direction_mfd.py +0 -0
  143. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_length_d8.py +0 -0
  144. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_length_dinf.py +0 -0
  145. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_length_mfd.py +0 -0
  146. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_path_d8.py +0 -0
  147. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_path_dinf.py +0 -0
  148. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/flow_path_mfd.py +0 -0
  149. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/hand_d8.py +0 -0
  150. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/hand_dinf.py +0 -0
  151. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/hand_mfd.py +0 -0
  152. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/sink_d8.py +0 -0
  153. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/snap_pour_point_d8.py +0 -0
  154. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/stream_link_d8.py +0 -0
  155. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/stream_link_dinf.py +0 -0
  156. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/stream_link_mfd.py +0 -0
  157. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/stream_order_d8.py +0 -0
  158. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/stream_order_dinf.py +0 -0
  159. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/stream_order_mfd.py +0 -0
  160. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/__init__.py +0 -0
  161. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/conftest.py +0 -0
  162. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_basin_d8.py +0 -0
  163. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_fill_d8.py +0 -0
  164. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_accumulation_d8.py +0 -0
  165. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_accumulation_dinf.py +0 -0
  166. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_accumulation_mfd.py +0 -0
  167. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_direction_d8.py +0 -0
  168. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_direction_dinf.py +0 -0
  169. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_direction_mfd.py +0 -0
  170. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_length_d8.py +0 -0
  171. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_length_dinf.py +0 -0
  172. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_length_mfd.py +0 -0
  173. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_path_d8.py +0 -0
  174. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_path_dinf.py +0 -0
  175. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_flow_path_mfd.py +0 -0
  176. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_hand_d8.py +0 -0
  177. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_hand_dinf.py +0 -0
  178. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_hand_mfd.py +0 -0
  179. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_sink_d8.py +0 -0
  180. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_snap_pour_point_d8.py +0 -0
  181. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_stream_link_d8.py +0 -0
  182. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_stream_link_dinf.py +0 -0
  183. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_stream_link_mfd.py +0 -0
  184. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_stream_order_d8.py +0 -0
  185. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_stream_order_dinf.py +0 -0
  186. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_stream_order_mfd.py +0 -0
  187. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_twi_d8.py +0 -0
  188. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_watershed_d8.py +0 -0
  189. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_watershed_dinf.py +0 -0
  190. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/tests/test_watershed_mfd.py +0 -0
  191. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/twi_d8.py +0 -0
  192. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/watershed_d8.py +0 -0
  193. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/watershed_dinf.py +0 -0
  194. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/hydro/watershed_mfd.py +0 -0
  195. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/interpolate/__init__.py +0 -0
  196. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/interpolate/_idw.py +0 -0
  197. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/interpolate/_kriging.py +0 -0
  198. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/interpolate/_spline.py +0 -0
  199. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/interpolate/_validation.py +0 -0
  200. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/mahalanobis.py +0 -0
  201. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/mcda/__init__.py +0 -0
  202. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/mcda/combine.py +0 -0
  203. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/mcda/constrain.py +0 -0
  204. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/mcda/sensitivity.py +0 -0
  205. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/mcda/standardize.py +0 -0
  206. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/mcda/weights.py +0 -0
  207. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/morphology.py +0 -0
  208. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/multispectral.py +0 -0
  209. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/pathfinding.py +0 -0
  210. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/perlin.py +0 -0
  211. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/polygonize.py +0 -0
  212. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/proximity.py +0 -0
  213. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_crs_utils.py +0 -0
  214. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_datum_grids.py +0 -0
  215. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_grid.py +0 -0
  216. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_interpolate.py +0 -0
  217. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_itrf.py +0 -0
  218. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_lite_crs.py +0 -0
  219. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_merge.py +0 -0
  220. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_projections.py +0 -0
  221. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_projections_cuda.py +0 -0
  222. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_transform.py +0 -0
  223. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/_vertical.py +0 -0
  224. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/at_bev_AT_GIS_GRID.tif +0 -0
  225. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/au_icsm_A66_National_13_09_01.tif +0 -0
  226. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/be_ign_bd72lb72_etrs89lb08.tif +0 -0
  227. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/ch_swisstopo_CHENyx06_ETRS.tif +0 -0
  228. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/de_adv_BETA2007.tif +0 -0
  229. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/es_ign_SPED2ETV2.tif +0 -0
  230. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/nl_nsgi_rdcorr2018.tif +0 -0
  231. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/pt_dgt_D73_ETRS89_geo.tif +0 -0
  232. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/uk_os_OSTN15_NTv2_OSGBtoETRS.tif +0 -0
  233. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/us_nga_egm96_15.tif +0 -0
  234. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/us_noaa_alaska.tif +0 -0
  235. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/us_noaa_conus.tif +0 -0
  236. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/us_noaa_hawaii.tif +0 -0
  237. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/us_noaa_nadcon5_nad27_nad83_1986_conus.tif +0 -0
  238. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/reproject/grids/us_noaa_prvi.tif +0 -0
  239. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/sky_view_factor.py +0 -0
  240. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/slope.py +0 -0
  241. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/terrain.py +0 -0
  242. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/terrain_metrics.py +0 -0
  243. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/__init__.py +0 -0
  244. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/bench_reproject_vs_rioxarray.py +0 -0
  245. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/conftest.py +0 -0
  246. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/general_checks.py +0 -0
  247. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_accessor.py +0 -0
  248. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_analytics.py +0 -0
  249. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_aspect.py +0 -0
  250. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_bilateral.py +0 -0
  251. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_bump.py +0 -0
  252. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_classify.py +0 -0
  253. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_contour.py +0 -0
  254. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_corridor.py +0 -0
  255. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_cost_distance.py +0 -0
  256. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_curvature.py +0 -0
  257. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_dask_cupy_gaps.py +0 -0
  258. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_dasymetric.py +0 -0
  259. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_dataset_support.py +0 -0
  260. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_datasets.py +0 -0
  261. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_diagnostics.py +0 -0
  262. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_diffusion.py +0 -0
  263. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_edge_detection.py +0 -0
  264. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_emerging_hotspots.py +0 -0
  265. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_erosion.py +0 -0
  266. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_fire.py +0 -0
  267. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_flood.py +0 -0
  268. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_focal.py +0 -0
  269. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_fused_overlap.py +0 -0
  270. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_geodesic_aspect.py +0 -0
  271. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_geodesic_slope.py +0 -0
  272. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_glcm.py +0 -0
  273. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_glcm_metric_order.py +0 -0
  274. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_hillshade.py +0 -0
  275. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_interpolation.py +0 -0
  276. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_lite_crs.py +0 -0
  277. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_mahalanobis.py +0 -0
  278. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_mcda.py +0 -0
  279. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_min_observable_height.py +0 -0
  280. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_morphology.py +0 -0
  281. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_morphology_derived.py +0 -0
  282. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_multi_overlap.py +0 -0
  283. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_multispectral.py +0 -0
  284. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_normalize.py +0 -0
  285. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_northness_eastness.py +0 -0
  286. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_pathfinding.py +0 -0
  287. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_perlin.py +0 -0
  288. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_polygonize.py +0 -0
  289. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_preview.py +0 -0
  290. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_proximity.py +0 -0
  291. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_rasterize.py +0 -0
  292. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_rasterize_accuracy.py +0 -0
  293. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_rechunk_no_shuffle.py +0 -0
  294. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_reproject.py +0 -0
  295. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_sky_view_factor.py +0 -0
  296. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_slope.py +0 -0
  297. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_surface_distance.py +0 -0
  298. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_terrain.py +0 -0
  299. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_terrain_metrics.py +0 -0
  300. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_utils.py +0 -0
  301. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_validation.py +0 -0
  302. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/tests/test_viewshed.py +0 -0
  303. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/utils.py +0 -0
  304. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/viewshed.py +0 -0
  305. {xarray_spatial-0.9.4 → xarray_spatial-0.9.6}/xrspatial/worley.py +0 -0
@@ -0,0 +1,9 @@
1
+ {
2
+ "inspections": {
3
+ "zonal": { "last_inspected": "2026-03-30T12:00:00Z", "issue": 1090 },
4
+ "focal": { "last_inspected": "2026-03-30T13:00:00Z", "issue": 1092 },
5
+ "multispectral": { "last_inspected": "2026-03-30T14:00:00Z", "issue": 1094 },
6
+ "proximity": { "last_inspected": "2026-03-30T15:00:00Z", "issue": null, "notes": "Direction >= boundary fragile but works due to truncated constant. Float32 truncation is design choice. No wrong-results bugs found." },
7
+ "curvature": { "last_inspected": "2026-03-30T15:00:00Z", "issue": null, "notes": "Formula matches ArcGIS reference. Backends consistent. No issues found." }
8
+ }
9
+ }
@@ -0,0 +1,158 @@
1
+ # Accuracy Sweep: Generate a Ralph Loop targeting under-inspected modules
2
+
3
+ Analyze xrspatial modules by recency and inspection history, then print a
4
+ ready-to-run `/ralph-loop` command that targets the highest-priority modules.
5
+
6
+ Optional arguments: $ARGUMENTS
7
+ (e.g. `--top 5`, `--exclude slope,aspect`, `--only-terrain`, `--reset-state`)
8
+
9
+ ---
10
+
11
+ ## Step 1 -- Gather module metadata via git
12
+
13
+ For every `.py` file directly under `xrspatial/` (skip `__init__.py`,
14
+ `_version.py`, `__main__.py`, `utils.py`, `accessor.py`, `preview.py`,
15
+ `dataset_support.py`, `diagnostics.py`, `analytics.py`), collect:
16
+
17
+ | Field | How |
18
+ |-------|-----|
19
+ | **last_modified** | `git log -1 --format=%aI -- xrspatial/<module>.py` |
20
+ | **first_commit** | `git log --diff-filter=A --format=%aI -- xrspatial/<module>.py` |
21
+ | **total_commits** | `git log --oneline -- xrspatial/<module>.py \| wc -l` |
22
+ | **recent_accuracy_commits** | `git log --oneline --grep='accuracy\|precision\|numerical\|geodesic' -- xrspatial/<module>.py` |
23
+
24
+ Store results in a temporary variable -- do NOT write intermediate files.
25
+
26
+ ## Step 2 -- Load inspection state
27
+
28
+ Read the state file at `.claude/accuracy-sweep-state.json`.
29
+
30
+ If it does not exist, treat every module as never-inspected.
31
+
32
+ If `$ARGUMENTS` contains `--reset-state`, delete the file and treat
33
+ everything as never-inspected.
34
+
35
+ The state file schema:
36
+
37
+ ```json
38
+ {
39
+ "inspections": {
40
+ "slope": { "last_inspected": "2026-03-28T14:00:00Z", "issue": 1042 },
41
+ "aspect": { "last_inspected": "2026-03-28T15:30:00Z", "issue": 1043 }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Step 3 -- Score each module
47
+
48
+ Compute a priority score for each module. Higher = more urgent.
49
+
50
+ ```
51
+ days_since_inspected = (today - last_inspected).days # 9999 if never inspected
52
+ days_since_modified = (today - last_modified).days
53
+ total_commits = from Step 1
54
+ has_recent_accuracy_work = 1 if recent_accuracy_commits is non-empty, else 0
55
+
56
+ score = (days_since_inspected * 3)
57
+ + (total_commits * 0.5)
58
+ - (days_since_modified * 0.2)
59
+ - (has_recent_accuracy_work * 500)
60
+ ```
61
+
62
+ Rationale:
63
+ - Modules never inspected dominate (9999 * 3)
64
+ - More commits = more complex = more likely to have bugs
65
+ - Recently modified modules slightly deprioritized (someone just touched them)
66
+ - Modules with existing accuracy work heavily deprioritized
67
+
68
+ ## Step 4 -- Apply filters from $ARGUMENTS
69
+
70
+ - `--top N` -- only include the top N modules (default: 5)
71
+ - `--exclude mod1,mod2` -- remove named modules from the list
72
+ - `--only-terrain` -- restrict to slope, aspect, curvature, terrain,
73
+ terrain_metrics, hillshade, sky_view_factor
74
+ - `--only-focal` -- restrict to focal, convolution, morphology, bilateral,
75
+ edge_detection, glcm
76
+ - `--only-hydro` -- restrict to flood, cost_distance, geodesic,
77
+ surface_distance, viewshed, erosion, diffusion
78
+
79
+ ## Step 5 -- Print the results
80
+
81
+ ### 5a. Print the ranked table
82
+
83
+ Print a markdown table showing ALL scored modules (not just the selected ones),
84
+ sorted by score descending:
85
+
86
+ ```
87
+ | Rank | Module | Score | Last Inspected | Last Modified | Commits |
88
+ |------|-----------------|--------|----------------|---------------|---------|
89
+ | 1 | viewshed | 30012 | never | 45 days ago | 23 |
90
+ | 2 | flood | 29998 | never | 120 days ago | 18 |
91
+ | ... | ... | ... | ... | ... | ... |
92
+ ```
93
+
94
+ ### 5b. Print the generated ralph-loop command
95
+
96
+ Using the top N modules from the ranked list, generate and print a command
97
+ like this (adapt the module list to actual results):
98
+
99
+ ````
100
+ /ralph-loop "Survey xarray-spatial modules for numerical accuracy issues.
101
+
102
+ **Target these modules in priority order:**
103
+ 1. viewshed (xrspatial/viewshed.py) -- never inspected, 23 commits
104
+ 2. flood (xrspatial/flood.py) -- never inspected, 18 commits
105
+ 3. focal (xrspatial/focal.py) -- never inspected, 31 commits
106
+ 4. erosion (xrspatial/erosion.py) -- never inspected, 12 commits
107
+ 5. classify (xrspatial/classify.py) -- never inspected, 9 commits
108
+
109
+ **For each module, in order:**
110
+ 1. Read the source and identify potential accuracy issues:
111
+ - Floating point precision loss
112
+ - Incorrect NaN propagation
113
+ - Off-by-one errors in neighborhood operations
114
+ - Missing or wrong Earth curvature corrections
115
+ - Backend inconsistencies (numpy vs cupy vs dask results differ)
116
+ 2. Run /rockout to fix the issue end-to-end (issue, worktree, fix, tests, docs)
117
+ 3. After completing rockout for ONE module, output <promise>ITERATION DONE</promise>
118
+
119
+ If you find no accuracy issues in the current target module, skip it and move
120
+ to the next one.
121
+
122
+ If all target modules have been addressed or have no issues, output
123
+ <promise>ALL ACCURACY ISSUES FIXED</promise>." --max-iterations {N} --completion-promise "ALL ACCURACY ISSUES FIXED"
124
+ ````
125
+
126
+ Set `--max-iterations` to the number of target modules + 2 (buffer for retries).
127
+
128
+ ### 5c. Print a reminder
129
+
130
+ ```
131
+ To run this sweep: copy the command above and paste it.
132
+ To update state after a manual rockout: edit .claude/accuracy-sweep-state.json
133
+ To reset all tracking: /accuracy-sweep --reset-state
134
+ ```
135
+
136
+ ## Step 6 -- Update state (ONLY when called from inside a ralph-loop)
137
+
138
+ This step is informational. The accuracy-sweep command itself does NOT update
139
+ the state file. State is updated when `/rockout` completes -- the rockout
140
+ workflow should append to `.claude/accuracy-sweep-state.json` after creating
141
+ the issue.
142
+
143
+ To enable this, print a note reminding the user that after each rockout
144
+ iteration completes, they can manually record the inspection:
145
+
146
+ ```json
147
+ // Add to .claude/accuracy-sweep-state.json after each rockout:
148
+ { "module_name": { "last_inspected": "ISO-DATE", "issue": ISSUE_NUMBER } }
149
+ ```
150
+
151
+ ---
152
+
153
+ ## General Rules
154
+
155
+ - Do NOT modify any source files. This command is read-only analysis.
156
+ - Do NOT create GitHub issues. This command only generates the ralph-loop command.
157
+ - Keep the output concise -- the table and command are the deliverables.
158
+ - If $ARGUMENTS is empty, use defaults: top 5, no category filter, no exclusions.
@@ -0,0 +1,494 @@
1
+ # Performance Sweep: Parallel Triage and Fix Workflow
2
+
3
+ Audit xrspatial modules for performance bottlenecks, OOM risk under 30TB dask
4
+ workloads, and backend-specific anti-patterns. Dispatches parallel subagents
5
+ for fast triage, then generates a ralph-loop to benchmark and fix HIGH-severity
6
+ issues.
7
+
8
+ Optional arguments: $ARGUMENTS
9
+ (e.g. `--top 5`, `--exclude slope,aspect`, `--only-io`, `--reset-state`)
10
+
11
+ ---
12
+
13
+ ## Step 0 -- Determine mode and parse arguments
14
+
15
+ Parse $ARGUMENTS for these flags (multiple may combine):
16
+
17
+ | Flag | Effect |
18
+ |------|--------|
19
+ | `--top N` | Limit Phase 1 to the top N scored modules (default: all) |
20
+ | `--exclude mod1,mod2` | Remove named modules from scope |
21
+ | `--only-terrain` | Restrict to: slope, aspect, curvature, terrain, terrain_metrics, hillshade, sky_view_factor |
22
+ | `--only-focal` | Restrict to: focal, convolution, morphology, bilateral, edge_detection, glcm |
23
+ | `--only-hydro` | Restrict to: flood, cost_distance, geodesic, surface_distance, viewshed, erosion, diffusion |
24
+ | `--only-io` | Restrict to: geotiff, reproject, rasterize, polygonize |
25
+ | `--reset-state` | Delete `.claude/performance-sweep-state.json` and treat all modules as never-inspected |
26
+ | `--skip-phase1` | Skip triage; reuse last state file; go straight to ralph-loop generation for unresolved HIGH items |
27
+ | `--report-only` | Run Phase 1 triage but do not generate a ralph-loop command |
28
+ | `--size small` | Phase 2 benchmarks use 128x128 arrays |
29
+ | `--size large` | Phase 2 benchmarks use 2048x2048 arrays |
30
+ | `--high-only` | Only report HIGH severity findings in the triage output |
31
+
32
+ If `--skip-phase1` is set, jump to Step 6 (ralph-loop generation).
33
+ Otherwise proceed to Step 1.
34
+
35
+ ## Step 1 -- Discover modules in scope
36
+
37
+ Enumerate all candidate modules. For each, record its file path(s):
38
+
39
+ **Single-file modules:** Every `.py` file directly under `xrspatial/`, excluding
40
+ `__init__.py`, `_version.py`, `__main__.py`, `utils.py`, `accessor.py`,
41
+ `preview.py`, `dataset_support.py`, `diagnostics.py`, `analytics.py`.
42
+
43
+ **Subpackage modules:** The `geotiff/` and `reproject/` directories under
44
+ `xrspatial/`. Treat each subpackage as a single audit unit. List all `.py`
45
+ files within each (excluding `__init__.py`).
46
+
47
+ Apply `--only-*` and `--exclude` filters from Step 0 to narrow the list.
48
+
49
+ Store the filtered module list in memory (do NOT write intermediate files).
50
+
51
+ ## Step 2 -- Gather metadata and score each module
52
+
53
+ For every module in scope, collect:
54
+
55
+ | Field | How |
56
+ |-------|-----|
57
+ | **last_modified** | `git log -1 --format=%aI -- <path>` (for subpackages, use the most recent file) |
58
+ | **total_commits** | `git log --oneline -- <path> \| wc -l` |
59
+ | **loc** | `wc -l < <path>` (for subpackages, sum all files) |
60
+ | **has_dask_backend** | grep the file(s) for `_run_dask`, `map_overlap`, `map_blocks` |
61
+ | **has_cuda_backend** | grep the file(s) for `@cuda.jit`, `import cupy` |
62
+ | **is_io_module** | module is geotiff or reproject |
63
+ | **has_existing_bench** | a file matching the module name exists in `benchmarks/benchmarks/` |
64
+
65
+ ### Load inspection state
66
+
67
+ Read `.claude/performance-sweep-state.json`. If it does not exist, treat every
68
+ module as never-inspected. If `--reset-state` was set, delete the file first.
69
+
70
+ State file schema:
71
+
72
+ ```json
73
+ {
74
+ "last_triage": "ISO-DATE",
75
+ "modules": {
76
+ "slope": {
77
+ "last_inspected": "ISO-DATE",
78
+ "oom_verdict": "SAFE",
79
+ "bottleneck": "compute-bound",
80
+ "high_count": 0,
81
+ "issue": null
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Compute scores
88
+
89
+ ```
90
+ days_since_inspected = (today - last_perf_inspected).days # 9999 if never
91
+ days_since_modified = (today - last_modified).days
92
+
93
+ score = (days_since_inspected * 3)
94
+ + (loc * 0.1)
95
+ + (total_commits * 0.5)
96
+ + (has_dask_backend * 200)
97
+ + (has_cuda_backend * 150)
98
+ + (is_io_module * 300)
99
+ - (days_since_modified * 0.2)
100
+ - (has_existing_bench * 100)
101
+ ```
102
+
103
+ Sort modules by score descending. If `--top N` is set, keep only the top N.
104
+
105
+ ## Step 3 -- Dispatch parallel subagents for static triage
106
+
107
+ For each module in the scored list, dispatch a subagent using the Agent tool.
108
+ Launch ALL subagents in a single message (parallel dispatch). Each subagent
109
+ receives the prompt below, with `MODULE_NAME` and `MODULE_FILES` substituted.
110
+
111
+ **Subagent prompt template:**
112
+
113
+ ~~~
114
+ You are auditing the xrspatial module "MODULE_NAME" for performance issues.
115
+
116
+ Read these files: MODULE_FILES
117
+
118
+ Perform ALL of the following analyses and return your findings as a single
119
+ JSON object. Do NOT modify any files. This is read-only analysis.
120
+
121
+ ### 1. Dask Path Analysis
122
+
123
+ Trace every dask code path (_run_dask, _run_dask_cupy, or any function that
124
+ receives dask-backed DataArrays). Flag these patterns with severity:
125
+
126
+ - HIGH: `.values` on a dask-backed DataArray or CuPy array (premature materialization)
127
+ - HIGH: `.compute()` inside a loop (materializes full graph each iteration)
128
+ - HIGH: `np.array()` or `np.asarray()` wrapping a dask or CuPy array
129
+ - MEDIUM: `da.stack()` without a following `.rechunk()`
130
+ - MEDIUM: `map_overlap` with depth >= chunk_size / 4
131
+ - MEDIUM: Missing `boundary` argument in `map_overlap`
132
+ - MEDIUM: Same function called twice on same input without caching
133
+ - MEDIUM: Python `for` loop iterating over dask chunks (serializes the graph)
134
+
135
+ If the module has NO dask code path, note "no dask backend" and skip.
136
+
137
+ ### 2. 30TB / 16GB OOM Verdict
138
+
139
+ For each dask code path found in section 1:
140
+
141
+ **Part A — Static trace:** Follow the code end-to-end. Answer: does peak
142
+ memory scale with total array size, or with chunk size? If any operation
143
+ forces full materialization, the verdict is WILL OOM.
144
+
145
+ **Part B — Task graph simulation:** Write and run a Python script (in /tmp/
146
+ with a unique name including "MODULE_NAME") that:
147
+
148
+ ```python
149
+ import dask.array as da
150
+ import xarray as xr
151
+ import json, sys
152
+
153
+ arr = da.zeros((2560, 2560), chunks=(256, 256), dtype='float64')
154
+ raster = xr.DataArray(arr, dims=['y', 'x'])
155
+
156
+ # Add coords if the function needs them (geodesic, slope with CRS, etc.)
157
+ # raster = raster.assign_coords(x=np.linspace(-180, 180, 2560),
158
+ # y=np.linspace(-90, 90, 2560))
159
+
160
+ try:
161
+ result = MODULE_FUNCTION(raster, **DEFAULT_ARGS)
162
+ graph = result.__dask_graph__()
163
+ task_count = len(graph)
164
+ tasks_per_chunk = task_count / 100.0
165
+
166
+ # Check for fan-out: any task key that depends on more than 4 other tasks
167
+ deps = dict(graph)
168
+ max_fan_in = 0
169
+ for key, val in deps.items():
170
+ if hasattr(val, '__dask_graph__'):
171
+ sub = val.__dask_graph__()
172
+ max_fan_in = max(max_fan_in, len(sub))
173
+
174
+ print(json.dumps({
175
+ "success": True,
176
+ "task_count": task_count,
177
+ "tasks_per_chunk": round(tasks_per_chunk, 2),
178
+ "max_fan_in": max_fan_in,
179
+ "extrapolation_30tb": "~{} tasks at 57M chunks".format(
180
+ int(tasks_per_chunk * 57_000_000))
181
+ }))
182
+ except Exception as e:
183
+ print(json.dumps({"success": False, "error": str(e)}))
184
+ ```
185
+
186
+ Adapt the function call and imports for the specific module. Run the script
187
+ and capture its JSON output. If it errors, record the error and rely on
188
+ Part A alone.
189
+
190
+ **Verdict:** One of:
191
+ - `SAFE` — memory bounded by chunk size, graph scales linearly
192
+ - `RISKY` — bounded but tight (e.g. large overlap depth, 3D intermediates)
193
+ - `WILL OOM` — forces full materialization or unbounded memory growth
194
+
195
+ ### 3. GPU Transfer Analysis
196
+
197
+ Scan for CuPy/CUDA code paths. Flag:
198
+
199
+ - HIGH: `.data.get()` followed by CuPy operations (GPU-CPU-GPU round-trip)
200
+ - HIGH: `cupy.asarray()` inside a loop (repeated CPU-GPU transfers)
201
+ - MEDIUM: Mixing NumPy and CuPy ops in same function without clear reason
202
+ - MEDIUM: Register pressure — count float64 local variables in `@cuda.jit`
203
+ kernels; flag if >20
204
+ - MEDIUM: Thread blocks >16x16 on kernels with >20 float64 locals
205
+
206
+ If the module has NO GPU code path, note "no GPU backend" and skip.
207
+
208
+ ### 4. Memory Allocation Patterns
209
+
210
+ - MEDIUM: Unnecessary `.copy()` on arrays never mutated downstream
211
+ - MEDIUM: Large temporary arrays that could be fused into the kernel
212
+ - LOW: `np.zeros_like()` + fill loop where `np.empty()` would suffice
213
+
214
+ ### 5. Numba Anti-Patterns
215
+
216
+ - MEDIUM: Missing `@ngjit` on nested for-loops over `.data` arrays
217
+ - MEDIUM: `@jit` without `nopython=True` (object-mode fallback risk)
218
+ - LOW: Type instability — initializing with int then assigning float
219
+ - LOW: Column-major iteration on row-major arrays (inner loop should be last axis)
220
+
221
+ ### 6. Bottleneck Classification
222
+
223
+ Based on your analysis, classify the module as ONE of:
224
+ - `IO-bound` — dominated by disk reads/writes or serialization
225
+ - `memory-bound` — peak allocation is the limiting factor
226
+ - `compute-bound` — CPU/GPU time dominates, memory is fine
227
+ - `graph-bound` — dask task graph overhead dominates
228
+
229
+ ### Output Format
230
+
231
+ Return EXACTLY this JSON structure (no extra text before or after):
232
+
233
+ ```json
234
+ {
235
+ "module": "MODULE_NAME",
236
+ "files_read": ["list of files you read"],
237
+ "findings": [
238
+ {
239
+ "severity": "HIGH|MEDIUM|LOW",
240
+ "category": "dask_materialization|dask_chunking|gpu_transfer|register_pressure|memory_allocation|numba_antipattern",
241
+ "file": "filename.py",
242
+ "line": 123,
243
+ "description": "what the issue is",
244
+ "fix": "how to fix it",
245
+ "backends_affected": ["dask+numpy", "dask+cupy", "cupy", "numpy"]
246
+ }
247
+ ],
248
+ "oom_verdict": {
249
+ "dask_numpy": "SAFE|RISKY|WILL OOM",
250
+ "dask_cupy": "SAFE|RISKY|WILL OOM",
251
+ "reasoning": "one-sentence explanation",
252
+ "estimated_peak_per_chunk_mb": 0.5,
253
+ "task_count": 3721,
254
+ "tasks_per_chunk": 37.21,
255
+ "graph_simulation_ran": true
256
+ },
257
+ "bottleneck": "compute-bound|memory-bound|IO-bound|graph-bound",
258
+ "bottleneck_reasoning": "one-sentence explanation"
259
+ }
260
+ ```
261
+
262
+ IMPORTANT: Only flag patterns that are ACTUALLY present in the code. Do not
263
+ report hypothetical issues. False positives are worse than missed issues.
264
+ If a pattern like `.values` is used on a known-numpy-only code path, do not
265
+ flag it.
266
+ ~~~
267
+
268
+ Wait for all subagents to return before proceeding to Step 4.
269
+
270
+ ## Step 4 -- Merge results and print the triage report
271
+
272
+ Parse the JSON returned by each subagent. If a subagent returned malformed
273
+ output, record the module as "audit failed" with a note.
274
+
275
+ ### 4a. Print the Module Risk Ranking Table
276
+
277
+ Sort modules by score descending. Print:
278
+
279
+ ```
280
+ ## Performance Sweep — Static Triage Report
281
+
282
+ ### Module Risk Ranking
283
+ | Rank | Module | Score | OOM Verdict | Bottleneck | HIGH | MED | LOW |
284
+ |------|-----------------|--------|-----------------|---------------|------|-----|-----|
285
+ | 1 | geotiff | 31200 | WILL OOM (d+np) | IO-bound | 3 | 1 | 0 |
286
+ | 2 | viewshed | 30050 | RISKY (d+np) | memory-bound | 2 | 2 | 1 |
287
+ | ... | ... | ... | ... | ... | ... | ... | ... |
288
+ ```
289
+
290
+ If `--high-only` is set, only count HIGH findings and omit modules with zero HIGH.
291
+
292
+ ### 4b. Print the 30TB / 16GB Verdict Summary
293
+
294
+ Group modules by OOM verdict:
295
+
296
+ ```
297
+ ### 30TB on Disk / 16GB RAM — Out-of-Memory Analysis
298
+
299
+ #### WILL OOM (fix required)
300
+ - **module_name**: reasoning from subagent
301
+
302
+ #### RISKY (bounded but tight)
303
+ - **module_name**: reasoning from subagent
304
+
305
+ #### SAFE (memory bounded by chunk size)
306
+ - module_name, module_name, module_name, ...
307
+ ```
308
+
309
+ ### 4c. Print Detailed Findings
310
+
311
+ For each module that has findings, print a severity-grouped table:
312
+
313
+ ```
314
+ ### module_name (bottleneck: compute-bound, OOM: SAFE)
315
+
316
+ | # | Severity | File:Line | Category | Description | Fix |
317
+ |---|----------|----------------|-------------------------|------------------------------|-------------------------------|
318
+ | 1 | HIGH | slope.py:142 | dask_materialization | .values on dask input | Use .data or stay lazy |
319
+ | 2 | MEDIUM | slope.py:88 | dask_chunking | map_overlap depth too large | Reduce depth or warn users |
320
+ ```
321
+
322
+ ### 4d. Print Actionable Rockout Commands
323
+
324
+ For each HIGH-severity finding, print a ready-to-paste `/rockout` command:
325
+
326
+ ```
327
+ ### Ready-to-Run Fixes (HIGH severity only)
328
+
329
+ 1. **geotiff** — eager .values materialization (WILL OOM)
330
+ /rockout "Fix eager .values materialization in geotiff reader.
331
+ The dask read path at reader.py:87 calls .values which forces
332
+ the full array into memory. For 30TB inputs this will OOM on
333
+ a 16GB machine. Must stay lazy through the entire read path."
334
+
335
+ 2. **cost_distance** — iterative solver unbounded memory (WILL OOM)
336
+ /rockout "Fix cost_distance iterative solver to work within
337
+ bounded memory. Currently materializes the full distance matrix
338
+ each iteration. Must use chunked iteration for 30TB dask inputs."
339
+ ```
340
+
341
+ Construct each `/rockout` command from the finding's description and fix fields.
342
+ Include the OOM verdict and bottleneck classification in the prompt text so
343
+ rockout has full context.
344
+
345
+ ## Step 5 -- Update state file
346
+
347
+ Write `.claude/performance-sweep-state.json` with the triage results:
348
+
349
+ ```json
350
+ {
351
+ "last_triage": "<current ISO datetime>",
352
+ "modules": {
353
+ "<module_name>": {
354
+ "last_inspected": "<current ISO datetime>",
355
+ "oom_verdict": "<SAFE|RISKY|WILL OOM>",
356
+ "bottleneck": "<IO-bound|memory-bound|compute-bound|graph-bound>",
357
+ "high_count": "<number of HIGH findings>",
358
+ "issue": null
359
+ }
360
+ }
361
+ }
362
+ ```
363
+
364
+ If the file already exists, merge — update entries for modules that were
365
+ just audited, keep entries for modules not in this run's scope.
366
+
367
+ If `--report-only` is set, stop here. Do not proceed to Step 6.
368
+
369
+ ## Step 6 -- Generate the ralph-loop command
370
+
371
+ Collect all modules from Step 4 (or from the state file if `--skip-phase1`)
372
+ that have at least one HIGH-severity finding and no `issue` recorded in the
373
+ state file (i.e. not yet fixed).
374
+
375
+ Sort them by: WILL OOM first, then RISKY, then by HIGH count descending.
376
+
377
+ Determine the benchmark array size from arguments:
378
+ - `--size small` → 128x128
379
+ - `--size large` → 2048x2048
380
+ - default → 512x512
381
+
382
+ ### 6a. Print the ranked target list
383
+
384
+ ```
385
+ ### Phase 2 Targets (HIGH severity, unfixed)
386
+ | # | Module | HIGH Count | OOM Verdict | Bottleneck |
387
+ |---|---------------|------------|-------------|--------------|
388
+ | 1 | geotiff | 3 | WILL OOM | IO-bound |
389
+ | 2 | cost_distance | 1 | WILL OOM | memory-bound |
390
+ | 3 | viewshed | 2 | RISKY | memory-bound |
391
+ ```
392
+
393
+ If no modules qualify, print:
394
+ "No HIGH-severity findings to fix. Run `/sweep-performance` without
395
+ `--skip-phase1` to refresh the triage."
396
+ Then stop.
397
+
398
+ ### 6b. Print the ralph-loop command
399
+
400
+ Using the target list, generate and print:
401
+
402
+ ````
403
+ /ralph-loop "Performance sweep Phase 2: benchmark and fix HIGH-severity findings.
404
+
405
+ **Target modules in priority order:**
406
+ 1. <module> (<N> HIGH findings, <OOM verdict>) -- <one-line summary of worst finding>
407
+ 2. <module> ...
408
+ ...
409
+
410
+ **For each module, in order:**
411
+
412
+ 1. Write a benchmark script at /tmp/perf_sweep_bench_<module>.py that:
413
+ - Imports the module's public functions
414
+ - Creates a test array (<SIZE>x<SIZE>, float64)
415
+ - For EACH available backend (numpy, dask+numpy; cupy and dask+cupy only if available):
416
+ a. Wrap the array in the appropriate DataArray type
417
+ b. Measure wall time: timeit.repeat(number=1, repeat=3), take median
418
+ c. Measure Python memory: tracemalloc.start() / tracemalloc.get_traced_memory()[1] for peak
419
+ d. Measure process memory: resource.getrusage(RUSAGE_SELF).ru_maxrss before and after
420
+ e. For CuPy backends: cupy.get_default_memory_pool().used_bytes() before and after
421
+ - Print results as JSON to stdout
422
+
423
+ 2. Run the benchmark script and capture results.
424
+
425
+ 3. Confirm the HIGH finding from Phase 1:
426
+ - If the dask backend uses significantly more memory than expected for
427
+ the chunk size, or wall time shows a materialization stall: CONFIRMED.
428
+ - If the benchmark shows no anomaly: downgrade to MEDIUM in state file,
429
+ print 'False positive — skipping' and move to the next module.
430
+
431
+ 4. If confirmed: run /rockout to fix the issue end-to-end (issue, worktree,
432
+ implementation, tests, docs). Include the benchmark numbers in the
433
+ issue body for context.
434
+
435
+ 5. After rockout completes: rerun the same benchmark script. Print a
436
+ before/after comparison:
437
+ | Backend | Metric | Before | After | Ratio | Verdict |
438
+ |------------|-------------|--------|--------|-------|------------|
439
+ | numpy | wall_ms | 45.2 | 12.1 | 0.27x | IMPROVED |
440
+ | dask+numpy | peak_rss_mb | 892 | 34 | 0.04x | IMPROVED |
441
+ Thresholds: IMPROVED < 0.8x, REGRESSION > 1.2x, else UNCHANGED.
442
+
443
+ 6. Update .claude/performance-sweep-state.json with the issue number.
444
+
445
+ 7. Output <promise>ITERATION DONE</promise>
446
+
447
+ If all targets have been addressed or confirmed as false positives:
448
+ <promise>ALL PERFORMANCE ISSUES FIXED</promise>." --max-iterations <N+2> --completion-promise "ALL PERFORMANCE ISSUES FIXED"
449
+ ````
450
+
451
+ Set `--max-iterations` to the number of target modules plus 2 (buffer for
452
+ retries).
453
+
454
+ ### 6c. Print reminder text
455
+
456
+ ```
457
+ Phase 1 triage complete. To proceed with fixes:
458
+ Copy the ralph-loop command above and paste it.
459
+
460
+ Other options:
461
+ Fix one manually: copy any /rockout command from the report above
462
+ Rerun triage only: /sweep-performance --report-only
463
+ Skip Phase 1: /sweep-performance --skip-phase1 (reuses last triage)
464
+ Reset all tracking: /sweep-performance --reset-state
465
+ ```
466
+
467
+ ---
468
+
469
+ ## General Rules
470
+
471
+ - Phase 1 subagents do NOT modify any source, test, or benchmark files.
472
+ Read-only analysis only.
473
+ - Phase 2 ralph-loop modifies code only through `/rockout`.
474
+ - Temporary benchmark scripts and graph simulation scripts go in `/tmp/`
475
+ with unique names including the module name (e.g. `/tmp/perf_sweep_bench_slope.py`,
476
+ `/tmp/perf_sweep_graph_slope.py`). Clean them up after capturing results.
477
+ - Only flag patterns that are ACTUALLY present in the code. Do not report
478
+ hypothetical issues or patterns that "could" occur.
479
+ - Include the exact file path and line number for every finding so the user
480
+ can navigate directly to the issue.
481
+ - False positives are worse than missed issues. If you are not confident a
482
+ pattern is actually harmful in context (e.g. `.values` used intentionally
483
+ on a known-numpy array), do not flag it.
484
+ - The 30TB simulation constructs the dask task graph only; it NEVER calls
485
+ `.compute()`.
486
+ - State file (`.claude/performance-sweep-state.json`) is gitignored by
487
+ convention — do not add it to git.
488
+ - If $ARGUMENTS is empty, use defaults: audit all modules, benchmark at
489
+ 512x512, generate ralph-loop for HIGH items.
490
+ - For subpackage modules (geotiff, reproject), the subagent should read ALL
491
+ `.py` files in the subpackage directory, not just `__init__.py`.
492
+ - When generating `/rockout` commands, include the OOM verdict, bottleneck
493
+ classification, and affected backends in the prompt text so rockout has
494
+ full performance context.