Skip to content

Vectorize KDTree queries and edge insertion in connect_nodes_across_graphs #138

Description

@Raj-Taware

Description


Building on the benchmarks and flamegraph profiling introduced in #117, this issue targets the a bottleneck identified in connect_nodes_across_graphs :- the repeated per-node KDTree queries and edge insertions that could be replaced with vectorized, batched operations.
The current implementation in base.py has three compounding inefficiencies inside a Python loop over all N_target nodes:

  • Serial KDTree queries : _find_neighbour_node_idxs_in_source_mesh(xy_target) is called once per target node, passing a single 2D point. All three underlying methods (kdt_s.query() for nearest_neighbour/nearest_neighbours, kdt_s.query_ball_point() for within_radius) natively accept an array of points so N_target individual Python-level calls could become one.
  • Per-edge add_edge calls : each edge is inserted individually into the DiGraph. NetworkX supports add_edges_from() which takes a list, cutting down on per-call overhead.
  • Per-edge attribute computation : len (Euclidean distance) and vdiff (position difference) are computed as scalar numpy ops inside the inner loop, when they could be derived in a single vectorized pass over all edges once neighbour indices are known.

Also worth noting: containing_rectangle calls connect_nodes_across_graphs recursively with within_radius any changes here need to keep that path intact.

The flamegraph from #117 makes this visible. We can see connect_nodes_across_graphs dominates the call stack:

Flamegraph call stack

Proposed Enhancements


High-level direction:

  • Stack all target node positions into a single array, run one batched KDTree query, build the full edge list from the result
  • nearest_neighbour / nearest_neighbours are the most straightforward, batch query returns a regular (N_target, k) array
  • within_radius needs a bit more thought since query_ball_point on an array returns a ragged list (variable neighbours per node), so edge-list construction is less clean. Open to ideas on the best approach here.

Happy to discuss before going to implementation.


Context & Links



Task List


  • Batch KDTree queries across all target nodes for nearest_neighbour and nearest_neighbours
  • Handle within_radius & query_ball_point ragged output
  • Replace per-edge add_edge loop with a single add_edges_from call
  • Vectorize len and vdiff edge attribute computation
  • Verify containing_rectangle (recursive call) still works correctly
  • Run scaling benchmark from [Feat] runtime scaling and flamegraph benchmark CLIs #117 before/after to measure improvement

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions