Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/output/vehrouteCountValidation.py
169674 views
1
#!/usr/bin/env python
2
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
# Copyright (C) 2012-2025 German Aerospace Center (DLR) and others.
4
# This program and the accompanying materials are made available under the
5
# terms of the Eclipse Public License 2.0 which is available at
6
# https://www.eclipse.org/legal/epl-2.0/
7
# This Source Code may also be made available under the following Secondary
8
# Licenses when the conditions for such availability set forth in the Eclipse
9
# Public License 2.0 are satisfied: GNU General Public License, version 2
10
# or later which is available at
11
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
14
# @file vehrouteCountValidation.py
15
# @author Jakob Erdmann
16
# @date 2022-10-20
17
18
"""
19
Compare counting data (edge counts or turn counts)
20
to counts obtained from a simulation (--vehroute-output, with exit-times).
21
"""
22
23
from __future__ import absolute_import
24
from __future__ import print_function
25
26
import os
27
import sys
28
29
if 'SUMO_HOME' in os.environ:
30
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
31
else:
32
THIS_DIR = os.path.abspath(os.path.dirname(__file__))
33
sys.path.append(os.path.join(THIS_DIR, '..'))
34
35
import sumolib # noqa
36
from sumolib.miscutils import parseTime # noqa
37
from routeSampler import getIntervals, parseCounts # noqa
38
39
40
def get_options(args=None):
41
parser = sumolib.options.ArgumentParser(description="Sample routes to match counts")
42
parser.add_argument("-r", "--route-files", dest="routeFiles",
43
help="Input route file (vehroute-output)")
44
parser.add_argument("-t", "--turn-files", dest="turnFiles",
45
help="Input turn-count file")
46
parser.add_argument("-T", "--turn-ratio-files", category="input", dest="turnRatioFiles", type=parser.file_list,
47
help="Input turn-ratio file")
48
parser.add_argument("-d", "--edgedata-files", dest="edgeDataFiles",
49
help="Input edgeData file (for counts)")
50
parser.add_argument("-O", "--od-files", category="input", dest="odFiles", type=parser.file_list,
51
help="Input edgeRelation and tazRelation files for origin-destination counts")
52
parser.add_argument("--taz-files", category="input", dest="tazFiles", type=parser.file_list,
53
help="Input TAZ (district) definitions for interpreting tazRelation files")
54
parser.add_argument("--edgedata-attribute", dest="edgeDataAttr", default="entered",
55
help="Read edgeData counts from the given attribute")
56
parser.add_argument("--arrival-attribute", dest="arrivalAttr",
57
help="Read arrival counts from the given edgeData file attribute")
58
parser.add_argument("--depart-attribute", dest="departAttr",
59
help="Read departure counts from the given edgeData file attribute")
60
parser.add_argument("--turn-attribute", dest="turnAttr", default="count",
61
help="Read turning counts from the given attribute")
62
parser.add_argument("--turn-ratio-attribute", dest="turnRatioAttr", default="probability",
63
help="Read turning ratios from the given attribute")
64
parser.add_argument("--turn-ratio-total", dest="turnRatioTotal", type=float, default=1,
65
help="Set value for normalizing turning ratios (default 1)")
66
parser.add_argument("--turn-max-gap", type=int, dest="turnMaxGap", default=0,
67
help="Allow at most a gap of INT edges between from-edge and to-edge")
68
parser.add_argument("-m", "--mismatch-files", dest="mismatchFiles",
69
help="Input mismatch file (as computed by routeSampler.py) to compensate sampling errors")
70
parser.add_argument("--prefix", dest="prefix", default="",
71
help="prefix for the vehicle ids")
72
parser.add_argument("--type", dest="type",
73
help="vehiclet type to filter by")
74
parser.add_argument("--mismatch-output", dest="mismatchOut", default="mismatch.xml",
75
help="write count-data with overflow/underflow information to FILE")
76
parser.add_argument("--geh-ok", dest="gehOk", type=float, default=5,
77
help="threshold for acceptable GEH values")
78
parser.add_argument("--pedestrians", action="store_true", default=False,
79
help="compare person walks instead of vehicle routes")
80
parser.add_argument("-b", "--begin", help="custom begin time (seconds or H:M:S)")
81
parser.add_argument("-e", "--end", help="custom end time (seconds or H:M:S)")
82
parser.add_argument("-i", "--interval", help="custom aggregation interval (seconds or H:M:S)")
83
parser.add_argument("-v", "--verbose", action="store_true", default=False,
84
help="tell me what you are doing")
85
parser.add_argument("-V", "--verbose.histograms", dest="verboseHistogram", action="store_true", default=False,
86
help="print histograms of edge numbers and detector passing count")
87
88
options = parser.parse_args(args=args)
89
if (options.routeFiles is None or
90
(options.turnFiles is None
91
and options.turnRatioFiles is None
92
and options.edgeDataFiles is None
93
and options.odFiles is None)):
94
sys.exit()
95
96
for attr in ["edgeDataAttr", "arrivalAttr", "departAttr", "turnAttr"]:
97
if getattr(options, attr) not in [None, "None"]:
98
setattr(options, attr, getattr(options, attr).split(","))
99
100
options.routeFiles = options.routeFiles.split(',')
101
options.turnFiles = options.turnFiles.split(',') if options.turnFiles is not None else []
102
options.turnRatioFiles = options.turnRatioFiles.split(',') if options.turnRatioFiles is not None else []
103
options.edgeDataFiles = options.edgeDataFiles.split(',') if options.edgeDataFiles is not None else []
104
options.odFiles = options.odFiles.split(',') if options.odFiles is not None else []
105
106
return options
107
108
109
class Routes:
110
"""dummy class to allow using the same methods as routeSampler.py"""
111
112
def __init__(self):
113
self.unique = []
114
115
116
def main(options):
117
intervals = getIntervals(options)
118
119
if len(intervals) == 0:
120
print("Error: no intervals loaded", file=sys.stderr)
121
sys.exit()
122
123
mismatchf = open(options.mismatchOut, 'w')
124
sumolib.writeXMLHeader(mismatchf, "$Id$", options=options) # noqa
125
mismatchf.write('<data>\n')
126
127
underflowSummary = sumolib.miscutils.Statistics("avg interval underflow")
128
overflowSummary = sumolib.miscutils.Statistics("avg interval overflow")
129
gehSummary = sumolib.miscutils.Statistics("avg interval GEH%")
130
inputCountSummary = sumolib.miscutils.Statistics("avg interval input count")
131
usedRoutesSummary = sumolib.miscutils.Statistics("avg interval loaded vehs")
132
133
for begin, end in intervals:
134
intervalPrefix = "" if len(intervals) == 1 else "%s_" % int(begin)
135
uFlow, oFlow, gehOK, inputCount, usedRoutes = checkInterval(options, begin, end, intervalPrefix, mismatchf)
136
underflowSummary.add(uFlow, begin)
137
overflowSummary.add(oFlow, begin)
138
gehSummary.add(gehOK, begin)
139
inputCountSummary.add(inputCount, begin)
140
usedRoutesSummary.add(usedRoutes, begin)
141
142
mismatchf.write('</data>\n')
143
mismatchf.close()
144
145
if len(intervals) > 1:
146
print(inputCountSummary)
147
print(usedRoutesSummary)
148
print(underflowSummary)
149
print(overflowSummary)
150
print(gehSummary)
151
152
153
def checkInterval(options, begin, end, intervalPrefix, mismatchf):
154
routes = Routes()
155
countData = parseCounts(options, routes, begin, end)
156
157
edgeCount = sumolib.miscutils.Statistics("route edge count", histogram=True)
158
detectorCount = sumolib.miscutils.Statistics("route detector count", histogram=True)
159
usedRoutes = []
160
161
for routeFile in options.routeFiles:
162
for vehicle in sumolib.xml.parse(routeFile, "vehicle"):
163
if options.type and vehicle.type != options.type:
164
continue
165
depart = parseTime(vehicle.depart)
166
if depart >= end:
167
continue
168
route = vehicle.route[0]
169
edges = tuple(route.edges.split())
170
edgeSet = set(edges)
171
172
exitTimes = []
173
if route.exitTimes:
174
exitTimes = tuple(map(parseTime, route.exitTimes.split()))
175
else:
176
exitTimes = [depart] * len(edges)
177
178
numPassedDets = 0
179
for cd in countData:
180
i = cd.routePasses(edges, edgeSet)
181
if i is not None:
182
numPassedDets += 1
183
et = exitTimes[i]
184
if et < begin or et >= end:
185
continue
186
cd.use()
187
usedRoutes.append(edges)
188
edgeCount.add(len(edges), vehicle.id)
189
detectorCount.add(numPassedDets, vehicle.id)
190
191
underflow = sumolib.miscutils.Statistics("underflow locations")
192
overflow = sumolib.miscutils.Statistics("overflow locations")
193
gehStats = sumolib.miscutils.Statistics("GEH")
194
numGehOK = 0.0
195
hourFraction = (end - begin) / 3600.0
196
totalCount = 0
197
totalOrigCount = 0
198
for cd in countData:
199
if cd.isRatio:
200
continue
201
localCount = cd.origCount - cd.count
202
totalCount += localCount
203
totalOrigCount += cd.origCount
204
if cd.count > 0:
205
underflow.add(cd.count, cd.edgeTuple)
206
elif cd.count < 0:
207
overflow.add(cd.count, cd.edgeTuple)
208
origHourly = cd.origCount / hourFraction
209
localHourly = localCount / hourFraction
210
geh = sumolib.miscutils.geh(origHourly, localHourly)
211
if geh < options.gehOk:
212
numGehOK += 1
213
gehStats.add(geh, "[%s] %s %s" % (
214
' '.join(cd.edgeTuple), int(origHourly), int(localHourly)))
215
216
outputIntervalPrefix = "" if intervalPrefix == "" else "%s: " % int(begin)
217
countPercentage = "%.2f%%" % (100 * totalCount / float(totalOrigCount)) if totalOrigCount else "-"
218
gehOKNum = 100 * numGehOK / float(len(countData)) if countData else 100
219
gehOK = "%.2f%%" % gehOKNum if countData else "-"
220
print("%sRead %s routes (%s distinct) achieving total count %s (%s) at %s locations. GEH<%s for %s" % (
221
outputIntervalPrefix,
222
len(usedRoutes), len(set(usedRoutes)),
223
totalCount, countPercentage, len(countData),
224
options.gehOk, gehOK))
225
226
if options.verboseHistogram:
227
print("result %s" % edgeCount)
228
print("result %s" % detectorCount)
229
print(gehStats)
230
231
if underflow.count() > 0:
232
print("Warning: %s (total %s)" % (underflow, sum(underflow.values)))
233
if overflow.count() > 0:
234
print("Warning: %s (total %s)" % (overflow, sum(overflow.values)))
235
sys.stdout.flush() # needed for multiprocessing
236
237
if mismatchf:
238
mismatchf.write(' <interval id="deficit" begin="%s" end="%s">\n' % (begin, end))
239
for cd in countData:
240
if len(cd.edgeTuple) == 1:
241
mismatchf.write(' <edge id="%s" measuredCount="%s" deficit="%s"/>\n' % (
242
cd.edgeTuple[0], cd.origCount, cd.count))
243
elif len(cd.edgeTuple) == 2:
244
mismatchf.write(' <edgeRelation from="%s" to="%s" measuredCount="%s" deficit="%s"/>\n' % (
245
cd.edgeTuple[0], cd.edgeTuple[1], cd.origCount, cd.count))
246
else:
247
print("Warning: output for edge relations with more than 2 edges not supported (%s)" % cd.edgeTuple,
248
file=sys.stderr)
249
mismatchf.write(' </interval>\n')
250
251
return sum(underflow.values), sum(overflow.values), gehOKNum, totalOrigCount, len(usedRoutes)
252
253
254
if __name__ == "__main__":
255
main(get_options())
256
257