Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/assign/costMemory.py
169673 views
1
# -*- coding: utf-8 -*-
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 costMemory.py
15
# @author Jakob Erdmann
16
# @author Michael Behrisch
17
# @date 2012-03-14
18
19
from __future__ import print_function
20
from __future__ import absolute_import
21
import os
22
import sys
23
from collections import defaultdict
24
from xml.sax import make_parser, handler
25
26
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
27
from sumolib.net import readNet # noqa
28
29
30
class EdgeMemory:
31
32
def __init__(self, cost):
33
self.cost = cost
34
self.seen = True
35
36
def update(self, cost, memory_weight, new_weight, pessimism):
37
p = (cost / self.cost) ** pessimism if self.cost > 0 else 1
38
memory_factor = memory_weight / (memory_weight + new_weight * p)
39
self.cost = self.cost * memory_factor + cost * (1 - memory_factor)
40
self.seen = True
41
42
43
class CostMemory(handler.ContentHandler):
44
# memorize the weighted average of edge costs
45
46
def __init__(self, cost_attribute, pessimism=0, network_file=None):
47
# the cost attribute to parse (i.e. 'traveltime')
48
self.cost_attribute = cost_attribute.encode('utf8')
49
# the duaIterate iteration index
50
self.iteration = None
51
# the main data store: for every interval and edge id we store costs and
52
# whether data was seen in the last call of load_costs()
53
# start -> (edge_id -> EdgeMemory)
54
self.intervals = defaultdict(dict)
55
# the interval length (only known for certain if multiple intervals
56
# have been seen)
57
self.interval_length = 214748 # SUMOTIME_MAXSTRING
58
# the interval currently being parsed
59
self.current_interval = None
60
# the combined weight of all previously loaded costs
61
self.memory_weight = 0.0
62
# update is done according to: memory * memory_factor + new * (1 -
63
# memory_factor)
64
self.memory_factor = None
65
# differences between the previously loaded costs and the memorized
66
# costs
67
self.errors = None
68
# some statistics
69
self.num_loaded = 0
70
self.num_decayed = 0
71
# travel times without obstructing traffic
72
# XXX could use the minimum known traveltime
73
self.traveltime_free = defaultdict(lambda: 0)
74
if network_file is not None:
75
# build a map of default weights for decaying edges assuming the
76
# attribute is traveltime
77
self.traveltime_free = dict([(e.getID(), e.getLength() / e.getSpeed())
78
for e in readNet(network_file).getEdges()])
79
self.pessimism = pessimism
80
81
def startElement(self, name, attrs):
82
if name == 'interval':
83
self.current_interval = self.intervals[float(attrs['begin'])]
84
if name == 'edge':
85
id = attrs['id']
86
# may be missing for some
87
if self.cost_attribute.decode('utf-8') in attrs:
88
self.num_loaded += 1
89
cost = float(attrs[self.cost_attribute.decode('utf-8')])
90
if id in self.current_interval:
91
edgeMemory = self.current_interval[id]
92
self.errors.append(edgeMemory.cost - cost)
93
edgeMemory.update(
94
cost, self.memory_weight, self.new_weight, self.pessimism)
95
# if id == "4.3to4.4":
96
# with open('debuglog', 'a') as f:
97
# print(self.memory_factor, edgeMemory.cost, file=f)
98
else:
99
self.errors.append(0)
100
self.current_interval[id] = EdgeMemory(cost)
101
102
def load_costs(self, dumpfile, iteration, weight):
103
# load costs from dumpfile and update memory according to weight and
104
# iteration
105
if weight <= 0:
106
sys.stderr.write(
107
"Skipped loading of costs because the weight was %s but should have been > 0\n" % weight)
108
return
109
assert weight > 0
110
if self.iteration is None and iteration != 0:
111
print("Warning: continuing with empty memory")
112
# update memory weights. memory is a weighted average across all runs
113
self.new_weight = float(weight)
114
self.iteration = iteration
115
self.errors = []
116
# mark all edges as unseen
117
for edges in self.intervals.values():
118
for edgeMemory in edges.values():
119
edgeMemory.seen = False
120
# parse costs
121
self.num_loaded = 0
122
parser = make_parser()
123
parser.setContentHandler(self)
124
parser.parse(dumpfile)
125
# decay costs of unseen edges
126
self.num_decayed = 0
127
for edges in self.intervals.values():
128
for id, edgeMemory in edges.items():
129
if not edgeMemory.seen:
130
edgeMemory.update(
131
self.traveltime_free[id], self.memory_weight, self.new_weight, self.pessimism)
132
self.num_decayed += 1
133
# if id == "4.3to4.4":
134
# with open('debuglog', 'a') as f:
135
# print(self.memory_factor, 'decay', edgeMemory.cost, file=f)
136
# figure out the interval length
137
if len(self.intervals.keys()) > 1:
138
sorted_begin_times = sorted(self.intervals.keys())
139
self.interval_length = sorted_begin_times[
140
1] - sorted_begin_times[0]
141
self.memory_weight += self.new_weight
142
143
def write_costs(self, weight_file):
144
with open(weight_file, 'w') as f:
145
f.write('<netstats>\n')
146
for start, edge_costs in self.intervals.items():
147
f.write(' <interval begin="%d" end="%d">\n' %
148
(start, start + self.interval_length))
149
for id, edgeMemory in edge_costs.items():
150
f.write(' <edge id="%s" %s="%s"/>\n' %
151
(id, self.cost_attribute.decode('utf-8'), edgeMemory.cost))
152
f.write(' </interval>\n')
153
f.write('</netstats>\n')
154
155
def avg_error(self, values=None):
156
if not values:
157
values = self.errors
158
length = len(list(values))
159
if length > 0:
160
return (sum(list(values)) / length)
161
return 0
162
163
def avg_abs_error(self):
164
return self.avg_error(list(map(abs, self.errors)))
165
166
def mean_error(self, values=None):
167
if not values:
168
values = self.errors
169
values.sort()
170
if values:
171
return values[len(values) // 2]
172
return 0
173
174
def mean_abs_error(self):
175
return self.mean_error(list(map(abs, self.errors)))
176
177
def loaded(self):
178
return self.num_loaded
179
180
def decayed(self):
181
return self.num_decayed
182
183