diff --git a/bin/test-polars.sh b/bin/test-polars.sh index 1224d4e7a7..b8cb307404 100755 --- a/bin/test-polars.sh +++ b/bin/test-polars.sh @@ -122,6 +122,7 @@ POLARS_TEST_FILES=( graphistry/tests/compute/gfql/row/test_alias_prefilter_alignment_2020.py graphistry/tests/compute/gfql/lazy/engine/polars/test_chain_alias_column_collision_2039.py graphistry/tests/compute/test_chain_alias_column_collision.py + graphistry/tests/compute/gfql/routes/test_route_harness.py graphistry/tests/compute/gfql/test_engine_polars_semi_key_dedup.py graphistry/tests/compute/gfql/test_engine_polars_call_modality.py graphistry/tests/compute/gfql/test_engine_polars_gpu.py diff --git a/graphistry/tests/compute/gfql/cypher/test_lowering.py b/graphistry/tests/compute/gfql/cypher/test_lowering.py index 65c6a6b5dd..a64dc5c482 100644 --- a/graphistry/tests/compute/gfql/cypher/test_lowering.py +++ b/graphistry/tests/compute/gfql/cypher/test_lowering.py @@ -18626,6 +18626,7 @@ def test_t6_col_stats_decisions_are_visible_in_the_trace() -> None: outcomes={"nodes.id": "served", "edges.s": "served"}) +@pytest.mark.route_engaged("cypher-fast") def test_t6_assert_col_stats_helper_fails_loudly() -> None: """The helper must FAIL when the optimization did not fire -- an engagement pin that cannot fail is worse than none, which is the whole failure mode @@ -19225,6 +19226,7 @@ def _mk_h3_case_data(fixture: str) -> Tuple[pd.DataFrame, pd.DataFrame]: raise AssertionError(f"unknown fixture {fixture}") +@pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("engine", ["polars", "polars-gpu"]) @pytest.mark.parametrize("label,fixture,query", _H3_DIFFERENTIAL_CASES, ids=[c[0] for c in _H3_DIFFERENTIAL_CASES]) def test_h3_fused_two_hop_count_matches_eager_twin_and_pandas( @@ -19249,6 +19251,7 @@ def test_h3_fused_two_hop_count_matches_eager_twin_and_pandas( assert fused == oracle, f"{label}: fused lane diverged from the pandas oracle" +@pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("engine", ["polars", "polars-gpu"]) def test_h3_fused_two_hop_count_empty_match_counts_zero(engine: str, monkeypatch: pytest.MonkeyPatch) -> None: """openCypher counts over no rows as 0 -- not an empty frame.""" @@ -19359,6 +19362,7 @@ def test_h3_two_hop_count_fast_path_has_no_order_by_or_limit_surface(suffix: str assert _two_hop_count_alias(compiled.chain) == expect_alias +@pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("engine", ["polars", "polars-gpu"]) def test_h3_fused_two_hop_count_handles_degenerate_bindings(engine: str, monkeypatch: pytest.MonkeyPatch) -> None: """The node key may share a name with an endpoint column, and source/destination may be bound diff --git a/graphistry/tests/compute/gfql/index/test_degree_consult.py b/graphistry/tests/compute/gfql/index/test_degree_consult.py index acacdbd404..df2d5976df 100644 --- a/graphistry/tests/compute/gfql/index/test_degree_consult.py +++ b/graphistry/tests/compute/gfql/index/test_degree_consult.py @@ -102,6 +102,7 @@ def test_identity_anchors_to_the_bound_frame_not_the_partition() -> None: assert fact.source_ref is g._edges +@pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("engine", ENGINES) @pytest.mark.parametrize("n_p,n_c", [(3, 3), (5, 1), (2, 8), (7, 2)]) def test_slice_is_exact_across_domain_shapes(n_p: int, n_c: int, engine: str) -> None: @@ -117,6 +118,7 @@ def test_slice_is_exact_across_domain_shapes(n_p: int, n_c: int, engine: str) -> assert value == oracle +@pytest.mark.route_engaged("cypher-fast") def test_gapped_node_space_builds_facts_and_stays_exact() -> None: """Density is NOT required for the degree arrays: ids absent from the span contribute ZERO to the dot, so a gapped node space builds valid facts. (The @@ -137,6 +139,7 @@ def test_gapped_node_space_builds_facts_and_stays_exact() -> None: assert used, "P-domain [0,2] is dense, so the kernel must consult the fact" +@pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("seed", range(6)) def test_differential_vs_the_scan_on_random_typed_graphs(seed: int) -> None: """Values must be identical with and without the fact, on arbitrary degree diff --git a/graphistry/tests/compute/gfql/index/test_index.py b/graphistry/tests/compute/gfql/index/test_index.py index 35c6fedace..4ab6fd2210 100644 --- a/graphistry/tests/compute/gfql/index/test_index.py +++ b/graphistry/tests/compute/gfql/index/test_index.py @@ -1465,6 +1465,7 @@ def _polars_indexed_graph(): return g.gfql_index_all(engine="polars") +@pytest.mark.route_engaged("index-hop") def test_auto_engine_gfql_serves_polars_index_1767_cliff(): """#1767 cliff pin: polars frames + explicit polars index + gfql with NO engine argument must serve path=index on engine=polars (AUTO routes native, so the @@ -1701,6 +1702,7 @@ def test_col_stats_auto_narrows_lazy_frames(self): gi = gl.gfql_index_col_stats() # AUTO on lazy frames must not crash assert gi is not None + @pytest.mark.route_engaged("index-hop") def test_inversion_auto_index_auto_gfql_serves_polars_index(self): """THE INVERSION PIN. The exact scenario the retracted #1767 regressed to the scan floor: ``gfql_index_all()`` with NO engine + ``g.gfql( Any: _assert_decision(decisions[0], seam="connected_bindings", served=True) +@pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("engine", ENGINES) def test_destination_property_projection_dtype_parity( engine: str, @@ -726,6 +728,7 @@ def test_node_property_index_prefers_the_most_selective_column( pytest.param({"grp": 0}, "grp", False, id="unselective-keeps-scan"), ], ) +@pytest.mark.route_engaged("index-hop", "indexed-kernel") def test_node_property_index_cost_gate_under_policy_use( seed: Dict[str, Any], indexed_column: str, diff --git a/graphistry/tests/compute/gfql/routes/corpus.py b/graphistry/tests/compute/gfql/routes/corpus.py index 9a2a7c46dc..5c89806fe7 100644 --- a/graphistry/tests/compute/gfql/routes/corpus.py +++ b/graphistry/tests/compute/gfql/routes/corpus.py @@ -11,6 +11,7 @@ from graphistry.compute.ast import ASTObject, e_forward, e_reverse, e_undirected, n from graphistry.compute.predicates.numeric import GT +from graphistry.tests.compute.gfql.routes.registry import Frames, register class Entry(NamedTuple): @@ -54,3 +55,6 @@ def tagged(tag: str) -> List[Entry]: def by_name() -> Dict[str, Entry]: return {e.name: e for e in CORPUS} + + +register("routes.corpus", [(e.name, e.ops, e.tags) for e in CORPUS], Frames(NODES, EDGES, "key", "s", "d", "eid")) diff --git a/graphistry/tests/compute/gfql/routes/registry.py b/graphistry/tests/compute/gfql/routes/registry.py new file mode 100644 index 0000000000..f064caf23f --- /dev/null +++ b/graphistry/tests/compute/gfql/routes/registry.py @@ -0,0 +1,72 @@ +"""Shape registry for the route harness. + +Each specialization's own test module registers the shape table it was written against +(``register(...)`` returns the table unchanged, so the module keeps using it), with the +frames those shapes address and the defect classes they exercise. The harness in +``test_route_harness.py`` then tries every registered shape against every route whose +admission predicate admits it, so one input is exercised by several hot paths, not one. +""" +from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Sequence, Tuple, Union + +import pandas as pd +import pytest + +from graphistry.Plottable import Plottable +from graphistry.compute.ast import ASTObject + +Build = Callable[[], List[ASTObject]] +Row = Union[Tuple[str, Build], Tuple[str, Build, Tuple[str, ...]]] + + +class Frames(NamedTuple): + nodes: pd.DataFrame + edges: pd.DataFrame + node: str + src: str + dst: str + edge: Optional[str] = None + + +class Shape(NamedTuple): + table: str + label: str + build: Build + frames: Frames + tags: Tuple[str, ...] + + @property + def name(self) -> str: + return f"{self.table}/{self.label}" + + +REGISTRY: Dict[str, Shape] = {} + + +def register(table: str, rows: Sequence[Row], frames: Frames, tags: Iterable[str] = (), + row_tags: Optional[Dict[str, Tuple[str, ...]]] = None) -> Sequence[Row]: + """Register ``rows`` ((label, build[, tags]) ...) under ``table``; returns ``rows``.""" + base = tuple(tags) + for row in rows: + label, build = row[0], row[1] + extra = tuple(row[2]) if len(row) > 2 else () + extra += (row_tags or {}).get(label, ()) + shape = Shape(table, label, build, frames, base + extra) + REGISTRY.setdefault(shape.name, shape) + return rows + + +def to_engine(df: pd.DataFrame, engine: str): + if engine == "pandas": + return df + if engine == "cudf": + return pytest.importorskip("cudf").from_pandas(df) + if engine == "polars": + return pytest.importorskip("polars").from_pandas(df) + raise ValueError(engine) + + +def graph_for(shape: Shape, engine: str, indexed: bool = False) -> Plottable: + import graphistry + f = shape.frames + g = graphistry.nodes(to_engine(f.nodes, engine), f.node).edges(to_engine(f.edges, engine), f.src, f.dst, f.edge) + return g.gfql_index_all(engine=engine) if indexed else g diff --git a/graphistry/tests/compute/gfql/routes/switch.py b/graphistry/tests/compute/gfql/routes/switch.py new file mode 100644 index 0000000000..781dde5325 --- /dev/null +++ b/graphistry/tests/compute/gfql/routes/switch.py @@ -0,0 +1,51 @@ +"""Route switch for test amplification: make named GFQL hot paths decline for a scope.""" +from contextlib import contextmanager +from typing import Iterable, Iterator, List, Tuple + +ROUTES = ("native-fast", "polars-seeded", "polars-plain", "index-hop", "indexed-kernel", "cypher-fast") + + +def _none(*a, **k): + return None + + +def _targets(routes: Iterable[str]) -> List[Tuple[object, str]]: + import graphistry.compute.chain as chain_mod + import graphistry.compute.gfql_unified as unified + import graphistry.compute.gfql.index as index_pkg + import graphistry.compute.gfql.index.api as index_api + import graphistry.compute.gfql.index.bindings as bindings + import graphistry.compute.gfql.lazy.engine.polars.chain as pchain + routes = set(routes) + unknown = routes - set(ROUTES) + assert not unknown, f"unknown route(s) {sorted(unknown)}; known: {ROUTES}" + out: List[Tuple[object, str]] = [] + if "native-fast" in routes: + out.append((chain_mod, "_try_chain_fast_path")) + if "polars-seeded" in routes: + out.append((pchain, "_try_seeded_chain_polars")) + if "polars-plain" in routes: + out.append((pchain, "polars_plain_single_hop_admits")) + if "index-hop" in routes: + out += [(index_pkg, "maybe_index_hop"), (index_api, "maybe_index_hop")] + if "indexed-kernel" in routes: + out.append((bindings, "_try_indexed_connected_bindings_state")) + if "cypher-fast" in routes: + out += [(unified, name) for name in ( + "_execute_seeded_node_lookup_fast_path", "_execute_seeded_typed_hop_fast_path", + "_execute_single_hop_grouped_aggregate_fast_path", "_execute_two_hop_count_fast_path")] + return out + + +@contextmanager +def routes_off(routes: Iterable[str]) -> Iterator[None]: + """Within the block the named routes decline, so the general path answers.""" + saved = [] + for mod, name in _targets(routes): + saved.append((mod, name, getattr(mod, name))) + setattr(mod, name, _none) + try: + yield + finally: + for mod, name, value in reversed(saved): + setattr(mod, name, value) diff --git a/graphistry/tests/compute/gfql/routes/test_route_harness.py b/graphistry/tests/compute/gfql/routes/test_route_harness.py new file mode 100644 index 0000000000..7ab70fdb6a --- /dev/null +++ b/graphistry/tests/compute/gfql/routes/test_route_harness.py @@ -0,0 +1,181 @@ +"""Route harness: every registered shape is tried against every chain route whose admission +predicate admits it. Three pins per cell: the route SERVES (its lane answers; a lane that +declines an admitted shape is recorded as an expected failure, the attenuation ledger), the +answer matches the same engine's general path (all routes off) on node/edge values, and its +node/edge sets match the pandas general path (the cross-engine oracle). Filed divergences are +strict expected failures keyed by their tag, so they flip when fixed. +""" +import math +import os +from typing import Callable, Dict, List, NamedTuple, Tuple + +import pandas as pd +import pytest + +import graphistry.compute.chain as chain_mod +import graphistry.compute.gfql.lazy.engine.polars.chain as pchain +from graphistry.Engine import Engine +from graphistry.compute.ast import ASTObject +from graphistry.compute.chain_specializations.admission import native_fast_path_admits +from graphistry.compute.gfql.lazy.engine.polars.chain_specializations.admission import ( + polars_plain_single_hop_admits, polars_seeded_lane_admits, +) +from graphistry.tests.compute.gfql.routes.registry import REGISTRY, Shape, graph_for +from graphistry.tests.compute.gfql.routes.switch import ROUTES as ALL_ROUTES, routes_off + +import graphistry.tests.compute.gfql.routes.corpus # noqa: F401 registers routes.corpus +import graphistry.tests.compute.test_chain # noqa: F401 registers test_chain.* +import graphistry.tests.compute.test_chain_alias_column_collision # noqa: F401 registers collision.* + + +class Route(NamedTuple): + name: str + engines: Tuple[str, ...] + admits: Callable[[List[ASTObject], str], bool] + lane: Tuple[object, str] + indexed: bool + + +ROUTES = [ + Route("native-fast", ("pandas", "cudf"), + lambda ops, engine: native_fast_path_admits(ops, Engine(engine), None) is not None, + (chain_mod, "_try_chain_fast_path"), False), + Route("polars-plain", ("polars",), + lambda ops, engine: polars_plain_single_hop_admits(ops, None) is not None, + (pchain, "_plain_single_hop_polars"), False), + Route("polars-seeded", ("polars",), + lambda ops, engine: polars_seeded_lane_admits(ops), + (pchain, "_try_seeded_chain_polars"), True), +] + +KNOWN: Dict[Tuple[str, str], str] = { # (route, tag) -> issue: strict xfail until it lands + ("polars-plain", "#2053"): "graphistry/pygraphistry#2053", +} + + +class Case(NamedTuple): + route: Route + engine: str + shape: Shape + + @property + def id(self) -> str: + return f"{self.route.name}/{self.engine}/{self.shape.name}" + + +def _cases() -> List[Case]: + out = [] + for shape in REGISTRY.values(): + for route in ROUTES: + for engine in route.engines: + try: + admitted = route.admits(shape.build(), engine) + except Exception: + admitted = False + if admitted: + out.append(Case(route, engine, shape)) + return out + + +CASES = _cases() + + +def _topd(df): + if df is None: + return None + if hasattr(df, "to_pandas"): + return df.to_pandas() + return df + + +def _canon(df) -> Tuple[Tuple[str, ...], List[Tuple]]: + df = _topd(df) + if df is None: + return ((), []) + cols = tuple(sorted(df.columns)) + rows = [] + for row in df[list(cols)].itertuples(index=False, name=None): + rows.append(tuple(None if (isinstance(v, float) and math.isnan(v)) or v is pd.NA or v is pd.NaT else v for v in row)) + rows.sort(key=repr) + return cols, rows + + +def _sig(res, frames) -> Tuple[List, List]: + nn, ee = _topd(res._nodes), _topd(res._edges) + nodes = sorted(nn[frames.node].tolist()) if nn is not None else [] + edges = sorted(map(tuple, ee[[frames.src, frames.dst]].values.tolist())) if ee is not None and len(ee) else [] + return nodes, edges + + +def _served(case: Case, monkeypatch): + mod, name = case.route.lane + real = getattr(mod, name) + calls = {"served": 0} + + def spy(*a, **k): + out = real(*a, **k) + calls["served"] += out is not None + return out + monkeypatch.setattr(mod, name, spy) + return calls + + +def _skip_unavailable(engine: str) -> None: + if engine == "cudf": + if os.environ.get("TEST_CUDF") != "1": + pytest.skip("cuDF lane runs with TEST_CUDF=1") + pytest.importorskip("cudf") + if engine == "polars": + pytest.importorskip("polars") + + +@pytest.mark.parametrize("case", CASES, ids=[c.id for c in CASES]) +def test_admitted_shape_is_served_and_matches_the_general_path(case: Case, request, monkeypatch): + _skip_unavailable(case.engine) + for tag in case.shape.tags: + if (case.route.name, tag) in KNOWN: + request.applymarker(pytest.mark.xfail(strict=True, reason=KNOWN[(case.route.name, tag)])) + g = graph_for(case.shape, case.engine, indexed=case.route.indexed) + calls = _served(case, monkeypatch) + try: + served = g.gfql(case.shape.build(), engine=case.engine) + except Exception as served_exc: + with routes_off(ALL_ROUTES): + with pytest.raises(type(served_exc)): + g.gfql(case.shape.build(), engine=case.engine) + return + with routes_off(ALL_ROUTES): + general = g.gfql(case.shape.build(), engine=case.engine) + oracle = _sig(graph_for(case.shape, "pandas").gfql(case.shape.build(), engine="pandas"), case.shape.frames) + assert _canon(served._nodes) == _canon(general._nodes), f"{case.id}: node rows differ from the general path" + assert _canon(served._edges) == _canon(general._edges), f"{case.id}: edge rows differ from the general path" + assert _sig(served, case.shape.frames) == oracle, f"{case.id}: node/edge sets differ from the pandas general path" + if calls["served"] == 0: + pytest.xfail(f"{case.id}: admitted by the predicate, declined by the lane body (attenuation ledger)") + + +@pytest.mark.route_engaged("native-fast", "polars-plain", "polars-seeded") +def test_every_route_serves_most_of_what_it_admits(monkeypatch): + """A lane that declines most admitted shapes has a predicate that no longer describes it.""" + per_route: Dict[str, List[int]] = {} + for case in CASES: + if case.engine != ("polars" if case.route.name.startswith("polars") else "pandas"): + continue + if case.engine == "polars": + pytest.importorskip("polars") + g = graph_for(case.shape, case.engine, indexed=case.route.indexed) + calls = _served(case, monkeypatch) + try: + g.gfql(case.shape.build(), engine=case.engine) + except Exception: + continue + per_route.setdefault(case.route.name, []).append(calls["served"] > 0) + for route, served in per_route.items(): + assert sum(served) * 2 >= len(served), f"{route}: served {sum(served)} of {len(served)} admitted shapes" + + +def test_every_route_has_admitted_shapes(): + covered = {(c.route.name, c.engine) for c in CASES} + for route in ROUTES: + for engine in route.engines: + assert (route.name, engine) in covered, f"{route.name}/{engine} admits no registered shape" diff --git a/graphistry/tests/compute/gfql/test_native_seed_lane_explain.py b/graphistry/tests/compute/gfql/test_native_seed_lane_explain.py index 9bd377a4b2..15d29fb133 100644 --- a/graphistry/tests/compute/gfql/test_native_seed_lane_explain.py +++ b/graphistry/tests/compute/gfql/test_native_seed_lane_explain.py @@ -44,7 +44,7 @@ def test_node_only_lookup_served_by_the_property_index_is_explained(engine): assert len(g.gfql(NODE_ONLY, engine=engine, index_policy="use")._nodes) == 1 -@pytest.mark.route_engaged("native-fast") +@pytest.mark.route_engaged("native-fast", "polars-seeded") @pytest.mark.parametrize("engine", ENGINES) def test_seeded_typed_hop_served_by_the_resident_indexes_is_explained(engine): g = _graph(engine) diff --git a/graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py b/graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py index fb9c0c0853..a8165d9fca 100644 --- a/graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py +++ b/graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py @@ -114,6 +114,7 @@ def spy(*a, **k): assert calls["n"] >= 1 and len(out._edges) == 1 +@pytest.mark.route_engaged("native-fast") @pytest.mark.parametrize("engine", ENGINES) @pytest.mark.parametrize("binding_first", [True, False]) def test_named_single_node_alias_layout_matches_the_full_path(engine, binding_first): @@ -129,6 +130,7 @@ def test_named_single_node_alias_layout_matches_the_full_path(engine, binding_fi assert list(fast._nodes.columns)[:2] == ["key", "p"] +@pytest.mark.route_engaged("native-fast") @pytest.mark.parametrize("engine", ENGINES) @pytest.mark.parametrize("binding_first", [True, False]) def test_named_single_node_alias_overwrites_colliding_property_like_full_path(engine, binding_first): @@ -146,6 +148,7 @@ def test_named_single_node_alias_overwrites_colliding_property_like_full_path(en pd.testing.assert_frame_equal(_canon(fast._nodes), _canon(full._nodes), check_dtype=False) +@pytest.mark.route_engaged("native-fast") @pytest.mark.parametrize("engine", ENGINES) def test_named_hop_aliases_overwrite_nonfinal_properties_like_full_path(engine): g = _lane_graph(engine) @@ -200,6 +203,7 @@ def test_policy_off_keeps_parity_and_uses_no_index(engine, shape): assert report["used_index"] is False and report["decision_code"] == "policy_off", report +@pytest.mark.route_engaged("native-fast") @pytest.mark.parametrize("engine", ENGINES) @pytest.mark.parametrize("shape", list(SHAPES)) def test_stale_indexes_keep_parity_and_are_not_used(engine, shape): @@ -231,6 +235,7 @@ def test_non_scalar_seed_predicates_keep_parity_without_the_index(engine, seed): assert not any(s.get("seam") in ("native_seed_lookup", "native_seeded_hop") and s.get("served") for s in steps), steps +@pytest.mark.route_engaged("native-fast") @pytest.mark.parametrize("engine", ENGINES) def test_duplicate_node_rows_are_answered_once_each_on_the_native_lookup(engine): """A node table that repeats a key row (a contract violation the engine tolerates): the diff --git a/graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py b/graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py index 8c95ce13ac..99450c856d 100644 --- a/graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py +++ b/graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py @@ -33,6 +33,7 @@ def _graph(reverse=False, indexed=True, padding=0): return g.gfql_index_all(engine="polars").gfql_index_node_props(["id"], engine="polars") if indexed else g +@pytest.mark.route_engaged("polars-seeded") @pytest.mark.parametrize("reverse", [False, True]) @pytest.mark.parametrize("indexed", [False, True]) @pytest.mark.parametrize("seed", [{"id": 104}, {"kind": "Message"}, {"id": 105}, {"id": 999}]) @@ -73,6 +74,7 @@ def spy(*args): _NAMED_TYPED_HOP = [n({"id": 104}, name="m"), e_forward({"type": "T"}, name="e"), n({"kind": "Person"}, name="p")] +@pytest.mark.route_engaged("polars-seeded") def test_native_seeded_hop_is_served_from_the_index_and_traced(): from graphistry.compute.gfql.index import index_trace g = _graph() @@ -98,6 +100,7 @@ def test_native_seeded_hop_declines_without_a_usable_index(policy, monkeypatch): assert_frame_equal(fast._edges, full._edges) +@pytest.mark.route_engaged("polars-seeded") @pytest.mark.parametrize("single_node", [False, True]) def test_native_property_seed_uses_resident_index(single_node, monkeypatch): import graphistry.compute.gfql.index.bindings as bindings diff --git a/graphistry/tests/compute/gfql/test_rewrite_param_discard.py b/graphistry/tests/compute/gfql/test_rewrite_param_discard.py index 91941879e9..c86ffc2651 100644 --- a/graphistry/tests/compute/gfql/test_rewrite_param_discard.py +++ b/graphistry/tests/compute/gfql/test_rewrite_param_discard.py @@ -210,6 +210,7 @@ def test_indexed_bypass_table_edges_survives_a_projection(engine: str) -> None: "(index/bindings.py gate), so polars-gpu always takes the scan path", )), ]) +@pytest.mark.route_engaged("indexed-kernel") def test_indexed_bypass_still_serves_a_bare_rows(engine: str) -> None: """THE NEGATIVE SIDE: declining on a non-default `table` must not decline everything. diff --git a/graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py b/graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py index f8280b2d28..e6c27ccee5 100644 --- a/graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py +++ b/graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py @@ -114,6 +114,7 @@ def test_node_lookup_engages_with_parity(engine, indexed, q, label): _assert_parity(_graph(engine, indexed), engine, q, "seeded_node_lookup") +@pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("engine", ENGINES) def test_node_lookup_matches_independent_oracle(engine): g = _graph(engine) @@ -263,6 +264,7 @@ def test_hub_seed_over_the_frontier_gate_keeps_parity(engine, indexed): pd.testing.assert_frame_equal(_canon(fast), _canon(full)) +@pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("engine", ENGINES) def test_seed_matching_several_nodes_projects_each_seed(engine): """A non-unique seed predicate: every seed row pairs with its own destinations.""" diff --git a/graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py b/graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py index 941e852ef6..422aa7ce7e 100644 --- a/graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py +++ b/graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py @@ -851,12 +851,14 @@ def spy(*a, **k): assert bool(hits["n"]) == expect_engage, f"engaged={hits['n']} expected={expect_engage}" return fast, full + @pytest.mark.route_engaged("cypher-fast") def test_pandas_int_bool_dtype_parity(self): fast, full = self._fast_and_full(self._typed_graph(), "pandas", self.Q) pd.testing.assert_frame_equal(_canon_nodes(fast), _canon_nodes(full)) dt = dict(zip(fast._nodes.columns, map(str, fast._nodes.dtypes))) assert dt == {"pid": "int64", "a": "float64", "f": "object"} + @pytest.mark.route_engaged("cypher-fast") def test_polars_int_bool_dtype_parity(self): pytest.importorskip("polars") fast, full = self._fast_and_full(self._pl_graph(), "polars", self.Q) @@ -876,6 +878,7 @@ def test_pandas_datetime_property_declines(self): fast, full = self._fast_and_full(self._typed_graph(), "pandas", q, expect_engage=False) pd.testing.assert_frame_equal(_canon_nodes(fast), _canon_nodes(full)) + @pytest.mark.route_engaged("cypher-fast") @pytest.mark.parametrize("engine", ["pandas", "polars"]) def test_edges_empty_frame_not_none(self, engine): g = self._typed_graph() if engine == "pandas" else self._pl_graph() @@ -1015,6 +1018,7 @@ def test_stale_index_declines_to_scan(self, monkeypatch): pd.testing.assert_frame_equal(self._canon(got), self._canon(plain)) + @pytest.mark.route_engaged("native-fast") def test_native_chain_hop_indexed_parity_forward_and_reverse(self, monkeypatch): """M1 pin: the native-chain hop helper's indexed branch (only reachable via chain ops, never Cypher) — forward (EDGE_OUT_ADJ) and reverse (EDGE_IN_ADJ), @@ -1041,6 +1045,7 @@ def spy(*a, **k): plain = mk().gfql(ops, engine="pandas") pd.testing.assert_frame_equal(self._canon(got), self._canon(plain)) + @pytest.mark.route_engaged("cypher-fast") def test_uint64_int64_id_mix_declines_not_collapses(self, monkeypatch): """B1 pin: int64<->uint64 promotes to float64, which collapses ids >= 2**53 into false matches; the gate must DECLINE (scan path compares exactly).""" diff --git a/graphistry/tests/compute/test_chain.py b/graphistry/tests/compute/test_chain.py index 6139fed707..d711451d10 100644 --- a/graphistry/tests/compute/test_chain.py +++ b/graphistry/tests/compute/test_chain.py @@ -5,6 +5,7 @@ import pytest from graphistry.compute.ast import ASTEdgeUndirected, ASTNode, ASTEdge, ASTObject, n, e, e_undirected, e_forward, e_reverse +from graphistry.tests.compute.gfql.routes.registry import Frames, register from graphistry.compute.chain import Chain, _try_chain_fast_path from graphistry.compute.typing import DataFrameT from graphistry.compute.predicates.is_in import IsIn, is_in @@ -652,9 +653,14 @@ def _cudf_or_skip(): return pytest.importorskip("cudf") +_FAST_FRAMES = Frames( + pd.DataFrame({'v': [0, 1, 2, 3, 4], 'attr': [10, 20, 30, 40, 50]}), + pd.DataFrame({'s': [0, 1, 2, 3, 0], 'd': [1, 2, 3, 4, 2], 'w': [5, 6, 7, 8, 9]}), + 'v', 's', 'd') + + def _fast_graph(engine): - nodes = pd.DataFrame({'v': [0, 1, 2, 3, 4], 'attr': [10, 20, 30, 40, 50]}) - edges = pd.DataFrame({'s': [0, 1, 2, 3, 0], 'd': [1, 2, 3, 4, 2], 'w': [5, 6, 7, 8, 9]}) + nodes, edges = _FAST_FRAMES.nodes, _FAST_FRAMES.edges if engine == "cudf": cudf = _cudf_or_skip() nodes = cudf.from_pandas(nodes) @@ -675,7 +681,7 @@ def topd(df): # shapes that ARE accelerated by the fast path -_FAST_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = [ +_FAST_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = register("test_chain.fast", [ ("node_only", lambda: [n()]), ("node_filter", lambda: [n({'attr': 20})]), ("node_pred", lambda: [n({'attr': is_in([10, 30])})]), @@ -702,10 +708,10 @@ def topd(df): ("named_all_fwd", lambda: [n(name='x'), e_forward(hops=1, name='r'), n(name='y')]), ("named_all_rev", lambda: [n(name='x'), e_reverse(hops=1, name='r'), n(name='y')]), ("named_filtered", lambda: [n({'attr': 10}, name='x'), e_forward(hops=1), n(name='y')]), -] +], _FAST_FRAMES, tags=("native-fast",)) # shapes that BYPASS the fast path (still must be correct via the full path) -_BYPASS_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = [ +_BYPASS_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = register("test_chain.bypass", [ ("hops_2", lambda: [n(), e_forward(hops=2), n()]), ("filtered_undirected", lambda: [n({'attr': 10}), e_undirected(hops=1), n({'attr': 30})]), # Named + undirected STAYS a bypass: an undirected edge makes a node reachable as @@ -716,7 +722,7 @@ def topd(df): # arrival side. Must bypass the fast path (regression guard for the prune gate). ("prune_endpoints_fwd", lambda: [n(), e_forward(hops=1, prune_to_endpoints=True), n()]), ("prune_endpoints_rev", lambda: [n(), e_reverse(hops=1, prune_to_endpoints=True), n()]), -] +], _FAST_FRAMES, tags=("native-fast-bypass",), row_tags={"prune_endpoints_fwd": ("#2053",), "prune_endpoints_rev": ("#2053",)}) _CUDF_26_DIVERGENT = {"prune_endpoints_fwd", "prune_endpoints_rev"} # graphistry/pygraphistry#2043 @@ -750,7 +756,7 @@ def test_fast_path_differential_parity_vs_full_path(engine, label, build, reques # Named shapes whose ALIAS FLAG COLUMNS (not merely node/edge sets) must match the full # path. `_setsig` above compares ids only, so it cannot see a wrong alias tag. -_NAMED_ALIAS_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = [ +_NAMED_ALIAS_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = register("test_chain.named_alias", [ ("src_only", lambda: [n(name='x'), e_forward(hops=1), n()]), ("dst_only", lambda: [n(), e_forward(hops=1), n(name='y')]), ("edge_only", lambda: [n(), e_forward(hops=1, name='r'), n()]), @@ -762,7 +768,7 @@ def test_fast_path_differential_parity_vs_full_path(engine, label, build, reques # DEAD END: attr==50 is node 4, which has no outgoing edge. The tag keys on the # SURVIVING EDGES, so the alias must come back False/empty rather than True. ("dead_end_seed", lambda: [n({'attr': 50}, name='x'), e_forward(hops=1, name='r'), n(name='y')]), -] +], _FAST_FRAMES, tags=("alias",)) @pytest.mark.parametrize("engine", ["pandas", "cudf"]) @@ -825,17 +831,18 @@ def _assert_full_frame_value_parity(fast: DataFrameT, full: DataFrameT, # Named served shapes for FULL-FRAME parity. `_setsig` compares id sets and the flags # test compares alias columns, so before this NO test compared the carried DATA columns # ('attr', 'w') of a named served result against the full path. -_NAMED_VALUE_PARITY_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = [ +_NAMED_VALUE_PARITY_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = register("test_chain.named_value_parity", [ ("all_forward", lambda: [n(name='x'), e_forward(hops=1, name='r'), n(name='y')]), ("all_reverse", lambda: [n(name='x'), e_reverse(hops=1, name='r'), n(name='y')]), ("seed_filtered", lambda: [n({'attr': 10}, name='x'), e_forward(hops=1, name='r'), n(name='y')]), ("edge_match", lambda: [n(name='x'), e_forward(hops=1, edge_match={'w': 5}, name='r'), n(name='y')]), -] +], _FAST_FRAMES, tags=("alias", "values")) @pytest.mark.parametrize("engine", ["pandas", "cudf"]) @pytest.mark.parametrize("label,build", _NAMED_VALUE_PARITY_SHAPES, ids=[s[0] for s in _NAMED_VALUE_PARITY_SHAPES]) +@pytest.mark.route_engaged("native-fast") def test_fast_path_named_full_frame_value_parity(engine, label, build): """POSITIVE, whole-frame: a named served result must carry the same VALUES as the full path on EVERY column — ids, data columns, and alias flags — not just the id @@ -866,13 +873,13 @@ def test_fast_path_named_full_frame_value_parity(engine, label, build): # cardinality, so these all engage the fast path — and an empty answer must come back # as the right empty SHAPE (alias columns present, zero rows), not a throw and not a # missing-column frame. -_NAMED_EMPTY_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = [ +_NAMED_EMPTY_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = register("test_chain.named_empty", [ # seed filter matches no node at all (distinct from dead_end_seed, which matches # a node that has no surviving edge) ("zero_seed", lambda: [n({'attr': 999}, name='x'), e_forward(hops=1, name='r'), n(name='y')]), ("zero_dst", lambda: [n(name='x'), e_forward(hops=1, name='r'), n({'attr': 999}, name='y')]), ("zero_edge_match", lambda: [n(name='x'), e_forward(hops=1, edge_match={'w': 999}, name='r'), n(name='y')]), -] +], _FAST_FRAMES, tags=("alias", "empty")) @pytest.mark.parametrize("engine", ["pandas", "cudf"]) @@ -1023,7 +1030,7 @@ def test_fast_path_named_datetime_categorical_columns_ride_along(): # overwrite/raise behavior alone. The FROM-side binding columns and the node-id # binding are excluded here: those wrong-served (diverged) before, are now GATED to # decline, and are pinned by the two regression tests below. -_ALIAS_SHADOW_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = [ +_ALIAS_SHADOW_SHAPES: List[Tuple[str, Callable[[], List[ASTObject]]]] = register("test_chain.alias_shadow", [ ("node_alias_shadows_node_data_col", lambda: [n(name='attr'), e_forward(hops=1), n()]), ("edge_alias_shadows_edge_data_col", lambda: [n(), e_forward(hops=1, name='w'), n()]), # edge aliases named like the source/destination/edge-id bindings are rejected before @@ -1031,7 +1038,7 @@ def test_fast_path_named_datetime_categorical_columns_ride_along(): # cross-frame names are NOT collisions: nodes have no 'w', edges have no 'v' ("node_alias_named_like_edge_col", lambda: [n(name='w'), e_forward(hops=1), n()]), ("edge_alias_named_like_node_id", lambda: [n(), e_forward(hops=1, name='v'), n()]), -] +], _FAST_FRAMES, tags=("alias-collision",)) @pytest.mark.parametrize("label,build", _ALIAS_SHADOW_SHAPES, diff --git a/graphistry/tests/compute/test_chain_alias_column_collision.py b/graphistry/tests/compute/test_chain_alias_column_collision.py index d90c4c725f..af874aeb29 100644 --- a/graphistry/tests/compute/test_chain_alias_column_collision.py +++ b/graphistry/tests/compute/test_chain_alias_column_collision.py @@ -13,11 +13,13 @@ import graphistry from graphistry.compute.ast import e_forward, e_reverse, e_undirected, n +from graphistry.tests.compute.gfql.routes.registry import Frames, register NODES = pd.DataFrame({"key": [1, 2, 3, 4], "id": [10, 20, 30, 40], "type": ["p", "p", "m", "m"], "w": [1, 2, 3, 4]}) EDGES = pd.DataFrame({"s": [3, 3, 4, 1], "d": [1, 2, 1, 4], "type": ["HAS_CREATOR", "OTHER", "HAS_CREATOR", "OTHER"], "eid": [100, 101, 102, 103], "w": [5, 6, 7, 8]}) ENGINES = ["pandas", "cudf", "polars"] +FRAMES = Frames(NODES, EDGES, "key", "s", "d", "eid") def _graph(engine, indexed): @@ -62,6 +64,8 @@ def topd(x): "seed, edge and destination aliases all collide": [n({"id": 30}, name="id"), e_forward({"type": "HAS_CREATOR"}, name="type"), n({"type": "p"}, name="type")], } +register("collision.served", [(k, (lambda v=v: list(v))) for k, v in SERVED.items()], FRAMES, tags=("alias-collision", "#2039")) + @pytest.mark.parametrize("engine", ENGINES) @pytest.mark.parametrize("indexed", [False, True], ids=["scan", "indexed"]) @@ -81,6 +85,8 @@ def test_single_hop_collisions_match_the_pandas_full_path(engine, indexed, shape "edge alias = filtered column, to_fixed_point": [n({"id": 30}, name="m"), e_forward({"type": "HAS_CREATOR"}, to_fixed_point=True, name="type"), n(name="p")], } +register("collision.multi_hop", [(k, (lambda v=v: list(v))) for k, v in MULTI_HOP.items()], FRAMES, tags=("alias-collision", "#2049")) + @pytest.mark.parametrize("engine", ENGINES) @pytest.mark.parametrize("shape", list(MULTI_HOP)) diff --git a/graphistry/tests/conftest.py b/graphistry/tests/conftest.py index b5b633cd92..221eac3707 100644 --- a/graphistry/tests/conftest.py +++ b/graphistry/tests/conftest.py @@ -41,39 +41,6 @@ def _gfql_routes_off(): if not routes: yield return - import graphistry.compute.chain as chain_mod - import graphistry.compute.gfql_unified as unified - import graphistry.compute.gfql.index as index_pkg - import graphistry.compute.gfql.index.api as index_api - import graphistry.compute.gfql.index.bindings as bindings - import graphistry.compute.gfql.lazy.engine.polars.chain as pchain - - def none(*a, **k): - return None - - patches = [] - - def patch(mod, name, value): - patches.append((mod, name, getattr(mod, name))) - setattr(mod, name, value) - - if "native-fast" in routes: - patch(chain_mod, "_try_chain_fast_path", none) - if "polars-seeded" in routes: - patch(pchain, "_try_seeded_chain_polars", none) - if "polars-plain" in routes: - patch(pchain, "polars_plain_single_hop_admits", none) - if "index-hop" in routes: - patch(index_pkg, "maybe_index_hop", none) - patch(index_api, "maybe_index_hop", none) - if "indexed-kernel" in routes: - patch(bindings, "_try_indexed_connected_bindings_state", none) - if "cypher-fast" in routes: - for name in ("_execute_seeded_node_lookup_fast_path", "_execute_seeded_typed_hop_fast_path", - "_execute_single_hop_grouped_aggregate_fast_path", "_execute_two_hop_count_fast_path"): - patch(unified, name, none) - try: + from graphistry.tests.compute.gfql.routes.switch import routes_off + with routes_off(routes): yield - finally: - for mod, name, value in reversed(patches): - setattr(mod, name, value) diff --git a/reviews/2054/all-off.divergences b/reviews/2054/all-off.divergences new file mode 100644 index 0000000000..5a1393b994 --- /dev/null +++ b/reviews/2054/all-off.divergences @@ -0,0 +1,57 @@ +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_empty_match_counts_zero[polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_handles_degenerate_bindings[polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[base_distinct_domain-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[base_distinct_edges-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[empty_no_edges-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[empty_no_matching_nodes-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[messy_distinct_domain-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[messy_distinct_edges-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[messy_end_only_filter-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[messy_start_only_filter-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[string_ids-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_t6_assert_col_stats_helper_fails_loudly +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[0] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[1] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[2] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[3] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[4] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[5] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_gapped_node_space_builds_facts_and_stays_exact +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[2-8-cudf] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[2-8-pandas] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[2-8-polars] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[3-3-cudf] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[3-3-pandas] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[3-3-polars] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[5-1-cudf] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[5-1-pandas] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[5-1-polars] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[7-2-cudf] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[7-2-pandas] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[7-2-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_destination_property_projection_dtype_parity[cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_destination_property_projection_dtype_parity[pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_destination_property_projection_dtype_parity[polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_node_property_index_cost_gate_under_policy_use[selective-uses-index] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_node_property_index_cost_gate_under_policy_use[unselective-keeps-scan] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_node_property_index_duplicate_values_match_scan +FAILED graphistry/tests/compute/gfql/index/test_index.py::test_auto_engine_gfql_serves_polars_index_1767_cliff +FAILED graphistry/tests/compute/gfql/index/test_index.py::TestIndexAutoPreservesPolarsFrames::test_inversion_auto_index_auto_gfql_serves_polars_index +FAILED graphistry/tests/compute/gfql/lazy/engine/polars/test_chain_alias_column_collision_2039.py::test_destination_alias_marker_replaces_the_colliding_column_like_pandas +FAILED graphistry/tests/compute/gfql/test_endpoint_closure_matrix.py::test_chain_surface_keeps_node_attribute_dtypes[pandas] +FAILED graphistry/tests/compute/gfql/test_polars_rows_entity_groupby.py::test_has_label_narrowing_applies_on_reached_collision[polars] +FAILED graphistry/tests/compute/gfql/test_rewrite_param_discard.py::test_indexed_bypass_still_serves_a_bare_rows[cudf] +FAILED graphistry/tests/compute/gfql/test_rewrite_param_discard.py::test_indexed_bypass_still_serves_a_bare_rows[pandas] +FAILED graphistry/tests/compute/gfql/test_rewrite_param_discard.py::test_indexed_bypass_still_serves_a_bare_rows[polars] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_node_lookup_matches_independent_oracle[cudf] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_node_lookup_matches_independent_oracle[pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_node_lookup_matches_independent_oracle[polars] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_node_lookup_returns_each_duplicate_id_row_once[cudf] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_node_lookup_returns_each_duplicate_id_row_once[pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_seed_matching_several_nodes_projects_each_seed[cudf] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_seed_matching_several_nodes_projects_each_seed[pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_seed_matching_several_nodes_projects_each_seed[polars] +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestSeededProjectionDtypeAndEdgesParity::test_edges_empty_frame_not_none[pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestSeededProjectionDtypeAndEdgesParity::test_edges_empty_frame_not_none[polars] +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestSeededProjectionDtypeAndEdgesParity::test_pandas_int_bool_dtype_parity +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestSeededProjectionDtypeAndEdgesParity::test_polars_int_bool_dtype_parity diff --git a/reviews/2054/cypher-fast.divergences b/reviews/2054/cypher-fast.divergences new file mode 100644 index 0000000000..540db7c953 --- /dev/null +++ b/reviews/2054/cypher-fast.divergences @@ -0,0 +1,46 @@ +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_empty_match_counts_zero[polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_handles_degenerate_bindings[polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[base_distinct_domain-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[base_distinct_edges-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[empty_no_edges-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[empty_no_matching_nodes-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[messy_distinct_domain-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[messy_distinct_edges-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[messy_end_only_filter-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[messy_start_only_filter-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_h3_fused_two_hop_count_matches_eager_twin_and_pandas[string_ids-polars] +FAILED graphistry/tests/compute/gfql/cypher/test_lowering.py::test_t6_assert_col_stats_helper_fails_loudly +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[0] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[1] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[2] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[3] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[4] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_differential_vs_the_scan_on_random_typed_graphs[5] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_gapped_node_space_builds_facts_and_stays_exact +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[2-8-cudf] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[2-8-pandas] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[2-8-polars] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[3-3-cudf] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[3-3-pandas] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[3-3-polars] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[5-1-cudf] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[5-1-pandas] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[5-1-polars] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[7-2-cudf] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[7-2-pandas] +FAILED graphistry/tests/compute/gfql/index/test_degree_consult.py::test_slice_is_exact_across_domain_shapes[7-2-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_destination_property_projection_dtype_parity[cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_destination_property_projection_dtype_parity[pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_destination_property_projection_dtype_parity[polars] +FAILED graphistry/tests/compute/gfql/test_polars_rows_entity_groupby.py::test_has_label_narrowing_applies_on_reached_collision[polars] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_node_lookup_matches_independent_oracle[cudf] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_node_lookup_matches_independent_oracle[pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_node_lookup_matches_independent_oracle[polars] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_seed_matching_several_nodes_projects_each_seed[cudf] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_seed_matching_several_nodes_projects_each_seed[pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_seed_matching_several_nodes_projects_each_seed[polars] +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestResidentIndexSeededFastPath::test_uint64_int64_id_mix_declines_not_collapses +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestSeededProjectionDtypeAndEdgesParity::test_edges_empty_frame_not_none[pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestSeededProjectionDtypeAndEdgesParity::test_edges_empty_frame_not_none[polars] +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestSeededProjectionDtypeAndEdgesParity::test_pandas_int_bool_dtype_parity +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestSeededProjectionDtypeAndEdgesParity::test_polars_int_bool_dtype_parity diff --git a/reviews/2054/index-hop.divergences b/reviews/2054/index-hop.divergences new file mode 100644 index 0000000000..5289985f9f --- /dev/null +++ b/reviews/2054/index-hop.divergences @@ -0,0 +1,14 @@ +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_destination_property_projection_dtype_parity[pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_node_property_index_duplicate_values_match_scan +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[canonical-distinct-order-limit-suffix-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[fixed-hop-reverse-composition-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is1-directed-projection-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is7-connected-two-hop-bag-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is7-optional-continuation-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[null-and-dtype-parity-pandas] +FAILED graphistry/tests/compute/gfql/index/test_index_gpu_edge_match.py::test_empty_candidate_batch_on_device[polars-gpu] +FAILED graphistry/tests/compute/gfql/index/test_index_gpu_edge_match.py::test_null_bearing_edge_predicate_matches_the_pandas_oracle_on_device[boolean-polars-gpu] +FAILED graphistry/tests/compute/gfql/index/test_index_gpu_edge_match.py::test_null_bearing_edge_predicate_matches_the_pandas_oracle_on_device[float-polars-gpu] +FAILED graphistry/tests/compute/gfql/index/test_index_gpu_edge_match.py::test_null_bearing_edge_predicate_matches_the_pandas_oracle_on_device[int64-polars-gpu] +FAILED graphistry/tests/compute/gfql/index/test_index_gpu_edge_match.py::test_null_bearing_edge_predicate_matches_the_pandas_oracle_on_device[Int64-polars-gpu] +FAILED graphistry/tests/compute/gfql/index/test_index_gpu_edge_match.py::test_null_bearing_edge_predicate_matches_the_pandas_oracle_on_device[string-polars-gpu] diff --git a/reviews/2054/indexed-kernel.divergences b/reviews/2054/indexed-kernel.divergences new file mode 100644 index 0000000000..8d590f0b2d --- /dev/null +++ b/reviews/2054/indexed-kernel.divergences @@ -0,0 +1,27 @@ +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[canonical-distinct-order-limit-suffix-cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[canonical-distinct-order-limit-suffix-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[canonical-distinct-order-limit-suffix-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[fixed-hop-reverse-composition-cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[fixed-hop-reverse-composition-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[fixed-hop-reverse-composition-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is1-directed-projection-cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is1-directed-projection-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is1-directed-projection-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is3-undirected-multiplicity-cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is3-undirected-multiplicity-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is3-undirected-multiplicity-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is7-connected-two-hop-bag-cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is7-connected-two-hop-bag-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is7-connected-two-hop-bag-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is7-optional-continuation-cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is7-optional-continuation-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[is7-optional-continuation-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[null-and-dtype-parity-cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[null-and-dtype-parity-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[null-and-dtype-parity-polars] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[official-no-match-stratum-cudf] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[official-no-match-stratum-pandas] +FAILED graphistry/tests/compute/gfql/index/test_indexed_bindings.py::test_standard_derived_connected_parity[official-no-match-stratum-polars] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_two_alias_projection_parity[MATCH (m:Message {id: 305})-[:HAS_CREATOR]->(p:Person) RETURN m.id AS a, m.id AS b, p.flag AS c-repeated + bool-indexed-pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_two_alias_projection_parity[MATCH (m:Message {id: 305})-[:HAS_CREATOR]->(p:Person) RETURN m.score AS ms, p.score AS ps, m.flag AS mf, p.flag AS pf-int + bool from both aliases-indexed-pandas] +FAILED graphistry/tests/compute/gfql/test_seeded_node_lookup_fastpath.py::test_two_alias_projection_parity[MATCH (m:Message {id: 305})-[r:HAS_CREATOR]->(p:Person) RETURN m.id, m.score, m.flag, m.firstName, r.w, r.eflag, r.type, p.id, p.firstName, p.age, p.score, p.flag-twelve properties across three aliases-indexed-pandas] diff --git a/reviews/2054/native-fast.divergences b/reviews/2054/native-fast.divergences new file mode 100644 index 0000000000..f673c6aab8 --- /dev/null +++ b/reviews/2054/native-fast.divergences @@ -0,0 +1,25 @@ +FAILED graphistry/tests/compute/gfql/lazy/engine/polars/test_chain_alias_column_collision_2039.py::test_destination_alias_marker_replaces_the_colliding_column_like_pandas +FAILED graphistry/tests/compute/gfql/test_endpoint_closure_matrix.py::test_chain_surface_keeps_node_attribute_dtypes[pandas] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_duplicate_node_rows_are_answered_once_each_on_the_native_lookup[cudf] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_duplicate_node_rows_are_answered_once_each_on_the_native_lookup[pandas] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_hop_aliases_overwrite_nonfinal_properties_like_full_path[cudf] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_hop_aliases_overwrite_nonfinal_properties_like_full_path[pandas] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_single_node_alias_layout_matches_the_full_path[False-cudf] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_single_node_alias_layout_matches_the_full_path[False-pandas] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_single_node_alias_layout_matches_the_full_path[True-cudf] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_single_node_alias_layout_matches_the_full_path[True-pandas] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_single_node_alias_overwrites_colliding_property_like_full_path[False-cudf] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_single_node_alias_overwrites_colliding_property_like_full_path[False-pandas] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_single_node_alias_overwrites_colliding_property_like_full_path[True-cudf] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_named_single_node_alias_overwrites_colliding_property_like_full_path[True-pandas] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_stale_indexes_keep_parity_and_are_not_used[seeded typed hop, all named-cudf] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_stale_indexes_keep_parity_and_are_not_used[seeded typed hop, all named-pandas] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_stale_indexes_keep_parity_and_are_not_used[seeded typed hop + rows + select-cudf] +FAILED graphistry/tests/compute/gfql/test_native_seed_resolution_2027.py::test_stale_indexes_keep_parity_and_are_not_used[seeded typed hop + rows + select-pandas] +FAILED graphistry/tests/compute/gfql/test_polars_lane_completeness.py::test_every_polars_mentioning_test_module_is_in_the_lane_or_justified +FAILED graphistry/tests/compute/gfql/test_polars_lane_completeness.py::test_no_module_level_polars_gate_outside_the_lane +FAILED graphistry/tests/compute/gfql/test_seeded_typed_hop_fastpath.py::TestResidentIndexSeededFastPath::test_native_chain_hop_indexed_parity_forward_and_reverse +FAILED graphistry/tests/compute/test_chain.py::test_fast_path_named_full_frame_value_parity[all_forward-pandas] +FAILED graphistry/tests/compute/test_chain.py::test_fast_path_named_full_frame_value_parity[all_reverse-pandas] +FAILED graphistry/tests/compute/test_chain.py::test_fast_path_named_full_frame_value_parity[edge_match-pandas] +FAILED graphistry/tests/compute/test_chain.py::test_fast_path_named_full_frame_value_parity[seed_filtered-pandas] diff --git a/reviews/2054/polars-plain.divergences b/reviews/2054/polars-plain.divergences new file mode 100644 index 0000000000..175c373fee --- /dev/null +++ b/reviews/2054/polars-plain.divergences @@ -0,0 +1 @@ +FAILED graphistry/tests/compute/gfql/routes/test_route_harness.py::test_every_route_serves_most_of_what_it_admits diff --git a/reviews/2054/polars-seeded.divergences b/reviews/2054/polars-seeded.divergences new file mode 100644 index 0000000000..11cafd867e --- /dev/null +++ b/reviews/2054/polars-seeded.divergences @@ -0,0 +1,12 @@ +FAILED graphistry/tests/compute/gfql/routes/test_route_harness.py::test_every_route_serves_most_of_what_it_admits +FAILED graphistry/tests/compute/gfql/test_native_seed_lane_explain.py::test_seeded_typed_hop_served_by_the_resident_indexes_is_explained[polars] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_named_typed_hop_preserves_full_path_tables[seed0-True-False] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_named_typed_hop_preserves_full_path_tables[seed0-True-True] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_named_typed_hop_preserves_full_path_tables[seed1-True-False] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_named_typed_hop_preserves_full_path_tables[seed1-True-True] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_named_typed_hop_preserves_full_path_tables[seed2-True-False] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_named_typed_hop_preserves_full_path_tables[seed2-True-True] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_named_typed_hop_preserves_full_path_tables[seed3-True-False] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_named_typed_hop_preserves_full_path_tables[seed3-True-True] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_property_seed_uses_resident_index[False] +FAILED graphistry/tests/compute/gfql/test_polars_native_seed_resolution.py::test_native_seeded_hop_is_served_from_the_index_and_traced diff --git a/reviews/2054/routes-off-ledger-3213f7d96.txt b/reviews/2054/routes-off-ledger-3213f7d96.txt new file mode 100644 index 0000000000..a5ad7e3ef4 --- /dev/null +++ b/reviews/2054/routes-off-ledger-3213f7d96.txt @@ -0,0 +1,7 @@ +native-fast: 25 ids +polars-seeded: 12 ids +polars-plain: 1 ids +index-hop: 14 ids +indexed-kernel: 27 ids +cypher-fast: 46 ids +all-off: 57 ids