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)
PRACTICAL_INFINITY = inf
def get_size(obj):
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

def benchmark(func):
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

def benchmark_cumulative(func):
 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.

class Benchmarker:
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
Benchmarker(active, description)
124    def __init__(self, active, description):
125        self.active = active
126        self.description = description
active
description
class working_dir:
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

working_dir(dir)
142    def __init__(self, dir):
143        self.dir = dir
144        self.origdir = os.getcwd()
dir
origdir
class Colorgen:
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()))
Colorgen(hsv, cycleLength=10.67)
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
DISTINCT = [(0.17, 1.0, 0.5), (0.0, 0.9, 1.0), (0.35, 0.67, 0.71), (0.14, 0.9, 1.0), (0.56, 1.0, 0.78), (0.07, 0.8, 0.96), (0.79, 0.83, 0.71), (0.5, 0.71, 0.94), (0.84, 0.79, 0.94), (0.2, 0.76, 0.96), (0.0, 0.24, 0.98), (0.5, 1.0, 0.5), (0.77, 0.25, 1.0), (0.09, 0.76, 0.67), (0.15, 0.22, 1.0), (0.0, 1.0, 0.5), (0.38, 0.33, 1.0), (0.67, 1.0, 0.5)]
hsv
cycle
cycleOffset
distinctIndex
def get_value(self, opt, index):
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)
def floatTuple(self):
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)])

return color as a tuple of floats each in [0,1]

def byteTuple(self):
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()])

return color as a tuple of bytes each in [0,255]

class priorityDictionary(builtins.dict):
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]
priorityDictionary()
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.

def smallest(self):
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.

def setdefault(self, key, val):
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__.

def update(self, other):
270    def update(self, other):
271        for key in other.keys():
272            self[key] = other[key]

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]

def getFreeSocketPort(numTries=10):
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
def getSocketStream(port, mode='rb'):
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)
def euclidean(a, b):
298def euclidean(a, b):
299    return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2)
def humanReadableTime(seconds):
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
SPECIAL_TIME_STRINGS = ['triggered', 'containerTriggered', 'split', 'begin']
def parseTime(t, factor=1):
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
def parseBool(val):
341def parseBool(val):
342    # see data/xsd/baseTypes:boolType
343    return val in ["true", "True", "x", "1", "yes", "on"]
def getFlowNumber(flow):
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

def intIfPossible(val):
379def intIfPossible(val):
380    if int(val) == val:
381        return int(val)
382    else:
383        return val
def openz(fileOrURL, mode='r', **kwargs):
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.

def short_names(filenames, noEmpty):
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
def getBaseName(filename):
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

def flattenPath(filename, sep='_'):
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