Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Diversified Late Acceptance approach #1253

Merged
merged 7 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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="enablePreviewFeatureList" nillable="true" type="tns:previewFeature"/>
zepfred marked this conversation as resolved.
Show resolved Hide resolved


<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": "{\"enablePreviewFeatureList\", \"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 @@ -10,6 +10,7 @@
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -60,6 +61,7 @@
*/
@XmlRootElement(name = SolverConfig.XML_ELEMENT_NAME)
@XmlType(name = SolverConfig.XML_TYPE_NAME, propOrder = {
"enablePreviewFeatureList",
zepfred marked this conversation as resolved.
Show resolved Hide resolved
"environmentMode",
"daemon",
"randomType",
Expand Down Expand Up @@ -210,6 +212,7 @@ 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

protected List<PreviewFeature> enablePreviewFeatureList = null;
zepfred marked this conversation as resolved.
Show resolved Hide resolved
protected EnvironmentMode environmentMode = null;
protected Boolean daemon = null;
protected RandomType randomType = null;
Expand Down Expand Up @@ -284,6 +287,14 @@ public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}

public @Nullable List<PreviewFeature> getEnablePreviewFeatureList() {
return enablePreviewFeatureList;
}

public void setEnablePreviewFeatureList(@Nullable List<PreviewFeature> enablePreviewFeatureList) {
this.enablePreviewFeatureList = enablePreviewFeatureList;
}

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

public @NonNull SolverConfig withPreviewFeature(@NonNull PreviewFeature previewFeature) {
zepfred marked this conversation as resolved.
Show resolved Hide resolved
if (enablePreviewFeatureList == null) {
enablePreviewFeatureList = new ArrayList<>();
}
enablePreviewFeatureList.add(previewFeature);
return this;
}

public @NonNull SolverConfig withEnvironmentMode(@NonNull EnvironmentMode environmentMode) {
this.environmentMode = environmentMode;
return this;
Expand Down Expand Up @@ -636,10 +655,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 +667,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());
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
@@ -1,13 +1,15 @@
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.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 List<PreviewFeature> previewFeatureList;
zepfred marked this conversation as resolved.
Show resolved Hide resolved
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,7 +132,7 @@ public Random getRandom() {
// ************************************************************************

public Builder<Solution_> cloneBuilder() {
return new Builder<>(environmentMode, moveThreadCount, moveThreadBufferSize, threadFactoryClass,
return new Builder<>(previewFeatureList, environmentMode, moveThreadCount, moveThreadBufferSize, threadFactoryClass,
nearbyDistanceMeterClass, random, initializingScoreTrend, solutionDescriptor, classInstanceCache)
.withLogIndentation(logIndentation);
}
Expand All @@ -148,11 +152,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 +167,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 +182,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,13 +208,24 @@ 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 <enablePreviewFeatureList> in your configuration file?"""
zepfred marked this conversation as resolved.
Show resolved Hide resolved
.formatted(previewFeature, previewFeature));
}
}

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

public static class Builder<Solution_> {

private final List<PreviewFeature> previewFeatureList;
private final EnvironmentMode environmentMode;
private final Integer moveThreadCount;
private final Integer moveThreadBufferSize;
Expand All @@ -225,11 +246,12 @@ public static class Builder<Solution_> {
private final Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass;
private final Random random;

public Builder(EnvironmentMode environmentMode, Integer moveThreadCount, Integer moveThreadBufferSize,
Class<? extends ThreadFactory> threadFactoryClass,
public Builder(List<PreviewFeature> previewFeatureList, EnvironmentMode environmentMode, Integer moveThreadCount,
Integer moveThreadBufferSize, Class<? extends ThreadFactory> threadFactoryClass,
Class<? extends NearbyDistanceMeter<?, ?>> nearbyDistanceMeterClass, Random random,
InitializingScoreTrend initializingScoreTrend, SolutionDescriptor<Solution_> solutionDescriptor,
ClassInstanceCache classInstanceCache) {
this.previewFeatureList = previewFeatureList;
this.environmentMode = environmentMode;
this.moveThreadCount = moveThreadCount;
this.moveThreadBufferSize = moveThreadBufferSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import ai.timefold.solver.core.config.localsearch.decider.acceptor.AcceptorType;
import ai.timefold.solver.core.config.localsearch.decider.acceptor.LocalSearchAcceptorConfig;
import ai.timefold.solver.core.config.localsearch.decider.acceptor.stepcountinghillclimbing.StepCountingHillClimbingType;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.greatdeluge.GreatDelugeAcceptor;
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.hillclimbing.HillClimbingAcceptor;
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.lateacceptance.DiversifiedLateAcceptanceAcceptor;
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.lateacceptance.LateAcceptanceAcceptor;
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.simulatedannealing.SimulatedAnnealingAcceptor;
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.stepcountinghillclimbing.StepCountingHillClimbingAcceptor;
Expand Down Expand Up @@ -45,6 +47,7 @@ public Acceptor<Solution_> buildAcceptor(HeuristicConfigPolicy<Solution_> config
buildMoveTabuAcceptor(configPolicy),
buildSimulatedAnnealingAcceptor(configPolicy),
buildLateAcceptanceAcceptor(),
buildDiversifiedLateAcceptanceAcceptor(configPolicy),
buildGreatDelugeAcceptor(configPolicy))
.filter(Optional::isPresent)
.map(Optional::get)
Expand Down Expand Up @@ -214,14 +217,26 @@ private Optional<MoveTabuAcceptor<Solution_>> buildMoveTabuAcceptor(HeuristicCon

private Optional<LateAcceptanceAcceptor<Solution_>> buildLateAcceptanceAcceptor() {
if (acceptorTypeListsContainsAcceptorType(AcceptorType.LATE_ACCEPTANCE)
|| acceptorConfig.getLateAcceptanceSize() != null) {
|| (!acceptorTypeListsContainsAcceptorType(AcceptorType.DIVERSIFIED_LATE_ACCEPTANCE)
&& acceptorConfig.getLateAcceptanceSize() != null)) {
var acceptor = new LateAcceptanceAcceptor<Solution_>();
acceptor.setLateAcceptanceSize(Objects.requireNonNullElse(acceptorConfig.getLateAcceptanceSize(), 400));
return Optional.of(acceptor);
}
return Optional.empty();
}

private Optional<LateAcceptanceAcceptor<Solution_>>
buildDiversifiedLateAcceptanceAcceptor(HeuristicConfigPolicy<Solution_> configPolicy) {
if (acceptorTypeListsContainsAcceptorType(AcceptorType.DIVERSIFIED_LATE_ACCEPTANCE)) {
configPolicy.ensurePreviewFeature(PreviewFeature.DIVERSIFIED_LATE_ACCEPTANCE);
var acceptor = new DiversifiedLateAcceptanceAcceptor<Solution_>();
acceptor.setLateAcceptanceSize(Objects.requireNonNullElse(acceptorConfig.getLateAcceptanceSize(), 5));
return Optional.of(acceptor);
}
return Optional.empty();
}

private Optional<GreatDelugeAcceptor<Solution_>> buildGreatDelugeAcceptor(HeuristicConfigPolicy<Solution_> configPolicy) {
if (acceptorTypeListsContainsAcceptorType(AcceptorType.GREAT_DELUGE)
|| acceptorConfig.getGreatDelugeWaterLevelIncrementScore() != null
Expand Down
Loading
Loading