Gurgolo
Gurgolo

Reputation: 332

Custom chain move implementation in OptaPlanner

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


The error

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)

The code

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

Answers (1)

yurloc
yurloc

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

Related Questions