2

I am trying to simulate a circular road with a series of traffic lights in sequence. Vehicles enter the system via a Poisson process. Once in the system, they queue at each light. It takes one time unit for them to go through each queue. They exit the system when they have traveled a number of queues equal to their trip length. The queues only operate during a green phase. Cars are represented by integers.

The problem is that I keep getting the error "pop from an empty deque" even though this part of the code is only reachable by an if statement that checks whether the deque has cars in it. I am not very familiar with simpy and so I think the problem has to do with the timeout. If I move the timeout after the pop operation, the code works. But this is not quiet what I want.

import simpy
from simpy.util import start_delayed
# import numpy.random
from collections import deque,namedtuple
from numpy import random
NUM_INT = 3
ARRIVAL_TIME_MEAN = 1.1
TRIP_LENGTH = 4
GREEN_TIME = 3.0
RED_TIME = 3.0

class Simulation(object):
    def __init__(self,env):
        self.env = env
        self.intersections = [Intersection(env,i) for i in range(NUM_INT)]
        for (i,intersection) in enumerate(self.intersections):
            intersection.set_next_intersection(self.intersections[(i+1)%NUM_INT])
        self.env.process(self.light())
        self.env.process(self.arrivals())

    def arrivals(self):
        while True:
            yield self.env.timeout(random.exponential(ARRIVAL_TIME_MEAN))
            intersection = random.choice(self.intersections)
            intersection.receive(TRIP_LENGTH)

    def light(self):
        while True:
            for intersection in self.intersections:
                intersection.start_departing()
            yield self.env.timeout(GREEN_TIME)
            for intersection in self.intersections:
                intersection.turn_red()
            yield env.timeout(RED_TIME)


class Intersection(object):
    def __init__(self,env,index):
        self.index = index
        self.queue = deque()
        self.env = env
        self.start_departing()

    def set_next_intersection(self,intersection):
        self.next_intersection = intersection

    def start_departing(self):
        self.is_departing = True
        self.action = env.process(self.departure())

    def turn_red(self):
        if self.is_departing:
            self.is_departing = False
            self.action.interrupt('red light')

    def receive(self,car):
        self.queue.append(car)
        if not self.is_departing:
            self.start_departing()

    def departure(self):
        while True:
            try:
                if len(self.queue)==0:
                    self.is_departing = False
                    self.env.exit('no more cars in %d'%self.index)
                else:
                    yield self.env.timeout(1.0)
                    car = self.queue.popleft()
                    car = car - 1
                    if car > 0:
                        self.next_intersection.receive(car)
            except simpy.Interrupt as i:
                print('interrupted by',i.cause)

env = simpy.Environment()
sim = Simulation(env)
env.run(until=15.0)
lewis500
  • 89
  • 6

2 Answers2

0

not a fix as much as a workaround...



import simpy
from simpy.util import start_delayed
# import numpy.random
from collections import deque,namedtuple
from numpy import random
NUM_INT = 3
ARRIVAL_TIME_MEAN = 1.1
TRIP_LENGTH = 4
GREEN_TIME = 3.0
RED_TIME = 3.0

class Simulation(object):
    def __init__(self,env):
        self.env = env
        self.intersections = [Intersection(env,i) for i in range(NUM_INT)]
        for (i,intersection) in enumerate(self.intersections):
            intersection.set_next_intersection(self.intersections[(i+1)%NUM_INT])
        self.env.process(self.light())
        self.env.process(self.arrivals())

    def arrivals(self):
        while True:
            yield self.env.timeout(random.exponential(ARRIVAL_TIME_MEAN))
            intersection = random.choice(self.intersections)
            intersection.receive(TRIP_LENGTH)

    def light(self):
        while True:
            for intersection in self.intersections:
                intersection.start_departing()
            yield self.env.timeout(GREEN_TIME)
            for intersection in self.intersections:
                intersection.turn_red()
            yield env.timeout(RED_TIME)


class Intersection(object):
    def __init__(self,env,index):
        self.index = index
        self.queue = deque()
        self.env = env
        self.start_departing()

    def set_next_intersection(self,intersection):
        self.next_intersection = intersection

    def start_departing(self):
        self.is_departing = True
        self.action = env.process(self.departure())

    def turn_red(self):
        if self.is_departing:
            self.is_departing = False
            self.action.interrupt('red light')

    def receive(self,car):
        self.queue.append(car)
        if not self.is_departing:
            self.start_departing()

    def departure(self):
        while True:
            try:
                if len(self.queue)==0:
                    self.is_departing = False
                    self.env.exit('no more cars in %d'%self.index)
                else:
                    yield self.env.timeout(1.0)
                    if len(self.queue)>0:
                        if len(self.queue)==1:
                            car=self.queue[0]
                            self.queue.clear()
                        else:
                            car = self.queue.popleft()
                        car = car - 1
                        if car > 0:
                            self.next_intersection.receive(car)
            except simpy.Interrupt as i:
                print('interrupted by',i.cause)

env = simpy.Environment()
sim = Simulation(env)
env.run(until=15.0)

tell me if this works for you

3NiGMa
  • 545
  • 1
  • 9
  • 24
  • testing for the queue length again does solve the problem, but it makes me afraid there is something wrong with the code that this is happening. I am wondering if it's possible there are two departure processes going on simultaneously. That isn't supposed to be happening. – lewis500 Nov 03 '19 at 22:13
  • Did you try the code, it does not return the error when I run it... – 3NiGMa Nov 03 '19 at 22:47
  • no i get "local variable 'car' referenced before assignment" – lewis500 Nov 03 '19 at 22:57
0

In departure, in the case where the queue is not empty, you immediately yield, which lets the calling process run, which can cause other events to cause the queue to be empty and raise your exception. This is a bit like cooperative multitasking, but without locks, so you have to be a bit careful with where you place yields.

Moving the yield to the end of that if fixes it for me.

    def departure(self):
        while True:
            try:
                if len(self.queue)==0:
                    self.is_departing = False
                    self.env.exit('no more cars in %d'%self.index)
                else:
                    car = self.queue.popleft()
                    car = car - 1
                    if car > 0:
                        self.next_intersection.receive(car)
                    yield self.env.timeout(1.0)
            except simpy.Interrupt as i:
                print('interrupted by',i.cause)
                yield self.env.timeout(1.0)
Dylan McNamee
  • 1,696
  • 14
  • 16