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

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

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

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

working_dir(dir)
143    def __init__(self, dir):
144        self.dir = dir
145        self.origdir = os.getcwd()
dir
origdir
class Colorgen:
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()))
Colorgen(hsv, cycleLength=10.67)
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
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):
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)
def floatTuple(self):
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)])

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

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

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

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

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

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

def update(self, other):
271    def update(self, other):
272        for key in other.keys():
273            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):
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
def getSocketStream(port, mode='rb'):
290def getSocketStream(port, mode='rb'):
291    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
292    s.bind(("localhost", port))
293    s.listen(1)
294    conn, _ = s.accept()
295    return conn.makefile(mode)
def euclidean(a, b):
299def euclidean(a, b):
300    return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2)
def humanReadableTime(seconds):
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
SPECIAL_TIME_STRINGS = ['triggered', 'containerTriggered', 'split', 'begin']
def parseTime(t, factor=1):
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
def parseBool(val):
342def parseBool(val):
343    # see data/xsd/baseTypes:boolType
344    return val in ["true", "True", "x", "1", "yes", "on"]
def getFlowNumber(flow):
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

def intIfPossible(val, precision=None):
380def intIfPossible(val, precision=None):
381    if int(val) == val:
382        return int(val)
383    elif precision is not None:
384        return builtins.round(val, precision)
385    else:
386        return val
def openz(fileOrURL, mode='r', **kwargs):
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.

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

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