Skip to content

Commit c6c30e9

Browse files
authored
feat: add Diversified Late Acceptance approach (#1253)
This PR adds a new acceptor approach: Diversified Late Acceptance approach (DLAS). The implementation is based on the work: `Diversified Late Acceptance Search by M. Namazi, C. Sanderson, M. A. H. Newton, M. M. A. Polash, and A. Sattar`
1 parent 8dfa1a1 commit c6c30e9

File tree

20 files changed

+634
-64
lines changed

20 files changed

+634
-64
lines changed

benchmark/src/main/resources/benchmark.xsd

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@
317317
<xs:sequence>
318318

319319

320+
<xs:element maxOccurs="unbounded" minOccurs="0" name="enablePreviewFeature" type="tns:previewFeature"/>
321+
322+
320323
<xs:element minOccurs="0" name="environmentMode" type="tns:environmentMode"/>
321324

322325

@@ -2543,6 +2546,21 @@
25432546
</xs:complexType>
25442547

25452548

2549+
<xs:simpleType name="previewFeature">
2550+
2551+
2552+
<xs:restriction base="xs:string">
2553+
2554+
2555+
<xs:enumeration value="DIVERSIFIED_LATE_ACCEPTANCE"/>
2556+
2557+
2558+
</xs:restriction>
2559+
2560+
2561+
</xs:simpleType>
2562+
2563+
25462564
<xs:simpleType name="environmentMode">
25472565

25482566

@@ -3062,6 +3080,9 @@
30623080
<xs:enumeration value="LATE_ACCEPTANCE"/>
30633081

30643082

3083+
<xs:enumeration value="DIVERSIFIED_LATE_ACCEPTANCE"/>
3084+
3085+
30653086
<xs:enumeration value="GREAT_DELUGE"/>
30663087

30673088

core/src/build/revapi-differences.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@
8080
"old": "method Score_ ai.timefold.solver.core.api.score.constraint.ConstraintMatch<Score_ extends ai.timefold.solver.core.api.score.Score<Score_>>::getScore()",
8181
"new": "method Score_ ai.timefold.solver.core.api.score.constraint.ConstraintMatch<Score_ extends ai.timefold.solver.core.api.score.Score<Score_>>::getScore()",
8282
"justification": "False positive after addition of @NonNull annotation"
83+
},
84+
{
85+
"ignore": true,
86+
"code": "java.annotation.attributeValueChanged",
87+
"old": "class ai.timefold.solver.core.config.solver.SolverConfig",
88+
"new": "class ai.timefold.solver.core.config.solver.SolverConfig",
89+
"annotationType": "jakarta.xml.bind.annotation.XmlType",
90+
"attribute": "propOrder",
91+
"oldValue": "{\"environmentMode\", \"daemon\", \"randomType\", \"randomSeed\", \"randomFactoryClass\", \"moveThreadCount\", \"moveThreadBufferSize\", \"threadFactoryClass\", \"monitoringConfig\", \"solutionClass\", \"entityClassList\", \"domainAccessType\", \"scoreDirectorFactoryConfig\", \"terminationConfig\", \"phaseConfigList\"}",
92+
"newValue": "{\"enablePreviewFeatureSet\", \"environmentMode\", \"daemon\", \"randomType\", \"randomSeed\", \"randomFactoryClass\", \"moveThreadCount\", \"moveThreadBufferSize\", \"threadFactoryClass\", \"monitoringConfig\", \"solutionClass\", \"entityClassList\", \"domainAccessType\", \"scoreDirectorFactoryConfig\", \"terminationConfig\", \"nearbyDistanceMeterClass\", \"phaseConfigList\"}",
93+
"justification": "Enable features preview config"
8394
}
8495
]
8596
}

