Raised by the owner reviewing the stack: "do we have both eager hop.py and polars/hop_eager.py? that seems problematic." Investigated; the instinct is right, though the naming hides what is actually going on.
What is actually there
| file |
lines |
role |
graphistry/compute/hop.py |
1165 |
pandas/cuDF implementation |
graphistry/compute/gfql/lazy/engine/polars/hop_eager.py |
502 |
the polars implementation |
graphistry/compute/gfql/lazy/engine/polars/hop.py |
55 |
entry point only — eagerness normalization (#1740) + the #1658 index hook, then delegates |
So it is not eager-vs-eager duplication. polars/hop.py is a dispatcher, and hop_eager.py is the polars kernel — which per its own docstring now contains both the lazy collect-once single-hop plan (the GPU path) and the eager BFS loop, after a separate lazy twin was consolidated into it.
Problem 1 (cheap, do now): the filename is stale and misleading
hop_eager.py has not been eager-only since that consolidation. Anyone auditing the tree reads the pair hop.py + hop_eager.py as redundant implementations — exactly what happened here. Rename to hop_impl.py or hop_polars.py (the exported symbol is already hop_polars).
Problem 2 (the real one): one semantic contract, two implementations, nothing forcing agreement
Both must implement the same 23 parameters identically: min_hops, max_hops, output_min_hops, output_max_hops, label_node_hops, label_edge_hops, label_seeds, to_fixed_point, direction, edge_match, source_node_match, destination_node_match, source_node_query, destination_node_query, edge_query, return_as_wave_front, include_zero_hop_seed, target_wave_front, plus the hop/seed basics.
This campaign is the empirical case that it drifts. Both hop umbrellas had to patch both files — #1892 (filter-domain invariant, 2a2911221) and #1888 (endpoint closure, 5ddbd666d) — and #1888 needed three kernels, because the polars chain fast path is a third home for the same rule. The pattern recurs beyond hop: #1913's stale-index fix hit two chain call sites, #1911's edge-identity fix hit three. One rule, N implementations, and the probes keep finding the site somebody missed.
Problem 3: the mitigation is thinner than it looks
graphistry/tests/compute/gfql/test_hop_semantics_pins.py is cross-engine parameterized — the right design — but it is 13 test functions against a 23-parameter surface, and the polars arm is polars_only-marked, so it silently vanishes on a pandas-only install (the #1898 skipped-lane class). Single flags are the well-covered part; combinations are the gap (bounds × to_fixed_point, output-window vs traversal-window, filters at hop k>1 — note #1892's bug was precisely a filter-DOMAIN invariant — direction × self-loops/parallel edges, target_wave_front × filters).
Proposed sequencing
- Now: rename the stale file (mechanical, zero risk).
- Now: widen the cross-engine pin matrix toward the parameter surface, prioritizing combinations. This is what actually catches drift, and it is cheap and safe. A round-011 re-probe of exactly these combinations is running and will feed it.
- Tracked follow-up, NOT this release: unifying the implementations. They differ for legitimate reasons — polars wants a single lazy collect-once plan so the GPU path reads/transfers the edge table once; pandas/cuDF want an eager BFS loop whose early-break and revisit bookkeeping need per-hop materialization. A naive merge either loses the GPU plan or bolts a lazy abstraction onto the pandas path. This is a project with its own design review, and it should not start while twelve PRs are stacked on top of
hop.py.
Deliberately not attempting (3) mid-release.
Raised by the owner reviewing the stack: "do we have both eager hop.py and polars/hop_eager.py? that seems problematic." Investigated; the instinct is right, though the naming hides what is actually going on.
What is actually there
graphistry/compute/hop.pygraphistry/compute/gfql/lazy/engine/polars/hop_eager.pygraphistry/compute/gfql/lazy/engine/polars/hop.pySo it is not eager-vs-eager duplication.
polars/hop.pyis a dispatcher, andhop_eager.pyis the polars kernel — which per its own docstring now contains both the lazy collect-once single-hop plan (the GPU path) and the eager BFS loop, after a separate lazy twin was consolidated into it.Problem 1 (cheap, do now): the filename is stale and misleading
hop_eager.pyhas not been eager-only since that consolidation. Anyone auditing the tree reads the pairhop.py+hop_eager.pyas redundant implementations — exactly what happened here. Rename tohop_impl.pyorhop_polars.py(the exported symbol is alreadyhop_polars).Problem 2 (the real one): one semantic contract, two implementations, nothing forcing agreement
Both must implement the same 23 parameters identically:
min_hops,max_hops,output_min_hops,output_max_hops,label_node_hops,label_edge_hops,label_seeds,to_fixed_point,direction,edge_match,source_node_match,destination_node_match,source_node_query,destination_node_query,edge_query,return_as_wave_front,include_zero_hop_seed,target_wave_front, plus the hop/seed basics.This campaign is the empirical case that it drifts. Both hop umbrellas had to patch both files — #1892 (filter-domain invariant,
2a2911221) and #1888 (endpoint closure,5ddbd666d) — and #1888 needed three kernels, because the polars chain fast path is a third home for the same rule. The pattern recurs beyond hop: #1913's stale-index fix hit two chain call sites, #1911's edge-identity fix hit three. One rule, N implementations, and the probes keep finding the site somebody missed.Problem 3: the mitigation is thinner than it looks
graphistry/tests/compute/gfql/test_hop_semantics_pins.pyis cross-engine parameterized — the right design — but it is 13 test functions against a 23-parameter surface, and the polars arm ispolars_only-marked, so it silently vanishes on a pandas-only install (the #1898 skipped-lane class). Single flags are the well-covered part; combinations are the gap (bounds ×to_fixed_point, output-window vs traversal-window, filters at hop k>1 — note #1892's bug was precisely a filter-DOMAIN invariant — direction × self-loops/parallel edges,target_wave_front× filters).Proposed sequencing
hop.py.Deliberately not attempting (3) mid-release.