From 506911d863e2a3d5ff5ddae04cff47ccd72e94fb Mon Sep 17 00:00:00 2001 From: Jarrod Pas <j.pas@usask.ca> Date: Thu, 27 Jul 2017 14:56:16 -0600 Subject: [PATCH] Refactors routers to callable objects --- pydtn/network.py | 50 +++++++++------ pydtn/routers/__init__.py | 20 +++--- pydtn/routers/base.py | 15 +++++ pydtn/routers/bubble.py | 55 +++++------------ pydtn/routers/community.py | 73 ++++++++++++++++++++++ pydtn/routers/direct.py | 16 +++-- pydtn/routers/epidemic.py | 38 ++++++++---- pydtn/routers/flooding.py | 13 ++-- pydtn/routers/hcbf.py | 122 +++++++++++-------------------------- 9 files changed, 214 insertions(+), 188 deletions(-) create mode 100644 pydtn/routers/base.py create mode 100644 pydtn/routers/community.py diff --git a/pydtn/network.py b/pydtn/network.py index bc2c26b..c624da7 100644 --- a/pydtn/network.py +++ b/pydtn/network.py @@ -1,4 +1,4 @@ -from collections import OrderedDict +from collections import defaultdict, Iterable, OrderedDict from itertools import combinations import random @@ -40,7 +40,7 @@ class Network: if node_factory is None: node_factory = NodeFactory(tick_rate=1, router=routers['direct']) self.nodes = [ - node_factory(env, self, nid) + node_factory(self, nid) for nid in range(self.trace.nodes) ] self.links = [ @@ -91,37 +91,46 @@ class Network: def NodeFactory(router, **kwargs): - def factory(env, network, nid): - return Node(env, network, nid, router=router, **kwargs) + def factory(network, nid): + return Node(network, nid, router=router, **kwargs) return factory class Node(TickProcess): '''''' - def __init__(self, env, network, nid, + def __init__(self, network, nid, buffer_size=None, tick_time=1, router=None): '''''' super().__init__(tick_time) - self.env = env - + self.env = network.env self.network = network self.id = nid self.buffer = Buffer(self.env, capacity=buffer_size) - # bind router as a class method if router is None: router = routers['direct'] - self.router = router #router.__get__(self, Node) - self.router_state = {} + self.router = router(self) - self.start(env) + self.start(self.env) def route_packets(self): packets_to_delete = [] for packet in self.buffer: - if self.router(self, packet, self.router_state): + targets, reason, delete = self.router(packet) + + # check if there are targets to send to + if targets is None: + continue + + # allow for targets to be iterable or single item + if not isinstance(targets, Iterable): + targets = [targets] + + self.send(targets, packet, reason) + + if delete: packets_to_delete.append(packet) for packet in packets_to_delete: @@ -134,10 +143,11 @@ class Node(TickProcess): self.route_packets() yield self.tick() - def send(self, to, packet, reason=None): + def send(self, targets, packet, reason): # TODO: transfer delay - packet.send(self, to, reason=reason) - self.network.send_link(self, to, packet) + packet.send(self, targets, reason=reason) + for target in targets: + self.network.send_link(self, target, packet) def recv(self, packet): if packet.destination == self: @@ -269,18 +279,20 @@ class Packet: self.path = [] - self.stats = dict() + self.stats = defaultdict(int) self.sent = 0 self.recieved = False self.duplicates = 0 self.dropped = 0 - def send(self, a, b, reason=None): + def send(self, sender, targets, reason=None): + target_ids = [ n.id for n in targets ] if reason is None: - self.path.append((a.id, b.id)) + self.path.append((sender.id, target_ids)) else: - self.path.append((a.id, b.id, reason)) + self.stats[f'send_{reason}'] += 1 + self.path.append((sender.id, target_ids, reason)) self.sent += 1 def recv(self): diff --git a/pydtn/routers/__init__.py b/pydtn/routers/__init__.py index 04c66c2..ce03567 100644 --- a/pydtn/routers/__init__.py +++ b/pydtn/routers/__init__.py @@ -1,14 +1,14 @@ -from .bubble import bubble -from .direct import direct -from .epidemic import epidemic -from .flooding import flooding -from .hcbf import hcbf +from .bubble import BubbleRouter +from .direct import DirectRouter +from .epidemic import EpidemicRouter +from .flooding import FloodingRouter +from .hcbf import HCBFRouter types = { - 'bubble': bubble, - 'direct': direct, - 'epidemic': epidemic, - 'flooding': flooding, - 'hcbf': hcbf, + 'bubble': BubbleRouter, + 'direct': DirectRouter, + 'epidemic': EpidemicRouter, + 'flooding': FloodingRouter, + 'hcbf': HCBFRouter, } diff --git a/pydtn/routers/base.py b/pydtn/routers/base.py new file mode 100644 index 0000000..2fb3fc5 --- /dev/null +++ b/pydtn/routers/base.py @@ -0,0 +1,15 @@ +from collections import namedtuple + +class Router: + ''' + Base Router class + ----------------- + All routers are callable objects + ''' + def __init__(self, node, **kwargs): + self.env = node.env + self.node = node + + def __call__(self, packet): + raise NotImplementedError + diff --git a/pydtn/routers/bubble.py b/pydtn/routers/bubble.py index 2cc740b..23afadf 100644 --- a/pydtn/routers/bubble.py +++ b/pydtn/routers/bubble.py @@ -1,46 +1,19 @@ -def bubble(self, packet, state): - '''''' - if 'bubble_lp' not in packet.stats: - packet.stats = { - f'bubble_{reason}': 0 - for reason in ['direct', 'lp', 'gp'] - } +from .community import CommunityRouter +class BubbleRouter(CommunityRouter): + def __call__(self, packet): + if packet.destination in self.node.links: + return packet.destination, 'direct', True - stats = packet.stats - dest = packet.destination - community = self.network.community + if self.local_community: + best, lp = self.best_lp() + if lp > self.lp(): + return best, 'lp', True - def send(to, reason): - stats[f'bubble_{reason}'] += 1 - self.send(to, packet, reason=reason) + elif self.not_local_community: + best, gp = self.best_gp() + if gp > self.gp(): + return best, 'gp', True - if dest in self.links: - send(dest, 'direct') - return True - - lp = community.get_lp - gp = community.get_gp - - local_community = [ - met for met in self.links if met in self.community - ] - - not_local_community = [ - met for met in self.links if met not in self.community - ] - - if local_community: - max_lp = max(local_community, key=lp) - if lp(max_lp) > lp(self): - send(max_lp, 'lp') - return True - - elif not_local_community: - max_gp = max(not_local_community, key=gp) - if gp(max_gp) > gp(self): - send(max_gp, 'gp') - return True - - return False + return None, None, False diff --git a/pydtn/routers/community.py b/pydtn/routers/community.py new file mode 100644 index 0000000..8e343e2 --- /dev/null +++ b/pydtn/routers/community.py @@ -0,0 +1,73 @@ +from pydtn.communities import LouvainCommunity + +from .base import Router + +class CommunityRouter(Router): + def __init__(self, node, **kwargs): + super().__init__(node, **kwargs) + + self.community = self.node.network.community + + @property + def local_community(self): + links = self.node.links + community = self.node.community + return [met for met in links if met in community] + + @property + def not_local_community(self): + links = self.node.links + community = self.node.community + return [met for met in links if met not in community] + + def best(self, nodes, key): + best = max(nodes, key=key) + return best, key(best) + + def ui(self): + return self.get_ui(self.node) + + def get_ui(self, x): + return self.community.get_ui(x) + + def best_ui(self): + return self.best(self.local_community, self.get_ui) + + def lp(self): + return self.get_lp(self.node) + + def get_lp(self, x): + return self.community.get_lp(x) + + def best_lp(self): + return self.best(self.local_community, self.get_lp) + + def gp(self): + return self.get_gp(self.node) + + def get_gp(self, x): + return self.community.get_gp(x) + + def best_gp(self): + return self.best(self.not_local_community, self.get_gp) + + def cbc(self, c_y): + return self.get_cbc(self.node, c_y) + + def get_cbc(self, x, c_y): + return self.community.get_cbc(x.community, c_y) + + def best_cbc(self, c_y): + get_cbc = lambda x: self.get_cbc(x, c_y) + return self.best(self.not_local_community, get_cbc) + + def ncf(self, c_y): + return self.get_ncf(self.node, c_y) + + def get_ncf(self, x, c_y): + return self.community.get_ncf(x, c_y) + + def best_ncf(self, c_y): + get_ncf = lambda x: self.get_ncf(x, c_y) + return self.best(self.local_community, get_ncf) + diff --git a/pydtn/routers/direct.py b/pydtn/routers/direct.py index 29f2518..76b314a 100644 --- a/pydtn/routers/direct.py +++ b/pydtn/routers/direct.py @@ -1,10 +1,8 @@ -def direct(self, packet, state): - ''' - Routes a packet via direct contact to the destination. - Returns True if the packet was sent successfully, otherwise False. - ''' - for met in self.links: - if packet.destination == met: - self.send(met, packet) - return False +from .base import Router + +class DirectRouter(Router): + def __call__(self, packet): + if packet.destination in self.node.links: + return packet.destination, 'direct', True + return None, None, False diff --git a/pydtn/routers/epidemic.py b/pydtn/routers/epidemic.py index 1047bb7..5605d7e 100644 --- a/pydtn/routers/epidemic.py +++ b/pydtn/routers/epidemic.py @@ -1,17 +1,29 @@ from collections import defaultdict -def epidemic(self, packet, state): - ''' - Routes a packet if it hasn't sent the packet on a link yet. - Always returns False. - ''' - if 'sent' not in state: - state['sent'] = defaultdict(dict) - sent = state['sent'] +from .base import Router - for met in self.links: - if packet not in sent[met]: - sent[met][packet] = True - self.send(met, packet) - return False +class EpidemicRouter(Router): + def __init__(self, node, **kwargs): + super().__init__(node, **kwargs) + + # maps a packet to a set of nodes that the packet has been sent to + self.sent = defaultdict(set) + + def __call__(self, packet): + # get list of currently encountered nodes that do not have the packet + targets = [ + met + for met in self.node.links + if met not in self.sent[packet] + ] + + # update set of nodes a packet was sent to + self.sent[packet].update(targets) + + # return list of nodes to send packet to + # (targets, remove from buffer, reason) + if targets: + return targets, 'epidemic', False + else: + return None, None, False diff --git a/pydtn/routers/flooding.py b/pydtn/routers/flooding.py index 6b9dded..5ddf6ce 100644 --- a/pydtn/routers/flooding.py +++ b/pydtn/routers/flooding.py @@ -1,9 +1,6 @@ -def flooding(self, packet, state): - ''' - Routes a packet via flooding, i.e. sends all packets on all links. - Always returns False. - ''' - for met in self.links: - self.send(met, packet) - return False +from .base import Router + +class FloodingRouter(Router): + def __call__(self, packet): + return self.node.links, 'flood', False diff --git a/pydtn/routers/hcbf.py b/pydtn/routers/hcbf.py index c8af6dc..442ee51 100644 --- a/pydtn/routers/hcbf.py +++ b/pydtn/routers/hcbf.py @@ -1,89 +1,35 @@ -def hcbf(self, packet, state): - if 'hcbf_ui' not in packet.stats: - packet.stats = { - f'hcbf_{reason}': 0 - for reason in ['direct', 'ui', 'lp','ui_lonely', 'lp_lonely', - 'cbc', 'ncf' ] - } - - stats = packet.stats - dest = packet.destination - community = self.network.community - - def send(to, reason): - stats[f'hcbf_{reason}'] += 1 - self.send(to, packet, reason=reason) - - # case 1: direct delivery - if dest in self.links: - send(dest, 'direct') - return True - - ui = community.get_ui - lp = community.get_lp - cbc = lambda n: community.get_cbc(n.community, dest.community) - ncf = lambda n: community.get_ncf(n, dest.community) - - local_community = [ - met for met in self.links if met in self.community - ] - - not_local_community = [ - met for met in self.links if met not in self.community - ] - - if self.community is dest.community and local_community: - max_ui = max(local_community, key=ui) - if ui(max_ui) > ui(self): - send(max_ui, 'ui') - return True - elif ui(max_ui) < ui(self): - return False - # ui(max_ui) == ui(self) - - max_lp = max(local_community, key=lp) - if lp(max_lp) > lp(self): - send(max_lp, 'lp') - return True - elif lp(max_lp) < lp(self): - return False - # lp(max_lp) == lp(self) - - return False - - if not_local_community: - max_cbc = max(not_local_community, key=cbc) - if cbc(max_cbc) > cbc(self): - send(max_cbc, 'cbc') - return True - elif cbc(max_cbc) < cbc(self): - return False - # cbc(max_cbc) == cbc(self) - - if local_community: - max_ncf = max(local_community, key=ncf) - if ncf(max_ncf) > ncf(self): - send(max_ncf, 'ncf') - return True - elif ncf(max_ncf) < ncf(self): - return False - # ncf(max_ncf) == ncf(self) - - max_ui = max(local_community, key=ui) - if ui(max_ui) > ui(self): - send(max_ui, 'ui_lonely') - return True - elif ui(max_ui) < ui(self): - return False - # ui(max_ui) == ui(self) - - max_lp = max(local_community, key=lp) - if lp(max_lp) > lp(self): - send(max_lp, 'lp_lonely') - return True - elif lp(max_lp) < lp(self): - return False - # lp(max_lp) == lp(self) - - return False +from .community import CommunityRouter + +class HCBFRouter(CommunityRouter): + def __call__(self, packet): + me = self.node + dest = packet.destination + + if dest in me.links: + return dest, 'direct', True + + if me.community is not dest.community and self.not_local_community: + best, cbc = self.best_cbc(dest.community) + if cbc > self.cbc(dest.community): + return best, 'cbc', True + + if self.local_community: + if me.community is not dest.community: + best, ncf = self.best_ncf(dest.community) + if ncf > self.ncf(dest.community): + return best, 'ncf', True + elif ncf < self.ncf(dest.community): + return None, None, False + + best, ui = self.best_ui() + if ui > self.ui(): + return best, 'ui', True + elif ui < self.ui(): + return None, None, False + + best, lp = self.best_lp() + if lp > self.lp(): + return best, 'lp', True + + return None, None, False -- GitLab