Skip to content

Commit 4641afc

Browse files
authored
Merge pull request #20 from saibalmars/forman_fix
Forman fix
2 parents 4464b97 + 69174e3 commit 4641afc

File tree

8 files changed

+430
-204
lines changed

8 files changed

+430
-204
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ install:
9393
pip install cython;
9494
pip install numpy;
9595
fi
96+
- pip install cython
9697
- pip install .
9798

9899
before_cache:

GraphRicciCurvature/FormanRicci.py

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,31 @@
1212
# Sreejith, R. P., Karthikeyan Mohanraj, Jürgen Jost, Emil Saucan, and Areejit Samal. 2016.
1313
# “Forman Curvature for Complex Networks.” Journal of Statistical Mechanics: Theory and Experiment 2016 (6).
1414
# IOP Publishing: 063206.
15+
# Samal, A., Sreejith, R.P., Gu, J. et al.
16+
# "Comparative analysis of two discretizations of Ricci curvature for complex networks."
17+
# Scientific Report 8, 8650 (2018).
1518

19+
20+
import networkx as nx
21+
import math
1622
from .util import logger, set_verbose
1723

1824

1925
class FormanRicci:
20-
def __init__(self, G, verbose="ERROR"):
26+
def __init__(self, G: nx.Graph, weight="weight", method="augmented", verbose="ERROR"):
2127
"""A class to compute Forman-Ricci curvature for all nodes and edges in G.
2228
2329
Parameters
2430
----------
2531
G : NetworkX graph
2632
A given NetworkX graph, unweighted graph only for now, edge weight will be ignored.
33+
weight : str
34+
The edge weight used to compute Ricci curvature. (Default value = "weight")
35+
method : {"1d", "augmented"}
36+
The method used to compute Forman-Ricci curvature. (Default value = "augmented")
37+
38+
- "1d": Computed with 1-dimensional simplicial complex (vertex, edge).
39+
- "augmented": Computed with 2-dimensional simplicial complex, length <=3 (vertex, edge, face).
2740
verbose: {"INFO","DEBUG","ERROR"}
2841
Verbose level. (Default value = "ERROR")
2942
- "INFO": show only iteration process log.
@@ -32,6 +45,22 @@ def __init__(self, G, verbose="ERROR"):
3245
"""
3346

3447
self.G = G.copy()
48+
self.weight = weight
49+
self.method = method
50+
51+
if not nx.get_edge_attributes(self.G, self.weight):
52+
logger.info('Edge weight not detected in graph, use "weight" as default edge weight.')
53+
for (v1, v2) in self.G.edges():
54+
self.G[v1][v2][self.weight] = 1.0
55+
if not nx.get_node_attributes(self.G, self.weight):
56+
logger.info('Node weight not detected in graph, use "weight" as default node weight.')
57+
for v in self.G.nodes():
58+
self.G.nodes[v][self.weight] = 1.0
59+
if self.G.is_directed():
60+
logger.info("Forman-Ricci curvature is not supported for directed graph yet, "
61+
"covert input graph to undirected.")
62+
self.G = self.G.to_undirected()
63+
3564
set_verbose(verbose)
3665

