Uploaded image for project: 'OptaPlanner'
  1. OptaPlanner
  2. PLANNER-313

Using scoreDefinitionType HARD_SOFT_BIG_DECIMAL (HardSoftBigDecimalScoreHolder) can raise score corruption when scale of BigDecimal changes

XMLWordPrintable

      Updated report:
      BigDecimal.ZERO = 0
      BigDecimal.ZERO + 0.01 = 0.01
      BigDecimal.ZERO + 0.01 - 0.01 = 0.00
      => BigDecimal.ZERO != BigDecimal.ZERO + 0.01 - 0.01
      => Score corruption guaranteed

      Original report:
      I currently get the following exception:

      java.lang.IllegalStateException: Score corruption: the workingScore (0hard/0.00soft) is not the uncorruptedScore (0hard/0soft) after completedAction (******cloud.optimizer.*******@699743b3 => null):
        The corrupted scoreDirector has no ConstraintMatch(s) which are in excess.
        The corrupted scoreDirector has no ConstraintMatch(s) which are missing.
        The corrupted scoreDirector has no ConstraintMatch(s) in excess or missing. That could be a bug in this class (class org.optaplanner.core.impl.score.director.drools.DroolsScoreDirector).
        Check your score constraints.
      	at org.optaplanner.core.impl.score.director.AbstractScoreDirector.assertWorkingScoreFromScratch(AbstractScoreDirector.java:335)
      	at org.optaplanner.core.impl.phase.scope.AbstractPhaseScope.assertExpectedUndoMoveScore(AbstractPhaseScope.java:139)
      	at org.optaplanner.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider.doMove(ConstructionHeuristicDecider.java:108)
      	at org.optaplanner.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider.decideNextStep(ConstructionHeuristicDecider.java:77)
      	at org.optaplanner.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.solve(DefaultConstructionHeuristicPhase.java:67)
      	at org.optaplanner.core.impl.solver.DefaultSolver.runPhases(DefaultSolver.java:213)
      	at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:176)
      	at ******.*****.testSolve(AnbauPlanungTest.java:21)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:606)
      	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
      	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
      	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
      	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
      	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
      	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
      	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
      	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
      	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
      	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
      	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
      	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
      	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
      	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
      	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
      	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
      	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
      	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
      	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
      

      The problem is that I am dividing my BigDecimals by 100 adding them and substracting them seems to result in a change of the scale. This will throw the given exception.

      The equals function of HardSoftBigDecimalScore should be able to handle such cases.

      HardSoftBigDecimalScore.java
      ...
          public boolean equals(Object o) {
              // A direct implementation (instead of EqualsBuilder) to avoid dependencies
              if (this == o) {
                  return true;
              } else if (o instanceof HardSoftBigDecimalScore) {
                  HardSoftBigDecimalScore other = (HardSoftBigDecimalScore) o;
                  return hardScore.equals(other.getHardScore())
                          && softScore.equals(other.getSoftScore());
              } else {
                  return false;
              }
          }
      ...
      

      /Manuel

              gdesmet@redhat.com Geoffrey De Smet (Inactive)
              manuelb_jira Manuel Blechschmidt (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

                Created:
                Updated:
                Resolved: