Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -22,72 +22,98 @@
@NullMarked
public final class ReachableValues {

private final Map<Object, Set<Object>> valueToEntityMap;
private final Map<Object, Set<Object>> valueToValueMap;
private final Map<Object, List<Object>> randomAccessValueToEntityMap;
private final Map<Object, List<Object>> randomAccessValueToValueMap;
private final Map<Object, ReachableItemValue> values;
private final @Nullable Class<?> valueClass;
private final boolean acceptsNullValue;
private @Nullable ReachableItemValue cachedObject;

public ReachableValues(Map<Object, Set<Object>> valueToEntityMap, Map<Object, Set<Object>> valueToValueMap) {
this.valueToEntityMap = valueToEntityMap;
this.randomAccessValueToEntityMap = new IdentityHashMap<>(this.valueToEntityMap.size());
this.valueToValueMap = valueToValueMap;
this.randomAccessValueToValueMap = new IdentityHashMap<>(this.valueToValueMap.size());
var first = valueToEntityMap.entrySet().stream().findFirst();
this.valueClass = first.<Class<?>> map(entry -> entry.getKey().getClass()).orElse(null);
public ReachableValues(Map<Object, ReachableItemValue> values, boolean acceptsNullValue) {
this.values = values;
this.acceptsNullValue = acceptsNullValue;
var firstValue = values.entrySet().stream().findFirst();
this.valueClass = firstValue.<Class<?>> map(entry -> entry.getKey().getClass()).orElse(null);
}

/**
* @return all reachable values for the given value.
*/
public @Nullable Set<Object> extractEntities(Object value) {
return valueToEntityMap.get(value);
}

/**
* @return all reachable entities for the given value.
*/
public @Nullable Set<Object> extractValues(Object value) {
return valueToValueMap.get(value);
private @Nullable ReachableItemValue fetchItemValue(Object value) {
if (cachedObject == null || value != cachedObject.value) {
cachedObject = values.get(value);
}
return cachedObject;
}

public List<Object> extractEntitiesAsList(Object value) {
var result = randomAccessValueToEntityMap.get(value);
if (result == null) {
var entitySet = this.valueToEntityMap.get(value);
if (entitySet != null) {
result = new ArrayList<>(entitySet);
} else {
result = Collections.emptyList();
}
randomAccessValueToEntityMap.put(value, result);
var itemValue = fetchItemValue(value);
if (itemValue == null) {
return Collections.emptyList();
}
return result;
return itemValue.randomAccessEntityList;
}

public List<Object> extractValuesAsList(Object value) {
var result = randomAccessValueToValueMap.get(value);
if (result == null) {
var valueSet = this.valueToValueMap.get(value);
if (valueSet != null) {
result = new ArrayList<>(valueSet);
} else {
result = Collections.emptyList();
}
randomAccessValueToValueMap.put(value, result);
var itemValue = fetchItemValue(value);
if (itemValue == null) {
return Collections.emptyList();
}
return result;
return itemValue.randomAccessValueList;
}

public int getSize() {
return valueToEntityMap.size();
return values.size();
}

public boolean isEntityReachable(Object origin, @Nullable Object entity) {
if (entity == null) {
return true;
}
var originItemValue = fetchItemValue(Objects.requireNonNull(origin));
if (originItemValue == null) {
return false;
}
return originItemValue.entitySet.contains(entity);
}

public boolean isValidValueClass(Object value) {
if (valueToEntityMap.isEmpty()) {
public boolean isValueReachable(Object origin, @Nullable Object otherValue) {
var originItemValue = fetchItemValue(Objects.requireNonNull(origin));
if (originItemValue == null) {
return false;
}
return Objects.requireNonNull(value).getClass().equals(valueClass);
if (otherValue == null) {
return acceptsNullValue;
}
return originItemValue.valueSet.contains(Objects.requireNonNull(otherValue));
}

public boolean matchesValueClass(Object value) {
return valueClass != null && valueClass.isAssignableFrom(Objects.requireNonNull(value).getClass());
}

@NullMarked
public static final class ReachableItemValue {
private final Object value;
private final Set<Object> entitySet;
private final Set<Object> valueSet;
private final List<Object> randomAccessEntityList;
private final List<Object> randomAccessValueList;

public ReachableItemValue(Object value, int entityListSize, int valueListSize) {
this.value = value;
this.entitySet = new LinkedHashSet<>(entityListSize);
this.randomAccessEntityList = new ArrayList<>(entityListSize);
this.valueSet = new LinkedHashSet<>(valueListSize);
this.randomAccessValueList = new ArrayList<>(valueListSize);
}

public void addEntity(Object entity) {
if (entitySet.add(entity)) {
randomAccessEntityList.add(entity);
}
}

public void addValue(Object value) {
if (valueSet.add(value)) {
randomAccessValueList.add(value);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ai.timefold.solver.core.impl.heuristic.selector.common;

public record ValueRangeRecorderId(String recorderId, boolean basicVariable) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,14 @@ public AbstractOriginalSwapIterator(ListIterable<SubSelection_> leftSubSelector,

@Override
protected Move_ createUpcomingSelection() {
if (!rightSubSelectionIterator.hasNext()) {
while (!rightSubSelectionIterator.hasNext()) {
if (!leftSubSelectionIterator.hasNext()) {
return noUpcomingSelection();
}
leftSubSelection = leftSubSelectionIterator.next();

if (!leftEqualsRight) {
rightSubSelectionIterator = rightSubSelector.listIterator();
if (!rightSubSelectionIterator.hasNext()) {
return noUpcomingSelection();
}
} else {
// Select A-B, A-C, B-C. Do not select B-A, C-A, C-B. Do not select A-A, B-B, C-C.
if (!leftSubSelectionIterator.hasNext()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@
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.ValueRangeRecorderId;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory;
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.entity.decorator.CachingEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityValueRangeSelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ProbabilityEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.SelectedCountLimitEntitySelector;
import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ShufflingEntitySelector;
Expand Down Expand Up @@ -79,12 +81,13 @@ public EntitySelector<Solution_> buildEntitySelector(HeuristicConfigPolicy<Solut
* then it should be at least this {@link SelectionCacheType} because an ancestor already uses such caching
* and less would be pointless.
* @param inheritedSelectionOrder never null
* @param entityValueRangeRecorderId the recorder id to be used to create a replaying selector when enabling entity value
* @param valueRangeRecorderId the recorder id to be used to create a replaying selector when enabling entity value
* range
* @return never null
*/
public EntitySelector<Solution_> buildEntitySelector(HeuristicConfigPolicy<Solution_> configPolicy,
SelectionCacheType minimumCacheType, SelectionOrder inheritedSelectionOrder, String entityValueRangeRecorderId) {
SelectionCacheType minimumCacheType, SelectionOrder inheritedSelectionOrder,
ValueRangeRecorderId valueRangeRecorderId) {
if (config.getMimicSelectorRef() != null) {
return buildMimicReplaying(configPolicy);
}
Expand Down Expand Up @@ -115,7 +118,7 @@ public EntitySelector<Solution_> buildEntitySelector(HeuristicConfigPolicy<Solut
// The nearby selector will implement its own logic to filter out unreachable elements.
// Therefore, we only apply entity value range filtering if the nearby feature is not enabled;
// otherwise, we would end up applying the filtering logic twice.
entitySelector = applyEntityValueRangeFiltering(configPolicy, entitySelector, entityValueRangeRecorderId,
entitySelector = applyEntityValueRangeFiltering(configPolicy, entitySelector, valueRangeRecorderId,
minimumCacheType, inheritedSelectionOrder, baseRandomSelection);
}
entitySelector = applyFiltering(entitySelector, instanceCache);
Expand All @@ -140,7 +143,12 @@ protected EntitySelector<Solution_> buildMimicReplaying(HeuristicConfigPolicy<So
"The entitySelectorConfig (%s) with mimicSelectorRef (%s) has another property that is not null."
.formatted(config, config.getMimicSelectorRef()));
}
var entityMimicRecorder = configPolicy.getEntityMimicRecorder(config.getMimicSelectorRef());
return buildMimicReplaying(configPolicy, config.getMimicSelectorRef());
}

private MimicReplayingEntitySelector<Solution_> buildMimicReplaying(HeuristicConfigPolicy<Solution_> configPolicy,
String id) {
var entityMimicRecorder = configPolicy.getEntityMimicRecorder(id);
if (entityMimicRecorder == null) {
throw new IllegalArgumentException(
"The entitySelectorConfig (%s) has a mimicSelectorRef (%s) for which no entitySelector with that id exists (in its solver phase)."
Expand Down Expand Up @@ -185,17 +193,23 @@ private boolean hasFiltering(EntityDescriptor<Solution_> entityDescriptor) {
}

private EntitySelector<Solution_> applyEntityValueRangeFiltering(HeuristicConfigPolicy<Solution_> configPolicy,
EntitySelector<Solution_> entitySelector, String entityValueRangeRecorderId, SelectionCacheType minimumCacheType,
EntitySelector<Solution_> entitySelector, ValueRangeRecorderId valueRangeRecorderId,
SelectionCacheType minimumCacheType,
SelectionOrder selectionOrder, boolean randomSelection) {
if (entityValueRangeRecorderId == null) {
if (valueRangeRecorderId == null || valueRangeRecorderId.recorderId() == null) {
return entitySelector;
}
var valueSelectorConfig = new ValueSelectorConfig()
.withMimicSelectorRef(entityValueRangeRecorderId);
var replayingValueSelector = (IterableValueSelector<Solution_>) ValueSelectorFactory
.<Solution_> create(valueSelectorConfig)
.buildValueSelector(configPolicy, entitySelector.getEntityDescriptor(), minimumCacheType, selectionOrder);
return new FilteringEntityValueRangeSelector<>(entitySelector, replayingValueSelector, randomSelection);
if (valueRangeRecorderId.basicVariable()) {
var replayingEntitySelector = buildMimicReplaying(configPolicy, valueRangeRecorderId.recorderId());
return new FilteringEntityByEntitySelector<>(entitySelector, replayingEntitySelector, randomSelection);
} else {
var valueSelectorConfig = new ValueSelectorConfig()
.withMimicSelectorRef(valueRangeRecorderId.recorderId());
var replayingValueSelector = (IterableValueSelector<Solution_>) ValueSelectorFactory
.<Solution_> create(valueSelectorConfig)
.buildValueSelector(configPolicy, entitySelector.getEntityDescriptor(), minimumCacheType, selectionOrder);
return new FilteringEntityByValueSelector<>(entitySelector, replayingValueSelector, randomSelection);
}
}

private EntitySelector<Solution_> applyNearbySelection(HeuristicConfigPolicy<Solution_> configPolicy,
Expand Down
Loading
Loading