Skip to content

Graph Analytics ​

Purple8 Hyper Graph ships two complementary analytics layers that run directly on the live graph — no separate data warehouse, no ETL pipeline, no external ML cluster.

LayerWhat it doesAPI
Graph AlgorithmsAuthority scoring, path analysis, cluster discovery, link prediction, spatial autocorrelationGET /algorithms/* / MCP graph.*
OLAP Analytics EngineStructural aggregations, distributions, projectionsAnalyticsEngine (Python SDK)

Both layers are available in both self-hosted licensing tiers — Developer (Self-Hosted Free) (50K nodes, 1 MCP agent) and Enterprise / Custom (Self-Hosted) (unlimited nodes/agents, full compliance/HA). Algorithms are feature-complete in both tiers; only scale quotas differ. See Pricing.


Graph Algorithms ​

PageRank — influence & authority scoring ​

PageRank measures how often a node would be visited by a random graph traversal. High-scoring nodes are influential connectors — the most-cited documents, most-trusted knowledge sources, or most-connected entities in your graph.

Algorithm: Power iteration with dangling-node redistribution (Google's original method). Converges when the L1 delta between iterations falls below tolerance.

python
result = engine.pagerank(
    damping=0.85,          # probability of following an edge (default: 0.85)
    max_iterations=100,    # cap on iterations before giving up
    tolerance=1e-6,        # convergence threshold
    top_k=20,              # number of top nodes to surface
)

print(result.converged)    # True/False
print(result.iterations)   # how many rounds it took
for node in result.top_nodes:
    print(node.node_id, node.score)

REST API:

bash
GET /algorithms/pagerank?damping=0.85&top_k=20

Use cases:

  • Rank documents by citation authority in a knowledge graph
  • Score team members by collaboration centrality
  • Find the most-referenced entities in an AI agent's memory graph
  • Prioritise nodes for manual review or enrichment

Community Detection — cluster discovery ​

Community detection partitions the graph into groups of nodes that are more densely connected to each other than to the rest of the graph. Purple8 uses synchronous label propagation — fast (O(E) per pass), parameter-free, and reproducible with a seed.

Returns:

  • Community ID per node
  • Community size distribution
  • Total number of communities
  • Newman-Girvan modularity Q — a score in [-1, 1] where values above 0.3 indicate well-defined clusters
python
result = engine.detect_communities(
    max_iterations=50,     # propagation rounds before stopping
    seed=42,               # optional — makes results reproducible
)

print(result.num_communities)
print(f"Modularity Q: {result.modularity:.4f}")

# Which community does a node belong to?
community_id = result.communities["node_abc"]

# Top communities by size
for c in sorted(result.community_sizes.items(), key=lambda x: -x[1])[:5]:
    print(f"Community {c[0]}: {c[1]} nodes")

REST API:

bash
GET /algorithms/communities?max_iterations=50&seed=42&top_k=10

Use cases:

  • Discover topic clusters in a document or knowledge graph
  • Map organisational clusters from collaboration or reporting relationships
  • Identify customer segments from interaction graphs
  • Detect anomalous isolated subgraphs (communities of size 1)
  • Measure knowledge graph quality — low modularity means weak structure

Betweenness Centrality — critical connector identification ​

Betweenness centrality measures how often a node sits on the shortest path between other pairs of nodes. High-centrality nodes are bridges — remove them and the graph fragments.

Algorithm: Brandes' exact algorithm. Time complexity O(V·E) — suitable for graphs up to ~1M nodes. For very large graphs, use the top_k parameter to limit output without affecting computation.

python
result = engine.betweenness_centrality(
    normalise=True,   # divide by (n-1)(n-2) so scores are in [0, 1]
    top_k=20,
)

for node in result.top_nodes:
    print(node.node_id, f"{node.score:.6f}")

REST API:

bash
GET /algorithms/centrality?normalise=true&top_k=20

Use cases:

  • Find bottleneck nodes in a process or workflow graph
  • Identify critical connectors in an access management graph (nodes that grant access to many paths)
  • Surface bridge entities between otherwise separate knowledge clusters
  • Detect single points of failure in infrastructure dependency graphs

PageRank and Betweenness Centrality aren't only for standalone analytics — they are the right structural-importance signal for retrieval seed ranking in Hybrid Graph-Vector RAG, and a materially stronger one than raw node degree (a plain edge count has no concept of importance propagated through the network).

HybridGraphVectorRetriever's dual-signal seed fusion reads a persisted _pagerank_score node property at query time — an O(1) lookup, not a per-query graph pass — and only falls back to a raw-degree probe if PageRank has never been computed on that graph. To keep the persisted score fresh:

python
result = engine.pagerank(persist=True)   # writes _pagerank_score onto every node

Because PageRank/Betweenness are full-graph, batch operations, run this periodically or on a data-change cadence rather than per-query. The convenience helper purple8_graph.hybrid_rag.refresh_centrality_scores(engine) wraps this (and, optionally, Betweenness persistence) for exactly that purpose, and is pre-registered as a named callback ("refresh_centrality_scores") with the scheduler — attach a CRON or INTERVAL trigger to it via the scheduler API to keep scores current with no custom code:

bash
POST /scheduler/triggers
{
  "trigger_type": "cron",
  "action_type": "callback",
  "callback_name": "refresh_centrality_scores",
  "cron_expression": "0 */6 * * *",
  "payload": {"include_betweenness": false}
}

