Skip to content
Snippets Groups Projects
communities.py 4.86 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jarrod Pas's avatar
    Jarrod Pas committed
    from collections import defaultdict
    from itertools import product
    
    import networkx as nx
    from community import best_partition as louvain_partition
    
    
    Jarrod Pas's avatar
    Jarrod Pas committed
    from .core import TickProcess
    
    Jarrod Pas's avatar
    Jarrod Pas committed
    
    class EpochCommunity(TickProcess):
    
    Jarrod Pas's avatar
    Jarrod Pas committed
        def __init__(self, epoch=1, **kwargs):
    
    Jarrod Pas's avatar
    Jarrod Pas committed
            super().__init__(epoch)
    
    Jarrod Pas's avatar
    Jarrod Pas committed
    
            self.graph = nx.Graph()
            self.old_graph = None
            self.community = defaultdict(frozenset)
            self._cbc_memo = {}
    
        def set_link(self, a, b, state, now):
            if a not in self.graph:
                self.graph.add_node(a)
    
            if b not in self.graph:
                self.graph.add_node(b)
    
            if b not in self.graph[a]:
                self.graph.add_edge(a, b, { 'start': -1 })
    
            edge = self.graph[a][b]
            if state:
                edge['start'] = now
                if 'duration' not in edge:
                    edge['duration'] = 0
            else:
                edge['duration'] = now - edge['start']
                edge['start'] = -1
    
        def next_epoch(self, now):
            self.community = defaultdict(frozenset)
            edges_to_keep = []
            self._cbc_memo = {}
    
            for a, b, start in self.graph.edges(data='start'):
                if start > -1:
                    self.set_link(a, b, False, now)
                    edges_to_keep.append((a, b))
    
            self.old_graph = self.graph
            self.graph = nx.Graph()
            for a, b in edges_to_keep:
                self.set_link(a, b, True, now)
    
            return self.old_graph
    
        def __getitem__(self, node):
            return self.community[node]
    
        def get_lp(self, node):
            '''local popularity of a node'''
            if node not in self.old_graph:
                return 0
    
            edges = self.old_graph[node]
            community = self[node]
            return sum([
                edge['duration']
                for other, edge in edges.items()
                if other in community
            ])
    
        def get_gp(self, node):
            '''global popularity of a node'''
            if node not in self.old_graph:
                return 0
    
    
            edges = self.old_graph[node]
            community = self[node]
    
    Jarrod Pas's avatar
    Jarrod Pas committed
            return sum([
                edge['duration']
                for other, edge in edges.items()
                if other not in community
            ])
    
        def get_ui(self, node):
            '''unique interactions with a node'''
            if node not in self.old_graph:
                return 0
    
            edges = self.old_graph[node]
            community = self[node]
            return len([
                other
                for other in edges
                if other in community
            ])
    
        def get_cbc(self, a, b):
            g = self.old_graph
            c_x = self[a]
            c_y = self[b]
            memo = (c_x, c_y)
    
            if a not in g or b not in g or c_x == c_y:
                return 0
    
            if memo in self._cbc_memo:
                return self._cbc_memo[memo]
    
            cbc = sum([
                g[x][y]['duration']
                for x, y in product(c_x, c_y)
                if y in g[x]
            ])
    
            '''
            for x in c_x:
                for y in c_y:
                    if x in g[y]:
                        cbc += g[x][y]['duration']
            '''
    
            self._cbc_memo[memo] = cbc
            self._cbc_memo[(memo[1], memo[0])] = cbc
            return cbc
    
        def get_ncf(self, x, b):
            g = self.old_graph
            c_y = self[b]
    
            if x not in g or b not in g:
                return 0
    
            return sum([
                g[x][y]['duration']
                for y in c_y
                if y in g[x]
            ])
    
    
    class KCliqueCommunity(EpochCommunity):
        def __init__(self, k=3, threshold=300, epoch=604800, **kwargs):
            super().__init__(epoch=epoch, **kwargs)
            self.k = k
            self.threshold = threshold
    
    
    Jarrod Pas's avatar
    Jarrod Pas committed
        def process(self, network):
    
    Jarrod Pas's avatar
    Jarrod Pas committed
            while True:
    
    Jarrod Pas's avatar
    Jarrod Pas committed
                yield self.env.timeout(self.tick)
    
    Jarrod Pas's avatar
    Jarrod Pas committed
                g = self.next_epoch(env.now)
    
                G = nx.Graph()
                G.add_nodes_from(g.nodes())
                for a, b, duration in g.edges(data='duration'):
                    if duration > self.threshold:
                        G.add_edge(a, b)
    
                for community in nx.k_clique_communities(G, self.k):
                    for node in community:
                        self.community[node] = community
    
    
    class LouvainCommunity(EpochCommunity):
        def __init__(self, epoch=604800, **kwargs):
            super().__init__(epoch=epoch, **kwargs)
    
    
    Jarrod Pas's avatar
    Jarrod Pas committed
        def process(self, network):
    
    Jarrod Pas's avatar
    Jarrod Pas committed
            while True:
    
    Jarrod Pas's avatar
    Jarrod Pas committed
                yield self.env.timeout(self.tick)
    
    Jarrod Pas's avatar
    Jarrod Pas committed
                g = self.next_epoch(env.now)
    
                p = louvain_partition(g, weight='duration')
                communities = defaultdict(set)
                for node, c in louvain_partition(g, weight='duration').items():
                    communities[c].add(node)
    
                for community in communities.values():
                    community = frozenset(community)
                    for node in community:
                        self.community[node] = community
    
    
    def none(**kwargs):
        return None
    
    
    types = {
        'kclique': KCliqueCommunity,
        'louvain': LouvainCommunity,
        'none': none,
    }