Can you please help me to understand the Timefold? I`m trying to setup a pretty simple problem: have 'processes' with the start moment defined with int and the relation 'link' between them. relation means that the 'consuming' process cannot start earlier than the 'producer' starts. but when I do execute it, both processes starts at 0.
@PlanningEntity
public class Process {
private int id;
private Integer startDate;
public Process() {
}
public Process(int id, Integer startDate) {
this.id = id;
this.startDate = startDate;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@PlanningVariable(valueRangeProviderRefs = "startDateRange")
public Integer getStartDate() {
return startDate;
}
public void setStartDate(Integer startDate) {
this.startDate = startDate;
}
}
public class Link {
private Process source;
private Process target;
public Link() {
}
public Link(Process source, Process target) {
this.source = source;
this.target = target;
}
public Process getSource() {
return source;
}
public void setSource(Process source) {
this.source = source;
}
public Process getTarget() {
return target;
}
public void setTarget(Process target) {
this.target = target;
}
}
@PlanningSolution
public class Schedule {
private List<Process> processList;
private List<Link> linkList;
private HardSoftScore score;
private List<Integer> startDateRange;
public Schedule() {
}
public Schedule(List<Process> processList, List<Link> linkList, List<Integer> startDateRange) {
this.processList = processList;
this.linkList = linkList;
this.startDateRange = startDateRange;
}
@PlanningEntityCollectionProperty
public List<Process> getProcessList() {
return processList;
}
public void setProcessList(List<Process> processList) {
this.processList = processList;
}
@ProblemFactCollectionProperty
public List<Link> getLinkList() {
return linkList;
}
public void setLinkList(List<Link> linkList) {
this.linkList = linkList;
}
@ValueRangeProvider(id = "startDateRange")
public List<Integer> getStartDateRange() {
return startDateRange;
}
public void setStartDateRange(List<Integer> startDateRange) {
this.startDateRange = startDateRange;
}
@ai.timefold.solver.core.api.domain.solution.PlanningScore
public HardSoftScore getScore() {
return score;
}
public void setScore(HardSoftScore score) {
this.score = score;
}
}
public class TimetableConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
processOrderConstraint(constraintFactory)
};
}
private Constraint processOrderConstraint(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Link.class)
.penalize("Process order constraint", HardSoftScore.ONE_HARD,
link -> {
Integer sourceStartDate = link.getSource().getStartDate();
Integer targetStartDate = link.getTarget().getStartDate();
if (sourceStartDate == null || targetStartDate == null) {
return 0; // No penalty if either start date is null
}
return sourceStartDate >= targetStartDate ? 1 : 0;
});
}
}
public class TimetableApp {
public static void main(String[] args) {
Process p1 = new Process(1, null);
Process p2 = new Process(2, null);
Link link = new Link(p1, p2);
List<Integer> startDateRange = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Schedule unsolvedSchedule = new Schedule(
Arrays.asList(p1, p2),
Arrays.asList(link),
startDateRange
);
SolverFactory<Schedule> solverFactory =
SolverFactory.create(new SolverConfig()
.withSolutionClass(Schedule.class)
.withEntityClasses(Process.class)
.withConstraintProviderClass(TimetableConstraintProvider.class)
.withTerminationSpentLimit(Duration.ofSeconds(100)));
Solver<Schedule> solver = solverFactory.buildSolver();
Schedule solvedSchedule = solver.solve(unsolvedSchedule);
for (Process process : solvedSchedule.getProcessList()) {
System.out.println("Process " + process.getId() + " starts at " + process.getStartDate());
}
}
}
If you turn on environmentMode FULL_ASSERT (which makes it slow), you'll probably discover a score corruption (it will flush out bugs), because of this code:
return constraintFactory.forEach(Link.class)
// SCORE CORRUPTION because this doesn't reevaluate if the Process instances change
.penalize("Process order constraint", HardSoftScore.ONE_HARD,
link -> {
Integer sourceStartDate = link.getSource().getStartDate();
Integer targetStartDate = link.getTarget().getStartDate();
if (sourceStartDate == null || targetStartDate == null) {
return 0; // No penalty if either start date is null
}
return sourceStartDate >= targetStartDate ? 1 : 0;
});
The way to fix this is to do something like:
constraintFactory.forEach(Process)
.join(Process.class,
filtering(a, b -> a.linkedDestinationProcesses.contains(b))
.penalize(...)