CoolFace
Apppublic

johnosborne/splay-network-tracking-detection

sourceHugging Facemitupdated 10mo agoView on Hugging Face
0likes
simulation.py144 linesDownload Raw Back to root
1import math2import random3from collections import defaultdict4 5# --- Configuration (Defaults) ---6AREA_SIZE = 1000.07N_CLUSTERS = 58T_MAX = 309ALPHA = 0.5; BETA = 0.3; KAPPA = 0.1; ZETA = 0.110 11def dist(a, b): return math.hypot(a[0]-b[0], a[1]-b[1])12 13class Node:14    def __init__(self, idx, pos, cluster):15        self.idx = idx; self.pos = pos; self.cluster = int(cluster)16        self.batt = 100.0; self.dead = False; self.S = 0.0; self.fair = 0.017        self.is_head = False; self.head_since = 018        self.dead_since = None19 20    def get_color(self):21        if self.dead: return '#ff0000' # Red22        if self.batt > 50: return '#00ff00' # Green23        if self.batt > 20: return '#ffff00' # Yellow24        return '#ff9900' # Orange25 26    def consume(self, amount, sim_time):27        if self.dead: return28        self.batt -= amount29        if self.batt <= 0: 30            self.batt = 0; self.dead = True; self.is_head = False31            self.dead_since = sim_time32 33def calculate_utility(node, gateway):34    term_S = min(node.S, 1.0); term_E = node.batt / 100.035    term_fair = min(node.fair, 1.0)36    d_gate = dist(node.pos, gateway)37    term_lq = 1.0 - (d_gate / (AREA_SIZE * 1.414))38    return ALPHA*term_S + BETA*term_E + KAPPA*term_fair + ZETA*term_lq39 40class Simulation:41    def __init__(self, n_nodes=50):42        self.n_nodes = n_nodes43        self.rng = random.Random() # New random instance44        self.nodes = []45        self.clusters = defaultdict(list)46        self.current_heads = {}47        self.sim_time = 048        self.gateway = (AREA_SIZE/2, AREA_SIZE/2)49        self.reset(n_nodes)50 51    def reset(self, n_nodes):52        self.n_nodes = int(n_nodes)53        self.sim_time = 054        55        # Setup Network56        centers = [(self.rng.uniform(100, AREA_SIZE-100), self.rng.uniform(100, AREA_SIZE-100)) for _ in range(N_CLUSTERS)]57        self.nodes = []58        for i in range(self.n_nodes):59            c_idx = self.rng.randint(0, N_CLUSTERS-1)60            cx, cy = centers[c_idx]61            nx = cx + self.rng.gauss(0, 80)62            ny = cy + self.rng.gauss(0, 80)63            pos = (max(0, min(AREA_SIZE, nx)), max(0, min(AREA_SIZE, ny)))64            self.nodes.append(Node(i, pos, c_idx))65 66        self.clusters = defaultdict(list)67        for n in self.nodes: self.clusters[n.cluster].append(n)68        self.current_heads = {c: None for c in self.clusters}69 70    def step(self):71        # Run logic 1x per frame to slow down backend progression too, or keep it 3x? 72        # User said "simulation is going really fast", often better to slow down updates.73        # Let's reduce internal ticks to 1 per step call as well.74        for _ in range(1):75            self._run_simulation_step()76        77        return self.get_state()78 79    def _run_simulation_step(self):80        self.sim_time += 181        ev_pos = (self.rng.uniform(0, AREA_SIZE), self.rng.uniform(0, AREA_SIZE))82 83        for n in self.nodes:84            if n.dead: continue85            n.consume(0.2, self.sim_time)86            n.S *= 0.987            if not n.is_head: n.fair += 0.0288            if dist(n.pos, ev_pos) < 150: n.S += 0.8; n.consume(0.5, self.sim_time)89 90        for c_id, members in self.clusters.items():91            head = self.current_heads.get(c_id)92            if head is None or head.dead or (head and (self.sim_time - head.head_since) > T_MAX):93                candidates = [n for n in members if not n.dead]94                if candidates:95                    winner = max(candidates, key=lambda n: calculate_utility(n, self.gateway))96                    if head and head != winner: head.is_head = False97                    self.current_heads[c_id] = winner; winner.is_head = True98                    winner.head_since = self.sim_time; winner.fair = 0.0; winner.consume(1.5, self.sim_time)99 100    def get_state(self):101        # Return serializable state102        nodes_data = []103        links = []104        dead_nodes_stats = []105        106        for n in self.nodes:107            color = n.get_color()108            109            # Logic for links: if not head, link to head110            if not n.is_head and not n.dead:111                head = self.current_heads.get(n.cluster)112                if head and not head.dead:113                    links.append({114                        'start': n.pos,115                        'end': head.pos116                    })117            118            if n.dead:119                downtime = self.sim_time - n.dead_since if n.dead_since is not None else 0120                dead_nodes_stats.append({121                    'id': n.idx,122                    'dead_since': n.dead_since,123                    'downtime': downtime124                })125 126            nodes_data.append({127                'id': n.idx,128                'x': n.pos[0],129                'y': n.pos[1],130                'color': color,131                'is_head': n.is_head,132                'dead': n.dead,133                'batt': n.batt,134                'cluster': n.cluster135            })136            137        return {138            'sim_time': self.sim_time,139            'gateway': self.gateway,140            'nodes': nodes_data,141            'links': links,142            'dead_stats': dead_nodes_stats143        }144