Skip to content

feat(layout): replace external treemap layout dep with built-in implementation - #1059

Merged
lmeyerov merged 7 commits into
masterfrom
feat/vectorize-treemap-layout
Apr 5, 2026
Merged

lmeyerov merged 7 commits into
masterfrom
feat/vectorize-treemap-layout

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Apr 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Removes a third-party layout dependency and replaces it with a built-in pure-Python implementation in graphistry/layout/gib/_squarify.py
  • Vectorizes per-node coordinate transforms in partitioned_layout.py: replaced 4× per-row dict .map() lookups with a single DataFrame merge for normalize + global-positioning steps
  • Full API compatibility maintained — treemap() and group_in_a_box_layout() are unchanged from the caller's perspective
  • Adds 353 unit tests (graphistry/tests/layout/test_treemap.py) covering geometry invariants, strip-loop boundary conditions, float-accumulation stress, numpy array inputs, and end-to-end treemap() integration
  • Adds benchmarks/layout/treemap.py with CPU+GPU comparison; results in benchmarks/layout/RESULTS.md

Performance

Benchmarked with pre-resident data (pandas in memory for CPU, cuDF on-device for GPU), 200–500 repeated measurements, median reported.

Algorithm only (normalize + layout kernel, no DataFrame):

n_partitions reference built-in speedup
2 5.8µs 5.7µs 1.02×
10 28.6µs 28.6µs 1.00×
50 160.8µs 156.9µs 1.02×
100 364.8µs 363.3µs 1.00×
500 2.75ms 2.70ms 1.02×

Built-in impl matches reference within noise (1.00–1.02×).

E2E treemap() — CPU (pandas) vs GPU (cuDF), dgx-spark, Rapids 26.02:

total nodes cpu median gpu median gpu/cpu
2,000 252µs 410µs 1.63×
25,000 428µs 473µs 1.11×
50,000 600µs 540µs 0.90× ← crossover
250,000 2.13ms 1.79ms 0.84×
500,000 4.34ms 3.36ms 0.77×

CPU E2E baseline: ~250µs (down from ~800µs pre-vectorization). GPU crossover at ~50k total nodes.

Validation

  • 353 unit tests passing (CPU)
  • GPU test suite (gfql profile): 250/250 passed on dgx-spark (Rapids 26.02, cuDF)
  • test_gib_cudf and test_gib_cudf_with_partitions both green

Test plan

  • python3.10 -m pytest graphistry/tests/layout/test_treemap.py graphistry/tests/layout/test_gib.py — 353 passed
  • GPU gfql profile on dgx-spark — 250 passed
  • benchmarks/layout/treemap.py run locally (CPU) and on dgx-spark (CPU+GPU)

🤖 Generated with Claude Code

lmeyerov and others added 7 commits April 4, 2026 15:50
…rized implementation

- Add graphistry/layout/gib/_squarify.py: pure Python + numpy squarified
  treemap layout algorithm (normalize_sizes + squarify + internal helpers)
- Wire treemap.py to use the built-in implementation
- Remove third-party dep from setup.py and mypy.ini
- Add graphistry/tests/layout/test_treemap.py: 356 unit tests covering
  normalize_sizes, squarify geometry invariants, strip-loop boundary
  conditions, float-accumulation stress, numpy array inputs, and
  treemap() end-to-end integration; cross-validated against reference

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
np.sum() has ~3µs per-call overhead on small lists vs Python's sum() at ~100ns.
Switching to sum() restores performance parity with the removed external dep
(1.00-1.03x across n=2-500 partitions, verified on local + dgx-spark).
Also removes the now-unnecessary numpy import from _squarify.py.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Replace four per-row dict .map() lookups in partitioned_layout.py with
two vectorized DataFrame merges (normalize + global positioning), giving
O(nodes) work in a single join instead of 4× serial Python dict scans.

Also fix treemap.py to compute partition_ids once outside the
comprehension (was calling reset_index() inside the loop).

Add benchmarks/layout/treemap.py with pre-resident data design:
- DataFrame built once before timing loop (measures only treemap() call)
- CPU=pandas in memory, GPU=cuDF on device (when available)
- Algorithm-only sweep (ref vs built-in) + E2E sweep (cpu vs gpu)
- RESULTS.md written alongside script

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
E2E numbers with pre-resident data (pandas in-memory vs cuDF on-device):
- CPU baseline: ~250µs (3× faster than pre-vectorization ~800µs)
- GPU crossover: ~50k total nodes; at 500k nodes GPU is 1.30× faster
- Algorithm-only: 1.00–1.02× parity with reference across n=2–500

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…LTS.md

- partitioned_layout.py: replace pd.DataFrame(partition_offsets) with
  df_cons(engine)(partition_offsets) so the offsets table is cuDF-native
  on the GPU path, not a pandas frame merged into a cuDF DataFrame
- benchmarks/layout/.gitignore: exclude RESULTS.md (machine-specific output)
- git rm benchmarks/layout/RESULTS.md (not source, belongs in .gitignore)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Rename offsets columns to ox/oy/odx/ody before merging so columns that
only exist on the right side (dx, dy) don't silently drop their suffix.
Previously dx/dy came through unsuffixed (no collision) while x/y got
_local/_offset suffixes, causing KeyError: 'dx_offset' at runtime.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@lmeyerov
lmeyerov merged commit 91bd894 into master Apr 5, 2026
101 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant