Search code examples
optimizationmodelingoptaplannerconstraint-satisfaction

Modeling: Efficiently model subset selection


The problem I want to model and solve using Optaplanner is that of creating a roster for a sports team (here: soccer). That is: From all available players, select 11 according to several criteria. I am using a hard/medium/soft score in order to define valid solutions, e.g. a hard criteria that specifies that exactly one goalkeeper must be present in a roster. The order in which players are selected does not matter.

I currently have this as my PlanningEntity:

@PlanningEntity
public class Roster
{
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member1;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member2;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member3;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member4;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member5;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member6;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member7;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member8;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member9;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member10;
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member11;

    public List<RosterMember> getMembers()
    {
        return Lists.newArrayList(member1, member2, member3, member4, member5, member6, member7, member8, member9, member10, member11);
    }
}

For the score calculation I only use getMembers, i.e. I don't care if a player is assigned to member1 or member2. The solver is mostly using the default configuration and is configured to timeout after 100s or if no improvement was achieved after 10s.

After playing around with this on some sample dataset, I've noticed that the solver doesn't find the optimal solution (but at least valid) in most cases, e.g. not the best goalkeeper is selected (this is usually easy to see since there are not many available and exactly one should be chosen).

I suspect that this has to to do with how I have modeled the PlanningEntity: Since all roster members can be set separetely, this seems to make the solutions space unnecessarily big. As mentioned before, it doesn't matter to which field a member is assigned. It only matters if (or rather who) is assigned and who is not. So I basically need to select a subset of players while fulfilling some constraints and optimization criteria.

However simply annotating a List of RosterMembers as @PlanningVariable doesn't seem to work. I also couldn't find a similar situation in the examples. I guess this should rather be a simple modeling example?

The only idea I could come up with so far is explicitly modeling some of the (hard) constraints, e.g. restricting the range to goalkeeper for exactly one of the planning variables, while restricting all other ones to non-goalkeeper (or maybe even further). According to the documentation (4.3.5.2.3.) this should rather be avoided.

Edit: I only have one roster instance. I assume that the rosters of different teams are not related and plan on running the solver for each team sequentially.

Edit 2: Following the suggestion, I now have this instead of the previous Roster:

@PlanningEntity
public class RosterAssignment
{
    @PlanningVariable(valueRangeProviderRefs = {"candidates"})
    private RosterMember member;
}

When creating the initial unsolved solution, I add eleven empty RosterAssignments. However the solver doesn't seem to be able to improve anything after the start solution has been created:

(DefaultConstructionHeuristicPhase.java:158) Construction Heuristic phase (0) ended: step total (11), time spent (111), best score (-1hard/-2medium/1275soft).
(DefaultLocalSearchPhase.java:152) Local Search phase (1) ended: step total (375104), time spent (10111), best score (-1hard/-2medium/1275soft).
(DefaultSolver.java:238)       Solving ended: time spent (10129), best score (-1hard/-2medium/1275soft), average calculate count per second (75545), environment mode (REPRODUCIBLE).

Solution

  • See the Tennis example: for each Day instance, there are 4 TeamAssignment instances, not 4 planning variables on Day itself.

    Although the search space is the same, the default moves (changeMove and swapMove) work much better, as you'll have swaps between TeamAssignment "day1-spot1" and TeamAssignment "day2-spot3".