How can I model this job shop scheduler in simpy?

55 Views Asked by At

I have to simulate a job shop. The steps became available as previous steps are being finished. In the example of the image, you can start steps 1 and 2 right away, but step 3 will only be available after step 1 finished, and so on. The model is graphically represented here: Process flow chart

There are a number of operators that can do the job, but I only want to give them the task once the previous task is finished (I can't do the full schedule all at once).

I am having a hard time trying to inject the next operation in the flow, as this depends on the end of other operations and the availability of resources at that point.

I tried requesting operators for the next step, but it might be that the next step available is not the one I expected, which caused a malfunction.

I tried requesting all operators, using the one that became available and cancel the other requests, but this, in the way I was doing it, causes the simulation to finish early (apparently it expects no more events)

I tried creating a custom event that the operator will succeed when they finished their current task, with a structure like the following:

operation_finished = env.event()

while True:
    """ find what operations and operators are available"""
    available_operations = check_available_operations()
    operators_available = find_any_operator_available()

    """ let the scheduler decide if it wants to schedule a next operation. For now,
 it always does, so I expect these to be null only when the sequence is finished"""
    next_operation = schedule_operation(available_operations, operators_available)
    next_operator = schedule_operator(available_operations, operators_available)

    """ launch the operation with the operator that we know is available.
     This operator will succeed operation_finished once the operation is finished"""
    env.process(operator_process(env, operation))

    """ If the scheduler did not make a decision or no machine was available,
 wait for the next operator to finish"""
    if len(next_operator) == 0:
        yield operation_finished
        print('Whatever is here will never print')

A structure like this only schedules the first and stops when the first it is finished

1

There are 1 best solutions below

0
Michael On

In simpy you usually do not have a "master" schedular controlling everything. Instead you have a bunch of processes running in parallel, where each task waits for a entity to show up, competes for a resource, take some time for processing, release the resource, and send the entity to its next task. Flow is controlled by competition for limited resources, or limited queue sizes.

Here is a example which has your two flows paths where each task needs a resource from the same resource pool. Requests for resources tend to be first in first out. I'm not sure I fully understood your scenario, but this should still be a good start.

"""
A quick simulation of different task compeating for the same resoures

enities can flow on one of two paths:
task 1 => task 3 => task 4 => task 6
or
task 2 => task 5 => task 6

all tasks need one resource from a common resource pool

Programmer: Michael R. Gibbs
"""

import simpy
import random

class Entity():
    """
    A quick entity class with unque Ids
    """

    # class var with next id
    next_id = 1

    def __init__(self):
        """
        get next id of instance,
        increment next id
        """
        self.id = self.__class__.next_id
        self.__class__.next_id += 1


class Task():
    """
    Class that paramertises a task and provides a generic do_task process
    prameters:
    env:    simpy envirionment
    task_name:  name used in logging
    resource_pool: resource pool to request a resource from
    time_dist:  A no parameter fuction that returns a time interval
    next_task:  A fuction that is the entity's next task
    """

    def __init__(self, env, task_name, resource_pool, time_dist, next_task=None):

        self.env = env
        self.task_name = task_name
        self.resouce_pool = resource_pool
        self.time_dist = time_dist
        self.next_task = next_task

    def do_task(self, entity):
        """
        The task to do with the entity

        in this class,
        the entity requests a resource,
        holds the resouces for a time_dist of time,
        releases the resouce,
        and sends the entity to its next task function
        """

        print(f'{self.env.now:.2f} entity {entity.id} has arrived in task {self.task_name}')
        with self.resouce_pool.request() as req:
            yield req
            print(f'{self.env.now:.2f} entity {entity.id} has got resource in task {self.task_name}')
            
            yield self.env.timeout(self.time_dist())
            print(f'{self.env.now:.2f} entity {entity.id} has finished task {self.task_name}')

        if self.next_task != None:
            self.env.process(self.next_task(entity))

class End_Task():
    """
    A special 'end' task

    It only logs the end of processing for the entity.
    It does not send the entity to a next task
    """
    def __init__(self, env):
        self.env = env

    def do_task(self, entity):
        yield env.timeout(0)
        print(f'{self.env.now:.2f} entity {entity.id} has finished processing')

def gen_task_1_ents(env, task):
    """
    Generates entites and sends them to a first task
    """
    
    while True:
        yield env.timeout(random.triangular(2,8,4))
        ent = Entity()

        env.process(task.do_task(ent))

def gen_task_2_ents(env, task):
    """
    Generates entites and sends them to a first task
    """

    while True:
        yield env.timeout(random.triangular(2,8,4))
        ent = Entity()

        env.process(task.do_task(ent))

#####
# boot up
env = simpy.Environment()
resource_pool = simpy.Resource(env,capacity=2)

# create and link tasks
end_task = End_Task(env)
task_6 = Task(env, "Task 6", resource_pool, lambda : random.triangular(2, 6, 4), end_task.do_task)
task_5 = Task(env, "Task 5", resource_pool, lambda : random.triangular(2, 6, 4), task_6.do_task)
task_4 = Task(env, "Task 4", resource_pool, lambda : random.triangular(2, 6, 4), task_6.do_task)
task_3 = Task(env, "Task 3", resource_pool, lambda : random.triangular(2, 6, 4), task_4.do_task)
task_2 = Task(env, "Task 2", resource_pool, lambda : random.triangular(2, 6, 4), task_5.do_task)
task_1 = Task(env, "Task 1", resource_pool, lambda : random.triangular(2, 6, 4), task_3.do_task)

# start sim entites
env.process(gen_task_1_ents(env, task_1))
env.process(gen_task_2_ents(env, task_2))

env.run(100)

print("done")