Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/assign/duaIterate_routeCosts.py
169673 views
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
4
# Copyright (C) 2012-2025 German Aerospace Center (DLR) and others.
5
# This program and the accompanying materials are made available under the
6
# terms of the Eclipse Public License 2.0 which is available at
7
# https://www.eclipse.org/legal/epl-2.0/
8
# This Source Code may also be made available under the following Secondary
9
# Licenses when the conditions for such availability set forth in the Eclipse
10
# Public License 2.0 are satisfied: GNU General Public License, version 2
11
# or later which is available at
12
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
13
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
14
15
# @file duaIterate_routeCosts.py
16
# @author Jakob Erdmann
17
# @date 2024-12-16
18
19
"""
20
Analyze the evolving travel time (cost) of routes over the iterations of
21
duaIterate.py based on edgeData output, rou.alt.xml and tripinfos
22
- user-defined routes
23
- occuring routes filtered by
24
- origin/destination
25
- intermediate edges that must have been passed
26
- edges that must have been avoided
27
- actual trip travel times
28
29
This module is meant to be loaded in an interpreter sessions to interrogate the data multiple times but only loading it
30
once.
31
32
Example sessions:
33
34
>>> import duaIterate_routeCosts as rc
35
>>> r = rc.load('.', range(47, 50)) # load iteration numbers 47,48,49
36
>>> f = rc.filter(r, via=['531478184','25483415']) # filter routes that pass both edges
37
47 Costs: count 1124, min 896.14 (veh0), max 1960.17 (veh1), mean 1355.41, Q1 1257.53, median 1325.86, Q3 1434.97
38
48 Costs: count 1124, min 896.47 (veh0), max 1993.74 (veh2), mean 1355.02, Q1 1257.32, median 1323.68, Q3 1434.09
39
49 Costs: count 1124, min 898.51 (veh3), max 1958.68 (veh1), mean 1355.00, Q1 1257.93, median 1323.39, Q3 1434.92
40
41
Implementation Note:
42
edgeIDs are mapped to numbers in a numpy array to conserve memory
43
(because strings have a large fixed memory overhead this reduces memory consumption by a factor of 10)
44
"""
45
46
from __future__ import absolute_import
47
from __future__ import print_function
48
import os
49
import sys
50
import glob
51
import subprocess
52
from collections import namedtuple
53
import numpy as np
54
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
55
import sumolib # noqa
56
from sumolib.statistics import Statistics # noqa
57
58
Route = namedtuple('Route', ['vehID', 'cost', 'index', 'used', 'depart', 'edges'])
59
StringDict = {}
60
ReverseStringDict = {}
61
62
63
def npindex(array, x):
64
i = np.where(array == x)
65
if i[0].size > 0:
66
return i[0][0]
67
else:
68
return None
69
70
71
def hasSequence(array, via):
72
i = npindex(array, via[0])
73
for edge in via[1:]:
74
i = npindex(array, edge)
75
if i is None:
76
return False
77
return True
78
79
80
def stringToNumber(string):
81
if string not in StringDict:
82
StringDict[string] = len(StringDict)
83
return StringDict[string]
84
85
86
def numberToString(n):
87
if not ReverseStringDict:
88
ReverseStringDict.update(((v, k) for k, v in StringDict.items()))
89
return ReverseStringDict[n]
90
91
92
def load(baseDir, iterations, suffix="gz"):
93
"""iterations is an iterable that gives the iteration numberes to load
94
"""
95
result = []
96
files = glob.glob(os.path.join(baseDir, "**/*.rou.alt.%s" % suffix))
97
files = [(int(os.path.basename(os.path.dirname(f))), f) for f in files]
98
for step, file in sorted(files):
99
if step not in iterations:
100
continue
101
print("loading step %s, file %s" % (step, file))
102
result.append((step, loadRoutes(file)))
103
return result
104
105
106
def loadRoutes(file):
107
result = []
108
for vehicle in sumolib.xml.parse(file, ['vehicle']):
109
last = int(vehicle.routeDistribution[0].last)
110
for ri, route in enumerate(vehicle.routeDistribution[0].route):
111
edges = np.array(list(map(stringToNumber, route.edges.split())), dtype=np.uint32)
112
result.append(Route(vehicle.id, float(route.cost), ri, ri == last, vehicle.depart, edges))
113
return result
114
115
116
def filter(stepRoutes, origin=None, dest=None, via=None, forbidden=None, cutVia=False):
117
"""Filter given routes according to origin, destination, via-edges or forbidden edges
118
If cutVia is set, the route will be truncated by the first and last via-edge and it's cost set to -1
119
"""
120
if cutVia:
121
if not via:
122
print("ignoring cutVia because via is not set", sys.stderr)
123
if len(via) == 1 and not origin and not dest:
124
print("cannot cutVia because only a single edge is given without origin or dest", sys.stderr)
125
result = []
126
if origin:
127
origin = stringToNumber(origin)
128
if dest:
129
dest = stringToNumber(dest)
130
if via:
131
via = list(map(stringToNumber, via))
132
if forbidden:
133
forbidden = list(map(stringToNumber, forbidden))
134
for step, routes in stepRoutes:
135
routes2 = []
136
for r in routes:
137
if origin and r.edges[0] != origin:
138
continue
139
if dest and r.edges[-1] != dest:
140
continue
141
if via or forbidden:
142
edgeSet = set(r.edges)
143
if forbidden and not edgeSet.isdisjoint(forbidden):
144
continue
145
if via and not (edgeSet.issuperset(via) and hasSequence(r.edges, via)):
146
continue
147
if cutVia and via:
148
iStart = npindex(r.edges, via[0])
149
if len(via) == 1:
150
if origin:
151
iStart = 0
152
iEnd = iStart
153
elif dest:
154
iEnd = r.edges.size - 1
155
else:
156
iEnd = iStart
157
else:
158
iEnd = npindex(r.edges, via[-1])
159
routes2.append(Route(r.vehID, -1, r.index, False, r.depart, r.edges[iStart:iEnd + 1]))
160
else:
161
routes2.append(r)
162
result.append((step, routes2))
163
return result
164
165
166
def costs(stepRoutes):
167
"""Compute statistics on costs computed by duarouter for the provided routes"""
168
for step, routes in stepRoutes:
169
s = Statistics("%s Costs" % step)
170
for r in routes:
171
s.add(r.cost, r.vehID)
172
print(s)
173
174
175
def distinct(stepRoutes, verbose=True):
176
"""Count the number of occurences for each distinct route and return distinct routes"""
177
result = []
178
for step, routes in stepRoutes:
179
routes2 = []
180
counts = {} # edges -> (count, info)
181
for r in routes:
182
etup = tuple(r.edges)
183
if etup in counts:
184
counts[etup][0] += 1
185
else:
186
# store information sufficient for identifying the route
187
counts[etup] = [1, (r.vehID, r.index)]
188
routes2.append(r)
189
result.append((step, routes2))
190
if verbose:
191
print("Route usage counts in step %s (Count vehID routeIndex)" % step)
192
rcounts = [(v, k) for k, v in counts.items()]
193
for (count, info), edges in sorted(rcounts):
194
print(count, info)
195
print("Total distinct routes: %s" % len(counts))
196
return result
197
198
199
def recompute_costs(baseDir, stepRoutes, netfile=None, tmpfileprefix="tmp"):
200
"""Recompute costs for all routes and all edgeData intervals"""
201
DUAROUTER = sumolib.checkBinary('duarouter')
202
tmp_input = tmpfileprefix + ".rou.gz"
203
tmp_output = tmpfileprefix + "_out.rou.gz"
204
tmp_output_alt = tmpfileprefix + "_out.rou.alt.gz"
205
dumpfiles = glob.glob(os.path.join(baseDir, "**/dump_*.xml"))
206
duarcfgs = glob.glob(os.path.join(baseDir, "**/*.duarcfg"))
207
dumpDict = dict([(int(os.path.basename(os.path.dirname(f))), f) for f in dumpfiles])
208
duarDict = dict([(int(os.path.basename(os.path.dirname(f))), f) for f in duarcfgs])
209
result = []
210
for step, routes in stepRoutes:
211
dumpfile = dumpDict[step]
212
with open(tmp_input, 'w') as tf:
213
tf.write('<routes>\n')
214
for interval in sumolib.xml.parse_fast(dumpfile, "interval", ["begin"]):
215
for r in routes:
216
# write a routeID for easier plotting
217
vehID = "%s_%s_%s" % (r.vehID, r.index, interval.begin)
218
routeID = "%s_%s" % (r.vehID, r.index)
219
tf.write(' <vehicle id="%s" depart="%s">\n' % (vehID, interval.begin))
220
tf.write(' <route edges="%s"/>\n' % ' '.join(map(numberToString, r.edges)))
221
tf.write(' <param key="routeID" value="%s"/>\n' % routeID)
222
tf.write(' </vehicle>\n')
223
tf.write('</routes>\n')
224
225
args = [DUAROUTER, '-c', duarDict[step],
226
'--weight-files', dumpfile,
227
'--skip-new-routes',
228
'--exit-times',
229
'-r', tmp_input,
230
'-o', tmp_output]
231
if netfile:
232
args += ['-n', netfile]
233
subprocess.call(args)
234
result.append((step, loadRoutes(tmp_output_alt)))
235
return result
236
237