├── .gitignore ├── .vscode └── settings.json ├── BapRuAlgorithmManager.py ├── README.md ├── algorithm.py ├── autorun.sh ├── connection.py ├── main.bat ├── main.sh ├── multiMain.py ├── plot.py ├── requirement.txt ├── runner.py ├── simulator ├── maps │ ├── map.net.xml │ └── traffic.sumocfg ├── route_manager.py └── simulator.py └── stats └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | log* 3 | stats/* 4 | !stats/.gitkeep 5 | simulator/maps/map.rou.xml -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/usr/bin/python3" 3 | } -------------------------------------------------------------------------------- /BapRuAlgorithmManager.py: -------------------------------------------------------------------------------- 1 | try: 2 | import traci 3 | except Exception: 4 | import os, sys 5 | if 'SUMO_HOME' in os.environ: 6 | tools = os.path.join(os.environ['SUMO_HOME'], 'tools') 7 | sys.path.append(tools) 8 | import traci 9 | else: 10 | sys.exit("please declare environment variable 'SUMO_HOME'") 11 | import re 12 | 13 | from algorithm import AlgorithmManager 14 | import math 15 | 16 | 17 | # todo name the constants properly 18 | class BapRuAlgorithmManager(AlgorithmManager): 19 | 20 | # static variable 21 | ACTIVATE_SWITCH = True 22 | REDUCE_BACKWARD_MESSAGE = True 23 | PERIOD_LEADER_STABLE = 0.1 # when the agent is not receiving conflict leader messages, then it set the period to a lower frequency 24 | # e.g., if original period is 100ms , then the broadcast period after convergence will be PERIOD_LEADER_STABLE seconds 25 | PERIOD_LEADER_NOT_STABLE = 0.1 26 | HEARTBEAT_FACTOR = 2 27 | THRESHOLD_DEC_FREQ_MSG = 0.5 # after this time being alone, the leader decrease the period of broadcasting 28 | def __init__(self, vehicle): 29 | super(BapRuAlgorithmManager, self).__init__(vehicle) 30 | 31 | self.counter = 0 32 | self.last_received_leader_message_time = self.simulator.time 33 | self.num_spam = 0 34 | self.max_spam_number = 3 35 | self.leader_switch_count = 0 36 | 37 | self.max_dis_switch_leader = 30 38 | self.max_leader_force_time = 3 39 | 40 | self.init_leader() 41 | 42 | def pre_step(self): 43 | return 44 | 45 | def post_step(self): 46 | return 47 | 48 | def init_leader(self, pos_vehicles = {}, force_time = 0): 49 | self.leader = self.id 50 | self.last_lead_msg_sent = 0 51 | self.time_leader = 0 52 | self.time_alone = 0 53 | self.lead_msg_dt = BapRuAlgorithmManager.PERIOD_LEADER_NOT_STABLE 54 | self.last_msg_received = self.create_leader_msg() 55 | self.dis_to_leader = 0 56 | 57 | self.pos_vehicles = pos_vehicles 58 | self.force_time = force_time 59 | 60 | # when a car follow a leader, then it must addapt his silent 61 | # time according to the lead msg freq 62 | def silentTime(self): 63 | return self.last_msg_received["lead_msg_dt"] * BapRuAlgorithmManager.HEARTBEAT_FACTOR 64 | 65 | 66 | def handle_leader_msg(self, msg): 67 | 68 | if msg["leader_id"] == self.id and self.is_leader(): 69 | return 70 | 71 | if msg["leader_id"] != self.id and self.is_leader(): 72 | self.time_alone = 0 73 | 74 | if msg["leader_id"] != self.leader: 75 | need_to_change_leader = self.selfCompare(msg) 76 | 77 | # in case two car can't decide to be a leader 78 | # this condition is not probable in real life 79 | if self.is_leader() and abs(msg["lane_position"] - self.vehicle.lane_position) < 0.5: 80 | if self.num_spam >= self.max_spam_number: 81 | self.num_spam = 0 82 | need_to_change_leader = msg["leader_id"] < self.last_msg_received["leader_id"] 83 | 84 | if not need_to_change_leader: 85 | self.num_spam += 1 86 | 87 | if msg["force_leader"]: 88 | need_to_change_leader = True 89 | 90 | # this is the only place we can change the leader 91 | if need_to_change_leader: 92 | self.leader = msg["leader_id"] 93 | self.last_received_leader_message_time = self.simulator.time 94 | self.last_msg_received = msg 95 | 96 | return 97 | 98 | if not self.is_leader() \ 99 | and msg["leader_id"] == self.leader: 100 | 101 | # decide if the message have to be relayed 102 | if msg["sequence_number"] > self.last_msg_received["sequence_number"]: 103 | 104 | if(not self.neighbors().issubset(self.last_msg_received["visited"])): 105 | # broadcast the message 106 | self.add_neighbors_to_visited() 107 | self.broadcast(self.last_msg_received) 108 | 109 | self.dis_to_leader = min(msg["dis_to_leader"] + 1, self.dis_to_leader) 110 | self.last_received_leader_message_time = self.simulator.time 111 | self.last_msg_received = msg 112 | else: 113 | self.add_new_visited(msg) 114 | self.dis_to_leader = msg["dis_to_leader"] + 1 115 | 116 | # replace with the function you want 117 | # you may need to modify create leader msg if you want to have more information on the leader 118 | def selfCompare(self, msg): 119 | return self.compare(msg, self.last_msg_received) 120 | 121 | def broadcast(self, msg): 122 | # keep up with simulation stats 123 | type_msg = msg["type_msg"] 124 | self.simulator.num_broadcast[type_msg] = self.simulator.num_broadcast.get(type_msg, 0) + 1 125 | if type_msg == "leader_msg" and self.is_leader(): 126 | self.simulator.num_broadcast["original_leader_msg"] = self.simulator.num_broadcast.get("original_leader_msg", 0) + 1 127 | 128 | self.connection_manager.broadcast(msg) 129 | 130 | # replace with the function you want 131 | # you may need to modify create leader msg if you want to have more information on the leader 132 | def compare(self, msg1, msg2): 133 | return msg1["lane_position"] < msg2["lane_position"] 134 | 135 | def neighbors(self): 136 | return set(self.connection_manager.connected_list) 137 | 138 | def create_leader_msg(self): 139 | # [message_type, leader id, distance to intersecrion center, set of visited car, 140 | # id of leader message, frequence between lead messages, 141 | # dis from the leader in leader graph, force the leader to stay leader] 142 | return { 143 | "type_msg": "leader_msg", 144 | "leader_id": self.id, 145 | "lane_position": self.vehicle.lane_position, 146 | "visited": set(self.connection_manager.connected_list), 147 | "sequence_number": self.counter, 148 | "lead_msg_dt": self.lead_msg_dt, 149 | "dis_to_leader": 0, 150 | "force_leader": False 151 | } 152 | 153 | # the 2 next function are used to change the set of visited car in leader message 154 | 155 | def add_new_visited(self, msg): 156 | self.last_msg_received["visited"] = msg["visited"].union(self.last_msg_received["visited"]) 157 | 158 | def add_neighbors_to_visited(self): 159 | self.last_msg_received["visited"] = self.neighbors().union(self.last_msg_received["visited"]) 160 | self.last_msg_received["dis_to_leader"] += 1 161 | 162 | # handle position messages 163 | def handle_pos_msg(self, msg): 164 | if msg["leader_id"] != self.leader: 165 | return 166 | 167 | if self.is_leader(): 168 | # record the pos of all car 169 | self.pos_vehicles[msg["vehicle_id"]] = msg 170 | 171 | if not BapRuAlgorithmManager.REDUCE_BACKWARD_MESSAGE: 172 | if self.id not in msg["visited"]: 173 | msg["visited"].add(self.id) 174 | self.broadcast(msg) 175 | # relay it only if the car which received is closer to the leader 176 | elif msg["dis_to_leader"] < self.dis_to_leader: 177 | msg["dis_to_leader"] = self.dis_to_leader 178 | self.broadcast(msg) 179 | 180 | 181 | # postion messages 182 | def create_pos_msg(self): 183 | # [type message, vehicle id, leader id, distance from the center, original lane 184 | #, dis to leader in position message graph, direction, time message sent] 185 | return { 186 | "type_msg": "pos_msg", 187 | "vehicle_id": self.id, 188 | "leader_id": self.leader, 189 | "lane_position": self.vehicle.lane_position, 190 | "original_lane": self.vehicle.original_lane, 191 | "visited": set(), 192 | "dis_to_leader": self.dis_to_leader, 193 | "direction": self.vehicle.get_direction(), 194 | "time": self.simulator.time 195 | } 196 | 197 | def leader_step(self): 198 | self.time_leader += self.simulator.deltaT 199 | self.time_alone += self.simulator.deltaT 200 | 201 | if self.last_lead_msg_sent >= self.lead_msg_dt: 202 | 203 | # the time alone is the time that have passed since the leader have not received any messages from 204 | # another leader 205 | 206 | if self.time_alone < BapRuAlgorithmManager.THRESHOLD_DEC_FREQ_MSG: 207 | # time alone too short, the leader is not stable 208 | self.lead_msg_dt = BapRuAlgorithmManager.PERIOD_LEADER_NOT_STABLE 209 | 210 | if self.time_alone >= BapRuAlgorithmManager.THRESHOLD_DEC_FREQ_MSG: 211 | # time alone long enouth, we can increase lead message period 212 | self.lead_msg_dt = BapRuAlgorithmManager.PERIOD_LEADER_STABLE 213 | 214 | 215 | 216 | self.last_msg_received = self.create_leader_msg() 217 | 218 | #force him to be the leader 219 | if self.time_leader < self.force_time: 220 | self.last_msg_received["lane_position"] = 0 221 | 222 | 223 | self.last_received_leader_message_time = self.simulator.time 224 | self.broadcast(self.last_msg_received) 225 | self.counter += 1 226 | self.last_lead_msg_sent = 0 227 | else: 228 | self.last_lead_msg_sent += self.simulator.deltaT 229 | 230 | self.pos_vehicles[self.id] = self.create_pos_msg() 231 | 232 | def step(self): 233 | 234 | for msg in self.connection_manager.curr_msg_buffer: 235 | # todo change force leader 236 | if msg["type_msg"] == "leader_msg" and msg["force_leader"] and msg["leader_id"] == self.id: 237 | self.init_leader(msg["pos_vehicles"], self.max_leader_force_time) 238 | break 239 | 240 | # if the car is his own leader 241 | if self.is_leader(): 242 | self.leader_step() 243 | else: 244 | self.broadcast(self.create_pos_msg()) 245 | 246 | for msg in self.connection_manager.curr_msg_buffer: 247 | if msg["type_msg"] == "leader_msg": 248 | self.handle_leader_msg(msg) 249 | 250 | if msg["type_msg"] == "pos_msg": 251 | self.handle_pos_msg(msg) 252 | 253 | # there is a timeout for receiving leader messages 254 | if not self.is_leader() \ 255 | and abs(self.last_received_leader_message_time - self.simulator.time) > self.silentTime(): 256 | # become a leader 257 | self.init_leader() 258 | 259 | def create_next_leader_msg(self, next_leader): 260 | # [message_type, leader id, distance to intersecrion center, set of visited car, 261 | # id of leader message, frequence between lead messages, 262 | # dis from the leader in leader graph, force the leader to stay leader] 263 | return { 264 | "type_msg": "leader_msg", 265 | "leader_id": next_leader, 266 | "lane_position": 0, 267 | "visited": set(self.connection_manager.connected_list), # todo what should we put here 268 | "sequence_number": self.counter, 269 | "lead_msg_dt": self.lead_msg_dt, 270 | "dis_to_leader": 0, 271 | "force_leader": True, 272 | "pos_vehicles": self.pos_vehicles 273 | } 274 | 275 | # get the closest on the given lane 276 | def get_next_leader_on_lane(self, pred): 277 | best_msg_pos = None 278 | 279 | for id_car, pos_msg in self.pos_vehicles.items(): 280 | 281 | # the car is not on the i,tersection anymore 282 | if abs(self.simulator.time - pos_msg["time"]) > 1: 283 | continue 284 | 285 | # take the best on a different lane 286 | if id_car != self.id and pos_msg["lane_position"] < self.max_dis_switch_leader \ 287 | and pred(best_msg_pos, pos_msg): 288 | best_msg_pos = pos_msg 289 | 290 | return None if best_msg_pos is None else best_msg_pos["vehicle_id"] 291 | 292 | # to select the next leader: 293 | # - we take the closest car in a different direction (n-s or e-w) in a 30m radius 294 | # - if there is none, then take the farthest car on the same direction (deacrese leader switch number) 295 | def get_next_leader(self): 296 | 297 | farthest_same_dir = \ 298 | lambda best_msg, msg: msg["direction"] == self.vehicle.direction \ 299 | and (best_msg is None or msg["lane_position"] > best_msg["lane_position"]) 300 | 301 | closest_diff_dir = \ 302 | lambda best_msg, msg: msg["direction"] != self.vehicle.direction \ 303 | and (best_msg is None or msg["lane_position"] < best_msg["lane_position"]) 304 | 305 | new_leader = self.get_next_leader_on_lane(closest_diff_dir) 306 | if not new_leader is None: 307 | return new_leader 308 | return self.get_next_leader_on_lane(farthest_same_dir) 309 | 310 | 311 | #this method is called when the car leave the intersection 312 | def leave_intersection(self): 313 | 314 | if not self.is_leader(): 315 | return 316 | 317 | new_leader = self.get_next_leader() 318 | 319 | if new_leader is None: 320 | return 321 | 322 | msg = self.create_next_leader_msg(new_leader) 323 | if BapRuAlgorithmManager.ACTIVATE_SWITCH: 324 | self.broadcast(msg) 325 | self.leader_switch_count+=1 326 | 327 | def get_leader_switch_count(self): 328 | return self.leader_switch_count -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simulation of leader selection algorithm for vehicular ad-hoc network: 2 | This is the simulation for leader selection algorithm, to see more details on the algorithm, visit the paper: 3 | https://arxiv.org/abs/1912.06776 4 | 5 | # Citing 6 | To cite the paper, use this Bibtex: 7 | ``` 8 | @inproceedings{zhang2019leader, 9 | title={Leader selection in Vehicular Ad-hoc Networks: a Proactive Approach}, 10 | author={Zhang, Rusheng and Jacquemot, Baptiste and Bakirci, Kagan and Bartholme, Sacha and Kaempf, Killian and Freydt, Baptiste and Montandon, Loic and Zhang, Shenqi and Tonguz, Ozan}, 11 | booktitle={2020 IEEE 88th Vehicular Technology Conference (VTC-Fall)}, 12 | year={2020}, 13 | organization={IEEE} 14 | } 15 | ``` 16 | To cite this code, you can use the following bibtex: 17 | ``` 18 | @misc{leaderselectionrepo, 19 | author = {Rusheng Zhang and Baptiste Jacquemot}, 20 | title = {Leader Selection Algorithm Repo}, 21 | year = {2019}, 22 | publisher = {GitHub}, 23 | journal = {GitHub repository}, 24 | howpublished = {\url{https://github.com/beedrill/leader-selection-simulation}}, 25 | } 26 | ``` 27 | 28 | 29 | # prerequisites 30 | - [SUMO](https://sumo.dlr.de/) version > 1.0 31 | - __SUMO_HOME__ environment parameter specified correctly 32 | - python packages: numpy, scipy, matplotlib, pandas, to install all required python packages, do: 33 | ```bash 34 | pip install -r requirement.txt 35 | ``` 36 | 37 | # Execute 38 | To execute the code, simply do: 39 | ```bash 40 | python runner.py 41 | ``` 42 | 43 | Algorithm parameters can be specified, check: 44 | ```bash 45 | python runner.py -h 46 | ``` 47 | 48 | # More details 49 | You can specify how to run the algorithm in more details, check __multiMain.py__ for example, it contains an example of doing multiple simulations, retrieve usefule data and calculate the average 50 | 51 | You write a batch script or bash script to use runner.py the way you want, check __main.sh__ and __main.bat__ for examples. 52 | -------------------------------------------------------------------------------- /algorithm.py: -------------------------------------------------------------------------------- 1 | try: 2 | import traci 3 | except Exception: 4 | import os, sys 5 | if 'SUMO_HOME' in os.environ: 6 | tools = os.path.join(os.environ['SUMO_HOME'], 'tools') 7 | sys.path.append(tools) 8 | import traci 9 | else: 10 | sys.exit("please declare environment variable 'SUMO_HOME'") 11 | import re 12 | 13 | # message type: traffic control message (1), selection initialization message (2), selection response message (3), lane position message (4) 14 | # traffic control message: 1,