|
1 | 1 | package ai.timefold.solver.core.impl.heuristic.selector.move.generic; |
2 | 2 |
|
| 3 | +import static ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils.mockEntitySelector; |
3 | 4 | import static ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils.phaseStarted; |
4 | 5 | import static ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils.solvingStarted; |
5 | 6 | import static ai.timefold.solver.core.testdomain.list.TestdataListUtils.getEntityDescriptor; |
|
15 | 16 | import java.util.List; |
16 | 17 | import java.util.Random; |
17 | 18 |
|
| 19 | +import ai.timefold.solver.core.api.score.director.ScoreDirector; |
18 | 20 | import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; |
19 | 21 | import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; |
20 | 22 | import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; |
| 23 | +import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; |
21 | 24 | import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; |
22 | 25 | import ai.timefold.solver.core.impl.heuristic.selector.entity.FromSolutionEntitySelector; |
23 | 26 | import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByEntitySelector; |
| 27 | +import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntitySelector; |
| 28 | +import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.ManualEntityMimicRecorder; |
24 | 29 | import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicRecordingEntitySelector; |
25 | 30 | import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicReplayingEntitySelector; |
26 | 31 | import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; |
27 | 32 | import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; |
28 | 33 | import ai.timefold.solver.core.impl.solver.scope.SolverScope; |
29 | 34 | import ai.timefold.solver.core.testdomain.TestdataEntity; |
| 35 | +import ai.timefold.solver.core.testdomain.TestdataObject; |
30 | 36 | import ai.timefold.solver.core.testdomain.TestdataValue; |
31 | 37 | import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingEntity; |
32 | 38 | import ai.timefold.solver.core.testdomain.valuerange.entityproviding.TestdataEntityProvidingSolution; |
@@ -336,6 +342,103 @@ void emptyRightOriginalLeftUnequalsRight() { |
336 | 342 | verifyPhaseLifecycle(rightEntitySelector, 1, 2, 5); |
337 | 343 | } |
338 | 344 |
|
| 345 | + @Test |
| 346 | + void originalEntitiesPinned() { |
| 347 | + var v1 = new TestdataValue("1"); |
| 348 | + var v2 = new TestdataValue("2"); |
| 349 | + var v3 = new TestdataValue("3"); |
| 350 | + var v4 = new TestdataValue("4"); |
| 351 | + var e1 = new TestdataAllowsUnassignedEntityProvidingEntity("A", List.of(v1, v4)); |
| 352 | + var e2 = new TestdataAllowsUnassignedEntityProvidingEntity("B", List.of(v2, v3)); |
| 353 | + var e3 = new TestdataAllowsUnassignedEntityProvidingEntity("C", List.of(v1, v4)); |
| 354 | + var solution = new TestdataAllowsUnassignedEntityProvidingSolution("s1"); |
| 355 | + solution.setEntityList(List.of(e1, e2, e3)); |
| 356 | + |
| 357 | + var scoreDirector = mockScoreDirector(TestdataAllowsUnassignedEntityProvidingSolution.buildSolutionDescriptor()); |
| 358 | + scoreDirector.setWorkingSolution(solution); |
| 359 | + |
| 360 | + var leftEntitySelector = new ManualEntityMimicRecorder<>( |
| 361 | + mockEntitySelector(TestdataAllowsUnassignedEntityProvidingEntity.buildEntityDescriptor(), e1, e2, e3)); |
| 362 | + |
| 363 | + var replayingEntitySelector = new MimicReplayingEntitySelector<>(leftEntitySelector); |
| 364 | + var filteringEntitySelector = |
| 365 | + FilteringEntitySelector.of( |
| 366 | + mockEntitySelector(TestdataAllowsUnassignedEntityProvidingEntity.buildEntityDescriptor(), e1, e2, e3), |
| 367 | + new EntityCodeFiltering<>(List.of("B", "C"))); |
| 368 | + var rightEntitySelector = |
| 369 | + new FilteringEntityByEntitySelector<>(filteringEntitySelector, replayingEntitySelector, false); |
| 370 | + var solverScope = solvingStarted(rightEntitySelector, scoreDirector); |
| 371 | + phaseStarted(rightEntitySelector, solverScope); |
| 372 | + |
| 373 | + // Regular iterator |
| 374 | + // The left selector chooses A, and the right selector returns no value |
| 375 | + leftEntitySelector.setRecordedEntity(e1); |
| 376 | + var iterator = rightEntitySelector.iterator(); |
| 377 | + assertThat(iterator.hasNext()).isFalse(); |
| 378 | + // The left selector chooses B, and the right selector returns A |
| 379 | + leftEntitySelector.setRecordedEntity(e2); |
| 380 | + iterator = rightEntitySelector.iterator(); |
| 381 | + assertThat(iterator.hasNext()).isTrue(); |
| 382 | + assertThat(iterator.next()).hasToString("A"); |
| 383 | + // No more moves |
| 384 | + assertThat(iterator.hasNext()).isFalse(); |
| 385 | + |
| 386 | + // ListIterator |
| 387 | + // The left selector chooses A, and the right selector returns no value |
| 388 | + leftEntitySelector.setRecordedEntity(e1); |
| 389 | + var listIterator = rightEntitySelector.listIterator(); |
| 390 | + assertThat(listIterator.hasNext()).isFalse(); |
| 391 | + // B <-> A |
| 392 | + leftEntitySelector.setRecordedEntity(e2); |
| 393 | + listIterator = rightEntitySelector.listIterator(); |
| 394 | + assertThat(listIterator.hasNext()).isTrue(); |
| 395 | + assertThat(listIterator.next()).hasToString("A"); |
| 396 | + assertThat(listIterator.hasNext()).isFalse(); |
| 397 | + // Backward move |
| 398 | + assertThat(listIterator.hasPrevious()).isTrue(); |
| 399 | + assertThat(listIterator.previous()).hasToString("A"); |
| 400 | + } |
| 401 | + |
| 402 | + @Test |
| 403 | + void randomEntitiesPinned() { |
| 404 | + var v1 = new TestdataValue("1"); |
| 405 | + var v2 = new TestdataValue("2"); |
| 406 | + var v3 = new TestdataValue("3"); |
| 407 | + var v4 = new TestdataValue("4"); |
| 408 | + var e1 = new TestdataAllowsUnassignedEntityProvidingEntity("A", List.of(v1, v4), v1); |
| 409 | + var e2 = new TestdataAllowsUnassignedEntityProvidingEntity("B", List.of(v2, v3), v2); |
| 410 | + var e3 = new TestdataAllowsUnassignedEntityProvidingEntity("C", List.of(v1, v4)); |
| 411 | + var solution = new TestdataAllowsUnassignedEntityProvidingSolution("s1"); |
| 412 | + solution.setEntityList(List.of(e1, e2, e3)); |
| 413 | + |
| 414 | + var scoreDirector = mockScoreDirector(TestdataAllowsUnassignedEntityProvidingSolution.buildSolutionDescriptor()); |
| 415 | + scoreDirector.setWorkingSolution(solution); |
| 416 | + |
| 417 | + var baseEntitySelector = |
| 418 | + new FromSolutionEntitySelector<>(getEntityDescriptor(scoreDirector), SelectionCacheType.JUST_IN_TIME, true); |
| 419 | + var leftEntitySelector = new ManualEntityMimicRecorder<>(baseEntitySelector); |
| 420 | + |
| 421 | + var replayingEntitySelector = new MimicReplayingEntitySelector<>(leftEntitySelector); |
| 422 | + var filteringEntitySelector = |
| 423 | + FilteringEntitySelector.of(baseEntitySelector, new EntityCodeFiltering<>(List.of("B", "C"))); |
| 424 | + var rightEntitySelector = |
| 425 | + new FilteringEntityByEntitySelector<>(filteringEntitySelector, replayingEntitySelector, true); |
| 426 | + var solverScope = solvingStarted(rightEntitySelector, scoreDirector, |
| 427 | + new TestRandom(0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)); |
| 428 | + phaseStarted(rightEntitySelector, solverScope); |
| 429 | + |
| 430 | + // Random iterator |
| 431 | + // The left selector chooses B, |
| 432 | + // and the right selector chooses A (not reachable), and then only B (excluded by the filter) |
| 433 | + leftEntitySelector.setRecordedEntity(e2); |
| 434 | + var iterator = rightEntitySelector.iterator(); |
| 435 | + assertThat(iterator.hasNext()).isTrue(); |
| 436 | + // Return the same as the left selector |
| 437 | + assertThat(iterator.next()).hasToString("B"); |
| 438 | + // No more moves |
| 439 | + assertThat(iterator.hasNext()).isFalse(); |
| 440 | + } |
| 441 | + |
339 | 442 | @Test |
340 | 443 | void singleVarRandomSelectionWithEntityValueRange() { |
341 | 444 | var v1 = new TestdataValue("1"); |
@@ -492,4 +595,18 @@ void noReachableEntities() { |
492 | 595 | var swapMove = (SwapMove<TestdataEntityProvidingSolution>) iterator.next(); |
493 | 596 | assertThat(swapMove.getLeftEntity()).isSameAs(swapMove.getRightEntity()); |
494 | 597 | } |
| 598 | + |
| 599 | + private static class EntityCodeFiltering<Solution_> implements SelectionFilter<Solution_, Object> { |
| 600 | + |
| 601 | + private final List<String> excludedCodes; |
| 602 | + |
| 603 | + public EntityCodeFiltering(List<String> excludedCodes) { |
| 604 | + this.excludedCodes = excludedCodes; |
| 605 | + } |
| 606 | + |
| 607 | + @Override |
| 608 | + public boolean accept(ScoreDirector<Solution_> scoreDirector, Object selection) { |
| 609 | + return !excludedCodes.contains(((TestdataObject) selection).getCode()); |
| 610 | + } |
| 611 | + } |
495 | 612 | } |
0 commit comments