Search code examples
optaplanner

OptaPlanner, excluded solution shows up


Please help me to understand DRL with this simple task assignment project. 2 workers id=1 and 2, 3 tasks id=1,2,3, each task has a duration in second. The duration for task 1 and 3 is a little bit more than task 2. At beginning I use following rule (only one rule) trying to balance total time for each worker, So I expect one worker takes task 1 and 3 while the other takes task 2.

rule "fairness"
    when
        $worker: Worker()
        accumulate(
            Task(worker == $worker, $d: durationInSec);
            $s: sum($d*$d)
        )       
    then
        scoreHolder.addSoftConstraintMatch(kcontext, -$s.longValue());
end

the result is all tasks are ALWAYS assigned to worker 1. I wanted to debug the issue and replaced the rule above with following 2 rules:

rule "A" 
    when
        $worker: Worker(id == 1) // for worker 1
    then
        scoreHolder.addHardConstraintMatch(kcontext, -1); // hard score -1
end

rule "B"
    when
        $worker: Worker(id == 2) // for worker 2
    then
        scoreHolder.addSoftConstraintMatch(kcontext, 1); // soft score +1
end

I thought worker 1 would be excluded because of rule "A", but the result was still, worker 1 got all 3 tasks, with this output:

o.o.c.i.l.DefaultLocalSearchPhase : Local Search phase (1) ended: time spent (30001), best score (-3hard/3soft), score calculation speed (7309/sec), step total (12948).
o.o.core.impl.solver.DefaultSolver: Solving ended: time spent (30002), best score (-3hard/3soft), score calculation speed (7258/sec), phase total (2), environment mode (NON_REPRODUCIBLE)

I expect worker 2 takes all 3 tasks with score 0hard/3soft. Following is part of my Solution class, where is the problem?

@PlanningSolution
@Entity
public class Solution {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    private SolverStatus solverStatus;
    
    @PlanningScore
    private HardSoftLongScore score;
    
    @ProblemFactCollectionProperty
    @OneToMany
    @ValueRangeProvider(id = "workerRange")
    private List<Worker> workerList;

    @PlanningEntityCollectionProperty
    @OneToMany
    private List<Task> taskList;
    ...
    
    
@Entity
@PlanningEntity
public class Task implements Comparable<Task> {
    
    @PlanningId
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade=CascadeType.ALL)
    @PlanningVariable(valueRangeProviderRefs = "workerRange")
    private Worker worker;

When putting a println in RHS I do see both worker 1 and 2 are printed out:

System.out.println("worker=" + $worker.toString() + ", task=" + $tb.toString() + "; " +
    scoreHolder.getHardScore() + "hard/" + scoreHolder.getHardScore() + "soft");

...
worker=1, task=2; -3hard/-3soft
worker=1, task=1; -3hard/-3soft
worker=2, task=3; -3hard/-3soft
worker=2, task=2; -3hard/-3soft
worker=2, task=1; -3hard/-3soft
worker=1, task=3; -3hard/-3soft
worker=2, task=1; -3hard/-3soft
worker=2, task=1; -3hard/-3soft
...

Solution

  • The rules "A" and "B" penalize/reward purely for the existence of these two workers, they don't say anything about any tasks assigned to them.

    At the first glance, I don't see anything wrong with the first constraint ("fairness"). You can always add System.out.println(...) in the then section of the rule to debug it.

    Alternatively, if you prefer Java to DRL, maybe the ConstraintStreams API could be interesting for you.