Skip to content

Commit

Permalink
introduce ChangeMoveProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
triceo committed Feb 6, 2025
1 parent 319d105 commit 728c0b9
Show file tree
Hide file tree
Showing 18 changed files with 204 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -315,14 +315,15 @@ public static boolean isList(Type type) {
return false;
}

public static List<Object> transformArrayToList(Object arrayObject) {
@SuppressWarnings("unchecked")
public static <Value_> List<Value_> transformArrayToList(Object arrayObject) {
if (arrayObject == null) {
return null;
}
int arrayLength = Array.getLength(arrayObject);
List<Object> list = new ArrayList<>(arrayLength);
for (int i = 0; i < arrayLength; i++) {
list.add(Array.get(arrayObject, i));
var arrayLength = Array.getLength(arrayObject);
var list = new ArrayList<Value_>(arrayLength);
for (var i = 0; i < arrayLength; i++) {
list.add((Value_) Array.get(arrayObject, i));
}
return list;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,18 @@ public boolean isCountable() {
return countable;
}

protected ValueRange<?> readValueRange(Object bean) {
@SuppressWarnings("unchecked")
protected <Value_> ValueRange<Value_> readValueRange(Object bean) {
Object valueRangeObject = memberAccessor.executeGetter(bean);
if (valueRangeObject == null) {
throw new IllegalStateException("The @" + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") called on bean (" + bean
+ ") must not return a null valueRangeObject (" + valueRangeObject + ").");
}
ValueRange<Object> valueRange;
ValueRange<Value_> valueRange;
if (collectionWrapping || arrayWrapping) {
List<Object> list = collectionWrapping ? transformCollectionToList((Collection<Object>) valueRangeObject)
List<Value_> list = collectionWrapping ? transformCollectionToList((Collection<Value_>) valueRangeObject)
: ReflectionHelper.transformArrayToList(valueRangeObject);
// Don't check the entire list for performance reasons, but do check common pitfalls
if (!list.isEmpty() && (list.get(0) == null || list.get(list.size() - 1) == null)) {
Expand All @@ -129,7 +130,7 @@ protected ValueRange<?> readValueRange(Object bean) {
}
valueRange = new ListValueRange<>(list);
} else {
valueRange = (ValueRange<Object>) valueRangeObject;
valueRange = (ValueRange<Value_>) valueRangeObject;
}
valueRange = doNullInValueRangeWrapping(valueRange);
if (valueRange.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ public boolean isEntityIndependent() {
}

@Override
public ValueRange<?> extractValueRange(Solution_ solution, Object entity) {
public <Value_> ValueRange<Value_> extractValueRange(Solution_ solution, Object entity) {
return innerExtractValueRange(solution, entity);
}

@SuppressWarnings("unchecked")
private <T> ValueRange<T> innerExtractValueRange(Solution_ solution, Object entity) {
var childValueRangeList = new ArrayList<CountableValueRange<T>>(childValueRangeDescriptorList.size());
for (var valueRangeDescriptor : childValueRangeDescriptorList) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public boolean isEntityIndependent() {
}

@Override
public ValueRange<?> extractValueRange(Solution_ solution, Object entity) {
public <Value_> ValueRange<Value_> extractValueRange(Solution_ solution, Object entity) {
return readValueRange(entity);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public boolean isEntityIndependent() {
}

@Override
public ValueRange<?> extractValueRange(Solution_ solution, Object entity) {
public <Value_> ValueRange<Value_> extractValueRange(Solution_ solution, Object entity) {
return readValueRange(solution);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public interface ValueRangeDescriptor<Solution_> {
* use {@link EntityIndependentValueRangeDescriptor#extractValueRange} instead.
* @return never null
*/
ValueRange<?> extractValueRange(Solution_ solution, Object entity);
<Value_> ValueRange<Value_> extractValueRange(Solution_ solution, Object entity);

/**
* @param solution never null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public long getSize(Object entity) {

@Override
public Iterator<Object> iterator(Object entity) {
ValueRange<Object> valueRange = (ValueRange<Object>) valueRangeDescriptor.extractValueRange(workingSolution, entity);
ValueRange<Object> valueRange = valueRangeDescriptor.extractValueRange(workingSolution, entity);
if (!randomSelection) {
return ((CountableValueRange<Object>) valueRange).createOriginalIterator();
} else {
Expand All @@ -79,7 +79,7 @@ public Iterator<Object> iterator(Object entity) {

@Override
public Iterator<Object> endingIterator(Object entity) {
ValueRange<Object> valueRange = (ValueRange<Object>) valueRangeDescriptor.extractValueRange(workingSolution, entity);
ValueRange<Object> valueRange = valueRangeDescriptor.extractValueRange(workingSolution, entity);
return ((CountableValueRange<Object>) valueRange).createOriginalIterator();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,17 @@ public abstract class AbstractMove<Solution_> implements Move<Solution_> {
}

@SuppressWarnings("unchecked")
protected final VariableDescriptor<Solution_> getVariableDescriptor(VariableMetaModel<Solution_, ?, ?> variableMetaModel) {
protected static <Solution_> VariableDescriptor<Solution_>
getVariableDescriptor(VariableMetaModel<Solution_, ?, ?> variableMetaModel) {
return ((InnerVariableMetaModel<Solution_>) variableMetaModel).variableDescriptor();
}

protected final GenuineVariableDescriptor<Solution_>
protected static <Solution_> GenuineVariableDescriptor<Solution_>
getVariableDescriptor(PlanningVariableMetaModel<Solution_, ?, ?> variableMetaModel) {
return ((DefaultPlanningVariableMetaModel<Solution_, ?, ?>) variableMetaModel).variableDescriptor();
}

protected final ListVariableDescriptor<Solution_>
protected static <Solution_> ListVariableDescriptor<Solution_>
getVariableDescriptor(PlanningListVariableMetaModel<Solution_, ?, ?> variableMetaModel) {
return ((DefaultPlanningListVariableMetaModel<Solution_, ?, ?>) variableMetaModel).variableDescriptor();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
public non-sealed interface BiMoveConstructor<Solution_, A, B>
extends MoveConstructor<Solution_> {

Move<Solution_> apply(A a, B b);
Move<Solution_> apply(Solution_ solution, A a, B b);

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.api.solver.change.ProblemChange;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
Expand Down Expand Up @@ -35,13 +34,13 @@ public interface Rebaser {
* Matching uses a {@link PlanningId} by default.
*
* @param problemFactOrPlanningEntity The fact or entity to rebase.
* @return null if externalObject is null
* @return null if problemFactOrPlanningEntity is null
* @throws IllegalArgumentException if there is no working object for the fact or entity,
* if it cannot be looked up,
* or if its class is not supported.
* @throws IllegalStateException if it cannot be looked up
* @param <T>
*/
<T> @Nullable T rebase(@NonNull T problemFactOrPlanningEntity);
<T> @Nullable T rebase(@Nullable T problemFactOrPlanningEntity);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ai.timefold.solver.core.preview.api.move.provider;

import java.util.Objects;

import ai.timefold.solver.core.impl.move.generic.ChangeMove;
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel;
import ai.timefold.solver.core.preview.api.move.MoveConstructor;
import ai.timefold.solver.core.preview.api.move.stream.MoveProvider;
import ai.timefold.solver.core.preview.api.move.stream.MoveStreams;

public class ChangeMoveProvider<Solution_, Entity_, Value_>
implements MoveProvider<Solution_> {

private final PlanningVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel;

public ChangeMoveProvider(PlanningVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel) {
this.variableMetaModel = Objects.requireNonNull(variableMetaModel);
}

@Override
public MoveConstructor<Solution_> apply(MoveStreams<Solution_> solutionMoveStreams) {
var valueStream = solutionMoveStreams.enumeratePossibleValues(variableMetaModel)
.filter(this::acceptValue);
if (variableMetaModel.allowsUnassigned()) {
valueStream = valueStream.addNull();
}
return solutionMoveStreams.pick(solutionMoveStreams.enumerateEntities(variableMetaModel.entity())
.filter(this::acceptEntity))
.pick(valueStream, this::acceptEntityValuePair)
.asMove((solution, entity, value) -> new ChangeMove<>(variableMetaModel, entity, value));
}

protected boolean acceptEntity(Entity_ entity) {
return true;
}

protected boolean acceptValue(Value_ value) {
return true;
}

protected boolean acceptEntityValuePair(Entity_ entity, Value_ value) {
return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ai.timefold.solver.core.preview.api.move.stream;

import java.util.function.Function;

public interface CachedMoveUniStream<Solution_, A> {

CachedMoveUniStream<Solution_, A> filter(Function<A, Boolean> filter);

CachedMoveUniStream<Solution_, A> addNull();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ai.timefold.solver.core.preview.api.move.stream;

import ai.timefold.solver.core.api.function.TriFunction;
import ai.timefold.solver.core.preview.api.move.BiMoveConstructor;
import ai.timefold.solver.core.preview.api.move.Move;

public interface JitMoveBiStream<Solution_, A, B> extends JitMoveStream<Solution_> {

BiMoveConstructor<Solution_, A, B> asMove(TriFunction<Solution_, A, B, Move<Solution_>> moveFactory);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ai.timefold.solver.core.preview.api.move.stream;

public interface JitMoveStream<Solution_> {

MoveStreams<Solution_> getMoveFactory();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ai.timefold.solver.core.preview.api.move.stream;

import java.util.function.BiPredicate;
import java.util.function.Function;

public interface JitMoveUniStream<Solution_, A> extends JitMoveStream<Solution_> {

default <B> JitMoveBiStream<Solution_, A, B> pick(Class<B> clz) {
return pick(getMoveFactory().enumerate(clz));
}

default <B> JitMoveBiStream<Solution_, A, B> pick(Class<B> clz, BiPredicate<A, B> filter) {
return pick(getMoveFactory().enumerate(clz), filter);
}

<B> JitMoveBiStream<Solution_, A, B> pick(CachedMoveUniStream<Solution_, B> cachedMoveUniStream);

<B> JitMoveBiStream<Solution_, A, B> pick(CachedMoveUniStream<Solution_, B> cachedMoveUniStream, BiPredicate<A, B> filter);

<B> JitMoveBiStream<Solution_, A, B> pick(Function<A, CachedMoveUniStream<Solution_, B>> cachedMoveUniStreamFunction);

default JitMoveBiStream<Solution_, A, A> pickOther(Class<A> clz) {
return pickOther(getMoveFactory().enumerate(clz));
}

default JitMoveBiStream<Solution_, A, A> pickOther(Class<A> clz, BiPredicate<A, A> filter) {
return pickOther(getMoveFactory().enumerate(clz), filter);
}

// TODO identity or equality?
default JitMoveBiStream<Solution_, A, A> pickOther(CachedMoveUniStream<Solution_, A> cachedMoveUniStream) {
return pick(cachedMoveUniStream, (a, b) -> !a.equals(b));
}

// TODO identity or equality?
default JitMoveBiStream<Solution_, A, A> pickOther(CachedMoveUniStream<Solution_, A> cachedMoveUniStream,
BiPredicate<A, A> filter) {
return pick(cachedMoveUniStream, filter);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ai.timefold.solver.core.preview.api.move.stream;

import java.util.function.Function;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.preview.api.move.MoveConstructor;

/**
* Implement this to provide a definition for one move type.
*
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
@FunctionalInterface
public interface MoveProvider<Solution_>
extends Function<MoveStreams<Solution_>, MoveConstructor<Solution_>> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ai.timefold.solver.core.preview.api.move.stream;

import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Function;

import ai.timefold.solver.core.impl.domain.solution.descriptor.DefaultPlanningVariableMetaModel;
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningEntityMetaModel;
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel;

public interface MoveStreams<Solution_> {

<A> CachedMoveUniStream<Solution_, A> enumerate(Class<A> clz);

default <Entity_> CachedMoveUniStream<Solution_, Entity_>
enumerateEntities(PlanningEntityMetaModel<Solution_, Entity_> entityMetaModel) {
return enumerate(entityMetaModel.type());
}

@SuppressWarnings("unchecked")
default <Entity_, A> CachedMoveUniStream<Solution_, A>
enumeratePossibleValues(PlanningVariableMetaModel<Solution_, Entity_, A> variableMetaModel) {
var variableDescriptor =
((DefaultPlanningVariableMetaModel<Solution_, Entity_, A>) variableMetaModel).variableDescriptor();
var valueRangeDescriptor = variableDescriptor.getValueRangeDescriptor();
if (variableDescriptor.isValueRangeEntityIndependent()) {
return enumerate(solution -> (Collection<A>) valueRangeDescriptor.extractValueRange(solution, null));
} else {
return enumerateFromEntity(variableMetaModel.entity(),
(solution, entity) -> (Collection<A>) valueRangeDescriptor.extractValueRange(solution, entity));
}
}

<A> CachedMoveUniStream<Solution_, A> enumerate(Function<Solution_, Collection<A>> collectionFunction);

<Entity_, A> CachedMoveUniStream<Solution_, A> enumerateFromEntity(
PlanningEntityMetaModel<Solution_, Entity_> entityMetaModel,
BiFunction<Solution_, Entity_, Collection<A>> collectionFunction);

<A> CachedMoveUniStream<Solution_, A> enumerate(Collection<A> collection);

default <A> JitMoveUniStream<Solution_, A> pick(Class<A> clz) {
return pick(enumerate(clz));
}

<A> JitMoveUniStream<Solution_, A> pick(CachedMoveUniStream<Solution_, A> cachedMoveUniStream);

}

0 comments on commit 728c0b9

Please sign in to comment.