Coroneal
Coroneal

Reputation: 21

IllegalStateException Score Corruption after solver config change

My local search part in solver config looks like:

<acceptor>
    <lateAcceptanceSize>400</lateAcceptanceSize>
    <entityTabuSize>9</entityTabuSize>
</acceptor>
<forager>
    <acceptedCountLimit>2000</acceptedCountLimit>
</forager>

and everything works fine but when I change it to(what can cause optimization gain I think):

<acceptor>
<lateAcceptanceSize>600</lateAcceptanceSize>
</acceptor>
<forager>
<acceptedCountLimit>4</acceptedCountLimit>
</forager>

After solver starts working I got exception

Score corruption: the solution's score (-20hard/-8medium/-4soft) is not the uncorruptedScore (-20hard/-8medium/-8soft)

What can cause this problem? (It is only information from FULL_ASSERT mode)

EDIT:

Something can be connected to rule:

// Boundary lessons have to be schedulead at the beginning/end in a day
rule "boundaryLesson"
    when
        $oddzial : Oddzial()
        $boundaryLesson : Lesson(scheduled == true, containsOddzial($oddzial), base.lessonLimits.isBoundaryLesson == true, $base : base)
        exists Lesson(scheduled == true, containsOddzial($oddzial), dayLessonNumber.day == $base.day, base.lessonNumberFrom < $base.lessonNumberFrom)
        and exists Lesson(scheduled == true, containsOddzial($oddzial), dayLessonNumber.day == $base.day, base.lessonNumberTo > $base.lessonNumberTo)
    then
        scoreHolder.addHardConstraintMatch(kcontext, -1);
end

because, sometimes I get following error also:

Score corruption: the workingScore (0hard/-2medium/0soft) is not the uncorruptedScore (-1hard/-2medium/0soft) after completedAction (8848-537:Tuesday-3 {com.pbz.plek.model.simple.DayLessonNr@5924af87 -> com.pbz.plek.model.simple.DayLessonNr@5924af87}):
  The corrupted scoreDirector has no ConstraintMatch(s) which are in excess.
  The corrupted scoreDirector has 1 ConstraintMatch(s) which are missing:
    com.praca.mgr.cp.algorytm.solver/boundaryLesson/level0/[8854-537:Tuesday-2, com.krakfin.pbz.plek.model.simple.Oddzial@c9d4]=-1
  Check your score constraints.

I know how incremental score calculation works but I cannot see what can be wrong with this rule

Upvotes: 1

Views: 709

Answers (2)

Coroneal
Coroneal

Reputation: 21

