Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
**/* @triceo

core/src/*/java/ai/timefold/solver/core/impl/domain/valuerange/** @triceo @zepfred
core/src/*/java/ai/timefold/solver/core/impl/heuristic/** @triceo @zepfred
core/src/*/java/ai/timefold/solver/core/impl/heuristic/selector/** @triceo @zepfred
core/src/*/java/ai/timefold/solver/core/impl/constructionheuristic/** @triceo @zepfred
core/src/*/java/ai/timefold/solver/core/impl/exhaustivesearch/** @triceo @zepfred
core/src/*/java/ai/timefold/solver/core/impl/localsearch/** @triceo @zepfred
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import ai.timefold.solver.core.config.phase.PhaseConfig;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodProvider;
import ai.timefold.solver.core.preview.api.neighborhood.NeighborhoodProvider;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
*/
public class Placement<Solution_> implements Iterable<Move<Solution_>> {

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <Solution_> Iterator<ai.timefold.solver.core.preview.api.move.Move<Solution_>>
toNewMoveIterator(Iterator<ai.timefold.solver.core.impl.heuristic.move.Move<Solution_>> legacyIterator) {
return (Iterator) legacyIterator;
}

private final Iterator<Move<Solution_>> moveIterator;

public Placement(Iterator<Move<Solution_>> moveIterator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.heuristic.move.Move;
import ai.timefold.solver.core.impl.heuristic.move.MoveAdapters;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
Expand Down Expand Up @@ -42,7 +41,7 @@ protected Placement<Solution_> createUpcomingSelection() {
if (!moveIterator.hasNext()) {
return noUpcomingSelection();
}
return new Placement<>(MoveAdapters.toNewMoveIterator(moveIterator));
return new Placement<>(Placement.toNewMoveIterator(moveIterator));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.heuristic.move.Move;
import ai.timefold.solver.core.impl.heuristic.move.MoveAdapters;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator;
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector;
Expand Down Expand Up @@ -67,7 +66,7 @@ protected Placement<Solution_> createUpcomingSelection() {
MoveSelector<Solution_> moveSelector = moveSelectorIterator.next();
moveIterator = moveSelector.iterator();
}
return new Placement<>(MoveAdapters.toNewMoveIterator(moveIterator));
return new Placement<>(Placement.toNewMoveIterator(moveIterator));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.Iterator;

import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
import ai.timefold.solver.core.impl.heuristic.move.MoveAdapters;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
Expand Down Expand Up @@ -59,7 +58,7 @@ protected Placement<Solution_> createUpcomingSelection() {
if (!moveIterator.hasNext()) {
return noUpcomingSelection();
}
return new Placement<>(MoveAdapters.toNewMoveIterator(moveIterator));
return new Placement<>(Placement.toNewMoveIterator(moveIterator));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter;
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.move.director.MoveDirector;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter;
import ai.timefold.solver.core.impl.move.MoveDirector;
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;
import ai.timefold.solver.core.preview.api.neighborhood.stream.enumerating.function.UniEnumeratingFilter;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.impl.domain.variable.ListVariableStateDemand;
import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor;
import ai.timefold.solver.core.impl.move.director.MoveDirector;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingPredicate;
import ai.timefold.solver.core.impl.move.MoveDirector;
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel;
import ai.timefold.solver.core.preview.api.neighborhood.stream.enumerating.function.BiEnumeratingPredicate;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
import ai.timefold.solver.core.impl.exhaustivesearch.scope.ExhaustiveSearchPhaseScope;
import ai.timefold.solver.core.impl.exhaustivesearch.scope.ExhaustiveSearchStepScope;
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.Moves;
import ai.timefold.solver.core.impl.phase.AbstractPhase;
import ai.timefold.solver.core.impl.phase.PhaseType;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
import ai.timefold.solver.core.impl.solver.termination.PhaseTermination;
import ai.timefold.solver.core.preview.api.move.Move;
import ai.timefold.solver.core.preview.api.move.builtin.Moves;

/**
* Default implementation of {@link ExhaustiveSearchPhase}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor;
import ai.timefold.solver.core.impl.move.director.VariableChangeRecordingScoreDirector;
import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.preview.api.move.MutableSolutionView;
import ai.timefold.solver.core.preview.api.move.Rebaser;

/**
* Abstract superclass for {@link Move}, requiring implementation of undo moves.
Expand Down Expand Up @@ -77,4 +79,24 @@ public static <E> Set<E> rebaseSet(Set<E> externalObjectSet, ScoreDirector<?> de
}
return rebasedObjectSet;
}

// ************************************************************************
// Final methods from the new move interface, to prevent user error
// ************************************************************************

@Override
public final void execute(MutableSolutionView<Solution_> solutionView) {
Move.super.execute(solutionView);
}

@Override
public final ai.timefold.solver.core.preview.api.move.Move<Solution_> rebase(Rebaser rebaser) {
return Move.super.rebase(rebaser);
}

@Override
public final String describe() {
return Move.super.describe();
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ai.timefold.solver.core.impl.heuristic.move;

import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.move.director.VariableChangeRecordingScoreDirector;
import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector;

/**
* This is an alternative to {@link AbstractMove},
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package ai.timefold.solver.core.impl.heuristic.move;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Iterator;

import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.lookup.PlanningId;
Expand All @@ -13,10 +10,12 @@
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.api.solver.Solver;
import ai.timefold.solver.core.config.localsearch.decider.acceptor.AcceptorType;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveListFactory;
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.tabu.MoveTabuAcceptor;
import ai.timefold.solver.core.impl.move.MoveDirector;
import ai.timefold.solver.core.preview.api.move.MutableSolutionView;
import ai.timefold.solver.core.preview.api.move.Rebaser;

/**
* A Move represents a change of 1 or more {@link PlanningVariable}s of 1 or more {@link PlanningEntity}s
Expand All @@ -31,11 +30,38 @@
* An implementation must extend {@link AbstractMove} to ensure backwards compatibility in future versions.
* It is highly recommended to override {@link #getPlanningEntities()} and {@link #getPlanningValues()},
* otherwise the resulting move will throw an exception when used with Tabu search.
* <p>
* To ease interoperability with Neighborhoods API,
* this interface extends {@link ai.timefold.solver.core.preview.api.move.Move},
* giving the user an option to override certain methods which they should not.
* Specifically, the following methods must not be overridden by the user,
* as suitable default implementations are provided:
*
* <ul>
* <li>{@link #execute(MutableSolutionView)}</li>
* <li>{@link #rebase(Rebaser)}</li>
* <li>{@link #describe()}</li>
* </ul>
*
* The following methods should also not be overridden, on account of being deprecated:
*
* <ul>
* <li>{@link #doMove(ScoreDirector)}</li>
* </ul>
*
* <p>
* This entire interface exists to provide interoperability with move selectors
* and will eventually be phased out in favor of the Neighborhoods API.
* It will be marked as deprecated for removal in a future release.
*
* <p>
* To avoid having to implement this interface and instead use the new Move API directly,
* you can use {@link MoveAdapters#toLegacyMoveIterator(Iterator)} in your move selectors.
*
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
* @see AbstractMove
*/
public interface Move<Solution_> {
public interface Move<Solution_> extends ai.timefold.solver.core.preview.api.move.Move<Solution_> {

/**
* Called before a move is evaluated to decide whether the move can be done and evaluated.
Expand Down Expand Up @@ -98,6 +124,15 @@ default void doMoveOnly(ScoreDirector<Solution_> scoreDirector) {
doMove(scoreDirector);
}

/**
* Do not override this default implementation.
*/
@Override
default void execute(MutableSolutionView<Solution_> solutionView) {
var scoreDirector = ((MoveDirector<Solution_, ?>) solutionView).getScoreDirector();
doMoveOnly(scoreDirector);
}

/**
* Rebases a move from an origin {@link ScoreDirector} to another destination {@link ScoreDirector}
* which is usually on another {@link Thread} or JVM.
Expand Down Expand Up @@ -134,6 +169,14 @@ default Move<Solution_> rebase(ScoreDirector<Solution_> destinationScoreDirector
.formatted(getClass()));
}

/**
* Do not override this default implementation.
*/
@Override
default ai.timefold.solver.core.preview.api.move.Move<Solution_> rebase(Rebaser rebaser) {
return rebase(((MoveDirector<Solution_, ?>) rebaser).getScoreDirector());
}

// ************************************************************************
// Introspection methods
// ************************************************************************
Expand All @@ -151,38 +194,11 @@ default String getSimpleMoveTypeDescription() {
}

/**
* Returns all planning entities that are being changed by this move.
* Required for {@link AcceptorType#ENTITY_TABU}.
* <p>
* This method is only called after {@link #doMoveOnly(ScoreDirector)} (which might affect the return values).
* <p>
* Duplicate entries in the returned {@link Collection} are best avoided.
* The returned {@link Collection} is recommended to be in a stable order.
* For example: use {@link List} or {@link LinkedHashSet}, but not {@link HashSet}.
*
* @return never null
* Do not override this default implementation.
*/
default Collection<? extends Object> getPlanningEntities() {
throw new UnsupportedOperationException(
"Move class (%s) doesn't implement the extractPlanningEntities() method, so Entity Tabu Search is impossible."
.formatted(getClass()));
@Override
default String describe() {
return getSimpleMoveTypeDescription();
}

/**
* Returns all planning values that entities are being assigned to by this move.
* Required for {@link AcceptorType#VALUE_TABU}.
* <p>
* This method is only called after {@link #doMoveOnly(ScoreDirector)} (which might affect the return values).
* <p>
* Duplicate entries in the returned {@link Collection} are best avoided.
* The returned {@link Collection} is recommended to be in a stable order.
* For example: use {@link List} or {@link LinkedHashSet}, but not {@link HashSet}.
*
* @return never null
*/
default Collection<? extends Object> getPlanningValues() {
throw new UnsupportedOperationException(
"Move class (%s) doesn't implement the extractPlanningEntities() method, so Value Tabu Search is impossible."
.formatted(getClass()));
}
}
Loading
Loading