xarray-spatial 0.9.4__tar.gz → 0.9.5__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.
- xarray_spatial-0.9.5/.claude/commands/sweep-performance.md +494 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/CHANGELOG.md +15 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/PKG-INFO +1 -1
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xarray_spatial.egg-info/PKG-INFO +1 -1
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xarray_spatial.egg-info/SOURCES.txt +1 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/_version.py +3 -3
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/aspect.py +12 -4
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/balanced_allocation.py +34 -6
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/cost_distance.py +33 -6
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/dasymetric.py +16 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/diffusion.py +90 -28
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/erosion.py +22 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/normalize.py +22 -33
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/__init__.py +13 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/surface_distance.py +14 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_balanced_allocation.py +32 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_zonal.py +32 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/zonal.py +27 -10
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/backend-parity.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/bench.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/dask-notebook.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/efficiency-audit.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/new-issues.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/release-major.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/release-minor.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/release-patch.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/review-pr.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/rockout.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/user-guide-notebook.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.claude/commands/validate.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.gitattributes +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.github/ISSUE_TEMPLATE/feature-proposal.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.github/labeler.yml +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.github/pull_request_template.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.github/workflows/benchmarks.yml +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.github/workflows/labeler.yml +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.github/workflows/pypi-publish.yml +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.github/workflows/test.yml +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.gitignore +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/.readthedocs.yml +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/CODE_OF_CONDUCT.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/CONTRIBUTING.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/Citation-styles.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/LICENSE.txt +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/MANIFEST.in +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/README.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/RELEASE.md +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/codecov.yml +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/pyproject.toml +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/setup.cfg +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/setup.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xarray_spatial.egg-info/dependency_links.txt +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xarray_spatial.egg-info/entry_points.txt +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xarray_spatial.egg-info/not-zip-safe +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xarray_spatial.egg-info/requires.txt +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xarray_spatial.egg-info/top_level.txt +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/__main__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/accessor.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/analytics.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/bilateral.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/bump.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/classify.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/contour.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/convolution.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/corridor.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/curvature.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/dataset_support.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/datasets/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/datasets/sentinel-2/blue_band.nc +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/datasets/sentinel-2/green_band.nc +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/datasets/sentinel-2/nir_band.nc +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/datasets/sentinel-2/red_band.nc +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/datasets/sentinel-2/swir1_band.nc +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/datasets/sentinel-2/swir2_band.nc +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/diagnostics.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/edge_detection.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/emerging_hotspots.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/experimental/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/experimental/min_observable_height.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/fire.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/flood.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/focal.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geodesic.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/_compression.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/_dtypes.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/_geotags.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/_gpu_decode.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/_header.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/_reader.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/_vrt.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/_writer.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/bench_vs_rioxarray.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/conftest.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_accessor_io.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_accuracy_1081.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_cog.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_compression.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_compression_level.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_dtype_read.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_edge_cases.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_features.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_geotags.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_header.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_jpeg.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_jpeg2000.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_lerc.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_lz4.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_reader.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_streaming_write.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_vrt_write.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/geotiff/tests/test_writer.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/glcm.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/gpu_rtx/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/gpu_rtx/cuda_utils.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/gpu_rtx/hillshade.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/gpu_rtx/mesh_utils.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/gpu_rtx/viewshed.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hillshade.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/_boundary_store.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/basin_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/fill_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_accumulation_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_accumulation_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_accumulation_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_direction_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_direction_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_direction_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_length_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_length_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_length_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_path_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_path_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/flow_path_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/hand_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/hand_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/hand_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/sink_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/snap_pour_point_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/stream_link_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/stream_link_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/stream_link_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/stream_order_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/stream_order_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/stream_order_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/conftest.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_basin_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_fill_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_accumulation_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_accumulation_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_accumulation_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_direction_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_direction_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_direction_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_length_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_length_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_length_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_path_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_path_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_flow_path_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_hand_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_hand_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_hand_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_sink_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_snap_pour_point_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_stream_link_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_stream_link_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_stream_link_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_stream_order_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_stream_order_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_stream_order_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_twi_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_watershed_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_watershed_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/tests/test_watershed_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/twi_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/watershed_d8.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/watershed_dinf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/hydro/watershed_mfd.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/interpolate/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/interpolate/_idw.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/interpolate/_kriging.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/interpolate/_spline.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/interpolate/_validation.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/mahalanobis.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/mcda/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/mcda/combine.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/mcda/constrain.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/mcda/sensitivity.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/mcda/standardize.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/mcda/weights.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/morphology.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/multispectral.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/pathfinding.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/perlin.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/polygonize.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/preview.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/proximity.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/rasterize.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_crs_utils.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_datum_grids.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_grid.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_interpolate.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_itrf.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_lite_crs.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_merge.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_projections.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_projections_cuda.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_transform.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/_vertical.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/at_bev_AT_GIS_GRID.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/au_icsm_A66_National_13_09_01.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/be_ign_bd72lb72_etrs89lb08.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/ch_swisstopo_CHENyx06_ETRS.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/de_adv_BETA2007.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/es_ign_SPED2ETV2.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/nl_nsgi_rdcorr2018.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/pt_dgt_D73_ETRS89_geo.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/uk_os_OSTN15_NTv2_OSGBtoETRS.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/us_nga_egm96_15.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/us_noaa_alaska.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/us_noaa_conus.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/us_noaa_hawaii.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/us_noaa_nadcon5_nad27_nad83_1986_conus.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/reproject/grids/us_noaa_prvi.tif +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/sky_view_factor.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/slope.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/terrain.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/terrain_metrics.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/__init__.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/bench_reproject_vs_rioxarray.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/conftest.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/general_checks.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_accessor.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_analytics.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_aspect.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_bilateral.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_bump.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_classify.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_contour.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_corridor.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_cost_distance.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_curvature.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_dask_cupy_gaps.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_dasymetric.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_dataset_support.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_datasets.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_diagnostics.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_diffusion.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_edge_detection.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_emerging_hotspots.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_erosion.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_fire.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_flood.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_focal.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_fused_overlap.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_geodesic_aspect.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_geodesic_slope.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_glcm.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_glcm_metric_order.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_hillshade.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_interpolation.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_lite_crs.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_mahalanobis.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_mcda.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_min_observable_height.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_morphology.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_morphology_derived.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_multi_overlap.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_multispectral.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_normalize.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_northness_eastness.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_pathfinding.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_perlin.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_polygonize.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_preview.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_proximity.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_rasterize.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_rasterize_accuracy.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_rechunk_no_shuffle.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_reproject.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_sky_view_factor.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_slope.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_surface_distance.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_terrain.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_terrain_metrics.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_utils.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_validation.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/tests/test_viewshed.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/utils.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/viewshed.py +0 -0
- {xarray_spatial-0.9.4 → xarray_spatial-0.9.5}/xrspatial/worley.py +0 -0
|
@@ -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.
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
-----------
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
### Version 0.9.5 - 2026-03-31
|
|
6
|
+
|
|
7
|
+
#### Bug fixes and improvements
|
|
8
|
+
- Add GPU memory guard to reproject dask+cupy path (#1131)
|
|
9
|
+
- Add memory guard to surface_distance geodesic dd_grid (#1129)
|
|
10
|
+
- Add memory guard to dasymetric validate_disaggregation (#1127)
|
|
11
|
+
- Replace boolean indexing with lazy reductions in normalize dask paths (#1125)
|
|
12
|
+
- Keep northness/eastness lazy on dask arrays (#1123)
|
|
13
|
+
- Add memory guard to erosion dask paths (#1121)
|
|
14
|
+
- Add memory guard to cost_distance iterative Dijkstra and da.block assembly (#1119)
|
|
15
|
+
- Fix diffusion dask OOM by passing scalar diffusivity directly to chunks (#1117)
|
|
16
|
+
- Fix balanced_allocation OOM with lazy source extraction and memory guard (#1115)
|
|
17
|
+
- Fix zonal dask memory guards and stats filtering (#1112)
|
|
18
|
+
|
|
19
|
+
|
|
5
20
|
### Version 0.9.4 - 2026-03-30
|
|
6
21
|
|
|
7
22
|
#### New features
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.9.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 9,
|
|
21
|
+
__version__ = version = '0.9.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 9, 5)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g45c714e12'
|
|
@@ -506,8 +506,12 @@ def northness(agg: xr.DataArray,
|
|
|
506
506
|
asp = aspect(agg, name='_aspect', method=method, z_unit=z_unit,
|
|
507
507
|
boundary=boundary)
|
|
508
508
|
asp_data = asp.data
|
|
509
|
-
|
|
510
|
-
|
|
509
|
+
if da is not None and isinstance(asp_data, da.Array):
|
|
510
|
+
trig = da.cos(da.deg2rad(asp_data))
|
|
511
|
+
out = da.where(asp_data == -1, np.nan, trig)
|
|
512
|
+
else:
|
|
513
|
+
trig = np.cos(np.deg2rad(asp_data))
|
|
514
|
+
out = np.where(asp_data == -1, np.nan, trig)
|
|
511
515
|
return xr.DataArray(out,
|
|
512
516
|
name=name,
|
|
513
517
|
coords=agg.coords,
|
|
@@ -582,8 +586,12 @@ def eastness(agg: xr.DataArray,
|
|
|
582
586
|
asp = aspect(agg, name='_aspect', method=method, z_unit=z_unit,
|
|
583
587
|
boundary=boundary)
|
|
584
588
|
asp_data = asp.data
|
|
585
|
-
|
|
586
|
-
|
|
589
|
+
if da is not None and isinstance(asp_data, da.Array):
|
|
590
|
+
trig = da.sin(da.deg2rad(asp_data))
|
|
591
|
+
out = da.where(asp_data == -1, np.nan, trig)
|
|
592
|
+
else:
|
|
593
|
+
trig = np.sin(np.deg2rad(asp_data))
|
|
594
|
+
out = np.where(asp_data == -1, np.nan, trig)
|
|
587
595
|
return xr.DataArray(out,
|
|
588
596
|
name=name,
|
|
589
597
|
coords=agg.coords,
|
|
@@ -52,14 +52,23 @@ def _as_numpy(arr):
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def _extract_sources(raster, target_values):
|
|
55
|
-
"""Return sorted array of unique source IDs from the raster.
|
|
56
|
-
|
|
55
|
+
"""Return sorted array of unique source IDs from the raster.
|
|
56
|
+
|
|
57
|
+
For dask arrays, uses ``da.unique`` (per-chunk reduction) so the full
|
|
58
|
+
raster is never pulled into RAM just to discover source IDs.
|
|
59
|
+
"""
|
|
57
60
|
if len(target_values) > 0:
|
|
58
61
|
ids = np.asarray(target_values, dtype=np.float64)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
return ids[np.isfinite(ids)]
|
|
63
|
+
|
|
64
|
+
data = raster.data
|
|
65
|
+
if da is not None and isinstance(data, da.Array):
|
|
66
|
+
uniq = da.unique(data).compute() # small result array
|
|
67
|
+
mask = np.isfinite(uniq) & (uniq != 0)
|
|
68
|
+
return np.sort(uniq[mask])
|
|
69
|
+
data_np = _to_numpy(data)
|
|
70
|
+
mask = np.isfinite(data_np) & (data_np != 0)
|
|
71
|
+
return np.unique(data_np[mask])
|
|
63
72
|
|
|
64
73
|
|
|
65
74
|
def _make_single_source_raster(raster, source_id):
|
|
@@ -297,6 +306,25 @@ def balanced_allocation(
|
|
|
297
306
|
return xr.DataArray(out.astype(np.float32), coords=raster.coords,
|
|
298
307
|
dims=raster.dims, attrs=raster.attrs)
|
|
299
308
|
|
|
309
|
+
# Memory guard: we hold N cost surfaces + friction simultaneously.
|
|
310
|
+
# Estimate total footprint before doing any expensive work.
|
|
311
|
+
array_bytes = np.prod(raster.shape) * 8 # float64
|
|
312
|
+
# N cost surfaces + friction + allocation + stacked intermediate
|
|
313
|
+
total_estimate = array_bytes * (n_sources + 3)
|
|
314
|
+
try:
|
|
315
|
+
from xrspatial.zonal import _available_memory_bytes
|
|
316
|
+
avail = _available_memory_bytes()
|
|
317
|
+
except ImportError:
|
|
318
|
+
avail = 2 * 1024**3
|
|
319
|
+
if total_estimate > 0.8 * avail:
|
|
320
|
+
raise MemoryError(
|
|
321
|
+
f"balanced_allocation with {n_sources} sources needs "
|
|
322
|
+
f"~{total_estimate / 1e9:.1f} GB ({n_sources} cost surfaces "
|
|
323
|
+
f"+ friction + intermediates) but only ~{avail / 1e9:.1f} GB "
|
|
324
|
+
f"available. Reduce the number of sources, downsample the "
|
|
325
|
+
f"raster, or increase available memory."
|
|
326
|
+
)
|
|
327
|
+
|
|
300
328
|
# Step 1: compute per-source cost-distance surfaces
|
|
301
329
|
cost_surfaces = [] # list of raw data arrays (numpy/cupy/dask)
|
|
302
330
|
for sid in source_ids:
|