4

The system that I am modelling has objects that require maintenance in the form of a series of tasks. Currently in this model, they request a "work location", then once they have seized that, they request the necessary "worker" resources they need to complete the first task. The tasks are objects in a list that is an attribute of the object having maintenance done to it, and some of the task networks allow for multiple tasks to be completed in parallel.

As it stands, I have the task list being iterated over in a for loop, where a nested for loop then requests and seizes the necessary "workers" and then times out for the duration of the task once all the "workers" have been seized.

        with self.location.request() as loc_req: # request work location
            yield loc_req # wait for work location

            for task in self.tasks[:]:
                t_duration = task.calc_duration(self)
                if t_duration == 0: # skip tasks where probability sets duration to 0
                    continue
                needs = task.resources[:]
                ## check if available workers are useful; if not, release them
                task_cur_resources = []
                for res,req in self.resources['available'].copy():
                    if res.worker_id in needs:
                        self.resources['busy'].add((res,req))
                        task_cur_resources.append((res,req))
                        needs.remove(res.worker_id)
                    else:
                        res.release(req)
                    self.resources['available'].remove((res,req))
                ## acquire all resources needed for task
                for need in needs[:]:
                    priority = len(needs) # prioritize tasks closer to meeting needs
                    res = self.location.workers[need] # set resource to worker of type need
                    req = res.request(priority) # save the request object
                    yield req # wait for resource
                    ## stash resource and request in order to release later
                    task_cur_resources.append((res, req))
                    self.resources['busy'].add((res,req))
                    needs.remove(res.worker_id)

                ## perform task with task duration timeout
                yield self.env.process(task.perform(self, t_duration))

                ## make resources available
                for worker in task_cur_resources: 
                    self.resources['busy'].remove(worker)
                    self.resources['available'].add(worker)

            for res,req in self.resources['available']:
                res.release(req)
            self.resources['available'] = set()

The issue is that this does not allow for concurrent task completion. The tasks are done sequentially with normally distributed durations based on input params. How can I change this to allow for tasks to be done whenever their predecessors are done and the workers are available? I tried a while loop that iterated through the task list and scheduled tasks that had their predecessors complete, but I kept ending up with infinite loops due to my apparent misuse of SimPy and the yields. Any ideas?

Charles Bushrow
  • 437
  • 4
  • 12

1 Answers1

1

env.process() creates a event

the trick is to collect these events in a list and then use env.all_of to yeild on that list of events. there is also env.any_of

here is a example

"""
Demonstrates how to grab some resources concurrently, and then do some tasks concurently

programer Michael R. Gibbs
"""

import simpy

class Asset():
    """
    asset that needs some maintenace
    creating the asset starts the assets processes

    to keep thins simple, I modeled  just the maintenace task and skipped a prime task that would 
    get interuppted when it was time to do maintence
    """
    def __init__(self, env, id, maintTasks):
        """
        store attributes, kick opp mantence process
        """
        self.id = id
        self.env = env
        self.maintTasks = maintTasks

        self.env.process(self.do_maintence())

    def do_maintence(self):
        """
        waits till time to do maintence
        grabs a work location resource
        do each maintence task
        each maintaince task has a list of resouces that are grabbed at the same time
        and once they have all been seized
        then the maintence task's list of processes are all kicked off at the same time
        affter all the process are finish, then all the resources are released
        """

        yield self.env.timeout(3)

        print(f'{self.env.now} object {self.id} is starting maintense')

        # grab work location
        with workLocRes.request() as workReq:
            yield workReq
            print(f'{self.env.now} object {self.id} got work loc, starting tasks')

            # for each maintTask, get the list of resources, and tasks
            for res, tasks in self.maintTasks:
                print(f'{self.env.now} -- object {self.id} start loop tasks')

                # get the requests for the needed resources
                print(f'{self.env.now} object {self.id} res 1: {res1.count}, res 2 {res2.count}')
                resList = []
                for r in res:
                    req = r.request()
                    req.r = r  # save the resource in the request
                    resList.append(req)
                # one yield that waits for all the requests
                yield self.env.all_of(resList)
                print(f'{self.env.now} object {self.id} res 1: {res1.count}, res 2 {res2.count}')

                # start all the tasks and save the events
                taskList = []
                for t in tasks:
                    taskList.append(self.env.process(t(self.env, self.id)))
                #one yield that waits for all the processes to finish
                yield self.env.all_of(taskList)

                for r in resList:
                    r.r.release(r)
                print(f'{self.env.now} object {self.id} res 1: {res1.count}, res 2 {res2.count}')
                print(f'{self.env.now} -- object {self.id} finish loop tasks')

            print(f'{self.env.now} object {self.id} finish all tasks')


# some processes for a maintence task
def task1(env, obj_id):
    print(f'{env.now} starting task 1 for object {obj_id}')
    yield env.timeout(3)
    print(f'{env.now} finish task 1 for object {obj_id}')

def task2(env, obj_id):
    print(f'{env.now} starting task 2 for object {obj_id}')
    yield env.timeout(3)
    print(f'{env.now} finish task 2 for object {obj_id}')
    
def task3(env, obj_id):
    print(f'{env.now} starting task 3 for object {obj_id}')
    yield env.timeout(3)
    print(f'{env.now} finish task 3 for object {obj_id}')
    

env = simpy.Environment()

workLocRes = simpy.Resource(env, capacity=3)
res1 = simpy.Resource(env, capacity=4)
res2 = simpy.Resource(env, capacity=5)

# build the maintence task with nested list of resources, and nested processes
maintTask = []
maintTask.append(([res1],[task1]))
maintTask.append(([res1,res2],[task2,task3]))

# creating asset also starts it
a = Asset(env,1,maintTask)

env.run(20)
Michael
  • 1,671
  • 2
  • 4
  • 8