1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo 2# Copyright (C) 2007-2019 German Aerospace Center (DLR) and others. 3# This program and the accompanying materials 4# are made available under the terms of the Eclipse Public License v2.0 5# which accompanies this distribution, and is available at 6# http://www.eclipse.org/legal/epl-v20.html 7# SPDX-License-Identifier: EPL-2.0 8 9# @file elements.py 10# @author Yun-Pang Floetteroed 11# @author Daniel Krajzewicz 12# @author Michael Behrisch 13# @date 2007-10-25 14# @version $Id$ 15 16""" 17This script is to define the classes and functions for 18- reading network geometric, 19- calculating link characteristics, such as capacity, travel time and link cost function, 20- recording vehicular and path information, and 21- conducting statistic tests. 22""" 23from __future__ import absolute_import 24from __future__ import print_function 25 26import sys 27import math 28from tables import crCurveTable, laneTypeTable 29import sumolib 30 31# This class is used for finding the k shortest paths. 32 33 34class Predecessor: 35 36 def __init__(self, edge, pred, distance): 37 self.edge = edge 38 self.pred = pred 39 self.distance = distance 40 41# This class is used to build the nodes in the investigated network and 42# includes the update-function for searching the k shortest paths. 43 44 45class Vertex(sumolib.net.node.Node): 46 47 """ 48 This class is to store node attributes and the respective incoming/outgoing links. 49 """ 50 51 def __init__(self, id, type=None, coord=None, incLanes=None): 52 sumolib.net.node.Node.__init__(self, id, type, coord, incLanes) 53 self.preds = [] 54 self.wasUpdated = False 55 56 def __repr__(self): 57 return self._id 58 59 def _addNewPredecessor(self, edge, updatePred, newPreds): 60 for pred in newPreds: 61 if pred.pred == updatePred: 62 return 63 pred = updatePred 64 while pred.edge is not None: 65 if pred.edge == edge: 66 return 67 pred = pred.pred 68 newPreds.append(Predecessor(edge, updatePred, 69 updatePred.distance + edge.actualtime)) 70 71 def update(self, KPaths, edge): 72 updatePreds = edge._from.preds 73 if len(self.preds) == KPaths\ 74 and updatePreds[0].distance + edge.actualtime >= self.preds[KPaths - 1].distance: 75 return False 76 newPreds = [] 77 updateIndex = 0 78 predIndex = 0 79 while len(newPreds) < KPaths\ 80 and (updateIndex < len(updatePreds) or 81 predIndex < len(self.preds)): 82 if predIndex == len(self.preds): 83 self._addNewPredecessor( 84 edge, updatePreds[updateIndex], newPreds) 85 updateIndex += 1 86 elif updateIndex == len(updatePreds): 87 newPreds.append(self.preds[predIndex]) 88 predIndex += 1 89 elif updatePreds[updateIndex].distance + edge.actualtime < self.preds[predIndex].distance: 90 self._addNewPredecessor( 91 edge, updatePreds[updateIndex], newPreds) 92 updateIndex += 1 93 else: 94 newPreds.append(self.preds[predIndex]) 95 predIndex += 1 96 if predIndex == len(newPreds): 97 return False 98 self.preds = newPreds 99 returnVal = not self.wasUpdated 100 self.wasUpdated = True 101 return returnVal 102 103# This class is used to store link information and estimate 104# as well as flow and capacity for the flow computation and some parameters 105# read from the net. 106 107 108class Edge(sumolib.net.edge.Edge): 109 110 """ 111 This class is to record link attributes 112 """ 113 114 def __init__(self, label, source, target, prio, function, name): 115 sumolib.net.edge.Edge.__init__( 116 self, label, source, target, prio, function, name) 117 self.capacity = sys.maxsize 118 # parameter for estimating capacities according to signal timing plans 119 self.junction = None 120 self.junctiontype = None 121 self.rightturn = None 122 self.straight = None 123 self.leftturn = None 124 self.uturn = None 125 self.leftlink = [] 126 self.straightlink = [] 127 self.rightlink = [] 128 self.conflictlink = {} 129# self.againstlinkexist = None 130 self.flow = 0.0 131 self.helpflow = 0.0 132 self.freeflowtime = 0.0 133 self.queuetime = 0.0 134 self.estcapacity = 0.0 135 self.CRcurve = None 136 self.actualtime = 0.0 137 self.ratio = 0.0 138 self.connection = 0 139 self.edgetype = None 140 # parameter in the Lohse traffic assignment 141 self.helpacttime = 0. 142 # parameter in the Lohse traffic assignment 143 self.fTT = 0. 144 # parameter in the Lohse traffic assignment 145 self.TT = 0. 146 # parameter in the Lohse traffic assignment 147 self.delta = 0. 148 # parameter in the Lohse traffic assignment 149 self.helpacttimeEx = 0. 150 # parameter in the matrix estimation 151 self.detected = False 152 self.detectorNum = 0. 153 self.detecteddata = {} 154 self.detectedlanes = 0. 155 self.penalty = 0. 156 self.capLeft = 0. 157 self.capRight = 0. 158 self.capThrough = 0. 159 160 def addLane(self, lane): 161 sumolib.net.edge.Edge.addLane(self, lane) 162 if self._from._id == self._to._id: 163 self.freeflowtime = 0.0 164 else: 165 self.freeflowtime = self._length / self._speed 166 self.actualtime = self.freeflowtime 167 self.helpacttime = self.freeflowtime 168 169 def __repr__(self): 170 cap = str(self.capacity) 171 if self.capacity == sys.maxsize or self.connection != 0: 172 cap = "inf" 173 return "%s_%s_%s_%s<%s|%s|%s|%s|%s|%s|%s|%s|%s>" % (self._function, self._id, self._from, self._to, 174 self.junctiontype, self._speed, 175 self.flow, self._length, self._lanes, 176 self.CRcurve, self.estcapacity, cap, self.ratio) 177 178 def getConflictLink(self): 179 """ 180 method to get the conflict links for each link, when the respective left-turn behavior exists. 181 """ 182 if self._function == 'real' and len(self.leftlink) > 0: 183 for leftEdge in self.leftlink: 184 affectedTurning = None 185 for edge in leftEdge.source.inEdges: 186 if edge.source == self.target: 187 affectedTurning = edge 188 affectedTurning.freeflowtime = 6. 189 affectedTurning.actualtime = affectedTurning.freeflowtime 190 affectedTurning.helpacttime = affectedTurning.freeflowtime 191 192 for edge in leftEdge.source.inEdges: 193 for upstreamlink in edge.source.inEdges: 194 if leftEdge in upstreamlink.rightlink and len(upstreamlink.straightlink) > 0: 195 if upstreamlink not in self.conflictlink: 196 self.conflictlink[upstreamlink] = [] 197 self.conflictlink[upstreamlink].append( 198 affectedTurning) 199 200 def getFreeFlowTravelTime(self): 201 return self.freeflowtime 202 203 def addDetectedData(self, detecteddataObj): 204 self.detecteddata[detecteddataObj.label] = detecteddataObj 205 206 def getCapacity(self): 207 """ 208 method to read the link capacity and the cr-curve type from the table.py 209 the applied CR-curve database is retrived from VISUM-Validate-network und VISUM-Koeln-network 210 """ 211 typeList = laneTypeTable[min(len(self._lanes), 4)] 212 for laneType in typeList: 213 if laneType[0] >= self._speed: 214 break 215 216 self.estcapacity = len(self._lanes) * laneType[1] 217 self.CRcurve = laneType[2] 218 219 def getAdjustedCapacity(self, net): 220 """ 221 method to adjust the link capacity based on the given signal timing plans 222 """ 223 straightGreen = 0. 224 rightGreen = 0. 225 leftGreen = 0. 226 greentime = 0. 227 straightSymbol = -1 228 rightSymbol = -1 229 leftSymbol = -1 230 cyclelength = 0. 231 count = 0 232 if self.junctiontype == 'signalized': 233 junction = net._junctions[self.junction] 234 if self.rightturn is not None and self.rightturn != 'O' and self.rightturn != 'o': 235 rightSymbol = int(self.rightturn) 236 if self.leftturn is not None and self.leftturn != 'O' and self.leftturn != 'o': 237 leftSymbol = int(self.leftturn) 238 if self.straight is not None and self.straight != 'O' and self.straight != 'o': 239 straightSymbol = int(self.straight) 240 for phase in junction.phases[:]: 241 count += 1 242 cyclelength += phase.duration 243 if straightSymbol != -1 and phase.green[straightSymbol] == "1": 244 straightGreen += phase.duration 245 if rightSymbol != -1 and phase.green[rightSymbol] == "1": 246 rightGreen += phase.duration 247 if leftSymbol != -1 and phase.green[leftSymbol] == "1": 248 leftGreen += phase.duration 249 if self.straight is not None: 250 self.estcapacity = ( 251 straightGreen * (3600. / cyclelength)) / 1.5 * len(self._lanes) 252 else: 253 greentime = max(rightGreen, leftGreen) 254 self.estcapacity = ( 255 greentime * (3600. / cyclelength)) / 1.5 * len(self._lanes) 256 257 def getActualTravelTime(self, options, lohse): 258 """ 259 method to calculate/update link travel time 260 """ 261 foutcheck = open('queue_info.txt', 'a') 262 263 if self.CRcurve in crCurveTable: 264 curve = crCurveTable[self.CRcurve] 265 if self.flow == 0.0 or self.connection > 0: 266 self.actualtime = self.freeflowtime 267 elif self.estcapacity != 0. and self.connection == 0: 268 self.actualtime = self.freeflowtime * \ 269 (1 + (curve[0] * (self.flow / 270 (self.estcapacity * curve[2]))**curve[1])) 271 272 if ((self.flow > self.estcapacity or self.flow == self.estcapacity) and 273 self.flow > 0. and self.connection == 0): 274 self.queuetime = self.queuetime + options.lamda * \ 275 (self.actualtime - self.freeflowtime * (1 + curve[0])) 276 if self.queuetime < 1.: 277 self.queuetime = 0. 278 else: 279 foutcheck.write( 280 'edge.label= %s: queuing time= %s.\n' % (self._id, self.queuetime)) 281 foutcheck.write('travel time at capacity: %s; actual travel time: %s.\n' % ( 282 self.freeflowtime * (1 + curve[0]), self.actualtime)) 283 else: 284 self.queuetime = 0. 285 286 self.actualtime += self.queuetime 287 288 if lohse: 289 self.getLohseParUpdate(options) 290 else: 291 self.helpacttime = self.actualtime 292 293 self.penalty = 0. 294 if len(self.conflictlink) > 0: 295 for edge in self.conflictlink: 296 conflictEdge = edge 297 flowCapRatio = conflictEdge.flow / conflictEdge.estcapacity 298 299 weightFactor = 1.0 300 if self.numberlane == 2.: 301 weightFactor = 0.85 302 elif self.numberlane == 3.: 303 weightFactor = 0.75 304 elif self.numberlane > 3.: 305 weightFactor = 0.6 306 if options.dijkstra != 'extend': 307 for edge in self.conflictlink: 308 penalty = 0. 309 if edge.estcapacity > 0. and edge.flow / edge.estcapacity > 0.12: 310 penalty = weightFactor * \ 311 (math.exp(self.flow / self.estcapacity) - 1. + 312 math.exp(edge.flow / edge.estcapacity) - 1.) 313 for affectedTurning in self.conflictlink[edge]: 314 affectedTurning.actualtime = self.actualtime * \ 315 penalty 316 if lohse: 317 affectedTurning.helpacttime = self.helpacttime * \ 318 penalty 319 else: 320 affectedTurning.helpacttime = affectedTurning.actualtime 321 else: 322 for edge in self.conflictlink: 323 if edge.estcapacity > 0. and edge.flow / edge.estcapacity >= flowCapRatio: 324 conflictEdge = edge 325 flowCapRatio = edge.flow / edge.estcapacity 326 327 if conflictEdge.estcapacity > 0. and conflictEdge.flow / conflictEdge.estcapacity > 0.12: 328 self.penalty = weightFactor * \ 329 (math.exp(self.flow / self.estcapacity) - 1. + 330 math.exp(conflictEdge.flow / conflictEdge.estcapacity) - 1.) 331 if lohse: 332 self.penalty *= self.helpacttime 333 else: 334 self.penalty *= self.actualtime 335 foutcheck.close() 336 337 def cleanFlow(self): 338 """ method to reset link flows """ 339 self.flow = 0. 340 self.helpflow = 0. 341 342 def getLohseParUpdate(self, options): 343 """ 344 method to update the parameter used in the Lohse-assignment (learning method - Lernverfahren) 345 """ 346 if self.helpacttime > 0.: 347 self.TT = abs(self.actualtime - self.helpacttime) / \ 348 self.helpacttime 349 self.fTT = options.v1 / \ 350 (1 + math.exp(options.v2 - options.v3 * self.TT)) 351 self.delta = options.under + \ 352 (options.upper - options.under) / ((1 + self.TT)**self.fTT) 353 self.helpacttimeEx = self.helpacttime 354 self.helpacttime = self.helpacttime + self.delta * \ 355 (self.actualtime - self.helpacttime) 356 357 def stopCheck(self, options): 358 """ 359 method to check if the convergence reaches in the Lohse-assignment 360 """ 361 stop = False 362 criteria = 0. 363 criteria = options.cvg1 * \ 364 self.helpacttimeEx**(options.cvg2 / options.cvg3) 365 366 if abs(self.actualtime - self.helpacttimeEx) <= criteria: 367 stop = True 368 return stop 369 370 371class Vehicle: 372 373 """ 374 This class is to store vehicle information, such as departure time, route and travel time. 375 """ 376 377 def __init__(self, label): 378 self.label = label 379 self.method = None 380 self.depart = 0. 381 self.arrival = 0. 382 self.speed = 0. 383 self.route = [] 384 self.traveltime = 0. 385 self.travellength = 0. 386 self.departdelay = 0. 387 self.waittime = 0. 388 self.rank = 0. 389 390 def __repr__(self): 391 return "%s_%s_%s_%s_%s_%s<%s>" % (self.label, self.depart, self.arrival, self.speed, 392 self.traveltime, self.travellength, self.route) 393 394 395pathNum = 0 396 397 398class Path: 399 400 """ 401 This class is to store path information which is mainly for the C-logit and the Lohse models. 402 """ 403 404 def __init__(self, source, target, edges): 405 self.source = source 406 self.target = target 407 global pathNum 408 self.label = "%s" % pathNum 409 pathNum += 1 410 self.edges = edges 411 self.length = 0.0 412 self.freepathtime = 0.0 413 self.actpathtime = 0.0 414 self.pathflow = 0.0 415 self.helpflow = 0.0 416 self.choiceprob = 0.0 417 self.sumOverlap = 0.0 418 self.utility = 0.0 419 # parameter used in the Lohse traffic assignment 420 self.usedcounts = 1 421 # parameter used in the Lohse traffic assignment 422 self.pathhelpacttime = 0. 423 # record if this path is the currrent shortest one. 424 self.currentshortest = True 425 426 def __repr__(self): 427 return "%s_%s_%s<%s|%s|%s|%s>" % (self.label, self.source, self.target, self.freepathtime, 428 self.pathflow, self.actpathtime, self.edges) 429 430 def getPathLength(self): 431 for edge in self.edges: 432 self.length += edge._length 433 434 def updateSumOverlap(self, newpath, gamma): 435 overlapLength = 0. 436 for edge in self.edges: 437 if edge in newpath.edges: 438 overlapLength += edge._length 439 overlapLength = overlapLength / 1000. 440 lengthOne = self.length / 1000. 441 lengthTwo = newpath.length / 1000. 442 self.sumOverlap += math.pow(overlapLength / 443 (math.pow(lengthOne, 0.5) * math.pow(lengthTwo, 0.5)), gamma) 444 445 def getPathTimeUpdate(self): 446 """ 447 used to update the path travel time in the c-logit and the Lohse traffic assignments 448 """ 449 self.actpathtime = 0. 450 self.pathhelpacttime = 0. 451 for edge in self.edges: 452 self.actpathtime += edge.actualtime 453 self.pathhelpacttime += edge.helpacttime 454 455 self.pathhelpacttime = self.pathhelpacttime / 3600. 456 self.actpathtime = self.actpathtime / 3600. 457 458 459class TLJunction: 460 461 def __init__(self): 462 self.label = None 463 self.phaseNum = 0 464 self.phases = [] 465 466 def __repr__(self): 467 return "%s_%s<%s>" % (self.label, self.phaseNum, self.phases) 468 469 470class Signalphase: 471 472 def __init__(self, duration, state=None, phase=None, brake=None, yellow=None): 473 self.label = None 474 self.state = state 475 self.duration = duration 476 self.green = '' 477 self.brake = '' 478 self.yellow = '' 479 480 if phase and brake and yellow: 481 self.green = phase[::-1] 482 self.brake = brake[::-1] 483 self.yellow = yellow[::-1] 484 elif self.state: 485 for elem in self.state: 486 if elem == 'G': 487 self.green += '1' 488 self.brake += '0' 489 self.yellow += '0' 490 elif elem == 'y': 491 self.green += '0' 492 self.brake += '0' 493 self.yellow += '1' 494 elif elem == 'r': 495 self.green += '0' 496 self.brake += '1' 497 self.yellow += '0' 498 else: 499 print('no timing plans exist!') 500 501 def __repr__(self): 502 return "%s_%s<%s|%s|%s>" % (self.label, self.duration, self.green, self.brake, self.yellow) 503