sumolib.miscutils
1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo 2# Copyright (C) 2012-2026 German Aerospace Center (DLR) and others. 3# This program and the accompanying materials are made available under the 4# terms of the Eclipse Public License 2.0 which is available at 5# https://www.eclipse.org/legal/epl-2.0/ 6# This Source Code may also be made available under the following Secondary 7# Licenses when the conditions for such availability set forth in the Eclipse 8# Public License 2.0 are satisfied: GNU General Public License, version 2 9# or later which is available at 10# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html 11# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later 12 13# @file miscutils.py 14# @author Jakob Erdmann 15# @author Michael Behrisch 16# @author Mirko Barthauer 17# @date 2012-05-08 18 19from __future__ import absolute_import 20from __future__ import print_function 21from __future__ import division 22import sys 23import time 24import builtins 25import os 26import math 27import colorsys 28import socket 29import random 30import gzip 31import codecs 32import io 33import atexit 34from functools import wraps 35from pathlib import Path 36from types import ModuleType, FunctionType 37from gc import get_referents 38try: 39 from urllib.request import urlopen 40except ImportError: 41 from urllib import urlopen 42# needed for backward compatibility 43from .statistics import Statistics, geh, uMax, uMin, round # noqa 44 45PRACTICAL_INFINITY = 1e400 46_BLACKLIST = type, ModuleType, FunctionType 47 48 49def get_size(obj): 50 """sum size of object & members. 51 lifted from https://stackoverflow.com/a/30316760 52 """ 53 if isinstance(obj, (_BLACKLIST)): 54 raise TypeError('getsize() does not take argument of type: ' + str(type(obj))) 55 seen_ids = set() 56 size = 0 57 objects = [obj] 58 while objects: 59 need_referents = [] 60 for obj in objects: 61 if not isinstance(obj, _BLACKLIST) and id(obj) not in seen_ids: 62 seen_ids.add(id(obj)) 63 size += sys.getsizeof(obj) 64 need_referents.append(obj) 65 objects = get_referents(*need_referents) 66 return size 67 68 69def benchmark(func): 70 """ 71 decorator for timing a function 72 """ 73 @wraps(func) 74 def benchmark_wrapper(*args, **kwargs): 75 started = time.time() 76 now = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime()) 77 print('function %s called at %s' % (func.__name__, now)) 78 sys.stdout.flush() 79 result = func(*args, **kwargs) 80 print('function %s finished after %.6f seconds' % 81 (func.__name__, time.time() - started)) 82 sys.stdout.flush() 83 return result 84 return benchmark_wrapper 85 86 87def benchmark_cumulative(func): 88 """ 89 Decorator for timing a function and tracking execution statistics over repeated invocations. 90 Prints statistics at program end. 91 """ 92 @wraps(func) 93 def benchmark_wrapper(*args, **kwargs): 94 if not hasattr(benchmark_wrapper, 'call_count'): 95 benchmark_wrapper.call_count = 0 96 benchmark_wrapper.total_duration = 0.0 97 started = time.time() 98 result = func(*args, **kwargs) 99 elapsed = time.time() - started 100 benchmark_wrapper.call_count += 1 101 benchmark_wrapper.total_duration += elapsed 102 return result 103 104 def print_stats(): 105 count = getattr(benchmark_wrapper, 'call_count', 0) 106 if count > 0: 107 total = benchmark_wrapper.total_duration 108 print("\n--- Statistics for %s: calls=%s total(s)=%.6f avg(s)=%.6f ---" % 109 (func.__name__, count, total, total / count)) 110 sys.stdout.flush() 111 112 atexit.register(print_stats) 113 return benchmark_wrapper 114 115 116class Benchmarker: 117 """ 118 class for benchmarking a function using a "with"-statement. 119 Preferable over the "benchmark" function for the following use cases 120 - benchmarking a code block that isn't wrapped in a function 121 - benchmarking a function only in some calls 122 """ 123 124 def __init__(self, active, description): 125 self.active = active 126 self.description = description 127 128 def __enter__(self): 129 self.started = time.time() 130 131 def __exit__(self, *args): 132 if self.active: 133 duration = time.time() - self.started 134 print("%s finished after %s" % (self.description, humanReadableTime(duration))) 135 136 137class working_dir: 138 """ 139 temporarily change working directory using 'with' statement 140 """ 141 142 def __init__(self, dir): 143 self.dir = dir 144 self.origdir = os.getcwd() 145 146 def __enter__(self): 147 os.chdir(self.dir) 148 149 def __exit__(self, type, value, traceback): 150 os.chdir(self.origdir) 151 152 153class Colorgen: 154 DISTINCT = [ 155 (0.17, 1.0, 0.5), 156 (0.0, 0.9, 1.0), 157 (0.35, 0.67, 0.71), 158 (0.14, 0.9, 1.0), 159 (0.56, 1.0, 0.78), 160 (0.07, 0.8, 0.96), 161 (0.79, 0.83, 0.71), 162 (0.5, 0.71, 0.94), 163 (0.84, 0.79, 0.94), 164 (0.2, 0.76, 0.96), 165 (0.0, 0.24, 0.98), 166 (0.5, 1.0, 0.5), 167 (0.77, 0.25, 1.0), 168 (0.09, 0.76, 0.67), 169 (0.15, 0.22, 1.0), 170 (0.0, 1.0, 0.5), 171 (0.38, 0.33, 1.0), 172 (0.67, 1.0, 0.5), 173 ] 174 175 def __init__(self, hsv, cycleLength=10.67): 176 self.hsv = hsv 177 self.cycle = [int(random.random() * 256) for x in self.hsv] 178 self.cycleOffset = int(round(256 / cycleLength)) 179 self.distinctIndex = 0 180 181 def get_value(self, opt, index): 182 if opt == 'random': 183 return random.random() 184 if opt == 'cycle': 185 # the 255 below is intentional to get all color values when cycling long enough 186 self.cycle[index] = (self.cycle[index] + self.cycleOffset) % 255 187 return self.cycle[index] / 255.0 188 if opt == 'distinct': 189 if index == 0: 190 self.distinctIndex = (self.distinctIndex + 1) % len(self.DISTINCT) 191 return self.DISTINCT[self.distinctIndex][index] 192 return float(opt) 193 194 def floatTuple(self): 195 """return color as a tuple of floats each in [0,1]""" 196 return colorsys.hsv_to_rgb(*[self.get_value(o, i) for i, o in enumerate(self.hsv)]) 197 198 def byteTuple(self): 199 """return color as a tuple of bytes each in [0,255]""" 200 return tuple([int(round(255 * x)) for x in self.floatTuple()]) 201 202 def __call__(self): 203 """return constant or randomized rgb-color string""" 204 return ','.join(map(str, self.byteTuple())) 205 206 207class priorityDictionary(dict): 208 209 def __init__(self): 210 '''Initialize priorityDictionary by creating binary heap 211 of pairs (value,key). Note that changing or removing a dict entry will 212 not remove the old pair from the heap until it is found by smallest() or 213 until the heap is rebuilt.''' 214 self.__heap = [] 215 dict.__init__(self) 216 217 def smallest(self): 218 '''Find smallest item after removing deleted items from heap.''' 219 if len(self) == 0: 220 raise IndexError("smallest of empty priorityDictionary") 221 heap = self.__heap 222 while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]: 223 lastItem = heap.pop() 224 insertionPoint = 0 225 while 1: 226 smallChild = 2 * insertionPoint + 1 227 if smallChild + 1 < len(heap) and \ 228 heap[smallChild][0] > heap[smallChild + 1][0]: 229 smallChild += 1 230 if smallChild >= len(heap) or lastItem <= heap[smallChild]: 231 heap[insertionPoint] = lastItem 232 break 233 heap[insertionPoint] = heap[smallChild] 234 insertionPoint = smallChild 235 return heap[0][1] 236 237 def __iter__(self): 238 '''Create destructive sorted iterator of priorityDictionary.''' 239 def iterfn(): 240 while len(self) > 0: 241 x = self.smallest() 242 yield x 243 del self[x] 244 return iterfn() 245 246 def __setitem__(self, key, val): 247 '''Change value stored in dictionary and add corresponding 248 pair to heap. Rebuilds the heap if the number of deleted items grows 249 too large, to avoid memory leakage.''' 250 dict.__setitem__(self, key, val) 251 heap = self.__heap 252 if len(heap) > 2 * len(self): 253 self.__heap = [(v, k) for k, v in self.items()] 254 self.__heap.sort() # builtin sort likely faster than O(n) heapify 255 else: 256 newPair = (val, key) 257 insertionPoint = len(heap) 258 heap.append(None) 259 while insertionPoint > 0 and val < heap[(insertionPoint - 1) // 2][0]: 260 heap[insertionPoint] = heap[(insertionPoint - 1) // 2] 261 insertionPoint = (insertionPoint - 1) // 2 262 heap[insertionPoint] = newPair 263 264 def setdefault(self, key, val): 265 '''Reimplement setdefault to call our customized __setitem__.''' 266 if key not in self: 267 self[key] = val 268 return self[key] 269 270 def update(self, other): 271 for key in other.keys(): 272 self[key] = other[key] 273 274 275def getFreeSocketPort(numTries=10): 276 for _ in range(numTries): 277 try: 278 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 279 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 280 s.bind(('', 0)) 281 p = s.getsockname()[1] 282 s.close() 283 return p 284 except socket.error: 285 pass 286 return None 287 288 289def getSocketStream(port, mode='rb'): 290 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 291 s.bind(("localhost", port)) 292 s.listen(1) 293 conn, _ = s.accept() 294 return conn.makefile(mode) 295 296 297# euclidean distance between two coordinates in the plane 298def euclidean(a, b): 299 return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) 300 301 302def humanReadableTime(seconds): 303 result = "" 304 sign = '-' if seconds < 0 else '' 305 seconds = abs(seconds) 306 ds = 3600 * 24 307 if seconds > ds: 308 result = "%s:" % int(seconds / ds) 309 seconds = seconds % ds 310 result += "%02i:" % int(seconds / 3600) 311 seconds = seconds % 3600 312 result += "%02i:" % int(seconds / 60) 313 seconds = seconds % 60 314 if seconds == int(seconds): 315 seconds = int(seconds) 316 result += "%02i" % seconds 317 return sign + result 318 319 320SPECIAL_TIME_STRINGS = ["triggered", "containerTriggered", "split", "begin"] 321 322 323def parseTime(t, factor=1): 324 try: 325 return float(t) * factor 326 except ValueError: 327 pass 328 try: 329 # prepended zero is ignored if the date value already contains days 330 days, hours, minutes, seconds = ([0] + list(map(float, t.split(':'))))[-4:] 331 sign = -1 if t.strip()[0] == '-' else 1 332 return (3600 * 24 * days + 3600 * hours + 60 * minutes + seconds) * sign * factor 333 except ValueError: 334 if t in SPECIAL_TIME_STRINGS: 335 # signal special case but don't crash 336 return None 337 else: 338 raise 339 340 341def parseBool(val): 342 # see data/xsd/baseTypes:boolType 343 return val in ["true", "True", "x", "1", "yes", "on"] 344 345 346def getFlowNumber(flow): 347 """interpret number of vehicles from a flow parsed by sumolib.xml.parse""" 348 if flow.number is not None: 349 return int(flow.number) 350 if flow.end is not None: 351 duration = parseTime(flow.end) - parseTime(flow.begin) 352 period = 0 353 isFractional = False 354 if flow.period is not None: 355 if 'exp' in flow.period: 356 # use expected value 357 period = 1 / float(flow.period[4:-1]) 358 isFractional = True 359 else: 360 period = float(flow.period) 361 elif flow.probability is not None: 362 # use expected value 363 period = 1 / float(flow.probability) 364 isFractional = True 365 for attr in ['perHour', 'vehsPerHour']: 366 if flow.hasAttribute(attr): 367 period = 3600 / float(flow.getAttribute(attr)) 368 if period > 0: 369 count = duration / period 370 if isFractional: 371 return count 372 else: 373 # flows with a regular period always start at least one vehicle at the begin time 374 return math.ceil(count) 375 else: 376 return 1 377 378 379def intIfPossible(val, precision=None): 380 if int(val) == val: 381 return int(val) 382 elif precision is not None: 383 return builtins.round(val, precision) 384 else: 385 return val 386 387 388def openz(fileOrURL, mode="r", **kwargs): 389 """ 390 Opens transparently files, URLs and gzipped files for reading and writing. 391 Special file names "stdout" and "stderr" are handled as well. 392 Also enforces UTF8 on text output / input and should handle BOMs in input. 393 Should be compatible with python 2 and 3. 394 """ 395 encoding = kwargs.get("encoding", "utf8" if "w" in mode else "utf-8-sig") 396 try: 397 if fileOrURL.startswith("http://") or fileOrURL.startswith("https://"): 398 return io.BytesIO(urlopen(fileOrURL).read()) 399 if fileOrURL == "stdout": 400 return sys.stdout 401 if fileOrURL == "stderr": 402 return sys.stderr 403 if fileOrURL.endswith(".gz") and "w" in mode: 404 if "b" in mode: 405 return gzip.open(fileOrURL, mode="w") 406 return gzip.open(fileOrURL, mode="wt", encoding=encoding) 407 if kwargs.get("trySocket") and fileOrURL.isdigit(): 408 return getSocketStream(int(fileOrURL), mode) 409 if kwargs.get("tryGZip", True) and "r" in mode: 410 with gzip.open(fileOrURL) as fd: 411 fd.read(1) 412 if "b" in mode: 413 return gzip.open(fileOrURL) 414 if sys.version_info[0] < 3: 415 return codecs.getreader('utf-8')(gzip.open(fileOrURL)) 416 return gzip.open(fileOrURL, mode="rt", encoding=encoding) 417 except OSError as e: 418 if kwargs.get("printErrors"): 419 print(e, file=sys.stderr) 420 except IOError as e: 421 if kwargs.get("printErrors"): 422 print(e, file=sys.stderr) 423 if "b" in mode: 424 return io.open(fileOrURL, mode=mode) 425 return io.open(fileOrURL, mode=mode, encoding=encoding) 426 427 428def short_names(filenames, noEmpty): 429 if len(filenames) == 1: 430 return filenames 431 reversedNames = [''.join(reversed(f)) for f in filenames] 432 prefix = os.path.commonprefix(filenames) 433 suffix = os.path.commonprefix(reversedNames) 434 prefixLen = len(prefix) 435 suffixLen = len(suffix) 436 shortened = [f[prefixLen:-suffixLen] for f in filenames] 437 if noEmpty and any([not f for f in shortened]): 438 # make longer to avoid empty file names 439 base = os.path.basename(prefix) 440 shortened = [base + f for f in shortened] 441 return shortened 442 443 444def getBaseName(filename): 445 """strip extensions such as .net.xml.gz""" 446 if filename[-11:] == ".net.xml.gz" and len(filename) > 11: 447 return filename[:-11] 448 elif filename[-8:] == ".net.xml" and len(filename) > 8: 449 return filename[:-8] 450 elif filename[-7:] == ".xml.gz" and len(filename) > 7: 451 return filename[:-7] 452 elif filename[-4:] == ".xml" and len(filename) > 4: 453 return filename[:-4] 454 else: 455 return filename 456 457 458def flattenPath(filename, sep='_'): 459 """create a filename that encodes the original directory structure""" 460 parts = Path(filename).parts 461 clean = [p for p in parts if p not in ('/', '\\') and ':' not in p] 462 return sep.join(clean)
50def get_size(obj): 51 """sum size of object & members. 52 lifted from https://stackoverflow.com/a/30316760 53 """ 54 if isinstance(obj, (_BLACKLIST)): 55 raise TypeError('getsize() does not take argument of type: ' + str(type(obj))) 56 seen_ids = set() 57 size = 0 58 objects = [obj] 59 while objects: 60 need_referents = [] 61 for obj in objects: 62 if not isinstance(obj, _BLACKLIST) and id(obj) not in seen_ids: 63 seen_ids.add(id(obj)) 64 size += sys.getsizeof(obj) 65 need_referents.append(obj) 66 objects = get_referents(*need_referents) 67 return size
sum size of object & members. lifted from https://stackoverflow.com/a/30316760
70def benchmark(func): 71 """ 72 decorator for timing a function 73 """ 74 @wraps(func) 75 def benchmark_wrapper(*args, **kwargs): 76 started = time.time() 77 now = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime()) 78 print('function %s called at %s' % (func.__name__, now)) 79 sys.stdout.flush() 80 result = func(*args, **kwargs) 81 print('function %s finished after %.6f seconds' % 82 (func.__name__, time.time() - started)) 83 sys.stdout.flush() 84 return result 85 return benchmark_wrapper
decorator for timing a function
88def benchmark_cumulative(func): 89 """ 90 Decorator for timing a function and tracking execution statistics over repeated invocations. 91 Prints statistics at program end. 92 """ 93 @wraps(func) 94 def benchmark_wrapper(*args, **kwargs): 95 if not hasattr(benchmark_wrapper, 'call_count'): 96 benchmark_wrapper.call_count = 0 97 benchmark_wrapper.total_duration = 0.0 98 started = time.time() 99 result = func(*args, **kwargs) 100 elapsed = time.time() - started 101 benchmark_wrapper.call_count += 1 102 benchmark_wrapper.total_duration += elapsed 103 return result 104 105 def print_stats(): 106 count = getattr(benchmark_wrapper, 'call_count', 0) 107 if count > 0: 108 total = benchmark_wrapper.total_duration 109 print("\n--- Statistics for %s: calls=%s total(s)=%.6f avg(s)=%.6f ---" % 110 (func.__name__, count, total, total / count)) 111 sys.stdout.flush() 112 113 atexit.register(print_stats) 114 return benchmark_wrapper
Decorator for timing a function and tracking execution statistics over repeated invocations. Prints statistics at program end.
117class Benchmarker: 118 """ 119 class for benchmarking a function using a "with"-statement. 120 Preferable over the "benchmark" function for the following use cases 121 - benchmarking a code block that isn't wrapped in a function 122 - benchmarking a function only in some calls 123 """ 124 125 def __init__(self, active, description): 126 self.active = active 127 self.description = description 128 129 def __enter__(self): 130 self.started = time.time() 131 132 def __exit__(self, *args): 133 if self.active: 134 duration = time.time() - self.started 135 print("%s finished after %s" % (self.description, humanReadableTime(duration)))
class for benchmarking a function using a "with"-statement. Preferable over the "benchmark" function for the following use cases
- benchmarking a code block that isn't wrapped in a function
- benchmarking a function only in some calls
138class working_dir: 139 """ 140 temporarily change working directory using 'with' statement 141 """ 142 143 def __init__(self, dir): 144 self.dir = dir 145 self.origdir = os.getcwd() 146 147 def __enter__(self): 148 os.chdir(self.dir) 149 150 def __exit__(self, type, value, traceback): 151 os.chdir(self.origdir)
temporarily change working directory using 'with' statement
154class Colorgen: 155 DISTINCT = [ 156 (0.17, 1.0, 0.5), 157 (0.0, 0.9, 1.0), 158 (0.35, 0.67, 0.71), 159 (0.14, 0.9, 1.0), 160 (0.56, 1.0, 0.78), 161 (0.07, 0.8, 0.96), 162 (0.79, 0.83, 0.71), 163 (0.5, 0.71, 0.94), 164 (0.84, 0.79, 0.94), 165 (0.2, 0.76, 0.96), 166 (0.0, 0.24, 0.98), 167 (0.5, 1.0, 0.5), 168 (0.77, 0.25, 1.0), 169 (0.09, 0.76, 0.67), 170 (0.15, 0.22, 1.0), 171 (0.0, 1.0, 0.5), 172 (0.38, 0.33, 1.0), 173 (0.67, 1.0, 0.5), 174 ] 175 176 def __init__(self, hsv, cycleLength=10.67): 177 self.hsv = hsv 178 self.cycle = [int(random.random() * 256) for x in self.hsv] 179 self.cycleOffset = int(round(256 / cycleLength)) 180 self.distinctIndex = 0 181 182 def get_value(self, opt, index): 183 if opt == 'random': 184 return random.random() 185 if opt == 'cycle': 186 # the 255 below is intentional to get all color values when cycling long enough 187 self.cycle[index] = (self.cycle[index] + self.cycleOffset) % 255 188 return self.cycle[index] / 255.0 189 if opt == 'distinct': 190 if index == 0: 191 self.distinctIndex = (self.distinctIndex + 1) % len(self.DISTINCT) 192 return self.DISTINCT[self.distinctIndex][index] 193 return float(opt) 194 195 def floatTuple(self): 196 """return color as a tuple of floats each in [0,1]""" 197 return colorsys.hsv_to_rgb(*[self.get_value(o, i) for i, o in enumerate(self.hsv)]) 198 199 def byteTuple(self): 200 """return color as a tuple of bytes each in [0,255]""" 201 return tuple([int(round(255 * x)) for x in self.floatTuple()]) 202 203 def __call__(self): 204 """return constant or randomized rgb-color string""" 205 return ','.join(map(str, self.byteTuple()))
182 def get_value(self, opt, index): 183 if opt == 'random': 184 return random.random() 185 if opt == 'cycle': 186 # the 255 below is intentional to get all color values when cycling long enough 187 self.cycle[index] = (self.cycle[index] + self.cycleOffset) % 255 188 return self.cycle[index] / 255.0 189 if opt == 'distinct': 190 if index == 0: 191 self.distinctIndex = (self.distinctIndex + 1) % len(self.DISTINCT) 192 return self.DISTINCT[self.distinctIndex][index] 193 return float(opt)
208class priorityDictionary(dict): 209 210 def __init__(self): 211 '''Initialize priorityDictionary by creating binary heap 212 of pairs (value,key). Note that changing or removing a dict entry will 213 not remove the old pair from the heap until it is found by smallest() or 214 until the heap is rebuilt.''' 215 self.__heap = [] 216 dict.__init__(self) 217 218 def smallest(self): 219 '''Find smallest item after removing deleted items from heap.''' 220 if len(self) == 0: 221 raise IndexError("smallest of empty priorityDictionary") 222 heap = self.__heap 223 while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]: 224 lastItem = heap.pop() 225 insertionPoint = 0 226 while 1: 227 smallChild = 2 * insertionPoint + 1 228 if smallChild + 1 < len(heap) and \ 229 heap[smallChild][0] > heap[smallChild + 1][0]: 230 smallChild += 1 231 if smallChild >= len(heap) or lastItem <= heap[smallChild]: 232 heap[insertionPoint] = lastItem 233 break 234 heap[insertionPoint] = heap[smallChild] 235 insertionPoint = smallChild 236 return heap[0][1] 237 238 def __iter__(self): 239 '''Create destructive sorted iterator of priorityDictionary.''' 240 def iterfn(): 241 while len(self) > 0: 242 x = self.smallest() 243 yield x 244 del self[x] 245 return iterfn() 246 247 def __setitem__(self, key, val): 248 '''Change value stored in dictionary and add corresponding 249 pair to heap. Rebuilds the heap if the number of deleted items grows 250 too large, to avoid memory leakage.''' 251 dict.__setitem__(self, key, val) 252 heap = self.__heap 253 if len(heap) > 2 * len(self): 254 self.__heap = [(v, k) for k, v in self.items()] 255 self.__heap.sort() # builtin sort likely faster than O(n) heapify 256 else: 257 newPair = (val, key) 258 insertionPoint = len(heap) 259 heap.append(None) 260 while insertionPoint > 0 and val < heap[(insertionPoint - 1) // 2][0]: 261 heap[insertionPoint] = heap[(insertionPoint - 1) // 2] 262 insertionPoint = (insertionPoint - 1) // 2 263 heap[insertionPoint] = newPair 264 265 def setdefault(self, key, val): 266 '''Reimplement setdefault to call our customized __setitem__.''' 267 if key not in self: 268 self[key] = val 269 return self[key] 270 271 def update(self, other): 272 for key in other.keys(): 273 self[key] = other[key]
210 def __init__(self): 211 '''Initialize priorityDictionary by creating binary heap 212 of pairs (value,key). Note that changing or removing a dict entry will 213 not remove the old pair from the heap until it is found by smallest() or 214 until the heap is rebuilt.''' 215 self.__heap = [] 216 dict.__init__(self)
Initialize priorityDictionary by creating binary heap of pairs (value,key). Note that changing or removing a dict entry will not remove the old pair from the heap until it is found by smallest() or until the heap is rebuilt.
218 def smallest(self): 219 '''Find smallest item after removing deleted items from heap.''' 220 if len(self) == 0: 221 raise IndexError("smallest of empty priorityDictionary") 222 heap = self.__heap 223 while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]: 224 lastItem = heap.pop() 225 insertionPoint = 0 226 while 1: 227 smallChild = 2 * insertionPoint + 1 228 if smallChild + 1 < len(heap) and \ 229 heap[smallChild][0] > heap[smallChild + 1][0]: 230 smallChild += 1 231 if smallChild >= len(heap) or lastItem <= heap[smallChild]: 232 heap[insertionPoint] = lastItem 233 break 234 heap[insertionPoint] = heap[smallChild] 235 insertionPoint = smallChild 236 return heap[0][1]
Find smallest item after removing deleted items from heap.
265 def setdefault(self, key, val): 266 '''Reimplement setdefault to call our customized __setitem__.''' 267 if key not in self: 268 self[key] = val 269 return self[key]
Reimplement setdefault to call our customized __setitem__.
D.update([E, ]**F) -> None. Update D from dict/iterable E and F. If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]
276def getFreeSocketPort(numTries=10): 277 for _ in range(numTries): 278 try: 279 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 280 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 281 s.bind(('', 0)) 282 p = s.getsockname()[1] 283 s.close() 284 return p 285 except socket.error: 286 pass 287 return None
303def humanReadableTime(seconds): 304 result = "" 305 sign = '-' if seconds < 0 else '' 306 seconds = abs(seconds) 307 ds = 3600 * 24 308 if seconds > ds: 309 result = "%s:" % int(seconds / ds) 310 seconds = seconds % ds 311 result += "%02i:" % int(seconds / 3600) 312 seconds = seconds % 3600 313 result += "%02i:" % int(seconds / 60) 314 seconds = seconds % 60 315 if seconds == int(seconds): 316 seconds = int(seconds) 317 result += "%02i" % seconds 318 return sign + result
324def parseTime(t, factor=1): 325 try: 326 return float(t) * factor 327 except ValueError: 328 pass 329 try: 330 # prepended zero is ignored if the date value already contains days 331 days, hours, minutes, seconds = ([0] + list(map(float, t.split(':'))))[-4:] 332 sign = -1 if t.strip()[0] == '-' else 1 333 return (3600 * 24 * days + 3600 * hours + 60 * minutes + seconds) * sign * factor 334 except ValueError: 335 if t in SPECIAL_TIME_STRINGS: 336 # signal special case but don't crash 337 return None 338 else: 339 raise
347def getFlowNumber(flow): 348 """interpret number of vehicles from a flow parsed by sumolib.xml.parse""" 349 if flow.number is not None: 350 return int(flow.number) 351 if flow.end is not None: 352 duration = parseTime(flow.end) - parseTime(flow.begin) 353 period = 0 354 isFractional = False 355 if flow.period is not None: 356 if 'exp' in flow.period: 357 # use expected value 358 period = 1 / float(flow.period[4:-1]) 359 isFractional = True 360 else: 361 period = float(flow.period) 362 elif flow.probability is not None: 363 # use expected value 364 period = 1 / float(flow.probability) 365 isFractional = True 366 for attr in ['perHour', 'vehsPerHour']: 367 if flow.hasAttribute(attr): 368 period = 3600 / float(flow.getAttribute(attr)) 369 if period > 0: 370 count = duration / period 371 if isFractional: 372 return count 373 else: 374 # flows with a regular period always start at least one vehicle at the begin time 375 return math.ceil(count) 376 else: 377 return 1
interpret number of vehicles from a flow parsed by sumolib.xml.parse
389def openz(fileOrURL, mode="r", **kwargs): 390 """ 391 Opens transparently files, URLs and gzipped files for reading and writing. 392 Special file names "stdout" and "stderr" are handled as well. 393 Also enforces UTF8 on text output / input and should handle BOMs in input. 394 Should be compatible with python 2 and 3. 395 """ 396 encoding = kwargs.get("encoding", "utf8" if "w" in mode else "utf-8-sig") 397 try: 398 if fileOrURL.startswith("http://") or fileOrURL.startswith("https://"): 399 return io.BytesIO(urlopen(fileOrURL).read()) 400 if fileOrURL == "stdout": 401 return sys.stdout 402 if fileOrURL == "stderr": 403 return sys.stderr 404 if fileOrURL.endswith(".gz") and "w" in mode: 405 if "b" in mode: 406 return gzip.open(fileOrURL, mode="w") 407 return gzip.open(fileOrURL, mode="wt", encoding=encoding) 408 if kwargs.get("trySocket") and fileOrURL.isdigit(): 409 return getSocketStream(int(fileOrURL), mode) 410 if kwargs.get("tryGZip", True) and "r" in mode: 411 with gzip.open(fileOrURL) as fd: 412 fd.read(1) 413 if "b" in mode: 414 return gzip.open(fileOrURL) 415 if sys.version_info[0] < 3: 416 return codecs.getreader('utf-8')(gzip.open(fileOrURL)) 417 return gzip.open(fileOrURL, mode="rt", encoding=encoding) 418 except OSError as e: 419 if kwargs.get("printErrors"): 420 print(e, file=sys.stderr) 421 except IOError as e: 422 if kwargs.get("printErrors"): 423 print(e, file=sys.stderr) 424 if "b" in mode: 425 return io.open(fileOrURL, mode=mode) 426 return io.open(fileOrURL, mode=mode, encoding=encoding)
Opens transparently files, URLs and gzipped files for reading and writing. Special file names "stdout" and "stderr" are handled as well. Also enforces UTF8 on text output / input and should handle BOMs in input. Should be compatible with python 2 and 3.
429def short_names(filenames, noEmpty): 430 if len(filenames) == 1: 431 return filenames 432 reversedNames = [''.join(reversed(f)) for f in filenames] 433 prefix = os.path.commonprefix(filenames) 434 suffix = os.path.commonprefix(reversedNames) 435 prefixLen = len(prefix) 436 suffixLen = len(suffix) 437 shortened = [f[prefixLen:-suffixLen] for f in filenames] 438 if noEmpty and any([not f for f in shortened]): 439 # make longer to avoid empty file names 440 base = os.path.basename(prefix) 441 shortened = [base + f for f in shortened] 442 return shortened
445def getBaseName(filename): 446 """strip extensions such as .net.xml.gz""" 447 if filename[-11:] == ".net.xml.gz" and len(filename) > 11: 448 return filename[:-11] 449 elif filename[-8:] == ".net.xml" and len(filename) > 8: 450 return filename[:-8] 451 elif filename[-7:] == ".xml.gz" and len(filename) > 7: 452 return filename[:-7] 453 elif filename[-4:] == ".xml" and len(filename) > 4: 454 return filename[:-4] 455 else: 456 return filename
strip extensions such as .net.xml.gz
459def flattenPath(filename, sep='_'): 460 """create a filename that encodes the original directory structure""" 461 parts = Path(filename).parts 462 clean = [p for p in parts if p not in ('/', '\\') and ':' not in p] 463 return sep.join(clean)
create a filename that encodes the original directory structure