Skip to content

Commit 997fe70

Browse files
committed
chore: improve Neighborhoods design for scalability
1 parent f47b08f commit 997fe70

File tree

56 files changed

+1360
-1010
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1360
-1010
lines changed

core/src/main/java/ai/timefold/solver/core/impl/bavet/bi/joiner/BiJoinerComber.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
*/
1515
public final class BiJoinerComber<A, B> {
1616

17-
public static <A, B> BiJoinerComber<A, B> comb(BiJoiner<A, B>[] joiners) {
17+
public static <A, B> BiJoinerComber<A, B> comb(BiJoiner<A, B>... joiners) {
1818
List<DefaultBiJoiner<A, B>> defaultJoinerList = new ArrayList<>(joiners.length);
1919
List<BiPredicate<A, B>> filteringList = new ArrayList<>(joiners.length);
2020

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/ComparisonIndexer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import ai.timefold.solver.core.impl.bavet.common.joiner.JoinerType;
1212
import ai.timefold.solver.core.impl.util.ElementAwareListEntry;
1313

14+
import org.jspecify.annotations.NullMarked;
15+
16+
@NullMarked
1417
final class ComparisonIndexer<T, Key_ extends Comparable<Key_>>
1518
implements Indexer<T> {
1619

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/EqualsIndexer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
import ai.timefold.solver.core.impl.util.ElementAwareListEntry;
1010

11+
import org.jspecify.annotations.NullMarked;
12+
13+
@NullMarked
1114
final class EqualsIndexer<T, Key_> implements Indexer<T> {
1215

1316
private final KeyRetriever<Key_> keyRetriever;

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/Indexer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleState;
66
import ai.timefold.solver.core.impl.util.ElementAwareListEntry;
77

8+
import org.jspecify.annotations.NullMarked;
9+
810
/**
911
* An indexer for entity or fact {@code X},
1012
* maps a property or a combination of properties of {@code X}, denoted by {@code indexKeys},
@@ -20,6 +22,7 @@
2022
* For example for {@code from(A).join(B)}, the tuple is {@code UniTuple<A>} xor {@code UniTuple<B>}.
2123
* For example for {@code Bi<A, B>.join(C)}, the tuple is {@code BiTuple<A, B>} xor {@code UniTuple<C>}.
2224
*/
25+
@NullMarked
2326
public sealed interface Indexer<T> permits ComparisonIndexer, EqualsIndexer, NoneIndexer {
2427

2528
ElementAwareListEntry<T> put(Object indexKeys, T tuple);

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/NoneIndexer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import ai.timefold.solver.core.impl.util.ElementAwareList;
66
import ai.timefold.solver.core.impl.util.ElementAwareListEntry;
77

8+
import org.jspecify.annotations.NullMarked;
9+
10+
@NullMarked
811
public final class NoneIndexer<T> implements Indexer<T> {
912

1013
private final ElementAwareList<T> tupleList = new ElementAwareList<>();

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/joiner/JoinerType.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,14 @@ public enum JoinerType {
2121
}
2222

2323
public JoinerType flip() {
24-
switch (this) {
25-
case LESS_THAN:
26-
return GREATER_THAN;
27-
case LESS_THAN_OR_EQUAL:
28-
return GREATER_THAN_OR_EQUAL;
29-
case GREATER_THAN:
30-
return LESS_THAN;
31-
case GREATER_THAN_OR_EQUAL:
32-
return LESS_THAN_OR_EQUAL;
33-
default:
34-
throw new IllegalStateException("The joinerType (" + this + ") cannot be flipped.");
35-
}
24+
return switch (this) {
25+
case LESS_THAN -> GREATER_THAN;
26+
case LESS_THAN_OR_EQUAL -> GREATER_THAN_OR_EQUAL;
27+
case GREATER_THAN -> LESS_THAN;
28+
case GREATER_THAN_OR_EQUAL -> LESS_THAN_OR_EQUAL;
29+
default -> throw new IllegalStateException("The joinerType (%s) cannot be flipped."
30+
.formatted(this));
31+
};
3632
}
3733

3834
public boolean matches(Object left, Object right) {

core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import ai.timefold.solver.core.impl.domain.variable.ListVariableStateDemand;
1616
import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor;
1717
import ai.timefold.solver.core.impl.move.director.MoveDirector;
18-
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter;
18+
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingPredicate;
1919

2020
public final class ListVariableDescriptor<Solution_> extends GenuineVariableDescriptor<Solution_> {
2121

@@ -24,7 +24,7 @@ public final class ListVariableDescriptor<Solution_> extends GenuineVariableDesc
2424
var list = getValue(entity);
2525
return list.contains(element);
2626
};
27-
private final BiEnumeratingFilter<Solution_, Object, Object> entityContainsPinnedValuePredicate =
27+
private final BiEnumeratingPredicate<Solution_, Object, Object> entityContainsPinnedValuePredicate =
2828
(solutionView, value, entity) -> {
2929
var moveDirector = (MoveDirector<Solution_, ?>) solutionView;
3030
return moveDirector.isPinned(this, value);
@@ -47,8 +47,8 @@ public <A> BiPredicate<A, Object> getInListPredicate() {
4747
}
4848

4949
@SuppressWarnings("unchecked")
50-
public <A, B> BiEnumeratingFilter<Solution_, A, B> getEntityContainsPinnedValuePredicate() {
51-
return (BiEnumeratingFilter<Solution_, A, B>) entityContainsPinnedValuePredicate;
50+
public <A, B> BiEnumeratingPredicate<Solution_, A, B> getEntityContainsPinnedValuePredicate() {
51+
return (BiEnumeratingPredicate<Solution_, A, B>) entityContainsPinnedValuePredicate;
5252
}
5353

5454
public boolean allowsUnassignedValues() {

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveStreamFactory.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@
55
import ai.timefold.solver.core.api.domain.entity.PlanningPinToIndex;
66
import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty;
77
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
8-
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.BiEnumeratingStream;
98
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.EnumeratingStream;
109
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream;
1110
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter;
12-
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.BiSamplingStream;
1311
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.UniSamplingStream;
1412
import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition;
15-
import ai.timefold.solver.core.preview.api.domain.metamodel.GenuineVariableMetaModel;
1613
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel;
1714
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel;
1815
import ai.timefold.solver.core.preview.api.domain.metamodel.UnassignedElement;
@@ -61,18 +58,6 @@ public interface MoveStreamFactory<Solution_> {
6158
*/
6259
<A> UniEnumeratingStream<Solution_, A> forEachUnfiltered(Class<A> sourceClass, boolean includeNull);
6360

64-
/**
65-
* Enumerate possible values for any given entity,
66-
* where entities are obtained using {@link #forEach(Class, boolean)},
67-
* with the class matching the entity type of the variable.
68-
* If the variable allows unassigned values, the resulting stream will include a null value.
69-
*
70-
* @param variableMetaModel the meta model of the variable to enumerate
71-
* @return enumerating stream with all possible values of a given variable
72-
*/
73-
<Entity_, Value_> BiEnumeratingStream<Solution_, Entity_, Value_>
74-
forEachEntityValuePair(GenuineVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel);
75-
7661
/**
7762
* Enumerate all possible positions of a list variable to which a value can be assigned.
7863
* This will eliminate all positions on {@link PlanningPin pinned entities},
@@ -89,6 +74,4 @@ public interface MoveStreamFactory<Solution_> {
8974

9075
<A> UniSamplingStream<Solution_, A> pick(UniEnumeratingStream<Solution_, A> enumeratingStream);
9176

92-
<A, B> BiSamplingStream<Solution_, A, B> pick(BiEnumeratingStream<Solution_, A, B> enumeratingStream);
93-
9477
}

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinition.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition;
66
import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream;
77
import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStreamFactory;
8+
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.EnumeratingJoiners;
9+
import ai.timefold.solver.core.impl.neighborhood.stream.DefaultMoveStreamFactory;
810
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel;
911

1012
import org.jspecify.annotations.NullMarked;
@@ -21,13 +23,12 @@ public ChangeMoveDefinition(PlanningVariableMetaModel<Solution_, Entity_, Value_
2123

2224
@Override
2325
public MoveStream<Solution_> build(MoveStreamFactory<Solution_> moveStreamFactory) {
24-
var enumeratingStream =
25-
moveStreamFactory.forEachEntityValuePair(variableMetaModel)
26-
.filter((solutionView, entity, value) -> {
27-
Value_ currentValue = solutionView.getValue(variableMetaModel, Objects.requireNonNull(entity));
28-
return !Objects.equals(currentValue, value);
29-
});
30-
return moveStreamFactory.pick(enumeratingStream)
26+
var nodeSharingSupportFunctions =
27+
((DefaultMoveStreamFactory<Solution_>) moveStreamFactory).getNodeSharingSupportFunctions(variableMetaModel);
28+
return moveStreamFactory.pick(moveStreamFactory.forEach(variableMetaModel.entity().type(), false))
29+
.pick(moveStreamFactory.forEach(variableMetaModel.type(), variableMetaModel.allowsUnassigned()),
30+
EnumeratingJoiners.filtering(nodeSharingSupportFunctions.differentValueFilter()),
31+
EnumeratingJoiners.filtering(nodeSharingSupportFunctions.valueInRangeFilter()))
3132
.asMove((solution, entity, value) -> Moves.change(Objects.requireNonNull(entity), value, variableMetaModel));
3233
}
3334

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinition.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,10 @@ public ListChangeMoveDefinition(PlanningListVariableMetaModel<Solution_, Entity_
4646
@Override
4747
public MoveStream<Solution_> build(MoveStreamFactory<Solution_> moveStreamFactory) {
4848
var entityValuePairs = moveStreamFactory.forEachAssignablePosition(variableMetaModel);
49-
// The stream of these positions is joined with the stream of all existing values,
50-
// filtering out those which would not result in a valid move.
51-
var enumeratingStream = moveStreamFactory.forEach(variableMetaModel.type(), false)
52-
.join(entityValuePairs, EnumeratingJoiners.filtering(this::isValidChange));
53-
// When picking from this stream, we decide what kind of move we need to create,
54-
// based on whether the value is assigned or unassigned.
55-
return moveStreamFactory.pick(enumeratingStream)
56-
.asMove((solutionView, value, targetPosition) -> {
49+
var availableValues = moveStreamFactory.forEach(variableMetaModel.type(), false);
50+
return moveStreamFactory.pick(entityValuePairs)
51+
.pick(availableValues, EnumeratingJoiners.filtering(this::isValidChange))
52+
.asMove((solutionView, targetPosition, value) -> {
5753
var currentPosition = solutionView.getPositionOf(variableMetaModel, Objects.requireNonNull(value));
5854
if (targetPosition instanceof UnassignedElement) {
5955
var currentElementPosition = currentPosition.ensureAssigned();
@@ -68,7 +64,7 @@ public MoveStream<Solution_> build(MoveStreamFactory<Solution_> moveStreamFactor
6864
});
6965
}
7066

71-
private boolean isValidChange(SolutionView<Solution_> solutionView, Value_ value, ElementPosition targetPosition) {
67+
private boolean isValidChange(SolutionView<Solution_> solutionView, ElementPosition targetPosition, Value_ value) {
7268
var currentPosition = solutionView.getPositionOf(variableMetaModel, value);
7369
if (currentPosition.equals(targetPosition)) { // No change needed.
7470
return false;

0 commit comments

Comments
 (0)