Skip to content
Snippets Groups Projects
Commit e22414bc authored by Jarrod Pas's avatar Jarrod Pas
Browse files

Finish base community implementation

parent 3ba6c789
No related branches found
No related tags found
2 merge requests!3Version 0.2,!1Full rewrite
...@@ -20,8 +20,8 @@ __version__ = '0.1' ...@@ -20,8 +20,8 @@ __version__ = '0.1'
__author__ = 'Jarrod Pas <j.pas@usask.ca>' __author__ = 'Jarrod Pas <j.pas@usask.ca>'
from collections import defaultdict from collections import defaultdict
from itertools import product
# TODO: Consider using igraph instead of networkx - jpas (2017-08-11)
import networkx as nx import networkx as nx
from community import best_partition as louvain_partition from community import best_partition as louvain_partition
...@@ -43,8 +43,8 @@ class Community: ...@@ -43,8 +43,8 @@ class Community:
self.epoch = epoch self.epoch = epoch
self.graph = nx.Graph() self.graph = nx.Graph()
self.graph_old = None
self._prev_graph = None
self._community = {} self._community = {}
def start(self, network): def start(self, network):
...@@ -64,54 +64,62 @@ class Community: ...@@ -64,54 +64,62 @@ class Community:
env = self.network.env env = self.network.env
while True: while True:
yield env.timeout(self.epoch) yield env.timeout(self.epoch)
new_graph = nx.Graph() print('epoch')
new = nx.Graph()
for node_a, node_b, start in self.graph.edges(data='start'): for node_a, node_b, start in self.graph.edges(data='start'):
if start is not None: if start is not None:
self.leave(node_a, node_b) self.leave(node_a, node_b)
data = { self.join(node_a, node_b, graph=new)
'start': env.now,
'duration': 0,
}
new_graph.add_edge(node_a, node_b, data)
self.graph, self._prev_graph = new_graph, self.graph self.graph_old = self.graph
self.partition() self.graph = new
for community in self.partition():
for node in community:
self._community[node] = community
def partition(self): def partition(self):
"""Partition nodes into communities.""" """Partition nodes into communities."""
self._community = {} for node in self.graph_old.node():
yield frozenset([node])
for node in self._prev_graph.nodes():
self._community[node] = frozenset(list(node))
def join(self, node_a, node_b): def join(self, node_a, node_b, graph=None):
""" """
Node a and b join each other's neighbourhoods. Node a and b join each other's neighbourhoods.
This is an idempotent operation. This is an idempotent operation.
""" """
if not self.graph.has_edge(node_a, node_b): if graph is None:
data = { if self.graph is None:
return
graph = self.graph
if not graph.has_edge(node_a, node_b):
graph.add_edge(node_a, node_b, {
'start': None, 'start': None,
'duration': 0, 'weight': 0,
} })
self.graph.add_edge(node_a, node_b, data)
edge = self.graph[node_a][node_b] edge = graph[node_a][node_b]
if edge['start'] is None: if edge['start'] is None:
edge['start'] = self.network.env.now edge['start'] = self.network.env.now
def leave(self, node_a, node_b): def leave(self, node_a, node_b, graph=None):
""" """
Node a and b leave each other's neighbourhoods. Node a and b leave each other's neighbourhoods.
This is an idempotent operation. This is an idempotent operation.
""" """
if self.graph.has_edge(node_a, node_b): if graph is None:
edge = self.graph[node_a][node_b] if self.graph is None:
return
graph = self.graph
if graph.has_edge(node_a, node_b):
edge = graph[node_a][node_b]
if edge['start'] is not None: if edge['start'] is not None:
edge['duration'] += self.network.env.now - edge['start'] edge['weight'] += self.network.env.now - edge['start']
edge['start'] = None edge['start'] = None
def __getitem__(self, node): def __getitem__(self, node):
...@@ -126,7 +134,7 @@ class Community: ...@@ -126,7 +134,7 @@ class Community:
def local_popularity(self, node): def local_popularity(self, node):
"""Return local popularity of a node.""" """Return local popularity of a node."""
graph = self._prev_graph graph = self.graph_old
if node not in graph: if node not in graph:
return 0 return 0
...@@ -138,7 +146,7 @@ class Community: ...@@ -138,7 +146,7 @@ class Community:
def global_popularity(self, node): def global_popularity(self, node):
"""Return global popularity of a node.""" """Return global popularity of a node."""
graph = self._prev_graph graph = self.graph_old
if node not in graph: if node not in graph:
return 0 return 0
...@@ -149,10 +157,15 @@ class Community: ...@@ -149,10 +157,15 @@ class Community:
) )
def unique_interactions(self, node): def unique_interactions(self, node):
graph = self._prev_graph """Unique interactions for a node within it's community."""
graph = self.graph_old
if node not in graph: if node not in graph:
return 0 return 0
return len(graph[node]) return len(
other
for other in node.community
if other in graph[node]
)
def community_betweenness(self, node_a, node_b): def community_betweenness(self, node_a, node_b):
"""Return community betweenness for 2 nodes.""" """Return community betweenness for 2 nodes."""
...@@ -162,7 +175,7 @@ class Community: ...@@ -162,7 +175,7 @@ class Community:
if community_a == community_b: if community_a == community_b:
return float('inf') return float('inf')
graph = self._prev_graph graph = self.graph_old
return sum( return sum(
graph[node_a][node_b]['duration'] graph[node_a][node_b]['duration']
for node_a, node_b in product(community_a, community_b) for node_a, node_b in product(community_a, community_b)
...@@ -170,30 +183,17 @@ class Community: ...@@ -170,30 +183,17 @@ class Community:
) )
class KCliqueCommunity(Community): class KCliqueCommunity(Community):
"""K-clique community detection.""" """K-clique community detection."""
def __init__(self, epoch, k, threshold): def __init__(self, epoch, k):
"""Create a k-clique community detector.""" """Create a k-clique community detector."""
super().__init__(epoch) super().__init__(epoch)
self.k = k self.k = k
self.threshold = threshold
def partition(self): def partition(self):
"""Partition graph by k-clique.""" """Partition graph by k-clique communities method."""
graph = nx.Graph() return nx.k_clique_communities(self.graph_old, k=self.k)
for edge in self._prev_graph.edges(data='duration'):
duration = edge[2]
if duration > self.threshold:
graph.add_edge(*edge)
communities = nx.k_clique_communities(graph, self.k)
for community in communities:
for node in community:
self._community[node] = community
class LouvainCommunity(Community): class LouvainCommunity(Community):
...@@ -201,16 +201,14 @@ class LouvainCommunity(Community): ...@@ -201,16 +201,14 @@ class LouvainCommunity(Community):
def partition(self): def partition(self):
"""Partition graph with Louvain algorithm.""" """Partition graph with Louvain algorithm."""
partitions = louvain_partition(self._prev_graph, weight='duration') partitions = louvain_partition(self.graph_old)
communities = defaultdict(set) communities = defaultdict(set)
for node, community in partitions.items(): for node, community in partitions.items():
communities[community].add(node) communities[community].add(node)
for community in communities.values(): for community in communities.values():
community = frozenset(community) yield frozenset(community)
for node in community:
self._community[node] = community
class CommunityNode(Node): class CommunityNode(Node):
"""Base node for community based forwarding heuristics.""" """Base node for community based forwarding heuristics."""
...@@ -227,6 +225,24 @@ class CommunityNode(Node): ...@@ -227,6 +225,24 @@ class CommunityNode(Node):
super().start(network) super().start(network)
self._community.start(network) self._community.start(network)
def join(self, node):
"""
Approach the neighbouhood of this node.
This is an idempotent operation.
"""
super().join(node)
self._community.join(self, node)
def leave(self, node):
"""
Leave the neighbouhood of this node.
This is an idempotent operation.
"""
super().leave(node)
self._community.leave(self, node)
@property @property
def community(self): def community(self):
"""Return my community.""" """Return my community."""
...@@ -261,11 +277,6 @@ class BubbleNode(CommunityNode): ...@@ -261,11 +277,6 @@ class BubbleNode(CommunityNode):
if forward: if forward:
return forward return forward
if self.in_community_neighbours:
return {}
elif self.out_community_neighbour:
return {}
return {} return {}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment