Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
76ca1a9
feat: add quarkus random seed
zepfred Feb 19, 2025
66389a1
feat: add Spring-boot random seed
zepfred Jul 14, 2025
72498d9
chore: unblock value range for entities with list variable
zepfred Jun 23, 2025
4bca992
feat: enable entity value range for list variable in the CH
zepfred Jun 30, 2025
3f32882
chore: address comments
zepfred Jul 1, 2025
2876c64
chore: improve caching
zepfred Jul 1, 2025
e505c51
chore: address comments
zepfred Jul 1, 2025
bff422e
feat: add value range state
zepfred Jul 2, 2025
801c7eb
chore: using the value range solver
zepfred Jul 3, 2025
601bb43
feat: enable entity range for LS and list change move
zepfred Jul 3, 2025
8239d09
feat: address comments
zepfred Jul 4, 2025
f5db7d5
test: add new tests
zepfred Jul 4, 2025
ea6c18f
feat: enable entity value range for LS and multiple move types
zepfred Jul 5, 2025
46be373
feat: enable entity value range for LS and remaining move types
zepfred Jul 7, 2025
836679e
chore: address comments and sonar
zepfred Jul 7, 2025
4ab67c1
chore: address comments
zepfred Jul 8, 2025
abc0763
chore: address comments
zepfred Jul 8, 2025
4db8e95
chore: address sonar
zepfred Jul 8, 2025
51d4a73
chore: address comment
zepfred Jul 10, 2025
0424a1d
chore: set the value range resolver
zepfred Jul 14, 2025
be1e7bb
chore: cache the value range size for solution
zepfred Jul 14, 2025
2eb6b08
chore: minor updates
zepfred Jul 15, 2025
4904f06
fix: ensure the use of the entity value range instead of the solution…
zepfred Jul 16, 2025
b6c3b75
chore: simplify the value range resolver
zepfred Jul 17, 2025
67aada7
chore: address comments
zepfred Jul 18, 2025
f7e2204
chore: Revert "feat: add Spring-boot random seed"
zepfred Jul 18, 2025
d9d77fb
chore: Revert "feat: add quarkus random seed"
zepfred Jul 18, 2025
2c9bb75
chore: Temporarily disabling entity value ranges for list variables
zepfred Jul 18, 2025
fcafe72
chore: address more comments
zepfred Jul 21, 2025
825ef38
chore: allow empty value range
zepfred Jul 21, 2025
3d177a0
handle empty ranges without exceptions
triceo Jul 22, 2025
3158a01
range size no longer necessary
triceo Jul 22, 2025
eb99f6f
clean up around entities/solutions
triceo Jul 22, 2025
3417084
clean up around nullity
triceo Jul 22, 2025
bb8af88
Improve (?) the logic of composite value range; a test fails now
triceo Jul 22, 2025
55e35ba
Enforce CountableValueRange consistently
triceo Jul 22, 2025
837cb3c
Fix composite
triceo Jul 22, 2025
9f6923e
chore: minor changes
zepfred Jul 22, 2025
0448a29
chore: minor changes
zepfred Jul 22, 2025
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 @@ -162,7 +162,7 @@ private static ValueSelectorConfig configureSecondaryValueSelector(ValueSelector
.withValueSelectorConfig(valueConfig);
}

