首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >优化Optapy约束并可能重新定义类

优化Optapy约束并可能重新定义类
EN

Stack Overflow用户
提问于 2022-10-15 15:19:59
回答 1查看 55关注 0票数 0

我上周开始使用OptaPy,我阅读了文档并观看了一些视频。我对全局情况有大致的了解--但是我很感激任何关于优化的输入和建议。

我正在尝试生成一个类似于github上的“员工调度示例”的计划--一些不同的条件。所以我用这个项目作为基线。

问题如下:

我有两个位置,每一个都有自己的最小和最大班次。

地点1-每班最少员工3人,每班最多雇员4人

地点2 -每班最少员工2人,每班最多雇员4人

每个地点都有自己的轮班时间表。

地点1-周一至周五(08:00至16:00) -5 x 2小时一天轮班。

地点2-周一至周五(08:00至6:00)-6 x 2小时一天轮班。星期六(08:00至12:00) -2 x 2小时轮班。

现在,事情就在这里进行干预。

因此,很明显,每个班次都应该有分配给它的最低要求的员工,但最好也能填补可选的员工。

一开始,我想用员工名单来定义轮班。但当我找不到这方面的例子时,我只找到了一个每班一名雇员的例子。为此,我从数据库中为每个时隙生成4个轮班,并设置一个employee_required布尔值。

例如:位置1-星期一08:00到10:00 -(因为位置的最大员工为4人,最小员工为3人),我为这个时隙创建了4班,其中3人要求设置为true,其中1人所需设置为false。

目前,我没有使用这些信息(因为我不知道如何先将OptaPy的焦点放在所需的轮班上,然后再使用可选的轮班以获得最佳的结果/分数)。

到目前为止,我只有两个限制。

  1. A员工必须有时间来完成这种轮班。如何计算,员工有一个时隙散列数组,他/她可以这样做。

代码语言:javascript
复制
 employee.timeslots_unique_hashes = ["Monday-08:00-10:00", "Thursday-12:00-14:00"]

然后,我有一个独特的哈希换班-“星期一-08:00-10:00”。因此,如果shift哈希不在哈希的员工列表中。我是根据换班时间来处罚的。以下是我的约束

