2

I'm trying to simulate a bunch of tasks that all together belong to the same generic project.
Tasks have duration and precedence (represented by a list of tasks that have to be done before the current task).

For example:

      /--->A --->B\
Start ---->C------->End
      \--->D---->E/

That means tasks A, C and D can be executed at same time, but task B should be executed after task A, and task E after task D.

I wrote this code:

#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import simpy


class Task(object):
    """ 
        Has an id i.e.: 'E',
        duration i.e.: 5
        a list with Task that precede the current
    """
    def __init__(self, idTask, duration, env, previousTasks=None):
        self.idTask = idTask
        self.duration = duration
        self.env = envs
        self.completed = env.event()
        self.action = env.process(self.run())
        if previousTasks is None:
            self.previousTasks = []
        else:
            self.previousTasks = previousTasks

    def run(self):
        while self.can_execute() and not self.completed.triggered:
            print "Starting task %s at time %s" % (self.idTask, self.env.now)
            yield self.env.timeout(self.duration)
            print "Completed task %s in time %s" % (self.idTask, self.env.now)
            self.completed.succeed(True)

    def can_execute(self):
        result = True
        for task in self.previousTasks:
            if not task.completed.triggered:
                result = False
                break
        return result

if __name__ == "__main__":
    env = simpy.Environment()
    taskA = Task('A', 4, env)
    taskB = Task('B', 5, env, [taskA])
    env.run(until=20)

Completed attribute in Task object, is for having a way to know if current task is completed or finished. (I tried to do that with a boolean value with no different results.)

taskB has taskA as a precedent task. That means that taskB should start before taskA finishes, but this is the output when I run it:

% python tasks.py
Starting task A at time 0
Completed task A at time 4
%

I don't understand why taskB couldn't run.

---------------------------------------------------------------------------

SOLVED

I used "start_delayed" method, available in simpy.util module from Simpy. Depending on if the current task has previous tasks to be performed, the "action" of each task is a simple process or a delayed process.
Delay for each task is calculated in add_precedences and estimate_delays but is necessary to sum duration of previous tasks to the previous task of the current.
Here is the final code:

#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import simpy
from simpy.util import start_delayed

delays = {}
completed = []


class Task(object):
    """ Has an id i.e.: 'E',
        duration i.e.: 5
        a list with Task that precede the current
    """
    def __init__(self, id, duration, env, previousTasks=None):
        self.id = id
        self.duration = duration
        self.env = env
        if previousTasks is None:
            self.previousTasks = []
        else:
            self.previousTasks = previousTasks
        self.action = None

    def run(self):
        while True:
            if delays[self.id] == self.env.now:
                print "Start task: %s at time: %s" % (self.id, self.env.now)
                yield self.env.timeout(self.duration)
                completed.append(self.id)
                print "Finish task: %s at time: %s" % (self.id, self.env.now)
            else:
                if self.id in completed:
                    self.env.exit()


def add_precedences(prevTask, durations):
    if len(prevTask) == 0:
        return 0
    else:
        durations.extend(map(lambda x: x.duration, prevTask))
        for prev in prevTask:
            add_precedences(prev.previousTasks, durations)
        return sum(durations)


def estimate_delays(tasks):
    result = {}
    for task in tasks:
        durations = []
        total = (add_precedences(task.previousTasks, durations))
        result.update({task.id: total})
    return result


def set_action(tasks):
    for task in tasks:
        if delays[task.id] > 0:
            task.action = start_delayed(task.env, task.run(), delays[task.id])
        else:
            task.action = env.process(task.run())

if __name__ == '__main__':
    env = simpy.Environment()
    taskA = Task('A', 4, env)
    taskB = Task('B', 5, env, previousTasks=[taskA])
    taskC = Task('C', 2, env, previousTasks=[taskB])
    tasks = [taskA, taskB, taskC]
    delays.update(estimate_delays(tasks))
    set_action(tasks)
    env.run(until=20)

In this case, the project has 3 tasks ('A','B','C') and here is the tree:

Start --->A--->B--->C--->End 

B has to start at time: 4 (because that is the duration of A). And C, has to start at time: 9 (because that is the duration of B+A).
To know which task has already finished, I created a list that each task adds its own id. If its own id is on that list each task is removed from the environment.

Community
  • 1
  • 1
pazitos10
  • 1,641
  • 16
  • 25
  • Small comment to your code: you should not use [mutable default arguments](http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument), they can lead to surprises. It is more common to do something like `def __init__(..., previousTasks=None): if previousTasks is None: previousTasks = []`. – Bas Swinckels May 09 '15 at 20:24

1 Answers1

1

I should do this as a comment, but I don't have the reputation to do so. Nevertheless, I think it might help with your problem.

I am not familiar with simpy but you might be confusing triggered and processed which is not allowing task B to execute; check this link: http://simpy.readthedocs.org/en/latest/api_reference/simpy.events.html#module-simpy.events

pbc1303
  • 142
  • 9
  • Hi! I tried with "self.completed.processed" instead of triggered, but it shows me this: RuntimeError: has already been triggered. But before, the simulation throws that only taskA is running. – pazitos10 May 09 '15 at 23:02
  • 1
    Ok. I think I got it now. When task B enters the run function, it cannot execute while, since A is a predecessor, so it exists the function and never returns to it. Maybe you should but a while loop external to the one in the run function (which would not be very efficient), The ideal way seems to be trying to signal task B that it can start. Is it clear? – pbc1303 May 10 '15 at 00:48
  • That's exactly what happens, that's because I tried to use Event class for completed attribute. But, I cannot signal to the other task using Simpy and if I could, I must had a record of the maximum duration of the predecessors and wake it up when env.now == timeToWake. Right? – pazitos10 May 10 '15 at 01:10
  • 1
    Yes, what you said seems right. But I think you should try to change your implementation a bit. Maybe try to use class **simpy.events.Condition**, the documentation says it is an event that is triggered when a list of conditions is satisfied. In your case, the list of conditions is the list of preceding tasks to be performed. – pbc1303 May 10 '15 at 05:11
  • I will change my implementation using that class you named. Thank you very much!. I will post the results :) – pazitos10 May 10 '15 at 18:39
  • its solved, thanks for your help!! Greetings from Argentina. Sorry for my english – pazitos10 May 11 '15 at 00:57