Skip to content

Commit

Permalink
feat: add Diversified Late Acceptance approach (#1253)
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
zepfred authored Dec 6, 2024
1 parent 8dfa1a1 commit c6c30e9
Show file tree
Hide file tree
Showing 20 changed files with 634 additions and 64 deletions.
21 changes: 21 additions & 0 deletions benchmark/src/main/resources/benchmark.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@
<xs:sequence>


<xs:element maxOccurs="unbounded" minOccurs="0" name="enablePreviewFeature" type="tns:previewFeature"/>


<xs:element minOccurs="0" name="environmentMode" type="tns:environmentMode"/>


Expand Down Expand Up @@ -2543,6 +2546,21 @@
</xs:complexType>


<xs:simpleType name="previewFeature">


<xs:restriction base="xs:string">


<xs:enumeration value="DIVERSIFIED_LATE_ACCEPTANCE"/>


</xs:restriction>


</xs:simpleType>


<xs:simpleType name="environmentMode">


Expand Down Expand Up @@ -3062,6 +3080,9 @@
<xs:enumeration value="LATE_ACCEPTANCE"/>


<xs:enumeration value="DIVERSIFIED_LATE_ACCEPTANCE"/>


<xs:enumeration value="GREAT_DELUGE"/>


Expand Down
11 changes: 11 additions & 0 deletions core/src/build/revapi-differences.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@
"old": "method Score_ ai.timefold.solver.core.api.score.constraint.ConstraintMatch<Score_ extends ai.timefold.solver.core.api.score.Score<Score_>>::getScore()",
"new": "method Score_ ai.timefold.solver.core.api.score.constraint.ConstraintMatch<Score_ extends ai.timefold.solver.core.api.score.Score<Score_>>::getScore()",
"justification": "False positive after addition of @NonNull annotation"
},
{
"ignore": true,
"code": "java.annotation.attributeValueChanged",
"old": "class ai.timefold.solver.core.config.solver.SolverConfig",
"new": "class ai.timefold.solver.core.config.solver.SolverConfig",
"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": "{\"enablePreviewFeatureSet\", \"environmentMode\", \"daemon\", \"randomType\", \"randomSeed\", \"randomFactoryClass\", \"moveThreadCount\", \"moveThreadBufferSize\", \"threadFactoryClass\", \"monitoringConfig\", \"solutionClass\", \"entityClassList\", \"domainAccessType\", \"scoreDirectorFactoryConfig\", \"terminationConfig\", \"nearbyDistanceMeterClass\", \"phaseConfigList\"}",
"justification": "Enable features preview config"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum AcceptorType {
UNDO_MOVE_TABU,
SIMULATED_ANNEALING,
LATE_ACCEPTANCE,
DIVERSIFIED_LATE_ACCEPTANCE,
GREAT_DELUGE,
STEP_COUNTING_HILL_CLIMBING
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ai.timefold.solver.core.config.solver;

public enum PreviewFeature {
DIVERSIFIED_LATE_ACCEPTANCE
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import java.nio.charset.StandardCharsets;
import java.time.Duration;
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;

Expand Down Expand Up @@ -60,6 +62,7 @@
*/
@XmlRootElement(name = SolverConfig.XML_ELEMENT_NAME)
@XmlType(name = SolverConfig.XML_TYPE_NAME, propOrder = {
"enablePreviewFeatureSet",
"environmentMode",
"daemon",
"randomType",
Expand Down Expand Up @@ -209,7 +212,8 @@ public class SolverConfig extends AbstractConfig<SolverConfig> {

// 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

@XmlElement(name = "enablePreviewFeature")
protected Set<PreviewFeature> enablePreviewFeatureSet = null;
protected EnvironmentMode environmentMode = null;
protected Boolean daemon = null;
protected RandomType randomType = null;
Expand Down Expand Up @@ -284,6 +288,14 @@ public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}

public @Nullable Set<PreviewFeature> getEnablePreviewFeatureSet() {
return enablePreviewFeatureSet;
}

public void setEnablePreviewFeatureSet(@Nullable Set<PreviewFeature> enablePreviewFeatureSet) {
this.enablePreviewFeatureSet = enablePreviewFeatureSet;
}

public @Nullable EnvironmentMode getEnvironmentMode() {
return environmentMode;
}
Expand Down Expand Up @@ -432,6 +444,11 @@ public void setMonitoringConfig(@Nullable MonitoringConfig monitoringConfig) {
// With methods
// ************************************************************************

public @NonNull SolverConfig withPreviewFeature(@NonNull PreviewFeature... previewFeature) {
enablePreviewFeatureSet = EnumSet.copyOf(Arrays.asList(previewFeature));
return this;
}

public @NonNull SolverConfig withEnvironmentMode(@NonNull EnvironmentMode environmentMode) {
this.environmentMode = environmentMode;
return this;
Expand Down Expand Up @@ -636,10 +653,8 @@ public boolean canTerminate() {
// ************************************************************************

public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) {
if (environmentMode == null || environmentMode.isReproducible()) {
if (randomFactoryClass == null && randomSeed == null) {
randomSeed = subSingleIndex;
}
if ((environmentMode == null || environmentMode.isReproducible()) && randomFactoryClass == null && randomSeed == null) {
randomSeed = subSingleIndex;
}
}

Expand All @@ -650,6 +665,8 @@ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) {
@Override
public @NonNull SolverConfig inherit(@NonNull SolverConfig inheritedConfig) {
classLoader = ConfigUtils.inheritOverwritableProperty(classLoader, inheritedConfig.getClassLoader());
enablePreviewFeatureSet = ConfigUtils.inheritMergeableEnumSetProperty(enablePreviewFeatureSet,
inheritedConfig.getEnablePreviewFeatureSet());
environmentMode = ConfigUtils.inheritOverwritableProperty(environmentMode, inheritedConfig.getEnvironmentMode());
daemon = ConfigUtils.inheritOverwritableProperty(daemon, inheritedConfig.getDaemon());
randomType = ConfigUtils.inheritOverwritableProperty(randomType, inheritedConfig.getRandomType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -211,6 +212,19 @@ public static void applyCustomProperties(@NonNull Object bean, @NonNull String b
}
}

public static <E extends Enum<E>> @Nullable Set<E> inheritMergeableEnumSetProperty(@Nullable Set<E> originalSet,
@Nullable Set<E> 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 <T> @Nullable List<T> inheritUniqueMergeableListProperty(@Nullable List<T> originalList,
@Nullable List<T> inheritedList) {
if (inheritedList == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import java.util.HashMap;
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;
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
Expand All @@ -25,6 +27,7 @@

public class HeuristicConfigPolicy<Solution_> {

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

private HeuristicConfigPolicy(Builder<Solution_> builder) {
this.previewFeatureList = builder.previewFeatureList;
this.environmentMode = builder.environmentMode;
this.logIndentation = builder.logIndentation;
this.moveThreadCount = builder.moveThreadCount;
Expand Down Expand Up @@ -128,8 +132,17 @@ public Random getRandom() {
// ************************************************************************

public Builder<Solution_> cloneBuilder() {
return new Builder<>(environmentMode, moveThreadCount, moveThreadBufferSize, threadFactoryClass,
nearbyDistanceMeterClass, random, initializingScoreTrend, solutionDescriptor, classInstanceCache)
return new Builder<Solution_>()
.withPreviewFeatureList(previewFeatureList)
.withEnvironmentMode(environmentMode)
.withMoveThreadCount(moveThreadCount)
.withMoveThreadBufferSize(moveThreadBufferSize)
.withThreadFactoryClass(threadFactoryClass)
.withNearbyDistanceMeterClass(nearbyDistanceMeterClass)
.withRandom(random)
.withInitializingScoreTrend(initializingScoreTrend)
.withSolutionDescriptor(solutionDescriptor)
.withClassInstanceCache(classInstanceCache)
.withLogIndentation(logIndentation);
}

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

public void addEntityMimicRecorder(String id, EntityMimicRecorder<Solution_> mimicRecordingEntitySelector) {
EntityMimicRecorder<Solution_> put = entityMimicRecorderMap.put(id, mimicRecordingEntitySelector);
var put = entityMimicRecorderMap.put(id, mimicRecordingEntitySelector);
if (put != null) {
throw new IllegalStateException("Multiple " + EntityMimicRecorder.class.getSimpleName() + "s (usually "
+ EntitySelector.class.getSimpleName() + "s) have the same id (" + id + ").\n" +
"Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?");
throw new IllegalStateException(
"""
Multiple %ss (usually %ss) have the same id (%s).
Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?"""
.formatted(EntityMimicRecorder.class.getSimpleName(), EntitySelector.class.getSimpleName(), id));
}
}

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

public void addSubListMimicRecorder(String id, SubListMimicRecorder<Solution_> mimicRecordingSubListSelector) {
SubListMimicRecorder<Solution_> put = subListMimicRecorderMap.put(id, mimicRecordingSubListSelector);
var put = subListMimicRecorderMap.put(id, mimicRecordingSubListSelector);
if (put != null) {
throw new IllegalStateException("Multiple " + SubListMimicRecorder.class.getSimpleName() + "s (usually "
+ SubListSelector.class.getSimpleName() + "s) have the same id (" + id + ").\n" +
"Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?");
throw new IllegalStateException(
"""
Multiple %ss (usually %ss) have the same id (%s).
Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?"""
.formatted(SubListMimicRecorder.class.getSimpleName(), SubListSelector.class.getSimpleName(), id));
}
}

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

public void addValueMimicRecorder(String id, ValueMimicRecorder<Solution_> mimicRecordingValueSelector) {
ValueMimicRecorder<Solution_> put = valueMimicRecorderMap.put(id, mimicRecordingValueSelector);
var put = valueMimicRecorderMap.put(id, mimicRecordingValueSelector);
if (put != null) {
throw new IllegalStateException("Multiple " + ValueMimicRecorder.class.getSimpleName() + "s (usually "
+ ValueSelector.class.getSimpleName() + "s) have the same id (" + id + ").\n" +
"Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?");
throw new IllegalStateException(
"""
Multiple %ss (usually %ss) have the same id (%s).
Maybe specify a variable name for the mimicking selector in situations with multiple variables on the same entity?"""
.formatted(ValueMimicRecorder.class.getSimpleName(), ValueSelector.class.getSimpleName(), id));
}
}

Expand All @@ -198,20 +217,31 @@ public ThreadFactory buildThreadFactory(ChildThreadType childThreadType) {
}
}

public void ensurePreviewFeature(PreviewFeature previewFeature) {
if (previewFeatureList == null || !previewFeatureList.contains(previewFeature)) {
throw new IllegalStateException(
"""
The preview feature %s is not enabled.
Maybe add %s to <enablePreviewFeature> in your configuration file?"""
.formatted(previewFeature, previewFeature));
}
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + environmentMode + ")";
}

public static class Builder<Solution_> {

private final EnvironmentMode environmentMode;
private final Integer moveThreadCount;
private final Integer moveThreadBufferSize;
private final Class<? extends ThreadFactory> threadFactoryClass;
private final InitializingScoreTrend initializingScoreTrend;
private final SolutionDescriptor<Solution_> solutionDescriptor;
private final ClassInstanceCache classInstanceCache;
private Set<PreviewFeature> previewFeatureList;
private EnvironmentMode environmentMode;
private Integer moveThreadCount;
private Integer moveThreadBufferSize;
private Class<? extends ThreadFactory> threadFactoryClass;
private InitializingScoreTrend initializingScoreTrend;
private SolutionDescriptor<Solution_> solutionDescriptor;
private ClassInstanceCache classInstanceCache;

private String logIndentation = "";

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

private final Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass;
private final Random random;
private Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass;
private Random random;

public Builder<Solution_> withPreviewFeatureList(Set<PreviewFeature> previewFeatureList) {
this.previewFeatureList = previewFeatureList;
return this;
}

public Builder(EnvironmentMode environmentMode, Integer moveThreadCount, Integer moveThreadBufferSize,
Class<? extends ThreadFactory> threadFactoryClass,
Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass, Random random,
InitializingScoreTrend initializingScoreTrend, SolutionDescriptor<Solution_> solutionDescriptor,
ClassInstanceCache classInstanceCache) {
public Builder<Solution_> withEnvironmentMode(EnvironmentMode environmentMode) {
this.environmentMode = environmentMode;
return this;
}

public Builder<Solution_> withMoveThreadCount(Integer moveThreadCount) {
this.moveThreadCount = moveThreadCount;
return this;
}

public Builder<Solution_> withMoveThreadBufferSize(Integer moveThreadBufferSize) {
this.moveThreadBufferSize = moveThreadBufferSize;
return this;
}

public Builder<Solution_> withThreadFactoryClass(Class<? extends ThreadFactory> threadFactoryClass) {
this.threadFactoryClass = threadFactoryClass;
return this;
}

public Builder<Solution_>
withNearbyDistanceMeterClass(Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass) {
this.nearbyDistanceMeterClass = nearbyDistanceMeterClass;
return this;
}

public Builder<Solution_> withRandom(Random random) {
this.random = random;
return this;
}

public Builder<Solution_> withInitializingScoreTrend(InitializingScoreTrend initializingScoreTrend) {
this.initializingScoreTrend = initializingScoreTrend;
return this;
}

public Builder<Solution_> withSolutionDescriptor(SolutionDescriptor<Solution_> solutionDescriptor) {
this.solutionDescriptor = solutionDescriptor;
return this;
}

public Builder<Solution_> withClassInstanceCache(ClassInstanceCache classInstanceCache) {
this.classInstanceCache = classInstanceCache;
return this;
}

public Builder<Solution_> withLogIndentation(String logIndentation) {
Expand Down
Loading

0 comments on commit c6c30e9

Please sign in to comment.