1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
2# Copyright (C) 2007-2019 German Aerospace Center (DLR) and others.
3# This program and the accompanying materials
4# are made available under the terms of the Eclipse Public License v2.0
5# which accompanies this distribution, and is available at
6# http://www.eclipse.org/legal/epl-v20.html
7# SPDX-License-Identifier: EPL-2.0
8
9# @file    elements.py
10# @author  Yun-Pang Floetteroed
11# @author  Daniel Krajzewicz
12# @author  Michael Behrisch
13# @date    2007-10-25
14# @version $Id$
15
16"""
17This script is to define the classes and functions for
18- reading network geometric,
19- calculating link characteristics, such as capacity, travel time and link cost function,
20- recording vehicular and path information, and
21- conducting statistic tests.
22"""
23from __future__ import absolute_import
24from __future__ import print_function
25
26import sys
27import math
28from tables import crCurveTable, laneTypeTable
29import sumolib
30
31# This class is used for finding the k shortest paths.
32
33
34class Predecessor:
35
36    def __init__(self, edge, pred, distance):
37        self.edge = edge
38        self.pred = pred
39        self.distance = distance
40
41# This class is used to build the nodes in the investigated network and
42# includes the update-function for searching the k shortest paths.
43
44
45class Vertex(sumolib.net.node.Node):
46
47    """
48    This class is to store node attributes and the respective incoming/outgoing links.
49    """
50
51    def __init__(self, id, type=None, coord=None, incLanes=None):
52        sumolib.net.node.Node.__init__(self, id, type, coord, incLanes)
53        self.preds = []
54        self.wasUpdated = False
55
56    def __repr__(self):
57        return self._id
58
59    def _addNewPredecessor(self, edge, updatePred, newPreds):
60        for pred in newPreds:
61            if pred.pred == updatePred:
62                return
63        pred = updatePred
64        while pred.edge is not None:
65            if pred.edge == edge:
66                return
67            pred = pred.pred
68        newPreds.append(Predecessor(edge, updatePred,
69                                    updatePred.distance + edge.actualtime))
70
71    def update(self, KPaths, edge):
72        updatePreds = edge._from.preds
73        if len(self.preds) == KPaths\
74           and updatePreds[0].distance + edge.actualtime >= self.preds[KPaths - 1].distance:
75            return False
76        newPreds = []
77        updateIndex = 0
78        predIndex = 0
79        while len(newPreds) < KPaths\
80            and (updateIndex < len(updatePreds) or
81                 predIndex < len(self.preds)):
82            if predIndex == len(self.preds):
83                self._addNewPredecessor(
84                    edge, updatePreds[updateIndex], newPreds)
85                updateIndex += 1
86            elif updateIndex == len(updatePreds):
87                newPreds.append(self.preds[predIndex])
88                predIndex += 1
89            elif updatePreds[updateIndex].distance + edge.actualtime < self.preds[predIndex].distance:
90                self._addNewPredecessor(
91                    edge, updatePreds[updateIndex], newPreds)
92                updateIndex += 1
93            else:
94                newPreds.append(self.preds[predIndex])
95                predIndex += 1
96        if predIndex == len(newPreds):
97            return False
98        self.preds = newPreds
99        returnVal = not self.wasUpdated
100        self.wasUpdated = True
101        return returnVal
102
103# This class is used to store link information and estimate
104# as well as flow and capacity for the flow computation and some parameters
105# read from the net.
106
107
108class Edge(sumolib.net.edge.Edge):
109
110    """
111    This class is to record link attributes
112    """
113
114    def __init__(self, label, source, target, prio, function, name):
115        sumolib.net.edge.Edge.__init__(
116            self, label, source, target, prio, function, name)
117        self.capacity = sys.maxsize
118        # parameter for estimating capacities according to signal timing plans
119        self.junction = None
120        self.junctiontype = None
121        self.rightturn = None
122        self.straight = None
123        self.leftturn = None
124        self.uturn = None
125        self.leftlink = []
126        self.straightlink = []
127        self.rightlink = []
128        self.conflictlink = {}
129#        self.againstlinkexist = None
130        self.flow = 0.0
131        self.helpflow = 0.0
132        self.freeflowtime = 0.0
133        self.queuetime = 0.0
134        self.estcapacity = 0.0
135        self.CRcurve = None
136        self.actualtime = 0.0
137        self.ratio = 0.0
138        self.connection = 0
139        self.edgetype = None
140        # parameter in the Lohse traffic assignment
141        self.helpacttime = 0.
142        # parameter in the Lohse traffic assignment
143        self.fTT = 0.
144        # parameter in the Lohse traffic assignment
145        self.TT = 0.
146        # parameter in the Lohse traffic assignment
147        self.delta = 0.
148        # parameter in the Lohse traffic assignment
149        self.helpacttimeEx = 0.
150        # parameter in the matrix estimation
151        self.detected = False
152        self.detectorNum = 0.
153        self.detecteddata = {}
154        self.detectedlanes = 0.
155        self.penalty = 0.
156        self.capLeft = 0.
157        self.capRight = 0.
158        self.capThrough = 0.
159
160    def addLane(self, lane):
161        sumolib.net.edge.Edge.addLane(self, lane)
162        if self._from._id == self._to._id:
163            self.freeflowtime = 0.0
164        else:
165            self.freeflowtime = self._length / self._speed
166            self.actualtime = self.freeflowtime
167            self.helpacttime = self.freeflowtime
168
169    def __repr__(self):
170        cap = str(self.capacity)
171        if self.capacity == sys.maxsize or self.connection != 0:
172            cap = "inf"
173        return "%s_%s_%s_%s<%s|%s|%s|%s|%s|%s|%s|%s|%s>" % (self._function, self._id, self._from, self._to,
174                                                            self.junctiontype, self._speed,
175                                                            self.flow, self._length, self._lanes,
176                                                            self.CRcurve, self.estcapacity, cap, self.ratio)
177
178    def getConflictLink(self):
179        """
180        method to get the conflict links for each link, when the respective left-turn behavior exists.
181        """
182        if self._function == 'real' and len(self.leftlink) > 0:
183            for leftEdge in self.leftlink:
184                affectedTurning = None
185                for edge in leftEdge.source.inEdges:
186                    if edge.source == self.target:
187                        affectedTurning = edge
188                        affectedTurning.freeflowtime = 6.
189                        affectedTurning.actualtime = affectedTurning.freeflowtime
190                        affectedTurning.helpacttime = affectedTurning.freeflowtime
191
192                for edge in leftEdge.source.inEdges:
193                    for upstreamlink in edge.source.inEdges:
194                        if leftEdge in upstreamlink.rightlink and len(upstreamlink.straightlink) > 0:
195                            if upstreamlink not in self.conflictlink:
196                                self.conflictlink[upstreamlink] = []
197                            self.conflictlink[upstreamlink].append(
198                                affectedTurning)
199
200    def getFreeFlowTravelTime(self):
201        return self.freeflowtime
202
203    def addDetectedData(self, detecteddataObj):
204        self.detecteddata[detecteddataObj.label] = detecteddataObj
205
206    def getCapacity(self):
207        """
208        method to read the link capacity and the cr-curve type from the table.py
209        the applied CR-curve database is retrived from VISUM-Validate-network und VISUM-Koeln-network
210        """
211        typeList = laneTypeTable[min(len(self._lanes), 4)]
212        for laneType in typeList:
213            if laneType[0] >= self._speed:
214                break
215
216        self.estcapacity = len(self._lanes) * laneType[1]
217        self.CRcurve = laneType[2]
218
219    def getAdjustedCapacity(self, net):
220        """
221        method to adjust the link capacity based on the given signal timing plans
222        """
223        straightGreen = 0.
224        rightGreen = 0.
225        leftGreen = 0.
226        greentime = 0.
227        straightSymbol = -1
228        rightSymbol = -1
229        leftSymbol = -1
230        cyclelength = 0.
231        count = 0
232        if self.junctiontype == 'signalized':
233            junction = net._junctions[self.junction]
234            if self.rightturn is not None and self.rightturn != 'O' and self.rightturn != 'o':
235                rightSymbol = int(self.rightturn)
236            if self.leftturn is not None and self.leftturn != 'O' and self.leftturn != 'o':
237                leftSymbol = int(self.leftturn)
238            if self.straight is not None and self.straight != 'O' and self.straight != 'o':
239                straightSymbol = int(self.straight)
240            for phase in junction.phases[:]:
241                count += 1
242                cyclelength += phase.duration
243                if straightSymbol != -1 and phase.green[straightSymbol] == "1":
244                    straightGreen += phase.duration
245                if rightSymbol != -1 and phase.green[rightSymbol] == "1":
246                    rightGreen += phase.duration
247                if leftSymbol != -1 and phase.green[leftSymbol] == "1":
248                    leftGreen += phase.duration
249            if self.straight is not None:
250                self.estcapacity = (
251                    straightGreen * (3600. / cyclelength)) / 1.5 * len(self._lanes)
252            else:
253                greentime = max(rightGreen, leftGreen)
254                self.estcapacity = (
255                    greentime * (3600. / cyclelength)) / 1.5 * len(self._lanes)
256
257    def getActualTravelTime(self, options, lohse):
258        """
259        method to calculate/update link travel time
260        """
261        foutcheck = open('queue_info.txt', 'a')
262
263        if self.CRcurve in crCurveTable:
264            curve = crCurveTable[self.CRcurve]
265            if self.flow == 0.0 or self.connection > 0:
266                self.actualtime = self.freeflowtime
267            elif self.estcapacity != 0. and self.connection == 0:
268                self.actualtime = self.freeflowtime * \
269                    (1 + (curve[0] * (self.flow /
270                                      (self.estcapacity * curve[2]))**curve[1]))
271
272            if ((self.flow > self.estcapacity or self.flow == self.estcapacity) and
273                    self.flow > 0. and self.connection == 0):
274                self.queuetime = self.queuetime + options.lamda * \
275                    (self.actualtime - self.freeflowtime * (1 + curve[0]))
276                if self.queuetime < 1.:
277                    self.queuetime = 0.
278                else:
279                    foutcheck.write(
280                        'edge.label= %s: queuing time= %s.\n' % (self._id, self.queuetime))
281                    foutcheck.write('travel time at capacity: %s; actual travel time: %s.\n' % (
282                        self.freeflowtime * (1 + curve[0]), self.actualtime))
283            else:
284                self.queuetime = 0.
285
286            self.actualtime += self.queuetime
287
288            if lohse:
289                self.getLohseParUpdate(options)
290            else:
291                self.helpacttime = self.actualtime
292
293            self.penalty = 0.
294            if len(self.conflictlink) > 0:
295                for edge in self.conflictlink:
296                    conflictEdge = edge
297                flowCapRatio = conflictEdge.flow / conflictEdge.estcapacity
298
299                weightFactor = 1.0
300                if self.numberlane == 2.:
301                    weightFactor = 0.85
302                elif self.numberlane == 3.:
303                    weightFactor = 0.75
304                elif self.numberlane > 3.:
305                    weightFactor = 0.6
306                if options.dijkstra != 'extend':
307                    for edge in self.conflictlink:
308                        penalty = 0.
309                        if edge.estcapacity > 0. and edge.flow / edge.estcapacity > 0.12:
310                            penalty = weightFactor * \
311                                (math.exp(self.flow / self.estcapacity) - 1. +
312                                 math.exp(edge.flow / edge.estcapacity) - 1.)
313                            for affectedTurning in self.conflictlink[edge]:
314                                affectedTurning.actualtime = self.actualtime * \
315                                    penalty
316                                if lohse:
317                                    affectedTurning.helpacttime = self.helpacttime * \
318                                        penalty
319                                else:
320                                    affectedTurning.helpacttime = affectedTurning.actualtime
321                else:
322                    for edge in self.conflictlink:
323                        if edge.estcapacity > 0. and edge.flow / edge.estcapacity >= flowCapRatio:
324                            conflictEdge = edge
325                            flowCapRatio = edge.flow / edge.estcapacity
326
327                    if conflictEdge.estcapacity > 0. and conflictEdge.flow / conflictEdge.estcapacity > 0.12:
328                        self.penalty = weightFactor * \
329                            (math.exp(self.flow / self.estcapacity) - 1. +
330                             math.exp(conflictEdge.flow / conflictEdge.estcapacity) - 1.)
331                    if lohse:
332                        self.penalty *= self.helpacttime
333                    else:
334                        self.penalty *= self.actualtime
335        foutcheck.close()
336
337    def cleanFlow(self):
338        """ method to reset link flows """
339        self.flow = 0.
340        self.helpflow = 0.
341
342    def getLohseParUpdate(self, options):
343        """
344        method to update the parameter used in the Lohse-assignment (learning method - Lernverfahren)
345        """
346        if self.helpacttime > 0.:
347            self.TT = abs(self.actualtime - self.helpacttime) / \
348                self.helpacttime
349            self.fTT = options.v1 / \
350                (1 + math.exp(options.v2 - options.v3 * self.TT))
351            self.delta = options.under + \
352                (options.upper - options.under) / ((1 + self.TT)**self.fTT)
353            self.helpacttimeEx = self.helpacttime
354            self.helpacttime = self.helpacttime + self.delta * \
355                (self.actualtime - self.helpacttime)
356
357    def stopCheck(self, options):
358        """
359        method to check if the convergence reaches in the Lohse-assignment
360        """
361        stop = False
362        criteria = 0.
363        criteria = options.cvg1 * \
364            self.helpacttimeEx**(options.cvg2 / options.cvg3)
365
366        if abs(self.actualtime - self.helpacttimeEx) <= criteria:
367            stop = True
368        return stop
369
370
371class Vehicle:
372
373    """
374    This class is to store vehicle information, such as departure time, route and travel time.
375    """
376
377    def __init__(self, label):
378        self.label = label
379        self.method = None
380        self.depart = 0.
381        self.arrival = 0.
382        self.speed = 0.
383        self.route = []
384        self.traveltime = 0.
385        self.travellength = 0.
386        self.departdelay = 0.
387        self.waittime = 0.
388        self.rank = 0.
389
390    def __repr__(self):
391        return "%s_%s_%s_%s_%s_%s<%s>" % (self.label, self.depart, self.arrival, self.speed,
392                                          self.traveltime, self.travellength, self.route)
393
394
395pathNum = 0
396
397
398class Path:
399
400    """
401    This class is to store path information which is mainly for the C-logit and the Lohse models.
402    """
403
404    def __init__(self, source, target, edges):
405        self.source = source
406        self.target = target
407        global pathNum
408        self.label = "%s" % pathNum
409        pathNum += 1
410        self.edges = edges
411        self.length = 0.0
412        self.freepathtime = 0.0
413        self.actpathtime = 0.0
414        self.pathflow = 0.0
415        self.helpflow = 0.0
416        self.choiceprob = 0.0
417        self.sumOverlap = 0.0
418        self.utility = 0.0
419        # parameter used in the Lohse traffic assignment
420        self.usedcounts = 1
421        # parameter used in the Lohse traffic assignment
422        self.pathhelpacttime = 0.
423        # record if this path is the currrent shortest one.
424        self.currentshortest = True
425
426    def __repr__(self):
427        return "%s_%s_%s<%s|%s|%s|%s>" % (self.label, self.source, self.target, self.freepathtime,
428                                          self.pathflow, self.actpathtime, self.edges)
429
430    def getPathLength(self):
431        for edge in self.edges:
432            self.length += edge._length
433
434    def updateSumOverlap(self, newpath, gamma):
435        overlapLength = 0.
436        for edge in self.edges:
437            if edge in newpath.edges:
438                overlapLength += edge._length
439        overlapLength = overlapLength / 1000.
440        lengthOne = self.length / 1000.
441        lengthTwo = newpath.length / 1000.
442        self.sumOverlap += math.pow(overlapLength /
443                                    (math.pow(lengthOne, 0.5) * math.pow(lengthTwo, 0.5)), gamma)
444
445    def getPathTimeUpdate(self):
446        """
447        used to update the path travel time in the c-logit and the Lohse traffic assignments
448        """
449        self.actpathtime = 0.
450        self.pathhelpacttime = 0.
451        for edge in self.edges:
452            self.actpathtime += edge.actualtime
453            self.pathhelpacttime += edge.helpacttime
454
455        self.pathhelpacttime = self.pathhelpacttime / 3600.
456        self.actpathtime = self.actpathtime / 3600.
457
458
459class TLJunction:
460
461    def __init__(self):
462        self.label = None
463        self.phaseNum = 0
464        self.phases = []
465
466    def __repr__(self):
467        return "%s_%s<%s>" % (self.label, self.phaseNum, self.phases)
468
469
470class Signalphase:
471
472    def __init__(self, duration, state=None, phase=None, brake=None, yellow=None):
473        self.label = None
474        self.state = state
475        self.duration = duration
476        self.green = ''
477        self.brake = ''
478        self.yellow = ''
479
480        if phase and brake and yellow:
481            self.green = phase[::-1]
482            self.brake = brake[::-1]
483            self.yellow = yellow[::-1]
484        elif self.state:
485            for elem in self.state:
486                if elem == 'G':
487                    self.green += '1'
488                    self.brake += '0'
489                    self.yellow += '0'
490                elif elem == 'y':
491                    self.green += '0'
492                    self.brake += '0'
493                    self.yellow += '1'
494                elif elem == 'r':
495                    self.green += '0'
496                    self.brake += '1'
497                    self.yellow += '0'
498        else:
499            print('no timing plans exist!')
500
501    def __repr__(self):
502        return "%s_%s<%s|%s|%s>" % (self.label, self.duration, self.green, self.brake, self.yellow)
503