To sum up:

  • I am using OptaPlanner 6.3.0.Final with FullAssert environment mode
  • I removed custom move to exclude possibility of bugs in that section
  • There are two plannig variables: Sala(Room) and DzienNrLekcji(Object consists day and lesson number)
  • There aren't any shadow variables
  • Problems with corrupted score exists at every score level so I left only hard constraints rules section, which looks like that:

    // ############################################################################ // Hard constraints // ############################################################################

    // two Lessons at the same time should be in another rooms.
    rule "salaOccupancy"
        when
                $leftLesson : Lesson($id : base.numericId, scheduled == true, $sala : sala)
                not Lesson(scheduled == true, timeCollision($leftLesson), sala == $sala, base.numericId < $id)
                $rightLesson : Lesson(scheduled == true, timeCollision($leftLesson), sala == $sala, base.numericId > $id)
        then
            scoreHolder.addHardConstraintMatch(kcontext, -10);
    end
    
    // each oddzial and nauczyciel can't have two lessons at the same time
    rule "przydzialCollision"
        when
               $przydzialConflict : PrzydzialConflict($leftPrzydzial : leftPrzydzial, $rightPrzydzial : rightPrzydzial)
               $leftLesson : Lesson(scheduled == true, base.przydzial == $leftPrzydzial)
               $rightLesson : Lesson(scheduled == true, base.przydzial == $rightPrzydzial, timeCollision($leftLesson), this != $leftLesson)
            then
                scoreHolder.addHardConstraintMatch(kcontext, -2 * $przydzialConflict.getConflictCount());
    end
    
    // sala's capacity shouldn't be exceeded
    rule "salaCapacity"
        when
            $sala : Sala($capacity : ograniczenia.maxLiczbaUczniow.max)
            $lesson : Lesson(scheduled == true, sala == $sala)
            $limit : LessonStudentLimit(lesson == $lesson, numberOfStudents > $capacity)
        then
            scoreHolder.addHardConstraintMatch(kcontext, -2);
    end
    
    // cannot put lesson into not available time period in Sala or Przydzial
    rule "availability"
        when
            Lesson( scheduled == true , dostepnaSala == false )
            or Lesson( scheduled == true , dostepnyPrzydzial == false)
        then
            scoreHolder.addHardConstraintMatch(kcontext, -2);
    end
    
    // Oddzials cannot have gaps between classes during a day
    rule "gaps"
        when
            $oddzial : Oddzial()
            $dzien : DzienTygodnia()
            $lessonList : ArrayList(LessonBlockCounter.calculateOddzialGaps($lessonList,TimetableSolution.maxLessonNr)>0) from collect (
                Lesson(scheduled == true, containsOddzial($oddzial), dzienNrLekcji.dzien == $dzien)
            )
        then
            scoreHolder.addHardConstraintMatch(kcontext, -5*LessonBlockCounter.calculateOddzialGaps($lessonList,TimetableSolution.maxLessonNr));
    end
    
    // If Przydzial has blocks distribution defined, only one lesson per day is allowed
    rule "blocks"
        when
            $przydzial : Przydzial( ograniczenia.ograniczeniaBlokiLekcyjnePrzydzialu.czyTylkoJednaLekcjaNaDzien.isAktywne() == true )
            $dzien : DzienTygodnia()
            $lessonCount : Number( intValue > 1 ) from accumulate (
                $lesson : Lesson(scheduled == true, base.przydzial == $przydzial,dzienNrLekcji.dzien == $dzien),
                count($lesson)
            )
        then
            scoreHolder.addHardConstraintMatch(kcontext, -2);
    end
    
    // Boundary lessons have to be schedulead at the beginning/end in a day
    rule "boundaryLesson"
        when
            $oddzial : Oddzial()
            $boundaryLesson : Lesson(scheduled == true, containsOddzial($oddzial), base.ograniczeniaLekcja.czyLekcjaGraniczna.aktywne == true, $base : base)
            exists Lesson(scheduled == true, containsOddzial($oddzial), dzienNrLekcji.dzien == $base.dzien, base.lekcjaNrOd < $base.lekcjaNrOd)
            and exists Lesson(scheduled == true, containsOddzial($oddzial), dzienNrLekcji.dzien == $base.dzien, base.lekcjaNrDo > $base.lekcjaNrDo)
        then
            scoreHolder.addHardConstraintMatch(kcontext, -1);
    end
    
    // Linked lessons have to take place at the same time
    rule "linkedLesson"
        when
            $linkedLesson : Lesson(scheduled == true, base.ograniczeniaLekcja.lekcjePolaczone.empty == false, $dzienNrLekcji : dzienNrLekcji)
            Lesson(scheduled == true, base.ograniczeniaLekcja.lekcjePolaczone contains $linkedLesson.base, dzienNrLekcji != $dzienNrLekcji)
        then
            scoreHolder.addHardConstraintMatch(kcontext, -5);
    end
    
    // Linked lessons have to take place at the same time
    rule "scheduledLinkedLesson"
        when
            $linkedLesson : Lesson(scheduled == false, base.ograniczeniaLekcja.lekcjePolaczone.empty == false)
        then
            scoreHolder.addHardConstraintMatch(kcontext, -10*$linkedLesson.getBase().getCzasTrwania());
    end
    
    
    // Lessons have to be placed in the school time boundaries
    rule "schoolTime"
        when
            $lesson : Lesson(scheduled == true, base.czasTrwania > 1 , base.lekcjaNrOd > TimetableSolution.maxLessonNr - base.czasTrwania)
        then
            scoreHolder.addHardConstraintMatch(kcontext, -5);
    end
    
    // Lessons have to be scheduled in one of the preferred sala
    rule "assignedSalaPrzydzialu"
        when
            $lesson : Lesson( scheduled == true,
            sala not memberOf base.przydzial.ograniczenia.perferowaneSale.preferowaneSale.saleList )
        then
           scoreHolder.addHardConstraintMatch(kcontext, -1);
    end
    
    // ############################################################################
    // Medium constraints
    // ############################################################################
    
    //lesson have to have sala and day assigned, not assigned lessons are acceptable in overconstrained problem
    rule "scheduledLesson"
        when
            $lesson : Lesson( scheduled == false )
        then
            scoreHolder.addMediumConstraintMatch(kcontext, -$lesson.getBase().getCzasTrwania());
    end
    

