diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd index 4f5b79aa96..0bcae1ee66 100644 --- a/benchmark/src/main/resources/benchmark.xsd +++ b/benchmark/src/main/resources/benchmark.xsd @@ -317,7 +317,7 @@ - + diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index f237ce2040..6a6ed1bd75 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -89,7 +89,7 @@ "annotationType": "jakarta.xml.bind.annotation.XmlType", "attribute": "propOrder", "oldValue": "{\"environmentMode\", \"daemon\", \"randomType\", \"randomSeed\", \"randomFactoryClass\", \"moveThreadCount\", \"moveThreadBufferSize\", \"threadFactoryClass\", \"monitoringConfig\", \"solutionClass\", \"entityClassList\", \"domainAccessType\", \"scoreDirectorFactoryConfig\", \"terminationConfig\", \"phaseConfigList\"}", - "newValue": "{\"enablePreviewFeatureList\", \"environmentMode\", \"daemon\", \"randomType\", \"randomSeed\", \"randomFactoryClass\", \"moveThreadCount\", \"moveThreadBufferSize\", \"threadFactoryClass\", \"monitoringConfig\", \"solutionClass\", \"entityClassList\", \"domainAccessType\", \"scoreDirectorFactoryConfig\", \"terminationConfig\", \"nearbyDistanceMeterClass\", \"phaseConfigList\"}", + "newValue": "{\"enablePreviewFeatureSet\", \"environmentMode\", \"daemon\", \"randomType\", \"randomSeed\", \"randomFactoryClass\", \"moveThreadCount\", \"moveThreadBufferSize\", \"threadFactoryClass\", \"monitoringConfig\", \"solutionClass\", \"entityClassList\", \"domainAccessType\", \"scoreDirectorFactoryConfig\", \"terminationConfig\", \"nearbyDistanceMeterClass\", \"phaseConfigList\"}", "justification": "Enable features preview config" } ] diff --git a/core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java b/core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java index b884780554..bc2c86b138 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java @@ -10,11 +10,12 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.function.Consumer; @@ -61,7 +62,7 @@ */ @XmlRootElement(name = SolverConfig.XML_ELEMENT_NAME) @XmlType(name = SolverConfig.XML_TYPE_NAME, propOrder = { - "enablePreviewFeatureList", + "enablePreviewFeatureSet", "environmentMode", "daemon", "randomType", @@ -211,8 +212,8 @@ public class SolverConfig extends AbstractConfig { // Warning: all fields are null (and not defaulted) because they can be inherited // and also because the input config file should match the output config file - - protected List enablePreviewFeatureList = null; + @XmlElement(name = "enablePreviewFeature") + protected Set enablePreviewFeatureSet = null; protected EnvironmentMode environmentMode = null; protected Boolean daemon = null; protected RandomType randomType = null; @@ -287,12 +288,12 @@ public void setClassLoader(@Nullable ClassLoader classLoader) { this.classLoader = classLoader; } - public @Nullable List getEnablePreviewFeatureList() { - return enablePreviewFeatureList; + public @Nullable Set getEnablePreviewFeatureSet() { + return enablePreviewFeatureSet; } - public void setEnablePreviewFeatureList(@Nullable List enablePreviewFeatureList) { - this.enablePreviewFeatureList = enablePreviewFeatureList; + public void setEnablePreviewFeatureSet(@Nullable Set enablePreviewFeatureSet) { + this.enablePreviewFeatureSet = enablePreviewFeatureSet; } public @Nullable EnvironmentMode getEnvironmentMode() { @@ -443,11 +444,8 @@ public void setMonitoringConfig(@Nullable MonitoringConfig monitoringConfig) { // With methods // ************************************************************************ - public @NonNull SolverConfig withPreviewFeature(@NonNull PreviewFeature previewFeature) { - if (enablePreviewFeatureList == null) { - enablePreviewFeatureList = new ArrayList<>(); - } - enablePreviewFeatureList.add(previewFeature); + public @NonNull SolverConfig withPreviewFeature(@NonNull PreviewFeature... previewFeature) { + enablePreviewFeatureSet = EnumSet.copyOf(Arrays.asList(previewFeature)); return this; } @@ -667,8 +665,8 @@ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) { @Override public @NonNull SolverConfig inherit(@NonNull SolverConfig inheritedConfig) { classLoader = ConfigUtils.inheritOverwritableProperty(classLoader, inheritedConfig.getClassLoader()); - enablePreviewFeatureList = ConfigUtils.inheritMergeableListProperty(enablePreviewFeatureList, - inheritedConfig.getEnablePreviewFeatureList()); + enablePreviewFeatureSet = ConfigUtils.inheritMergeableEnumSetProperty(enablePreviewFeatureSet, + inheritedConfig.getEnablePreviewFeatureSet()); environmentMode = ConfigUtils.inheritOverwritableProperty(environmentMode, inheritedConfig.getEnvironmentMode()); daemon = ConfigUtils.inheritOverwritableProperty(daemon, inheritedConfig.getDaemon()); randomType = ConfigUtils.inheritOverwritableProperty(randomType, inheritedConfig.getRandomType()); diff --git a/core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java b/core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java index 8ccd764712..6ab894e062 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java +++ b/core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -211,6 +212,19 @@ public static void applyCustomProperties(@NonNull Object bean, @NonNull String b } } + public static > @Nullable Set inheritMergeableEnumSetProperty(@Nullable Set originalSet, + @Nullable Set inheritedSet) { + if (inheritedSet == null) { + return originalSet; + } else if (originalSet == null) { + return EnumSet.copyOf(inheritedSet); + } else { + var newSet = EnumSet.copyOf(originalSet); + newSet.addAll(inheritedSet); + return newSet; + } + } + public static @Nullable List inheritUniqueMergeableListProperty(@Nullable List originalList, @Nullable List inheritedList) { if (inheritedList == null) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java index 7488be81b0..94b34c575b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java @@ -1,9 +1,9 @@ package ai.timefold.solver.core.impl.heuristic; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.concurrent.ThreadFactory; import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner; @@ -27,7 +27,7 @@ public class HeuristicConfigPolicy { - private final List previewFeatureList; + private final Set previewFeatureList; private final EnvironmentMode environmentMode; private final String logIndentation; private final Integer moveThreadCount; @@ -132,8 +132,17 @@ public Random getRandom() { // ************************************************************************ public Builder cloneBuilder() { - return new Builder<>(previewFeatureList, environmentMode, moveThreadCount, moveThreadBufferSize, threadFactoryClass, - nearbyDistanceMeterClass, random, initializingScoreTrend, solutionDescriptor, classInstanceCache) + return new Builder() + .withPreviewFeatureList(previewFeatureList) + .withEnvironmentMode(environmentMode) + .withMoveThreadCount(moveThreadCount) + .withMoveThreadBufferSize(moveThreadBufferSize) + .withThreadFactoryClass(threadFactoryClass) + .withNearbyDistanceMeterClass(nearbyDistanceMeterClass) + .withRandom(random) + .withInitializingScoreTrend(initializingScoreTrend) + .withSolutionDescriptor(solutionDescriptor) + .withClassInstanceCache(classInstanceCache) .withLogIndentation(logIndentation); } @@ -225,14 +234,14 @@ public String toString() { public static class Builder { - private final List previewFeatureList; - private final EnvironmentMode environmentMode; - private final Integer moveThreadCount; - private final Integer moveThreadBufferSize; - private final Class threadFactoryClass; - private final InitializingScoreTrend initializingScoreTrend; - private final SolutionDescriptor solutionDescriptor; - private final ClassInstanceCache classInstanceCache; + private Set previewFeatureList; + private EnvironmentMode environmentMode; + private Integer moveThreadCount; + private Integer moveThreadBufferSize; + private Class threadFactoryClass; + private InitializingScoreTrend initializingScoreTrend; + private SolutionDescriptor solutionDescriptor; + private ClassInstanceCache classInstanceCache; private String logIndentation = ""; @@ -243,24 +252,58 @@ public static class Builder { private boolean initializedChainedValueFilterEnabled = false; private boolean unassignedValuesAllowed = false; - private final Class> nearbyDistanceMeterClass; - private final Random random; + private Class> nearbyDistanceMeterClass; + private Random random; - public Builder(List previewFeatureList, EnvironmentMode environmentMode, Integer moveThreadCount, - Integer moveThreadBufferSize, Class threadFactoryClass, - Class> nearbyDistanceMeterClass, Random random, - InitializingScoreTrend initializingScoreTrend, SolutionDescriptor solutionDescriptor, - ClassInstanceCache classInstanceCache) { + public Builder withPreviewFeatureList(Set previewFeatureList) { this.previewFeatureList = previewFeatureList; + return this; + } + + public Builder withEnvironmentMode(EnvironmentMode environmentMode) { this.environmentMode = environmentMode; + return this; + } + + public Builder withMoveThreadCount(Integer moveThreadCount) { this.moveThreadCount = moveThreadCount; + return this; + } + + public Builder withMoveThreadBufferSize(Integer moveThreadBufferSize) { this.moveThreadBufferSize = moveThreadBufferSize; + return this; + } + + public Builder withThreadFactoryClass(Class threadFactoryClass) { this.threadFactoryClass = threadFactoryClass; + return this; + } + + public Builder + withNearbyDistanceMeterClass(Class> nearbyDistanceMeterClass) { this.nearbyDistanceMeterClass = nearbyDistanceMeterClass; + return this; + } + + public Builder withRandom(Random random) { this.random = random; + return this; + } + + public Builder withInitializingScoreTrend(InitializingScoreTrend initializingScoreTrend) { this.initializingScoreTrend = initializingScoreTrend; + return this; + } + + public Builder withSolutionDescriptor(SolutionDescriptor solutionDescriptor) { this.solutionDescriptor = solutionDescriptor; + return this; + } + + public Builder withClassInstanceCache(ClassInstanceCache classInstanceCache) { this.classInstanceCache = classInstanceCache; + return this; } public Builder withLogIndentation(String logIndentation) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/AcceptorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/AcceptorFactory.java index 08f1e0292f..f1d2232668 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/AcceptorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/AcceptorFactory.java @@ -226,7 +226,7 @@ private Optional> buildLateAcceptanceAcceptor( return Optional.empty(); } - private Optional> + private Optional> buildDiversifiedLateAcceptanceAcceptor(HeuristicConfigPolicy configPolicy) { if (acceptorTypeListsContainsAcceptorType(AcceptorType.DIVERSIFIED_LATE_ACCEPTANCE)) { configPolicy.ensurePreviewFeature(PreviewFeature.DIVERSIFIED_LATE_ACCEPTANCE); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/lateacceptance/DiversifiedLateAcceptanceAcceptor.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/lateacceptance/DiversifiedLateAcceptanceAcceptor.java index d1978a2b29..9dd9156274 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/lateacceptance/DiversifiedLateAcceptanceAcceptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/lateacceptance/DiversifiedLateAcceptanceAcceptor.java @@ -1,17 +1,28 @@ package ai.timefold.solver.core.impl.localsearch.decider.acceptor.lateacceptance; +import java.util.Arrays; + import ai.timefold.solver.core.api.score.Score; +import ai.timefold.solver.core.impl.localsearch.decider.acceptor.AbstractAcceptor; import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchMoveScope; import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchPhaseScope; -import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchStepScope; -public class DiversifiedLateAcceptanceAcceptor extends LateAcceptanceAcceptor { +public class DiversifiedLateAcceptanceAcceptor extends AbstractAcceptor { // The worst score in the late elements list protected Score lateWorse; // Number of occurrences of lateWorse in the late elements protected int lateWorseOccurrences = -1; + protected int lateAcceptanceSize = -1; + + protected Score[] previousScores; + protected int lateScoreIndex = -1; + + public void setLateAcceptanceSize(int lateAcceptanceSize) { + this.lateAcceptanceSize = lateAcceptanceSize; + } + // ************************************************************************ // Worker methods // ************************************************************************ @@ -19,61 +30,72 @@ public class DiversifiedLateAcceptanceAcceptor extends LateAcceptance @Override public void phaseStarted(LocalSearchPhaseScope phaseScope) { super.phaseStarted(phaseScope); + validate(); + previousScores = new Score[lateAcceptanceSize]; + var initialScore = phaseScope.getBestScore(); + Arrays.fill(previousScores, initialScore); + lateScoreIndex = 0; lateWorseOccurrences = lateAcceptanceSize; - lateWorse = phaseScope.getBestScore(); + lateWorse = initialScore; + } + + private void validate() { + if (lateAcceptanceSize <= 0) { + throw new IllegalArgumentException( + "The lateAcceptanceSize (%d) cannot be negative or zero.".formatted(lateAcceptanceSize)); + } } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({ "rawtypes", "unchecked" }) public boolean isAccepted(LocalSearchMoveScope moveScope) { // The acceptance and replacement strategies are based on the work: // Diversified Late Acceptance Search by M. Namazi, C. Sanderson, M. A. H. Newton, M. M. A. Polash, and A. Sattar var lateScore = previousScores[lateScoreIndex]; var moveScore = moveScope.getScore(); - var current = moveScope.getStepScope().getPhaseScope().getLastCompletedStepScope().getScore(); + var current = (Score) moveScope.getStepScope().getPhaseScope().getLastCompletedStepScope().getScore(); var previous = current; - var accept = compare(moveScore, current) == 0 || compare(moveScore, lateWorse) > 0; + var accept = moveScore.compareTo(current) == 0 || moveScore.compareTo(lateWorse) > 0; if (accept) { current = moveScore; } // Improves the diversification to allow the next iterations to find a better solution - var lateUnimprovedCmp = compare(current, lateScore) < 0; + var currentScoreWorse = current.compareTo(lateScore) < 0; // Improves the intensification but avoids replacing values when the search falls into a plateau or local minima - var lateImprovedCmp = compare(current, lateScore) > 0 && compare(current, previous) > 0; - if (lateUnimprovedCmp || lateImprovedCmp) { + var currentScoreBetter = current.compareTo(lateScore) > 0 && current.compareTo(previous) > 0; + if (currentScoreWorse || currentScoreBetter) { updateLateScore(current); } lateScoreIndex = (lateScoreIndex + 1) % lateAcceptanceSize; return accept; } - @Override - public void stepEnded(LocalSearchStepScope stepScope) { - // Do nothing - } - - private void updateLateScore(Score score) { - var worseCmp = compare(score, lateWorse); - var lateCmp = compare(previousScores[lateScoreIndex], lateWorse); - if (worseCmp < 0) { - this.lateWorse = score; + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void updateLateScore(Score newScore) { + var newScoreWorse = newScore.compareTo(lateWorse) < 0; + var newScoreEqual = newScore.compareTo(lateWorse) == 0; + var lateScore = (Score) previousScores[lateScoreIndex]; + var lateScoreEqual = lateScore.compareTo(lateWorse) == 0; + if (newScoreWorse) { + this.lateWorse = newScore; this.lateWorseOccurrences = 1; - } else if (lateCmp == 0 && worseCmp != 0) { + } else if (lateScoreEqual && !newScoreEqual) { this.lateWorseOccurrences--; - } else if (lateCmp != 0 && worseCmp == 0) { + } else if (!lateScoreEqual && newScoreEqual) { this.lateWorseOccurrences++; } - previousScores[lateScoreIndex] = score; + previousScores[lateScoreIndex] = newScore; // Recompute the new lateWorse and the number of occurrences if (lateWorseOccurrences == 0) { lateWorse = previousScores[0]; lateWorseOccurrences = 1; for (var i = 1; i < lateAcceptanceSize; i++) { - var cmp = compare(previousScores[i], lateWorse); - if (cmp < 0) { + Score previousScore = previousScores[i]; + var scoreCmp = previousScore.compareTo(lateWorse); + if (scoreCmp < 0) { lateWorse = previousScores[i]; lateWorseOccurrences = 1; - } else if (cmp == 0) { + } else if (scoreCmp == 0) { lateWorseOccurrences++; } } @@ -83,13 +105,9 @@ private void updateLateScore(Score score) { @Override public void phaseEnded(LocalSearchPhaseScope phaseScope) { super.phaseEnded(phaseScope); + previousScores = null; + lateScoreIndex = -1; lateWorse = null; lateWorseOccurrences = -1; } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private int compare(Score first, Score second) { - return ((Score) first).compareTo(second); - } - } \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java index ada30e7f3a..74fdde61a4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java @@ -114,19 +114,20 @@ public > InnerScoreDirectorFactory buildBestSolutionRecaller(environmentMode); var randomFactory = buildRandomFactory(environmentMode); - var previewFeaturesEnabled = solverConfig.getEnablePreviewFeatureList(); + var previewFeaturesEnabled = solverConfig.getEnablePreviewFeatureSet(); - var configPolicy = new HeuristicConfigPolicy.Builder<>( - previewFeaturesEnabled, - environmentMode, - moveThreadCount, - solverConfig.getMoveThreadBufferSize(), - solverConfig.getThreadFactoryClass(), - solverConfig.getNearbyDistanceMeterClass(), - randomFactory.createRandom(), - scoreDirectorFactory.getInitializingScoreTrend(), - solutionDescriptor, - ClassInstanceCache.create()).build(); + var configPolicy = new HeuristicConfigPolicy.Builder() + .withPreviewFeatureList(previewFeaturesEnabled) + .withEnvironmentMode(environmentMode) + .withMoveThreadCount(moveThreadCount) + .withMoveThreadCount(solverConfig.getMoveThreadBufferSize()) + .withThreadFactoryClass(solverConfig.getThreadFactoryClass()) + .withNearbyDistanceMeterClass(solverConfig.getNearbyDistanceMeterClass()) + .withRandom(randomFactory.createRandom()) + .withInitializingScoreTrend(scoreDirectorFactory.getInitializingScoreTrend()) + .withSolutionDescriptor(solutionDescriptor) + .withClassInstanceCache(ClassInstanceCache.create()) + .build(); var basicPlumbingTermination = new BasicPlumbingTermination(isDaemon); var termination = buildTerminationConfig(basicPlumbingTermination, configPolicy, configOverride); var phaseList = buildPhaseList(configPolicy, bestSolutionRecaller, termination); diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index f7f637643b..fd299b1598 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -11,7 +11,7 @@ - + diff --git a/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java b/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java index 4703b2830c..b7fa6c0665 100644 --- a/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java +++ b/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java @@ -65,6 +65,7 @@ class SolverConfigTest { private static final String TEST_SOLVER_CONFIG_WITH_NAMESPACE = "testSolverConfigWithNamespace.xml"; private static final String TEST_SOLVER_CONFIG_WITHOUT_NAMESPACE = "testSolverConfigWithoutNamespace.xml"; + private static final String TEST_SOLVER_CONFIG_WITH_ENUM_SET = "testSolverConfigWithEnumSet.xml"; private final SolverConfigIO solverConfigIO = new SolverConfigIO(); @ParameterizedTest @@ -167,9 +168,9 @@ void withConstraintProviderClass() { @Test void withEnablePreviewFeatureList() { var solverConfig = new SolverConfig(); - assertThat(solverConfig.getEnablePreviewFeatureList()).isNull(); + assertThat(solverConfig.getEnablePreviewFeatureSet()).isNull(); solverConfig.withPreviewFeature(PreviewFeature.DIVERSIFIED_LATE_ACCEPTANCE); - assertThat(solverConfig.getEnablePreviewFeatureList()) + assertThat(solverConfig.getEnablePreviewFeatureSet()) .hasSameElementsAs(List.of(PreviewFeature.DIVERSIFIED_LATE_ACCEPTANCE)); } @@ -201,6 +202,14 @@ void inherit() { assertThat(inheritedSolverConfig).usingRecursiveComparison().isEqualTo(originalSolverConfig); } + @Test + void inheritEnumSet() { + var originalSolverConfig = readSolverConfig(TEST_SOLVER_CONFIG_WITH_ENUM_SET); + var inheritedSolverConfig = + new SolverConfig().inherit(originalSolverConfig); + assertThat(inheritedSolverConfig).usingRecursiveComparison().isEqualTo(originalSolverConfig); + } + @Test void visitReferencedClasses() { var solverConfig = readSolverConfig(TEST_SOLVER_CONFIG_WITHOUT_NAMESPACE); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicyTestUtils.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicyTestUtils.java index 6a1e7a13d8..e43e367d5b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicyTestUtils.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicyTestUtils.java @@ -15,8 +15,12 @@ public static HeuristicConfigPolicy buildHeuristicConfigPolicy public static HeuristicConfigPolicy buildHeuristicConfigPolicy(SolutionDescriptor solutionDescriptor) { - return new HeuristicConfigPolicy.Builder<>(null, EnvironmentMode.REPRODUCIBLE, null, null, null, null, new Random(), - null, solutionDescriptor, ClassInstanceCache.create()).build(); + return new HeuristicConfigPolicy.Builder() + .withEnvironmentMode(EnvironmentMode.REPRODUCIBLE) + .withRandom(new Random()) + .withSolutionDescriptor(solutionDescriptor) + .withClassInstanceCache(ClassInstanceCache.create()) + .build(); } private HeuristicConfigPolicyTestUtils() { diff --git a/core/src/test/resources/ai/timefold/solver/core/config/solver/testSolverConfigWithEnumSet.xml b/core/src/test/resources/ai/timefold/solver/core/config/solver/testSolverConfigWithEnumSet.xml new file mode 100644 index 0000000000..cfaf16f65a --- /dev/null +++ b/core/src/test/resources/ai/timefold/solver/core/config/solver/testSolverConfigWithEnumSet.xml @@ -0,0 +1,18 @@ + + + DIVERSIFIED_LATE_ACCEPTANCE + FULL_ASSERT + AUTO + ai.timefold.solver.core.impl.testdata.domain.TestdataSolution + ai.timefold.solver.core.impl.testdata.domain.TestdataEntity + + ai.timefold.solver.core.config.solver.SolverConfigTest$DummyConstraintProvider + ONLY_DOWN + + + FIRST_FIT_DECREASING + + + TABU_SEARCH + + diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/local-search.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/local-search.adoc index 1c7bda2042..346790c023 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/local-search.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/local-search.adoc @@ -528,19 +528,26 @@ Simplest configuration: [source,xml,options="nowrap"] ---- + + DIVERSIFIED_LATE_ACCEPTANCE + ... DIVERSIFIED_LATE_ACCEPTANCE + ---- -The late elements list is updated -only if the current solution score is worse than the late score or better than the late score and different from the previous one. -The size of the late elements list is typically smaller. +The late elements list is updated as follows: + +* The current solution score is worse than the late score. +* The current solution score is better than the late score and different from the previous one. +The size of the late elements list is typically smaller. Advanced configuration: [source,xml,options="nowrap"] ---- + ... ... @@ -552,6 +559,12 @@ Advanced configuration: ---- +[IMPORTANT] +==== +The new acceptor is available as a xref:upgrading-timefold-solver/backwards-compatibility.adoc#previewFeatures[preview feature] +and must be specifically enabled with ``. +==== + [#greatDeluge] == Great Deluge diff --git a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc index e3033d2ba6..8410363084 100644 --- a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc +++ b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc @@ -36,6 +36,7 @@ but we're just not entirely comfortable yet to write their signatures in stone. Timefold Solver includes several components which are only available as preview features. These are: +- _Diversified Late Acceptance_ acceptor in the package `ai.timefold.solver.core.impl.localsearch.decider.acceptor.lateacceptance`. - _Move Streams API_ in the `ai.timefold.solver.core.api.move` package and its subpackages, as well as the `ai.timefold.solver.core.api.domain.metamodel` package and its subpackages. - _Timefold Solver for Python_, which is currently in beta. diff --git a/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/reflect-config.json b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/reflect-config.json index e882fd9185..0a6bcda3b4 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/reflect-config.json +++ b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/native-image/ai.timefold.solver/timefold-solver-spring-boot-autoconfigure/reflect-config.json @@ -594,6 +594,10 @@ } ] }, + { + "name": "ai.timefold.solver.core.config.solver.PreviewFeature", + "allDeclaredFields": true + }, { "name": "ai.timefold.solver.core.config.solver.EnvironmentMode", "allDeclaredFields": true