chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
from .cycle_ratio import *
|
||||
from .degree import *
|
||||
from .hypercoreness import *
|
||||
from .s_centrality import *
|
||||
from .vector_centrality import *
|
||||
@@ -0,0 +1,193 @@
|
||||
import copy
|
||||
import itertools
|
||||
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
__all__ = [
|
||||
"my_all_shortest_paths",
|
||||
"getandJudgeSimpleCircle",
|
||||
"getSmallestCycles",
|
||||
"StatisticsAndCalculateIndicators",
|
||||
"cycle_ratio_centrality",
|
||||
]
|
||||
|
||||
|
||||
def my_all_shortest_paths(G, source, target):
|
||||
pred = eg.predecessor(G, source)
|
||||
if target not in pred:
|
||||
raise eg.EasyGraphNoPath(
|
||||
f"Target {target} cannot be reached from given sources"
|
||||
)
|
||||
sources = {source}
|
||||
seen = {target}
|
||||
stack = [[target, 0]]
|
||||
top = 0
|
||||
while top >= 0:
|
||||
node, i = stack[top]
|
||||
if node in sources:
|
||||
yield [p for p, n in reversed(stack[: top + 1])]
|
||||
if len(pred[node]) > i:
|
||||
stack[top][1] = i + 1
|
||||
next = pred[node][i]
|
||||
if next in seen:
|
||||
continue
|
||||
else:
|
||||
seen.add(next)
|
||||
top += 1
|
||||
if top == len(stack):
|
||||
stack.append([next, 0])
|
||||
else:
|
||||
stack[top][:] = [next, 0]
|
||||
else:
|
||||
seen.discard(node)
|
||||
top -= 1
|
||||
|
||||
|
||||
def getandJudgeSimpleCircle(objectList, G): # 这里添加 G 作为参数
|
||||
numEdge = 0
|
||||
for eleArr in list(itertools.combinations(objectList, 2)):
|
||||
if G.has_edge(eleArr[0], eleArr[1]):
|
||||
numEdge += 1
|
||||
if numEdge != len(objectList):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def getSmallestCycles(G, NodeGirth, Coreness, DEF_IMPOSSLEN):
|
||||
NodeList = list(G.nodes)
|
||||
NodeList.sort()
|
||||
# setp 1
|
||||
curCyc = list()
|
||||
for ix in NodeList[:-2]: # v1
|
||||
if NodeGirth[ix] == 0:
|
||||
continue
|
||||
curCyc.append(ix)
|
||||
for jx in NodeList[NodeList.index(ix) + 1 : -1]: # v2
|
||||
if NodeGirth[jx] == 0:
|
||||
continue
|
||||
curCyc.append(jx)
|
||||
if G.has_edge(ix, jx):
|
||||
for kx in NodeList[NodeList.index(jx) + 1 :]: # v3
|
||||
if NodeGirth[kx] == 0:
|
||||
continue
|
||||
if G.has_edge(kx, ix):
|
||||
curCyc.append(kx)
|
||||
if G.has_edge(kx, jx):
|
||||
yield tuple(curCyc) # 这里改为 yield
|
||||
for i in curCyc:
|
||||
NodeGirth[i] = 3
|
||||
curCyc.pop()
|
||||
curCyc.pop()
|
||||
curCyc.pop()
|
||||
|
||||
# setp 2
|
||||
ResiNodeList = [] # Residual Node List
|
||||
for nod in NodeList:
|
||||
if NodeGirth[nod] == DEF_IMPOSSLEN:
|
||||
ResiNodeList.append(nod)
|
||||
if len(ResiNodeList) == 0:
|
||||
return
|
||||
else:
|
||||
visitedNodes = dict.fromkeys(ResiNodeList, set())
|
||||
for nod in ResiNodeList:
|
||||
if Coreness[nod] == 2 and NodeGirth[nod] < DEF_IMPOSSLEN:
|
||||
continue
|
||||
for nei in list(G.neighbors(nod)):
|
||||
if Coreness[nei] == 2 and NodeGirth[nei] < DEF_IMPOSSLEN:
|
||||
continue
|
||||
if not nei in visitedNodes.keys() or not nod in visitedNodes[nei]:
|
||||
visitedNodes[nod].add(nei)
|
||||
if nei not in visitedNodes.keys():
|
||||
visitedNodes[nei] = set([nod])
|
||||
else:
|
||||
visitedNodes[nei].add(nod)
|
||||
if Coreness[nei] == 2 and NodeGirth[nei] < DEF_IMPOSSLEN:
|
||||
continue
|
||||
G.remove_edge(nod, nei)
|
||||
if eg.single_source_dijkstra(G, nod, nei):
|
||||
for path in my_all_shortest_paths(G, nod, nei):
|
||||
lenPath = len(path)
|
||||
path.sort()
|
||||
yield tuple(path) # 这里改为 yield
|
||||
for i in path:
|
||||
if NodeGirth[i] > lenPath:
|
||||
NodeGirth[i] = lenPath
|
||||
G.add_edge(nod, nei)
|
||||
|
||||
|
||||
def StatisticsAndCalculateIndicators(SmallestCyclesOfNodes, CycLenDict, SmallestCycles):
|
||||
NumSmallCycles = len(SmallestCycles)
|
||||
for cyc in SmallestCycles:
|
||||
lenCyc = len(cyc)
|
||||
CycLenDict[lenCyc] += 1
|
||||
for nod in cyc:
|
||||
SmallestCyclesOfNodes[nod].add(cyc)
|
||||
CycleRatio = {} # 这里将 CycleRatio 作为局部变量
|
||||
for objNode, SmaCycs in SmallestCyclesOfNodes.items():
|
||||
if len(SmaCycs) == 0:
|
||||
continue
|
||||
cycleNeighbors = set()
|
||||
NeiOccurTimes = {}
|
||||
for cyc in SmaCycs:
|
||||
for n in cyc:
|
||||
if n in NeiOccurTimes.keys():
|
||||
NeiOccurTimes[n] += 1
|
||||
else:
|
||||
NeiOccurTimes[n] = 1
|
||||
cycleNeighbors = cycleNeighbors.union(cyc)
|
||||
cycleNeighbors.remove(objNode)
|
||||
del NeiOccurTimes[objNode]
|
||||
sum = 0
|
||||
for nei in cycleNeighbors:
|
||||
sum += float(NeiOccurTimes[nei]) / len(SmallestCyclesOfNodes[nei])
|
||||
CycleRatio[objNode] = sum + 1
|
||||
return CycleRatio
|
||||
|
||||
|
||||
def cycle_ratio_centrality(G):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
G : eg.Graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
cycle ratio centrality of each node in G : dict
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> G = eg.Graph()
|
||||
>>> G.add_edges([(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (1, 5), (2, 5)])
|
||||
>>> cycle_ratio_centrality(G)
|
||||
{1: 4.083333333333333, 2: 4.083333333333333, 3: 2.6666666666666665, 4: 2.6666666666666665, 5: 1.5}
|
||||
|
||||
"""
|
||||
NumNode = G.number_of_nodes() # update
|
||||
DEF_IMPOSSLEN = NumNode + 1 # Impossible simple cycle length
|
||||
NodeGirth = dict()
|
||||
CycLenDict = dict()
|
||||
|
||||
SmallestCyclesOfNodes = {}
|
||||
removeNodes = set()
|
||||
Coreness = dict(zip(list(G.nodes), eg.k_core(G)))
|
||||
for i in list(G.nodes):
|
||||
SmallestCyclesOfNodes[i] = set()
|
||||
if G.degree()[i] <= 1 or Coreness[i] <= 1:
|
||||
NodeGirth[i] = 0
|
||||
removeNodes.add(i)
|
||||
else:
|
||||
NodeGirth[i] = DEF_IMPOSSLEN
|
||||
|
||||
G.remove_nodes_from(removeNodes)
|
||||
|
||||
NodeNum = G.number_of_nodes()
|
||||
for i in range(3, NodeNum + 2):
|
||||
CycLenDict[i] = 0
|
||||
|
||||
SmallestCycles = set(getSmallestCycles(G, NodeGirth, Coreness, DEF_IMPOSSLEN))
|
||||
cycle_ratio = StatisticsAndCalculateIndicators(
|
||||
SmallestCyclesOfNodes, CycLenDict, SmallestCycles
|
||||
)
|
||||
return cycle_ratio
|
||||
@@ -0,0 +1,28 @@
|
||||
__all__ = ["hyepergraph_degree_centrality"]
|
||||
|
||||
|
||||
def hyepergraph_degree_centrality(G):
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : eg.Hypergraph
|
||||
The target hypergraph
|
||||
|
||||
Returns
|
||||
----------
|
||||
degree centrality of each node in G : dict
|
||||
|
||||
"""
|
||||
res = {}
|
||||
node_list = G.v
|
||||
# Get hyperedge list
|
||||
edge_list = G.e[0]
|
||||
for node in node_list:
|
||||
res[node] = 0
|
||||
|
||||
for e in edge_list:
|
||||
for n in e:
|
||||
res[n] += 1
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,351 @@
|
||||
from itertools import compress
|
||||
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
|
||||
|
||||
__all__ = ["size_independent_hypercoreness", "frequency_based_hypercoreness"]
|
||||
|
||||
|
||||
def size_independent_hypercoreness(h):
|
||||
"""The size_independent_hypercoreness of nodes in hypergraph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : eg.Hypergraph.
|
||||
|
||||
|
||||
Returns
|
||||
----------
|
||||
dict
|
||||
Centrality, where keys are node IDs and values are lists of centralities.
|
||||
|
||||
References
|
||||
----------
|
||||
Mancastroppa, M., Iacopini, I., Petri, G. et al. Hyper-cores promote localization and efficient seeding in higher-order processes. Nat Commun 14, 6223 (2023). https://doi.org/10.1038/s41467-023-41887-2.
|
||||
|
||||
"""
|
||||
e_list = h.e[0]
|
||||
initial_node_num = h.num_v
|
||||
data = [e_list[i] for i in range(len(e_list)) if len(e_list[i]) > 1]
|
||||
|
||||
data.sort(key=len)
|
||||
L = len(data)
|
||||
size_max = len(data[L - 1])
|
||||
|
||||
size = list([len(data[j]) for j in range(L)])
|
||||
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=data)
|
||||
IDX = list(range(0, X.num_v))
|
||||
|
||||
M = range(2, size_max + 1)
|
||||
k_step = 1
|
||||
K = range(1, 1200, k_step)
|
||||
k_shell_dict = {}
|
||||
idx_orig = IDX
|
||||
|
||||
IDX_size = range(len(size))
|
||||
k_max = np.zeros(len(M))
|
||||
|
||||
for j in idx_orig:
|
||||
k_shell_dict[j] = np.zeros(len(M))
|
||||
|
||||
for x in range(len(M)):
|
||||
m = M[x]
|
||||
|
||||
D = np.zeros(len(K))
|
||||
|
||||
# consider only hyperedges of size >=m
|
||||
idx_size = list(
|
||||
compress(IDX_size, np.greater_equal(size, m * np.ones(len(size))))
|
||||
)
|
||||
int_sel = list([data[i] for i in idx_size])
|
||||
# build hypergraph with only interactions of size >=m
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=int_sel)
|
||||
node_set = set()
|
||||
for sublist in int_sel:
|
||||
for element in sublist:
|
||||
node_set.add(element)
|
||||
IDX = list(node_set)
|
||||
# IDX_e = list(X.e[0])
|
||||
|
||||
for y in range(len(K)):
|
||||
kk = K[y]
|
||||
|
||||
d_tot_m = np.zeros(len(IDX))
|
||||
prev_shell = IDX
|
||||
|
||||
for i in range(len(IDX)):
|
||||
d_tot_m[i] = X.degree_node[IDX[i]]
|
||||
|
||||
idx_n_remove = list(
|
||||
compress(IDX, np.greater(kk * np.ones(len(d_tot_m)), d_tot_m))
|
||||
) # nodes with degree<k are removed
|
||||
# X.remove_nodes_from(idx_n_remove)
|
||||
now_e_list = X.e[0]
|
||||
new_e_list = []
|
||||
for e in now_e_list:
|
||||
new_e = []
|
||||
for n in e:
|
||||
if n not in idx_n_remove:
|
||||
new_e.append(n)
|
||||
if len(new_e) > 0:
|
||||
new_e_list.append(new_e)
|
||||
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=new_e_list)
|
||||
|
||||
IDX_e = list(range(0, len(X.e[0])))
|
||||
|
||||
sizes = [
|
||||
len(X.e[0][i]) for i in IDX_e
|
||||
] # hyperedges with size <m are removed
|
||||
idx_e_remove = [IDX_e[i] for i in range(len(IDX_e)) if sizes[i] < m]
|
||||
now_e_list = X.e[0]
|
||||
new_e_list = []
|
||||
for i in range(len(now_e_list)):
|
||||
if i not in idx_e_remove:
|
||||
new_e_list.append(now_e_list[i])
|
||||
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=new_e_list)
|
||||
|
||||
node_set = set()
|
||||
for sublist in X.e[0]:
|
||||
for element in sublist:
|
||||
node_set.add(element)
|
||||
IDX = list(node_set)
|
||||
|
||||
while len(idx_n_remove) > 0 or len(idx_e_remove) > 0:
|
||||
d_tot_m = np.zeros(len(IDX))
|
||||
|
||||
for i in range(len(IDX)):
|
||||
d_tot_m[i] = X.degree_node[IDX[i]]
|
||||
|
||||
idx_n_remove = list(
|
||||
compress(IDX, np.greater(kk * np.ones(len(d_tot_m)), d_tot_m))
|
||||
) # nodes with degree<k are removed
|
||||
# X.remove_nodes_from(idx_n_remove)
|
||||
now_e_list = X.e[0]
|
||||
new_e_list = []
|
||||
for e in now_e_list:
|
||||
new_e = []
|
||||
for n in e:
|
||||
if n not in idx_n_remove:
|
||||
new_e.append(n)
|
||||
if len(new_e) > 0:
|
||||
new_e_list.append(new_e)
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=new_e_list)
|
||||
|
||||
IDX_e = list(range(len(X.e[0])))
|
||||
sizes = [
|
||||
len(X.e[0][i]) for i in IDX_e
|
||||
] # hyperedges with size <m are removed
|
||||
|
||||
idx_e_remove = [IDX_e[i] for i in range(len(IDX_e)) if sizes[i] < m]
|
||||
now_e_list = X.e[0]
|
||||
new_e_list = []
|
||||
for i in range(len(now_e_list)):
|
||||
if i not in idx_e_remove:
|
||||
new_e_list.append(now_e_list[i])
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=new_e_list)
|
||||
|
||||
node_set = set()
|
||||
for sublist in X.e[0]:
|
||||
for element in sublist:
|
||||
node_set.add(element)
|
||||
IDX = list(node_set)
|
||||
|
||||
shell_kk = list(sorted(set(prev_shell) - set(IDX)))
|
||||
for j in shell_kk:
|
||||
# if j not in idx_n_remove:
|
||||
# continue
|
||||
k_shell_dict[j][x] = kk - k_step
|
||||
|
||||
node_set = set()
|
||||
for sublist in X.e[0]:
|
||||
for element in sublist:
|
||||
node_set.add(element)
|
||||
IDX = list(node_set)
|
||||
|
||||
D[y] = len(node_set)
|
||||
if y > 0:
|
||||
if D[y] == 0 and D[y - 1] != 0:
|
||||
# maximum connectivity at order m
|
||||
k_max[x] = kk - k_step
|
||||
# stop the decomposition when the (k,m)-core is empty
|
||||
if D[y] == 0:
|
||||
break
|
||||
|
||||
# size-independent hypercoreness
|
||||
R_dict = {}
|
||||
for y in k_shell_dict:
|
||||
R_dict[y] = sum(np.array(k_shell_dict[y]) / np.array(k_max))
|
||||
|
||||
return R_dict
|
||||
|
||||
|
||||
def frequency_based_hypercoreness(h):
|
||||
r"""The frequency-based hypercoreness of nodes in hypergraph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : easygraph.Hypergraph
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict : Centrality, where keys are node IDs and values are lists of centralities.
|
||||
|
||||
References
|
||||
----------
|
||||
Mancastroppa, M., Iacopini, I., Petri, G. et al. Hyper-cores promote localization and efficient seeding in higher-order processes. Nat Commun 14, 6223 (2023). https://doi.org/10.1038/s41467-023-41887-2
|
||||
|
||||
"""
|
||||
e_list = h.e[0]
|
||||
initial_node_num = h.num_v
|
||||
data = [e_list[i] for i in range(len(e_list)) if len(e_list[i]) > 1]
|
||||
|
||||
data.sort(key=len)
|
||||
L = len(data)
|
||||
size_max = len(data[L - 1])
|
||||
|
||||
size = list([len(data[j]) for j in range(L)])
|
||||
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=data)
|
||||
IDX = list(range(0, X.num_v))
|
||||
|
||||
M = range(2, size_max + 1)
|
||||
k_step = 1
|
||||
K = range(1, 1200, k_step)
|
||||
k_shell_dict = {}
|
||||
idx_orig = IDX
|
||||
|
||||
IDX_size = range(len(size))
|
||||
k_max = np.zeros(len(M))
|
||||
|
||||
for j in idx_orig:
|
||||
k_shell_dict[j] = np.zeros(len(M))
|
||||
|
||||
for x in range(len(M)):
|
||||
m = M[x]
|
||||
|
||||
D = np.zeros(len(K))
|
||||
# consider only hyperedges of size >=m
|
||||
idx_size = list(
|
||||
compress(IDX_size, np.greater_equal(size, m * np.ones(len(size))))
|
||||
)
|
||||
int_sel = list([data[i] for i in idx_size])
|
||||
# build hypergraph with only interactions of size >=m
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=int_sel)
|
||||
node_set = set()
|
||||
for sublist in int_sel:
|
||||
for element in sublist:
|
||||
node_set.add(element)
|
||||
IDX = list(node_set)
|
||||
|
||||
for y in range(len(K)):
|
||||
kk = K[y]
|
||||
|
||||
d_tot_m = np.zeros(len(IDX))
|
||||
prev_shell = IDX
|
||||
|
||||
for i in range(len(IDX)):
|
||||
d_tot_m[i] = X.degree_node[IDX[i]]
|
||||
|
||||
idx_n_remove = list(
|
||||
compress(IDX, np.greater(kk * np.ones(len(d_tot_m)), d_tot_m))
|
||||
) # nodes with degree<k are removed
|
||||
now_e_list = X.e[0]
|
||||
new_e_list = []
|
||||
for e in now_e_list:
|
||||
new_e = []
|
||||
for n in e:
|
||||
if n not in idx_n_remove:
|
||||
new_e.append(n)
|
||||
if len(new_e) > 0:
|
||||
new_e_list.append(new_e)
|
||||
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=new_e_list)
|
||||
|
||||
IDX_e = list(range(0, len(X.e[0])))
|
||||
|
||||
# hyperedges with size <m are removed
|
||||
sizes = [len(X.e[0][i]) for i in IDX_e]
|
||||
idx_e_remove = [IDX_e[i] for i in range(len(IDX_e)) if sizes[i] < m]
|
||||
now_e_list = X.e[0]
|
||||
new_e_list = []
|
||||
for i in range(len(now_e_list)):
|
||||
if i not in idx_e_remove:
|
||||
new_e_list.append(now_e_list[i])
|
||||
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=new_e_list)
|
||||
|
||||
node_set = set()
|
||||
for sublist in X.e[0]:
|
||||
for element in sublist:
|
||||
node_set.add(element)
|
||||
IDX = list(node_set)
|
||||
|
||||
while len(idx_n_remove) > 0 or len(idx_e_remove) > 0:
|
||||
d_tot_m = np.zeros(len(IDX))
|
||||
|
||||
for i in range(len(IDX)):
|
||||
d_tot_m[i] = X.degree_node[IDX[i]]
|
||||
# nodes with degree<k are removed
|
||||
idx_n_remove = list(
|
||||
compress(IDX, np.greater(kk * np.ones(len(d_tot_m)), d_tot_m))
|
||||
)
|
||||
now_e_list = X.e[0]
|
||||
new_e_list = []
|
||||
for e in now_e_list:
|
||||
new_e = []
|
||||
for n in e:
|
||||
if n not in idx_n_remove:
|
||||
new_e.append(n)
|
||||
if len(new_e) > 0:
|
||||
new_e_list.append(new_e)
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=new_e_list)
|
||||
|
||||
IDX_e = list(range(len(X.e[0])))
|
||||
# hyperedges with size <m are removed
|
||||
sizes = [len(X.e[0][i]) for i in IDX_e]
|
||||
|
||||
idx_e_remove = [IDX_e[i] for i in range(len(IDX_e)) if sizes[i] < m]
|
||||
now_e_list = X.e[0]
|
||||
new_e_list = []
|
||||
for i in range(len(now_e_list)):
|
||||
if i not in idx_e_remove:
|
||||
new_e_list.append(now_e_list[i])
|
||||
X = eg.Hypergraph(num_v=initial_node_num, e_list=new_e_list)
|
||||
|
||||
node_set = set()
|
||||
for sublist in X.e[0]:
|
||||
for element in sublist:
|
||||
node_set.add(element)
|
||||
IDX = list(node_set)
|
||||
|
||||
shell_kk = list(sorted(set(prev_shell) - set(IDX)))
|
||||
for j in shell_kk:
|
||||
k_shell_dict[j][x] = kk - k_step
|
||||
|
||||
node_set = set()
|
||||
for sublist in X.e[0]:
|
||||
for element in sublist:
|
||||
node_set.add(element)
|
||||
IDX = list(node_set)
|
||||
|
||||
D[y] = len(node_set)
|
||||
if y > 0:
|
||||
if D[y] == 0 and D[y - 1] != 0:
|
||||
k_max[x] = kk - k_step # maximum connectivity at order m
|
||||
if D[y] == 0:
|
||||
break # stop the decomposition when the (k,m)-core is empty
|
||||
|
||||
# Psi(m) distribution of hyperedges size
|
||||
Psi = []
|
||||
for m in range(2, size_max + 1):
|
||||
Psi.append(size.count(m) / len(size))
|
||||
# frequency-based hypercoreness
|
||||
R_w_dict = {}
|
||||
for y in k_shell_dict:
|
||||
R_w_dict[y] = sum(np.array(Psi) * np.array(k_shell_dict[y]) / np.array(k_max))
|
||||
return R_w_dict
|
||||
@@ -0,0 +1,89 @@
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
__all__ = ["s_betweenness", "s_closeness", "s_eccentricity"]
|
||||
|
||||
|
||||
def s_betweenness(H, s=1, weight=False, n_workers=None):
|
||||
"""Computes the betweenness centrality for each edge in the hypergraph.
|
||||
|
||||
Computes the betweenness centrality for each edge in the hypergraph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
H : eg.Hypergraph.
|
||||
The hypergraph to compute
|
||||
|
||||
s : int, optional.
|
||||
|
||||
Returns
|
||||
----------
|
||||
dict
|
||||
The keys are the edges and the values are the betweenness centrality.
|
||||
The betweenness centrality for each edge in the hypergraph.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
linegraph = H.get_linegraph(s=s, weight=weight)
|
||||
results = eg.betweenness_centrality(linegraph, n_workers=n_workers)
|
||||
return results
|
||||
|
||||
|
||||
def s_closeness(H, s=1, weight=False, n_workers=None):
|
||||
"""
|
||||
Compute the closeness centrality for each edge in the hypergraph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
H : eg.Hypergraph.
|
||||
s : int, optional
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict. The closeness centrality for each edge in the hypergraph. The keys are the edges and the values are the closeness centrality.
|
||||
"""
|
||||
linegraph = H.get_linegraph(s=s, weight=weight)
|
||||
results = eg.closeness_centrality(linegraph, n_workers=n_workers)
|
||||
return results
|
||||
|
||||
|
||||
def s_eccentricity(H, s=1, edges=True, source=None):
|
||||
r"""
|
||||
The length of the longest shortest path from a vertex $u$ to every other vertex in
|
||||
the s-linegraph.
|
||||
$V$ = set of vertices in the s-linegraph
|
||||
$d$ = shortest path distance
|
||||
|
||||
.. math::
|
||||
|
||||
\text{s-ecc}(u) = \text{max}\{d(u,v): v \in V\}
|
||||
|
||||
Parameters
|
||||
----------
|
||||
H : eg.Hypergraph
|
||||
|
||||
s : int, optional
|
||||
|
||||
edges : bool, optional
|
||||
Indicates if method should compute edge linegraph (default) or node linegraph.
|
||||
|
||||
source : str, optional
|
||||
Identifier of node or edge of interest for computing centrality
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict or float
|
||||
returns the s-eccentricity value of the edges(nodes).
|
||||
If source=None a dictionary of values for each s-edge in H is returned.
|
||||
If source then a single value is returned.
|
||||
If the s-linegraph is disconnected, np.inf is returned.
|
||||
|
||||
"""
|
||||
|
||||
g = H.get_linegraph(s=s)
|
||||
result = eg.eccentricity(g)
|
||||
if source:
|
||||
return result[source]
|
||||
else:
|
||||
return result
|
||||
@@ -0,0 +1,72 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
class TestCycleRatioCentrality(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.G_triangle = eg.Graph()
|
||||
self.G_triangle.add_edges([(1, 2), (2, 3), (3, 1)])
|
||||
|
||||
self.G_star = eg.Graph()
|
||||
self.G_star.add_edges([(1, 2), (1, 3), (1, 4)])
|
||||
|
||||
self.G_complete = eg.complete_graph(4)
|
||||
|
||||
self.G_disconnected = eg.Graph()
|
||||
self.G_disconnected.add_edges([(1, 2), (3, 4)])
|
||||
|
||||
def test_triangle_graph(self):
|
||||
result = eg.cycle_ratio_centrality(self.G_triangle.copy())
|
||||
self.assertTrue(all(isinstance(v, float) for v in result.values()))
|
||||
self.assertEqual(len(result), 3)
|
||||
|
||||
def test_star_graph(self):
|
||||
result = eg.cycle_ratio_centrality(self.G_star.copy())
|
||||
self.assertEqual(result, {})
|
||||
|
||||
def test_complete_graph(self):
|
||||
result = eg.cycle_ratio_centrality(self.G_complete.copy())
|
||||
self.assertEqual(len(result), 4)
|
||||
self.assertTrue(all(v > 0 for v in result.values()))
|
||||
|
||||
def test_disconnected_graph(self):
|
||||
result = eg.cycle_ratio_centrality(self.G_disconnected.copy())
|
||||
self.assertEqual(result, {})
|
||||
|
||||
def test_my_all_shortest_paths_valid(self):
|
||||
G = eg.Graph()
|
||||
G.add_edges([(1, 2), (2, 3), (3, 4)])
|
||||
paths = list(eg.my_all_shortest_paths(G, 1, 4))
|
||||
self.assertIn([1, 2, 3, 4], paths)
|
||||
|
||||
def test_my_all_shortest_paths_invalid(self):
|
||||
G = eg.Graph()
|
||||
G.add_edges([(1, 2), (3, 4)])
|
||||
with self.assertRaises(eg.EasyGraphNoPath):
|
||||
list(eg.my_all_shortest_paths(G, 1, 4))
|
||||
|
||||
def test_getandJudgeSimpleCircle_true(self):
|
||||
G = eg.Graph()
|
||||
G.add_edges([(1, 2), (2, 3), (3, 1)])
|
||||
self.assertTrue(eg.getandJudgeSimpleCircle([1, 2, 3], G))
|
||||
|
||||
def test_getandJudgeSimpleCircle_false(self):
|
||||
G = eg.Graph()
|
||||
G.add_edges([(1, 2), (2, 3)])
|
||||
self.assertFalse(eg.getandJudgeSimpleCircle([1, 2, 3], G))
|
||||
|
||||
def test_statistics_and_calculate_indicators(self):
|
||||
SmallestCyclesOfNodes = {1: set(), 2: set(), 3: set()}
|
||||
CycLenDict = {3: 0}
|
||||
SmallestCycles = {(1, 2, 3)}
|
||||
result = eg.StatisticsAndCalculateIndicators(
|
||||
SmallestCyclesOfNodes, CycLenDict, SmallestCycles
|
||||
)
|
||||
self.assertTrue(isinstance(result, dict))
|
||||
self.assertIn(1, result)
|
||||
self.assertGreater(result[1], 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,38 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
class TestHypergraphDegreeCentrality(unittest.TestCase):
|
||||
def test_basic_degree_centrality(self):
|
||||
hg = eg.Hypergraph(num_v=4, e_list=[(0, 1), (1, 2), (2, 3), (0, 2)])
|
||||
result = eg.hyepergraph_degree_centrality(hg)
|
||||
expected = {0: 2, 1: 2, 2: 3, 3: 1}
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_empty_hypergraph(self):
|
||||
hg = eg.Hypergraph(num_v=1, e_list=[])
|
||||
result = eg.hyepergraph_degree_centrality(hg)
|
||||
self.assertEqual(result, {0: 0})
|
||||
|
||||
def test_single_edge(self):
|
||||
hg = eg.Hypergraph(num_v=3, e_list=[(0, 1, 2)])
|
||||
result = eg.hyepergraph_degree_centrality(hg)
|
||||
expected = {0: 1, 1: 1, 2: 1}
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_singleton_nodes(self):
|
||||
hg = eg.Hypergraph(num_v=3, e_list=[(0,), (1,), (2,)])
|
||||
result = eg.hyepergraph_degree_centrality(hg)
|
||||
expected = {0: 1, 1: 1, 2: 1}
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_node_with_no_edges(self):
|
||||
hg = eg.Hypergraph(num_v=4, e_list=[(0, 1), (1, 2)])
|
||||
result = eg.hyepergraph_degree_centrality(hg)
|
||||
expected = {0: 1, 1: 2, 2: 1, 3: 0} # node 3 has no edges
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,51 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
class TestHypercoreness(unittest.TestCase):
|
||||
def test_simple_hypergraph(self):
|
||||
hg = eg.Hypergraph(num_v=5, e_list=[(0, 1), (1, 2, 3), (3, 4)])
|
||||
si = eg.size_independent_hypercoreness(hg)
|
||||
fb = eg.frequency_based_hypercoreness(hg)
|
||||
|
||||
self.assertIsInstance(si, dict)
|
||||
self.assertIsInstance(fb, dict)
|
||||
self.assertTrue(set(si.keys()).issubset(set(hg.v)))
|
||||
self.assertTrue(set(fb.keys()).issubset(set(hg.v)))
|
||||
|
||||
for val in si.values():
|
||||
self.assertIsInstance(val, float)
|
||||
self.assertGreaterEqual(val, 0)
|
||||
|
||||
for val in fb.values():
|
||||
self.assertIsInstance(val, float)
|
||||
self.assertGreaterEqual(val, 0)
|
||||
|
||||
def test_single_hyperedge(self):
|
||||
hg = eg.Hypergraph(num_v=3, e_list=[(0, 1, 2)])
|
||||
si = eg.size_independent_hypercoreness(hg)
|
||||
fb = eg.frequency_based_hypercoreness(hg)
|
||||
|
||||
self.assertTrue(all(v >= 0 for v in si.values()))
|
||||
self.assertTrue(all(v >= 0 for v in fb.values()))
|
||||
|
||||
def test_large_uniform_hypergraph(self):
|
||||
hg = eg.Hypergraph(num_v=10, e_list=[(i, i + 1, i + 2) for i in range(7)])
|
||||
si = eg.size_independent_hypercoreness(hg)
|
||||
fb = eg.frequency_based_hypercoreness(hg)
|
||||
|
||||
self.assertEqual(len(si), 10)
|
||||
self.assertEqual(len(fb), 10)
|
||||
|
||||
def test_empty_hypergraph_raises(self):
|
||||
hg = eg.Hypergraph(num_v=1, e_list=[])
|
||||
with self.assertRaises(IndexError):
|
||||
eg.size_independent_hypercoreness(hg)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
eg.frequency_based_hypercoreness(hg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,40 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
|
||||
|
||||
class TestHypergraphSCentrality(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Simple test hypergraph
|
||||
self.hg = eg.Hypergraph(num_v=5, e_list=[(0, 1), (1, 2, 3), (3, 4)])
|
||||
self.empty_hg = eg.Hypergraph(num_v=1, e_list=[])
|
||||
self.singleton_hg = eg.Hypergraph(num_v=3, e_list=[(0,), (1,), (2,)])
|
||||
|
||||
def test_s_betweenness_normal(self):
|
||||
result = eg.s_betweenness(self.hg)
|
||||
self.assertIsInstance(result, (list, dict))
|
||||
self.assertTrue(all(isinstance(x, (int, float)) for x in result))
|
||||
|
||||
def test_s_closeness_normal(self):
|
||||
result = eg.s_closeness(self.hg)
|
||||
self.assertIsInstance(result, (list, dict))
|
||||
self.assertTrue(all(isinstance(x, (int, float)) for x in result))
|
||||
|
||||
def test_s_eccentricity_all(self):
|
||||
result = eg.s_eccentricity(self.hg)
|
||||
self.assertIsInstance(result, dict)
|
||||
for v in result.values():
|
||||
self.assertIsInstance(v, (int, float, np.integer, np.floating))
|
||||
|
||||
def test_s_eccentricity_edges_false(self):
|
||||
result = eg.s_eccentricity(self.hg, edges=False)
|
||||
self.assertIsInstance(result, dict)
|
||||
|
||||
def test_s_eccentricity_invalid_source(self):
|
||||
with self.assertRaises(KeyError):
|
||||
eg.s_eccentricity(self.hg, source=(999, 888))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,43 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
|
||||
from easygraph.exception import EasyGraphError
|
||||
|
||||
|
||||
class TestVectorCentrality(unittest.TestCase):
|
||||
def test_single_edge(self):
|
||||
hg = eg.Hypergraph(num_v=3, e_list=[(0, 1, 2)])
|
||||
result = eg.vector_centrality(hg)
|
||||
self.assertEqual(set(result.keys()), {0, 1, 2})
|
||||
for val in result.values():
|
||||
self.assertEqual(len(val), 2) # because D = 3 → k = 2 and 3
|
||||
|
||||
def test_multiple_edges_different_orders(self):
|
||||
hg = eg.Hypergraph(num_v=4, e_list=[(0, 1), (1, 2, 3)])
|
||||
result = eg.vector_centrality(hg)
|
||||
self.assertEqual(set(result.keys()), {0, 1, 2, 3})
|
||||
for val in result.values():
|
||||
self.assertEqual(len(val), 2)
|
||||
self.assertTrue(all(isinstance(x, (float, np.floating)) for x in val))
|
||||
|
||||
def test_disconnected_hypergraph_raises(self):
|
||||
hg = eg.Hypergraph(num_v=6, e_list=[(0, 1), (2, 3)])
|
||||
with self.assertRaises(EasyGraphError):
|
||||
eg.vector_centrality(hg)
|
||||
|
||||
def test_non_consecutive_node_ids(self):
|
||||
hg = eg.Hypergraph(num_v=5, e_list=[(0, 2, 4)])
|
||||
result = eg.vector_centrality(hg)
|
||||
self.assertEqual(len(result), 5)
|
||||
for val in result.values():
|
||||
self.assertEqual(len(val), 2)
|
||||
|
||||
def test_index_error_due_to_wrong_num_v(self):
|
||||
with self.assertRaises(eg.EasyGraphError):
|
||||
eg.Hypergraph(num_v=3, e_list=[(0, 1, 5)])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,91 @@
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
|
||||
from easygraph.exception import EasyGraphError
|
||||
|
||||
|
||||
__all__ = ["vector_centrality"]
|
||||
|
||||
|
||||
def vector_centrality(H):
|
||||
"""The vector centrality of nodes in the line graph of the hypergraph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
H : eg.Hypergraph
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Centrality, where keys are node IDs and values are lists of centralities.
|
||||
|
||||
References
|
||||
----------
|
||||
"Vector centrality in hypergraphs", K. Kovalenko, M. Romance, E. Vasilyeva,
|
||||
D. Aleja, R. Criado, D. Musatov, A.M. Raigorodskii, J. Flores, I. Samoylenko,
|
||||
K. Alfaro-Bittner, M. Perc, S. Boccaletti,
|
||||
https://doi.org/10.1016/j.chaos.2022.112397
|
||||
|
||||
"""
|
||||
|
||||
# If the hypergraph is empty, then return an empty dictionary
|
||||
if H.num_v == 0:
|
||||
return dict()
|
||||
|
||||
LG = H.get_linegraph()
|
||||
if not eg.is_connected(LG):
|
||||
raise EasyGraphError("This method is not defined for disconnected hypergraphs.")
|
||||
LGcent = eigenvector_centrality(LG)
|
||||
|
||||
vc = {node: [] for node in range(0, H.num_v)}
|
||||
|
||||
edge_label_dict = {tuple(edge): index for index, edge in enumerate(H.e[0])}
|
||||
|
||||
hyperedge_dims = {tuple(edge): len(edge) for edge in H.e[0]}
|
||||
|
||||
D = max([len(e) for e in H.e[0]])
|
||||
|
||||
for k in range(2, D + 1):
|
||||
c_i = np.zeros(H.num_v)
|
||||
|
||||
for edge, _ in list(filter(lambda x: x[1] == k, hyperedge_dims.items())):
|
||||
for node in edge:
|
||||
try:
|
||||
c_i[node] += LGcent[edge_label_dict[edge]]
|
||||
except IndexError:
|
||||
raise Exception(
|
||||
"Nodes must be written with the Pythonic indexing (0,1,2...)"
|
||||
)
|
||||
|
||||
c_i *= 1 / k
|
||||
|
||||
for node in range(H.num_v):
|
||||
vc[node].append(c_i[node])
|
||||
|
||||
return vc
|
||||
|
||||
|
||||
def eigenvector_centrality(G, max_iter=100, tol=1.0e-6):
|
||||
from collections import defaultdict
|
||||
|
||||
nodes = list(G.nodes)
|
||||
n = len(nodes)
|
||||
x = {v: 1.0 for v in nodes}
|
||||
|
||||
for _ in range(max_iter):
|
||||
x_new = defaultdict(float)
|
||||
for v in G:
|
||||
for nbr in G.neighbors(v):
|
||||
x_new[v] += x[nbr]
|
||||
|
||||
# Normalize
|
||||
norm = sum(v**2 for v in x_new.values()) ** 0.5
|
||||
if norm == 0:
|
||||
return x_new
|
||||
x_new = {k: v / norm for k, v in x_new.items()}
|
||||
|
||||
# Check convergence
|
||||
if all(abs(x_new[v] - x[v]) < tol for v in nodes):
|
||||
return x_new
|
||||
x = x_new
|
||||
Reference in New Issue
Block a user