Skip to content

gfql polars: the plain single-hop chain branch ignores prune_to_endpoints (pandas keeps only the arrival side) #2053

Description

@lmeyerov

Summary

Route-level sibling found while hoisting the chain admission predicates: the pandas/cuDF chain fast path declines prune_to_endpoints=True ("prune keeps only the arrival side → full path"), but the polars chain's plain single-hop branch admits the shape and returns both endpoints plus the edges, so polars and pandas disagree.

Repro

import pandas as pd, polars as pl, graphistry
from graphistry.compute.ast import n, e_forward
nodes = pd.DataFrame({"key": [1, 2, 3, 4, 5], "id": [10, 20, 30, 40, 50]})
edges = pd.DataFrame({"s": [1, 1, 2, 3, 3, 4], "d": [2, 3, 3, 1, 1, 5], "eid": range(6)})
ops = [n({"key": 1}), e_forward(prune_to_endpoints=True), n()]
graphistry.nodes(nodes, "key").edges(edges, "s", "d", "eid").gfql(ops)                       # nodes [2, 3], no edges
graphistry.nodes(pl.from_pandas(nodes), "key").edges(pl.from_pandas(edges), "s", "d", "eid").gfql(ops, engine="polars")  # nodes [1, 2, 3], edges [0, 1]

Unseeded [n(), e_forward(prune_to_endpoints=True), n()]: pandas [1, 2, 3, 5], polars [1, 2, 3, 4, 5].

Expected

Either the polars plain branch declines prune_to_endpoints like the pandas gate (polars_plain_single_hop_admits is the single place to do it once the predicate refactor lands) or it implements the arrival-side contract; both engines must agree.

Pins

A strict-xfail parity pin lives in graphistry/tests/compute/gfql/lazy/engine/polars/test_chain_admission.py over the shared route corpus and flips when this is fixed.

Activity

  1. lmeyerov commented on Sep 6, 2026

    @lmeyerov
    ContributorAuthor

    Scope update from the route harness (#2061): the divergence is not only the plain single-hop branch. With every hot path declined (GFQL_ROUTES_OFF=all, pygraphistry's test-side route switch), the polars general traversal returns the same rows as the plain branch, so the polars engine as a whole ignores prune_to_endpoints while pandas keeps only the arrival side.

    Probe on the issue's frames ([n({"key": 1}), e_forward(prune_to_endpoints=True), n()]):

    path nodes edges
    pandas lane [2, 3] []
    pandas general [2, 3] []
    polars lane [1, 2, 3] [(1,2), (1,3)]
    polars general [1, 2, 3] [(1,2), (1,3)]

    Unseeded [n(), e_forward(prune_to_endpoints=True), n()]: pandas general [1, 2, 3, 5], polars general [1, 2, 3, 4, 5] (plus the (4,5) edge).

    So declining the shape in polars_plain_single_hop_admits would only move the wrong answer from the lane to the full path. The fix is the arrival-side contract in the polars chain itself (or a typed decline until it exists). The harness pins this as a strict expected failure on polars-plain keyed by tag #2053 (routes corpus + the pandas _BYPASS_SHAPES prune rows), so it flips when fixed.

  2. 2 remaining items

  3. lmeyerov commented on Oct 3, 2026

    @lmeyerov
    ContributorAuthor

    Closing: fixed by #2065. Its "Closes #2053" did not fire because the PR merged into a stacked base (fix/gfql-2058-general-path-int-dtypes) and the stack was re-landed under new SHAs, so GitHub never saw it reach master; the content is on master — test_prune_to_endpoints_is_a_typed_decline_on_polars_and_served_on_pandas and the prune_to_endpoints admission guard in chain_specializations/admission.py. Polars declines single-hop prune_to_endpoints with a typed error instead of ignoring it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions