如何添加HardConstraint最小和最大计数每组在OptaPlanner?



我是OptaPlanner的新手。

我需要在员工之间划分工作列表,但一个员工执行的工作数量不应少于或多于指定的值。例如,一个员工每天完成的工作不能少于10个,不能超过30个。

源代码
@Data
@NoArgsConstructor
@PlanningEntity
public class Job {
@PlanningId
private Long id;
@PlanningVariable(valueRangeProviderRefs = "employeeRange")
private Employee employee;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@PlanningEntity
public class Inspector {
@PlanningId
private Long id;
@InverseRelationShadowVariable(sourceVariableName = "employee")
private List<Job> jobs;
}
@Data
@NoArgsConstructor
@PlanningSolution
public class Plan {
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "employeeRange")
private List<Employee> employees;
@PlanningEntityCollectionProperty
private List<Job> jobs;
@PlanningScore
private HardSoftScore score;
}

还有一个问题:

如何将多个约束合并为一个约束?例如,一名员工每天可以完成10到30项工作。一次出差持续2天,因此,在此期间他总共可以做20到60份工作。

我读了文档,但我不确定count() countDistinct() min() max()是否适合这个目的

在您的域中,我没有看到任何用于确定作业日期的字段,因此我假设所有作业共享相同的日期。使用约束流,可以建模您的最小和最大工作约束如下:

public class MyConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[] {
minimumJobsPerDay(constraintFactory),
maximumJobsPerDay(constraintFactory)
};
}
Constraint minimumJobsPerDay(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Inspector.class)
.filter(inspector -> inspector.getJobs().size() < 10)
.penalize("Employee performing less than minimum jobs per day",
HardSoftScore.ONE_HARD,
inspector -> 10 - inspector.getJobs().size());
}
Constraint maximumJobsPerDay(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Inspector.class)
.filter(inspector -> inspector.getJobs().size() > 30)
.penalize("Employee performing more than maximum jobs per day",
HardSoftScore.ONE_HARD,
inspector -> inspector.getJobs().size() - 30);
}
}

对于第二个问题,Compose Constraint Collector可以用于组合约束(尽管在您的示例中不需要它)。对于您的示例(带有一个额外的问题事实类BusinessTrip):

@Data
@AllArgsConstructor
public class BusinessTrip {
private Employee employee;
private LocalDate start;
private LocalDate end;
}

和改变Job,所以它有一个日期:

@Data
@NoArgsConstructor
@PlanningEntity
public class Job {
@PlanningId
private Long id;
private LocalDate date;
@PlanningVariable(valueRangeProviderRefs = "employeeRange")
private Employee employee;
}

约束可以建模如下:

Constraint jobsDuringBusinessTrip(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(BusinessTrip.class)
.join(Employee.class, Joiners.equal(BusinessTrip::getEmployee, employee -> employee))
.join(Job.class, Joiners.overlapping(
(trip, employee) -> trip.getStart().atStartOfDay(),
(trip, employee) -> trip.getEnd().atStartOfDay(),
job -> job.getDate().atStartOfDay(),
job -> job.getDate().plusDays(1).atStartOfDay()),
Joiners.equal((trip, employee) -> employee, Job::getEmployee))
.groupBy((trip,employee,job) -> trip,
(trip,employee,job) -> employee,
ConstraintCollectors.countTri())
.filter((trip, employee, jobCount) -> jobCount < 20 || jobCount > 60)
.penalize("Over or under job count during business trip",
HardSoftScore.ONE_HARD);
}

试试这个解决方案:

public class CustomConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{minNumberConflict(constraintFactory), maxNumberConflict(constraintFactory)};
}
Constraint maxNumberConflict(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Job.class).groupBy(Job::getEmployee, count()).filter((employee, count) -> count > 30).penalize("Max Count", HardMediumSoftScore.ONE_HARD, (employee, count) -> count - 30);
}
Constraint minNumberConflict(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Job.class).groupBy(Job::getEmployee, count()).filter((employee, count) -> 10 > count).penalize("Min Count", HardMediumSoftScore.ONE_MEDIUM, (employee, count) -> 10 - count);
}
}

最新更新