'optaplanner can't get the best solution, and different input orders produce different solutions

I'm trying to make a demo using optaplanner: there are some schemes, each scheme has attribute of gain and cost, and a scheme may conflict with one or more other schemes. The question is to find out a group of schemes which match following constraints: hard constraint: selected schemea may not conflict with each other in this group soft constraint: make the difference between total gain and total cost as high as possible

I built following code and try to resolve the question:

@PlanningEntity
@Data
@NoArgsConstructor
public class Scheme {
    @PlanningId
    private String id;
    private int gain;
    private int cost;
    @PlanningVariable(valueRangeProviderRefs = {"validRange"})
    // when valid is ture means this scheme will be selected into the solution group
    private Boolean valid;
    private Set<String> conflicts = new HashSet<>();

    public void addConflict(String id) {
        conflicts.add(id);
    }

    public Scheme(String id, int gain, int cost, String[] conflicts) {
        this.id = id;
        this.gain = gain;
        this.cost = cost;
        for (String s : conflicts) {
            addConflict(s);
        }
    }
}

@PlanningSolution
public class SchemeSolution {
    private HardSoftScore score;
    private List<Scheme> schemeList;

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "validRange")
    public List<Boolean> getValidRange() {
        return Arrays.asList(Boolean.FALSE, Boolean.TRUE);
    }

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

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

    @PlanningEntityCollectionProperty
    public List<Scheme> getSchemeList() {
        return schemeList;
    }

    public void setSchemeList(List<Scheme> schemeList) {
        this.schemeList = schemeList;
    }
}

And the constraint rule as below:

rule "conflictCheck"
when
    Boolean(this==true) from accumulate (
        $schs: List() from collect (Scheme(valid==true)),
        init(boolean cfl = false;Set cfSet = new HashSet();List ids = new ArrayList()),
        action(
            for(int i = 0; i < $schs.size(); ++i) {
                Scheme sch = (Scheme)$schs.get(i);
                cfSet.addAll(sch.getConflicts());
                ids.add(sch.getId());
            }
            for( int i = 0; i < ids.size(); ++i) {
                String id = (String)ids.get(i);
                if(cfSet.contains(id)) {
                    cfl = true;
                    return true;
                }
            }
        ),
        result(cfl)
    )
then
    scoreHolder.addHardConstraintMatch(kcontext, -10000);
end

rule "bestGain"
when
    $gc : Number() from
    accumulate(
        Scheme(valid==true, $gain : gain, $cost: cost),
        sum($gain - $cost)
    )
then
    scoreHolder.addSoftConstraintMatch(kcontext, $gc.intValue());
end

Then I constructed three schemes as input of the test. Oddly, I found that optaplanner can't get the best solution, and different input orders produce different solutions. When I set input as following:

private static List<Scheme>  getSchemes() {
    List<Scheme> ret = new ArrayList();
    ret.add(new Scheme("S1", 5, 2, new String[]{"S3"}));
    ret.add(new Scheme("S2", 3, 1, new String[]{"S3"}));
    ret.add(new Scheme("S3", 10, 4, new String[]{"S1", "S2"}));
    return ret;
}

the output is :

0hard/5soft
Scheme(id=S1, gain=5, cost=2, valid=true, conflicts=[S3])
Scheme(id=S2, gain=3, cost=1, valid=true, conflicts=[S3])
Scheme(id=S3, gain=10, cost=4, valid=false, conflicts=[S1, S2])

And when I set input as following:

private static List<Scheme>  getSchemes() {
    List<Scheme> ret = new ArrayList();
    ret.add(new Scheme("S3", 10, 4, new String[]{"S1", "S2"}));
    ret.add(new Scheme("S1", 5, 2, new String[]{"S3"}));
    ret.add(new Scheme("S2", 3, 1, new String[]{"S3"}));
    return ret;
}

I get the best solution and the output is :

0hard/6soft
Scheme(id=S3, gain=10, cost=4, valid=true, conflicts=[S1, S2])
Scheme(id=S1, gain=5, cost=2, valid=false, conflicts=[S3])
Scheme(id=S2, gain=3, cost=1, valid=false, conflicts=[S3])

Could anyone help me about it?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source