代码语言:javascript
复制
def required_timeslot(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each(Shift) \
        .filter(lambda shift: shift.unique_hash not in shift.employee.timeslots_unique_hashes) \
        .penalize("Employee missing timeslot", HardSoftScore.ONE_HARD, lambda shift: get_shift_duration_in_minutes(shift))

  1. A员工每天只能在所有地点轮班一班。

代码语言:javascript
复制
def one_shift_per_day(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each_unique_pair(Shift,
              Joiners.equal(lambda shift: shift.employee),
              Joiners.equal(lambda shift: shift.day_name)
        ) \
        .penalize("Max one shift per day", HardSoftScore.ONE_HARD)

因此,我需要建议或帮助的地方如下:

在shift上实现所需的布尔值(

  • (可选))--专注于所需的轮班,以便在可选选项上填充较少的优先级,以改进约束--雇员有一组所需的其他员工ids。所以可能有一对夫妻需要在一起。或者母女。我试着编写约束,但需要帮助完成它。

这里的逻辑是按其独特的散列对转移进行分组,遍历员工并为该转移构建一组所需的员工。如果分配给该轮班的数组员工与所需员工的数组不匹配,则根据失踪员工的数量对其进行处罚。

代码语言:javascript
复制
def required_employees(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each(Shift) \
        .groupBy(lambda shift: shift.unique_hash, to_list(lambda shift: shift.employee)) \
        .filter(lambda shift, employees: shift_does_not_contains_all_required_employees(shift)) \
        .penalize("Shift does not contain all the required employees", HardSoftScore.ONE_HARD, lambda shift: get_missing_required_employees_count(shift))

  • (必需但不确定是否可行)约束--确保所有员工至少有一次轮班。显然,有些员工有可能每天轮班一次,但我想优先考虑的是,每个人都至少有一次轮班,然后再给已经换班的人换班。

如有任何帮助/建议或建议,将不胜感激。谢谢

EN

回答 1

Stack Overflow用户

发布于 2022-10-19 03:37:54

  • 您对“员工缺失时隙”的限制看起来是正确的。这与员工计划的快速启动(https://github.com/optapy/optapy-quickstarts/tree/stable/employee-scheduling)处理方法不同。在快速启动时,我们使用可用性对象,并与该员工轮班,如果可用性对象不可用,则以硬惩罚;如果不需要,则以软惩罚;如果需要,则以软惩罚:

代码语言:javascript
复制
def unavailable_employee(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each(Shift) \
        .join(Availability,
              Joiners.equal(lambda shift: shift.employee,
                            lambda availability: availability.employee),
              Joiners.equal(lambda shift: shift.start.date(),
                            lambda availability: availability.date)
              ) \
        .filter(lambda shift, availability: availability.availability_type == AvailabilityType.UNAVAILABLE) \
        .penalize('Unavailable employee', HardSoftScore.ONE_HARD,
                  lambda shift, availability: get_shift_duration_in_minutes(shift))


def desired_day_for_employee(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each(Shift) \
        .join(Availability,
              Joiners.equal(lambda shift: shift.employee,
                            lambda availability: availability.employee),
              Joiners.equal(lambda shift: shift.start.date(),
                            lambda availability: availability.date)
              ) \
        .filter(lambda shift, availability: availability.availability_type == AvailabilityType.DESIRED) \
        .reward('Desired day for employee', HardSoftScore.ONE_SOFT,
                lambda shift, availability: get_shift_duration_in_minutes(shift))


def undesired_day_for_employee(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each(Shift) \
        .join(Availability,
              Joiners.equal(lambda shift: shift.employee,
                            lambda availability: availability.employee),
              Joiners.equal(lambda shift: shift.start.date(),
                            lambda availability: availability.date)
              ) \
        .filter(lambda shift, availability: availability.availability_type == AvailabilityType.UNDESIRED) \
        .penalize('Undesired day for employee', HardSoftScore.ONE_SOFT,
                  lambda shift, availability: get_shift_duration_in_minutes(shift))

该模型将可用性与员工分离开来,使它们更容易地存储在数据库中。但是,如果用例更方便的话,您的当前方法没有什么问题。

  • 您对“员工每天只能在所有地点轮班一次”的限制。是正确的,也是员工计划快速启动的方式。

  • “在shift上实现所需的布尔值--将注意力集中在所需的轮班上,在可选的选项上填充较少的优先级,以提高分数。这可以通过使员工计划变量为空并将得分类型更改为HardMediumSoftScore来完成。此外,如果需要,Shift类将有一个bool字段,即True,否则为False。您的Shift类将如下所示:

代码语言:javascript
复制
@optapy.planning_entity
class Shift:
    employee: Employee | None
    is_required: bool
    
    # ...
    # other fields, __init__, etc.
    # ...
    
    @optapy.planning_variable(Employee, value_range_provider_refs=['employee_range'], nullable=True)
    def get_employee(self):
        return self.employee

    def set_employee(self, employee):
        self.employee = employee

对于没有员工的Shift,您有两个约束:

代码语言:javascript
复制
def required_shift_missing_employee(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each_including_null_vars(Shift) \
        .filter(lambda shift: shift.is_required and shift.employee is None) \
        .penalize('Required shift missing employee', HardMediumSoftScore.ONE_HARD)

def required_shift_missing_employee(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each_including_null_vars(Shift) \
        .filter(lambda shift: (not shift.is_required) and shift.employee is None) \
        .penalize('Optional shift missing employee', HardMediumSoftScore.ONE_MEDIUM)

这些限制使所需的班次、失踪的员工受到困难分数的处罚,而可选的班次、失踪的员工则受到中等分数的处罚。由于1 hard >任何介质,optapy将重点首先填充所需的移位,然后它可以做的许多可选的移位。

  • 您对"Shift不包含所有所需员工“的约束看起来是正确的。我可能会这样做:

代码语言:javascript
复制
def required_employees(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each(Shift) \
        .join(Shift,
              Joiners.equal(lambda shift: shift.unique_hash),
              Joiners.filtering(lambda shift_1, shift_2: shift_2.employee in shift_1.required_coworker_list) \
        .group_by(lambda shift_1, shift_2: shift_1,
                  ConstraintCollectors.count_distinct(lambda shift_1, shift_2: shift_2.employee)) \
        .filter(lambda shift, number_of_required_coworkers_sharing_shift: number_of_required_coworkers_sharing_shift < len(shift.employee.required_coworker_list)) \
        .penalize("Shift does not contain all the required employees",
                  HardSoftScore.ONE_HARD,
                  lambda shift, number_of_required_coworkers_sharing_shift: len(shift.employee.required_coworker_list) - number_of_required_coworkers_sharing_shift)

这一限制所做的是:

每个班次( (shift_1)

  • Join shift_1.required_coworker_list

  • Count,shift_1 )与班次( shift,shift_2 ),其中shift_1和shift_2共享相同的时隙,shift_2.employee在shift_2.employee中,对于每组shift_1

  • Filter,只包括不同员工数小于len(shift_1.employee.required_coworker_list)的匹配项。由于them.

  • Penalize的员工只能是shift_1.employee.required_coworker_list的成员,所以只有当不是所有员工的所需同事都与len(shift_1.employee.required_coworker_list) - number_of_coworkers_sharing_shift共享轮班的情况下,才能匹配该条件,后者是指所有员工至少得到一次轮班的丢失employees.

代码语言:javascript
复制
def all_employees_have_at_least_one_shift(constraint_factory: ConstraintFactory):
    return constraint_factory \
        .for_each(Employee) \
        .if_not_exists(Shift, Joiners.equal(lambda employee: employee, lambda shift: shift.employee)) \
        .penalize("Employee has no shift", HardSoftScore.ONE_HARD)

有关如何真正做到公平,请参见此堆栈溢出答案:https://stackoverflow.com/a/74018711/9698517;(注意:当前OptaPlanner (https://issues.redhat.com/browse/PLANNER-2834)中存在一个错误,可能会导致使用该代码引发异常)。

关于改变分数类型的注意事项:您需要在任何地方都这样做;您不能同时使用HardMediumSoftScoreHardSoftScore。因此,您需要修改所有约束的penalize/reward以使用HardMediumSoftScore,还需要更改在@planning_solution类中传递给@planning_score的类型。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74080688

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档