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

Refactors routers to callable objects

parent 3c446d4e
No related branches found
No related tags found
1 merge request!3Version 0.2
from collections import OrderedDict from collections import defaultdict, Iterable, OrderedDict
from itertools import combinations from itertools import combinations
import random import random
...@@ -40,7 +40,7 @@ class Network: ...@@ -40,7 +40,7 @@ class Network:
if node_factory is None: if node_factory is None:
node_factory = NodeFactory(tick_rate=1, router=routers['direct']) node_factory = NodeFactory(tick_rate=1, router=routers['direct'])
self.nodes = [ self.nodes = [
node_factory(env, self, nid) node_factory(self, nid)
for nid in range(self.trace.nodes) for nid in range(self.trace.nodes)
] ]
self.links = [ self.links = [
...@@ -91,37 +91,46 @@ class Network: ...@@ -91,37 +91,46 @@ class Network:
def NodeFactory(router, **kwargs): def NodeFactory(router, **kwargs):
def factory(env, network, nid): def factory(network, nid):
return Node(env, network, nid, router=router, **kwargs) return Node(network, nid, router=router, **kwargs)
return factory return factory
class Node(TickProcess): class Node(TickProcess):
'''''' ''''''
def __init__(self, env, network, nid, def __init__(self, network, nid,
buffer_size=None, tick_time=1, router=None): buffer_size=None, tick_time=1, router=None):
'''''' ''''''
super().__init__(tick_time) super().__init__(tick_time)
self.env = env self.env = network.env
self.network = network self.network = network
self.id = nid self.id = nid
self.buffer = Buffer(self.env, capacity=buffer_size) self.buffer = Buffer(self.env, capacity=buffer_size)
# bind router as a class method
if router is None: if router is None:
router = routers['direct'] router = routers['direct']
self.router = router #router.__get__(self, Node) self.router = router(self)
self.router_state = {}
self.start(env) self.start(self.env)
def route_packets(self): def route_packets(self):
packets_to_delete = [] packets_to_delete = []
for packet in self.buffer: 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) packets_to_delete.append(packet)
for packet in packets_to_delete: for packet in packets_to_delete:
...@@ -134,10 +143,11 @@ class Node(TickProcess): ...@@ -134,10 +143,11 @@ class Node(TickProcess):
self.route_packets() self.route_packets()
yield self.tick() yield self.tick()
def send(self, to, packet, reason=None): def send(self, targets, packet, reason):
# TODO: transfer delay # TODO: transfer delay
packet.send(self, to, reason=reason) packet.send(self, targets, reason=reason)
self.network.send_link(self, to, packet) for target in targets:
self.network.send_link(self, target, packet)
def recv(self, packet): def recv(self, packet):
if packet.destination == self: if packet.destination == self:
...@@ -269,18 +279,20 @@ class Packet: ...@@ -269,18 +279,20 @@ class Packet:
self.path = [] self.path = []
self.stats = dict() self.stats = defaultdict(int)
self.sent = 0 self.sent = 0
self.recieved = False self.recieved = False
self.duplicates = 0 self.duplicates = 0
self.dropped = 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: if reason is None:
self.path.append((a.id, b.id)) self.path.append((sender.id, target_ids))
else: 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 self.sent += 1
def recv(self): def recv(self):
......
from .bubble import bubble from .bubble import BubbleRouter
from .direct import direct from .direct import DirectRouter
from .epidemic import epidemic from .epidemic import EpidemicRouter
from .flooding import flooding from .flooding import FloodingRouter
from .hcbf import hcbf from .hcbf import HCBFRouter
types = { types = {
'bubble': bubble, 'bubble': BubbleRouter,
'direct': direct, 'direct': DirectRouter,
'epidemic': epidemic, 'epidemic': EpidemicRouter,
'flooding': flooding, 'flooding': FloodingRouter,
'hcbf': hcbf, 'hcbf': HCBFRouter,
} }
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
def bubble(self, packet, state): from .community import CommunityRouter
''''''
if 'bubble_lp' not in packet.stats:
packet.stats = {
f'bubble_{reason}': 0
for reason in ['direct', 'lp', 'gp']
}
class BubbleRouter(CommunityRouter):
def __call__(self, packet):
if packet.destination in self.node.links:
return packet.destination, 'direct', True
stats = packet.stats if self.local_community:
dest = packet.destination best, lp = self.best_lp()
community = self.network.community if lp > self.lp():
return best, 'lp', True
def send(to, reason): elif self.not_local_community:
stats[f'bubble_{reason}'] += 1 best, gp = self.best_gp()
self.send(to, packet, reason=reason) if gp > self.gp():
return best, 'gp', True
if dest in self.links: return None, None, False
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
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)
def direct(self, packet, state): from .base import Router
'''
Routes a packet via direct contact to the destination. class DirectRouter(Router):
Returns True if the packet was sent successfully, otherwise False. def __call__(self, packet):
''' if packet.destination in self.node.links:
for met in self.links: return packet.destination, 'direct', True
if packet.destination == met: return None, None, False
self.send(met, packet)
return False
from collections import defaultdict from collections import defaultdict
def epidemic(self, packet, state): from .base import Router
'''
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']
for met in self.links: class EpidemicRouter(Router):
if packet not in sent[met]: def __init__(self, node, **kwargs):
sent[met][packet] = True super().__init__(node, **kwargs)
self.send(met, packet)
return False # 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
def flooding(self, packet, state): from .base import Router
'''
Routes a packet via flooding, i.e. sends all packets on all links. class FloodingRouter(Router):
Always returns False. def __call__(self, packet):
''' return self.node.links, 'flood', False
for met in self.links:
self.send(met, packet)
return False
def hcbf(self, packet, state): from .community import CommunityRouter
if 'hcbf_ui' not in packet.stats:
packet.stats = { class HCBFRouter(CommunityRouter):
f'hcbf_{reason}': 0 def __call__(self, packet):
for reason in ['direct', 'ui', 'lp','ui_lonely', 'lp_lonely', me = self.node
'cbc', 'ncf' ] dest = packet.destination
}
if dest in me.links:
stats = packet.stats return dest, 'direct', True
dest = packet.destination
community = self.network.community if me.community is not dest.community and self.not_local_community:
best, cbc = self.best_cbc(dest.community)
def send(to, reason): if cbc > self.cbc(dest.community):
stats[f'hcbf_{reason}'] += 1 return best, 'cbc', True
self.send(to, packet, reason=reason)
if self.local_community:
# case 1: direct delivery if me.community is not dest.community:
if dest in self.links: best, ncf = self.best_ncf(dest.community)
send(dest, 'direct') if ncf > self.ncf(dest.community):
return True return best, 'ncf', True
elif ncf < self.ncf(dest.community):
ui = community.get_ui return None, None, False
lp = community.get_lp
cbc = lambda n: community.get_cbc(n.community, dest.community) best, ui = self.best_ui()
ncf = lambda n: community.get_ncf(n, dest.community) if ui > self.ui():
return best, 'ui', True
local_community = [ elif ui < self.ui():
met for met in self.links if met in self.community return None, None, False
]
best, lp = self.best_lp()
not_local_community = [ if lp > self.lp():
met for met in self.links if met not in self.community return best, 'lp', True
]
return None, None, False
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
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