MrWoffle
MrWoffle

Reputation: 149

OptaPlaner simple example cant find feasible solution

to get familiar with optaplanner i created a simple test project. I only have one Solution and one Entity class. The Entity has only one value between 0 and 9. There should only be odd numbers and the sum of all should be less then 10 (this are just some random constraints i came up with).

As Score i use a simple HardSoftScore. Here is the code:

public class TestScoreCalculator implements EasyScoreCalculator<TestSolution>{

    @Override
    public HardSoftScore calculateScore(TestSolution sol) {
        int hardScore = 0;
        int softScore = 0;
        int valueSum = 0;

        for (TestEntity entity : sol.getTestEntities()) {
            valueSum += entity.getValue() == null? 0 : entity.getValue();
        }

        // hard Score
        for (TestEntity entity : sol.getTestEntities()) {
            if(entity.getValue() == null || entity.getValue() % 2 == 0)
                hardScore -= 1; // constraint: only odd numbers
        }
        if(valueSum > 10)
            hardScore -= 2; // constraint: sum should be less than 11

        // soft Score
        softScore = valueSum; // maximize

        return HardSoftScore.valueOf(hardScore, softScore);
    }   
}

and this is my config file:

<?xml version="1.0" encoding="UTF-8"?>
<solver>
  <!-- Domain model configuration -->
  <scanAnnotatedClasses/>

  <!-- Score configuration -->
  <scoreDirectorFactory>
    <easyScoreCalculatorClass>score.TestScoreCalculator</easyScoreCalculatorClass>
  </scoreDirectorFactory>

  <!-- Optimization algorithms configuration -->
  <termination>
    <secondsSpentLimit>30</secondsSpentLimit>
  </termination>
</solver>

for some reason OptaPlanner cant find a feasible solution. It terminates with LS step (161217), time spent (29910), score (-2hard/10soft), best score (-2hard/10soft)... and the solution 9 1 0 0. So the hardScore is -2 because the two 0 are not odd. A possible solution would be 7 1 1 1 for example. Why is this ? This should be a really easy example ...

(when i set the Start values to 7 1 1 1 it terminates with this solution and a score of (0hard/10soft) how it should be)


Edit:

The Entity class

@PlanningEntity
public class TestEntity {
    private Integer value;

    @PlanningVariable(valueRangeProviderRefs = {"TestEntityValueRange"})
    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

    @ValueRangeProvider(id = "TestEntityValueRange")
    public CountableValueRange<Integer> getStartPeriodRange() {
        return ValueRangeFactory.createIntValueRange(0, 10);
    }

}

The Solution class

@PlanningSolution
public class TestSolution {
    private List<TestEntity> TestEntities;
    private HardSoftScore score;

    @PlanningEntityCollectionProperty
    public List<TestEntity> getTestEntities() {
        return TestEntities;
    }

    public void setTestEntities(List<TestEntity> testEntities) {
        TestEntities = testEntities;
    }

    @PlanningScore
    public HardSoftScore getScore() {
        return score;
    }

    public void setScore(HardSoftScore score) {
        this.score = score;
    }

    @Override
    public String toString() {
        String str = "";
        for (TestEntity testEntity : TestEntities) 
            str += testEntity.getValue()+" ";
        return str;
    }    
}

The Main Program class

public class Main {

    public static final String SOLVER_CONFIG = "score/TestConfig.xml";

    public static int printCount = 0;

    public static void main(String[] args) {
        init();
    }

    private static void init() {
        SolverFactory<TestSolution> solverFactory = SolverFactory.createFromXmlResource(SOLVER_CONFIG);
        Solver<TestSolution> solver = solverFactory.buildSolver();

        TestSolution model = new TestSolution();
        List<TestEntity> list = new ArrayList<TestEntity>();
//      list.add(new TestEntity(){{setValue(7);}});
//      list.add(new TestEntity(){{setValue(1);}});
//      list.add(new TestEntity(){{setValue(1);}});
//      list.add(new TestEntity(){{setValue(1);}});
        for (int i = 0; i < 4; i++) {
            list.add(new TestEntity());
        }
        model.setTestEntities(list);


        // Solve the problem
        TestSolution solution = solver.solve(model);

        // Display the result
        System.out.println(solution);
    }

}

Upvotes: 0

Views: 211

Answers (1)

Geoffrey De Smet
Geoffrey De Smet

Reputation: 27312

It gets stuck in a local optima because there is no move that takes 1 from entity and gives it to another entity. With a custom move you can add that. These kind of moves only apply to numeric value ranges (which are rare, usually value ranges are a list of employees etc), but they should probably exist out of the box (feel free to create a jira for them).

Anyway, another way to get the good solution is to add <exhaustiveSearch/>, that bypassing local search and therefore the local optima. But that doesn't scale well.

Upvotes: 1

Related Questions