Skip to content

Commit 20b973e

Browse files
committed
fix: avoid more situations where a K-OPT cannot be found
1 parent 75afd70 commit 20b973e

1 file changed

Lines changed: 31 additions & 20 deletions

File tree

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptListMoveIterator.java

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator;
1212
import ai.timefold.solver.core.impl.heuristic.selector.value.EntityIndependentValueSelector;
1313

14+
import org.jspecify.annotations.NullMarked;
15+
import org.jspecify.annotations.Nullable;
16+
17+
@NullMarked
1418
final class KOptListMoveIterator<Solution_, Node_> extends UpcomingSelectionIterator<Move<Solution_>> {
1519

1620
private final Random workingRandom;
@@ -96,7 +100,7 @@ private Iterator<Node_> getValuesOnSelectedEntitiesIterator(Node_[] pickedValues
96100
}
97101

98102
@SuppressWarnings("unchecked")
99-
private KOptDescriptor<Node_> pickKOptMove(int k) {
103+
private @Nullable KOptDescriptor<Node_> pickKOptMove(int k) {
100104
// The code in the paper used 1-index arrays
101105
var pickedValues = (Node_[]) new Object[2 * k + 1];
102106
var originIterator = (Iterator<Node_>) originSelector.iterator();
@@ -135,18 +139,17 @@ private KOptDescriptor<Node_> pickKOptMove(int k) {
135139
}
136140
}
137141

138-
private KOptDescriptor<Node_> pickKOptMoveRec(Iterator<Node_> valueIterator,
139-
EntityOrderInfo entityOrderInfo,
140-
Node_[] pickedValues,
141-
int pickedSoFar,
142-
int k,
143-
boolean canSelectNewEntities) {
142+
private @Nullable KOptDescriptor<Node_> pickKOptMoveRec(Iterator<Node_> valueIterator, EntityOrderInfo entityOrderInfo,
143+
Node_[] pickedValues, int pickedSoFar, int k, boolean canSelectNewEntities) {
144144
var previousRemovedEdgeEndpoint = pickedValues[2 * pickedSoFar - 2];
145145
Node_ nextRemovedEdgePoint, nextRemovedEdgeOppositePoint;
146146

147147
var remainingAttempts = (k - pickedSoFar + 3) * 2;
148148
while (remainingAttempts > 0) {
149-
nextRemovedEdgePoint = valueIterator.next();
149+
nextRemovedEdgePoint = getNextNodeOrNull(valueIterator);
150+
if (nextRemovedEdgePoint == null) {
151+
return null;
152+
}
150153
var newEntityOrderInfo =
151154
entityOrderInfo.withNewNode(nextRemovedEdgePoint, listVariableStateSupply);
152155
while (nextRemovedEdgePoint == getNodePredecessor(newEntityOrderInfo, previousRemovedEdgeEndpoint) ||
@@ -161,7 +164,10 @@ && isEdgeAlreadyDeleted(pickedValues, nextRemovedEdgePoint,
161164
if (remainingAttempts == 0) {
162165
return null;
163166
}
164-
nextRemovedEdgePoint = valueIterator.next();
167+
nextRemovedEdgePoint = getNextNodeOrNull(valueIterator);
168+
if (nextRemovedEdgePoint == null) {
169+
return null;
170+
}
165171
newEntityOrderInfo =
166172
entityOrderInfo.withNewNode(nextRemovedEdgePoint, listVariableStateSupply);
167173
remainingAttempts--;
@@ -191,24 +197,19 @@ && isEdgeAlreadyDeleted(pickedValues, nextRemovedEdgePoint,
191197
}
192198

193199
if (pickedSoFar < k) {
194-
var descriptor = pickKOptMoveRec(valueIterator, newEntityOrderInfo, pickedValues,
195-
pickedSoFar + 1, k, canSelectNewEntities);
200+
var descriptor = pickKOptMoveRec(valueIterator, newEntityOrderInfo, pickedValues, pickedSoFar + 1, k,
201+
canSelectNewEntities);
196202
if (descriptor != null && descriptor.isFeasible(minK, maxCyclesPatchedInInfeasibleMove)) {
197203
return descriptor;
198204
}
199205
} else {
200-
var descriptor = new KOptDescriptor<Node_>(pickedValues,
201-
KOptUtils.getMultiEntitySuccessorFunction(pickedValues,
202-
listVariableStateSupply),
203-
KOptUtils.getMultiEntityBetweenPredicate(pickedValues,
204-
listVariableStateSupply));
206+
var descriptor = new KOptDescriptor<>(pickedValues,
207+
KOptUtils.getMultiEntitySuccessorFunction(pickedValues, listVariableStateSupply),
208+
KOptUtils.getMultiEntityBetweenPredicate(pickedValues, listVariableStateSupply));
205209
if (descriptor.isFeasible(minK, maxCyclesPatchedInInfeasibleMove)) {
206210
return descriptor;
207211
} else {
208-
descriptor = patchCycles(
209-
descriptor,
210-
newEntityOrderInfo, pickedValues,
211-
pickedSoFar);
212+
descriptor = patchCycles(descriptor, newEntityOrderInfo, pickedValues, pickedSoFar);
212213
if (descriptor.isFeasible(minK, maxCyclesPatchedInInfeasibleMove)) {
213214
return descriptor;
214215
}
@@ -218,6 +219,16 @@ && isEdgeAlreadyDeleted(pickedValues, nextRemovedEdgePoint,
218219
return null;
219220
}
220221

222+
private @Nullable Node_ getNextNodeOrNull(Iterator<Node_> iterator) {
223+
if (!iterator.hasNext()) {
224+
return null;
225+
}
226+
// This may still be null.
227+
// Either due to filtering the underlying iterator,
228+
// or due to the underlying iterator returning null.
229+
return iterator.next();
230+
}
231+
221232
KOptDescriptor<Node_> patchCycles(KOptDescriptor<Node_> descriptor, EntityOrderInfo entityOrderInfo,
222233
Node_[] oldRemovedEdges, int k) {
223234
Node_ s1, s2;

0 commit comments

Comments
 (0)