Skip to content

Commit

Permalink
refactor: Multi-threaded solving and nearby selection become services (
Browse files Browse the repository at this point in the history
…#53)

Signed-off-by: Lukáš Petrovický <[email protected]>
  • Loading branch information
triceo authored Jun 1, 2023
1 parent 5d15619 commit f23cedd
Show file tree
Hide file tree
Showing 99 changed files with 985 additions and 671 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ai.timefold.solver.core;

import java.util.Iterator;
import java.util.ServiceLoader;

import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider;
import ai.timefold.solver.core.impl.constructionheuristic.decider.forager.ConstructionHeuristicForager;
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
import ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider;
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.Acceptor;
import ai.timefold.solver.core.impl.localsearch.decider.forager.LocalSearchForager;
import ai.timefold.solver.core.impl.solver.termination.Termination;

public interface MultithreadedSolvingEnterpriseService {

static MultithreadedSolvingEnterpriseService load(Integer moveThreadCount) {
ServiceLoader<MultithreadedSolvingEnterpriseService> serviceLoader =
ServiceLoader.load(MultithreadedSolvingEnterpriseService.class);
Iterator<MultithreadedSolvingEnterpriseService> iterator = serviceLoader.iterator();
if (!iterator.hasNext()) {
throw new IllegalStateException(
"Multi-threaded solving requested with moveThreadCount (" + moveThreadCount
+ ") but Timefold Enterprise not found on classpath.\n" +
"Either add the ai.timefold.solver:timefold-solver-enterprise dependency, " +
"or remove moveThreadCount from solver configuration.\n" +
"Note: Timefold Enterprise is a commercial product.");
}
return iterator.next();
}

<Solution_> ConstructionHeuristicDecider<Solution_> buildConstructionHeuristic(int moveThreadCount,
Termination<Solution_> termination, ConstructionHeuristicForager<Solution_> forager,
EnvironmentMode environmentMode, HeuristicConfigPolicy<Solution_> configPolicy);

<Solution_> LocalSearchDecider<Solution_> buildLocalSearch(int moveThreadCount, Termination<Solution_> termination,
MoveSelector<Solution_> moveSelector, Acceptor<Solution_> acceptor, LocalSearchForager<Solution_> forager,
EnvironmentMode environmentMode, HeuristicConfigPolicy<Solution_> configPolicy);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ai.timefold.solver.core;

import java.util.Iterator;
import java.util.ServiceLoader;

import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder;
import ai.timefold.solver.core.config.heuristic.selector.common.nearby.NearbySelectionConfig;
import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.list.DestinationSelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.list.SubListSelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector;
import ai.timefold.solver.core.impl.heuristic.selector.list.ElementDestinationSelector;
import ai.timefold.solver.core.impl.heuristic.selector.list.RandomSubListSelector;
import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector;

public interface NearbySelectionEnterpriseService {

static NearbySelectionEnterpriseService load() {
ServiceLoader<NearbySelectionEnterpriseService> serviceLoader =
ServiceLoader.load(NearbySelectionEnterpriseService.class);
Iterator<NearbySelectionEnterpriseService> iterator = serviceLoader.iterator();
if (!iterator.hasNext()) {
throw new IllegalStateException(
"Nearby selection requested but Timefold Enterprise not found on classpath.\n" +
"Either add the ai.timefold.solver:timefold-solver-enterprise dependency, " +
"or remove nearby selection from solver configuration.\n" +
"Note: Timefold Enterprise is a commercial product.");
}
return iterator.next();
}

<Solution_> EntitySelector<Solution_> applyNearbySelection(EntitySelectorConfig entitySelectorConfig,
HeuristicConfigPolicy<Solution_> configPolicy, NearbySelectionConfig nearbySelectionConfig,
SelectionCacheType minimumCacheType, SelectionOrder resolvedSelectionOrder,
EntitySelector<Solution_> entitySelector);

<Solution_> ValueSelector<Solution_> applyNearbySelection(ValueSelectorConfig valueSelectorConfig,
HeuristicConfigPolicy<Solution_> configPolicy, EntityDescriptor<Solution_> entityDescriptor,
SelectionCacheType minimumCacheType, SelectionOrder resolvedSelectionOrder, ValueSelector<Solution_> valueSelector);

<Solution_> SubListSelector<Solution_> applyNearbySelection(SubListSelectorConfig subListSelectorConfig,
HeuristicConfigPolicy<Solution_> configPolicy, SelectionCacheType minimumCacheType,
SelectionOrder resolvedSelectionOrder, RandomSubListSelector<Solution_> subListSelector);

<Solution_> DestinationSelector<Solution_> applyNearbySelection(DestinationSelectorConfig destinationSelectorConfig,
HeuristicConfigPolicy<Solution_> configPolicy, SelectionCacheType minimumCacheType,
SelectionOrder resolvedSelectionOrder, ElementDestinationSelector<Solution_> destinationSelector);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ai.timefold.solver.core;

import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.function.BiFunction;

import ai.timefold.solver.core.config.partitionedsearch.PartitionedSearchPhaseConfig;
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.partitionedsearch.PartitionedSearchPhase;
import ai.timefold.solver.core.impl.solver.termination.Termination;

public interface PartitionedSearchEnterpriseService {

static PartitionedSearchEnterpriseService load() {
ServiceLoader<PartitionedSearchEnterpriseService> serviceLoader =
ServiceLoader.load(PartitionedSearchEnterpriseService.class);
Iterator<PartitionedSearchEnterpriseService> iterator = serviceLoader.iterator();
if (!iterator.hasNext()) {
throw new IllegalStateException(
"Partitioned search requested but Timefold Enterprise not found on classpath.\n" +
"Either add the ai.timefold.solver:timefold-solver-enterprise dependency, " +
"or remove partitioned search from solver configuration.\n" +
"Note: Timefold Enterprise is a commercial product.");
}
return iterator.next();
}

<Solution_> PartitionedSearchPhase<Solution_> buildPartitionedSearch(int phaseIndex,
PartitionedSearchPhaseConfig phaseConfig, HeuristicConfigPolicy<Solution_> solverConfigPolicy,
Termination<Solution_> solverTermination,
BiFunction<HeuristicConfigPolicy<Solution_>, Termination<Solution_>, Termination<Solution_>> phaseTerminationFunction);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadFactory;

import ai.timefold.solver.core.MultithreadedSolvingEnterpriseService;
import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig;
import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType;
import ai.timefold.solver.core.config.constructionheuristic.decider.forager.ConstructionHeuristicForagerConfig;
Expand All @@ -24,7 +24,6 @@
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider;
import ai.timefold.solver.core.impl.constructionheuristic.decider.MultiThreadedConstructionHeuristicDecider;
import ai.timefold.solver.core.impl.constructionheuristic.decider.forager.ConstructionHeuristicForager;
import ai.timefold.solver.core.impl.constructionheuristic.decider.forager.ConstructionHeuristicForagerFactory;
import ai.timefold.solver.core.impl.constructionheuristic.placer.EntityPlacer;
Expand All @@ -38,7 +37,6 @@
import ai.timefold.solver.core.impl.phase.AbstractPhaseFactory;
import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller;
import ai.timefold.solver.core.impl.solver.termination.Termination;
import ai.timefold.solver.core.impl.solver.thread.ChildThreadType;

public class DefaultConstructionHeuristicPhaseFactory<Solution_>
extends AbstractPhaseFactory<Solution_, ConstructionHeuristicPhaseConfig> {
Expand Down Expand Up @@ -181,26 +179,8 @@ private ConstructionHeuristicDecider<Solution_> buildDecider(HeuristicConfigPoli
if (moveThreadCount == null) {
decider = new ConstructionHeuristicDecider<>(configPolicy.getLogIndentation(), termination, forager);
} else {
Integer moveThreadBufferSize = configPolicy.getMoveThreadBufferSize();
if (moveThreadBufferSize == null) {
// TODO Verify this is a good default by more meticulous benchmarking on multiple machines and JDK's
// If it's too low, move threads will need to wait on the buffer, which hurts performance
// If it's too high, more moves are selected that aren't foraged
moveThreadBufferSize = 10;
}
ThreadFactory threadFactory = configPolicy.buildThreadFactory(ChildThreadType.MOVE_THREAD);
int selectedMoveBufferSize = moveThreadCount * moveThreadBufferSize;
MultiThreadedConstructionHeuristicDecider<Solution_> multiThreadedDecider =
new MultiThreadedConstructionHeuristicDecider<>(configPolicy.getLogIndentation(), termination, forager,
threadFactory, moveThreadCount, selectedMoveBufferSize);
if (environmentMode.isNonIntrusiveFullAsserted()) {
multiThreadedDecider.setAssertStepScoreFromScratch(true);
}
if (environmentMode.isIntrusiveFastAsserted()) {
multiThreadedDecider.setAssertExpectedStepScore(true);
multiThreadedDecider.setAssertShadowVariablesAreNotStaleAfterStep(true);
}
decider = multiThreadedDecider;
decider = MultithreadedSolvingEnterpriseService.load(moveThreadCount)
.buildConstructionHeuristic(moveThreadCount, termination, forager, environmentMode, configPolicy);
}
if (environmentMode.isNonIntrusiveFullAsserted()) {
decider.setAssertMoveScoreFromScratch(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Objects;
import java.util.stream.Stream;

import ai.timefold.solver.core.NearbySelectionEnterpriseService;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder;
Expand All @@ -22,9 +23,6 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyRandom;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyRandomFactory;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.CachingEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ProbabilityEntitySelector;
Expand All @@ -34,7 +32,6 @@
import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.EntityMimicRecorder;
import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicRecordingEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicReplayingEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.nearby.NearEntityNearbyEntitySelector;
import ai.timefold.solver.core.impl.solver.ClassInstanceCache;

public class EntitySelectorFactory<Solution_> extends AbstractSelectorFactory<Solution_, EntitySelectorConfig> {
Expand Down Expand Up @@ -178,22 +175,9 @@ private boolean hasFiltering(EntityDescriptor<Solution_> entityDescriptor) {
private EntitySelector<Solution_> applyNearbySelection(HeuristicConfigPolicy<Solution_> configPolicy,
NearbySelectionConfig nearbySelectionConfig, SelectionCacheType minimumCacheType,
SelectionOrder resolvedSelectionOrder, EntitySelector<Solution_> entitySelector) {
boolean randomSelection = resolvedSelectionOrder.toRandomSelectionBoolean();
if (nearbySelectionConfig.getOriginEntitySelectorConfig() == null) {
throw new IllegalArgumentException("The entitySelector (" + config
+ ")'s nearbySelectionConfig (" + nearbySelectionConfig + ") requires an originEntitySelector.");
}
EntitySelectorFactory<Solution_> entitySelectorFactory =
EntitySelectorFactory.create(nearbySelectionConfig.getOriginEntitySelectorConfig());
EntitySelector<Solution_> originEntitySelector =
entitySelectorFactory.buildEntitySelector(configPolicy, minimumCacheType, resolvedSelectionOrder);
NearbyDistanceMeter nearbyDistanceMeter =
configPolicy.getClassInstanceCache().newInstance(nearbySelectionConfig, "nearbyDistanceMeterClass",
nearbySelectionConfig.getNearbyDistanceMeterClass());
// TODO Check nearbyDistanceMeterClass.getGenericInterfaces() to confirm generic type S is an entityClass
NearbyRandom nearbyRandom = NearbyRandomFactory.create(nearbySelectionConfig).buildNearbyRandom(randomSelection);
return new NearEntityNearbyEntitySelector<>(entitySelector, originEntitySelector, nearbyDistanceMeter,
nearbyRandom, randomSelection);
return NearbySelectionEnterpriseService.load()
.applyNearbySelection(config, configPolicy, nearbySelectionConfig, minimumCacheType,
resolvedSelectionOrder, entitySelector);
}

private EntitySelector<Solution_> applyFiltering(EntitySelector<Solution_> entitySelector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Objects;

import ai.timefold.solver.core.NearbySelectionEnterpriseService;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder;
Expand All @@ -10,13 +11,8 @@
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyRandom;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyRandomFactory;
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactory;
import ai.timefold.solver.core.impl.heuristic.selector.list.nearby.NearSubListNearbyDestinationSelector;
import ai.timefold.solver.core.impl.heuristic.selector.list.nearby.NearValueNearbyDestinationSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.EntityIndependentValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory;
Expand Down Expand Up @@ -93,52 +89,14 @@ private EntityIndependentValueSelector<Solution_> buildEntityIndependentValueSel
return (EntityIndependentValueSelector<Solution_>) valueSelector;
}

private DestinationSelector<Solution_> applyNearbySelection(
HeuristicConfigPolicy<Solution_> configPolicy,
SelectionCacheType minimumCacheType,
SelectionOrder resolvedSelectionOrder,
private DestinationSelector<Solution_> applyNearbySelection(HeuristicConfigPolicy<Solution_> configPolicy,
SelectionCacheType minimumCacheType, SelectionOrder resolvedSelectionOrder,
ElementDestinationSelector<Solution_> destinationSelector) {
NearbySelectionConfig nearbySelectionConfig = config.getNearbySelectionConfig();
if (nearbySelectionConfig == null) {
return destinationSelector;
}

nearbySelectionConfig.validateNearby(minimumCacheType, resolvedSelectionOrder);

boolean randomSelection = resolvedSelectionOrder.toRandomSelectionBoolean();

NearbyDistanceMeter<?, ?> nearbyDistanceMeter =
configPolicy.getClassInstanceCache().newInstance(nearbySelectionConfig,
"nearbyDistanceMeterClass", nearbySelectionConfig.getNearbyDistanceMeterClass());
// TODO Check nearbyDistanceMeterClass.getGenericInterfaces() to confirm generic type S is an entityClass
NearbyRandom nearbyRandom = NearbyRandomFactory.create(nearbySelectionConfig).buildNearbyRandom(randomSelection);

if (nearbySelectionConfig.getOriginValueSelectorConfig() != null) {
ValueSelector<Solution_> originValueSelector = ValueSelectorFactory
.<Solution_> create(nearbySelectionConfig.getOriginValueSelectorConfig())
.buildValueSelector(configPolicy, destinationSelector.getEntityDescriptor(), minimumCacheType,
resolvedSelectionOrder);
return new NearValueNearbyDestinationSelector<>(
destinationSelector,
((EntityIndependentValueSelector<Solution_>) originValueSelector),
nearbyDistanceMeter,
nearbyRandom,
randomSelection);
} else if (nearbySelectionConfig.getOriginSubListSelectorConfig() != null) {
SubListSelector<Solution_> subListSelector = SubListSelectorFactory
.<Solution_> create(nearbySelectionConfig.getOriginSubListSelectorConfig())
// Entity selector not needed for replaying selector.
.buildSubListSelector(configPolicy, null, minimumCacheType, resolvedSelectionOrder);
return new NearSubListNearbyDestinationSelector<>(
destinationSelector,
subListSelector,
nearbyDistanceMeter,
nearbyRandom,
randomSelection);
} else {
throw new IllegalArgumentException("The destinationSelector (" + config
+ ")'s nearbySelectionConfig (" + nearbySelectionConfig
+ ") requires an originSubListSelector or an originValueSelector.");
}
return NearbySelectionEnterpriseService.load()
.applyNearbySelection(config, configPolicy, minimumCacheType, resolvedSelectionOrder, destinationSelector);
}
}
Loading

0 comments on commit f23cedd

Please sign in to comment.