Scott A. Levinson
Scott A. Levinson

Reputation: 67

How to combine multiple planning entities for solving one problem in OptaPlanner?

Given:

Employee {
    int id;
    int minJobNum;
    int maxJobNum;
    int totalWorkTime;

    @OneToMany
    List<Job> jobs;

    @ManyToOne
    Vehicle vehicle;
}

@PlanningEntity
Job {
    @PlanningId
    int id;

    @ManyToOne
    Employee employee;

    @PlanningVariable(valueRangeProviderRefs = "employeeRange")
    private Employee employee;
}

@PlanningEntity
Vehicle {
    @PlanningId
    int id;
    int capacity;

    @OneToMany
    @PlanningListVariable(valueRangeProviderRefs = "employeeRange")
    List<Employee> employees;
}

cost matrix:

List<Cost> costs;

Cost {

    Job from;
    Job to;
    Long time;

}

Here is the main class:

SolverFactory<Solution> solverFactory = SolverFactory.create(new SolverConfig()
        .withSolutionClass(Solution.class)
        .withEntityClasses(Job.class, Vehicle.class)
        .withConstraintProviderClass(SolutionConstraintProvider.class)
        .withTerminationSpentLimit(Duration.ofSeconds(5)));

Solution problem = generateDemoData();

Solver<Solution> solver = solverFactory.buildSolver();
Solution solution = solver.solve(problem);

ScoreManager<Solution, HardMediumSoftScore> scoreManager = ScoreManager.create(solverFactory);
ScoreExplanation<Solution, HardMediumSoftScore> scoreExplanation = scoreManager.explainScore(solution);
System.out.println(scoreExplanation.getSummary());
System.out.println("Is Feasible: " + scoreExplanation.getScore().isFeasible());

My Constraints:

public class SolutionConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[]{
                minJobNumberConflict(constraintFactory),
                maxJobNumberConflict(constraintFactory),
                vehicleCapacity(constraintFactory),
                vehicleMaxCapacity(constraintFactory)
        };
    }

    private Constraint minJobNumberConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(Job.class).groupBy(Job::getEmployee, count())
                .filter((employee, count) -> 10 > count)
                .penalize("minJobNumberConflict",
                        HardMediumSoftScore.ONE_MEDIUM, (employee, count) -> 10 - count);
    }

    private Constraint maxJobNumberConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(Job.class).groupBy(Job::getEmployee, count())
                .filter((employee, count) -> count > 30)
                .penalize("maxJobNumberConflict",
                        HardMediumSoftScore.ONE_HARD, (employee, count) -> count - 30);
    }

    private Constraint vehicleMaxCapacity(ConstraintFactory factory) {
        return factory.forEach(Vehicle.class)
                .filter(vehicle -> vehicle.getEmployeeList().size() > vehicle.getCapacity())
                .penalizeLong("vehicleMaxCapacity",
                        HardMediumSoftLongScore.ONE_HARD, vehicle -> vehicle.getEmployeeList().size() - vehicle.getCapacity());
    }

    private Constraint vehicleCapacity(ConstraintFactory factory) {
        return factory.forEach(Vehicle.class)
                .filter(vehicle -> !vehicle.getEmployeeList().isEmpty())
                .filter(vehicle -> vehicle.getEmployeeList().size() < vehicle.getCapacity())
                .penalizeLong("vehicleCapacityConflict",
                        HardMediumSoftLongScore.ONE_SOFT, vehicle -> vehicle.getCapacity() - vehicle.getEmployeeList().size());
    }

}

My Solution class:

@PlanningSolution
public class Solution {

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "employeeRange")
    private List<Employee> employees;

    @PlanningEntityCollectionProperty
    private List<Vehicle> vehicles;

    @PlanningEntityCollectionProperty
    private List<Job> jobs;

    @PlanningScore
    private HardMediumSoftScore score;

    public Plan(List<Employee> employees, List<Job> applications, List<Vehicle> vehicles) {
        this.employees = employees;
        this.jobs = jobs;
        this.vehicles = vehicles;
    }

}

When i try to run the code i get the following error:

The entityClass (class Vehicle) has a @PlanningVariable annotated property (employeeList) that refers to a @ValueRangeProvider annotated member (field private java.util.List Solution.employees) that returns a Collection with elements of type (class Employee) which cannot be assigned to the @PlanningVariable's type (interface java.util.List).

Upvotes: 0

Views: 470

Answers (1)

Geoffrey De Smet
Geoffrey De Smet

Reputation: 27337

Good use cases with multiple planning entity classes are rare. They require custom moves, which makes them labor intensive. We are working on improving the easy of use of that though.

However, in most cases, it's better to do multi-stage planning instead, business wise. I think I discussed that in this video. Conway's law etc.

Upvotes: 1

Related Questions