Nothing runs automatically until a trigger is attached — retrieval quality degrades gracefully to the previous degree-only behavior on a graph where this has never been enabled.

When to enable graph fusion — and when NOT to ​

HybridGraphVectorRetriever and HybridRAGPipeline default to HybridRetrievalMode.VECTOR_FIRST (fusion_alpha=0.2) — not BALANCED. This is a deliberate, measured decision, not a conservative placeholder waiting to be tuned up.

Why graph fusion is opt-in, not default: PageRank-weighted seed fusion only improves retrieval when the graph's edges encode information orthogonal to the embedding space already being searched — real authority, citation, shared-entity, or curated hierarchy structure. It regresses retrieval quality when the graph was itself derived from the same embedding space it's meant to complement (for example, a CO_RELEVANT-style graph built by linking each query's ANN/kNN top-K results to each other — a common pattern for giving PageRank something to compute on a corpus with no native graph structure).

The mechanism: such a graph is a lossy re-encoding of the same vector geometry. Dense regions of the embedding space naturally produce high-in-degree "hub" nodes; PageRank concentrates score on those hubs because they are globally central, not because they are relevant to any specific query. Fusing that query-independent signal into a query-dependent ranking systematically pushes true positives below generically-popular hubs.

Measured impact (100K real MS MARCO passages, 200 real queries, graph built from ANN-kNN windows):

fusion_alphaMRR@10nDCG@10Recall@100
0.0 (baseline, pure vector)0.7560.7950.973
0.10.719 (-4.9%)0.770 (-3.2%)0.982 (+1.0%)
0.2 (VECTOR_FIRST default)0.619 (-18.1%)0.695 (-12.5%)0.978 (+0.5%)
0.5 (BALANCED)0.143 (-81.1%)0.260 (-67.3%)0.980 (+0.8%)

Ranking quality degrades monotonically as fusion_alpha rises, while Recall@100 barely moves — the candidate pool is essentially unchanged; only its order is corrupted. Full reproduction: benchmarks/pagerank_msmarco_bench.py, results in benchmarks/results/pagerank_msmarco_bench_100000.json.

Decision checklist — only raise fusion_alpha above the VECTOR_FIRST default (i.e. use BALANCED or GRAPH_FIRST, or an explicit fusion_alpha override) when you can answer "yes" to both:

  1. Are your graph's edges independent of the embeddings? Real citations, a CRM org chart, curated ADVANCED_TO Journey transitions, AEC structural adjacency, explicit entity co-references extracted separately from the embedding model — yes. Edges built by linking nearest neighbours in the same vector space you're searching (kNN graphs, "co-relevant" links derived from ANN results) — no.
  2. Have you validated it on this corpus? Run rag.tune_collection (or the checklist above manually) on a held-out query sample and confirm fusion_alpha > 0 does not regress MRR/nDCG relative to fusion_alpha=0.0. Because rag.profile_collection derives its baseline fusion_alpha from edge density rather than edge provenance, always verify on a held-out sample with rag.tune_collection before promoting a higher fusion_alpha to production.

If you built graph edges purely to give PageRank something to rank over (no native graph structure existed otherwise), keep fusion_alpha=0.0 / mode=VECTOR_FIRST — the graph gives PageRank something to compute, but nothing informative to add to ranking.


Shortest Path — weighted path queries ​

Dijkstra weighted shortest path between any two nodes. Supports optional edge type filtering and a custom weight property.

python
result = engine.shortest_path(
    source_id="node_a",
    target_id="node_z",
    weight_property="latency_ms",   # optional — uniform weight if omitted
    edge_type="DEPENDS_ON",         # optional — restricts traversal to this edge type
)

print(result.found)        # True/False
print(result.hops)         # number of edges in the path
print(result.total_cost)   # sum of weight_property along the path
print(result.path)         # list of node IDs from source to target

REST API:

bash
GET /nodes/{source_id}/shortest-path?target=node_z&weight_property=latency_ms

Use cases:

  • Find the lowest-latency path through an infrastructure graph
  • Determine the chain of approvals between a user and a resource (access management)
  • Shortest dependency resolution path in a software component graph
  • Minimum-cost routing in a logistics or supply chain graph

OLAP Analytics Engine ​

The AnalyticsEngine runs read-only OLAP-style computations over a live GraphEngine. All methods iterate over the storage layer — no data export required.

python
from purple8_graph import GraphEngine, AnalyticsEngine

engine = GraphEngine(storage)
ax = AnalyticsEngine()

Node & edge aggregations ​

python
# How many nodes of each label exist?
ax.label_counts(engine)
# → {"Document": 12400, "Person": 830, "Concept": 4200}

# How many edges of each type?
ax.edge_type_counts(engine)
# → {"AUTHORED_BY": 8900, "REFERENCES": 31000, "TAGGED_WITH": 19200}

# Out-degree frequency histogram with min / max / mean
ax.degree_distribution(engine)
# → {"data": {0: 120, 1: 430, 2: 890, ...}, "min": 0, "max": 147, "mean": 4.3}

# Top-K nodes by degree (out / in / both)
ax.top_k_nodes_by_degree(engine, k=10, direction="out")
# → [("node_abc", 147), ("node_xyz", 134), ...]

# Average out-degree
ax.avg_degree(engine)
# → 4.3

# Graph density — ratio of actual to maximum possible directed edges
ax.density(engine)
# → 0.00031

Structural analytics ​

python
# Weakly-connected components — find isolated subgraphs
result = ax.connected_components(engine)
# result.data → {"num_components": 3, "component_sizes": [12400, 8, 1], ...}

Temporal analytics ​

python
# Group nodes (or edges) by a date property, bucketed by day / month / year
result = ax.temporal_grouping(
    engine,
    property_name="created_at",
    interval="month",
    entity="node",
)
# result.data → {"2026-01": 340, "2026-02": 890, "2026-03": 1200}

Property analytics ​

python
# Numeric aggregation over any node or edge property
result = ax.property_stats(engine, property_name="score", entity="node")
# result.data → {"min": 0.01, "max": 0.99, "mean": 0.54, "count": 12400}

Graph projection (for ML / BI pipelines) ​

GraphProjection extracts structured representations from the live graph for downstream use — pandas, networkx, PyTorch Geometric, or any BI tool.

python
from purple8_graph import GraphProjection

proj = GraphProjection()

# List of dicts for all "Document" nodes — ready for pandas
docs = proj.project_label(engine, label="Document")
df = pd.DataFrame(docs)

# Adjacency list: {node_id: [neighbour_ids]}
adj = proj.to_adjacency_list(engine)

# Edge list: [(source_id, target_id, edge_type)]
edges = proj.to_edge_list(engine)

# Node feature matrix: {node_id: {property: value, ...}}
features = proj.to_node_feature_matrix(engine, label="Document")

Additional Built-In Graph Algorithms ​

All algorithms in core/algorithms.py operate directly on BrickCoreStorage (Purple8 Cortex) iterators (iter_nodes() / iter_edges()) without materialising the full graph into Python lists, and all return result objects with .to_dict() serialisation:

AlgorithmSDK / FunctionMCP ToolNotes
PageRankengine.pagerank()graph.pagerankPower-iteration with dangling-node redistribution
Temporal PageRanktemporal_pagerank()—Time-decayed edge weights
Dynamic PageRankdynamic_pagerank()—Incremental re-propagation on graph deltas
Community detectionengine.detect_communities()graph.communitiesLouvain-style label propagation + Newman-Girvan modularity
Dynamic communitiesdynamic_detect_communities()—Delta-aware re-clustering
Betweenness centralityengine.betweenness_centrality()graph.centralityBrandes' exact algorithm, normalised 0–1
Shortest pathengine.shortest_path()graph.shortest_pathWeighted Dijkstra
Link predictionengine.predict_links()graph.link_predictionJaccard, Adamic-Adar, Resource Allocation, Preferential Attachment, Common Neighbours, PPR
Spatial autocorrelationmorans_i() / local_morans_i()graph.morans_i, graph.local_morans_iGlobal and local Moran's I over graph weights
Triangle countingcount_triangles()—Per-node clustering coefficients
Biconnected componentsbiconnected_components()—Tarjan DFS
Bridge detectionfind_bridges()—Single points of failure
Bipartite detection & matchingis_bipartite() / bipartite_matching()—BFS 2-colouring + Hopcroft-Karp max matching
Minimum spanning treeminimum_spanning_tree()—Kruskal + union-find

Performance guidelines ​

AlgorithmComplexityPractical limit
PageRankO(V + E) per iteration10M+ nodes in seconds
Community detectionO(E) per pass10M+ nodes in seconds
Betweenness centralityO(V·E)~500K nodes comfortably; sample for larger
Shortest path (Dijkstra)O((V + E) log V)0.79 ms p50 @ 10K nodes; real-time for most graphs
OLAP aggregationsO(V) or O(E)Linear — handles full graph size

Running analytics in production

On Enterprise / Custom (Self-Hosted) deployments with Raft sharding or WAL-streaming replicas, schedule heavy analytics (such as full-graph betweenness centrality) during off-peak hours or run them against a read replica to avoid competing with live query traffic.


Web UI ​

The Analytics tab in the LCNC admin console (/lcnc/graph) provides a no-code interface for graph algorithms:

  • PageRank — configurable damping factor and top-K, bar chart of top nodes
  • Communities — run detection, see modularity score, browse community sizes
  • Centrality — normalised betweenness scores, ranked node list

REST API and MCP access (graph.pagerank, graph.communities, graph.centrality, graph.link_prediction, graph.shortest_path, graph.morans_i, graph.local_morans_i) are available in both Developer (Self-Hosted Free) and Enterprise / Custom (Self-Hosted) tiers — only scale quotas differ.

Purple8 Hyper Graph v0.72.0 · AI-Native Execution Infrastructure · NVIDIA Inception Member · Red Hat Technology Partner.