forked from VA602AA-master/VASTKnowledgeGraphVisualization
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcomponents.py
More file actions
71 lines (57 loc) · 2.53 KB
/
Copy pathcomponents.py
File metadata and controls
71 lines (57 loc) · 2.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""
Connected component analysis for the Connected Components panel.
Returns per-component breakdown: size, composition by node type, internal edge count,
and top-K nodes by degree. WCC for all graphs; SCC additionally for directed graphs.
"""
import networkx as nx
from schema import node_type
# How many top-degree nodes to ship per component for the drill-down.
TOP_K_PER_COMPONENT = 5
# Stop reporting per-component detail past this rank — payload control on
# pathological inputs with thousands of singletons. The aggregate count is
# still accurate; only the per-component records are truncated.
MAX_COMPONENT_DETAILS = 200
def _component_record(G, nodes, comp_id):
"""Build the per-component payload for a single component."""
sub = G.subgraph(nodes)
by_type = {}
for n in nodes:
t = node_type(G, n)
by_type[t] = by_type.get(t, 0) + 1
# Degree is computed on the subgraph so it reflects internal connectivity,
# not the node's degree in the whole graph.
deg_iter = sub.degree()
top = sorted(deg_iter, key=lambda x: x[1], reverse=True)[:TOP_K_PER_COMPONENT]
top_degree = [{'id': str(nid), 'degree': int(d)} for nid, d in top]
return {
'id': comp_id,
'size': len(nodes),
'by_type': by_type,
'edges_internal': sub.number_of_edges(),
'top_degree': top_degree,
}
def _summarize(G, components):
"""components is an iterable of node-set; returns the panel-ready summary."""
comps_sorted = sorted(components, key=len, reverse=True)
total_nodes = G.number_of_nodes()
lcc_size = len(comps_sorted[0]) if comps_sorted else 0
records = [
_component_record(G, nodes, i)
for i, nodes in enumerate(comps_sorted[:MAX_COMPONENT_DETAILS])
]
return {
'count': len(comps_sorted),
'lcc_size': lcc_size,
'lcc_fraction': (lcc_size / total_nodes) if total_nodes else 0,
'singletons': sum(1 for nodes in comps_sorted if len(nodes) == 1),
'truncated': len(comps_sorted) > MAX_COMPONENT_DETAILS,
'components': records,
}
def compute_components(G):
"""Connectivity breakdown: weakly connected components always, plus strongly connected
components on directed graphs (per-component records + aggregate counts)."""
wcc = _summarize(G, nx.weakly_connected_components(G) if G.is_directed() else nx.connected_components(G))
result = {'wcc': wcc, 'directed': G.is_directed()}
if G.is_directed():
result['scc'] = _summarize(G, nx.strongly_connected_components(G))
return result