private static String addRandomSuffix(String name, Random random) {
public static String addRandomSuffix(String name, Random random) {
Comment thread
triceo marked this conversation as resolved.
var value = new StringBuilder(name);
value.append("-");
random.ints(97, 122) // ['a', 'z']
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ai.timefold.solver.core.config.util;

import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory.MemberAccessorType.FIELD_OR_READ_METHOD;
import static ai.timefold.solver.core.impl.domain.solution.cloner.DeepCloningUtils.IMMUTABLE_CLASSES;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
Expand Down Expand Up @@ -449,6 +450,17 @@ Maybe the member (%s) should return a parameterized %s."""
memberName, type, memberName, type.getSimpleName())));
}

/**
* @param type the class type
* @return true if it is immutable; otherwise false
*/
public static boolean isGenericTypeImmutable(Class<?> type) {
if (type == null) {
return false;
}
return type.isRecord() || IMMUTABLE_CLASSES.contains(type);
}

public static Optional<Class<?>> extractGenericTypeParameter(@NonNull String parentClassConcept,
@NonNull Class<?> parentClass, @NonNull Class<?> type, @NonNull Type genericType,
@Nullable Class<? extends Annotation> annotationClass, @NonNull String memberName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ public void solve(SolverScope<Solution_> solverScope) {
// (When it exhausts all values, it will start over from the beginning.)
// To prevent that, we need to limit the number of steps to the number of unassigned values.
var workingSolution = phaseScope.getWorkingSolution();
maxStepCount = solutionDescriptor.getListVariableDescriptor().countUnassigned(workingSolution);
maxStepCount = solutionDescriptor.getListVariableDescriptor().countUnassigned(workingSolution,
solverScope.getValueRangeManager());
}

TerminationStatus earlyTerminationStatus = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected ChangeMoveSelectorConfig buildChangeMoveSelectorConfig(HeuristicConfig
.withVariableName(variableDescriptor.getVariableName());
if (ValueSelectorConfig.hasSorter(configPolicy.getValueSorterManner(), variableDescriptor)) {
changeValueSelectorConfig = changeValueSelectorConfig
.withCacheType(variableDescriptor.isValueRangeEntityIndependent() ? PHASE : STEP)
.withCacheType(variableDescriptor.canExtractValueRangeFromSolution() ? PHASE : STEP)
.withSelectionOrder(SelectionOrder.SORTED)
.withSorterManner(configPolicy.getValueSorterManner());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveIteratorFactory;
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.list.ListChangeMoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.EntityIndependentValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.EntityIndependentFilteringValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.IterableFilteringValueSelector;

public class QueuedValuePlacer<Solution_> extends AbstractEntityPlacer<Solution_> implements EntityPlacer<Solution_> {

protected final EntityIndependentValueSelector<Solution_> valueSelector;
protected final IterableValueSelector<Solution_> valueSelector;
protected final MoveSelector<Solution_> moveSelector;

public QueuedValuePlacer(EntityPlacerFactory<Solution_> factory, HeuristicConfigPolicy<Solution_> configPolicy,
EntityIndependentValueSelector<Solution_> valueSelector, MoveSelector<Solution_> moveSelector) {
IterableValueSelector<Solution_> valueSelector, MoveSelector<Solution_> moveSelector) {
super(factory, configPolicy);
this.valueSelector = valueSelector;
this.moveSelector = moveSelector;
Expand Down Expand Up @@ -67,7 +67,7 @@ protected Placement<Solution_> createUpcomingSelection() {
@Override
public EntityPlacer<Solution_> rebuildWithFilter(SelectionFilter<Solution_, Object> filter) {
return new QueuedValuePlacer<>(factory, configPolicy,
(EntityIndependentFilteringValueSelector<Solution_>) FilteringValueSelector.of(valueSelector, filter),
(IterableFilteringValueSelector<Solution_>) FilteringValueSelector.of(valueSelector, filter),
moveSelector);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelectorFactory;
import ai.timefold.solver.core.impl.heuristic.selector.value.EntityIndependentValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory;

public class QueuedValuePlacerFactory<Solution_>
extends AbstractEntityPlacerFactory<Solution_, QueuedValuePlacerConfig> {

public static QueuedValuePlacerConfig unfoldNew(MoveSelectorConfig templateMoveSelectorConfig) {
throw new UnsupportedOperationException("The <constructionHeuristic> contains a moveSelector ("
+ templateMoveSelectorConfig + ") and the <queuedValuePlacer> does not support unfolding those yet.");
throw new UnsupportedOperationException(
"The <constructionHeuristic> contains a moveSelector (%s) and the <queuedValuePlacer> does not support unfolding those yet."
.formatted(templateMoveSelectorConfig));
}

public QueuedValuePlacerFactory(QueuedValuePlacerConfig placerConfig) {
Expand All @@ -48,15 +49,14 @@ public QueuedValuePlacer<Solution_> buildEntityPlacer(HeuristicConfigPolicy<Solu

MoveSelector<Solution_> moveSelector = MoveSelectorFactory.<Solution_> create(moveSelectorConfig_)
.buildMoveSelector(configPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.ORIGINAL, false);
if (!(valueSelector instanceof EntityIndependentValueSelector)) {
throw new IllegalArgumentException("The queuedValuePlacer (" + this
+ ") needs to be based on an "
+ EntityIndependentValueSelector.class.getSimpleName() + " (" + valueSelector + ")."
+ " Check your @" + ValueRangeProvider.class.getSimpleName() + " annotations.");
if (!(valueSelector instanceof IterableValueSelector<Solution_> iterableValueSelector)) {
throw new IllegalArgumentException(
"The queuedValuePlacer (%s) needs to be based on an %s (%s). Check your @%s annotations.".formatted(this,
IterableValueSelector.class.getSimpleName(), valueSelector,
ValueRangeProvider.class.getSimpleName()));

}
return new QueuedValuePlacer<>(this, configPolicy, (EntityIndependentValueSelector<Solution_>) valueSelector,
moveSelector);
return new QueuedValuePlacer<>(this, configPolicy, iterableValueSelector, moveSelector);
}

private ValueSelectorConfig buildValueSelectorConfig(HeuristicConfigPolicy<Solution_> configPolicy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
import ai.timefold.solver.core.api.domain.entity.PlanningPin;
import ai.timefold.solver.core.api.domain.entity.PlanningPinToIndex;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange;
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
import ai.timefold.solver.core.api.domain.variable.AnchorShadowVariable;
import ai.timefold.solver.core.api.domain.variable.CascadingUpdateShadowVariable;
Expand Down Expand Up @@ -67,6 +65,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter;
import ai.timefold.solver.core.impl.score.director.ValueRangeManager;
import ai.timefold.solver.core.impl.util.CollectionUtils;
import ai.timefold.solver.core.impl.util.MutableInt;
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningEntityMetaModel;
Expand Down Expand Up @@ -859,18 +858,31 @@ public PlanningPinToIndexReader getEffectivePlanningPinToIndexReader() {
return effectivePlanningPinToIndexReader;
}

public long getMaximumValueCount(Solution_ solution, Object entity) {
public long getMaximumValueCount(Solution_ solution, Object entity, ValueRangeManager<Solution_> valueRangeManager) {
var maximumValueCount = 0L;
for (var variableDescriptor : effectiveGenuineVariableDescriptorList) {
maximumValueCount = Math.max(maximumValueCount, variableDescriptor.getValueRangeSize(solution, entity));
if (variableDescriptor.canExtractValueRangeFromSolution()) {
maximumValueCount = Math.max(maximumValueCount,
valueRangeManager.countOnSolution(variableDescriptor.getValueRangeDescriptor(),
solution));
} else {
maximumValueCount = Math.max(maximumValueCount,
valueRangeManager.countOnEntity(variableDescriptor.getValueRangeDescriptor(),
entity));

}
}
return maximumValueCount;

}

public void processProblemScale(Solution_ solution, Object entity, ProblemScaleTracker tracker) {
public void processProblemScale(Solution_ solution, Object entity, ProblemScaleTracker tracker,
ValueRangeManager<Solution_> valueRangeManager) {
for (var variableDescriptor : effectiveGenuineVariableDescriptorList) {
var valueCount = variableDescriptor.getValueRangeSize(solution, entity);
var valueCount = variableDescriptor.canExtractValueRangeFromSolution()
? valueRangeManager.countOnSolution(variableDescriptor.getValueRangeDescriptor(),
solution)
: valueRangeManager.countOnEntity(variableDescriptor.getValueRangeDescriptor(), entity);
// TODO: When minimum Java supported is 21, this can be replaced with a sealed interface switch
if (variableDescriptor instanceof BasicVariableDescriptor<Solution_> basicVariableDescriptor) {
if (basicVariableDescriptor.isChained()) {
Expand All @@ -880,34 +892,34 @@ public void processProblemScale(Solution_ solution, Object entity, ProblemScaleT
tracker.addPinnedListValueCount(1);
}
// Anchors are entities
var valueRange = variableDescriptor.getValueRangeDescriptor().extractValueRange(solution, entity);
if (valueRange instanceof CountableValueRange<?> countableValueRange) {
var valueIterator = countableValueRange.createOriginalIterator();
while (valueIterator.hasNext()) {
var value = valueIterator.next();
if (variableDescriptor.isValuePotentialAnchor(value)) {
if (tracker.isAnchorVisited(value)) {
continue;
}
// Assumes anchors are not pinned
tracker.incrementListEntityCount(true);
var valueRange = variableDescriptor.canExtractValueRangeFromSolution()
? valueRangeManager.getFromSolution(variableDescriptor.getValueRangeDescriptor(),
solution)
: valueRangeManager.getFromEntity(variableDescriptor.getValueRangeDescriptor(),
entity);
var valueIterator = valueRange.createOriginalIterator();
while (valueIterator.hasNext()) {
var value = valueIterator.next();
if (variableDescriptor.isValuePotentialAnchor(value)) {
if (tracker.isAnchorVisited(value)) {
continue;
}
// Assumes anchors are not pinned
tracker.incrementListEntityCount(true);
}
} else {
throw new IllegalStateException("""
The value range (%s) for variable (%s) is not countable.
Verify that a @%s does not return a %s when it can return %s or %s.
""".formatted(valueRange, variableDescriptor.getSimpleEntityAndVariableName(),
ValueRangeProvider.class.getSimpleName(), ValueRange.class.getSimpleName(),
CountableValueRange.class.getSimpleName(), Collection.class.getSimpleName()));
}
} else {
if (isMovable(solution, entity)) {
tracker.addBasicProblemScale(valueCount);
}
}
} else if (variableDescriptor instanceof ListVariableDescriptor<Solution_> listVariableDescriptor) {
tracker.setListTotalValueCount((int) listVariableDescriptor.getValueRangeSize(solution, entity));
var size = variableDescriptor.canExtractValueRangeFromSolution()
? valueRangeManager.countOnSolution(listVariableDescriptor.getValueRangeDescriptor(),
solution)
: valueRangeManager.countOnEntity(listVariableDescriptor.getValueRangeDescriptor(),
entity);
tracker.setListTotalValueCount((int) size);
if (isMovable(solution, entity)) {
tracker.incrementListEntityCount(true);
tracker.addPinnedListValueCount(listVariableDescriptor.getFirstUnpinnedIndex(entity));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
public final class DeepCloningUtils {

// Instances of these JDK classes will never be deep-cloned.
private static final Set<Class<?>> IMMUTABLE_CLASSES = Set.of(
public static final Set<Class<?>> IMMUTABLE_CLASSES = Set.of(
// Numbers
Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, BigInteger.class, BigDecimal.class,
// Optional
Expand Down
Loading
Loading