我上周开始使用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的焦点放在所需的轮班上,然后再使用可选的轮班以获得最佳的结果/分数)。
到目前为止,我只有两个限制。
employee.timeslots_unique_hashes = ["Monday-08:00-10:00", "Thursday-12:00-14:00"]然后,我有一个独特的哈希换班-“星期一-08:00-10:00”。因此,如果shift哈希不在哈希的员工列表中。我是根据换班时间来处罚的。以下是我的约束
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))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上实现所需的布尔值(
这里的逻辑是按其独特的散列对转移进行分组,遍历员工并为该转移构建一组所需的员工。如果分配给该轮班的数组员工与所需员工的数组不匹配,则根据失踪员工的数量对其进行处罚。
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))如有任何帮助/建议或建议,将不胜感激。谢谢
发布于 2022-10-19 03:37:54
。
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))该模型将可用性与员工分离开来,使它们更容易地存储在数据库中。但是,如果用例更方便的话,您的当前方法没有什么问题。
HardMediumSoftScore来完成。此外,如果需要,Shift类将有一个bool字段,即True,否则为False。您的Shift类将如下所示:@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,您有两个约束:
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将重点首先填充所需的移位,然后它可以做的许多可选的移位。
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)
len(shift_1.employee.required_coworker_list)的匹配项。由于them.
shift_1.employee.required_coworker_list的成员,所以只有当不是所有员工的所需同事都与len(shift_1.employee.required_coworker_list) - number_of_coworkers_sharing_shift共享轮班的情况下,才能匹配该条件,后者是指所有员工至少得到一次轮班的丢失employees.
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)中存在一个错误,可能会导致使用该代码引发异常)。
关于改变分数类型的注意事项:您需要在任何地方都这样做;您不能同时使用HardMediumSoftScore和HardSoftScore。因此,您需要修改所有约束的penalize/reward以使用HardMediumSoftScore,还需要更改在@planning_solution类中传递给@planning_score的类型。
https://stackoverflow.com/questions/74080688
复制相似问题