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