3766
def compute_ricci_curvature(self):
@@ -45,34 +74,67 @@ def compute_ricci_curvature(self):
4574
4675
Examples
4776
--------
48-
To compute the Forman-Ricci curvature for karate club graph::
77+
To compute the Forman-Ricci curvature for karate club graph:
4978
5079
>>> G = nx.karate_club_graph()
5180
>>> frc = FormanRicci(G)
5281
>>> frc.compute_ricci_curvature()
53-
>>> frc.G[0][1]
54-
{'formanCurvature': 0}
82+
>>> frc.G[0][2]
83+
{'weight': 1.0, 'formanCurvature': -7.0}
5584
"""
5685

57-
# Edge Forman curvature
58-
for (v1, v2) in self.G.edges():
59-
if self.G.is_directed():
60-
v1_nbr = set(list(self.G.predecessors(v1)) + list(self.G.successors(v1)))
61-
v2_nbr = set(list(self.G.predecessors(v2)) + list(self.G.successors(v2)))
62-
else:
86+
if self.method == "1d":
87+
# Edge Forman curvature
88+
for (v1, v2) in self.G.edges():
89+
v1_nbr = set(self.G.neighbors(v1))
90+
v1_nbr.remove(v2)
91+
v2_nbr = set(self.G.neighbors(v2))
92+
v2_nbr.remove(v1)
93+
94+
w_e = self.G[v1][v2][self.weight]
95+
w_v1 = self.G.nodes[v1][self.weight]
96+
w_v2 = self.G.nodes[v2][self.weight]
97+
ev1_sum = sum([w_v1 / math.sqrt(w_e * self.G[v1][v][self.weight]) for v in v1_nbr])
98+
ev2_sum = sum([w_v2 / math.sqrt(w_e * self.G[v2][v][self.weight]) for v in v2_nbr])
99+
100+
self.G[v1][v2]["formanCurvature"] = w_e * (w_v1 / w_e + w_v2 / w_e - (ev1_sum + ev2_sum))
101+
102+
logger.debug("Source: %s, target: %d, Forman-Ricci curvature = %f " % (
103+
v1, v2, self.G[v1][v2]["formanCurvature"]))
104+
105+
elif self.method == "augmented":
106+
# Edge Forman curvature
107+
for (v1, v2) in self.G.edges():
63108
v1_nbr = set(self.G.neighbors(v1))
64109
v1_nbr.remove(v2)
65110
v2_nbr = set(self.G.neighbors(v2))
66111
v2_nbr.remove(v1)
67-
face = v1_nbr & v2_nbr
68-
# G[v1][v2]["face"]=face
69-
prl_nbr = (v1_nbr | v2_nbr) - face
70-
# G[v1][v2]["prl_nbr"]=prl_nbr
71112

72-
self.G[v1][v2]["formanCurvature"] = len(face) + 2 - len(prl_nbr)
113+
face = v1_nbr & v2_nbr
114+
# prl_nbr = (v1_nbr | v2_nbr) - face
115+
116+
w_e = self.G[v1][v2][self.weight]
117+
w_f = 1 # Assume all face have weight 1
118+
w_v1 = self.G.nodes[v1][self.weight]
119+
w_v2 = self.G.nodes[v2][self.weight]
120+
121+
sum_ef = sum([w_e / w_f for _ in face])
122+
sum_ve = sum([w_v1 / w_e + w_v2 / w_e])
73123

74-
logger.debug("Source: %s, target: %d, Forman-Ricci curvature = %f " % (
75-
v1, v2, self.G[v1][v2]["formanCurvature"]))
124+
# sum_ehef = sum([math.sqrt(w_e*self.G[v1][v][self.weight])/w_f +
125+
# math.sqrt(w_e*self.G[v2][v][self.weight])/w_f
126+
# for v in face])
127+
sum_ehef = 0 # Always 0 for cycle = 3 case.
128+
sum_veeh = sum([w_v1 / math.sqrt(w_e * self.G[v1][v][self.weight]) for v in (v1_nbr - face)] +
129+
[w_v2 / math.sqrt(w_e * self.G[v2][v][self.weight]) for v in (v2_nbr - face)])
130+
131+
self.G[v1][v2]["formanCurvature"] = w_e * (sum_ef + sum_ve - math.fabs(sum_ehef - sum_veeh))
132+
133+
logger.debug("Source: %s, target: %d, Forman-Ricci curvature = %f " % (
134+
v1, v2, self.G[v1][v2]["formanCurvature"]))
135+
136+
else:
137+
assert True, 'Method %s not available. Support methods: {"1d","augmented"}' % self.method
76138

77139
# Node Forman curvature
78140
for n in self.G.nodes():
@@ -84,6 +146,8 @@ def compute_ricci_curvature(self):
84146

85147
# assign the node Forman curvature to be the average of node's adjacency edges
86148
self.G.nodes[n]['formanCurvature'] = fcsum / self.G.degree(n)
149+
else:
150+
self.G.nodes[n]['formanCurvature'] = fcsum
87151

88152
logger.debug("node %d, Forman Curvature = %f" % (n, self.G.nodes[n]['formanCurvature']))
89-
print("Forman curvature computation done.")
153+
print("Forman curvature (%s) computation done." % self.method)

GraphRicciCurvature/OllivierRicci.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -500,16 +500,6 @@ def _compute_ricci_curvature(G: nx.Graph, weight="weight", **kwargs):
500500
A NetworkX graph with "ricciCurvature" on nodes and edges.
501501
"""
502502