core/src/main/java/ai/timefold/solver/core/config/localsearch/decider/acceptor/AcceptorType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public enum AcceptorType {
1111
UNDO_MOVE_TABU,
1212
SIMULATED_ANNEALING,
1313
LATE_ACCEPTANCE,
14+
DIVERSIFIED_LATE_ACCEPTANCE,
1415
GREAT_DELUGE,
1516
STEP_COUNTING_HILL_CLIMBING
1617
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ai.timefold.solver.core.config.solver;
2+
3+
public enum PreviewFeature {
4+
DIVERSIFIED_LATE_ACCEPTANCE
5+
}

core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
import java.nio.charset.StandardCharsets;
1212
import java.time.Duration;
1313
import java.util.Arrays;
14+
import java.util.EnumSet;
1415
import java.util.List;
1516
import java.util.Map;
1617
import java.util.Objects;
18+
import java.util.Set;
1719
import java.util.concurrent.ThreadFactory;
1820
import java.util.function.Consumer;
1921

@@ -60,6 +62,7 @@
6062
*/
6163
@XmlRootElement(name = SolverConfig.XML_ELEMENT_NAME)
6264
@XmlType(name = SolverConfig.XML_TYPE_NAME, propOrder = {
65+
"enablePreviewFeatureSet",
6366
"environmentMode",
6467
"daemon",
6568
"randomType",
@@ -209,7 +212,8 @@ public class SolverConfig extends AbstractConfig<SolverConfig> {
209212

210213
// Warning: all fields are null (and not defaulted) because they can be inherited
211214
// and also because the input config file should match the output config file
212-
215+
@XmlElement(name = "enablePreviewFeature")
216+
protected Set<PreviewFeature> enablePreviewFeatureSet = null;
213217
protected EnvironmentMode environmentMode = null;
214218
protected Boolean daemon = null;
215219
protected RandomType randomType = null;
@@ -284,6 +288,14 @@ public void setClassLoader(@Nullable ClassLoader classLoader) {
284288
this.classLoader = classLoader;
285289
}
286290

291+
public @Nullable Set<PreviewFeature> getEnablePreviewFeatureSet() {
292+
return enablePreviewFeatureSet;
293+
}
294+
295+
public void setEnablePreviewFeatureSet(@Nullable Set<PreviewFeature> enablePreviewFeatureSet) {
296+
this.enablePreviewFeatureSet = enablePreviewFeatureSet;
297+
}
298+
287299
public @Nullable EnvironmentMode getEnvironmentMode() {
288300
return environmentMode;
289301
}
@@ -432,6 +444,11 @@ public void setMonitoringConfig(@Nullable MonitoringConfig monitoringConfig) {
432444
// With methods
433445
// ************************************************************************
434446

447+
public @NonNull SolverConfig withPreviewFeature(@NonNull PreviewFeature... previewFeature) {
448+
enablePreviewFeatureSet = EnumSet.copyOf(Arrays.asList(previewFeature));
449+
return this;
450+
}
451+
435452
public @NonNull SolverConfig withEnvironmentMode(@NonNull EnvironmentMode environmentMode) {
436453
this.environmentMode = environmentMode;
437454
return this;
@@ -636,10 +653,8 @@ public boolean canTerminate() {
636653
// ************************************************************************
637654

638655
public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) {
639-
if (environmentMode == null || environmentMode.isReproducible()) {
640-
if (randomFactoryClass == null && randomSeed == null) {
641-
randomSeed = subSingleIndex;
642-
}
656+
if ((environmentMode == null || environmentMode.isReproducible()) && randomFactoryClass == null && randomSeed == null) {
657+
randomSeed = subSingleIndex;
643658
}
644659
}
645660

@@ -650,6 +665,8 @@ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) {
650665
@Override
651666
public @NonNull SolverConfig inherit(@NonNull SolverConfig inheritedConfig) {
652667
classLoader = ConfigUtils.inheritOverwritableProperty(classLoader, inheritedConfig.getClassLoader());
668+
enablePreviewFeatureSet = ConfigUtils.inheritMergeableEnumSetProperty(enablePreviewFeatureSet,
669+
inheritedConfig.getEnablePreviewFeatureSet());
653670
environmentMode = ConfigUtils.inheritOverwritableProperty(environmentMode, inheritedConfig.getEnvironmentMode());
654671
daemon = ConfigUtils.inheritOverwritableProperty(daemon, inheritedConfig.getDaemon());
655672
randomType = ConfigUtils.inheritOverwritableProperty(randomType, inheritedConfig.getRandomType());

core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.Arrays;
1818
import java.util.Collection;
1919
import java.util.Collections;
20+
import java.util.EnumSet;
2021
import java.util.LinkedHashMap;
2122
import java.util.LinkedHashSet;
2223
import java.util.List;
@@ -211,6 +212,19 @@ public static void applyCustomProperties(@NonNull Object bean, @NonNull String b
211212
}
212213
}
213214

215+
public static <E extends Enum<E>> @Nullable Set<E> inheritMergeableEnumSetProperty(@Nullable Set<E> originalSet,
216+
@Nullable Set<E> inheritedSet) {
217+
if (inheritedSet == null) {
218+
return originalSet;
219+
} else if (originalSet == null) {
220+
return EnumSet.copyOf(inheritedSet);
221+
} else {
222+
var newSet = EnumSet.copyOf(originalSet);
223+
newSet.addAll(inheritedSet);
224+
return newSet;
225+
}
226+
}
227+
214228
public static <T> @Nullable List<T> inheritUniqueMergeableListProperty(@Nullable List<T> originalList,
215229
@Nullable List<T> inheritedList) {
216230
if (inheritedList == null) {

core/src/main/java/ai/timefold/solver/core/impl/heuristic/HeuristicConfigPolicy.java

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import java.util.HashMap;
44
import java.util.Map;
55
import java.util.Random;
6+
import java.util.Set;
67
import java.util.concurrent.ThreadFactory;
78

89
import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner;
910
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner;
1011
import ai.timefold.solver.core.config.solver.EnvironmentMode;
12+
import ai.timefold.solver.core.config.solver.PreviewFeature;
1113
import ai.timefold.solver.core.config.util.ConfigUtils;
1214
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
1315
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
@@ -25,6 +27,7 @@
2527

2628
public class HeuristicConfigPolicy<Solution_> {
2729

30+
private final Set<PreviewFeature> previewFeatureList;
2831
private final EnvironmentMode environmentMode;
2932
private final String logIndentation;
3033
private final Integer moveThreadCount;
@@ -46,6 +49,7 @@ public class HeuristicConfigPolicy<Solution_> {
4649
private final Map<String, ValueMimicRecorder<Solution_>> valueMimicRecorderMap = new HashMap<>();
4750

4851
private HeuristicConfigPolicy(Builder<Solution_> builder) {
52+
this.previewFeatureList = builder.previewFeatureList;
4953
this.environmentMode = builder.environmentMode;
5054
this.logIndentation = builder.logIndentation;
5155
this.moveThreadCount = builder.moveThreadCount;
@@ -128,8 +132,17 @@ public Random getRandom() {
128132
// ************************************************************************
129133

130134
public Builder<Solution_> cloneBuilder() {
131-
return new Builder<>(environmentMode, moveThreadCount, moveThreadBufferSize, threadFactoryClass,
132-
nearbyDistanceMeterClass, random, initializingScoreTrend, solutionDescriptor, classInstanceCache)
135+
return new Builder<Solution_>()
136+
.withPreviewFeatureList(previewFeatureList)
137+
.withEnvironmentMode(environmentMode)
138+
.withMoveThreadCount(moveThreadCount)
139+
.withMoveThreadBufferSize(moveThreadBufferSize)
140+
.withThreadFactoryClass(threadFactoryClass)
141+
.withNearbyDistanceMeterClass(nearbyDistanceMeterClass)
142+
.withRandom(random)
143+
.withInitializingScoreTrend(initializingScoreTrend)
144+
.withSolutionDescriptor(solutionDescriptor)
145+
.withClassInstanceCache(classInstanceCache)
133146
.withLogIndentation(logIndentation);
134147
}
135148

@@ -148,11 +161,13 @@ public HeuristicConfigPolicy<Solution_> createChildThreadConfigPolicy(ChildThrea
148161
// ************************************************************************
149162

150163
public void addEntityMimicRecorder(String id, EntityMimicRecorder<Solution_> mimicRecordingEntitySelector) {
151-
EntityMimicRecorder<Solution_> put = entityMimicRecorderMap.put(id, mimicRecordingEntitySelector);
164+
var put = entityMimicRecorderMap.put(id, mimicRecordingEntitySelector);
152165
if (put != null) {
153-
throw new IllegalStateException("Multiple " + EntityMimicRecorder.class.getSimpleName() + "s (usually "
154-
+ EntitySelector.class.getSimpleName() + "s) have the same id (" + id + ").\n" +
155-
"Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?");
166+
throw new IllegalStateException(
167+
"""
168+
Multiple %ss (usually %ss) have the same id (%s).
169+
Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?"""
170+
.formatted(EntityMimicRecorder.class.getSimpleName(), EntitySelector.class.getSimpleName(), id));
156171
}
157172
}
158173

@@ -161,11 +176,13 @@ public EntityMimicRecorder<Solution_> getEntityMimicRecorder(String id) {
161176
}
162177

163178
public void addSubListMimicRecorder(String id, SubListMimicRecorder<Solution_> mimicRecordingSubListSelector) {
164-
SubListMimicRecorder<Solution_> put = subListMimicRecorderMap.put(id, mimicRecordingSubListSelector);
179+
var put = subListMimicRecorderMap.put(id, mimicRecordingSubListSelector);
165180
if (put != null) {
166-
throw new IllegalStateException("Multiple " + SubListMimicRecorder.class.getSimpleName() + "s (usually "
167-
+ SubListSelector.class.getSimpleName() + "s) have the same id (" + id + ").\n" +
168-
"Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?");
181+
throw new IllegalStateException(
182+
"""
183+
Multiple %ss (usually %ss) have the same id (%s).
184+
Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?"""
185+
.formatted(SubListMimicRecorder.class.getSimpleName(), SubListSelector.class.getSimpleName(), id));
169186
}
170187
}
171188

@@ -174,11 +191,13 @@ public SubListMimicRecorder<Solution_> getSubListMimicRecorder(String id) {
174191
}
175192

176193
public void addValueMimicRecorder(String id, ValueMimicRecorder<Solution_> mimicRecordingValueSelector) {
177-
ValueMimicRecorder<Solution_> put = valueMimicRecorderMap.put(id, mimicRecordingValueSelector);
194+
var put = valueMimicRecorderMap.put(id, mimicRecordingValueSelector);
178195
if (put != null) {
179-
throw new IllegalStateException("Multiple " + ValueMimicRecorder.class.getSimpleName() + "s (usually "
180-
+ ValueSelector.class.getSimpleName() + "s) have the same id (" + id + ").\n" +
181-
"Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?");
196+
throw new IllegalStateException(
197+
"""
198+
Multiple %ss (usually %ss) have the same id (%s).
199+
Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?"""
200+
.formatted(ValueMimicRecorder.class.getSimpleName(), ValueSelector.class.getSimpleName(), id));
182201
}
183202
}
184203

@@ -198,20 +217,31 @@ public ThreadFactory buildThreadFactory(ChildThreadType childThreadType) {
198217
}
199218
}
200219

220+
public void ensurePreviewFeature(PreviewFeature previewFeature) {
221+
if (previewFeatureList == null || !previewFeatureList.contains(previewFeature)) {
222+
throw new IllegalStateException(
223+
"""
224+
The preview feature %s is not enabled.
225+
Maybe add %s to <enablePreviewFeature> in your configuration file?"""
226+
.formatted(previewFeature, previewFeature));
227+
}
228+
}
229+
201230
@Override
202231
public String toString() {
203232
return getClass().getSimpleName() + "(" + environmentMode + ")";
204233
}
205234

206235
public static class Builder<Solution_> {
207236

208-
private final EnvironmentMode environmentMode;
209-
private final Integer moveThreadCount;
210-
private final Integer moveThreadBufferSize;
211-
private final Class<? extends ThreadFactory> threadFactoryClass;
212-
private final InitializingScoreTrend initializingScoreTrend;
213-
private final SolutionDescriptor<Solution_> solutionDescriptor;
214-
private final ClassInstanceCache classInstanceCache;
237+
private Set<PreviewFeature> previewFeatureList;
238+
private EnvironmentMode environmentMode;
239+
private Integer moveThreadCount;
240+
private Integer moveThreadBufferSize;
241+
private Class<? extends ThreadFactory> threadFactoryClass;
242+
private InitializingScoreTrend initializingScoreTrend;
243+
private SolutionDescriptor<Solution_> solutionDescriptor;
244+
private ClassInstanceCache classInstanceCache;
215245

216246
private String logIndentation = "";
217247

@@ -222,23 +252,58 @@ public static class Builder<Solution_> {
222252
private boolean initializedChainedValueFilterEnabled = false;
223253
private boolean unassignedValuesAllowed = false;
224254

225-
private final Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass;
226-
private final Random random;
255+
private Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass;
256+
private Random random;
257+
258+
public Builder<Solution_> withPreviewFeatureList(Set<PreviewFeature> previewFeatureList) {
259+
this.previewFeatureList = previewFeatureList;
260+
return this;
261+
}
227262

228-
public Builder(EnvironmentMode environmentMode, Integer moveThreadCount, Integer moveThreadBufferSize,
229-
Class<? extends ThreadFactory> threadFactoryClass,
230-
Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass, Random random,
231-
InitializingScoreTrend initializingScoreTrend, SolutionDescriptor<Solution_> solutionDescriptor,
232-
ClassInstanceCache classInstanceCache) {
263+
public Builder<Solution_> withEnvironmentMode(EnvironmentMode environmentMode) {
233264
this.environmentMode = environmentMode;
265+
return this;
266+
}
267+
268+
public Builder<Solution_> withMoveThreadCount(Integer moveThreadCount) {
234269
this.moveThreadCount = moveThreadCount;
270+
return this;
271+
}
272+
273+
public Builder<Solution_> withMoveThreadBufferSize(Integer moveThreadBufferSize) {
235274
this.moveThreadBufferSize = moveThreadBufferSize;
275+
return this;
276+
}
277+
278+
public Builder<Solution_> withThreadFactoryClass(Class<? extends ThreadFactory> threadFactoryClass) {
236279
this.threadFactoryClass = threadFactoryClass;
280+
return this;
281+
}
282+
283+
public Builder<Solution_>
284+
withNearbyDistanceMeterClass(Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass) {
237285
this.nearbyDistanceMeterClass = nearbyDistanceMeterClass;
286+
return this;
287+
}
288+
289+
public Builder<Solution_> withRandom(Random random) {
238290
this.random = random;
291+
return this;
292+
}
293+
294+
public Builder<Solution_> withInitializingScoreTrend(InitializingScoreTrend initializingScoreTrend) {
239295
this.initializingScoreTrend = initializingScoreTrend;
296+
return this;
297+
}
298+
299+
public Builder<Solution_> withSolutionDescriptor(SolutionDescriptor<Solution_> solutionDescriptor) {
240300
this.solutionDescriptor = solutionDescriptor;
301+
return this;
302+
}
303+
304+
public Builder<Solution_> withClassInstanceCache(ClassInstanceCache classInstanceCache) {
241305
this.classInstanceCache = classInstanceCache;
306+
return this;
242307
}
243308

244309
public Builder<Solution_> withLogIndentation(String logIndentation) {

0 commit comments

Comments
 (0)