简单的资源可用性计划

时间:2015-10-26 13:03:53

标签: python resources schedule simpy

我在Python中使用SimPy来创建离散事件模拟,该模拟需要根据用户在csv文件中输入的时间表来提供资源。目的是表示在一天中的不同时间可用的相同资源(例如,工作人员)的不同数量。据我所知,这并不是基于SimPy的可用资源 - 就像资源优先级一样。

我已经设法让这个工作,并包括下面的代码,以显示如何。但是我想询问社区是否有更好的方法在SimPy中实现此功能?

下面的代码通过在每天开始时请求资源来确定它们不应该可用的时间 - 具有更高的优先级以确保它们获得资源。然后在适当的时间释放资源以供其他事件/进程使用。正如我所说它有效,但似乎很浪费,许多虚拟过程正在努力确保资源的正确真实可用性。任何可能导致改进的评论都会受到欢迎。

所以csv看起来像:

Number  time
0        23
50       22
100      17
50       10
20       8
5        6

其中Number表示在定义的时间变为可用的人员数。例如:6-8人将有5名员工,8-10人将有20名员工,10-17岁将有50人,依此类推,直到当天结束。

代码:

import csv
import simpy

# empty list ready to hold the input data in the csv
input_list = []

# a dummy process that "uses" staff until the end of the current day
def take_res():
    req = staff.request(priority=-100)
    yield req  # Request a staff resource at set priority
    yield test_env.timeout(24 - test_env.now)

# A dummy process that "uses" staff for the time those staff should not 
# be available for the real processes     
def request_res(delay, avail_time):
    req = staff.request(priority=-100)
    yield req  # Request a staff resource at set priority
    yield test_env.timeout(delay)
    yield staff.release(req)
    # pass time it is avail for
    yield test_env.timeout(avail_time)
    test_env.process(take_res())

# used to print current levels of resource usage
def print_usage():
    print('At time %0.2f %d res are in use' % (test_env.now, staff.count))
    yield test_env.timeout(0.5)
    test_env.process(print_usage())

# used to open the csv and read the data into a list
with open('staff_schedule.csv', mode="r") as infile:
    reader = csv.reader(infile)
    next(reader, None)  # ignore header
    for row in reader:
        input_list.append(row[:2])

    # calculates the time the current number of resources will be 
    # available for and adds to the list 

    i = 0
    for row in the_list:
        if i == 0:
            row.append(24 - int(input_list[i][1]))
        else:
            row.append(int(input_list[i-1][1]) - int(input_list[i][1]))

        i += 1

    # converts list to tuple of tuples to prevent any accidental 
    # edits from this point in
    staff_tuple = tuple(tuple(row) for row in input_list)
    print(staff_tuple)

# define environment and creates resources   
test_env = simpy.Environment()
staff = simpy.PriorityResource(test_env, capacity=sum(int(l[0]) for l in staff_tuple))

# for each row in the tuple run dummy processes to hold resources 
# according to schedule in the csv
for item in the_tuple:
    print(item[0])
    for i in range(int(item[0])):
        test_env.process(request_res(int(item[1]), int(item[2])))

# run event to print usage over time
test_env.process(print_usage())

# run for 25 hours - so 1 day
test_env.run(until=25)

3 个答案:

答案 0 :(得分:1)

我尝试了其他的东西,我重载了Resource类,只添加了一个方法,虽然我不完全理解源代码,但它似乎正常工作。您可以告诉资源在模拟中的某处更改容量。

from simpy.resources.resource import Resource, Request, Release
from simpy.core import BoundClass
from simpy.resources.base import BaseResource

class VariableResource(BaseResource):
    def __init__(self, env, capacity):
        super(VariableResource, self).__init__(env, capacity)
        self.users = []
        self.queue = self.put_queue

    @property
    def count(self):
        return len(self.users)

    request = BoundClass(Request)
    release = BoundClass(Release)

    def _do_put(self, event):
        if len(self.users) < self.capacity:
            self.users.append(event)
            event.usage_since = self._env.now
            event.succeed()

    def _do_get(self, event):
        try:
            self.users.remove(event.request)
        except ValueError:
            pass
        event.succeed()

    def _change_capacity(self, capacity):
        self._capacity = capacity

