Reputation: 332
For academic purpose, I'm trying to implement a custom chain move in OptaPlanner. My aim is to purposefully move one link from a chain to another to speed up the resolution of the problem.
Start
1. A -> B -> C
2. D -> E -> F
Move E from D to A
1. A -> E -> B -> C
2. D -> F
Undo move
A -> B -> C
D -> E -> F
I have read both the documentation and several posts on stackoverflow (mainly this, this, this and this) but I keep getting java.lang.IllegalStateException
error.
I am surely missing some concept to solve the problem.
What am I doing wrong?
Thanks
2022-08-30 18:18:24,429 (OptaPool-10-MoveThread-2) Move thread (1) exception that will be propagated to the solver thread.: java.lang.IllegalStateException: The entity (eu.gurgolo.domain.Student@1de) has a variable (previousStandStill) with value (eu.gurgolo.domain.Student@1fb) which has a sourceVariableName variable (nextStudent) with a value (eu.gurgolo.domain.Student@1f8) which is not null.
Verify the consistency of your input problem for that sourceVariableName variable.
at org.optaplanner.core.impl.domain.variable.inverserelation.SingletonInverseVariableListener.insert(SingletonInverseVariableListener.java:74)
at org.optaplanner.core.impl.domain.variable.inverserelation.SingletonInverseVariableListener.afterVariableChanged(SingletonInverseVariableListener.java:53)
CustomMoveFactory
public class CustomMoveFactory implements MoveListFactory<MySolution> {
@Override
public List<CustomMove> createMoveList(MySolution solution) {
List<CustomMove> customMoves = new ArrayList<>();
List<Student> students = solution.getStudents();
for(Student s1 : students) {
for(Student s2 : students) {
//Here some logic to add
customMoves.add(new CustomMove(s1, s2));
}
}
return customMoves;
}
}
CustomMove
public class CustomMove extends AbstractMove<MySolution> {
private Student newStandStill;
private Student student;
public CustomMove(Student s1, Student s2) {
this.newStandStill = s1;
this.student = s2;
}
@Override
public boolean isMoveDoable(ScoreDirector<MySolution> scoreDirector) {
return !Objects.equals(newStandStill, student.getPreviousStandStill());
}
@Override
protected AbstractMove<MySolution> createUndoMove(ScoreDirector<MySolution> scoreDirector) {
return new CustomMove(student, newStandStill);
}
@Override
protected void doMoveOnGenuineVariables(ScoreDirector<MySolution> scoreDirector) {
Student nextStudent = student.getNextStudent();
Student nextStandStill = newStandStill.getNextStudent();
// 1. fix the chain where the student will be removed
if(nextStudent != null) {
scoreDirector.beforeVariableChanged(nextStudent, "previousStandStill");
nextStudent.setPreviousStandStill(student.getPreviousStandStill());
scoreDirector.afterVariableChanged(nextStudent, "previousStandStill");
}
// 2. fix the chain where the student will be added
if(nextStandStill != null) {
scoreDirector.beforeVariableChanged(nextStandStill, "previousStandStill");
nextStandStill.setPreviousStandStill(student.getPreviousStandStill());
scoreDirector.afterVariableChanged(nextStandStill, "previousStandStill");
}
// 3. move the student in the chain
scoreDirector.beforeVariableChanged(student, "previousStandStill");
student.setPreviousStandStill(newStandStill);
scoreDirector.afterVariableChanged(student, "previousStandStill");
}
@Override
public CustomMove rebase(ScoreDirector<MySolution> destinationScoreDirector) {
return new CustomMove(destinationScoreDirector.lookUpWorkingObject(newStandStill),
destinationScoreDirector.lookUpWorkingObject(student));
}
@Override
public Collection<? extends Object> getPlanningEntities() {
return Collections.singletonList(newStandStill);
}
@Override
public Collection<? extends Object> getPlanningValues() {
return Collections.singletonList(student);
}
@Override
public String getSimpleMoveTypeDescription() {
return getClass().getSimpleName() + "(" + Student.class.getSimpleName() + ".student)";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final CustomMove other = (CustomMove) o;
return Objects.equals(newStandStill, other.newStandStill) &&
Objects.equals(student, other.student);
}
@Override
public int hashCode() {
return Objects.hash(newStandStill, student);
}
@Override
public String toString() {
long profId = newStandStill.getProf().getId();
long nextProfId = student.getProf().getId();
return newStandStill.getStudentId() + " {" + profId + " -> " + nextProfId + "}";
}
}
Upvotes: 0
Views: 108
Reputation: 2358
I have evaluated your CustomMove implementation on paper and tried to move E from D to A as proposed in your question.
I think you have a bug in step 2. Your code:
// 2. fix the chain where the student will be added
if(nextStandStill != null) {
scoreDirector.beforeVariableChanged(nextStandStill, "previousStandStill");
nextStandStill.setPreviousStandStill(student.getPreviousStandStill());
scoreDirector.afterVariableChanged(nextStandStill, "previousStandStill");
}
Fixed version of the above:
// 2. fix the chain where the student will be added
if(nextStandStill != null) {
scoreDirector.beforeVariableChanged(nextStandStill, "previousStandStill");
nextStandStill.setPreviousStandStill(student);
scoreDirector.afterVariableChanged(nextStandStill, "previousStandStill");
}
In "move E from D to A", A
is newStandstill
and so B
is nextStandstill
. E
(the moved student
) should become B
's new previousStandstill
. I hope that illuminates the proposed fix above.
Upvotes: 1