Skip to content

chore: move streams change moves for list variable #1727

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Aug 12, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,24 @@

import java.util.IdentityHashMap;
import java.util.Map;
import java.util.stream.Stream;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode;
import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode.LifecycleOperation;
import ai.timefold.solver.core.impl.score.director.SessionContext;

public abstract class AbstractSession implements AutoCloseable {
public abstract class AbstractSession {

private final NodeNetwork nodeNetwork;
private final Map<Class<?>, AbstractForEachUniNode.InitializableForEachNode<Object>[]> initializeEffectiveClassToNodeArrayMap;
private final Map<Class<?>, AbstractForEachUniNode<Object>[]> insertEffectiveClassToNodeArrayMap;
private final Map<Class<?>, AbstractForEachUniNode<Object>[]> updateEffectiveClassToNodeArrayMap;
private final Map<Class<?>, AbstractForEachUniNode<Object>[]> retractEffectiveClassToNodeArrayMap;

protected AbstractSession(NodeNetwork nodeNetwork) {
this.nodeNetwork = nodeNetwork;
this.initializeEffectiveClassToNodeArrayMap = new IdentityHashMap<>(nodeNetwork.forEachNodeCount());
this.insertEffectiveClassToNodeArrayMap = new IdentityHashMap<>(nodeNetwork.forEachNodeCount());
this.updateEffectiveClassToNodeArrayMap = new IdentityHashMap<>(nodeNetwork.forEachNodeCount());
this.retractEffectiveClassToNodeArrayMap = new IdentityHashMap<>(nodeNetwork.forEachNodeCount());
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public final void initialize(SessionContext context) {
for (var node : findInitializableNodes()) {
node.initialize(context);
}
}

public final void insert(Object fact) {
var factClass = fact.getClass();
for (var node : findNodes(factClass, LifecycleOperation.INSERT)) {
Expand All @@ -57,29 +45,6 @@ private AbstractForEachUniNode<Object>[] findNodes(Class<?> factClass, Lifecycle
return nodeArray;
}

@SuppressWarnings("unchecked")
private AbstractForEachUniNode.InitializableForEachNode<Object>[] findInitializableNodes() {
// There will only be one solution class in the problem.
// Therefore we do not need to know what it is, and using the annotation class will serve as a unique key.
var factClass = PlanningSolution.class;
var effectiveClassToNodeArrayMap = initializeEffectiveClassToNodeArrayMap;
// Map.computeIfAbsent() would have created lambdas on the hot path, this will not.
var nodeArray = effectiveClassToNodeArrayMap.get(factClass);
if (nodeArray == null) {
nodeArray = nodeNetwork.getForEachNodes(factClass)
.flatMap(node -> {
if (node instanceof AbstractForEachUniNode.InitializableForEachNode<?> initializableForEachNode) {
return Stream.of(initializableForEachNode);
} else {
return Stream.empty();
}
})
.toArray(AbstractForEachUniNode.InitializableForEachNode[]::new);
effectiveClassToNodeArrayMap.put(factClass, nodeArray);
}
return nodeArray;
}

public final void update(Object fact) {
var factClass = fact.getClass();
for (var node : findNodes(factClass, LifecycleOperation.UPDATE)) {
Expand All @@ -98,13 +63,4 @@ public void settle() {
nodeNetwork.settle();
}

@Override
public final void close() {
for (var node : findInitializableNodes()) {
// Initializable nodes get a supply manager, fair to assume they will be demanding supplies.
// Give them the opportunity to cancel those demands.
node.close();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle;
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleState;
import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple;
import ai.timefold.solver.core.impl.score.director.SessionContext;

import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
Expand Down Expand Up @@ -136,13 +135,4 @@ public enum LifecycleOperation {
RETRACT
}

public interface InitializableForEachNode<Solution_> extends AutoCloseable {

void initialize(SessionContext<Solution_> context);

@Override
void close(); // Drop the checked exception.

}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
import org.jspecify.annotations.Nullable;

@NullMarked
public sealed class ForEachIncludingUnassignedUniNode<A>
extends AbstractForEachUniNode<A>
permits ForEachFromSolutionUniNode {
public final class ForEachIncludingUnassignedUniNode<A>
extends AbstractForEachUniNode<A> {

public ForEachIncludingUnassignedUniNode(Class<A> forEachClass, TupleLifecycle<UniTuple<A>> nextNodesTupleLifecycle,
int outputStoreSize) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,9 @@ public String name() {
return variableDescriptor.getVariableName();
}

@Override
public boolean hasValueRangeOnEntity() {
return !variableDescriptor.canExtractValueRangeFromSolution();
}

@Override
public boolean allowsUnassignedValues() {
return false;
return variableDescriptor.allowsUnassignedValues();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ public String name() {
return variableDescriptor.getVariableName();
}

@Override
public boolean hasValueRangeOnEntity() {
return !variableDescriptor.canExtractValueRangeFromSolution();
}

@Override
public boolean allowsUnassigned() {
return variableDescriptor.allowsUnassigned();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Convert your entities (%s) to use @%s instead."""
.formatted(moveProvidersClass, moveProviderList.size()));
}
var moveProvider = moveProviderList.get(0);
var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor);
var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, configPolicy.getEnvironmentMode());
var moveProducer = moveProvider.apply(moveStreamFactory);
var moveRepository = new MoveStreamsBasedMoveRepository<>(moveStreamFactory, moveProducer,
pickSelectionOrder() == SelectionOrder.RANDOM);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ public void stepEnded(AbstractStepScope<Solution_> stepScope) {
@Override
public void phaseEnded(AbstractPhaseScope<Solution_> phaseScope) {
if (moveStreamSession != null) {
moveStreamSession.close();
moveStreamSession = null;
}
phaseScope.getScoreDirector().setMoveRepository(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ public final <Entity_, Value_> Value_ getValue(PlanningVariableMetaModel<Solutio
return extractVariableDescriptor(variableMetaModel).getValue(entity);
}

@Override
public <Entity_, Value_> int countValues(PlanningListVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel,
Entity_ entity) {
return extractVariableDescriptor(variableMetaModel).getValue(entity).size();
}

@SuppressWarnings("unchecked")
@Override
public final <Entity_, Value_> Value_ getValueAtIndex(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.Objects;

import ai.timefold.solver.core.impl.move.streams.dataset.BiDataset;
import ai.timefold.solver.core.impl.move.streams.dataset.bi.BiDataset;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveConstructor;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveStream;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.Objects;
import java.util.function.BiPredicate;

import ai.timefold.solver.core.impl.move.streams.dataset.UniDataset;
import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDataset;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveConstructor;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveStream;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package ai.timefold.solver.core.impl.move.streams;

import ai.timefold.solver.core.impl.domain.solution.descriptor.DefaultPlanningListVariableMetaModel;
import ai.timefold.solver.core.impl.domain.solution.descriptor.DefaultPlanningVariableMetaModel;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.move.streams.dataset.AbstractBiDataStream;
import ai.timefold.solver.core.impl.move.streams.dataset.AbstractUniDataStream;
import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory;
import ai.timefold.solver.core.impl.move.streams.dataset.DatasetSessionFactory;
import ai.timefold.solver.core.impl.move.streams.dataset.bi.AbstractBiDataStream;
import ai.timefold.solver.core.impl.move.streams.dataset.uni.AbstractUniDataStream;
import ai.timefold.solver.core.impl.move.streams.maybeapi.DataJoiners;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiDataStream;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveStream;
Expand All @@ -28,14 +26,14 @@ public final class DefaultMoveStreamFactory<Solution_>
private final DataStreamFactory<Solution_> dataStreamFactory;
private final DatasetSessionFactory<Solution_> datasetSessionFactory;

public DefaultMoveStreamFactory(SolutionDescriptor<Solution_> solutionDescriptor) {
this.dataStreamFactory = new DataStreamFactory<>(solutionDescriptor);
public DefaultMoveStreamFactory(SolutionDescriptor<Solution_> solutionDescriptor, EnvironmentMode environmentMode) {
this.dataStreamFactory = new DataStreamFactory<>(solutionDescriptor, environmentMode);
this.datasetSessionFactory = new DatasetSessionFactory<>(dataStreamFactory);
}

public DefaultMoveStreamSession<Solution_> createSession(SessionContext<Solution_> context) {
var session = datasetSessionFactory.buildSession(context);
return new DefaultMoveStreamSession<>(session, context.workingSolution());
return new DefaultMoveStreamSession<>(session, context.solutionView());
}

@Override
Expand Down Expand Up @@ -71,35 +69,14 @@ public <A> UniDataStream<Solution_, A> enumerateIncludingPinned(Class<A> sourceC
public <Entity_, Value_> BiDataStream<Solution_, Entity_, Value_> enumerateEntityValuePairs(
GenuineVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel,
UniDataStream<Solution_, Entity_> entityDataStream) {
var variableDescriptor = getVariableDescriptor(variableMetaModel);
var valueRangeDescriptor = variableDescriptor.getValueRangeDescriptor();
var includeNull =
variableMetaModel instanceof PlanningVariableMetaModel<Solution_, Entity_, Value_> planningVariableMetaModel
? planningVariableMetaModel.allowsUnassigned()
: variableMetaModel instanceof PlanningListVariableMetaModel<Solution_, Entity_, Value_> planningListVariableMetaModel
&& planningListVariableMetaModel.allowsUnassignedValues();
if (valueRangeDescriptor.canExtractValueRangeFromSolution()) {
// No need for filtering the value range; all values from solution are valid.
var stream = dataStreamFactory.forEachFromSolution(variableMetaModel, includeNull);
return entityDataStream.join(stream);
} else {
var stream = dataStreamFactory.forEachExcludingPinned(variableMetaModel.type(), includeNull);
return entityDataStream.join(stream, DataJoiners.<Solution_, Entity_, Value_> filtering(
(solutionView, entity, value) -> solutionView.isValueInRange(variableMetaModel, entity, value)));
}
}

private static <Solution_> GenuineVariableDescriptor<Solution_>
getVariableDescriptor(GenuineVariableMetaModel<Solution_, ?, ?> variableMetaModel) {
if (variableMetaModel instanceof DefaultPlanningVariableMetaModel<Solution_, ?, ?> planningVariableMetaModel) {
return planningVariableMetaModel.variableDescriptor();
} else if (variableMetaModel instanceof DefaultPlanningListVariableMetaModel<Solution_, ?, ?> planningListVariableMetaModel) {
return planningListVariableMetaModel.variableDescriptor();
} else {
throw new IllegalStateException(
"Impossible state: variable metamodel (%s) represents neither basic not list variable."
.formatted(variableMetaModel.getClass().getSimpleName()));
}
var stream = dataStreamFactory.forEachExcludingPinned(variableMetaModel.type(), includeNull);
return entityDataStream.join(stream, DataJoiners.<Solution_, Entity_, Value_> filtering(
(solutionView, entity, value) -> solutionView.isValueInRange(variableMetaModel, entity, value)));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@

import java.util.Objects;

import ai.timefold.solver.core.impl.move.streams.dataset.BiDataset;
import ai.timefold.solver.core.impl.move.streams.dataset.BiDatasetInstance;
import ai.timefold.solver.core.impl.move.streams.dataset.DatasetSession;
import ai.timefold.solver.core.impl.move.streams.dataset.UniDataset;
import ai.timefold.solver.core.impl.move.streams.dataset.UniDatasetInstance;
import ai.timefold.solver.core.impl.move.streams.dataset.bi.BiDataset;
import ai.timefold.solver.core.impl.move.streams.dataset.bi.BiDatasetInstance;
import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDataset;
import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDatasetInstance;
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession;
import ai.timefold.solver.core.preview.api.move.SolutionView;

import org.jspecify.annotations.NullMarked;

@NullMarked
public final class DefaultMoveStreamSession<Solution_>
implements MoveStreamSession<Solution_>, AutoCloseable {
implements MoveStreamSession<Solution_> {

private final DatasetSession<Solution_> datasetSession;
private final Solution_ workingSolution;
private final SolutionView<Solution_> solutionView;

public DefaultMoveStreamSession(DatasetSession<Solution_> datasetSession, Solution_ workingSolution) {
public DefaultMoveStreamSession(DatasetSession<Solution_> datasetSession, SolutionView<Solution_> solutionView) {
this.datasetSession = Objects.requireNonNull(datasetSession);
this.workingSolution = Objects.requireNonNull(workingSolution);
this.solutionView = Objects.requireNonNull(solutionView);
}

public <A> UniDatasetInstance<Solution_, A> getDatasetInstance(UniDataset<Solution_, A> dataset) {
Expand All @@ -47,12 +48,8 @@ public void settle() {
datasetSession.settle();
}

public Solution_ getWorkingSolution() {
return workingSolution;
public SolutionView<Solution_> getSolutionView() {
return solutionView;
}

@Override
public void close() {
datasetSession.close();
}
}
Loading
Loading