Search code examples
optaplanner

Allowing overlap with OptaPlanner and TSPTW


I currently have OptaPlanner solving a TSPTW problem. For the following it will help to think of the destinations as tasks.

Each task currently has a chained planning variable called previousTask. The tasks can be categorized as Type A or Type B. What I now want to do is allow Type B tasks to optionally overlap Type A tasks, letting OptaPlanner decide whether overlap is the right choice.

For example, given the tasks A1, A2, B1, OptaPlanner may decide that A1 -> B1 -> A2 is best or that A1 -> (A2 with B1 overlapping) is best.

The way I thought I could achieve this is:

  1. Give each Type A task a second (non-chained) planning variable called overlappingTask.
  2. Split the current "tasks" ValueRangeProvider into two ValueRangeProviders, "typeATasks" and "typeBTasks".
  3. Annotate previousTask's ValueRangeProviders to be both typeATasks and typeBTasks.
  4. Annotate overlappingTask's ValueRangeProviders to only be typeBTasks.

The problem I am solving will always have at least one Type A task but may not have any Type B tasks. This caused a problem with my proposed solution because the "typeBTasks" ValueRangeProvider is sometimes empty, which throws an IllegalStateException for the previousTask planning variable.

Is there a better way to approach this problem? Is there a way to get around the empty ValueRangeProvider issue? The empty complaint against previousTask seems odd given that the combination of the ValueRangeProviders isn't empty. It seems like it would be better for OptaPlanner to check whether the combination is empty, rather than each input separately.

Here are some code snippets to clarify the current design:

public Solution
{
    @PlanningEntityCollectionProperty
    @ValueRangeProvider(id = "typeATasks")
    public List<TypeA> getTypeATasks)

    @PlanningEntityCollectionProperty
    @ValueRangeProvider(id = "typeBTasks")
    public List<TypeB> getTypeBTasks()
}

public class Task
{
    @PlanningVariable(valueRangeProviderRefs = { "typeATasks", "typeBTasks" },
                      graphType = PlanningVariableGraphType.CHAINED)
    public Task getPreviousTask()
}

public class TaskB extends Task {}

public class TaskA extends Task
{
    @PlanningVariable(valueRangeProviderRefs = { "typeBTasks" }, nullable = true)
    public TaskB getOverlappingTask()
}

Solution

  • Not that your model is bad, let's call yours proposal C). See my comment above, it's a bug that optaplanner 6.4.0.Beta2 fails fast on that model.

    But I was thinking of a model like this, proposal A):

    @PlanningEntity class TaskAssignment {
        TaskDef taskDef;
        @PlanningVariable TaskAssignment previousTaskAssignment;
        @PlanningVariable Boolean overlapPreviousIfPossible;
    
        boolean isOverLappingPrevious {
            return taskDef.isTypeB() && overlapPreviousIfPossible;
        }
    }
    

    In this case, the value range provider would just return all TaskAssignments.

    But I am also thinking of another model, let's call that proposal B), like in the examination example: planning entity AbstractTaskAssignment, extended by TypeATaskAssigement and TaskBTaskAssignment. That is a better domain model (no overlap vars for type A assignements), but a far more painful config (especially moves are harder).