Consolidate and unify cached spatial indexing (KDTree)#182
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors spatial indexing (KDTree) usage across swarms/mesh variables to reduce redundant KDTree construction in hot solver loops, while also adjusting swarm migration routing behavior.
Changes:
- Introduces cached
_get_kdtree()helpers forSwarm,PICSwarm, andMeshVariable, and updates call sites to reuse cached indices. - Adds a cached mesh-domain centroid KDTree (
Mesh._get_domain_kdtree()) and updatesSwarm.migrate()to use it. - Adjusts swarm migration/advection behavior (rank assignment indexing change;
advection()no longer forcesmax_its=1).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/underworld3/swarms/pic_swarm.py |
Adds cached swarm KDTree accessor and updates nearest-neighbour map construction to use it. |
src/underworld3/swarm.py |
Adds cached swarm KDTree accessor, switches several interpolations/routing paths to cached trees, and updates migration rank assignment logic. |
src/underworld3/discretisation/discretisation_mesh.py |
Reuses cached variable KDTrees for vertex/DOF mapping and adds a cached centroid KDTree for migration routing. |
src/underworld3/discretisation/discretisation_mesh_variables.py |
Adds cached KDTree for mesh-variable DOF coordinates keyed by mesh version and uses it for RBF interpolation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| swarm_rank_array = self.dm.getField("DMSwarm_rank") | ||
| swarm_coord_array = self.dm.getField("DMSwarmPIC_coor").reshape(-1, self.dim) | ||
|
|
||
| if not_my_points.shape[0] > 0: | ||
| dist, rank = mesh_domain_kdtree.query( | ||
| swarm_coord_array[not_my_points], k=it + 1, sqr_dists=False | ||
| ) | ||
| swarm_rank_array[not_my_points, 0] = rank.reshape(-1, it + 1)[:, it] | ||
| swarm_rank_array[not_my_points] = rank.reshape(-1, it + 1)[:, it] | ||
|
|
| tree = uw.kdtree.KDTree(self.X.coords) | ||
| # Use cached KDTree from coordinate variable | ||
| tree = self.X._get_kdtree() | ||
| dists, indices = tree.query(self.parent.X.coords, sqr_dists=False) |
|
|
||
| tree = uw.kdtree.KDTree(sub_var.coords) | ||
| tree = sub_var._get_kdtree() | ||
| dists, indices = tree.query(parent_var.coords, sqr_dists=False) |
|
Thanks @bknight1 — flagging things to address before this lands, since two related PRs merged just after you opened this one. What's now on
|
Memory diagnostics revealed significant RSS growth caused by repeated instantiation of KDTree objects (52 per step in AdvectionDiffusion). Each instantiation builds a new C++ index, creating memory pressure and fragmentation. Fix: - Implement _get_domain_kdtree() on Mesh for cached centroid routing - Cache KDTree in MeshVariable.rbf_interpolate(), keyed by mesh version - Use cached trees in Swarm.migrate() and evaluation hot-paths Reduces KDTree constructions from 52 to 0 per timestep in stable runs. Tests show stable RSS and PASSED test_0006_memory_leak.py.
Refactored Swarm, PICSwarm, and MeshVariable to use a unified _get_kdtree() pattern. This ensures consistent, cached access to spatial indices across the project, eliminating redundant builds. Key improvements: - Swarm coordinates automatically invalidate the cache on migration. - Mesh domain KDTree is now version-aware, rebuilding on deformation. - Mesh vertex and DOF mapping now utilize cached coordinate trees. - Fixed off-by-one indexing bug in PICSwarm._get_map.
Removed incorrect 2nd dimension index [ , 0] when assigning to 'swarm_rank_array'. This array is 1-dimensional (rank identifiers) across all supported PETSc versions in this context. This fix ensures particles that fall outside the local domain can be successfully routed to their nearest rank-centroid during migration.
…e._get_kdtree Addressing review comments for PR #182: - Rebased on latest development (resolved conflict in Swarm.advection) - Fixed IndexError in Swarm.migrate using a flattened one-line assignment - Exposed _get_kdtree in EnhancedMeshVariable for direct access - Added regression tests for KDTree caching in both MeshVariable and Swarm
9f91b11 to
e5c6f96
Compare
…e._get_kdtree Addressing review comments for PR #182: - Rebased on latest development (resolved conflict in Swarm.advection) - Fixed IndexError in Swarm.migrate using a flattened one-line assignment - Exposed _get_kdtree in EnhancedMeshVariable for direct access - Added regression tests for KDTree caching in both MeshVariable and Swarm
e5c6f96 to
1229937
Compare
|
Addressed review comments:
|
…e._get_kdtree Addressing review comments for PR #182: - Rebased on latest development (resolved conflict in Swarm.advection) - Fixed IndexError in Swarm.migrate using a flattened one-line assignment - Exposed _get_kdtree in EnhancedMeshVariable for direct access - Added regression tests for KDTree caching in both MeshVariable and Swarm
1229937 to
60a6e43
Compare
|
Further updates addressing Copilot and user suggestions:
|
…e._get_kdtree Addressing review comments for PR #182: - Rebased on latest development (resolved conflict in Swarm.advection) - Fixed IndexError in Swarm.migrate using a flattened one-line assignment - Exposed _get_kdtree in EnhancedMeshVariable for direct access - Added regression tests for KDTree caching in both MeshVariable and Swarm
60a6e43 to
d549f13
Compare
|
Consistency audit for _get_kdtree():
|
This PR refactors how spatial indices (KDTree) are managed across Underworld3 to eliminate redundant construction overhead in solver loops.
Key changes:
_get_kdtree()pattern acrossSwarm,PICSwarm, andMeshVariable. This ensures that a single C++ index is built per step and reused for all property updates.IndexErrorinSwarm.migrate()where a 1D rank array was incorrectly indexed as 2D during centroid-based routing.max_its=10inSwarm.advectionmigration to ensure particles are correctly routed across multiple MPI partitions in high-velocity flows.