Skip to content

Commit b8b6193

Browse files
lmeyerovclaude
andcommitted
refactor(gfql/polars): de-dup eager+lazy hop setup into shared helpers
The eager hop (hop_eager.py) and the lazy single-hop (hop.py) carried verbatim copies of the column-name + edge-id + node-dtype setup and the directed-pairs builder (hop.py even commented 'stay textually identical ... don't drift'). Extracted _hop_setup_columns() + _build_hop_pairs() — identical on pl.DataFrame (eager) and pl.LazyFrame (lazy), so both call sites share one implementation. Pure refactor, behavior-preserving. Validated on dgx: polars chain fuzz + hop suites 1133 pass, mypy clean. (The bigger pandas/polars node-output policy unification stays an issue — risky production-hop.py surgery + label-dtype contract from #1664/#1663 is a prerequisite.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c274e90 commit b8b6193

2 files changed

Lines changed: 39 additions & 60 deletions

File tree

graphistry/compute/gfql/lazy/engine/polars/hop.py

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -128,36 +128,12 @@ def hop_polars_lazy(
128128
if edge_match is not None:
129129
edges = filter_by_dict_polars(edges, edge_match)
130130

131-
FROM = generate_safe_column_name("__gfql_from__", edges, prefix="__gfql_", suffix="__")
132-
TO = generate_safe_column_name("__gfql_to__", edges, prefix="__gfql_", suffix="__")
133-
NID = generate_safe_column_name("__gfql_nid__", all_nodes, prefix="__gfql_", suffix="__")
134-
135-
if g._edge is not None and g._edge in edges.columns:
136-
EID = g._edge
137-
edges_idx = edges
138-
synth_eid = False
139-
else:
140-
EID = generate_safe_column_name("__gfql_eid__", edges, prefix="__gfql_", suffix="__")
141-
edges_idx = edges.with_row_index(EID)
142-
synth_eid = True
143-
144-
node_dtype = all_nodes.schema[node_col]
131+
from graphistry.compute.gfql.lazy.engine.polars.hop_eager import _hop_setup_columns, _build_hop_pairs
132+
FROM, TO, NID, EID, edges_idx, synth_eid, node_dtype = _hop_setup_columns(
133+
edges, all_nodes, node_col, g._edge)
145134
edges_lf = edges_idx.lazy()
146135
all_nodes_lf = all_nodes.lazy()
147-
148-
def _pairs(s: str, d: str) -> Any:
149-
return edges_lf.select(
150-
pl.col(s).cast(node_dtype).alias(FROM),
151-
pl.col(d).cast(node_dtype).alias(TO),
152-
pl.col(EID),
153-
)
154-
155-
if direction == "forward":
156-
pairs = _pairs(src, dst)
157-
elif direction == "reverse":
158-
pairs = _pairs(dst, src)
159-
else:
160-
pairs = pl.concat([_pairs(src, dst), _pairs(dst, src)], how="vertical_relaxed")
136+
pairs = _build_hop_pairs(edges_lf, direction, src, dst, node_dtype, FROM, TO, EID)
161137

162138
def _idframe_lf(lf: Any, col: str) -> Any:
163139
return lf.select(pl.col(col).cast(node_dtype).alias(NID)).unique()

graphistry/compute/gfql/lazy/engine/polars/hop_eager.py

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,38 @@ def ensure_nodes_polars(g: Plottable) -> Plottable:
4646
return g.nodes(ids, node_id)
4747

4848

49+
def _hop_setup_columns(edges, all_nodes, node_col, edge_binding):
50+
"""Shared eager+lazy hop setup: safe (FROM, TO, NID, EID) column names, the edge-id frame
51+
(reuse an existing `edge_binding` like the chain's __gfql_edge_index__, else synthesize a row
52+
index), and the node-id join dtype. Identical on pl.DataFrame and pl.LazyFrame edges (the lazy
53+
hop and the eager hop kept verbatim copies of this — see hop.py — now de-duplicated)."""
54+
FROM = generate_safe_column_name("__gfql_from__", edges, prefix="__gfql_", suffix="__")
55+
TO = generate_safe_column_name("__gfql_to__", edges, prefix="__gfql_", suffix="__")
56+
NID = generate_safe_column_name("__gfql_nid__", all_nodes, prefix="__gfql_", suffix="__")
57+
if edge_binding is not None and edge_binding in edges.columns:
58+
EID, edges_idx, synth_eid = edge_binding, edges, False
59+
else:
60+
EID = generate_safe_column_name("__gfql_eid__", edges, prefix="__gfql_", suffix="__")
61+
edges_idx, synth_eid = edges.with_row_index(EID), True
62+
return FROM, TO, NID, EID, edges_idx, synth_eid, all_nodes.schema[node_col]
63+
64+
65+
def _build_hop_pairs(frame, direction, src, dst, node_dtype, FROM, TO, EID):
66+
"""Shared directed-(FROM,TO,EID) builder with the join-key dtype aligned (polars won't coerce
67+
int/float join keys like pandas). `frame` is the edge-id frame — eager pl.DataFrame or its
68+
.lazy() LazyFrame; `.select`/`pl.concat` behave identically on both."""
69+
import polars as pl
70+
71+
def _p(s, d):
72+
return frame.select(pl.col(s).cast(node_dtype).alias(FROM),
73+
pl.col(d).cast(node_dtype).alias(TO), pl.col(EID))
74+
if direction == "forward":
75+
return _p(src, dst)
76+
if direction == "reverse":
77+
return _p(dst, src)
78+
return pl.concat([_p(src, dst), _p(dst, src)], how="vertical_relaxed")
79+
80+
4981
def _min_hops_labeled_node_output(all_nodes, needed, labeled, node_col, NID):
5082
"""min_hops CHAIN wavefront node frame, mirroring pandas' labeled (track_node_hops) hop.
5183
@@ -156,38 +188,9 @@ def hop_polars(
156188
f"Must provide integer hops when to_fixed_point is False, received: {resolved_max_hops}"
157189
)
158190

159-
FROM = generate_safe_column_name("__gfql_from__", edges, prefix="__gfql_", suffix="__")
160-
TO = generate_safe_column_name("__gfql_to__", edges, prefix="__gfql_", suffix="__")
161-
NID = generate_safe_column_name("__gfql_nid__", all_nodes, prefix="__gfql_", suffix="__")
162-
163-
# Reuse an existing edge-id binding (e.g. chain's __gfql_edge_index__) rather
164-
# than synthesizing a second monotonic index over the full edge table.
165-
if g._edge is not None and g._edge in edges.columns:
166-
EID = g._edge
167-
edges_idx = edges
168-
synth_eid = False
169-
else:
170-
EID = generate_safe_column_name("__gfql_eid__", edges, prefix="__gfql_", suffix="__")
171-
edges_idx = edges.with_row_index(EID)
172-
synth_eid = True
173-
174-
# Align join-key dtype: node ids and edge endpoints must share a dtype for
175-
# polars joins (pandas coerces int/float; polars does not).
176-
node_dtype = all_nodes.schema[node_col]
177-
178-
def _pairs(s: str, d: str) -> "pl.DataFrame":
179-
return edges_idx.select(
180-
pl.col(s).cast(node_dtype).alias(FROM),
181-
pl.col(d).cast(node_dtype).alias(TO),
182-
pl.col(EID),
183-
)
184-
185-
if direction == "forward":
186-
pairs = _pairs(src, dst)
187-
elif direction == "reverse":
188-
pairs = _pairs(dst, src)
189-
else:
190-
pairs = pl.concat([_pairs(src, dst), _pairs(dst, src)], how="vertical_relaxed")
191+
FROM, TO, NID, EID, edges_idx, synth_eid, node_dtype = _hop_setup_columns(
192+
edges, all_nodes, node_col, g._edge)
193+
pairs = _build_hop_pairs(edges_idx, direction, src, dst, node_dtype, FROM, TO, EID)
191194

192195
def _idframe(df, col) -> "pl.DataFrame":
193196
return df.select(pl.col(col).cast(node_dtype).alias(NID)).unique()

0 commit comments

Comments
 (0)