我认为这应该有效,但我对这些触发器的工作方式并不是100%有信心。

答案 1 :(得分:0)

SimPy相关

也许您可以使用PreemptiveResource(请参阅this example)。有了这个,你每个资源只需要一个阻塞进程,因为它可以“踢”不太重要的进程。

与Python相关

  • 记录您的代码。 take_res()request_res()的目的是什么? (为什么两个函数都使用priority=-100,无论如何?)
  • 使用更好的名字。 the_listthe_tuple不是很有帮助。
  • 您可以the_list.append(row[0], row[1])代替the_list.append(row[:2])
  • 为什么要将列表列表转换为元组元组?据我所知,有益处。但它增加了额外的代码,因此,编程错误可能会产生额外的混乱和可能性。
  • 您应该尽快离开with open(file)区块(在您的情况下,前四行之后)。没有必要让文件保持打开的时间超过必要的时间,当你完成所有行的迭代时,就不再需要它了。

答案 2 :(得分:0)

这就是我为我的应用程序解决它的方法。它并不完美,但鉴于我在Python和SimPy方面的基本技能水平,我能做的最好。

结果是在所需的时间内有正确数量的顾问。

首先,我定义一个商店,并将容量设置为等于模拟中存在的顾问实例总数。

self.adviser_store = simpy.FilterStore(self.env,
capacity=self.total_ad_instances)

所需的Adviser类的实例是在初始化步骤中创建的,为简洁起见,我没有将其包括在内。我实际上使用JSON文件来自定义各个顾问实例,然后将它们放在列表中。

下面的类定义中的run参数实际上是另一个类,它包含与当前模拟运行相关的所有信息 - 例如,它包含模拟的开始和结束日期。因此,self.start_date定义了顾问开始工作的日期。 self.run.start_date是模拟的开始日期。

class Adviser(object):

    def __init__(self, run, id_num, start_time, end_time, start_date,  end_date):

    self.env = run.env
    self.run = run
    self.id_num = id_num        
    self.start_time = start_time
    self.end_time = end_time
    self.start_date = datetime.datetime.strptime(start_date, '%Y, %m, %d')
    self.end_date = datetime.datetime.strptime(end_date, '%Y, %m, %d')
    self.ad_type = ad_type

    self.avail = False
    self.run.env.process(self.set_availability())  

因此,您可以看到创建顾问类也会启动设置可用性的过程。在下面的示例中,我简化了它,以便在给定日期范围内每天设置相同的可用性。您当然可以根据日期/日期等设置不同的可用性。

def set_availability(self):

    start_delay = self.start_time + (self.start_date - self.run.start_date).total_seconds()/3600 # this returns the time in hours until the resource becomes available and is applied below.
    end_delay = self.end_time + (self.start_date - self.run.start_date).total_seconds()/3600
    repeat = (self.end_date - self.start_date).days + 1  # defines how man days to repeat it for

    for i in range(repeat):

        start_delayed(self.run.env, self.add_to_store(), start_delay)
        start_delayed(self.run.env, self.remove_from_store(), end_delay)
        start_delay += 24
        end_delay += 24

    yield self.run.env.timeout(0)


def add_to_store(self):

    self.run.ad_avail.remove(self)  # take adviser from a list
    self.run.adviser_store.put(self)  # and put it in the store
    yield self.run.env.timeout(0)

def remove_from_store(self):        

    current_ad = yield self.run.adviser_store.get(lambda item: item.id_num == self.id_num)  # get itself from the store 
    self.run.ad_avail.append(current_ad) # and put it back in the list
    yield self.run.env.timeout(0)

因此,客户基本上只能从商店请求顾问,而顾问只会在特定时间进入商店。其余的时间它们都在当前运行模拟的列表中。

我认为这里仍有一个陷阱。顾问对象可能在其变得不可用时使用。如果这种情况发生,我还没有注意到,如果发生这种影响,我还没有注意到。