Skip to content

feat: implement the graph API and core algorithms - #3

Open
nish2292 wants to merge 2 commits into
daft-engine:mainfrom
nish2292:feat/core-api-and-algorithms
Open

feat: implement the graph API and core algorithms#3
nish2292 wants to merge 2 commits into
daft-engine:mainfrom
nish2292:feat/core-api-and-algorithms

Conversation

@nish2292

Copy link
Copy Markdown

Implements the Discussion #1 API and the core algorithms requested in #2. The package was an empty template, so this provides the initial implementation.

API (the two-class model from #1)

  • Abstract Graph base with DirectedGraph and UndirectedGraph subclasses, so the type carries direction and each algorithm declares the flavor it needs.
  • Built from an edge list with optional vertices and configurable columns.
  • Graph methods: degrees, triplets, filter_vertices / filter_edges, drop_isolated_vertices, degree_by_type, and directed/undirected conversion (reverse, as_undirected, as_directed).

Algorithms (the #2 set)

  • Connected components: connected_components (weak), strongly_connected_components
  • Centrality: pagerank (+ personalized), parallel_personalized_pagerank
  • Traversal: bfs, bfs_paths, shortest_paths, all_shortest_paths, all_paths
  • Community: label_propagation, power_iteration_clustering
  • Motif: find (GraphFrames-style DSL)
  • Message passing: aggregate_messages, pregel
  • Also: triangle_count, k_core, cycle detection (has_cycle, vertices_on_cycles), maximal_independent_set, random_walks, svd_plus_plus, hyper_anf, id reindexing, and edge utilities

Implementation

  • Pure Python on top of Daft. Iterative algorithms run on a materialize-between-rounds engine, the analog of GraphFrames checkpointing.
  • Connected components ports the large-star / small-star contraction algorithm (Kiveris et al. 2014) from Daft's own minhash-dedupe example, then hardens it with types and tests.
  • The core depends on daft alone. numpy and scipy are an optional local extra used only for the single-node solves (connected_components strategy="local", svd_plus_plus).
  • Correctness is validated against igraph and networkx (test-only). 331 tests pass, ruff and mypy are clean, on Python 3.10 through 3.13.

Opening as a draft for maintainer feedback on scope and shape. Closes #2.

Implements the Discussion daft-engine#1 two class API and the core algorithm set from
issue daft-engine#2, ported from the validated internal prototype and shaped to this
repo's conventions (Apache-2.0, hatchling, ruff/pydocstyle, Python 3.10+).

API:
- abstract Graph plus DirectedGraph and UndirectedGraph; the type carries
  direction, so algorithms declare the flavor they need
- construction from edges with optional vertices, configurable columns,
  opt in validation; transforms preserve the concrete flavor

Algorithms:
- connected components (regular + strong), pagerank (+ personalized) and
  parallel_personalized_pagerank, bfs/bfs_paths/shortest_paths/
  all_shortest_paths/all_paths, label_propagation, power_iteration_clustering,
  find (motif DSL), aggregate_messages/pregel, triangle_count, k_core,
  cycle detection, maximal_independent_set, random_walks, svd_plus_plus,
  hyper_anf, plus reindex/restore_ids and edge utilities

Packaging:
- core depends on daft only; numpy and scipy behind the optional local extra
- Self imported under TYPE_CHECKING for the 3.10 target
- igraph and networkx are test only oracles

Verified: 331 tests pass, ruff and mypy clean.
Every distributed iterative algorithm stalled on Daft's Ray/Flotilla runner:
partition counts compounded round over round until each shuffle needed a
partition-count-squared number of pieces, exhausting the cluster. Daft resolves
a shuffle's output partition count to the repartition spec's count or else the
input's (unwrap_or(input_num_partitions)) and never lowers it, and union_all
sums its inputs' counts, so a step that symmetrizes/unions doubles the count
every round. Invisible on the native runner (no partitions), fatal on Ray.

Fix: bound the partition count wherever iterative state is carried, via a cheap
plan rewrite (into_partitions on the already-optimized plan, no extra execution),
gated to the Ray runner (no-op on native):

- iterate.py: bound_partitions() (lazy cap) and collect_bounded() (materialize
  then present at a bounded count). collect_bounded returns the coalesced frame
  LAZILY - re-collecting after into_partitions makes num_partitions() report 0,
  which silently disables every downstream cap.
- message_passing.py: cap triplets, the aggregate_messages union, and each
  pregel step (fixes label_propagation, k_core, shortest_paths, pagerank, ...).
- connected_components: cap the star step passes, label propagation, adjacency.
- Custom-loop algorithms that bypass the shared machinery: strongly_connected_
  components (peeling loop + active_v/active_e + union fold), hyper_anf (per-hop
  HLL), maximal_independent_set (per-round status), shortest_paths (landmark
  fold), and the shared BFS frontier in _traversal (visited/levels growth).
- Static once-collected inputs (adjacency/edges/degrees) joined every round.

Also adds tests/test_bfs_scaling.py, examples/, and benchmarks/ from the
driver-memory BFS rewrite.
@nish2292
nish2292 marked this pull request as ready for review August 23, 2026 05:51
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.

Implement basic API and core algorithsm

1 participant