Skip to content

Commit

Permalink
fix: exhaustive search must not skip non-doable moves
Browse files Browse the repository at this point in the history
  • Loading branch information
triceo authored and rsynek committed Oct 9, 2023
1 parent b8f7a00 commit 5d78bfd
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private ExhaustiveSearchDecider<Solution_> buildDecider(HeuristicConfigPolicy<So
MoveSelectorConfig<?> moveSelectorConfig_ = buildMoveSelectorConfig(configPolicy,
sourceEntitySelector, mimicSelectorId);
MoveSelector<Solution_> moveSelector = MoveSelectorFactory.<Solution_> create(moveSelectorConfig_)
.buildMoveSelector(configPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.ORIGINAL, true);
.buildMoveSelector(configPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.ORIGINAL, false);
ScoreBounder scoreBounder = scoreBounderEnabled
? new TrendBasedScoreBounder(configPolicy.getScoreDefinition(), configPolicy.getInitializingScoreTrend())
: null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ private boolean hasFiltering() {

private MoveSelector<Solution_> applyFiltering(MoveSelector<Solution_> moveSelector, boolean skipNonDoableMoves) {
/*
* Do not filter out pointless moves in Construction Heuristics,
* Do not filter out pointless moves in Construction Heuristics and Exhaustive Search,
* because the original value of the entity is irrelevant.
* If the original value is null and the variable is nullable,
* the change move to null must be done too.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package ai.timefold.solver.core.impl.exhaustivesearch;

import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.SoftAssertions.assertSoftly;

import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.config.exhaustivesearch.ExhaustiveSearchPhaseConfig;
import ai.timefold.solver.core.config.exhaustivesearch.ExhaustiveSearchType;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.core.impl.exhaustivesearch.scope.ExhaustiveSearchStepScope;
import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter;
import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope;
import ai.timefold.solver.core.impl.solver.DefaultSolver;
import ai.timefold.solver.core.impl.testdata.domain.TestdataEasyScoreCalculator;
import ai.timefold.solver.core.impl.testdata.domain.TestdataEntity;
import ai.timefold.solver.core.impl.testdata.domain.TestdataSolution;
import ai.timefold.solver.core.impl.testdata.domain.nullable.TestdataNullableEasyScoreCalculator;
import ai.timefold.solver.core.impl.testdata.domain.nullable.TestdataNullableEntity;
import ai.timefold.solver.core.impl.testdata.domain.nullable.TestdataNullableSolution;

import org.junit.jupiter.api.Test;

class BruteForceTest {

@Test
void doesNotIncludeNullForNonNullableVariable() {
var solverConfig = new SolverConfig()
.withSolutionClass(TestdataSolution.class)
.withEntityClasses(TestdataEntity.class)
.withEasyScoreCalculatorClass(TestdataEasyScoreCalculator.class)
.withPhases(new ExhaustiveSearchPhaseConfig()
.withExhaustiveSearchType(ExhaustiveSearchType.BRUTE_FORCE));
var solver = (DefaultSolver<TestdataSolution>) SolverFactory.<TestdataSolution> create(solverConfig)
.buildSolver();

var solution = TestdataSolution.generateSolution(2, 2);
for (TestdataEntity entity : solution.getEntityList()) { // Make sure nothing is set.
entity.setValue(null);
}

solver.addPhaseLifecycleListener(new PhaseLifecycleListenerAdapter<>() {

@Override
public void stepStarted(AbstractStepScope<TestdataSolution> stepScope) {
if (stepScope instanceof ExhaustiveSearchStepScope<TestdataSolution> exhaustiveSearchStepScope) {
if (exhaustiveSearchStepScope.getStepIndex() == 3) {
fail("The exhaustive search phase was not ended after 3 steps.");
}
} else {
fail("Wrong phase was started: " + stepScope.getClass().getSimpleName());
}
}

});

var finalBestSolution = solver.solve(solution);

assertSoftly(softly -> {
softly.assertThat(finalBestSolution.getScore())
.isEqualTo(SimpleScore.ZERO);
softly.assertThat(finalBestSolution.getEntityList().get(0).getValue())
.isEqualTo(solution.getValueList().get(0));
softly.assertThat(finalBestSolution.getEntityList().get(1).getValue())
.isEqualTo(solution.getValueList().get(1));
});
}

@Test
void includesNullsForNullableVariable() {
var solverConfig = new SolverConfig()
.withSolutionClass(TestdataNullableSolution.class)
.withEntityClasses(TestdataNullableEntity.class)
.withEasyScoreCalculatorClass(TestdataNullableEasyScoreCalculator.class)
.withPhases(new ExhaustiveSearchPhaseConfig()
.withExhaustiveSearchType(ExhaustiveSearchType.BRUTE_FORCE));
var solver = (DefaultSolver<TestdataNullableSolution>) SolverFactory.<TestdataNullableSolution> create(solverConfig)
.buildSolver();

var solution = TestdataNullableSolution.generateSolution(1, 2);
for (TestdataNullableEntity entity : solution.getEntityList()) { // Make sure nothing is set.
entity.setValue(null);
}

solver.addPhaseLifecycleListener(new PhaseLifecycleListenerAdapter<>() {

@Override
public void stepStarted(AbstractStepScope<TestdataNullableSolution> stepScope) {
if (stepScope instanceof ExhaustiveSearchStepScope<TestdataNullableSolution> exhaustiveSearchStepScope) {
if (exhaustiveSearchStepScope.getStepIndex() == 3) {
fail("The exhaustive search phase was not ended after 3 steps.");
}
} else {
fail("Wrong phase was started: " + stepScope.getClass().getSimpleName());
}
}

});

var finalBestSolution = solver.solve(solution);

assertSoftly(softly -> {
softly.assertThat(finalBestSolution.getScore())
.isEqualTo(SimpleScore.of(-1));
softly.assertThat(finalBestSolution.getEntityList().get(0).getValue())
.as("The first entity's value was set.")
.isNull();
softly.assertThat(finalBestSolution.getEntityList().get(1).getValue())
.as("The second entity's value was not set.")
.isNotNull();
});
}

}

0 comments on commit 5d78bfd

Please sign in to comment.