503-
if not nx.get_edge_attributes(G, weight):
504-
logger.info('Edge weight not detected in graph, use "weight" as default edge weight.')
505-
for (v1, v2) in G.edges():
506-
G[v1][v2][weight] = 1.0
507-
508-
self_loop_edges = list(nx.selfloop_edges(G))
509-
if self_loop_edges:
510-
logger.info('Self-loop edge detected. Removing %d self-loop edges.' % len(self_loop_edges))
511-
G.remove_edges_from(self_loop_edges)
512-
513503
# compute Ricci curvature for all edges
514504
edge_ricci = _compute_ricci_curvature_edges(G, weight=weight, **kwargs)
515505

@@ -701,6 +691,16 @@ def __init__(self, G: nx.Graph, weight="weight", alpha=0.5, method="Sinkhorn",
701691
assert importlib.util.find_spec("ot"), \
702692
"Package POT: Python Optimal Transport is required for Sinkhorn distance."
703693

694+
if not nx.get_edge_attributes(self.G, weight):
695+
logger.info('Edge weight not detected in graph, use "weight" as default edge weight.')
696+
for (v1, v2) in self.G.edges():
697+
self.G[v1][v2][weight] = 1.0
698+
699+
self_loop_edges = list(nx.selfloop_edges(self.G))
700+
if self_loop_edges:
701+
logger.info('Self-loop edge detected. Removing %d self-loop edges.' % len(self_loop_edges))
702+
self.G.remove_edges_from(self_loop_edges)
703+
704704
def set_verbose(self, verbose):
705705
"""Set the verbose level for this process.
706706

GraphRicciCurvature/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
__version__ = "0.5.1"
1+
__version__ = "0.5.2"
22
__author__ = "Chien-Chun Ni"
33
__email__ = "saibalmars@gmail.com"

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,24 @@ A Python library to compute Discrete Ricci curvature, Ricci flow, and Ricci comm
99
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
1010

1111
-----
12-
This work computes the **Ollivier-Ricci Curvature**[Ni], **Ollivier-Ricci Flow**[Ni2,Ni3], **Forman-Ricci Curvature**(or **Forman curvature**)[Sreejith], and **Ricci community**[Ni3] detected by Ollivier-Ricci flow metric.
12+
This work computes the **Ollivier-Ricci Curvature**[Ni], **Ollivier-Ricci Flow**[Ni2,Ni3], **Forman-Ricci Curvature**(or **Forman curvature**)[Sreejith, Samal], and **Ricci community**[Ni3] detected by Ollivier-Ricci flow metric.
1313

1414
<p align="center">
1515
<img src="https://github.com/saibalmars/GraphRicciCurvature/raw/master/doc/_static/rf-manifold.png" title="Manifold Ricci flow" width="300" >
1616
<img src="https://github.com/saibalmars/GraphRicciCurvature/raw/master/doc/_static/karate_demo.png" title="karate club demo" width="500" >
1717
</p>
1818

19-
Curvature is a geometric property to describe the local shape of an object. If we draw two parallel paths on a surface with positive curvature like a sphere, these two paths move closer to each other while for a negatively curved surface like a saddle, these two paths tend to be apart.
19+
Curvature is a geometric property to describe the local shape of an object. If we draw two parallel paths on a surface with positive curvature like a sphere, these two paths move closer to each other while for a negatively curved surface like a saddle, these two paths tend to be apart. Currently there are multiple ways to discretize curvature on graph, in this library, we include two of the most frequently used discrete Ricci curvature: **Ollivier-Ricci curvature** which is based on optimal transportation theory and **Forman-Ricci curvature** which is base on CW complexes.
2020

21-
In [Ni], we observe that the edge Ricci curvature plays an important role in the graph structure. An edge with positive curvature represents an edge within a cluster, while a negatively curved edge tent to be a bridge within clusters. Also, negatively curved edges are highly related to graph connectivity, with negatively curved edges removed from a connected graph, the graph soon become disconnected.
21+
In [Ni], edge Ricci curvature is observed to play an important role in the graph structure. An edge with positive curvature represents an edge within a cluster, while a negatively curved edge tent to be a bridge within clusters. Also, negatively curved edges are highly related to graph connectivity, with negatively curved edges removed from a connected graph, the graph soon become disconnected.
2222

2323
Ricci flow is a process to uniformized the edge Ricci curvature of the graph. For a given graph, the Ricci flow gives a "Ricci flow metric" on each edge as edge weights, such that under these edge weights, the Ricci curvature of the graph is mostly equal everywhere. In [Ni3], this "Ricci flow metric" is shown to be able to detect communities.
2424

2525
Both Ricci curvature and Ricci flow metric can act as a graph fingerprint for graph classification. The different graph gives different edge Ricci curvature distributions and different Ricci flow metric.
2626

2727
Video demonstration of Ricci flow for community detection:
2828
<p align="center">
29-
<a href="https://youtu.be/QlENb_XlJ_8?t=20">
29+
<a href="https://youtu.be/QlENb_XlJ_8">
3030
<img src="https://github.com/saibalmars/GraphRicciCurvature/raw/master/doc/_static/ricci_community.png" title="Ricci Community" width="600" >
3131
</a>
3232
</p>
@@ -107,14 +107,15 @@ More example in [example.py](example.py).
107107

108108
## Reference
109109

110-
[Ni]: Ni, C.-C., Lin, Y.-Y., Gao, J., Gu, X., and Saucan, E. 2015. "Ricci curvature of the Internet topology" (Vol. 26, pp. 2758–2766). Presented at the 2015 IEEE Conference on Computer Communications (INFOCOM), IEEE. [arXiv](https://arxiv.org/abs/1501.04138)
110+
[Ni]: Ni, C.-C., Lin, Y.-Y., Gao, J., Gu, X., and Saucan, E. "Ricci curvature of the Internet topology" (Vol. 26, pp. 2758–2766). Presented at the 2015 IEEE Conference on Computer Communications (INFOCOM), IEEE. [arXiv](https://arxiv.org/abs/1501.04138)
111111

112-
[Ni2]: Ni, C.-C., Lin, Y.-Y., Gao, J., and Gu, X. 2018. "Network Alignment by Discrete Ollivier-Ricci Flow", Graph Drawing 2018, [arXiv](https://arxiv.org/abs/1809.00320)
112+
[Ni2]: Ni, C.-C., Lin, Y.-Y., Gao, J., and Gu, X. "Network Alignment by Discrete Ollivier-Ricci Flow", Graph Drawing 2018, [arXiv](https://arxiv.org/abs/1809.00320)
113113

114-
[Ni3]: Ni, C.-C., Lin, Y.-Y., Luo, F. and Gao, J. 2019. "Community Detection on Networks with Ricci Flow", Scientific Reports, [arXiv](https://arxiv.org/abs/1907.03993)
114+
[Ni3]: Ni, C.-C., Lin, Y.-Y., Luo, F. and Gao, J. "Community Detection on Networks with Ricci Flow", Scientific Reports 9, 9984 (2019), [arXiv](https://arxiv.org/abs/1907.03993)
115115

116-
[Sreejith]: Sreejith, R. P., Karthikeyan Mohanraj, Jürgen Jost, Emil Saucan, and Areejit Samal. 2016. “Forman Curvature for Complex Networks. Journal of Statistical Mechanics: Theory and Experiment 2016 (6). IOP Publishing: 063206. [arxiv](https://arxiv.org/abs/1603.00386)
116+
[Sreejith]: Sreejith, R. P., Karthikeyan Mohanraj, Jürgen Jost, Emil Saucan, and Areejit Samal. "Forman Curvature for Complex Networks." Journal of Statistical Mechanics: Theory and Experiment 2016 (6). IOP Publishing: 063206. [arxiv](https://arxiv.org/abs/1603.00386)
117117

118+
[Samal]: Samal, A., Sreejith, R.P., Gu, J. et al. "Comparative analysis of two discretizations of Ricci curvature for complex networks." Scientific Report 8, 8650 (2018). [arXiv](https://arxiv.org/abs/1712.07600)
118119

119120
## Contact
120121

0 commit comments

Comments
 (0)