Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
*/
public interface NearbyAutoConfigurationEnabled<Config_ extends MoveSelectorConfig<Config_>> {

/**
* @return true if it can enable the nearby setting for the given move configuration; otherwise, it returns false.
*/
boolean canEnableNearbyInMixedModels();

/**
* @return new instance with the Nearby Selection settings properly configured
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ public boolean hasNearbySelectionConfig() {
&& moveSelectorConfigList.stream().anyMatch(MoveSelectorConfig::hasNearbySelectionConfig);
}

@Override
public boolean canEnableNearbyInMixedModels() {
return false;
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + moveSelectorConfigList + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ public boolean hasNearbySelectionConfig() {
|| (valueSelectorConfig != null && valueSelectorConfig.hasNearbySelectionConfig());
}

@Override
public boolean canEnableNearbyInMixedModels() {
return false;
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + entitySelectorConfig + ", " + valueSelectorConfig + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ public boolean hasNearbySelectionConfig() {
|| (secondaryEntitySelectorConfig != null && secondaryEntitySelectorConfig.hasNearbySelectionConfig());
}

@Override
public boolean canEnableNearbyInMixedModels() {
return false;
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + entitySelectorConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ public boolean hasNearbySelectionConfig() {
|| (valueSelectorConfig != null && valueSelectorConfig.hasNearbySelectionConfig());
}

@Override
public boolean canEnableNearbyInMixedModels() {
return false;
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + entitySelectorConfig + ", " + valueSelectorConfig + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ public boolean hasNearbySelectionConfig() {
|| (destinationSelectorConfig != null && destinationSelectorConfig.hasNearbySelectionConfig());
}

@Override
public boolean canEnableNearbyInMixedModels() {
return true;
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + valueSelectorConfig + ", " + destinationSelectorConfig + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ public boolean hasNearbySelectionConfig() {
|| (secondaryValueSelectorConfig != null && secondaryValueSelectorConfig.hasNearbySelectionConfig());
}

@Override
public boolean canEnableNearbyInMixedModels() {
return true;
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + valueSelectorConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ public boolean hasNearbySelectionConfig() {
|| (valueSelectorConfig != null && valueSelectorConfig.hasNearbySelectionConfig());
}

@Override
public boolean canEnableNearbyInMixedModels() {
return true;
}

@Override
public String toString() {
return getClass().getSimpleName() + "()";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,6 @@ public static <Solution_> SolutionDescriptor<Solution_> buildSolutionDescriptor(
solutionDescriptor.constraintWeightSupplier.initialize(solutionDescriptor,
descriptorPolicy.getMemberAccessorFactory(), descriptorPolicy.getDomainAccessType());
}
// Temporally disabling the mixed model
Comment thread
zepfred marked this conversation as resolved.
if (solutionDescriptor.hasBothBasicAndListVariables()) {
throw new IllegalStateException("Combining list variable and basic variables is currently not supported.");
}
return solutionDescriptor;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ public HeuristicConfigPolicy<Solution_> createPhaseConfigPolicy() {
return cloneBuilder().build();
}

public HeuristicConfigPolicy<Solution_> copyConfigPolicyWithoutNearbySetting() {
return cloneBuilder()
.withNearbyDistanceMeterClass(null)
.build();
}

public HeuristicConfigPolicy<Solution_> createChildThreadConfigPolicy(ChildThreadType childThreadType) {
return cloneBuilder()
.withLogIndentation(logIndentation + " ")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,16 @@ private SubListSelector<Solution_> applyNearbySelection(HeuristicConfigPolicy<So
private EntityIndependentValueSelector<Solution_> buildEntityIndependentValueSelector(
HeuristicConfigPolicy<Solution_> configPolicy, EntityDescriptor<Solution_> entityDescriptor,
SelectionCacheType minimumCacheType, SelectionOrder inheritedSelectionOrder) {
ValueSelectorConfig valueSelectorConfig =
Objects.requireNonNullElseGet(config.getValueSelectorConfig(), ValueSelectorConfig::new);
ValueSelectorConfig valueSelectorConfig = config != null ? config.getValueSelectorConfig() : null;
if (valueSelectorConfig == null) {
valueSelectorConfig = new ValueSelectorConfig();
}
// Mixed models require that the variable name be set
if (configPolicy.getSolutionDescriptor().hasBothBasicAndListVariables()
&& valueSelectorConfig.getVariableName() == null) {
var variableName = entityDescriptor.getGenuineListVariableDescriptor().getVariableName();
valueSelectorConfig.setVariableName(variableName);
}
ValueSelector<Solution_> valueSelector = ValueSelectorFactory
.<Solution_> create(valueSelectorConfig)
.buildValueSelector(configPolicy, entityDescriptor, minimumCacheType, inheritedSelectionOrder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ protected MoveSelector<Solution_> buildBaseMoveSelector(HeuristicConfigPolicy<So
SelectionCacheType minimumCacheType, boolean randomSelection) {
var moveSelectorConfigList = new LinkedList<>(config.getMoveSelectorList());
if (configPolicy.getNearbyDistanceMeterClass() != null) {
var isMixedModel = configPolicy.getSolutionDescriptor().hasBothBasicAndListVariables();
for (var selectorConfig : config.getMoveSelectorList()) {
if (selectorConfig instanceof NearbyAutoConfigurationEnabled nearbySelectorConfig) {
if (selectorConfig.hasNearbySelectionConfig()) {
Expand All @@ -38,7 +39,12 @@ The selector configuration (%s) already includes the Nearby Selection setting, m
// We delay the autoconfiguration to the deepest UnionMoveSelectorConfig node in the tree
// to avoid duplicating configuration
// when there are nested unionMoveSelector configurations
if (selectorConfig instanceof UnionMoveSelectorConfig) {
var isUnionMoveSelectorConfig = selectorConfig instanceof UnionMoveSelectorConfig;
// When using a mixed model, we do not enable nearby for basic variables,
// as it applies only to list or chained variables.
// Chained variables are forbidden in mixed models.
var isNearbyDisabled = isMixedModel && !nearbySelectorConfig.canEnableNearbyInMixedModels();
if (isUnionMoveSelectorConfig || isNearbyDisabled) {
continue;
}
// Add a new configuration with Nearby Selection enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;

import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig;
import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig;
import ai.timefold.solver.core.config.exhaustivesearch.ExhaustiveSearchPhaseConfig;
import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig;
import ai.timefold.solver.core.config.partitionedsearch.PartitionedSearchPhaseConfig;
Expand Down Expand Up @@ -56,15 +57,28 @@ static <Solution_> List<Phase<Solution_>> buildPhases(List<PhaseConfig> phaseCon
+ "without a configured termination (" + previousPhaseConfig + ").");
}
}
var isConstructionPhase = ConstructionHeuristicPhaseConfig.class.isAssignableFrom(phaseConfig.getClass());
// We currently do not support nearby functionality for CH.
// Additionally, mixed models must not include any nearby settings for basic variables,
// as this may cause failures in certain cases,
// such as when defining multiple variables with a Cartesian product.
var entityPlacerConfig =
isConstructionPhase ? ((ConstructionHeuristicPhaseConfig) phaseConfig).getEntityPlacerConfig() : null;
var disableNearbySetting =
configPolicy.getNearbyDistanceMeterClass() != null && entityPlacerConfig != null
&& QueuedEntityPlacerConfig.class.isAssignableFrom(entityPlacerConfig.getClass())
&& configPolicy.getSolutionDescriptor().hasBothBasicAndListVariables();
var updatedConfigPolicy =
disableNearbySetting ? configPolicy.copyConfigPolicyWithoutNearbySetting() : configPolicy;
// The initialization phase can only be applied to construction heuristics or custom phases
var isConstructionOrCustomPhase = ConstructionHeuristicPhaseConfig.class.isAssignableFrom(phaseConfig.getClass())
|| CustomPhaseConfig.class.isAssignableFrom(phaseConfig.getClass());
var isConstructionOrCustomPhase =
isConstructionPhase || CustomPhaseConfig.class.isAssignableFrom(phaseConfig.getClass());
// The next phase must be a local search
var isNextPhaseLocalSearch = phaseIndex + 1 < phaseConfigList.size()
&& LocalSearchPhaseConfig.class.isAssignableFrom(phaseConfigList.get(phaseIndex + 1).getClass());
PhaseFactory<Solution_> phaseFactory = PhaseFactory.create(phaseConfig);
var phase = phaseFactory.buildPhase(phaseIndex,
!isPhaseSelected && isConstructionOrCustomPhase && isNextPhaseLocalSearch, configPolicy,
!isPhaseSelected && isConstructionOrCustomPhase && isNextPhaseLocalSearch, updatedConfigPolicy,
bestSolutionRecaller, termination);
// Ensure only one initialization phase is set
if (!isPhaseSelected && isConstructionOrCustomPhase && isNextPhaseLocalSearch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
import ai.timefold.solver.core.testutil.AbstractMeterTest;
import ai.timefold.solver.core.testutil.PlannerTestUtils;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import io.micrometer.core.instrument.Metrics;
Expand Down Expand Up @@ -349,7 +348,6 @@ void constructionHeuristicAllocateToValueFromQueue() {
.filter(e -> e.getValue() == null)).isEmpty();
}

@Disabled("The mixed model is currently unavailable for general use")
@Test
void failMixedModelDefaultConfiguration() {
var solverConfig = PlannerTestUtils
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static ai.timefold.solver.core.testutil.PlannerAssert.assertAllCodesOfMoveSelector;
import static ai.timefold.solver.core.testutil.PlannerAssert.verifyPhaseLifecycle;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

Expand All @@ -11,6 +12,7 @@
import java.util.Map;
import java.util.Random;

import ai.timefold.solver.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig;
import ai.timefold.solver.core.impl.heuristic.move.DummyMove;
import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
Expand Down Expand Up @@ -211,4 +213,9 @@ void emptyBiasedRandomSelection() {
verifyPhaseLifecycle(childMoveSelectorList.get(1), 1, 1, 1);
}

@Test
void testEnableNearbyMixedModel() {
var moveSelectorConfig = new UnionMoveSelectorConfig();
assertThat(moveSelectorConfig.canEnableNearbyInMixedModels()).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig;
import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory;
import ai.timefold.solver.core.testdomain.TestdataEntity;
import ai.timefold.solver.core.testdomain.TestdataSolution;
Expand Down Expand Up @@ -168,4 +169,9 @@ void toStringTestMultiVar() {
assertThat(new ChangeMove<>(variableDescriptor, c, v3)).hasToString("c {v4 -> v3}");
}

@Test
void testEnableNearbyMixedModel() {
var moveSelectorConfig = new ChangeMoveSelectorConfig();
assertThat(moveSelectorConfig.canEnableNearbyInMixedModels()).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.move.generic.SwapMoveSelectorConfig;
import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory;
import ai.timefold.solver.core.testdomain.TestdataEntity;
import ai.timefold.solver.core.testdomain.TestdataSolution;
Expand Down Expand Up @@ -259,4 +260,9 @@ void toStringTestMultiVar() {
assertThat(new SwapMove<>(variableDescriptorList, c, b)).hasToString("c {v2, v4, w2} <-> b {v1, v3, w1}");
}

@Test
void testEnableNearbyMixedModel() {
var moveSelectorConfig = new SwapMoveSelectorConfig();
assertThat(moveSelectorConfig.canEnableNearbyInMixedModels()).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.TailChainSwapMoveSelectorConfig;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.domain.variable.anchor.AnchorVariableDemand;
import ai.timefold.solver.core.impl.domain.variable.anchor.AnchorVariableSupply;
Expand Down Expand Up @@ -292,4 +293,10 @@ void extractPlanningEntitiesWithRightEntityNull() {
move.doMoveOnGenuineVariables(innerScoreDirector);
assertThat(move.getPlanningEntities()).doesNotContainNull();
}

@Test
void testEnableNearbyMixedModel() {
var moveSelectorConfig = new TailChainSwapMoveSelectorConfig();
assertThat(moveSelectorConfig.canEnableNearbyInMixedModels()).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.stream.Stream;

import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.score.director.InnerScoreDirector;
import ai.timefold.solver.core.testdomain.TestdataObject;
Expand Down Expand Up @@ -188,4 +189,10 @@ void toStringTest() {
assertThat(new ListChangeMove<>(variableDescriptor, e1, 1, e1, 0)).hasToString("2 {e1[1] -> e1[0]}");
assertThat(new ListChangeMove<>(variableDescriptor, e1, 0, e2, 1)).hasToString("1 {e1[0] -> e2[1]}");
}

@Test
void testEnableNearbyMixedModel() {
var moveSelectorConfig = new ListChangeMoveSelectorConfig();
assertThat(moveSelectorConfig.canEnableNearbyInMixedModels()).isTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.mockito.Mockito.verify;

import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.move.director.MoveDirector;
import ai.timefold.solver.core.impl.score.director.InnerScoreDirector;
Expand Down Expand Up @@ -137,4 +138,10 @@ void toStringTest() {
assertThat(new ListSwapMove<>(variableDescriptor, e1, 0, e1, 1)).hasToString("1 {e1[0]} <-> 2 {e1[1]}");
assertThat(new ListSwapMove<>(variableDescriptor, e1, 1, e2, 0)).hasToString("2 {e1[1]} <-> 3 {e2[0]}");
}

@Test
void testEnableNearbyMixedModel() {
var moveSelectorConfig = new ListSwapMoveSelectorConfig();
assertThat(moveSelectorConfig.canEnableNearbyInMixedModels()).isTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import ai.timefold.solver.core.testdomain.list.TestdataListSolution;
import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySolution;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
Expand All @@ -44,7 +43,6 @@ void buildMoveSelector() {
assertThat(selector.isSelectReversingMoveToo()).isTrue();
}

@Disabled("The mixed model is currently unavailable for general use")
@Test
void buildMoveSelectorMultiEntity() {
var config = new SubListChangeMoveSelectorConfig();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import ai.timefold.solver.core.testdomain.list.TestdataListSolution;
import ai.timefold.solver.core.testdomain.mixed.multientity.TestdataMixedMultiEntitySolution;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
Expand All @@ -41,7 +40,6 @@ void buildBaseMoveSelector() {
assertThat(selector.isSelectReversingMoveToo()).isTrue();
}

@Disabled("The mixed model is currently unavailable for general use")
@Test
void buildMoveSelectorMultiEntity() {
var config = new SubListSwapMoveSelectorConfig();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.List;
import java.util.function.Function;

import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.kopt.KOptListMoveSelectorConfig;
import ai.timefold.solver.core.impl.domain.variable.ListVariableStateDemand;
import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
Expand Down Expand Up @@ -548,6 +549,12 @@ void testIsFeasible() {
assertThat(kOptListMove.isMoveDoable(scoreDirector)).isFalse();
}

@Test
void testEnableNearbyMixedModel() {
var moveSelectorConfig = new KOptListMoveSelectorConfig();
assertThat(moveSelectorConfig.canEnableNearbyInMixedModels()).isTrue();
}

/**
* Create a sequential or non-sequential k-opt from the supplied pairs of undirected removed and added edges.
*
Expand Down
Loading
Loading