You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置Timefold使规划实体保留未分配值

调度问题求解器优化:无合适技术人员时自动设为未分配

问题背景

我正在处理一个调度问题,需要将技术人员分配给待服务设备。当没有具备所需技能的合适技术人员时,设备应保持未分配状态(即device.pesel设为None)。

领域模型代码

@dataclass
class Technician:
    id: Annotated[int, PlanningId]
    pesel: str
    name: str
    rbh_per_week: int
    rbh_per_year: float
    rbh_week_plan: float
    selected_rbh: float
    free_rbh: float
    iums: Set[str]

    def has_ium(self, ium: str) -> bool:
        return ium in self.iums

    def __str__(self) -> str:
        return (f"Technician(id={self.id}, name={self.name}, pesel={self.pesel}, "
                f"rbh_per_week={self.rbh_per_week}, rbh_per_year={self.rbh_per_year}, "
                f"rbh_week_plan={self.rbh_week_plan}, selected_rbh={self.selected_rbh}, "
                f"free_rbh={self.free_rbh}, iums={self.iums})")

@planning_entity
@dataclass
class Device:
    index: Annotated[int, PlanningId]
    ind_rek: str
    ium: str
    nazwa: str
    typ: str
    nr_fab: str
    norma_rbh: float
    data_dostawy: str
    uzytkownik: str
    pesel: Annotated[Technician | None, PlanningVariable(value_range_provider_refs=['technicianRange'], nullable=True)] = field(default=None)

    def __str__(self) -> str:
        technician_str = str(self.pesel) if self.pesel else "None"
        return (f"Device(index={self.index}, ind_rek={self.ind_rek}, ium={self.ium}, "
                f"nazwa={self.nazwa}, typ={self.typ}, nr_fab={self.nr_fab}, "
                f"norma_rbh={self.norma_rbh}, data_dostawy={self.data_dostawy}, "
                f"uzytkownik={self.uzytkownik}, assigned_technician={technician_str})")

@planning_solution
@dataclass
class DeviceSchedule:
    id: str
    technician_list: Annotated[List[Technician], ProblemFactCollectionProperty, ValueRangeProvider]
    device_list: Annotated[List[Device], PlanningEntityCollectionProperty]
    score: Annotated[HardSoftScore, PlanningScore] = field(default=None)

    def __str__(self):
        return (
            f"DeviceSchedule("
            f"id={self.id},\n"
            f"technician_list={self.technician_list},\n"
            f"device_list={self.device_list},\n"
            f"score={self.score}"
            f")"
        )

当前约束代码

@constraint_provider
def define_constraints(constraint_factory: ConstraintFactory):
    return [
        # HARD constraint to avoid assigning a technician without the required skill
        technician_skill_conflict(constraint_factory),
    ]

def technician_skill_conflict(constraint_factory: ConstraintFactory):
    return (constraint_factory
            .for_each(Device)
            .filter(lambda device: device.pesel is not None and not device.pesel.has_ium(device.ium))
            .penalize(HardSoftScore.ONE_HARD)
            .as_constraint("Technician skill conflict"))

现存问题

尽管已在PlanningVariable中设置nullable=True,且定义了硬约束惩罚技能不匹配的分配,但求解器仍倾向于为每个设备分配技术人员,即便不分配是更优选择。

解决方案

要让求解器在无合适人员时自动将device.pesel设为None,需要从约束和求解器配置两方面调整:

1. 清理强制分配的隐含约束

检查是否存在其他硬约束强制要求设备必须分配技术人员,若有则直接移除,或改为软约束。当前的硬约束仅惩罚技能不匹配的分配,本身不强制分配,这部分逻辑没问题。

2. 添加软约束引导合理分配逻辑

通过软约束明确优先级:优先分配具备对应技能的人员,其次允许未分配状态。修改后的约束代码如下:

@constraint_provider
def define_constraints(constraint_factory: ConstraintFactory):
    return [
        # 硬约束:禁止分配无对应技能的技术人员
        technician_skill_conflict(constraint_factory),
        # 软约束:奖励分配了合适技能的技术人员
        reward_skill_matched_assignment(constraint_factory),
        # 可选软约束:轻微惩罚未分配设备(仅当需要尽可能分配时启用)
        penalize_unassigned_device(constraint_factory),
    ]

def technician_skill_conflict(constraint_factory: ConstraintFactory):
    return (constraint_factory
            .for_each(Device)
            .filter(lambda device: device.pesel is not None and not device.pesel.has_ium(device.ium))
            .penalize(HardSoftScore.ONE_HARD)
            .as_constraint("Technician skill conflict"))

def reward_skill_matched_assignment(constraint_factory: ConstraintFactory):
    return (constraint_factory
            .for_each(Device)
            .filter(lambda device: device.pesel is not None and device.pesel.has_ium(device.ium))
            .reward(HardSoftScore.ONE_SOFT)
            .as_constraint("Reward skill-matched assignment"))

def penalize_unassigned_device(constraint_factory: ConstraintFactory):
    return (constraint_factory
            .for_each(Device)
            .filter(lambda device: device.pesel is None)
            .penalize(HardSoftScore.ONE_SOFT)
            .as_constraint("Penalize unassigned device"))

3. 优化求解器搜索策略

若求解器仍强行分配,可调整构造启发式策略,例如启用FIRST_FIT_DECREASING或CHEAPEST_INSERTION,让求解器优先为设备匹配合适人员,无匹配时自动留空。

4. 验证技能匹配逻辑

确认Technician.has_ium()方法逻辑正确,检查iums集合是否正确加载了技术人员的技能标签,避免因数据错误导致求解器误判无合适人员。

核心思路总结

  1. 用硬约束完全禁止无效分配(技能不匹配的技术人员)
  2. 用软约束引导求解器优先选择有效分配,同时允许未分配状态
  3. 移除任何强制设备必须分配的硬约束

内容的提问来源于stack exchange,提问作者Peter Pirog

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:16:01