6b7e6b44f1
gh-pages / build (push) Waiting to run
Python Publish (pypi) / Upload release to PyPI (push) Waiting to run
Spellcheck / spellcheck (push) Waiting to run
Python Build and Type Check / python-ci (ubuntu-latest, 3.11) (push) Has been cancelled
Python Build and Type Check / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Build and Type Check / python-ci (windows-latest, 3.11) (push) Has been cancelled
Python Build and Type Check / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Integration Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Integration Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Notebook Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Notebook Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Smoke Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Smoke Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Unit Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Unit Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
167 lines
5.2 KiB
Python
167 lines
5.2 KiB
Python
# Copyright (c) 2024 Microsoft Corporation.
|
|
# Licensed under the MIT License
|
|
|
|
"""Side-by-side tests comparing NetworkX connected components with DataFrame-based implementation."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import networkx as nx
|
|
import pandas as pd
|
|
from graphrag.graphs.connected_components import (
|
|
connected_components,
|
|
largest_connected_component,
|
|
)
|
|
|
|
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
|
|
|
|
|
def _load_fixture() -> pd.DataFrame:
|
|
"""Load the realistic graph fixture as a relationships DataFrame."""
|
|
with open(FIXTURES_DIR / "graph.json") as f:
|
|
data = json.load(f)
|
|
return pd.DataFrame(data["edges"])
|
|
|
|
|
|
def _make_relationships(*edges: tuple[str, str, float]) -> pd.DataFrame:
|
|
"""Build a relationships DataFrame from (source, target, weight) tuples."""
|
|
return pd.DataFrame([{"source": s, "target": t, "weight": w} for s, t, w in edges])
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# NetworkX reference helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _nx_connected_components(relationships: pd.DataFrame) -> list[set[str]]:
|
|
"""Compute connected components using NetworkX."""
|
|
graph = nx.from_pandas_edgelist(relationships, source="source", target="target")
|
|
return sorted(
|
|
[set(c) for c in nx.connected_components(graph)],
|
|
key=len,
|
|
reverse=True,
|
|
)
|
|
|
|
|
|
def _nx_largest_connected_component(relationships: pd.DataFrame) -> set[str]:
|
|
"""Return the LCC using NetworkX."""
|
|
components = _nx_connected_components(relationships)
|
|
return components[0] if components else set()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Simple topology tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_single_component():
|
|
"""Fully connected graph should have one component."""
|
|
rels = _make_relationships(
|
|
("A", "B", 1.0),
|
|
("B", "C", 1.0),
|
|
("A", "C", 1.0),
|
|
)
|
|
nx_components = _nx_connected_components(rels)
|
|
df_components = connected_components(rels)
|
|
assert len(nx_components) == len(df_components) == 1
|
|
assert nx_components[0] == df_components[0]
|
|
|
|
|
|
def test_two_components():
|
|
"""Two disconnected pairs should give two components."""
|
|
rels = _make_relationships(
|
|
("A", "B", 1.0),
|
|
("C", "D", 1.0),
|
|
)
|
|
nx_components = _nx_connected_components(rels)
|
|
df_components = connected_components(rels)
|
|
assert len(nx_components) == len(df_components) == 2
|
|
assert {frozenset(c) for c in nx_components} == {
|
|
frozenset(c) for c in df_components
|
|
}
|
|
|
|
|
|
def test_three_components_lcc():
|
|
"""LCC should pick the largest of three components."""
|
|
rels = _make_relationships(
|
|
("A", "B", 1.0),
|
|
("A", "C", 1.0),
|
|
("A", "D", 1.0),
|
|
("X", "Y", 1.0),
|
|
("P", "Q", 1.0),
|
|
)
|
|
nx_lcc = _nx_largest_connected_component(rels)
|
|
df_lcc = largest_connected_component(rels)
|
|
assert nx_lcc == df_lcc
|
|
assert df_lcc == {"A", "B", "C", "D"}
|
|
|
|
|
|
def test_star_topology():
|
|
"""Star should be a single component."""
|
|
rels = _make_relationships(
|
|
("hub", "a", 1.0),
|
|
("hub", "b", 1.0),
|
|
("hub", "c", 1.0),
|
|
)
|
|
df_lcc = largest_connected_component(rels)
|
|
nx_lcc = _nx_largest_connected_component(rels)
|
|
assert df_lcc == nx_lcc == {"hub", "a", "b", "c"}
|
|
|
|
|
|
def test_duplicate_edges():
|
|
"""Duplicate edges should not affect component membership."""
|
|
rels = _make_relationships(
|
|
("A", "B", 1.0),
|
|
("A", "B", 2.0),
|
|
("C", "D", 1.0),
|
|
)
|
|
nx_components = _nx_connected_components(rels)
|
|
df_components = connected_components(rels)
|
|
assert len(nx_components) == len(df_components) == 2
|
|
assert {frozenset(c) for c in nx_components} == {
|
|
frozenset(c) for c in df_components
|
|
}
|
|
|
|
|
|
def test_empty_relationships():
|
|
"""Empty edge list should produce no components."""
|
|
rels = pd.DataFrame(columns=["source", "target", "weight"])
|
|
assert connected_components(rels) == []
|
|
assert largest_connected_component(rels) == set()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Realistic fixture tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_fixture_component_count():
|
|
"""Component count should match NetworkX on the realistic fixture."""
|
|
rels = _load_fixture()
|
|
nx_components = _nx_connected_components(rels)
|
|
df_components = connected_components(rels)
|
|
assert len(df_components) == len(nx_components)
|
|
|
|
|
|
def test_fixture_component_sizes():
|
|
"""Component sizes (sorted desc) should match NetworkX."""
|
|
rels = _load_fixture()
|
|
nx_sizes = [len(c) for c in _nx_connected_components(rels)]
|
|
df_sizes = [len(c) for c in connected_components(rels)]
|
|
assert df_sizes == nx_sizes
|
|
|
|
|
|
def test_fixture_lcc_membership():
|
|
"""LCC membership should be identical to NetworkX."""
|
|
rels = _load_fixture()
|
|
nx_lcc = _nx_largest_connected_component(rels)
|
|
df_lcc = largest_connected_component(rels)
|
|
assert df_lcc == nx_lcc
|
|
|
|
|
|
def test_fixture_lcc_size():
|
|
"""LCC should contain 535 nodes (known from the fixture)."""
|
|
rels = _load_fixture()
|
|
lcc = largest_connected_component(rels)
|
|
assert len(lcc) == 535
|