After running algorithm, I'am getting exception:

2015-11-04 10:39:21,493 [http-8080-3] INFO  org.optaplanner.core.impl.solver.DefaultSolver - Solving started: time spent (426), best score (uninitialized/-160hard/-165medium/0soft), environment mode (FULL_ASSERT), random (JDK with seed 0).
2015-11-04 10:39:23,969 [http-8080-3] INFO  org.optaplanner.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase - Construction Heuristic phase (0) ended: step total (165), time spent (2903), best score (-160hard/-165medium/0soft).
2015-11-04 10:39:24,615 [http-8080-3] ERROR org.apache.struts2.dispatcher.Dispatcher - Exception occurred during processing request: Score corruption: the solution's score (-123hard/-161medium/0soft) is not the uncorruptedScore (-126hard/-160medium/0soft).
java.lang.IllegalStateException: Score corruption: the solution's score (-123hard/-161medium/0soft) is not the uncorruptedScore (-126hard/-160medium/0soft).
    at org.optaplanner.core.impl.score.director.AbstractScoreDirectorFactory.assertScoreFromScratch(AbstractScoreDirectorFactory.java:100)
    at org.optaplanner.core.impl.solver.scope.DefaultSolverScope.assertScoreFromScratch(DefaultSolverScope.java:127)
    at org.optaplanner.core.impl.solver.recaller.BestSolutionRecaller.processWorkingSolutionDuringStep(BestSolutionRecaller.java:107)
    ...

After studied problem I am pretty sure that it's connected with incremental score calculation and drl file. I thought that problem causes "gaps" rule because "calulateOddzialGaps" method checks day and lesson number of collected $lessonList, but after commented this rule problem still exists. Any other rule doesn't use lessons(planningEntity) at WHEN section inside java method. What can be wrong? I don't have any other ideas...

Upvotes: 0

Geoffrey De Smet
Geoffrey De Smet

Reputation: 27312

In both cases you'll have potential score corruption, but only in the second case it surfaces. For production reliability, you'll definitely want to fix it.

See docs on "incremental score calculation" to understand what score corruption is. Usual causes:

  • Shadow variable corruption. Use OptaPlanner 6.3.0.Final or later and it will show up as "VariableListener corruption" instead of "Score corruption" and provide more info.
  • A bad custom Move due to a bad undo move. Normally this will show up as "Undo Move corruption" instead of "Score corruption".
  • A bad custom Move that acts different the second time it's done on the same solution state. This will be detected during processWorkingSolutionDuringStep().

If you use Drools calculation:

  • A bad score rule that causes "Score corruption". As of OptaPlanner 6.1 this is unlikely, because it's much harder to write a bad score rule. Try commenting out score rules to figure out which one is to blame.
  • A bug in Drools. Unlikely, but possible. Create a dedicated reproducer and submit a jira.

If you use incremental score calculation:

  • A bad Java Incremental score calculator. Use <assertScoreDirectorFactor> with an easy score calculator too. Good luck in this case.

Upvotes: 1

Related Questions