Opraplanner制服员工轮班计划
Opraplanner uniform employees shifts planning
我在计划员工轮班时遇到问题,因为员工在轮班中均匀(随机)分布。
在我的最小示例中,我使用 Spring boot、Lombock 和 Optaplanner spring boot starter (8.15.0.Final) 包。
我在一个文件中的最小示例:
package com.example.planner;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.lookup.PlanningId;
import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.solver.SolverManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.ArrayList;
import java.util.List;
@SpringBootApplication
public class PlannerApplication implements CommandLineRunner {
@Autowired
private SolverManager<Problem, Long> solverManager;
public static void main(String[] args) {
SpringApplication.run(PlannerApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
final var problem = new Problem(
List.of(new Employee(1L), new Employee(2L), new Employee(3L)),
List.of(new Shift(1L), new Shift(2L), new Shift(3L), new Shift(4L), new Shift(5L), new Shift(6L))
);
final var job = solverManager.solveAndListen(1L, id -> problem, bestSolution -> {
for (final var shift : bestSolution.shifts) {
System.err.println("Shift " + shift.id + ": Employee " + shift.employee.id);
}
});
}
@NoArgsConstructor
public static class PlannerConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{};
}
}
@PlanningSolution
@Data @NoArgsConstructor @AllArgsConstructor
public static class Problem {
@ValueRangeProvider(id = "employeeRange")
@ProblemFactCollectionProperty
private List<Employee> employees;
@PlanningEntityCollectionProperty
private List<Shift> shifts = new ArrayList<>(0);
@PlanningScore
private HardSoftScore score;
public Problem(List<Employee> employees, List<Shift> shifts) {
this.employees = employees;
this.shifts = shifts;
}
}
@Data @NoArgsConstructor @AllArgsConstructor
public static class Employee {
@PlanningId
private Long id;
}
@PlanningEntity
@Data @NoArgsConstructor @AllArgsConstructor
public static class Shift {
@PlanningId
private Long id;
@PlanningVariable(valueRangeProviderRefs = "employeeRange")
private Employee employee;
public Shift(Long id) {
this.id = id;
}
}
}
这个例子的输出是:
Shift 1: Employee 1
Shift 2: Employee 1
Shift 3: Employee 1
Shift 4: Employee 1
Shift 5: Employee 1
Shift 6: Employee 1
期望的输出是:
Shift 1: Employee 1
Shift 2: Employee 2
Shift 3: Employee 3
Shift 4: Employee 1
Shift 5: Employee 2
Shift 6: Employee 3
(或其他统一组合)
你没有任何限制。您还没有告诉 OptaPlanner 要优化什么,因此所有解决方案都同样有利于 OptaPlanner。
(其实这段代码没有失败我挺意外的,没有约束的情况应该会抛出异常。)
您还没有定义任何约束,因此OptaPlaner 没有理由提出更好的解决方案。你没有告诉它什么更好。
OptaPlanner“认为”这个解决方案可能是最好的,因为(我猜)它的分数是 0hard/0soft
(您可以在控制台中查看),这是一个理想的分数。
要获得所需的输出,您应该定义一个 fair workload distribution 约束条件,该约束条件将对每个员工的工作量进行平方惩罚。可能这样的事情应该适用于你的情况:
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[] {
fairWorkloadDistribution(constraintFactory)
};
}
...
Constraint fairWorkloadDistribution(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Shift.class)
.groupBy(Shift::getEmployee, ConstraintCollectors.count())
.penalize(
"Employee workload squared",
HardSoftScore.ONE_SOFT,
(employee, shifts) -> shifts * shifts);
}
我在计划员工轮班时遇到问题,因为员工在轮班中均匀(随机)分布。
在我的最小示例中,我使用 Spring boot、Lombock 和 Optaplanner spring boot starter (8.15.0.Final) 包。
我在一个文件中的最小示例:
package com.example.planner;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.lookup.PlanningId;
import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.solver.SolverManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.ArrayList;
import java.util.List;
@SpringBootApplication
public class PlannerApplication implements CommandLineRunner {
@Autowired
private SolverManager<Problem, Long> solverManager;
public static void main(String[] args) {
SpringApplication.run(PlannerApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
final var problem = new Problem(
List.of(new Employee(1L), new Employee(2L), new Employee(3L)),
List.of(new Shift(1L), new Shift(2L), new Shift(3L), new Shift(4L), new Shift(5L), new Shift(6L))
);
final var job = solverManager.solveAndListen(1L, id -> problem, bestSolution -> {
for (final var shift : bestSolution.shifts) {
System.err.println("Shift " + shift.id + ": Employee " + shift.employee.id);
}
});
}
@NoArgsConstructor
public static class PlannerConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{};
}
}
@PlanningSolution
@Data @NoArgsConstructor @AllArgsConstructor
public static class Problem {
@ValueRangeProvider(id = "employeeRange")
@ProblemFactCollectionProperty
private List<Employee> employees;
@PlanningEntityCollectionProperty
private List<Shift> shifts = new ArrayList<>(0);
@PlanningScore
private HardSoftScore score;
public Problem(List<Employee> employees, List<Shift> shifts) {
this.employees = employees;
this.shifts = shifts;
}
}
@Data @NoArgsConstructor @AllArgsConstructor
public static class Employee {
@PlanningId
private Long id;
}
@PlanningEntity
@Data @NoArgsConstructor @AllArgsConstructor
public static class Shift {
@PlanningId
private Long id;
@PlanningVariable(valueRangeProviderRefs = "employeeRange")
private Employee employee;
public Shift(Long id) {
this.id = id;
}
}
}
这个例子的输出是:
Shift 1: Employee 1
Shift 2: Employee 1
Shift 3: Employee 1
Shift 4: Employee 1
Shift 5: Employee 1
Shift 6: Employee 1
期望的输出是:
Shift 1: Employee 1
Shift 2: Employee 2
Shift 3: Employee 3
Shift 4: Employee 1
Shift 5: Employee 2
Shift 6: Employee 3
(或其他统一组合)
你没有任何限制。您还没有告诉 OptaPlanner 要优化什么,因此所有解决方案都同样有利于 OptaPlanner。
(其实这段代码没有失败我挺意外的,没有约束的情况应该会抛出异常。)
您还没有定义任何约束,因此OptaPlaner 没有理由提出更好的解决方案。你没有告诉它什么更好。
OptaPlanner“认为”这个解决方案可能是最好的,因为(我猜)它的分数是 0hard/0soft
(您可以在控制台中查看),这是一个理想的分数。
要获得所需的输出,您应该定义一个 fair workload distribution 约束条件,该约束条件将对每个员工的工作量进行平方惩罚。可能这样的事情应该适用于你的情况:
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[] {
fairWorkloadDistribution(constraintFactory)
};
}
...
Constraint fairWorkloadDistribution(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Shift.class)
.groupBy(Shift::getEmployee, ConstraintCollectors.count())
.penalize(
"Employee workload squared",
HardSoftScore.ONE_SOFT,
(employee, shifts) -> shifts * shifts);
}