在optaplanner中访问链位置或多实体子链?

问题描述 投票:0回答:1

我们正在使用Optaplanner来提出遵循一系列声音音乐原理和规则(关于主要变更等)的音乐播放列表:

https://www.youtube.com/watch?v=Iqya6xqc1jY

[我们正在使用链式规划变量,以避免破坏播放列表曲目的本来坚实的”条纹”,但是我们的许多规则都涉及X个先前曲目的子链中时间推理的某些方面。举例来说,我正在尝试实施一条规则,要求该密钥至少每五首歌曲更改一次(以防止播放列表变得过于枯燥/单调)。我想出的办法行得通,但是我想知道是否有一种不太尴尬的方法。

这是我们现在所拥有的规则,从DRY和可配置性的角度来看,我觉得这很丑陋:

https://github.com/spotfire-io/spotfire-solver/blob/1c0fcda5256c337e214b33043a27fc25f615d0ef/src/main/resources/io/spotfire/solver/rules/rules.drl#L79-L88

rule "Should change key at least once every five songs"
    when
        $t0: RestPlaylistTrack(keyDistance == 0) // previousTrack is a chained variable
        $t1: RestPlaylistTrack(keyDistance == 0, previousTrack == $t0)
        $t2: RestPlaylistTrack(keyDistance == 0, previousTrack == $t1)
        $t3: RestPlaylistTrack(keyDistance == 0, previousTrack == $t3)
        $t4: RestPlaylistTrack(keyDistance == 0, previousTrack == $t3)
     then
        scoreHolder.addSoftConstraintMatch(kcontext, 0, new BigDecimal(-2));
end

[另一个示例将实施一个规则,该规则将相同类型的曲目一起分批处理(例如,连续播放4个爵士曲目,然后播放4个摇滚曲目),或者确保我们避免从上次播放同一首艺术家开始播放5个曲目那位艺术家。

在此示例中,是否有更好的方法来跟踪两条轨道之间的距离,然后对此进行约束?我们考虑过的一些潜在选择包括...

  1. 提供一种以编程方式提取X长度子链并将规则应用于该子链的方法。
  2. 创建一个阴影变量,该变量表示轨道相对于锚点的位置。然后,我们可以创建约束RestPlaylistTrack(position < $t.position, position > $t.position - 5)来应用于$ t的5条轨道内的任何轨道。
  3. 使用某种Drools聚合表达式,该表达式通过map-reducey事物累积以前的轨道,直到达到某个最大轨道数为止。

我们用前两种解决方案所面临的挑战是,链交换计划涉及对三个计划变量的更改。如果我们有一个看起来像A <- B <- C <- D的链,则B与D之间的交换涉及将D指向A,将B指向C并将C指向D的更改。在Drools或shadow变量级别,我认为这样做存在风险移动完成之前的一堆中间计算。这可能会使分数计算效率低下。对于第三个选项,我们只是不确定类似的机制如何工作。

[如果有人(尤其是@ geoffrey-de-smet)拥有有关如何完成此操作的示例,将不胜感激。如果在当前版本的Optaplanner中确实存在棘手的问题,我们认为将自然位置机制添加到链接的计划模式中将作为将来的功能非常有用。

optaplanner
1个回答
0
投票

听起来像护士排班中的连续班次限制。在手写DRL中检测“连续排不超过n个移位”并非易事。在护士排班中,我们使用insertLogicals来处理这些问题,但我建议不要使用它(这会降低性能)。我猜想方法1)(放弃增量计算)仍然比任何insertLogical方法都要快,除非您要排队数千首歌曲。

在ConstraintStreams中,方法1可能有一天看起来像这样:

constraintFactory.from(Shift.class)
    .groupBy(Shift::getEmployee,
             sort(Comparable.from(Shift::getStartDateTime, Shift::getId)) // BiConstraintStream<Employee, List<Shift>)) 
    .penalize((employee, sortedShiftList) -> ...); // One match for all bad subsequences of 1 employee

方法2)很有趣。尝试一下,让我们知道它是否对您足够好。

方法3)是我在某个时候在ConstraintStreams中要想到的。这是增量的。类似于:

constraintFactory.from(Shift.class)
    .forEachSortedSubList(Shift::getEmployee,
             Comparable.from(Shift::getStartDateTime, Shift::getId,
             (employee, sortedShiftSubList) -> ...)
    .penalize(...); // One match per bad subsequence

[如果您对如何使用方法3的API有任何建议,或者可能看起来像如何,请将它们放在我们的Google网上论坛论坛上。它可以帮助推进工作。

© www.soinside.com 2019 - 2024. All rights reserved.