Skip to content

Commit d95bbc3

Browse files
committed
Second working test
1 parent a22ca4c commit d95bbc3

File tree

8 files changed

+196
-85
lines changed

8 files changed

+196
-85
lines changed

core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/AbstractForEachUniNode.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package ai.timefold.solver.core.impl.bavet.uni;
22

3-
import java.util.IdentityHashMap;
4-
import java.util.Map;
5-
63
import ai.timefold.solver.core.impl.bavet.common.AbstractNode;
74
import ai.timefold.solver.core.impl.bavet.common.Propagator;
85
import ai.timefold.solver.core.impl.bavet.common.StaticPropagationQueue;
96
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle;
107
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleState;
118
import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple;
129
import ai.timefold.solver.core.impl.score.director.SessionContext;
13-
1410
import org.jspecify.annotations.NullMarked;
1511
import org.jspecify.annotations.Nullable;
1612

13+
import java.util.IdentityHashMap;
14+
import java.util.Map;
15+
1716
/**
1817
* Filtering nodes are expensive.
1918
* Considering that most streams start with a nullity check on genuine planning variables,

core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/AbstractForEachDataStream.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
11
package ai.timefold.solver.core.impl.move.streams.dataset.uni;
22

3-
import static ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode.LifecycleOperation;
4-
5-
import java.util.Objects;
6-
import java.util.Set;
7-
83
import ai.timefold.solver.core.impl.bavet.common.TupleSource;
94
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle;
105
import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple;
116
import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode;
127
import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory;
138
import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream;
149
import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper;
15-
1610
import org.jspecify.annotations.NullMarked;
1711

12+
import java.util.Objects;
13+
import java.util.Set;
14+
15+
import static ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode.LifecycleOperation;
16+
1817
@NullMarked
1918
abstract sealed class AbstractForEachDataStream<Solution_, A>
2019
extends AbstractUniDataStream<Solution_, A>
2120
implements TupleSource
2221
permits ForEachIncludingPinnedDataStream, ForEachFromSolutionDataStream {
2322

2423
protected final Class<A> forEachClass;
25-
private final boolean shouldIncludeNull;
24+
final boolean shouldIncludeNull;
2625

27-
protected AbstractForEachDataStream(DataStreamFactory<Solution_> dataStreamFactory, Class<A> forEachClass,
28-
boolean includeNull) {
26+
protected AbstractForEachDataStream(DataStreamFactory<Solution_> dataStreamFactory, Class<A> forEachClass, boolean includeNull) {
2927
super(dataStreamFactory, null);
3028
this.forEachClass = Objects.requireNonNull(forEachClass);
3129
this.shouldIncludeNull = includeNull;

core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/ForEachFromSolutionDataStream.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package ai.timefold.solver.core.impl.move.streams.dataset.uni;
22

3-
import java.util.Objects;
4-
53
import ai.timefold.solver.core.impl.bavet.common.TupleSource;
64
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle;
75
import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple;
86
import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode;
97
import ai.timefold.solver.core.impl.bavet.uni.ForEachFromSolutionUniNode;
108
import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor;
11-
129
import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory;
1310
import org.jspecify.annotations.NullMarked;
1411

12+
import java.util.Objects;
13+
1514
@NullMarked
1615
public final class ForEachFromSolutionDataStream<Solution_, A>
1716
extends AbstractForEachDataStream<Solution_, A>
@@ -34,13 +33,14 @@ protected AbstractForEachUniNode<A> getNode(TupleLifecycle<UniTuple<A>> tupleLif
3433
@Override
3534
public boolean equals(Object o) {
3635
return o instanceof ForEachFromSolutionDataStream<?, ?> that &&
36+
Objects.equals(shouldIncludeNull, that.shouldIncludeNull) &&
3737
Objects.equals(forEachClass, that.forEachClass) &&
3838
Objects.equals(valueRangeDescriptor, that.valueRangeDescriptor);
3939
}
4040

4141
@Override
4242
public int hashCode() {
43-
return Objects.hash(forEachClass, valueRangeDescriptor);
43+
return Objects.hash(shouldIncludeNull, forEachClass, valueRangeDescriptor);
4444
}
4545

4646
@Override

core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/ForEachIncludingPinnedDataStream.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
package ai.timefold.solver.core.impl.move.streams.dataset.uni;
22

3-
import java.util.Objects;
4-
53
import ai.timefold.solver.core.impl.bavet.common.TupleSource;
64
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle;
75
import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple;
86
import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode;
97
import ai.timefold.solver.core.impl.bavet.uni.ForEachIncludingUnassignedUniNode;
10-
118
import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory;
129
import org.jspecify.annotations.NullMarked;
1310

11+
import java.util.Objects;
12+
1413
@NullMarked
1514
public final class ForEachIncludingPinnedDataStream<Solution_, A>
1615
extends AbstractForEachDataStream<Solution_, A>
1716
implements TupleSource {
1817

19-
public ForEachIncludingPinnedDataStream(DataStreamFactory<Solution_> dataStreamFactory, Class<A> forEachClass,
20-
boolean includeNull) {
18+
public ForEachIncludingPinnedDataStream(DataStreamFactory<Solution_> dataStreamFactory, Class<A> forEachClass, boolean includeNull) {
2119
super(dataStreamFactory, forEachClass, includeNull);
2220
}
2321

@@ -29,12 +27,13 @@ protected AbstractForEachUniNode<A> getNode(TupleLifecycle<UniTuple<A>> tupleLif
2927
@Override
3028
public boolean equals(Object o) {
3129
return o instanceof ForEachIncludingPinnedDataStream<?, ?> that &&
30+
Objects.equals(shouldIncludeNull, that.shouldIncludeNull) &&
3231
Objects.equals(forEachClass, that.forEachClass);
3332
}
3433

3534
@Override
3635
public int hashCode() {
37-
return forEachClass.hashCode();
36+
return Objects.hash(shouldIncludeNull, forEachClass);
3837
}
3938

4039
@Override

core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/provider/ListChangeMoveProvider.java

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,43 @@ public class ListChangeMoveProvider<Solution_, Entity_, Value_>
2222

2323
private final PlanningListVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel;
2424
private final BiDataFilter<Solution_, Entity_, Value_> isValueInListFilter;
25-
private final BiDataFilter<Solution_, Value_, ElementPosition> noChangeDetectionFilter;
25+
private final BiDataFilter<Solution_, Value_, ElementPosition> validChangeFilter;
2626

2727
public ListChangeMoveProvider(PlanningListVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel) {
2828
this.variableMetaModel = Objects.requireNonNull(variableMetaModel);
2929
this.isValueInListFilter = (solution, entity, value) -> {
30-
if (entity == null) {
31-
// Necessary for the null entity to survive until the later stage,
32-
// where we will use it as a marker to unassigned the value.
30+
if (entity == null || value == null) {
31+
// Necessary for the null to survive until the later stage,
32+
// where we will use it as a marker to unassign the value.
3333
return true;
3434
}
3535
return solution.isValueInRange(variableMetaModel, entity, value);
3636
};
37-
this.noChangeDetectionFilter = (solutionView, value, targetPosition) -> {
38-
var currentPosition = solutionView.getPositionOf(variableMetaModel, Objects.requireNonNull(value));
39-
if (!(currentPosition instanceof PositionInList currentPositionInList)) {
40-
// The current position is unassigned, which is acceptable if we can assign it to a list.
41-
return targetPosition instanceof PositionInList;
37+
this.validChangeFilter = (solutionView, value, targetPosition) -> {
38+
var currentPosition = solutionView.getPositionOf(variableMetaModel, value);
39+
if (currentPosition.equals(targetPosition)) {
40+
return false;
4241
}
43-
if (!(targetPosition instanceof PositionInList targetPositionInList)) {
44-
// The target position is unassigned, which is only acceptable if we can unassign the value.
45-
return true;
46-
}
47-
if (Objects.equals(currentPositionInList, targetPositionInList)) {
48-
return false; // No change in position, so no need to create a move.
49-
}
50-
if (currentPositionInList.entity() != targetPositionInList.entity()) {
51-
return true; // Different entities, so we can freely change the position.
52-
}
53-
var listLength = solutionView.countValues(variableMetaModel, currentPositionInList.entity());
54-
if (listLength == 1) {
55-
return false; // No need to change the position, as the value is the only one in this list.
42+
if (currentPosition instanceof UnassignedElement) {
43+
var targetPositionInList = targetPosition.ensureAssigned();
44+
return solutionView.isValueInRange(variableMetaModel, targetPositionInList.entity(), value);
45+
} else {
46+
if (!(targetPosition instanceof PositionInList targetPositionInList)) { // Unassigning a value.
47+
return true;
48+
}
49+
var currentPositionInList = currentPosition.ensureAssigned();
50+
if (currentPositionInList.entity() == targetPositionInList.entity()) {
51+
var valueCount = solutionView.countValues(variableMetaModel, currentPositionInList.entity());
52+
if (valueCount == 1) {
53+
return false; // The value is already in the list, and it is the only one.
54+
} else {
55+
return currentPositionInList.index() != targetPositionInList.index();
56+
}
57+
} else {
58+
// We can move freely between entities.
59+
return solutionView.isValueInRange(variableMetaModel, targetPositionInList.entity(), value);
60+
}
5661
}
57-
// Make sure we're not moving the value past the list end.
58-
// This would happen if the value was already at the end of the list.
59-
return currentPositionInList.index() != listLength - 1 || targetPositionInList.index() != listLength;
6062
};
6163
}
6264

@@ -69,14 +71,11 @@ public MoveProducer<Solution_> apply(MoveStreamFactory<Solution_> moveStreamFact
6971
// To assign or reassign a value, we need to create:
7072
// - A move for every unpinned value in every entity's list variable to assign the value before that position.
7173
// - A move for every entity to assign it to the last position in the list variable.
72-
var unpinnedValuesToChange = moveStreamFactory.enumerate(variableMetaModel.type(), false);
73-
var unpinnedEntities = moveStreamFactory.enumerate(variableMetaModel.entity().type(), true);
74-
var unpinnedValues = moveStreamFactory.enumerate(variableMetaModel.type(), true);
74+
var unpinnedEntities = moveStreamFactory.enumerate(variableMetaModel.entity().type(), false);
75+
var unpinnedValues = moveStreamFactory.enumerate(variableMetaModel.type(), true)
76+
.filter((solutionView, value) -> value == null || solutionView.getPositionOf(variableMetaModel, value) instanceof PositionInList);
7577
var entityValuePairs = unpinnedEntities.join(unpinnedValues, DataJoiners.filtering(isValueInListFilter))
7678
.map((solutionView, entity, value) -> {
77-
if (entity == null) { // This will trigger unassignment of the value.
78-
return ElementPosition.unassigned();
79-
}
8079
var valueCount = solutionView.countValues(variableMetaModel, entity);
8180
if (value == null || valueCount == 0) { // This will trigger assignment of the value at the end of the list.
8281
return ElementPosition.of(entity, valueCount);
@@ -85,8 +84,8 @@ public MoveProducer<Solution_> apply(MoveStreamFactory<Solution_> moveStreamFact
8584
}
8685
})
8786
.distinct();
88-
var dataStream = unpinnedValuesToChange.join(entityValuePairs,
89-
DataJoiners.filtering(noChangeDetectionFilter));
87+
var dataStream = moveStreamFactory.enumerate(variableMetaModel.type(), false)
88+
.join(entityValuePairs, DataJoiners.filtering(validChangeFilter));
9089
return moveStreamFactory.pick(dataStream)
9190
.asMove((solutionView, value, targetPosition) -> {
9291
var currentPosition = solutionView.getPositionOf(variableMetaModel, Objects.requireNonNull(value));

0 commit comments

Comments
 (0)