|
12 | 12 | import static ai.timefold.solver.core.testdomain.list.TestdataListUtils.mockEntitySelector; |
13 | 13 | import static ai.timefold.solver.core.testdomain.list.TestdataListUtils.mockIterableFromEntityPropertyValueSelector; |
14 | 14 | import static ai.timefold.solver.core.testdomain.list.TestdataListUtils.mockIterableValueSelector; |
| 15 | +import static ai.timefold.solver.core.testdomain.list.TestdataListUtils.mockUpcomingSelectionIterator; |
15 | 16 | import static ai.timefold.solver.core.testutil.PlannerAssert.assertAllCodesOfIterableSelector; |
16 | 17 | import static ai.timefold.solver.core.testutil.PlannerAssert.assertAllCodesOfIterator; |
17 | 18 | import static ai.timefold.solver.core.testutil.PlannerAssert.assertCodesOfNeverEndingIterableSelector; |
|
21 | 22 | import static ai.timefold.solver.core.testutil.PlannerAssert.verifyPhaseLifecycle; |
22 | 23 | import static ai.timefold.solver.core.testutil.PlannerTestUtils.mockScoreDirector; |
23 | 24 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; |
| 25 | +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; |
24 | 26 | import static org.mockito.Mockito.doReturn; |
25 | 27 | import static org.mockito.Mockito.mock; |
26 | 28 | import static org.mockito.Mockito.times; |
27 | 29 | import static org.mockito.Mockito.verify; |
28 | 30 |
|
29 | 31 | import java.util.Collections; |
30 | 32 | import java.util.List; |
| 33 | +import java.util.NoSuchElementException; |
31 | 34 | import java.util.Random; |
32 | 35 |
|
33 | 36 | import ai.timefold.solver.core.api.solver.SolutionManager; |
34 | 37 | import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; |
| 38 | +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; |
35 | 39 | import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; |
36 | 40 | import ai.timefold.solver.core.impl.heuristic.selector.entity.FromSolutionEntitySelector; |
37 | 41 | import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByValueSelector; |
| 42 | +import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntitySelector; |
38 | 43 | import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector; |
39 | 44 | import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueRangeSelector; |
| 45 | +import ai.timefold.solver.core.impl.heuristic.selector.value.mimic.ManualValueMimicRecorder; |
| 46 | +import ai.timefold.solver.core.impl.heuristic.selector.value.mimic.MimicReplayingValueSelector; |
40 | 47 | import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchPhaseScope; |
41 | 48 | import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; |
42 | 49 | import ai.timefold.solver.core.impl.solver.scope.SolverScope; |
| 50 | +import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition; |
| 51 | +import ai.timefold.solver.core.testdomain.TestdataValue; |
43 | 52 | import ai.timefold.solver.core.testdomain.list.TestdataListEntity; |
44 | 53 | import ai.timefold.solver.core.testdomain.list.TestdataListSolution; |
45 | 54 | import ai.timefold.solver.core.testdomain.list.TestdataListUtils; |
|
56 | 65 | import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingEntity; |
57 | 66 | import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingSolution; |
58 | 67 | import ai.timefold.solver.core.testdomain.list.valuerange.TestdataListEntityProvidingValue; |
| 68 | +import ai.timefold.solver.core.testdomain.list.valuerange.unassignedvar.pinned.TestdataListUnassignedPinnedEntityProvidingEntity; |
| 69 | +import ai.timefold.solver.core.testdomain.list.valuerange.unassignedvar.pinned.TestdataListUnassignedPinnedEntityProvidingSolution; |
59 | 70 | import ai.timefold.solver.core.testutil.TestRandom; |
60 | 71 |
|
61 | 72 | import org.junit.jupiter.api.Test; |
@@ -472,6 +483,65 @@ void randomFullyPinned() { |
472 | 483 | "B[0]"); |
473 | 484 | } |
474 | 485 |
|
| 486 | + @Test |
| 487 | + void refreshReachableEntities() { |
| 488 | + var v1 = new TestdataValue("1"); |
| 489 | + var v2 = new TestdataValue("2"); |
| 490 | + var a = new TestdataListUnassignedPinnedEntityProvidingEntity("A", List.of(v1)); // Pinned |
| 491 | + var b = new TestdataListUnassignedPinnedEntityProvidingEntity("B", List.of(v2)); // Not pinned |
| 492 | + // a is pinned |
| 493 | + a.setPinned(true); |
| 494 | + a.setValueList(List.of(v1)); |
| 495 | + b.setValueList(List.of(v2)); |
| 496 | + var solution = new TestdataListUnassignedPinnedEntityProvidingSolution(); |
| 497 | + solution.setEntityList(List.of(a, b)); |
| 498 | + SolutionManager.updateShadowVariables(solution); |
| 499 | + |
| 500 | + var scoreDirector = mockScoreDirector(TestdataListUnassignedPinnedEntityProvidingSolution.buildSolutionDescriptor()); |
| 501 | + scoreDirector.setWorkingSolution(solution); |
| 502 | + |
| 503 | + // Value selector |
| 504 | + var listVariableDescriptor = TestdataListUnassignedPinnedEntityProvidingEntity.buildVariableDescriptorForValueList(); |
| 505 | + var iterableValueSelector = mockIterableValueSelector(listVariableDescriptor, v1, v2); |
| 506 | + var mimicRecorder = new ManualValueMimicRecorder<>(iterableValueSelector); |
| 507 | + var replayingValueSelector = new MimicReplayingValueSelector<>(mimicRecorder); |
| 508 | + // Entity selector with non-pinned entity filtered by value |
| 509 | + var entityDescriptor = TestdataListUnassignedPinnedEntityProvidingEntity.buildEntityDescriptor(); |
| 510 | + var entitySelector = new FromSolutionEntitySelector<>(entityDescriptor, SelectionCacheType.PHASE, true); |
| 511 | + var filteringEntity = new FilteringEntityByValueSelector<>(entitySelector, replayingValueSelector, true, false); |
| 512 | + var pinningFilterFunction = entityDescriptor.getEffectiveMovableEntityFilter(); |
| 513 | + var nonPinnedEntitySelector = FilteringEntitySelector.of(filteringEntity, SelectionFilter |
| 514 | + .compose((director, selection) -> pinningFilterFunction.test(director.getWorkingSolution(), selection))); |
| 515 | + // Destination selector with non-pinned entity selector filtered by value |
| 516 | + var selector = |
| 517 | + new ElementDestinationSelector<>(nonPinnedEntitySelector, replayingValueSelector, iterableValueSelector, true, |
| 518 | + false); |
| 519 | + |
| 520 | + // First, we select v1, which is pinned, and the entity iterator does not return a feasible destination. |
| 521 | + // However, the value selector has another assigned value, |
| 522 | + // which makes maybeMovableValues in ElementDestinationSelector to be set to true |
| 523 | + // We always return 0 to meet the bailout size, |
| 524 | + // and then return 1 to ensure the entity is selected by destination iterator |
| 525 | + var random = new TestRandom(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1); |
| 526 | + var solverScope = solvingStarted(selector, scoreDirector, random); |
| 527 | + phaseStarted(selector, solverScope); |
| 528 | + var iterator = selector.iterator(); |
| 529 | + mimicRecorder.setRecordedValue(v1); |
| 530 | + assertThat(iterator.hasNext()).isTrue(); |
| 531 | + // The expected position is unassigned as v1 has no feasible destination |
| 532 | + assertThat(iterator.next()).isSameAs(ElementPosition.unassigned()); |
| 533 | + // Next, we select v2, which is not pinned, and the entity iterator returns a feasible destination. |
| 534 | + // This will cause the iterator to call tryUpdateEntityIterator, and reload the entity list |
| 535 | + // b is the only reachable non-pinned entity for v2 |
| 536 | + mimicRecorder.setRecordedValue(v2); |
| 537 | + assertThat(iterator.hasNext()).isTrue(); |
| 538 | + var position = iterator.next().ensureAssigned(); |
| 539 | + var entity = position.entity(); |
| 540 | + var index = position.index(); |
| 541 | + assertThat(entity).isSameAs(b); |
| 542 | + assertThat(index).isSameAs(0); |
| 543 | + } |
| 544 | + |
475 | 545 | @Test |
476 | 546 | void emptyIfThereAreNoEntities() { |
477 | 547 | var v1 = new TestdataListValue("1"); |
@@ -614,4 +684,41 @@ void discardOldValues() { |
614 | 684 | // the entity iterator must discard the previous entity during the hasNext() calls |
615 | 685 | verify(entityIterator, times(1)).discardUpcomingSelection(); |
616 | 686 | } |
| 687 | + |
| 688 | + @Test |
| 689 | + void discardOldValuesAndResetState() { |
| 690 | + var v1 = new TestdataListEntityProvidingValue("V1"); |
| 691 | + var v2 = new TestdataListEntityProvidingValue("V2"); |
| 692 | + var a = new TestdataListEntityProvidingEntity("A", List.of(), List.of()); |
| 693 | + var solution = new TestdataListEntityProvidingSolution(); |
| 694 | + solution.setEntityList(List.of(a)); |
| 695 | + |
| 696 | + var scoreDirector = mockScoreDirector(TestdataListEntityProvidingSolution.buildSolutionDescriptor()); |
| 697 | + scoreDirector.setWorkingSolution(solution); |
| 698 | + |
| 699 | + var entitySelector = mockEntitySelector(a); |
| 700 | + var entityIterator = mockUpcomingSelectionIterator(a, null, a); |
| 701 | + doReturn(entityIterator).when(entitySelector).iterator(); |
| 702 | + var valueSelector = mockIterableValueSelector(getEntityRangeListVariableDescriptor(scoreDirector), v1); |
| 703 | + IterableValueSelector<TestdataListEntityProvidingSolution> replayingValueSelector = |
| 704 | + mockReplayingValueSelector(getEntityRangeListVariableDescriptor(scoreDirector), v1, v1, v2); |
| 705 | + |
| 706 | + var selector = new ElementDestinationSelector<>(entitySelector, replayingValueSelector, valueSelector, true, false); |
| 707 | + // Value 0 makes the iterator to always request an entity from the related iterator |
| 708 | + var random = new TestRandom(0, 0); |
| 709 | + solvingStarted(selector, scoreDirector, random); |
| 710 | + var iterator = selector.iterator(); |
| 711 | + // entityIterator returns a |
| 712 | + assertThat(iterator.hasNext()).isTrue(); |
| 713 | + assertThat(iterator.next()).isNotNull(); |
| 714 | + // entityIterator gets null and call noUpcomingSelection |
| 715 | + assertThat(iterator.hasNext()).isFalse(); |
| 716 | + assertThatCode(iterator::next).isInstanceOf(NoSuchElementException.class); |
| 717 | + // replayingValueSelector returns v2, discardUpcomingSelection is called, and entityIterator returns a again |
| 718 | + assertThat(iterator.hasNext()).isTrue(); |
| 719 | + assertThat(iterator.next()).isNotNull(); |
| 720 | + // iterator exhausted again |
| 721 | + assertThat(iterator.hasNext()).isFalse(); |
| 722 | + assertThatCode(iterator::next).isInstanceOf(NoSuchElementException.class); |
| 723 | + } |
617 | 724 | } |
0 commit comments