diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/AbstractSession.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/AbstractSession.java new file mode 100644 index 0000000000..a0eac1c62a --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/AbstractSession.java @@ -0,0 +1,56 @@ +package ai.timefold.solver.core.impl.bavet; + +import java.util.IdentityHashMap; +import java.util.Map; + +import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode; + +public abstract class AbstractSession { + + private final NodeNetwork nodeNetwork; + private final Map, AbstractForEachUniNode[]> effectiveClassToNodeArrayMap; + + protected AbstractSession(NodeNetwork nodeNetwork) { + this.nodeNetwork = nodeNetwork; + this.effectiveClassToNodeArrayMap = new IdentityHashMap<>(nodeNetwork.forEachNodeCount()); + } + + public final void insert(Object fact) { + var factClass = fact.getClass(); + for (var node : findNodes(factClass)) { + node.insert(fact); + } + } + + @SuppressWarnings("unchecked") + private AbstractForEachUniNode[] findNodes(Class factClass) { + // 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) + .filter(AbstractForEachUniNode::supportsIndividualUpdates) + .toArray(AbstractForEachUniNode[]::new); + effectiveClassToNodeArrayMap.put(factClass, nodeArray); + } + return nodeArray; + } + + public final void update(Object fact) { + var factClass = fact.getClass(); + for (var node : findNodes(factClass)) { + node.update(fact); + } + } + + public final void retract(Object fact) { + var factClass = fact.getClass(); + for (var node : findNodes(factClass)) { + node.retract(fact); + } + } + + protected void settle() { + nodeNetwork.settle(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/NodeNetwork.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/NodeNetwork.java similarity index 77% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/NodeNetwork.java rename to core/src/main/java/ai/timefold/solver/core/impl/bavet/NodeNetwork.java index cb4cf990bf..63fec53a90 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/NodeNetwork.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/NodeNetwork.java @@ -1,9 +1,10 @@ -package ai.timefold.solver.core.impl.score.stream.bavet; +package ai.timefold.solver.core.impl.bavet; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Stream; import ai.timefold.solver.core.impl.bavet.common.Propagator; import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode; @@ -17,7 +18,7 @@ * @param layeredNodes nodes grouped first by their layer, then by their index within the layer; * propagation needs to happen in this order. */ -record NodeNetwork(Map, List>> declaredClassToNodeMap, Propagator[][] layeredNodes) { +public record NodeNetwork(Map, List>> declaredClassToNodeMap, Propagator[][] layeredNodes) { public static final NodeNetwork EMPTY = new NodeNetwork(Map.of(), new Propagator[0][0]); @@ -29,23 +30,27 @@ public int layerCount() { return layeredNodes.length; } - @SuppressWarnings("unchecked") - public AbstractForEachUniNode[] getApplicableForEachNodes(Class factClass) { + public Stream> getForEachNodes() { + return declaredClassToNodeMap.values() + .stream() + .flatMap(List::stream); + } + + public Stream> getForEachNodes(Class factClass) { return declaredClassToNodeMap.entrySet() .stream() .filter(entry -> entry.getKey().isAssignableFrom(factClass)) .map(Map.Entry::getValue) - .flatMap(List::stream) - .toArray(AbstractForEachUniNode[]::new); + .flatMap(List::stream); } - public void propagate() { + public void settle() { for (var layerIndex = 0; layerIndex < layerCount(); layerIndex++) { - propagateInLayer(layeredNodes[layerIndex]); + settleLayer(layeredNodes[layerIndex]); } } - private static void propagateInLayer(Propagator[] nodesInLayer) { + private static void settleLayer(Propagator[] nodesInLayer) { var nodeCount = nodesInLayer.length; if (nodeCount == 1) { nodesInLayer[0].propagateEverything(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/NodeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNodeBuildHelper.java similarity index 60% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/NodeBuildHelper.java rename to core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNodeBuildHelper.java index 6397d65f44..5609347b08 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/NodeBuildHelper.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractNodeBuildHelper.java @@ -6,29 +6,25 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.UnaryOperator; +import java.util.function.Function; -import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; +import ai.timefold.solver.core.impl.bavet.common.tuple.LeftTupleLifecycle; +import ai.timefold.solver.core.impl.bavet.common.tuple.RightTupleLifecycle; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode; -import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintStream; -import ai.timefold.solver.core.impl.score.stream.common.inliner.AbstractScoreInliner; -public final class NodeBuildHelper> { +public abstract class AbstractNodeBuildHelper { - private final Set activeStreamSet; - private final AbstractScoreInliner scoreInliner; - private final Map> nodeCreatorMap; - private final Map> tupleLifecycleMap; - private final Map storeIndexMap; + private final Set activeStreamSet; + private final Map nodeCreatorMap; + private final Map> tupleLifecycleMap; + private final Map storeIndexMap; private List reversedNodeList; - public NodeBuildHelper(Set activeStreamSet, AbstractScoreInliner scoreInliner) { + public AbstractNodeBuildHelper(Set activeStreamSet) { this.activeStreamSet = activeStreamSet; - this.scoreInliner = scoreInliner; int activeStreamSetSize = activeStreamSet.size(); this.nodeCreatorMap = new HashMap<>(Math.max(16, activeStreamSetSize)); this.tupleLifecycleMap = new HashMap<>(Math.max(16, activeStreamSetSize)); @@ -36,19 +32,15 @@ public NodeBuildHelper(Set activeStreamSet, Abstract this.reversedNodeList = new ArrayList<>(activeStreamSetSize); } - public boolean isStreamActive(ConstraintStream stream) { + public boolean isStreamActive(Stream_ stream) { return activeStreamSet.contains(stream); } - public AbstractScoreInliner getScoreInliner() { - return scoreInliner; - } - - public void addNode(AbstractNode node, BavetAbstractConstraintStream creator) { + public void addNode(AbstractNode node, Stream_ creator) { addNode(node, creator, creator); } - public void addNode(AbstractNode node, BavetAbstractConstraintStream creator, BavetAbstractConstraintStream parent) { + public void addNode(AbstractNode node, Stream_ creator, Stream_ parent) { reversedNodeList.add(node); nodeCreatorMap.put(node, creator); if (!(node instanceof AbstractForEachUniNode)) { @@ -59,43 +51,42 @@ public void addNode(AbstractNode node, BavetAbstractConstraintStream creator, } } - public void addNode( - AbstractTwoInputNode node, BavetAbstractConstraintStream creator, - BavetAbstractConstraintStream leftParent, BavetAbstractConstraintStream rightParent) { + public void addNode(AbstractNode node, Stream_ creator, Stream_ leftParent, Stream_ rightParent) { reversedNodeList.add(node); nodeCreatorMap.put(node, creator); - putInsertUpdateRetract(leftParent, TupleLifecycle.ofLeft(node)); - putInsertUpdateRetract(rightParent, TupleLifecycle.ofRight(node)); + putInsertUpdateRetract(leftParent, TupleLifecycle.ofLeft((LeftTupleLifecycle) node)); + putInsertUpdateRetract(rightParent, TupleLifecycle.ofRight((RightTupleLifecycle) node)); } - public void putInsertUpdateRetract(ConstraintStream stream, + public void putInsertUpdateRetract(Stream_ stream, TupleLifecycle tupleLifecycle) { tupleLifecycleMap.put(stream, tupleLifecycle); } - public void putInsertUpdateRetract(ConstraintStream stream, - List> childStreamList, - UnaryOperator> tupleLifecycleFunction) { + public void putInsertUpdateRetract(Stream_ stream, List childStreamList, + Function, TupleLifecycle> tupleLifecycleFunction) { TupleLifecycle tupleLifecycle = getAggregatedTupleLifecycle(childStreamList); putInsertUpdateRetract(stream, tupleLifecycleFunction.apply(tupleLifecycle)); } @SuppressWarnings("unchecked") public TupleLifecycle - getAggregatedTupleLifecycle(List streamList) { + getAggregatedTupleLifecycle(List streamList) { var tupleLifecycles = streamList.stream() .filter(this::isStreamActive) .map(s -> getTupleLifecycle(s, tupleLifecycleMap)) .toArray(TupleLifecycle[]::new); - if (tupleLifecycles.length == 0) { - throw new IllegalStateException("Impossible state: None of the streamList (%s) are active.".formatted(streamList)); - } - return TupleLifecycle.aggregate(tupleLifecycles); + return switch (tupleLifecycles.length) { + case 0 -> + throw new IllegalStateException("Impossible state: None of the streamList (" + streamList + ") are active."); + case 1 -> tupleLifecycles[0]; + default -> TupleLifecycle.aggregate(tupleLifecycles); + }; } @SuppressWarnings("unchecked") - private static TupleLifecycle getTupleLifecycle(ConstraintStream stream, - Map> tupleLifecycleMap) { + private static TupleLifecycle getTupleLifecycle(Stream_ stream, + Map> tupleLifecycleMap) { var tupleLifecycle = (TupleLifecycle) tupleLifecycleMap.get(stream); if (tupleLifecycle == null) { throw new IllegalStateException("Impossible state: the stream (" + stream + ") hasn't built a node yet."); @@ -103,7 +94,7 @@ private static TupleLifecycle getTupleLif return tupleLifecycle; } - public int reserveTupleStoreIndex(ConstraintStream tupleSourceStream) { + public int reserveTupleStoreIndex(Stream_ tupleSourceStream) { return storeIndexMap.compute(tupleSourceStream, (k, index) -> { if (index == null) { return 0; @@ -116,7 +107,7 @@ public int reserveTupleStoreIndex(ConstraintStream tupleSourceStream) { }); } - public int extractTupleStoreSize(ConstraintStream tupleSourceStream) { + public int extractTupleStoreSize(Stream_ tupleSourceStream) { Integer lastIndex = storeIndexMap.put(tupleSourceStream, Integer.MIN_VALUE); return (lastIndex == null) ? 0 : lastIndex + 1; } @@ -128,17 +119,17 @@ public List destroyAndGetNodeList() { return nodeList; } - public BavetAbstractConstraintStream getNodeCreatingStream(AbstractNode node) { + public Stream_ getNodeCreatingStream(AbstractNode node) { return nodeCreatorMap.get(node); } - public AbstractNode findParentNode(BavetAbstractConstraintStream childNodeCreator) { + public AbstractNode findParentNode(Stream_ childNodeCreator) { if (childNodeCreator == null) { // We've recursed to the bottom without finding a parent node. throw new IllegalStateException( "Impossible state: node-creating stream (" + childNodeCreator + ") has no parent node."); } // Look the stream up among node creators and if found, the node is the parent node. - for (Map.Entry> entry : this.nodeCreatorMap.entrySet()) { + for (Map.Entry entry : this.nodeCreatorMap.entrySet()) { if (entry.getValue() == childNodeCreator) { return entry.getKey(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetAbstractConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetAbstractConstraintStream.java index 8bc59188fd..f5402bfc17 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetAbstractConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetAbstractConstraintStream.java @@ -10,13 +10,17 @@ import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintStream; import ai.timefold.solver.core.impl.score.stream.common.RetrievalSemantics; import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; import org.jspecify.annotations.NonNull; -public abstract class BavetAbstractConstraintStream extends AbstractConstraintStream { +public abstract class BavetAbstractConstraintStream + extends AbstractConstraintStream + implements BavetStream { protected final BavetConstraintFactory constraintFactory; protected final BavetAbstractConstraintStream parent; @@ -109,7 +113,7 @@ public BavetAbstractConstraintStream getTupleSource() { return parent.getTupleSource(); } - public abstract > void buildNode(NodeBuildHelper buildHelper); + public abstract > void buildNode(ConstraintNodeBuildHelper buildHelper); // ************************************************************************ // Helper methods @@ -135,6 +139,7 @@ protected void assertEmptyChildStreamList() { * @return null for join/ifExists nodes, which have left and right parents instead; * also null for forEach node, which has no parent. */ + @Override public final BavetAbstractConstraintStream getParent() { return parent; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStream.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStream.java new file mode 100644 index 0000000000..5866eefdb8 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStream.java @@ -0,0 +1,7 @@ +package ai.timefold.solver.core.impl.bavet.common; + +public interface BavetStream { + + Stream_ getParent(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructor.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructor.java index 594b43d96e..bbe8b66f35 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructor.java @@ -2,8 +2,6 @@ import java.util.List; -import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; @@ -11,7 +9,8 @@ import ai.timefold.solver.core.impl.util.Quadruple; import ai.timefold.solver.core.impl.util.Triple; -public interface GroupNodeConstructor { +public sealed interface GroupNodeConstructor + permits GroupNodeConstructorWithAccumulate, GroupNodeConstructorWithoutAccumulate { // Although Tuple_ is unused in GroupNodeConstructor, // it is used in its two implementations: GroupNodeConstructorWithAccumulate // and GroupNodeConstructorWithoutAccumulate. The Tuple_ here serves as a type hint @@ -57,7 +56,7 @@ public interface GroupNodeConstructor { zeroKeysGroupBy(CollectorA_ collectorA, CollectorB_ collectorB, CollectorC_ collectorC, GroupBy0Mapping3CollectorNodeBuilder builder) { return new GroupNodeConstructorWithAccumulate<>( - new Triple(collectorA, collectorB, collectorC), + new Triple<>(collectorA, collectorB, collectorC), (groupStoreIndex, undoStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build( groupStoreIndex, undoStoreIndex, collectorA, collectorB, collectorC, nextNodesTupleLifecycle, outputStoreSize, environmentMode)); @@ -67,7 +66,7 @@ public interface GroupNodeConstructor { zeroKeysGroupBy(CollectorA_ collectorA, CollectorB_ collectorB, CollectorC_ collectorC, CollectorD_ collectorD, GroupBy0Mapping4CollectorNodeBuilder builder) { return new GroupNodeConstructorWithAccumulate<>( - new Quadruple(collectorA, collectorB, collectorC, + new Quadruple<>(collectorA, collectorB, collectorC, collectorD), (groupStoreIndex, undoStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build( groupStoreIndex, undoStoreIndex, collectorA, collectorB, collectorC, collectorD, @@ -94,7 +93,7 @@ public interface GroupNodeConstructor { oneKeyGroupBy(KeyA_ keyMappingA, CollectorB_ collectorB, CollectorC_ collectorC, GroupBy1Mapping2CollectorNodeBuilder builder) { return new GroupNodeConstructorWithAccumulate<>( - new Triple(keyMappingA, collectorB, collectorC), + new Triple<>(keyMappingA, collectorB, collectorC), (groupStoreIndex, undoStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build( keyMappingA, groupStoreIndex, undoStoreIndex, collectorB, collectorC, nextNodesTupleLifecycle, @@ -105,7 +104,7 @@ public interface GroupNodeConstructor { oneKeyGroupBy(KeyA_ keyMappingA, CollectorB_ collectorB, CollectorC_ collectorC, CollectorD_ collectorD, GroupBy1Mapping3CollectorNodeBuilder builder) { return new GroupNodeConstructorWithAccumulate<>( - new Quadruple(keyMappingA, collectorB, collectorC, collectorD), + new Quadruple<>(keyMappingA, collectorB, collectorC, collectorD), (groupStoreIndex, undoStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build( keyMappingA, groupStoreIndex, undoStoreIndex, collectorB, collectorC, collectorD, nextNodesTupleLifecycle, @@ -125,7 +124,7 @@ public interface GroupNodeConstructor { twoKeysGroupBy(KeyA_ keyMappingA, KeyB_ keyMappingB, CollectorC_ collectorC, GroupBy2Mapping1CollectorNodeBuilder builder) { return new GroupNodeConstructorWithAccumulate<>( - new Triple(keyMappingA, keyMappingB, collectorC), + new Triple<>(keyMappingA, keyMappingB, collectorC), (groupStoreIndex, undoStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build( keyMappingA, keyMappingB, groupStoreIndex, undoStoreIndex, collectorC, nextNodesTupleLifecycle, @@ -136,7 +135,7 @@ public interface GroupNodeConstructor { twoKeysGroupBy(KeyA_ keyMappingA, KeyB_ keyMappingB, CollectorC_ collectorC, CollectorD_ collectorD, GroupBy2Mapping2CollectorNodeBuilder builder) { return new GroupNodeConstructorWithAccumulate<>( - new Quadruple(keyMappingA, keyMappingB, collectorC, collectorD), + new Quadruple<>(keyMappingA, keyMappingB, collectorC, collectorD), (groupStoreIndex, undoStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build( keyMappingA, keyMappingB, groupStoreIndex, undoStoreIndex, collectorC, collectorD, nextNodesTupleLifecycle, @@ -147,7 +146,7 @@ public interface GroupNodeConstructor { threeKeysGroupBy(KeyA_ keyMappingA, KeyB_ keyMappingB, KeyC_ keyMappingC, GroupBy3Mapping0CollectorNodeBuilder builder) { return new GroupNodeConstructorWithoutAccumulate<>( - new Triple(keyMappingA, keyMappingB, keyMappingC), + new Triple<>(keyMappingA, keyMappingB, keyMappingC), (groupStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build(keyMappingA, keyMappingB, keyMappingC, groupStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode)); @@ -157,7 +156,7 @@ public interface GroupNodeConstructor { threeKeysGroupBy(KeyA_ keyMappingA, KeyB_ keyMappingB, KeyC_ keyMappingC, CollectorD_ collectorD, GroupBy3Mapping1CollectorNodeBuilder builder) { return new GroupNodeConstructorWithAccumulate<>( - new Quadruple(keyMappingA, keyMappingB, keyMappingC, collectorD), + new Quadruple<>(keyMappingA, keyMappingB, keyMappingC, collectorD), (groupStoreIndex, undoStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build( keyMappingA, keyMappingB, keyMappingC, groupStoreIndex, undoStoreIndex, collectorD, nextNodesTupleLifecycle, @@ -168,7 +167,7 @@ public interface GroupNodeConstructor { fourKeysGroupBy(KeyA_ keyMappingA, KeyB_ keyMappingB, KeyC_ keyMappingC, KeyD_ keyMappingD, GroupBy4Mapping0CollectorNodeBuilder builder) { return new GroupNodeConstructorWithoutAccumulate<>( - new Quadruple(keyMappingA, keyMappingB, keyMappingC, keyMappingD), + new Quadruple<>(keyMappingA, keyMappingB, keyMappingC, keyMappingD), (groupStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode) -> builder.build(keyMappingA, keyMappingB, keyMappingC, keyMappingD, groupStoreIndex, nextNodesTupleLifecycle, outputStoreSize, environmentMode)); @@ -312,10 +311,8 @@ AbstractNode build(KeyA_ keyMappingA, TupleLifecycle nextNodesTupleLifecycle, int outputStoreSize, EnvironmentMode environmentMode); } - > void build(NodeBuildHelper buildHelper, - BavetAbstractConstraintStream parentTupleSource, - BavetAbstractConstraintStream aftStream, List aftStreamChildList, - BavetAbstractConstraintStream thisStream, - List thisStreamChildList, EnvironmentMode environmentMode); + void build(AbstractNodeBuildHelper buildHelper, Stream_ parentTupleSource, + Stream_ aftStream, List aftStreamChildList, Stream_ thisStream, List thisStreamChildList, + EnvironmentMode environmentMode); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructorWithAccumulate.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructorWithAccumulate.java index cc0715b7c3..8ea0e87de2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructorWithAccumulate.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructorWithAccumulate.java @@ -3,8 +3,6 @@ import java.util.List; import java.util.Objects; -import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; @@ -21,19 +19,17 @@ public GroupNodeConstructorWithAccumulate(Object equalityKey, } @Override - public > void build(NodeBuildHelper buildHelper, - BavetAbstractConstraintStream parentTupleSource, - BavetAbstractConstraintStream aftStream, List aftStreamChildList, - BavetAbstractConstraintStream bridgeStream, List bridgeStreamChildList, + public void build(AbstractNodeBuildHelper buildHelper, Stream_ parentTupleSource, + Stream_ aftStream, List aftStreamChildList, Stream_ bridgeStream, List bridgeStreamChildList, EnvironmentMode environmentMode) { if (!bridgeStreamChildList.isEmpty()) { throw new IllegalStateException("Impossible state: the stream (" + bridgeStream + ") has an non-empty childStreamList (" + bridgeStreamChildList + ") but it's a groupBy bridge."); } - int groupStoreIndex = buildHelper.reserveTupleStoreIndex(parentTupleSource); - int undoStoreIndex = buildHelper.reserveTupleStoreIndex(parentTupleSource); + var groupStoreIndex = buildHelper.reserveTupleStoreIndex(parentTupleSource); + var undoStoreIndex = buildHelper.reserveTupleStoreIndex(parentTupleSource); TupleLifecycle tupleLifecycle = buildHelper.getAggregatedTupleLifecycle(aftStreamChildList); - int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); + var outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); var node = nodeConstructorFunction.apply(groupStoreIndex, undoStoreIndex, tupleLifecycle, outputStoreSize, environmentMode); buildHelper.addNode(node, bridgeStream); @@ -45,7 +41,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - GroupNodeConstructorWithAccumulate that = (GroupNodeConstructorWithAccumulate) object; + var that = (GroupNodeConstructorWithAccumulate) object; return Objects.equals(equalityKey, that.equalityKey); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructorWithoutAccumulate.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructorWithoutAccumulate.java index 955951bb17..4a462d0715 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructorWithoutAccumulate.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/GroupNodeConstructorWithoutAccumulate.java @@ -3,8 +3,6 @@ import java.util.List; import java.util.Objects; -import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; @@ -21,18 +19,16 @@ public GroupNodeConstructorWithoutAccumulate(Object equalityKey, } @Override - public > void build(NodeBuildHelper buildHelper, - BavetAbstractConstraintStream parentTupleSource, - BavetAbstractConstraintStream aftStream, List aftStreamChildList, - BavetAbstractConstraintStream bridgeStream, List bridgeStreamChildList, + public void build(AbstractNodeBuildHelper buildHelper, Stream_ parentTupleSource, + Stream_ aftStream, List aftStreamChildList, Stream_ bridgeStream, List bridgeStreamChildList, EnvironmentMode environmentMode) { if (!bridgeStreamChildList.isEmpty()) { throw new IllegalStateException("Impossible state: the stream (" + bridgeStream + ") has an non-empty childStreamList (" + bridgeStreamChildList + ") but it's a groupBy bridge."); } - int groupStoreIndex = buildHelper.reserveTupleStoreIndex(parentTupleSource); + var groupStoreIndex = buildHelper.reserveTupleStoreIndex(parentTupleSource); TupleLifecycle tupleLifecycle = buildHelper.getAggregatedTupleLifecycle(aftStreamChildList); - int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); + var outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); var node = nodeConstructorFunction.apply(groupStoreIndex, tupleLifecycle, outputStoreSize, environmentMode); buildHelper.addNode(node, bridgeStream); } @@ -43,7 +39,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - GroupNodeConstructorWithoutAccumulate that = (GroupNodeConstructorWithoutAccumulate) object; + var that = (GroupNodeConstructorWithoutAccumulate) object; return Objects.equals(equalityKey, that.equalityKey); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/ConditionalTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/ConditionalTupleLifecycle.java index ae63e60a83..d4a4d8257b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/ConditionalTupleLifecycle.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/ConditionalTupleLifecycle.java @@ -3,12 +3,12 @@ import java.util.Objects; import java.util.function.Predicate; -record ConditionalTupleLifecycle(TupleLifecycle downstreamLifecycle, +public record ConditionalTupleLifecycle(TupleLifecycle downstreamLifecycle, TuplePredicate predicate) implements TupleLifecycle { - ConditionalTupleLifecycle { + public ConditionalTupleLifecycle { Objects.requireNonNull(downstreamLifecycle); Objects.requireNonNull(predicate); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/AbstractForEachUniNode.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/AbstractForEachUniNode.java index 5e9fe3b436..6f89934d00 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/AbstractForEachUniNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/AbstractForEachUniNode.java @@ -14,20 +14,20 @@ * Filtering nodes are expensive. * Considering that most streams start with a nullity check on genuine planning variables, * it makes sense to create a specialized version of the node for this case ({@link ForEachExcludingUnassignedUniNode}), - * as opposed to forcing an extra filter node on the generic case ({@link ForEachIncludingUnassignedUniNode}). + * as opposed to forcing an extra filter node on the generic case ({@link ForEachUniNode}). * * @param */ public abstract sealed class AbstractForEachUniNode extends AbstractNode - permits ForEachExcludingUnassignedUniNode, ForEachIncludingUnassignedUniNode { + permits ForEachExcludingUnassignedUniNode, ForEachUniNode, ForEachStaticUniNode { private final Class forEachClass; private final int outputStoreSize; private final StaticPropagationQueue> propagationQueue; protected final Map> tupleMap = new IdentityHashMap<>(1000); - public AbstractForEachUniNode(Class forEachClass, TupleLifecycle> nextNodesTupleLifecycle, + protected AbstractForEachUniNode(Class forEachClass, TupleLifecycle> nextNodesTupleLifecycle, int outputStoreSize) { this.forEachClass = forEachClass; this.outputStoreSize = outputStoreSize; @@ -35,8 +35,8 @@ public AbstractForEachUniNode(Class forEachClass, TupleLifecycle> } public void insert(A a) { - UniTuple tuple = new UniTuple<>(a, outputStoreSize); - UniTuple old = tupleMap.put(a, tuple); + var tuple = new UniTuple<>(a, outputStoreSize); + var old = tupleMap.put(a, tuple); if (old != null) { throw new IllegalStateException("The fact (" + a + ") was already inserted, so it cannot insert again."); } @@ -45,8 +45,8 @@ public void insert(A a) { public abstract void update(A a); - protected final void innerUpdate(A a, UniTuple tuple) { - TupleState state = tuple.state; + protected final void updateExisting(A a, UniTuple tuple) { + var state = tuple.state; if (state.isDirty()) { if (state == TupleState.DYING || state == TupleState.ABORTING) { throw new IllegalStateException("The fact (" + a + ") was retracted, so it cannot update."); @@ -58,11 +58,15 @@ protected final void innerUpdate(A a, UniTuple tuple) { } public void retract(A a) { - UniTuple tuple = tupleMap.remove(a); + var tuple = tupleMap.remove(a); if (tuple == null) { throw new IllegalStateException("The fact (" + a + ") was never inserted, so it cannot retract."); } - TupleState state = tuple.state; + retractExisting(a, tuple); + } + + protected void retractExisting(A a, UniTuple tuple) { + var state = tuple.state; if (state.isDirty()) { if (state == TupleState.DYING || state == TupleState.ABORTING) { throw new IllegalStateException("The fact (" + a + ") was already retracted, so it cannot retract."); @@ -82,6 +86,10 @@ public final Class getForEachClass() { return forEachClass; } + public boolean supportsIndividualUpdates() { + return true; + } + @Override public final String toString() { return super.toString() + "(" + forEachClass.getSimpleName() + ")"; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachExcludingUnassignedUniNode.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachExcludingUnassignedUniNode.java index f3a26e20fa..cadde434df 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachExcludingUnassignedUniNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachExcludingUnassignedUniNode.java @@ -26,22 +26,23 @@ public void insert(A a) { @Override public void update(A a) { - UniTuple tuple = tupleMap.get(a); + var tuple = tupleMap.get(a); if (tuple == null) { // The tuple was never inserted because it did not pass the filter. insert(a); } else if (filter.test(a)) { - innerUpdate(a, tuple); - } else { - super.retract(a); // Call super.retract() to avoid testing the filter again. + updateExisting(a, tuple); + } else { // Tuple no longer passes the filter. + retract(a); } } @Override public void retract(A a) { - if (!filter.test(a)) { // The tuple was never inserted because it did not pass the filter. + var tuple = tupleMap.remove(a); + if (tuple == null) { // The tuple was never inserted because it did not pass the filter. return; } - super.retract(a); + super.retractExisting(a, tuple); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachFromSolutionUniNode.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachFromSolutionUniNode.java new file mode 100644 index 0000000000..39de817be1 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachFromSolutionUniNode.java @@ -0,0 +1,68 @@ +package ai.timefold.solver.core.impl.bavet.uni; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Objects; + +import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.preview.api.move.SolutionExtractor; +import ai.timefold.solver.core.preview.api.move.SolutionView; + +public final class ForEachFromSolutionUniNode + extends ForEachUniNode { + + private final SolutionExtractor solutionExtractor; + + public ForEachFromSolutionUniNode(Class forEachClass, SolutionExtractor solutionExtractor, + TupleLifecycle> nextNodesTupleLifecycle, int outputStoreSize) { + super(forEachClass, nextNodesTupleLifecycle, outputStoreSize); + this.solutionExtractor = Objects.requireNonNull(solutionExtractor); + } + + public void read(SolutionView solutionView, Solution_ solution) { + var seenFactSet = Collections.newSetFromMap(new IdentityHashMap()); + solutionExtractor.apply(solutionView, solution).forEach(a -> { + if (seenFactSet.contains(a)) { // Eliminate duplicates in the source data. + return; + } + seenFactSet.add(a); + var tuple = tupleMap.get(a); + if (tuple == null) { + super.insert(a); + } else { + updateExisting(a, tuple); + } + }); + // Retract all tuples that were not seen in the source data. + var iterator = tupleMap.entrySet().iterator(); + while (iterator.hasNext()) { + var entry = iterator.next(); + var fact = entry.getKey(); + if (!seenFactSet.contains(fact)) { + iterator.remove(); + retractExisting(fact, entry.getValue()); + } + } + } + + @Override + public void insert(A a) { + throw new IllegalStateException("Impossible state: solution-based node cannot insert."); + } + + @Override + public void update(A a) { + throw new IllegalStateException("Impossible state: solution-based node cannot update."); + } + + @Override + public void retract(A a) { + throw new IllegalStateException("Impossible state: solution-based node cannot retract."); + } + + @Override + public boolean supportsIndividualUpdates() { + return false; + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachStaticUniNode.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachStaticUniNode.java new file mode 100644 index 0000000000..59ccc88976 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachStaticUniNode.java @@ -0,0 +1,44 @@ +package ai.timefold.solver.core.impl.bavet.uni; + +import java.util.Collection; +import java.util.Objects; + +import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; + +public final class ForEachStaticUniNode + extends AbstractForEachUniNode { + + private final Collection source; + + public ForEachStaticUniNode(Class forEachClass, Collection source, + TupleLifecycle> nextNodesTupleLifecycle, int outputStoreSize) { + super(forEachClass, nextNodesTupleLifecycle, outputStoreSize); + this.source = Objects.requireNonNull(source); + } + + public void initialize() { + source.forEach(super::insert); + } + + @Override + public void insert(A a) { + throw new IllegalStateException("Impossible state: static node cannot insert."); + } + + @Override + public void update(A a) { + throw new IllegalStateException("Impossible state: static node cannot update."); + } + + @Override + public void retract(A a) { + throw new IllegalStateException("Impossible state: static node cannot retract."); + } + + @Override + public boolean supportsIndividualUpdates() { + return false; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachIncludingUnassignedUniNode.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachUniNode.java similarity index 63% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachIncludingUnassignedUniNode.java rename to core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachUniNode.java index 0de78b7e08..105b984b0f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachIncludingUnassignedUniNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/uni/ForEachUniNode.java @@ -3,20 +3,21 @@ import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; -public final class ForEachIncludingUnassignedUniNode extends AbstractForEachUniNode { +public sealed class ForEachUniNode + extends AbstractForEachUniNode permits ForEachFromSolutionUniNode { - public ForEachIncludingUnassignedUniNode(Class forEachClass, TupleLifecycle> nextNodesTupleLifecycle, + public ForEachUniNode(Class forEachClass, TupleLifecycle> nextNodesTupleLifecycle, int outputStoreSize) { super(forEachClass, nextNodesTupleLifecycle, outputStoreSize); } @Override public void update(A a) { - UniTuple tuple = tupleMap.get(a); + var tuple = tupleMap.get(a); if (tuple == null) { throw new IllegalStateException("The fact (" + a + ") was never inserted, so it cannot update."); } - innerUpdate(a, tuple); + updateExisting(a, tuple); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/visual/NodeGraph.java b/core/src/main/java/ai/timefold/solver/core/impl/bavet/visual/NodeGraph.java index 4a53b0c686..53dfa18a97 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/visual/NodeGraph.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/bavet/visual/NodeGraph.java @@ -16,26 +16,26 @@ import ai.timefold.solver.core.impl.bavet.common.AbstractNode; import ai.timefold.solver.core.impl.bavet.common.AbstractTwoInputNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetStreamBinaryOperation; import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetStreamBinaryOperation; import ai.timefold.solver.core.impl.score.stream.bavet.uni.BavetForEachUniConstraintStream; public record NodeGraph(Solution_ solution, List sources, List edges, List> sinks) { - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) public static NodeGraph of(Solution_ solution, List nodeList, Set constraintSet, - Function> nodeToStreamFunction, - Function, AbstractNode> streamToParentNodeFunction) { + Function> nodeToStreamFunction, + Function, AbstractNode> streamToParentNodeFunction) { var sourceList = new ArrayList(); var edgeList = new ArrayList(); for (var node : nodeList) { var nodeCreator = nodeToStreamFunction.apply(node); if (nodeCreator instanceof BavetForEachUniConstraintStream) { sourceList.add(node); - } else if (nodeCreator instanceof BavetStreamBinaryOperation binaryOperation) { + } else if (nodeCreator instanceof BavetStreamBinaryOperation binaryOperation) { var leftParent = streamToParentNodeFunction.apply(binaryOperation.getLeftParent()); edgeList.add(new GraphEdge(leftParent, node)); var rightParent = streamToParentNodeFunction.apply(binaryOperation.getRightParent()); @@ -48,7 +48,7 @@ public static NodeGraph of(Solution_ solution, List>(); for (var constraint : constraintSet) { var castConstraint = (BavetConstraint) constraint; - var stream = (BavetAbstractConstraintStream) castConstraint.getScoringConstraintStream(); + var stream = (BavetAbstractConstraintStream) castConstraint.getScoringConstraintStream(); var node = streamToParentNodeFunction.apply(stream); sinkList.add(new GraphSink<>(node, castConstraint)); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/ReflectionHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/ReflectionHelper.java index 951d92a05e..b2ace5035b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/ReflectionHelper.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/ReflectionHelper.java @@ -315,14 +315,15 @@ public static boolean isList(Type type) { return false; } - public static List transformArrayToList(Object arrayObject) { + @SuppressWarnings("unchecked") + public static List transformArrayToList(Object arrayObject) { if (arrayObject == null) { return null; } - int arrayLength = Array.getLength(arrayObject); - List 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(arrayLength); + for (var i = 0; i < arrayLength; i++) { + list.add((Value_) Array.get(arrayObject, i)); } return list; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java index 3b50662472..a614549126 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java @@ -102,7 +102,8 @@ public boolean isCountable() { return countable; } - protected ValueRange readValueRange(Object bean) { + @SuppressWarnings("unchecked") + protected ValueRange readValueRange(Object bean) { Object valueRangeObject = memberAccessor.executeGetter(bean); if (valueRangeObject == null) { throw new IllegalStateException("The @" + ValueRangeProvider.class.getSimpleName() @@ -110,9 +111,9 @@ protected ValueRange readValueRange(Object bean) { + ") called on bean (" + bean + ") must not return a null valueRangeObject (" + valueRangeObject + ")."); } - ValueRange valueRange; + ValueRange valueRange; if (collectionWrapping || arrayWrapping) { - List list = collectionWrapping ? transformCollectionToList((Collection) valueRangeObject) + List list = collectionWrapping ? transformCollectionToList((Collection) 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)) { @@ -129,7 +130,7 @@ protected ValueRange readValueRange(Object bean) { } valueRange = new ListValueRange<>(list); } else { - valueRange = (ValueRange) valueRangeObject; + valueRange = (ValueRange) valueRangeObject; } valueRange = doNullInValueRangeWrapping(valueRange); if (valueRange.isEmpty()) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/CompositeValueRangeDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/CompositeValueRangeDescriptor.java index 3bbc0ec387..a9e7105eb8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/CompositeValueRangeDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/CompositeValueRangeDescriptor.java @@ -51,10 +51,11 @@ public boolean isEntityIndependent() { } @Override - public ValueRange extractValueRange(Solution_ solution, Object entity) { + public ValueRange extractValueRange(Solution_ solution, Object entity) { return innerExtractValueRange(solution, entity); } + @SuppressWarnings("unchecked") private ValueRange innerExtractValueRange(Solution_ solution, Object entity) { var childValueRangeList = new ArrayList>(childValueRangeDescriptorList.size()); for (var valueRangeDescriptor : childValueRangeDescriptorList) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/FromEntityPropertyValueRangeDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/FromEntityPropertyValueRangeDescriptor.java index 0a87c65afa..0c89ef4c62 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/FromEntityPropertyValueRangeDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/FromEntityPropertyValueRangeDescriptor.java @@ -26,7 +26,7 @@ public boolean isEntityIndependent() { } @Override - public ValueRange extractValueRange(Solution_ solution, Object entity) { + public ValueRange extractValueRange(Solution_ solution, Object entity) { return readValueRange(entity); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/FromSolutionPropertyValueRangeDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/FromSolutionPropertyValueRangeDescriptor.java index 235e51a7e8..70e4600703 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/FromSolutionPropertyValueRangeDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/FromSolutionPropertyValueRangeDescriptor.java @@ -28,7 +28,7 @@ public boolean isEntityIndependent() { } @Override - public ValueRange extractValueRange(Solution_ solution, Object entity) { + public ValueRange extractValueRange(Solution_ solution, Object entity) { return readValueRange(solution); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/ValueRangeDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/ValueRangeDescriptor.java index b14f9991d2..2cfc179cb4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/ValueRangeDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/ValueRangeDescriptor.java @@ -40,7 +40,7 @@ public interface ValueRangeDescriptor { * use {@link EntityIndependentValueRangeDescriptor#extractValueRange} instead. * @return never null */ - ValueRange extractValueRange(Solution_ solution, Object entity); + ValueRange extractValueRange(Solution_ solution, Object entity); /** * @param solution never null diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/FromEntityPropertyValueSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/FromEntityPropertyValueSelector.java index 48d6ea2ade..84e477c07f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/FromEntityPropertyValueSelector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/FromEntityPropertyValueSelector.java @@ -69,7 +69,7 @@ public long getSize(Object entity) { @Override public Iterator iterator(Object entity) { - ValueRange valueRange = (ValueRange) valueRangeDescriptor.extractValueRange(workingSolution, entity); + ValueRange valueRange = valueRangeDescriptor.extractValueRange(workingSolution, entity); if (!randomSelection) { return ((CountableValueRange) valueRange).createOriginalIterator(); } else { @@ -79,7 +79,7 @@ public Iterator iterator(Object entity) { @Override public Iterator endingIterator(Object entity) { - ValueRange valueRange = (ValueRange) valueRangeDescriptor.extractValueRange(workingSolution, entity); + ValueRange valueRange = valueRangeDescriptor.extractValueRange(workingSolution, entity); return ((CountableValueRange) valueRange).createOriginalIterator(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveStreamSessionFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/move/MoveStreamSessionFactory.java new file mode 100644 index 0000000000..d57d5a2678 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/MoveStreamSessionFactory.java @@ -0,0 +1,9 @@ +package ai.timefold.solver.core.impl.move; + +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; + +public interface MoveStreamSessionFactory { + + MoveStreamSession createMoveStreamSession(Solution_ workingSolution); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/AbstractDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/AbstractDataStream.java new file mode 100644 index 0000000000..36385509b2 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/AbstractDataStream.java @@ -0,0 +1,76 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import ai.timefold.solver.core.impl.bavet.common.BavetStream; +import ai.timefold.solver.core.impl.bavet.common.TupleSource; +import ai.timefold.solver.core.impl.move.dataset.common.DataNodeBuildHelper; + +public abstract class AbstractDataStream + implements BavetStream { + + protected final DefaultDatasetFactory datasetFactory; + protected final AbstractDataStream parent; + protected final List> childStreamList = new ArrayList<>(2); + + protected AbstractDataStream(DefaultDatasetFactory datasetFactory, AbstractDataStream parent) { + this.datasetFactory = datasetFactory; + this.parent = parent; + } + + public final > Stream_ shareAndAddChild(Stream_ stream) { + return datasetFactory.share(stream, childStreamList::add); + } + + // ************************************************************************ + // Node creation + // ************************************************************************ + + public void collectActiveDataStreams(Set> constraintStreamSet) { + if (parent == null) { // Maybe a join/ifExists/forEach forgot to override this? + throw new IllegalStateException("Impossible state: the stream (" + this + ") does not have a parent."); + } + parent.collectActiveDataStreams(constraintStreamSet); + constraintStreamSet.add(this); + } + + /** + * Returns the stream which first produced the tuple that this stream operates on. + * If a stream does not have a single parent nor is it a source, it is expected to override this method. + * + * @return this if {@link TupleSource}, otherwise parent's tuple source. + */ + public AbstractDataStream getTupleSource() { + if (this instanceof TupleSource) { + return this; + } else if (parent == null) { // Maybe some stream forgot to override this? + throw new IllegalStateException("Impossible state: the stream (" + this + ") does not have a parent."); + } + return parent.getTupleSource(); + } + + public abstract void buildNode(DataNodeBuildHelper buildHelper); + + // ************************************************************************ + // Helper methods + // ************************************************************************ + + protected void assertEmptyChildStreamList() { + if (!childStreamList.isEmpty()) { + throw new IllegalStateException( + "Impossible state: the stream (" + this + ") has a non-empty childStreamList (" + childStreamList + ")."); + } + } + + @Override + public final AbstractDataStream getParent() { + return parent; + } + + public final List> getChildStreamList() { + return childStreamList; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/AbstractUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/AbstractUniDataStream.java new file mode 100644 index 0000000000..d4ecf311ca --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/AbstractUniDataStream.java @@ -0,0 +1,85 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.function.Predicate; + +import ai.timefold.solver.core.api.score.stream.bi.BiJoiner; +import ai.timefold.solver.core.impl.bavet.bi.joiner.BiJoinerComber; +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.move.dataset.common.bridge.ForeBridgeUniDataStream; +import ai.timefold.solver.core.preview.api.move.UniDataStream; + +import org.jspecify.annotations.NonNull; + +public abstract class AbstractUniDataStream extends AbstractDataStream + implements UniDataStream { + + protected AbstractUniDataStream(DefaultDatasetFactory datasetFactory) { + super(datasetFactory, null); + } + + protected AbstractUniDataStream(DefaultDatasetFactory datasetFactory, AbstractDataStream parent) { + super(datasetFactory, parent); + } + + @Override + public final @NonNull UniDataStream filter(@NonNull Predicate predicate) { + return shareAndAddChild(new FilterUniDataStream<>(datasetFactory, this, predicate)); + } + + @SafeVarargs + @Override + public final @NonNull UniDataStream ifExists(@NonNull Class otherClass, + @NonNull BiJoiner... joiners) { + return ifExists(datasetFactory.forEach(otherClass), joiners); + } + + @SafeVarargs + @Override + public final @NonNull UniDataStream ifExists(@NonNull UniDataStream otherStream, + @NonNull BiJoiner... joiners) { + return ifExistsOrNot(true, otherStream, joiners); + } + + @SafeVarargs + @Override + public final @NonNull UniDataStream ifExistsIncludingUnassigned(@NonNull Class otherClass, + @NonNull BiJoiner... joiners) { + return ifExists(datasetFactory.forEachIncludingUnassigned(otherClass), joiners); + } + + @SafeVarargs + @Override + public final @NonNull UniDataStream ifNotExists(@NonNull Class otherClass, + @NonNull BiJoiner... joiners) { + return ifExistsOrNot(false, datasetFactory.forEach(otherClass), joiners); + } + + @SafeVarargs + @Override + public final @NonNull UniDataStream ifNotExists(@NonNull UniDataStream otherStream, + @NonNull BiJoiner... joiners) { + return ifExistsOrNot(false, otherStream, joiners); + } + + @SafeVarargs + @Override + public final @NonNull UniDataStream ifNotExistsIncludingUnassigned(@NonNull Class otherClass, + @NonNull BiJoiner... joiners) { + return ifNotExists(datasetFactory.forEachIncludingUnassigned(otherClass), joiners); + } + + private UniDataStream ifExistsOrNot(boolean shouldExist, UniDataStream otherStream, + BiJoiner[] joiners) { + var other = (AbstractUniDataStream) otherStream; + var joinerComber = BiJoinerComber.comb(joiners); + var parentBridgeB = other.shareAndAddChild(new ForeBridgeUniDataStream(datasetFactory, other)); + return datasetFactory.share(new IfExistsUniDataStream<>(datasetFactory, this, parentBridgeB, shouldExist, + joinerComber.getMergedJoiner(), joinerComber.getMergedFiltering()), childStreamList::add); + } + + public Dataset> createDataset() { + var stream = shareAndAddChild(new TerminalUniDataStream<>(datasetFactory, this)); + return stream.getDataset(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/Dataset.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/Dataset.java new file mode 100644 index 0000000000..6999aa6965 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/Dataset.java @@ -0,0 +1,39 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.Objects; +import java.util.Set; + +import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; + +public final class Dataset { + + private final DefaultDatasetFactory defaultDatasetFactory; + private final AbstractDataStream parent; + + public Dataset(DefaultDatasetFactory defaultDatasetFactory, + AbstractDataStream parent) { + this.defaultDatasetFactory = Objects.requireNonNull(defaultDatasetFactory); + this.parent = Objects.requireNonNull(parent); + } + + public void collectActiveDataStreams(Set> dataStreamSet) { + parent.collectActiveDataStreams(dataStreamSet); + } + + public DatasetInstance instantiate(int inputStoreIndex) { + return new DatasetInstance<>(this, inputStoreIndex); + } + + @Override + public boolean equals(Object entity) { + if (!(entity instanceof Dataset dataset)) { + return false; + } + return Objects.equals(defaultDatasetFactory, dataset.defaultDatasetFactory) && Objects.equals(parent, dataset.parent); + } + + @Override + public int hashCode() { + return Objects.hash(defaultDatasetFactory, parent); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetInstance.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetInstance.java new file mode 100644 index 0000000000..193eda9a8b --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetInstance.java @@ -0,0 +1,49 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.Iterator; +import java.util.Objects; +import java.util.Random; + +import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; +import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; +import ai.timefold.solver.core.impl.util.ElementAwareList; +import ai.timefold.solver.core.impl.util.ElementAwareListEntry; + +public final class DatasetInstance + implements TupleLifecycle { + + private final Dataset parent; + private final int inputStoreIndex; + private final ElementAwareList tupleList = new ElementAwareList<>(); + + public DatasetInstance(Dataset parent, int inputStoreIndex) { + this.parent = Objects.requireNonNull(parent); + this.inputStoreIndex = inputStoreIndex; + } + + @Override + public void insert(Tuple_ tuple) { + var entry = tupleList.add(tuple); + tuple.setStore(inputStoreIndex, entry); + } + + @Override + public void update(Tuple_ tuple) { + // No need to do anything. + } + + @Override + public void retract(Tuple_ tuple) { + ElementAwareListEntry entry = tuple.removeStore(inputStoreIndex); + entry.remove(); + } + + public Iterator iterator() { + return tupleList.iterator(); + } + + public Iterator iterator(Random workingRandom) { + return tupleList.randomizedIterator(workingRandom); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetSession.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetSession.java new file mode 100644 index 0000000000..8466bc3951 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetSession.java @@ -0,0 +1,58 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.Collections; +import java.util.IdentityHashMap; + +import ai.timefold.solver.core.impl.bavet.AbstractSession; +import ai.timefold.solver.core.impl.bavet.NodeNetwork; +import ai.timefold.solver.core.impl.bavet.uni.ForEachFromSolutionUniNode; +import ai.timefold.solver.core.impl.bavet.uni.ForEachStaticUniNode; +import ai.timefold.solver.core.preview.api.move.SolutionView; + +public final class DatasetSession extends AbstractSession { + + private final ForEachStaticUniNode[] effectiveClassToStaticNodeArray; + private final ForEachFromSolutionUniNode[] effectiveClassToFromSolutionNodeArray; + + @SuppressWarnings("unchecked") + DatasetSession(NodeNetwork nodeNetwork) { + super(nodeNetwork); + var staticNodeSet = Collections.newSetFromMap(new IdentityHashMap, Boolean>()); + var fromSolutionNodeSet = Collections.newSetFromMap(new IdentityHashMap, Boolean>()); + nodeNetwork.getForEachNodes().forEach(node -> { + if (node instanceof ForEachStaticUniNode forEachStaticUniNode) { + staticNodeSet.add(forEachStaticUniNode); + } else if (node instanceof ForEachFromSolutionUniNode forEachFromSolutionUniNode) { + fromSolutionNodeSet.add(forEachFromSolutionUniNode); + } + }); + this.effectiveClassToStaticNodeArray = + staticNodeSet.isEmpty() ? null : staticNodeSet.toArray(ForEachStaticUniNode[]::new); + this.effectiveClassToFromSolutionNodeArray = + fromSolutionNodeSet.isEmpty() ? null : fromSolutionNodeSet.toArray(ForEachFromSolutionUniNode[]::new); + } + + public void initialize() { + if (effectiveClassToStaticNodeArray == null) { + return; + } + for (var node : effectiveClassToStaticNodeArray) { + node.initialize(); + } + } + + public void updateWorkingSolution(SolutionView solutionView, Solution_ solution) { + if (effectiveClassToFromSolutionNodeArray == null) { + return; + } + for (var node : effectiveClassToFromSolutionNodeArray) { + node.read(solutionView, solution); + } + } + + @Override + public void settle() { + super.settle(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetSessionFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetSessionFactory.java new file mode 100644 index 0000000000..76269f5940 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DatasetSessionFactory.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.move.dataset; + +public final class DatasetSessionFactory { + + public DatasetSession buildSession(Solution_ workingSolution) { + return null; + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DefaultDatasetFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DefaultDatasetFactory.java new file mode 100644 index 0000000000..c1046cf1de --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/DefaultDatasetFactory.java @@ -0,0 +1,134 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import ai.timefold.solver.core.api.score.stream.Joiners; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.preview.api.move.DatasetFactory; +import ai.timefold.solver.core.preview.api.move.SolutionExtractor; +import ai.timefold.solver.core.preview.api.move.UniDataStream; + +import org.jspecify.annotations.NonNull; + +public final class DefaultDatasetFactory implements DatasetFactory { + + private final SolutionDescriptor solutionDescriptor; + private final Map, AbstractDataStream> sharingStreamMap = new HashMap<>(256); + + public DefaultDatasetFactory(SolutionDescriptor solutionDescriptor) { + this.solutionDescriptor = solutionDescriptor; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public @NonNull UniDataStream forEach(@NonNull Class sourceClass) { + assertValidForEachType(sourceClass); + var entityDescriptor = solutionDescriptor.findEntityDescriptor(sourceClass); + if (entityDescriptor == null) { + // Not genuine or shadow entity; no need for filtering. + return share(new ForEachDataStream<>(this, sourceClass)); + } + var listVariableDescriptor = solutionDescriptor.getListVariableDescriptor(); + if (listVariableDescriptor == null || !listVariableDescriptor.acceptsValueType(sourceClass)) { + // No applicable list variable; don't need to check inverse relationships. + return share(new ForEachDataStream<>(this, sourceClass, entityDescriptor.getHasNoNullVariablesPredicateBasicVar())); + } + var entityClass = listVariableDescriptor.getEntityDescriptor().getEntityClass(); + if (entityClass == sourceClass) { + throw new IllegalStateException("Impossible state: entityClass (%s) and sourceClass (%s) are the same." + .formatted(entityClass.getCanonicalName(), sourceClass.getCanonicalName())); + } + var shadowDescriptor = listVariableDescriptor.getInverseRelationShadowVariableDescriptor(); + if (shadowDescriptor == null) { + // The list variable element doesn't have the @InverseRelationShadowVariable annotation. + // We don't want the users to be forced to implement it in quickstarts, + // so we'll do this expensive thing instead. + return forEachIncludingUnassigned(sourceClass) + .ifExists((Class) entityClass, + Joiners.filtering(listVariableDescriptor.getInListPredicate())); + } else { // We have the inverse relation variable, so we can read its value directly. + return share(new ForEachDataStream<>(this, sourceClass, entityDescriptor.getHasNoNullVariablesPredicateListVar())); + } + } + + public UniDataStream forEachIncludingUnassigned(@NonNull Class sourceClass) { + assertValidForEachType(sourceClass); + return share(new ForEachDataStream<>(this, sourceClass)); + } + + public void assertValidForEachType(Class fromType) { + SolutionDescriptor solutionDescriptor = getSolutionDescriptor(); + Set> problemFactOrEntityClassSet = solutionDescriptor.getProblemFactOrEntityClassSet(); + /* + * Need to support the following situations: + * 1/ FactType == FromType; querying for the declared type. + * 2/ FromType extends/implements FactType; querying for impl type where declared type is its interface. + * 3/ FromType super FactType; querying for interface where declared type is its implementation. + */ + boolean hasMatchingType = problemFactOrEntityClassSet.stream() + .anyMatch(factType -> fromType.isAssignableFrom(factType) || factType.isAssignableFrom(fromType)); + if (!hasMatchingType) { + List canonicalClassNameList = problemFactOrEntityClassSet.stream() + .map(Class::getCanonicalName) + .sorted() + .toList(); + throw new IllegalArgumentException("Cannot use class (" + fromType.getCanonicalName() + + ") in a data stream as it is neither the same as, nor a superclass or superinterface of " + + "one of planning entities or problem facts.\n" + + "Ensure that all forEach(), join(), ifExists() and ifNotExists() building blocks only reference " + + "classes assignable from planning entities or problem facts (" + canonicalClassNameList + ") " + + "annotated on the planning solution (" + solutionDescriptor.getSolutionClass().getCanonicalName() + + ")."); + } + } + + @Override + public @NonNull UniDataStream forEach(@NonNull Class sourceClass, + @NonNull SolutionExtractor extractor) { + assertValidForEachType(sourceClass); + return share(new ForEachDataStream<>(this, sourceClass, extractor)); + } + + @Override + public @NonNull UniDataStream forEach(@NonNull Class sourceClass, @NonNull Collection collection) { + // Not checking for valid from type because the collection is designed to inject arbitrary data into the stream. + return share(new ForEachDataStream<>(this, sourceClass, collection)); + } + + public > Stream_ share(Stream_ stream) { + return share(stream, t -> { + }); + } + + /** + * Enables node sharing. + * If a stream already exists in this factory, it replaces it with the old copy. + * {@link AbstractDataStream} implement equals/hashcode ignoring child streams. + *

+ * {@link DatasetSessionFactory#buildSession(Object)} needs this to happen for all streams. + *

+ * This must be called before the stream receives child streams. + * + * @param stream never null + * @param consumer never null + * @param the {@link AbstractDataStream} subclass + * @return never null + */ + @SuppressWarnings("unchecked") + public > Stream_ share(Stream_ stream, Consumer consumer) { + return (Stream_) sharingStreamMap.computeIfAbsent(stream, k -> { + consumer.accept(stream); + return stream; + }); + } + + public SolutionDescriptor getSolutionDescriptor() { + return solutionDescriptor; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/FilterUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/FilterUniDataStream.java new file mode 100644 index 0000000000..e56d364bae --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/FilterUniDataStream.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.Objects; +import java.util.function.Predicate; + +import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.move.dataset.common.DataNodeBuildHelper; + +final class FilterUniDataStream + extends AbstractUniDataStream { + + private final Predicate predicate; + + public FilterUniDataStream(DefaultDatasetFactory datasetFactory, + AbstractUniDataStream parent, Predicate predicate) { + super(datasetFactory, parent); + this.predicate = predicate; + if (predicate == null) { + throw new IllegalArgumentException("The predicate (null) cannot be null."); + } + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + buildHelper.> putInsertUpdateRetract(this, childStreamList, + tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); + } + + @Override + public int hashCode() { + return Objects.hash(parent, predicate); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof FilterUniDataStream other) { + return parent == other.parent + && predicate == other.predicate; + } else { + return false; + } + } + + @Override + public String toString() { + return "Filter() with " + childStreamList.size() + " children"; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/ForEachDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/ForEachDataStream.java new file mode 100644 index 0000000000..0df8c50e9c --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/ForEachDataStream.java @@ -0,0 +1,113 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; + +import ai.timefold.solver.core.impl.bavet.common.TupleSource; +import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode; +import ai.timefold.solver.core.impl.bavet.uni.ForEachExcludingUnassignedUniNode; +import ai.timefold.solver.core.impl.bavet.uni.ForEachFromSolutionUniNode; +import ai.timefold.solver.core.impl.bavet.uni.ForEachStaticUniNode; +import ai.timefold.solver.core.impl.bavet.uni.ForEachUniNode; +import ai.timefold.solver.core.impl.move.dataset.common.DataNodeBuildHelper; +import ai.timefold.solver.core.preview.api.move.SolutionExtractor; + +public final class ForEachDataStream + extends AbstractUniDataStream + implements TupleSource { + + private final Class forEachClass; + private final Predicate filter; + private final SolutionExtractor extractor; + private final Collection source; + + public ForEachDataStream(DefaultDatasetFactory datasetFactory, Class forEachClass) { + this(datasetFactory, forEachClass, (Predicate) null); + } + + public ForEachDataStream(DefaultDatasetFactory datasetFactory, Class forEachClass, Predicate filter) { + super(datasetFactory, null); + this.forEachClass = forEachClass; + if (forEachClass == null) { + throw new IllegalArgumentException("The forEachClass (null) cannot be null."); + } + this.filter = filter; + this.extractor = null; + this.source = null; + } + + public ForEachDataStream(DefaultDatasetFactory datasetFactory, Class forEachClass, + SolutionExtractor extractor) { + super(datasetFactory, null); + this.forEachClass = forEachClass; + this.filter = null; + this.extractor = Objects.requireNonNull(extractor); + this.source = null; + } + + public ForEachDataStream(DefaultDatasetFactory datasetFactory, Class forEachClass, Collection source) { + super(datasetFactory, null); + this.forEachClass = forEachClass; + this.filter = null; + this.extractor = null; + this.source = Objects.requireNonNull(source); + } + + @Override + public void collectActiveDataStreams(Set> dataStreamSet) { + dataStreamSet.add(this); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + TupleLifecycle> tupleLifecycle = buildHelper.getAggregatedTupleLifecycle(childStreamList); + int outputStoreSize = buildHelper.extractTupleStoreSize(this); + var node = getNode(tupleLifecycle, outputStoreSize); + buildHelper.addNode(node, this, null); + } + + private AbstractForEachUniNode getNode(TupleLifecycle> tupleLifecycle, int outputStoreSize) { + if (source != null) { + return new ForEachStaticUniNode<>(forEachClass, source, tupleLifecycle, outputStoreSize); + } else if (extractor != null) { + return new ForEachFromSolutionUniNode<>(forEachClass, extractor, tupleLifecycle, outputStoreSize); + } else if (filter == null) { + return new ForEachUniNode<>(forEachClass, tupleLifecycle, outputStoreSize); + } else { + return new ForEachExcludingUnassignedUniNode<>(forEachClass, filter, tupleLifecycle, outputStoreSize); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ForEachDataStream that)) + return false; + return Objects.equals(forEachClass, that.forEachClass) && Objects.equals(filter, that.filter) + && Objects.equals(extractor, that.extractor) && Objects.equals(source, that.source); + } + + @Override + public int hashCode() { + return Objects.hash(forEachClass, filter, extractor, source); + } + + @Override + public String toString() { + if (source != null) { + return "Static ForEach(" + forEachClass.getSimpleName() + ") with " + childStreamList.size() + " children"; + } else if (extractor != null) { + return "ForEach(" + forEachClass.getSimpleName() + ") from solution with " + childStreamList.size() + " children"; + } else if (filter == null) { + return "ForEach(" + forEachClass.getSimpleName() + ") with " + childStreamList.size() + " children"; + } else { + return "ForEach(" + forEachClass.getSimpleName() + ") with filter and " + childStreamList.size() + " children"; + } + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/IfExistsUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/IfExistsUniDataStream.java new file mode 100644 index 0000000000..c468ff2bdb --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/IfExistsUniDataStream.java @@ -0,0 +1,114 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.Objects; +import java.util.Set; +import java.util.function.BiPredicate; + +import ai.timefold.solver.core.impl.bavet.bi.joiner.DefaultBiJoiner; +import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; +import ai.timefold.solver.core.impl.bavet.common.index.IndexerFactory; +import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.bavet.uni.IndexedIfExistsUniNode; +import ai.timefold.solver.core.impl.bavet.uni.UnindexedIfExistsUniNode; +import ai.timefold.solver.core.impl.move.dataset.common.BavetIfExistsDataStream; +import ai.timefold.solver.core.impl.move.dataset.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.move.dataset.common.bridge.ForeBridgeUniDataStream; + +final class IfExistsUniDataStream + extends AbstractUniDataStream + implements BavetIfExistsDataStream { + + private final AbstractUniDataStream parentA; + private final ForeBridgeUniDataStream parentBridgeB; + private final boolean shouldExist; + private final DefaultBiJoiner joiner; + private final BiPredicate filtering; + + public IfExistsUniDataStream(DefaultDatasetFactory datasetFactory, + AbstractUniDataStream parentA, + ForeBridgeUniDataStream parentBridgeB, + boolean shouldExist, + DefaultBiJoiner joiner, BiPredicate filtering) { + super(datasetFactory); + this.parentA = parentA; + this.parentBridgeB = parentBridgeB; + this.shouldExist = shouldExist; + this.joiner = joiner; + this.filtering = filtering; + } + + @Override + public void collectActiveDataStreams(Set> dataStreamSet) { + parentA.collectActiveDataStreams(dataStreamSet); + parentBridgeB.collectActiveDataStreams(dataStreamSet); + dataStreamSet.add(this); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); + var indexerFactory = new IndexerFactory<>(joiner); + var node = indexerFactory.hasJoiners() + ? (filtering == null ? new IndexedIfExistsUniNode<>(shouldExist, indexerFactory, + buildHelper.reserveTupleStoreIndex(parentA.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentA.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentBridgeB.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentBridgeB.getTupleSource()), + downstream) + : new IndexedIfExistsUniNode<>(shouldExist, indexerFactory, + buildHelper.reserveTupleStoreIndex(parentA.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentA.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentA.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentBridgeB.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentBridgeB.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentBridgeB.getTupleSource()), + downstream, filtering)) + : (filtering == null ? new UnindexedIfExistsUniNode<>(shouldExist, + buildHelper.reserveTupleStoreIndex(parentA.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentBridgeB.getTupleSource()), downstream) + : new UnindexedIfExistsUniNode<>(shouldExist, + buildHelper.reserveTupleStoreIndex(parentA.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentA.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentBridgeB.getTupleSource()), + buildHelper.reserveTupleStoreIndex(parentBridgeB.getTupleSource()), + downstream, filtering)); + buildHelper.addNode(node, this, this, parentBridgeB); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof IfExistsUniDataStream that)) + return false; + return shouldExist == that.shouldExist && Objects.equals(parentA, that.parentA) + && Objects.equals(parentBridgeB, that.parentBridgeB) && Objects.equals(joiner, that.joiner) + && Objects.equals(filtering, that.filtering); + } + + @Override + public int hashCode() { + return Objects.hash(parentA, parentBridgeB, shouldExist, joiner, filtering); + } + + @Override + public String toString() { + return "IfExists() with " + childStreamList.size() + " children"; + } + + @Override + public AbstractDataStream getTupleSource() { + return parentA.getTupleSource(); + } + + @Override + public BavetAbstractConstraintStream> getLeftParent() { + return null; + } + + @Override + public BavetAbstractConstraintStream> getRightParent() { + return null; + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/IndexedList.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/IndexedList.java new file mode 100644 index 0000000000..1aa278371b --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/IndexedList.java @@ -0,0 +1,81 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import ai.timefold.solver.core.impl.util.CollectionUtils; + +final class IndexedList extends AbstractList { + + private final List list; + private final Map indexMap; + + public IndexedList() { + this(16); + } + + public IndexedList(int expectedSize) { + this.list = new ArrayList<>(expectedSize); + this.indexMap = CollectionUtils.newIdentityHashMap(expectedSize); + } + + @Override + public E get(int index) { + return list.get(index); + } + + @Override + public E set(int index, E element) { + if (index < 0 || index >= list.size()) { + throw new IndexOutOfBoundsException("The index (" + index + ") must be between 0 and " + (list.size() - 1) + "."); + } else if (indexMap.containsKey(element)) { + throw new IllegalArgumentException("The element (" + element + ") was already added to the list."); + } else { + E oldElement = list.set(index, element); + indexMap.remove(oldElement); + indexMap.put(element, index); + return oldElement; + } + } + + @Override + public void add(int index, E element) { + if (index < 0 || index > list.size()) { + throw new IndexOutOfBoundsException("The index (" + index + ") must be between 0 and " + list.size() + "."); + } else if (indexMap.containsKey(element)) { + throw new IllegalArgumentException("The element (" + element + ") was already added to the list."); + } + list.add(index, element); + indexMap.put(element, index); + reindex(index + 1); + } + + private void reindex(int startingIndex) { + int listSize = list.size(); + for (int i = startingIndex; i < listSize; i++) { + indexMap.put(list.get(i), i); + } + } + + @Override + public E remove(int index) { + E oldElement = list.remove(index); + indexMap.remove(oldElement); + reindex(index); + return oldElement; + } + + @SuppressWarnings("unchecked") + @Override + public int indexOf(Object o) { + return indexMap.getOrDefault((E) o, -1); + } + + @Override + public int size() { + return list.size(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/TerminalUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/TerminalUniDataStream.java new file mode 100644 index 0000000000..35b5426780 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/TerminalUniDataStream.java @@ -0,0 +1,47 @@ +package ai.timefold.solver.core.impl.move.dataset; + +import java.util.Objects; + +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.move.dataset.common.DataNodeBuildHelper; + +final class TerminalUniDataStream + extends AbstractUniDataStream { + + private final Dataset> dataset; + + public TerminalUniDataStream(DefaultDatasetFactory datasetFactory, AbstractUniDataStream parent) { + super(datasetFactory, parent); + this.dataset = new Dataset<>(datasetFactory, this); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + assertEmptyChildStreamList(); + var inputStoreIndex = buildHelper.reserveTupleStoreIndex(this); + buildHelper.putInsertUpdateRetract(this, dataset.instantiate(inputStoreIndex)); + } + + public Dataset> getDataset() { + return dataset; + } + + @Override + public boolean equals(Object entity) { + if (!(entity instanceof TerminalUniDataStream that)) { + return false; + } + return Objects.equals(dataset, that.dataset); + } + + @Override + public int hashCode() { + return Objects.hashCode(dataset); + } + + @Override + public String toString() { + return "Terminal node"; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/BavetDataStreamBinaryOperation.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/BavetDataStreamBinaryOperation.java new file mode 100644 index 0000000000..475a6f6a07 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/BavetDataStreamBinaryOperation.java @@ -0,0 +1,10 @@ +package ai.timefold.solver.core.impl.move.dataset.common; + +import ai.timefold.solver.core.impl.move.dataset.AbstractDataStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetStreamBinaryOperation; + +public sealed interface BavetDataStreamBinaryOperation + extends BavetStreamBinaryOperation> + permits BavetIfExistsDataStream { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/BavetIfExistsDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/BavetIfExistsDataStream.java new file mode 100644 index 0000000000..1b46da0aa2 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/BavetIfExistsDataStream.java @@ -0,0 +1,5 @@ +package ai.timefold.solver.core.impl.move.dataset.common; + +public non-sealed interface BavetIfExistsDataStream extends BavetDataStreamBinaryOperation { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/DataNodeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/DataNodeBuildHelper.java new file mode 100644 index 0000000000..675092f8ce --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/DataNodeBuildHelper.java @@ -0,0 +1,14 @@ +package ai.timefold.solver.core.impl.move.dataset.common; + +import java.util.Set; + +import ai.timefold.solver.core.impl.bavet.common.AbstractNodeBuildHelper; +import ai.timefold.solver.core.impl.move.dataset.AbstractDataStream; + +public final class DataNodeBuildHelper extends AbstractNodeBuildHelper> { + + public DataNodeBuildHelper(Set> activeStreamSet) { + super(activeStreamSet); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/AftBridgeUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/AftBridgeUniDataStream.java new file mode 100644 index 0000000000..b49aba4d60 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/AftBridgeUniDataStream.java @@ -0,0 +1,46 @@ +package ai.timefold.solver.core.impl.move.dataset.common.bridge; + +import java.util.Objects; + +import ai.timefold.solver.core.impl.bavet.common.TupleSource; +import ai.timefold.solver.core.impl.move.dataset.AbstractDataStream; +import ai.timefold.solver.core.impl.move.dataset.AbstractUniDataStream; +import ai.timefold.solver.core.impl.move.dataset.DefaultDatasetFactory; +import ai.timefold.solver.core.impl.move.dataset.common.DataNodeBuildHelper; + +public final class AftBridgeUniDataStream + extends AbstractUniDataStream + implements TupleSource { + + public AftBridgeUniDataStream(DefaultDatasetFactory datasetFactory, AbstractDataStream parent) { + super(datasetFactory, parent); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + // Do nothing. The parent stream builds everything. + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AftBridgeUniDataStream that = (AftBridgeUniDataStream) o; + return Objects.equals(parent, that.parent); + } + + @Override + public int hashCode() { + return parent.hashCode(); + } + + @Override + public String toString() { + return "Bridge from " + parent + " with " + childStreamList.size() + " children"; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/ForeBridgeUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/ForeBridgeUniDataStream.java new file mode 100644 index 0000000000..1bcc5f32df --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/ForeBridgeUniDataStream.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.impl.move.dataset.common.bridge; + +import ai.timefold.solver.core.impl.move.dataset.AbstractDataStream; +import ai.timefold.solver.core.impl.move.dataset.AbstractUniDataStream; +import ai.timefold.solver.core.impl.move.dataset.DefaultDatasetFactory; +import ai.timefold.solver.core.impl.move.dataset.common.DataNodeBuildHelper; +import ai.timefold.solver.core.preview.api.move.UniDataStream; + +public final class ForeBridgeUniDataStream + extends AbstractUniDataStream + implements UniDataStream { + + public ForeBridgeUniDataStream(DefaultDatasetFactory datasetFactory, + AbstractDataStream parent) { + super(datasetFactory, parent); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + // Do nothing. The child stream builds everything. + } + + @Override + public String toString() { + return "Generic bridge"; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/package-info.java b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/package-info.java new file mode 100644 index 0000000000..9ac8de06bc --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/dataset/common/bridge/package-info.java @@ -0,0 +1,14 @@ +/** + * Data streams that serve as bridges. + * Fore bridges go before the stream they bridge, + * while aft bridges go after. + *

+ * Aft bridges are node-shared, + * therefore their {@link java.lang.Object#equals(Object)} and {@link java.lang.Object#hashCode()} (java.lang.Object)} + * methods are overridden to reference the bridged stream, + * which carries all the equality data. + *

+ * Fore bridges are node-shared through their child stream + * and therefore the equality logic can reside there entirely. + */ +package ai.timefold.solver.core.impl.move.dataset.common.bridge; \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/director/ChangeAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/director/ChangeAction.java index 693161ad36..564fbca7ef 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/director/ChangeAction.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/director/ChangeAction.java @@ -3,7 +3,7 @@ import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.preview.api.move.Rebaser; -sealed interface ChangeAction +public sealed interface ChangeAction permits ListVariableAfterAssignmentAction, ListVariableAfterChangeAction, ListVariableAfterUnassignmentAction, ListVariableBeforeAssignmentAction, ListVariableBeforeChangeAction, ListVariableBeforeUnassignmentAction, VariableChangeAction { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/director/EphemeralMoveDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/move/director/EphemeralMoveDirector.java index 84e2010cc5..6a1020ef0a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/director/EphemeralMoveDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/director/EphemeralMoveDirector.java @@ -1,7 +1,5 @@ package ai.timefold.solver.core.impl.move.director; -import java.util.List; - import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; @@ -23,10 +21,8 @@ public final class EphemeralMoveDirector extends MoveDirector(scoreDirector, false)); } - @SuppressWarnings("unchecked") public Move createUndoMove() { - var changes = (List>) getVariableChangeRecordingScoreDirector().copyChanges(); - return new RecordedUndoMove<>(changes); + return new RecordedUndoMove<>(getVariableChangeRecordingScoreDirector().copyChanges()); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/director/MoveDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/move/director/MoveDirector.java index 3d409d0b4b..4e98170fc4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/director/MoveDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/director/MoveDirector.java @@ -21,9 +21,30 @@ public sealed class MoveDirector permits EphemeralMoveDirector { protected final VariableDescriptorAwareScoreDirector scoreDirector; + private final MoveStreamSession moveStreamSession; public MoveDirector(VariableDescriptorAwareScoreDirector scoreDirector) { + this(scoreDirector, null); + } + + public MoveDirector(VariableDescriptorAwareScoreDirector scoreDirector, + MoveStreamSession moveStreamSession) { this.scoreDirector = Objects.requireNonNull(scoreDirector); + this.moveStreamSession = moveStreamSession; + } + + @Override + public void assignValue( + @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Value_ planningValue, + @NonNull Entity_ destinationEntity, int destinationIndex) { + + } + + @Override + public void unassignValue( + @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Value_ movedValue, + @NonNull Entity_ sourceEntity, int sourceIndex) { + } public final void changeVariable( @@ -33,47 +54,69 @@ public final void changeVariable( scoreDirector.beforeVariableChanged(variableDescriptor, entity); variableDescriptor.setValue(entity, newValue); scoreDirector.afterVariableChanged(variableDescriptor, entity); + if (moveStreamSession != null) { + moveStreamSession.update(entity); + } } - public final void moveValueBetweenLists( + @SuppressWarnings("unchecked") + public final Value_ moveValueBetweenLists( @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Entity_ sourceEntity, int sourceIndex, @NonNull Entity_ destinationEntity, int destinationIndex) { if (sourceEntity == destinationEntity) { - moveValueInList(variableMetaModel, sourceEntity, sourceIndex, destinationIndex); - return; + return moveValueInList(variableMetaModel, sourceEntity, sourceIndex, destinationIndex); } var variableDescriptor = extractVariableDescriptor(variableMetaModel); scoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex + 1); - var element = variableDescriptor.removeElement(sourceEntity, sourceIndex); + var element = (Value_) variableDescriptor.removeElement(sourceEntity, sourceIndex); scoreDirector.afterListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex); scoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, destinationIndex); variableDescriptor.addElement(destinationEntity, destinationIndex, element); scoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, destinationIndex + 1); + + if (moveStreamSession != null) { + moveStreamSession.update(sourceEntity); + moveStreamSession.update(destinationEntity); + } + return element; } + @SuppressWarnings("unchecked") @Override - public final void moveValueInList( + public final Value_ moveValueInList( @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Entity_ entity, int sourceIndex, int destinationIndex) { if (sourceIndex == destinationIndex) { - return; + return null; } else if (sourceIndex > destinationIndex) { // Always start from the lower index. - moveValueInList(variableMetaModel, entity, destinationIndex, sourceIndex); - return; + return moveValueInList(variableMetaModel, entity, destinationIndex, sourceIndex); } var variableDescriptor = extractVariableDescriptor(variableMetaModel); var toIndex = destinationIndex + 1; scoreDirector.beforeListVariableChanged(variableDescriptor, entity, sourceIndex, toIndex); var variable = variableDescriptor.getValue(entity); - var value = variable.remove(sourceIndex); + var value = (Value_) variable.remove(sourceIndex); variable.add(destinationIndex, value); scoreDirector.afterListVariableChanged(variableDescriptor, entity, sourceIndex, toIndex); + if (moveStreamSession != null) { + moveStreamSession.update(entity); + } + return value; } @Override public final void updateShadowVariables() { - scoreDirector.triggerVariableListeners(); + updateShadowVariables(false); // Called by the move itself. + } + + public final void updateShadowVariables(boolean comingFromScoreDirector) { + if (!comingFromScoreDirector) { // Prevent recursion. + scoreDirector.triggerVariableListeners(); + } + if (moveStreamSession != null) { + moveStreamSession.settle(); + } } @SuppressWarnings("unchecked") @@ -86,7 +129,7 @@ public final Value_ getValue( @SuppressWarnings("unchecked") @Override - public final Value_ getValueAtIndex( + public final @NonNull Value_ getValueAtIndex( @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Entity_ entity, int index) { return (Value_) extractVariableDescriptor(variableMetaModel).getValue(entity).get(index); @@ -134,4 +177,12 @@ public VariableDescriptorAwareScoreDirector getScoreDirector() { return scoreDirector; } + public void resetWorkingSolution(Solution_ workingSolution) { + if (moveStreamSession == null) { + return; + } + moveStreamSession.resetWorkingSolution(workingSolution); + scoreDirector.getSolutionDescriptor().visitAll(workingSolution, moveStreamSession::insert); + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/director/MoveStreamSession.java b/core/src/main/java/ai/timefold/solver/core/impl/move/director/MoveStreamSession.java new file mode 100644 index 0000000000..90eb0c18eb --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/director/MoveStreamSession.java @@ -0,0 +1,15 @@ +package ai.timefold.solver.core.impl.move.director; + +public interface MoveStreamSession { + + void resetWorkingSolution(Solution_ workingSolution); + + void insert(Object problemFactOrEntity); + + void update(Object problemFactOrEntity); + + void retract(Object problemFactOrEntity); + + void settle(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/director/RecordedUndoMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/director/RecordedUndoMove.java index 01e52662af..a97f027c14 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/director/RecordedUndoMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/director/RecordedUndoMove.java @@ -10,9 +10,9 @@ import org.jspecify.annotations.NonNull; -final class RecordedUndoMove implements Move { - - private final List> variableChangeActionList; +record RecordedUndoMove(List> variableChangeActionList) + implements + Move { RecordedUndoMove(List> variableChangeActionList) { this.variableChangeActionList = Objects.requireNonNull(variableChangeActionList); @@ -32,8 +32,4 @@ public void execute(@NonNull MutableSolutionView solutionView) { .map(changeAction -> changeAction.rebase(rebaser)) .toList()); } - - List> getVariableChangeActionList() { - return variableChangeActionList; - } } \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/director/VariableChangeRecordingScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/move/director/VariableChangeRecordingScoreDirector.java index bb17755800..00470b2954 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/director/VariableChangeRecordingScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/director/VariableChangeRecordingScoreDirector.java @@ -61,7 +61,7 @@ private VariableChangeRecordingScoreDirector(InnerScoreDirector ba @Override @SuppressWarnings("unchecked") - public List copyChanges() { + public List> copyChanges() { return List.copyOf(variableChanges); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/generic/AbstractMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/AbstractMove.java new file mode 100644 index 0000000000..65772aef7a --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/AbstractMove.java @@ -0,0 +1,90 @@ +package ai.timefold.solver.core.impl.move.generic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import ai.timefold.solver.core.api.score.director.ScoreDirector; +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.impl.domain.solution.descriptor.InnerVariableMetaModel; +import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel; +import ai.timefold.solver.core.preview.api.move.Move; + +import org.jspecify.annotations.NonNull; + +public abstract class AbstractMove implements Move { + + private static final char OPENING_PARENTHESES = '('; + private static final char CLOSING_PARENTHESES = ')'; + + @Override + public final @NonNull String describe() { + var metaModels = getVariableMetaModels(); + var substring = switch (metaModels.size()) { + case 0 -> ""; + case 1 -> OPENING_PARENTHESES + getVariableDescriptor(metaModels.get(0)).getSimpleEntityAndVariableName() + + CLOSING_PARENTHESES; + default -> { + var stringBuilder = new StringBuilder() + .append(OPENING_PARENTHESES); + var first = true; + for (var variableMetaModel : getVariableMetaModels()) { + if (first) { + first = false; + } else { + stringBuilder.append(", "); + } + stringBuilder.append(getVariableDescriptor(variableMetaModel).getSimpleEntityAndVariableName()); + } + stringBuilder.append(CLOSING_PARENTHESES); + yield stringBuilder.toString(); + } + }; + return getClass().getSimpleName() + substring; + } + + protected List> getVariableMetaModels() { + return Collections.emptyList(); + } + + @SuppressWarnings("unchecked") + protected static VariableDescriptor + getVariableDescriptor(VariableMetaModel variableMetaModel) { + return ((InnerVariableMetaModel) variableMetaModel).variableDescriptor(); + } + + protected static GenuineVariableDescriptor + getVariableDescriptor(PlanningVariableMetaModel variableMetaModel) { + return ((DefaultPlanningVariableMetaModel) variableMetaModel).variableDescriptor(); + } + + protected static ListVariableDescriptor + getVariableDescriptor(PlanningListVariableMetaModel variableMetaModel) { + return ((DefaultPlanningListVariableMetaModel) variableMetaModel).variableDescriptor(); + } + + public static List rebaseList(List externalObjectList, ScoreDirector destinationScoreDirector) { + var rebasedObjectList = new ArrayList(externalObjectList.size()); + for (var entity : externalObjectList) { + rebasedObjectList.add(destinationScoreDirector.lookUpWorkingObject(entity)); + } + return rebasedObjectList; + } + + public static Set rebaseSet(Set externalObjectSet, ScoreDirector destinationScoreDirector) { + var rebasedObjectSet = new LinkedHashSet(externalObjectSet.size()); + for (var entity : externalObjectSet) { + rebasedObjectSet.add(destinationScoreDirector.lookUpWorkingObject(entity)); + } + return rebasedObjectSet; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ChainedChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ChainedChangeMove.java new file mode 100644 index 0000000000..0b81169c1e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ChainedChangeMove.java @@ -0,0 +1,56 @@ +package ai.timefold.solver.core.impl.move.generic; + +import java.util.Objects; + +import ai.timefold.solver.core.impl.domain.variable.inverserelation.SingletonInverseVariableSupply; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; +import ai.timefold.solver.core.preview.api.move.MutableSolutionView; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NonNull; + +public final class ChainedChangeMove extends ChangeMove { + + private final Entity_ oldTrailingEntity; + private final Entity_ newTrailingEntity; + + @SuppressWarnings("unchecked") + public ChainedChangeMove(PlanningVariableMetaModel variableMetaModel, Entity_ entity, + Entity_ toPlanningValue, SingletonInverseVariableSupply inverseVariableSupply) { + super(variableMetaModel, entity, toPlanningValue); + this.oldTrailingEntity = (Entity_) Objects.requireNonNull(inverseVariableSupply).getInverseSingleton(entity); + this.newTrailingEntity = + toPlanningValue == null ? null : (Entity_) inverseVariableSupply.getInverseSingleton(toPlanningValue); + } + + ChainedChangeMove(PlanningVariableMetaModel variableMetaModel, Entity_ entity, + Entity_ toPlanningValue, Entity_ oldTrailingEntity, Entity_ newTrailingEntity) { + super(variableMetaModel, entity, toPlanningValue); + this.oldTrailingEntity = oldTrailingEntity; + this.newTrailingEntity = newTrailingEntity; + } + + @Override + public void execute(@NonNull MutableSolutionView solutionView) { + // Close the old chain + if (oldTrailingEntity != null) { + solutionView.changeVariable(variableMetaModel, oldTrailingEntity, readValue(solutionView)); + } + // Change the entity + solutionView.changeVariable(variableMetaModel, entity, toPlanningValue); + // Reroute the new chain + if (newTrailingEntity != null) { + solutionView.changeVariable(variableMetaModel, newTrailingEntity, entity); + } + } + + @Override + public @NonNull ChainedChangeMove rebase(@NonNull Rebaser rebaser) { + return new ChainedChangeMove<>(variableMetaModel, + rebaser.rebase(entity), + rebaser.rebase(toPlanningValue), + rebaser.rebase(oldTrailingEntity), + rebaser.rebase(newTrailingEntity)); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ChangeMove.java new file mode 100644 index 0000000000..d06c8597ab --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ChangeMove.java @@ -0,0 +1,89 @@ +package ai.timefold.solver.core.impl.move.generic; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.MutableSolutionView; +import ai.timefold.solver.core.preview.api.move.Rebaser; +import ai.timefold.solver.core.preview.api.move.SolutionView; + +import org.jspecify.annotations.NonNull; + +/** + * @param the solution type, the class with the {@link PlanningSolution} annotation + */ +public class ChangeMove extends AbstractMove { + + protected final PlanningVariableMetaModel variableMetaModel; + protected final Entity_ entity; + protected final Value_ toPlanningValue; + + private Value_ currentValue; + + public ChangeMove(PlanningVariableMetaModel variableMetaModel, Entity_ entity, + Value_ toPlanningValue) { + this.variableMetaModel = Objects.requireNonNull(variableMetaModel); + this.entity = Objects.requireNonNull(entity); + this.toPlanningValue = toPlanningValue; + } + + protected Value_ readValue(SolutionView solutionView) { + if (currentValue == null) { + currentValue = solutionView.getValue(variableMetaModel, entity); + } + return currentValue; + } + + @Override + public void execute(@NonNull MutableSolutionView solutionView) { + solutionView.changeVariable(variableMetaModel, entity, toPlanningValue); + } + + @Override + public @NonNull Move rebase(@NonNull Rebaser rebaser) { + return new ChangeMove<>(variableMetaModel, rebaser.rebase(entity), rebaser.rebase(toPlanningValue)); + } + + @Override + public @NonNull Collection extractPlanningEntities() { + return Collections.singletonList(entity); + } + + @Override + public @NonNull Collection extractPlanningValues() { + return Collections.singletonList(toPlanningValue); + } + + @Override + protected List> getVariableMetaModels() { + return List.of(variableMetaModel); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ChangeMove that)) + return false; + return Objects.equals(variableMetaModel, that.variableMetaModel) && Objects.equals(entity, that.entity) + && Objects.equals(toPlanningValue, that.toPlanningValue); + } + + @Override + public int hashCode() { + return Objects.hash(variableMetaModel, entity, toPlanningValue); + } + + @Override + public @NonNull String toString() { + var oldValue = currentValue == null ? getVariableDescriptor(variableMetaModel).getValue(entity) : currentValue; + return entity + " {" + oldValue + " -> " + toPlanningValue + "}"; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListAssignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListAssignMove.java new file mode 100644 index 0000000000..280a3b3963 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListAssignMove.java @@ -0,0 +1,63 @@ +package ai.timefold.solver.core.impl.move.generic; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.MutableSolutionView; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NonNull; + +public final class ListAssignMove extends AbstractMove { + + private final PlanningListVariableMetaModel variableMetaModel; + private final Value_ planningValue; + private final Entity_ destinationEntity; + private final int destinationIndex; + + public ListAssignMove(PlanningListVariableMetaModel variableMetaModel, Value_ planningValue, + Entity_ destinationEntity, int destinationIndex) { + this.variableMetaModel = Objects.requireNonNull(variableMetaModel); + this.planningValue = Objects.requireNonNull(planningValue); + this.destinationEntity = Objects.requireNonNull(destinationEntity); + if (destinationIndex < 0) { + throw new IllegalArgumentException("The destinationIndex (" + destinationIndex + ") must be greater than 0."); + } + this.destinationIndex = destinationIndex; + } + + @Override + public void execute(@NonNull MutableSolutionView mutableSolutionView) { + mutableSolutionView.assignValue(variableMetaModel, planningValue, destinationEntity, destinationIndex); + } + + @Override + public @NonNull Move rebase(@NonNull Rebaser rebaser) { + return new ListAssignMove<>(variableMetaModel, rebaser.rebase(planningValue), + rebaser.rebase(destinationEntity), destinationIndex); + } + + @Override + public @NonNull Collection extractPlanningEntities() { + return List.of(destinationEntity); + } + + @Override + public @NonNull Collection extractPlanningValues() { + return List.of(planningValue); + } + + @Override + protected List> getVariableMetaModels() { + return List.of(variableMetaModel); + } + + @Override + public @NonNull String toString() { + return String.format("%s {null -> %s[%d]}", planningValue, destinationEntity, destinationIndex); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListChangeMove.java new file mode 100644 index 0000000000..fafc3aa104 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListChangeMove.java @@ -0,0 +1,178 @@ +package ai.timefold.solver.core.impl.move.generic; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel; +import ai.timefold.solver.core.preview.api.move.MutableSolutionView; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NonNull; + +/** + * Moves an element of a {@link PlanningListVariable list variable}. The moved element is identified + * by an entity instance and a position in that entity's list variable. The element is inserted at the given index + * in the given destination entity's list variable. + *

+ * An undo move is simply created by flipping the source and destination entity+index. + * + * @param the solution type, the class with the {@link PlanningSolution} annotation + */ +public final class ListChangeMove extends AbstractMove { + + private final PlanningListVariableMetaModel variableMetaModel; + private final Entity_ sourceEntity; + private final int sourceIndex; + private final Entity_ destinationEntity; + private final int destinationIndex; + + private Value_ planningValue; + + /** + * The move removes a planning value element from {@code sourceEntity.listVariable[sourceIndex]} + * and inserts the planning value at {@code destinationEntity.listVariable[destinationIndex]}. + * + *

ListChangeMove anatomy

+ * + *
+     * {@code
+     *                             / destinationEntity
+     *                             |   / destinationIndex
+     *                             |   |
+     *                A {Ann[0]}->{Bob[2]}
+     *                |  |   |
+     * planning value /  |   \ sourceIndex
+     *                   \ sourceEntity
+     * }
+     * 
+ * + *

Example 1 - source and destination entities are different

+ * + *
+     * {@code
+     * GIVEN
+     * Ann.tasks = [A, B, C]
+     * Bob.tasks = [X, Y]
+     *
+     * WHEN
+     * ListChangeMove: A {Ann[0]->Bob[2]}
+     *
+     * THEN
+     * Ann.tasks = [B, C]
+     * Bob.tasks = [X, Y, A]
+     * }
+     * 
+ * + *

Example 2 - source and destination is the same entity

+ * + *
+     * {@code
+     * GIVEN
+     * Ann.tasks = [A, B, C]
+     *
+     * WHEN
+     * ListChangeMove: A {Ann[0]->Ann[2]}
+     *
+     * THEN
+     * Ann.tasks = [B, C, A]
+     * }
+     * 
+ * + * @param variableMetaModel never null + * @param sourceEntity planning entity instance from which a planning value will be removed, for example "Ann" + * @param sourceIndex index in sourceEntity's list variable from which a planning value will be removed + * @param destinationEntity planning entity instance to which a planning value will be moved, for example "Bob" + * @param destinationIndex index in destinationEntity's list variable where the moved planning value will be inserted + */ + public ListChangeMove(PlanningListVariableMetaModel variableMetaModel, Entity_ sourceEntity, + int sourceIndex, Entity_ destinationEntity, int destinationIndex) { + this.variableMetaModel = Objects.requireNonNull(variableMetaModel); + this.sourceEntity = Objects.requireNonNull(sourceEntity); + if (sourceIndex < 0) { + throw new IllegalArgumentException("The sourceIndex (" + sourceIndex + ") must be greater than 0."); + } + this.sourceIndex = sourceIndex; + this.destinationEntity = Objects.requireNonNull(destinationEntity); + if (destinationIndex < 0) { + throw new IllegalArgumentException("The destinationIndex (" + destinationIndex + ") must be greater than 0."); + } + this.destinationIndex = destinationIndex; + } + + @SuppressWarnings("unchecked") + private Value_ getMovedValue() { + if (planningValue == null) { + planningValue = (Value_) getVariableDescriptor(variableMetaModel) + .getValue(sourceEntity) + .get(sourceIndex); + } + return planningValue; + } + + // ************************************************************************ + // Worker methods + // ************************************************************************ + + @Override + public void execute(@NonNull MutableSolutionView solutionView) { + if (sourceEntity == destinationEntity) { + planningValue = solutionView.moveValueInList(variableMetaModel, sourceEntity, sourceIndex, destinationIndex); + } else { + planningValue = solutionView.moveValueBetweenLists(variableMetaModel, sourceEntity, sourceIndex, destinationEntity, + destinationIndex); + } + } + + @Override + public @NonNull ListChangeMove rebase(@NonNull Rebaser rebaser) { + return new ListChangeMove<>(variableMetaModel, + rebaser.rebase(sourceEntity), sourceIndex, + rebaser.rebase(destinationEntity), destinationIndex); + } + + @Override + public @NonNull Collection extractPlanningEntities() { + if (sourceEntity == destinationEntity) { + return Collections.singleton(sourceEntity); + } else { + return List.of(sourceEntity, destinationEntity); + } + } + + @Override + public @NonNull Collection extractPlanningValues() { + return Collections.singleton(planningValue); + } + + @Override + protected List> getVariableMetaModels() { + return List.of(variableMetaModel); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ListChangeMove that)) + return false; + return sourceIndex == that.sourceIndex && destinationIndex == that.destinationIndex + && Objects.equals(variableMetaModel, that.variableMetaModel) && Objects.equals(sourceEntity, that.sourceEntity) + && Objects.equals(destinationEntity, that.destinationEntity); + } + + @Override + public int hashCode() { + return Objects.hash(variableMetaModel, sourceEntity, sourceIndex, destinationEntity, destinationIndex); + } + + @Override + public @NonNull String toString() { + return String.format("%s {%s[%d] -> %s[%d]}", + getMovedValue(), sourceEntity, sourceIndex, destinationEntity, destinationIndex); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListUnassignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListUnassignMove.java new file mode 100644 index 0000000000..e56693af8d --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/generic/ListUnassignMove.java @@ -0,0 +1,79 @@ +package ai.timefold.solver.core.impl.move.generic; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel; +import ai.timefold.solver.core.preview.api.move.Move; +import ai.timefold.solver.core.preview.api.move.MutableSolutionView; +import ai.timefold.solver.core.preview.api.move.Rebaser; + +import org.jspecify.annotations.NonNull; + +public final class ListUnassignMove extends AbstractMove { + + private final PlanningListVariableMetaModel variableMetaModel; + private final Value_ movedValue; + private final Entity_ sourceEntity; + private final int sourceIndex; + + public ListUnassignMove(PlanningListVariableMetaModel variableMetaModel, Value_ value, + Entity_ sourceEntity, int sourceIndex) { + this.variableMetaModel = Objects.requireNonNull(variableMetaModel); + this.movedValue = Objects.requireNonNull(value); + this.sourceEntity = Objects.requireNonNull(sourceEntity); + if (sourceIndex < 0) { + throw new IllegalArgumentException("The sourceIndex (" + sourceIndex + ") must be greater than or equal to 0."); + } + this.sourceIndex = sourceIndex; + } + + @Override + public void execute(@NonNull MutableSolutionView solutionView) { + solutionView.unassignValue(variableMetaModel, movedValue, sourceEntity, sourceIndex); + } + + @Override + public @NonNull Move rebase(@NonNull Rebaser rebaser) { + return new ListUnassignMove<>(variableMetaModel, rebaser.rebase(movedValue), rebaser.rebase(sourceEntity), + sourceIndex); + } + + @Override + public @NonNull Collection extractPlanningEntities() { + return Collections.singleton(sourceEntity); + } + + @Override + public @NonNull Collection extractPlanningValues() { + return Collections.singleton(movedValue); + } + + @Override + protected List> getVariableMetaModels() { + return List.of(variableMetaModel); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ListUnassignMove that)) + return false; + return sourceIndex == that.sourceIndex && Objects.equals(variableMetaModel, that.variableMetaModel) + && Objects.equals(sourceEntity, that.sourceEntity); + } + + @Override + public int hashCode() { + return Objects.hash(variableMetaModel, sourceEntity, sourceIndex); + } + + @Override + public @NonNull String toString() { + return String.format("%s {%s[%d] -> null}", movedValue, sourceEntity, sourceIndex); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java index 6d691d449c..20706128aa 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java @@ -33,6 +33,7 @@ import ai.timefold.solver.core.impl.domain.variable.listener.support.violation.SolutionTracker; import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; import ai.timefold.solver.core.impl.move.director.MoveDirector; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.phase.scope.SolverLifecyclePoint; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.definition.ScoreDefinition; @@ -81,13 +82,13 @@ public abstract class AbstractScoreDirector solutionTracker; - private final MoveDirector moveDirector = new MoveDirector<>(this); + private final MoveDirector moveDirector; // Null when no list variable private final ListVariableStateSupply listVariableStateSupply; - protected AbstractScoreDirector(Factory_ scoreDirectorFactory, boolean lookUpEnabled, - ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState) { + protected AbstractScoreDirector(Factory_ scoreDirectorFactory, MoveStreamSession moveStreamSession, + boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState) { var solutionDescriptor = scoreDirectorFactory.getSolutionDescriptor(); this.lookUpEnabled = lookUpEnabled; this.lookUpManager = lookUpEnabled @@ -107,6 +108,7 @@ protected AbstractScoreDirector(Factory_ scoreDirectorFactory, boolean lookUpEna this.solutionTracker = null; this.trackingWorkingSolution = false; } + this.moveDirector = new MoveDirector<>(this, moveStreamSession); var listVariableDescriptor = solutionDescriptor.getListVariableDescriptor(); if (listVariableDescriptor == null) { this.listVariableStateSupply = null; @@ -245,6 +247,7 @@ public void setWorkingSolution(Solution_ workingSolution) { assertInitScoreZeroOrLess(); workingGenuineEntityCount = initializationStatistics.genuineEntityCount(); variableListenerSupport.resetWorkingSolution(); + moveDirector.resetWorkingSolution(workingSolution); } private void assertInitScoreZeroOrLess() { @@ -320,11 +323,13 @@ public Solution_ cloneSolution(Solution_ originalSolution) { @Override public void triggerVariableListeners() { variableListenerSupport.triggerVariableListenersInNotificationQueues(); + moveDirector.updateShadowVariables(true); } @Override public void forceTriggerVariableListeners() { variableListenerSupport.forceTriggerAllVariableListeners(getWorkingSolution()); + moveDirector.updateShadowVariables(true); } protected void setCalculatedScore(Score_ score) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirectorFactory.java index e73659839f..d8fcb85fdd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirectorFactory.java @@ -5,6 +5,7 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintMatch; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.definition.ScoreDefinition; import ai.timefold.solver.core.impl.score.trend.InitializingScoreTrend; @@ -27,19 +28,22 @@ public interface InnerScoreDirectorFactory getScoreDefinition(); @Override - default InnerScoreDirector buildScoreDirector(boolean lookUpEnabled, + default InnerScoreDirector buildScoreDirector(MoveStreamSession moveStreamSession, + boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy) { - return buildScoreDirector(lookUpEnabled, constraintMatchPolicy, true); + return buildScoreDirector(moveStreamSession, lookUpEnabled, constraintMatchPolicy, true); } @Override - InnerScoreDirector buildScoreDirector(boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, - boolean expectShadowVariablesInCorrectState); + InnerScoreDirector buildScoreDirector(MoveStreamSession moveStreamSession, + boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState); /** - * Like {@link #buildScoreDirector(boolean, ConstraintMatchPolicy)}, but makes the score director a derived one. + * Like {@link #buildScoreDirector(MoveStreamSession, boolean, ConstraintMatchPolicy)}, + * but makes the score director a derived one. * Derived score directors may make choices which the main score director can not make, such as reducing logging. * Derived score directors are typically used for multithreaded solving, testing and assert modes. + * Derived score directors do not support move streams, as they are only used to calculate the score. * * @param lookUpEnabled true if a {@link ScoreDirector} implementation should track all working objects * for {@link ScoreDirector#lookUpWorkingObject(Object)} @@ -51,7 +55,7 @@ InnerScoreDirector buildScoreDirector(boolean lookUpEnabled, default InnerScoreDirector buildDerivedScoreDirector(boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy) { // Most score directors don't need derived status; CS will override this. - return buildScoreDirector(lookUpEnabled, constraintMatchPolicy); + return buildScoreDirector(null, lookUpEnabled, constraintMatchPolicy); } /** diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactory.java index d699253c8b..6d5caba55d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactory.java @@ -3,6 +3,7 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.constraint.ConstraintMatch; import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; /** @@ -13,19 +14,21 @@ public interface ScoreDirectorFactory { /** - * Like {@link #buildScoreDirector(boolean, ConstraintMatchPolicy, boolean)}, + * Like {@link #buildScoreDirector(MoveStreamSession, boolean, ConstraintMatchPolicy, boolean)}, * with the final parameter set to true. * + * @param moveStreamSession null if move streams not supported * @param lookUpEnabled true if a {@link ScoreDirector} implementation should track all working objects * for {@link ScoreDirector#lookUpWorkingObject(Object)} * @param constraintMatchPolicy how should the {@link ScoreDirector} track {@link ConstraintMatch constraint matches}. * @return never null */ - default ScoreDirector buildScoreDirector(boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy) { - return buildScoreDirector(lookUpEnabled, constraintMatchPolicy, true); + default ScoreDirector buildScoreDirector(MoveStreamSession moveStreamSession, boolean lookUpEnabled, + ConstraintMatchPolicy constraintMatchPolicy) { + return buildScoreDirector(moveStreamSession, lookUpEnabled, constraintMatchPolicy, true); } - ScoreDirector buildScoreDirector(boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, - boolean expectShadowVariablesInCorrectState); + ScoreDirector buildScoreDirector(MoveStreamSession moveStreamSession, boolean lookUpEnabled, + ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java index c34c6abd8a..752f8330ef 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java @@ -10,6 +10,7 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; import ai.timefold.solver.core.api.score.constraint.Indictment; import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirector; @@ -29,9 +30,10 @@ public final class EasyScoreDirector> private final EasyScoreCalculator easyScoreCalculator; public EasyScoreDirector(EasyScoreDirectorFactory scoreDirectorFactory, - boolean lookUpEnabled, boolean expectShadowVariablesInCorrectState, + MoveStreamSession moveStreamSession, boolean lookUpEnabled, boolean expectShadowVariablesInCorrectState, EasyScoreCalculator easyScoreCalculator) { - super(scoreDirectorFactory, lookUpEnabled, ConstraintMatchPolicy.DISABLED, expectShadowVariablesInCorrectState); + super(scoreDirectorFactory, moveStreamSession, lookUpEnabled, ConstraintMatchPolicy.DISABLED, + expectShadowVariablesInCorrectState); this.easyScoreCalculator = easyScoreCalculator; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java index 0ad39fd873..9ce275e703 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java @@ -6,6 +6,7 @@ import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactory; @@ -49,9 +50,10 @@ public EasyScoreDirectorFactory(SolutionDescriptor solutionDescriptor // ************************************************************************ @Override - public EasyScoreDirector buildScoreDirector(boolean lookUpEnabled, - ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState) { - return new EasyScoreDirector<>(this, lookUpEnabled, expectShadowVariablesInCorrectState, easyScoreCalculator); + public EasyScoreDirector buildScoreDirector(MoveStreamSession moveStreamSession, + boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState) { + return new EasyScoreDirector<>(this, moveStreamSession, lookUpEnabled, expectShadowVariablesInCorrectState, + easyScoreCalculator); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirector.java index 3476932336..8b63b764a1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirector.java @@ -18,6 +18,7 @@ import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.constraint.DefaultIndictment; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirector; @@ -37,9 +38,10 @@ public final class IncrementalScoreDirector incrementalScoreCalculator; public IncrementalScoreDirector(IncrementalScoreDirectorFactory scoreDirectorFactory, - boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState, + MoveStreamSession moveStreamSession, boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, + boolean expectShadowVariablesInCorrectState, IncrementalScoreCalculator incrementalScoreCalculator) { - super(scoreDirectorFactory, lookUpEnabled, + super(scoreDirectorFactory, moveStreamSession, lookUpEnabled, determineCorrectPolicy(constraintMatchPolicy, incrementalScoreCalculator), expectShadowVariablesInCorrectState); this.incrementalScoreCalculator = incrementalScoreCalculator; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorFactory.java index 57f336c459..8ee589a2ab 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorFactory.java @@ -9,6 +9,7 @@ import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactory; @@ -55,10 +56,10 @@ public boolean supportsConstraintMatching() { } @Override - public IncrementalScoreDirector buildScoreDirector(boolean lookUpEnabled, - ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState) { - return new IncrementalScoreDirector<>(this, lookUpEnabled, constraintMatchPolicy, expectShadowVariablesInCorrectState, - incrementalScoreCalculatorSupplier.get()); + public IncrementalScoreDirector buildScoreDirector(MoveStreamSession moveStreamSession, + boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState) { + return new IncrementalScoreDirector<>(this, moveStreamSession, lookUpEnabled, constraintMatchPolicy, + expectShadowVariablesInCorrectState, incrementalScoreCalculatorSupplier.get()); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java index 0395aefb96..7505f34ce8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java @@ -11,6 +11,7 @@ import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirector; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintSession; @@ -29,15 +30,42 @@ public final class BavetConstraintStreamScoreDirector session; + /** + * Builds a standard {@link BavetConstraintStreamScoreDirector}. + * + * @param scoreDirectorFactory + * @param moveStreamSession + * @param lookUpEnabled + * @param constraintMatchPolicy + * @param expectShadowVariablesInCorrectState + */ public BavetConstraintStreamScoreDirector(BavetConstraintStreamScoreDirectorFactory scoreDirectorFactory, - boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState) { - this(scoreDirectorFactory, lookUpEnabled, constraintMatchPolicy, expectShadowVariablesInCorrectState, false); + MoveStreamSession moveStreamSession, boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, + boolean expectShadowVariablesInCorrectState) { + this(scoreDirectorFactory, moveStreamSession, lookUpEnabled, constraintMatchPolicy, expectShadowVariablesInCorrectState, + false); } + /** + * Builds a derived {@link BavetConstraintStreamScoreDirector}. + * + * @param scoreDirectorFactory + * @param lookUpEnabled + * @param constraintMatchPolicy + * @param expectShadowVariablesInCorrectState + */ public BavetConstraintStreamScoreDirector(BavetConstraintStreamScoreDirectorFactory scoreDirectorFactory, - boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState, - boolean derived) { - super(scoreDirectorFactory, lookUpEnabled, constraintMatchPolicy, expectShadowVariablesInCorrectState); + boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, + boolean expectShadowVariablesInCorrectState) { + this(scoreDirectorFactory, null, lookUpEnabled, constraintMatchPolicy, expectShadowVariablesInCorrectState, true); + } + + private BavetConstraintStreamScoreDirector( + BavetConstraintStreamScoreDirectorFactory scoreDirectorFactory, + MoveStreamSession moveStreamSession, boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, + boolean expectShadowVariablesInCorrectState, boolean derived) { + super(scoreDirectorFactory, moveStreamSession, lookUpEnabled, constraintMatchPolicy, + expectShadowVariablesInCorrectState); this.derived = derived; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java index c6bdc2574e..36e7d1e747 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java @@ -11,6 +11,7 @@ import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; @@ -62,16 +63,17 @@ public BavetConstraintStreamScoreDirectorFactory(SolutionDescriptor s } @Override - public BavetConstraintStreamScoreDirector buildScoreDirector(boolean lookUpEnabled, + public BavetConstraintStreamScoreDirector buildScoreDirector( + MoveStreamSession moveStreamSession, boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy, boolean expectShadowVariablesInCorrectState) { - return new BavetConstraintStreamScoreDirector<>(this, lookUpEnabled, constraintMatchPolicy, + return new BavetConstraintStreamScoreDirector<>(this, moveStreamSession, lookUpEnabled, constraintMatchPolicy, expectShadowVariablesInCorrectState); } @Override public InnerScoreDirector buildDerivedScoreDirector(boolean lookUpEnabled, ConstraintMatchPolicy constraintMatchPolicy) { - return new BavetConstraintStreamScoreDirector<>(this, lookUpEnabled, constraintMatchPolicy, true, true); + return new BavetConstraintStreamScoreDirector<>(this, lookUpEnabled, constraintMatchPolicy, true); } public BavetConstraintSession newSession(Solution_ workingSolution, ConstraintMatchPolicy constraintMatchPolicy, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraint.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraint.java index 518b3b2ae3..797e0d04ba 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraint.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraint.java @@ -5,7 +5,7 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.constraint.ConstraintRef; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraint; import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintFactory.java index 6acb8eaa7d..5e728d78f4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintFactory.java @@ -71,7 +71,8 @@ public > Stream_ share( * {@link BavetAbstractConstraintStream} implement equals/hashcode ignoring child streams. *

* {@link BavetConstraintSessionFactory#buildSession(Object, ConstraintMatchPolicy, boolean, Consumer)} needs this to happen - * for all streams. + * for all + * streams. *

* This must be called before the stream receives child streams. * diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSession.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSession.java index 3657b82635..9d43ea7f29 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSession.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSession.java @@ -1,13 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet; -import java.util.IdentityHashMap; import java.util.Map; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; import ai.timefold.solver.core.api.score.constraint.Indictment; +import ai.timefold.solver.core.impl.bavet.AbstractSession; +import ai.timefold.solver.core.impl.bavet.NodeNetwork; import ai.timefold.solver.core.impl.bavet.common.PropagationQueue; -import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.director.stream.BavetConstraintStreamScoreDirectorFactory; import ai.timefold.solver.core.impl.score.stream.common.inliner.AbstractScoreInliner; @@ -18,57 +18,24 @@ * {@link BavetConstraintStreamScoreDirectorFactory#newSession(Object, ConstraintMatchPolicy, boolean)}. * * @see PropagationQueue Description of the tuple propagation mechanism. + * * @param */ -public final class BavetConstraintSession> { +public final class BavetConstraintSession> extends AbstractSession { private final AbstractScoreInliner scoreInliner; - private final NodeNetwork nodeNetwork; - private final Map, AbstractForEachUniNode[]> effectiveClassToNodeArrayMap; BavetConstraintSession(AbstractScoreInliner scoreInliner) { this(scoreInliner, NodeNetwork.EMPTY); } BavetConstraintSession(AbstractScoreInliner scoreInliner, NodeNetwork nodeNetwork) { + super(nodeNetwork); this.scoreInliner = scoreInliner; - this.nodeNetwork = nodeNetwork; - this.effectiveClassToNodeArrayMap = new IdentityHashMap<>(nodeNetwork.forEachNodeCount()); - } - - public void insert(Object fact) { - var factClass = fact.getClass(); - for (var node : findNodes(factClass)) { - node.insert(fact); - } - } - - private AbstractForEachUniNode[] findNodes(Class factClass) { - // Map.computeIfAbsent() would have created lambdas on the hot path, this will not. - var nodeArray = effectiveClassToNodeArrayMap.get(factClass); - if (nodeArray == null) { - nodeArray = nodeNetwork.getApplicableForEachNodes(factClass); - effectiveClassToNodeArrayMap.put(factClass, nodeArray); - } - return nodeArray; - } - - public void update(Object fact) { - var factClass = fact.getClass(); - for (var node : findNodes(factClass)) { - node.update(fact); - } - } - - public void retract(Object fact) { - var factClass = fact.getClass(); - for (var node : findNodes(factClass)) { - node.retract(fact); - } } public Score_ calculateScore(int initScore) { - nodeNetwork.propagate(); + settle(); return scoreInliner.extractScore(initScore); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java index e2e212a7e1..ad02bbdd85 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintSessionFactory.java @@ -14,22 +14,20 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.api.score.stream.ConstraintMetaModel; -import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; +import ai.timefold.solver.core.impl.bavet.NodeNetwork; import ai.timefold.solver.core.impl.bavet.common.AbstractIfExistsNode; import ai.timefold.solver.core.impl.bavet.common.AbstractJoinNode; import ai.timefold.solver.core.impl.bavet.common.AbstractNode; +import ai.timefold.solver.core.impl.bavet.common.AbstractTwoInputNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetIfExistsConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetJoinConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetStreamBinaryOperation; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.PropagationQueue; import ai.timefold.solver.core.impl.bavet.common.Propagator; import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode; import ai.timefold.solver.core.impl.bavet.visual.NodeGraph; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetStreamBinaryOperation; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.common.inliner.AbstractScoreInliner; import ai.timefold.solver.core.impl.util.CollectionUtils; @@ -128,7 +126,7 @@ public BavetConstraintSession buildSession(Solution_ workingSolution, Co private static > NodeNetwork buildNodeNetwork(Solution_ workingSolution, Set> constraintStreamSet, AbstractScoreInliner scoreInliner, Consumer nodeNetworkVisualizationConsumer) { - var buildHelper = new NodeBuildHelper<>(constraintStreamSet, scoreInliner); + var buildHelper = new ConstraintNodeBuildHelper<>(constraintStreamSet, scoreInliner); var declaredClassToNodeMap = new LinkedHashMap, List>>(); var nodeList = buildNodeList(constraintStreamSet, buildHelper, node -> { if (!(node instanceof AbstractForEachUniNode forEachUniNode)) { @@ -147,8 +145,9 @@ private static > NodeNetwork buildNodeNe }); if (nodeNetworkVisualizationConsumer != null) { var constraintSet = scoreInliner.getConstraints(); - var visualisation = NodeGraph.of(workingSolution, nodeList, constraintSet, buildHelper::getNodeCreatingStream, - buildHelper::findParentNode) + var visualisation = NodeGraph + .of(workingSolution, nodeList, constraintSet, buildHelper::getNodeCreatingStream, + buildHelper::findParentNode) .buildGraphvizDOT(); nodeNetworkVisualizationConsumer.accept(visualisation); } @@ -167,8 +166,8 @@ private static > NodeNetwork buildNodeNe } private static > List buildNodeList( - Set> constraintStreamSet, NodeBuildHelper buildHelper, - Consumer nodeProcessor) { + Set> constraintStreamSet, + ConstraintNodeBuildHelper buildHelper, Consumer nodeProcessor) { /* * Build constraintStreamSet in reverse order to create downstream nodes first * so every node only has final variables (some of which have downstream node method references). @@ -207,33 +206,23 @@ private static > List buil * @param buildHelper never null * @return at least 0 */ - private static > long determineLayerIndex(AbstractNode node, - NodeBuildHelper buildHelper) { + @SuppressWarnings("unchecked") + private static > long determineLayerIndex(AbstractNode node, + ConstraintNodeBuildHelper buildHelper) { if (node instanceof AbstractForEachUniNode) { // ForEach nodes, and only they, are in layer 0. return 0; - } else if (node instanceof AbstractJoinNode joinNode) { - return determineLayerIndexOfBinaryOperation( - (BavetJoinConstraintStream) buildHelper.getNodeCreatingStream(joinNode), buildHelper); - } else if (node instanceof AbstractConcatNode concatNode) { - return determineLayerIndexOfBinaryOperation( - (BavetConcatConstraintStream) buildHelper.getNodeCreatingStream(concatNode), buildHelper); - } else if (node instanceof AbstractIfExistsNode ifExistsNode) { - return determineLayerIndexOfBinaryOperation( - (BavetIfExistsConstraintStream) buildHelper.getNodeCreatingStream(ifExistsNode), buildHelper); + } else if (node instanceof AbstractTwoInputNode joinNode) { + var nodeCreator = (BavetStreamBinaryOperation) buildHelper.getNodeCreatingStream(joinNode); + var leftParent = nodeCreator.getLeftParent(); + var rightParent = nodeCreator.getRightParent(); + var leftParentNode = buildHelper.findParentNode(leftParent); + var rightParentNode = buildHelper.findParentNode(rightParent); + return Math.max(leftParentNode.getLayerIndex(), rightParentNode.getLayerIndex()) + 1; } else { - var nodeCreator = (BavetAbstractConstraintStream) buildHelper.getNodeCreatingStream(node); + var nodeCreator = buildHelper.getNodeCreatingStream(node); var parentNode = buildHelper.findParentNode(nodeCreator.getParent()); return parentNode.getLayerIndex() + 1; } } - private static > long determineLayerIndexOfBinaryOperation( - BavetStreamBinaryOperation nodeCreator, NodeBuildHelper buildHelper) { - var leftParent = nodeCreator.getLeftParent(); - var rightParent = nodeCreator.getRightParent(); - var leftParentNode = buildHelper.findParentNode(leftParent); - var rightParentNode = buildHelper.findParentNode(rightParent); - return Math.max(leftParentNode.getLayerIndex(), rightParentNode.getLayerIndex()) + 1; - } - } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetAbstractBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetAbstractBiConstraintStream.java index 77d1821042..819ba58127 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetAbstractBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetAbstractBiConstraintStream.java @@ -36,22 +36,22 @@ import ai.timefold.solver.core.impl.bavet.bi.Group3Mapping1CollectorBiNode; import ai.timefold.solver.core.impl.bavet.bi.Group4Mapping0CollectorBiNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.bavet.tri.joiner.TriJoinerComber; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetAbstractQuadConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetBiConcatQuadConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.tri.BavetAbstractTriConstraintStream; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiConcatBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiConcatBiConstraintStream.java index 9b075d00a0..8a42d87a57 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiConcatBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiConcatBiConstraintStream.java @@ -7,12 +7,12 @@ import ai.timefold.solver.core.impl.bavet.bi.ConcatBiBiNode; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; public final class BavetBiConcatBiConstraintStream extends BavetAbstractBiConstraintStream implements BavetConcatConstraintStream { @@ -47,7 +47,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); int leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); int rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiGroupBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiGroupBiConstraintStream.java index 8d3743e3d5..580ff2c80d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiGroupBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiGroupBiConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.bi; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetBiGroupBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiMapBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiMapBiConstraintStream.java index a181532801..5bf601d28b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiMapBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetBiMapBiConstraintStream.java @@ -5,9 +5,9 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.bi.MapBiToBiNode; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetBiMapBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -38,7 +38,7 @@ public void setAftBridge(BavetAftBridgeBiConstraintStream // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFilterBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFilterBiConstraintStream.java index a23bbabf9f..da65124acf 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFilterBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFilterBiConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.BiPredicate; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; final class BavetFilterBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -29,7 +29,7 @@ public BavetFilterBiConstraintStream(BavetConstraintFactory constrain // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { buildHelper.> putInsertUpdateRetract(this, childStreamList, tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFlattenLastBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFlattenLastBiConstraintStream.java index 74ee4aeba0..783557e86f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFlattenLastBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFlattenLastBiConstraintStream.java @@ -6,10 +6,10 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.bi.FlattenLastBiNode; import ai.timefold.solver.core.impl.bavet.common.AbstractFlattenLastNode; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetFlattenLastBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -38,7 +38,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(flattenLastStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetIfExistsBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetIfExistsBiConstraintStream.java index fa4eaff8e7..1291db1aa7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetIfExistsBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetIfExistsBiConstraintStream.java @@ -8,14 +8,14 @@ import ai.timefold.solver.core.impl.bavet.bi.IndexedIfExistsBiNode; import ai.timefold.solver.core.impl.bavet.bi.UnindexedIfExistsBiNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetIfExistsConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.index.IndexerFactory; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.tri.joiner.DefaultTriJoiner; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetIfExistsConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; final class BavetIfExistsBiConstraintStream extends BavetAbstractBiConstraintStream @@ -63,7 +63,7 @@ public BavetAbstractConstraintStream getTupleSource() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); IndexerFactory indexerFactory = new IndexerFactory<>(joiner); var node = indexerFactory.hasJoiners() diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetJoinBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetJoinBiConstraintStream.java index ee1355b27f..8df0b6a407 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetJoinBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetJoinBiConstraintStream.java @@ -9,13 +9,13 @@ import ai.timefold.solver.core.impl.bavet.bi.UnindexedJoinBiNode; import ai.timefold.solver.core.impl.bavet.bi.joiner.DefaultBiJoiner; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetJoinConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.index.IndexerFactory; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetJoinConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public final class BavetJoinBiConstraintStream extends BavetAbstractBiConstraintStream implements BavetJoinConstraintStream { @@ -53,7 +53,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { int outputStoreSize = buildHelper.extractTupleStoreSize(this); TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); IndexerFactory indexerFactory = new IndexerFactory<>(joiner); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetQuadGroupBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetQuadGroupBiConstraintStream.java index 05ccf2f43b..085cac464d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetQuadGroupBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetQuadGroupBiConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.bi; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetQuadGroupBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetQuadMapBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetQuadMapBiConstraintStream.java index b378c9b25e..55ed43f87d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetQuadMapBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetQuadMapBiConstraintStream.java @@ -5,9 +5,9 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.bi.MapBiToQuadNode; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetQuadMapBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -46,7 +46,7 @@ public void setAftBridge(BavetAftBridgeQuadConstraintStream> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetScoringBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetScoringBiConstraintStream.java index 27c39b8c46..e508269cac 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetScoringBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetScoringBiConstraintStream.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.impl.score.stream.bavet.bi; -import static ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream.impactWithConstraintMatchNoJustifications; +import static ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream.impactWithConstraintMatchNoJustifications; import java.math.BigDecimal; import java.util.function.BiFunction; @@ -9,11 +9,11 @@ import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.common.inliner.ConstraintMatchSupplier; import ai.timefold.solver.core.impl.score.stream.common.inliner.UndoScoreImpacter; import ai.timefold.solver.core.impl.score.stream.common.inliner.WeightedScoreImpacter; @@ -70,7 +70,7 @@ public void setConstraint(BavetConstraint constraint) { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); var scoreImpacter = buildScoreImpacter(buildHelper.getScoreInliner().getConstraintMatchPolicy()); var weightedScoreImpacter = buildHelper.getScoreInliner().buildWeightedScoreImpacter(constraint); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetTriGroupBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetTriGroupBiConstraintStream.java index a6bb6cc3f2..c68d30ab2d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetTriGroupBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetTriGroupBiConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.bi; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetTriGroupBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetTriMapBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetTriMapBiConstraintStream.java index 47c93b5387..429adb05fd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetTriMapBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetTriMapBiConstraintStream.java @@ -5,9 +5,9 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.bi.MapBiToTriNode; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetTriMapBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -43,7 +43,7 @@ public void setAftBridge(BavetAftBridgeTriConstraintStream> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniConcatBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniConcatBiConstraintStream.java index 703861020e..d056d28ec0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniConcatBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniConcatBiConstraintStream.java @@ -9,13 +9,13 @@ import ai.timefold.solver.core.impl.bavet.bi.ConcatUniBiNode; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public final class BavetUniConcatBiConstraintStream extends BavetAbstractBiConstraintStream implements BavetConcatConstraintStream { @@ -69,7 +69,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); var leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); var rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniGroupBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniGroupBiConstraintStream.java index 94ad04de5f..3e9c365eed 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniGroupBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniGroupBiConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.bi; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetUniGroupBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniMapBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniMapBiConstraintStream.java index 81ebf10045..6dce8209d4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniMapBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetUniMapBiConstraintStream.java @@ -5,9 +5,9 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.bi.MapBiToUniNode; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetUniMapBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -35,7 +35,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetConcatConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetConcatConstraintStream.java similarity index 50% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetConcatConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetConcatConstraintStream.java index c4474e4aa8..9f20d65c52 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetConcatConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetConcatConstraintStream.java @@ -1,4 +1,6 @@ -package ai.timefold.solver.core.impl.bavet.common; +package ai.timefold.solver.core.impl.score.stream.bavet.common; + +import ai.timefold.solver.core.impl.bavet.common.TupleSource; public interface BavetConcatConstraintStream extends BavetStreamBinaryOperation, TupleSource { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetIfExistsConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetIfExistsConstraintStream.java similarity index 63% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetIfExistsConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetIfExistsConstraintStream.java index 32a3fd99ff..ed46705d00 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetIfExistsConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetIfExistsConstraintStream.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.bavet.common; +package ai.timefold.solver.core.impl.score.stream.bavet.common; public interface BavetIfExistsConstraintStream extends BavetStreamBinaryOperation { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetJoinConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetJoinConstraintStream.java similarity index 59% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetJoinConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetJoinConstraintStream.java index 6f1bd40c97..9c839e0804 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetJoinConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetJoinConstraintStream.java @@ -1,6 +1,8 @@ -package ai.timefold.solver.core.impl.bavet.common; +package ai.timefold.solver.core.impl.score.stream.bavet.common; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; +import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; +import ai.timefold.solver.core.impl.bavet.common.TupleSource; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public interface BavetJoinConstraintStream extends BavetStreamBinaryOperation, TupleSource { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetScoringConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetScoringConstraintStream.java similarity index 92% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetScoringConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetScoringConstraintStream.java index 8576696b80..7518927884 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetScoringConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetScoringConstraintStream.java @@ -1,9 +1,10 @@ -package ai.timefold.solver.core.impl.bavet.common; +package ai.timefold.solver.core.impl.score.stream.bavet.common; import java.math.BigDecimal; import java.util.Set; import ai.timefold.solver.core.api.score.Score; +import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; import ai.timefold.solver.core.impl.score.stream.common.inliner.ConstraintMatchSupplier; import ai.timefold.solver.core.impl.score.stream.common.inliner.UndoScoreImpacter; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStreamBinaryOperation.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetStreamBinaryOperation.java similarity index 59% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStreamBinaryOperation.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetStreamBinaryOperation.java index 9a5538dc9c..093dda911f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/BavetStreamBinaryOperation.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/BavetStreamBinaryOperation.java @@ -1,6 +1,7 @@ -package ai.timefold.solver.core.impl.bavet.common; +package ai.timefold.solver.core.impl.score.stream.bavet.common; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; +import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public interface BavetStreamBinaryOperation { /** diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/ConstraintNodeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/ConstraintNodeBuildHelper.java new file mode 100644 index 0000000000..455b2658f5 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/ConstraintNodeBuildHelper.java @@ -0,0 +1,25 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common; + +import java.util.Set; + +import ai.timefold.solver.core.api.score.Score; +import ai.timefold.solver.core.impl.bavet.common.AbstractNodeBuildHelper; +import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; +import ai.timefold.solver.core.impl.score.stream.common.inliner.AbstractScoreInliner; + +public final class ConstraintNodeBuildHelper> + extends AbstractNodeBuildHelper> { + + private final AbstractScoreInliner scoreInliner; + + public ConstraintNodeBuildHelper(Set> activeStreamSet, + AbstractScoreInliner scoreInliner) { + super(activeStreamSet); + this.scoreInliner = scoreInliner; + } + + public AbstractScoreInliner getScoreInliner() { + return scoreInliner; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeBiConstraintStream.java similarity index 85% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeBiConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeBiConstraintStream.java index f6c22d6085..b42a89e805 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeBiConstraintStream.java @@ -1,13 +1,13 @@ -package ai.timefold.solver.core.impl.bavet.common.bridge; +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.TupleSource; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; import ai.timefold.solver.core.impl.score.stream.bavet.bi.BavetAbstractBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; public final class BavetAftBridgeBiConstraintStream extends BavetAbstractBiConstraintStream @@ -19,7 +19,7 @@ public BavetAftBridgeBiConstraintStream(BavetConstraintFactory constr } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { // Do nothing. The parent stream builds everything. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeQuadConstraintStream.java similarity index 85% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeQuadConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeQuadConstraintStream.java index ba5ab82242..ef798c6c78 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeQuadConstraintStream.java @@ -1,12 +1,12 @@ -package ai.timefold.solver.core.impl.bavet.common.bridge; +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.TupleSource; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetAbstractQuadConstraintStream; public final class BavetAftBridgeQuadConstraintStream @@ -19,7 +19,7 @@ public BavetAftBridgeQuadConstraintStream(BavetConstraintFactory cons } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { // Do nothing. The parent stream builds everything. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeTriConstraintStream.java similarity index 85% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeTriConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeTriConstraintStream.java index ff10b51048..0942635ab3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeTriConstraintStream.java @@ -1,12 +1,12 @@ -package ai.timefold.solver.core.impl.bavet.common.bridge; +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.TupleSource; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.tri.BavetAbstractTriConstraintStream; public final class BavetAftBridgeTriConstraintStream @@ -19,7 +19,7 @@ public BavetAftBridgeTriConstraintStream(BavetConstraintFactory const } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { // Do nothing. The parent stream builds everything. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeUniConstraintStream.java similarity index 85% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeUniConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeUniConstraintStream.java index eba50a18b8..a53869bdb0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetAftBridgeUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetAftBridgeUniConstraintStream.java @@ -1,12 +1,12 @@ -package ai.timefold.solver.core.impl.bavet.common.bridge; +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.TupleSource; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.uni.BavetAbstractUniConstraintStream; public final class BavetAftBridgeUniConstraintStream @@ -19,7 +19,7 @@ public BavetAftBridgeUniConstraintStream(BavetConstraintFactory const } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { // Do nothing. The parent stream builds everything. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeBiConstraintStream.java similarity index 80% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeBiConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeBiConstraintStream.java index 732cb031a9..d917acc221 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeBiConstraintStream.java @@ -1,9 +1,9 @@ -package ai.timefold.solver.core.impl.bavet.common.bridge; +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; import ai.timefold.solver.core.impl.score.stream.bavet.bi.BavetAbstractBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; public final class BavetForeBridgeBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -18,7 +18,7 @@ public BavetForeBridgeBiConstraintStream(BavetConstraintFactory const // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { // Do nothing. The child stream builds everything. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeQuadConstraintStream.java similarity index 80% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeQuadConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeQuadConstraintStream.java index 2c8d9aab1c..0f65cde50d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeQuadConstraintStream.java @@ -1,8 +1,8 @@ -package ai.timefold.solver.core.impl.bavet.common.bridge; +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetAbstractQuadConstraintStream; public final class BavetForeBridgeQuadConstraintStream @@ -18,7 +18,7 @@ public BavetForeBridgeQuadConstraintStream(BavetConstraintFactory con // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { // Do nothing. The child stream builds everything. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeTriConstraintStream.java similarity index 80% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeTriConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeTriConstraintStream.java index 81554be2d2..6252686a6d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeTriConstraintStream.java @@ -1,8 +1,8 @@ -package ai.timefold.solver.core.impl.bavet.common.bridge; +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.tri.BavetAbstractTriConstraintStream; public final class BavetForeBridgeTriConstraintStream @@ -18,7 +18,7 @@ public BavetForeBridgeTriConstraintStream(BavetConstraintFactory cons // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { // Do nothing. The child stream builds everything. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeUniConstraintStream.java similarity index 76% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeUniConstraintStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeUniConstraintStream.java index 5cdce16a70..a6877f97e0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/BavetForeBridgeUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/BavetForeBridgeUniConstraintStream.java @@ -1,8 +1,8 @@ -package ai.timefold.solver.core.impl.bavet.common.bridge; +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.uni.BavetAbstractUniConstraintStream; public final class BavetForeBridgeUniConstraintStream @@ -18,7 +18,7 @@ public BavetForeBridgeUniConstraintStream(BavetConstraintFactory cons // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { // Do nothing. The child stream builds everything. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/package-info.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/package-info.java similarity index 88% rename from core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/package-info.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/package-info.java index 5324b7acd8..1ca15f18a2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/bavet/common/bridge/package-info.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/bridge/package-info.java @@ -11,4 +11,4 @@ * Fore bridges are node-shared through their child stream * and therefore the equality logic can reside there entirely. */ -package ai.timefold.solver.core.impl.bavet.common.bridge; \ No newline at end of file +package ai.timefold.solver.core.impl.score.stream.bavet.common.bridge; \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetAbstractQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetAbstractQuadConstraintStream.java index f8c424dc91..4aaee60c15 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetAbstractQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetAbstractQuadConstraintStream.java @@ -24,16 +24,7 @@ import ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream; import ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; @@ -55,6 +46,15 @@ import ai.timefold.solver.core.impl.bavet.quad.Group4Mapping0CollectorQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; import ai.timefold.solver.core.impl.score.stream.bavet.bi.BavetAbstractBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.tri.BavetAbstractTriConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.uni.BavetAbstractUniConstraintStream; import ai.timefold.solver.core.impl.score.stream.common.RetrievalSemantics; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiConcatQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiConcatQuadConstraintStream.java index 0f7f8fe552..84fe4c9b0c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiConcatQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiConcatQuadConstraintStream.java @@ -7,15 +7,15 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.quad.ConcatBiQuadNode; import ai.timefold.solver.core.impl.bavet.quad.ConcatQuadBiNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; public final class BavetBiConcatQuadConstraintStream extends BavetAbstractQuadConstraintStream @@ -73,7 +73,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); var leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); var rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiGroupQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiGroupQuadConstraintStream.java index 1a612f5516..f874452736 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiGroupQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiGroupQuadConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.quad; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetBiGroupQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiMapQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiMapQuadConstraintStream.java index 04864d0adb..cda1944edf 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiMapQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetBiMapQuadConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.quad.MapQuadToBiNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetBiMapQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -38,7 +38,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFilterQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFilterQuadConstraintStream.java index dec78249ea..351b9f0457 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFilterQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFilterQuadConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.QuadPredicate; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; final class BavetFilterQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -29,7 +29,7 @@ public BavetFilterQuadConstraintStream(BavetConstraintFactory constra // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { buildHelper.> putInsertUpdateRetract(this, childStreamList, tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFlattenLastQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFlattenLastQuadConstraintStream.java index a476e76d38..a4b8ef4187 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFlattenLastQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFlattenLastQuadConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.quad.FlattenLastQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetFlattenLastQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -36,7 +36,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(flattenLastStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetIfExistsQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetIfExistsQuadConstraintStream.java index dd17b13272..0225a7f1d4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetIfExistsQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetIfExistsQuadConstraintStream.java @@ -6,9 +6,6 @@ import ai.timefold.solver.core.api.function.PentaPredicate; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetIfExistsConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.index.IndexerFactory; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; @@ -16,6 +13,9 @@ import ai.timefold.solver.core.impl.bavet.quad.IndexedIfExistsQuadNode; import ai.timefold.solver.core.impl.bavet.quad.UnindexedIfExistsQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetIfExistsConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; final class BavetIfExistsQuadConstraintStream extends BavetAbstractQuadConstraintStream @@ -63,7 +63,7 @@ public BavetAbstractConstraintStream getTupleSource() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); IndexerFactory indexerFactory = new IndexerFactory<>(joiner); var node = indexerFactory.hasJoiners() diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetJoinQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetJoinQuadConstraintStream.java index 033527ccd0..69e39b4e54 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetJoinQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetJoinQuadConstraintStream.java @@ -6,10 +6,6 @@ import ai.timefold.solver.core.api.function.QuadPredicate; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetJoinConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.index.IndexerFactory; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; @@ -17,6 +13,10 @@ import ai.timefold.solver.core.impl.bavet.quad.UnindexedJoinQuadNode; import ai.timefold.solver.core.impl.bavet.quad.joiner.DefaultQuadJoiner; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetJoinConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public final class BavetJoinQuadConstraintStream extends BavetAbstractQuadConstraintStream @@ -56,7 +56,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { int outputStoreSize = buildHelper.extractTupleStoreSize(this); TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); IndexerFactory indexerFactory = new IndexerFactory<>(joiner); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadConcatQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadConcatQuadConstraintStream.java index 4229cc46c9..374ddea107 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadConcatQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadConcatQuadConstraintStream.java @@ -6,13 +6,13 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.quad.ConcatQuadQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; public final class BavetQuadConcatQuadConstraintStream extends BavetAbstractQuadConstraintStream @@ -48,7 +48,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); int leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); int rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadGroupQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadGroupQuadConstraintStream.java index 2daa9dd0b5..2f368368e9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadGroupQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadGroupQuadConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.quad; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetQuadGroupQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadMapQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadMapQuadConstraintStream.java index 3098d1ef60..d2c0c1e46d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadMapQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetQuadMapQuadConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.quad.MapQuadToQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetQuadMapQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -43,7 +43,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetScoringQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetScoringQuadConstraintStream.java index 9555334043..2acade862b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetScoringQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetScoringQuadConstraintStream.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.impl.score.stream.bavet.quad; -import static ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream.impactWithConstraintMatchNoJustifications; +import static ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream.impactWithConstraintMatchNoJustifications; import java.math.BigDecimal; @@ -9,11 +9,11 @@ import ai.timefold.solver.core.api.function.ToIntQuadFunction; import ai.timefold.solver.core.api.function.ToLongQuadFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.common.inliner.ConstraintMatchSupplier; import ai.timefold.solver.core.impl.score.stream.common.inliner.UndoScoreImpacter; import ai.timefold.solver.core.impl.score.stream.common.inliner.WeightedScoreImpacter; @@ -71,7 +71,7 @@ public void setConstraint(BavetConstraint constraint) { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); var scoreImpacter = buildScoreImpacter(buildHelper.getScoreInliner().getConstraintMatchPolicy()); var weightedScoreImpacter = buildHelper.getScoreInliner().buildWeightedScoreImpacter(constraint); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriConcatQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriConcatQuadConstraintStream.java index 5cbda3d5f1..d887f3eb52 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriConcatQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriConcatQuadConstraintStream.java @@ -7,15 +7,15 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.quad.ConcatQuadTriNode; import ai.timefold.solver.core.impl.bavet.quad.ConcatTriQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; public final class BavetTriConcatQuadConstraintStream extends BavetAbstractQuadConstraintStream @@ -70,7 +70,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); var leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); var rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriGroupQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriGroupQuadConstraintStream.java index 4a1e847fd6..13268b46f7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriGroupQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriGroupQuadConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.quad; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetTriGroupQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriMapQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriMapQuadConstraintStream.java index f478aba019..4c49e8e8c5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriMapQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetTriMapQuadConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.quad.MapQuadToTriNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetTriMapQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -40,7 +40,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniConcatQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniConcatQuadConstraintStream.java index 237cbb65e1..baa03a7ce8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniConcatQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniConcatQuadConstraintStream.java @@ -7,15 +7,15 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.quad.ConcatQuadUniNode; import ai.timefold.solver.core.impl.bavet.quad.ConcatUniQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public final class BavetUniConcatQuadConstraintStream extends BavetAbstractQuadConstraintStream @@ -76,7 +76,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); var leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); var rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniGroupQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniGroupQuadConstraintStream.java index 062d4fb50c..1fdc734219 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniGroupQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniGroupQuadConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.quad; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetUniGroupQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniMapQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniMapQuadConstraintStream.java index c9f908eeaf..27d0d3b881 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniMapQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetUniMapQuadConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.quad.MapQuadToUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetUniMapQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -35,7 +35,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetAbstractTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetAbstractTriConstraintStream.java index 0a003f83b0..6cbe3d96ad 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetAbstractTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetAbstractTriConstraintStream.java @@ -28,16 +28,7 @@ import ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream; import ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; @@ -59,6 +50,15 @@ import ai.timefold.solver.core.impl.bavet.tri.Group4Mapping0CollectorTriNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; import ai.timefold.solver.core.impl.score.stream.bavet.bi.BavetAbstractBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetAbstractQuadConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetJoinQuadConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetTriConcatQuadConstraintStream; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiConcatTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiConcatTriConstraintStream.java index 0b891aa14f..56033eecb9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiConcatTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiConcatTriConstraintStream.java @@ -7,15 +7,15 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.tri.ConcatBiTriNode; import ai.timefold.solver.core.impl.bavet.tri.ConcatTriBiNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; public final class BavetBiConcatTriConstraintStream extends BavetAbstractTriConstraintStream @@ -70,7 +70,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); var leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); var rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiGroupTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiGroupTriConstraintStream.java index 28d37c103e..53b5a22428 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiGroupTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiGroupTriConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.tri; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetBiGroupTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiMapTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiMapTriConstraintStream.java index 0236a8650d..4705afe757 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiMapTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetBiMapTriConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.tri.MapTriToBiNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetBiMapTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -38,7 +38,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFilterTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFilterTriConstraintStream.java index ff183aa33c..26797a2e0f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFilterTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFilterTriConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.TriPredicate; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; final class BavetFilterTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -29,7 +29,7 @@ public BavetFilterTriConstraintStream(BavetConstraintFactory constrai // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { buildHelper.> putInsertUpdateRetract(this, childStreamList, tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFlattenLastTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFlattenLastTriConstraintStream.java index 5af955bc01..643b0a2026 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFlattenLastTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFlattenLastTriConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.tri.FlattenLastTriNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetFlattenLastTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -36,7 +36,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(flattenLastStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetIfExistsTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetIfExistsTriConstraintStream.java index 8463f8c993..83ff4355f4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetIfExistsTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetIfExistsTriConstraintStream.java @@ -6,9 +6,6 @@ import ai.timefold.solver.core.api.function.QuadPredicate; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetIfExistsConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.index.IndexerFactory; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; @@ -16,6 +13,9 @@ import ai.timefold.solver.core.impl.bavet.tri.IndexedIfExistsTriNode; import ai.timefold.solver.core.impl.bavet.tri.UnindexedIfExistsTriNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetIfExistsConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; final class BavetIfExistsTriConstraintStream extends BavetAbstractTriConstraintStream @@ -63,7 +63,7 @@ public BavetAbstractConstraintStream getTupleSource() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); IndexerFactory indexerFactory = new IndexerFactory<>(joiner); var node = indexerFactory.hasJoiners() diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetJoinTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetJoinTriConstraintStream.java index b02e5be244..8303c34a00 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetJoinTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetJoinTriConstraintStream.java @@ -6,10 +6,6 @@ import ai.timefold.solver.core.api.function.TriPredicate; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetJoinConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.index.IndexerFactory; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; @@ -17,6 +13,10 @@ import ai.timefold.solver.core.impl.bavet.tri.UnindexedJoinTriNode; import ai.timefold.solver.core.impl.bavet.tri.joiner.DefaultTriJoiner; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetJoinConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public final class BavetJoinTriConstraintStream extends BavetAbstractTriConstraintStream @@ -56,7 +56,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { int outputStoreSize = buildHelper.extractTupleStoreSize(this); TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); IndexerFactory indexerFactory = new IndexerFactory<>(joiner); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetQuadGroupTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetQuadGroupTriConstraintStream.java index 95d25c8240..563eb3cd42 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetQuadGroupTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetQuadGroupTriConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.tri; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetQuadGroupTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetQuadMapTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetQuadMapTriConstraintStream.java index 05e5390a29..96e6e209e3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetQuadMapTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetQuadMapTriConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.tri.MapTriToQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetQuadMapTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -45,7 +45,7 @@ public void setAftBridge(BavetAftBridgeQuadConstraintStream> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetScoringTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetScoringTriConstraintStream.java index 914c9c563f..c8e2c28201 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetScoringTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetScoringTriConstraintStream.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.impl.score.stream.bavet.tri; -import static ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream.impactWithConstraintMatchNoJustifications; +import static ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream.impactWithConstraintMatchNoJustifications; import java.math.BigDecimal; @@ -9,11 +9,11 @@ import ai.timefold.solver.core.api.function.ToLongTriFunction; import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.common.inliner.ConstraintMatchSupplier; import ai.timefold.solver.core.impl.score.stream.common.inliner.UndoScoreImpacter; import ai.timefold.solver.core.impl.score.stream.common.inliner.WeightedScoreImpacter; @@ -71,7 +71,7 @@ public void setConstraint(BavetConstraint constraint) { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); var scoreImpacter = buildScoreImpacter(buildHelper.getScoreInliner().getConstraintMatchPolicy()); var weightedScoreImpacter = buildHelper.getScoreInliner().buildWeightedScoreImpacter(constraint); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriConcatTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriConcatTriConstraintStream.java index cb5b6b9f88..93520b77da 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriConcatTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriConcatTriConstraintStream.java @@ -6,13 +6,13 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.tri.ConcatTriTriNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; public final class BavetTriConcatTriConstraintStream extends BavetAbstractTriConstraintStream @@ -48,7 +48,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); int leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); int rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriGroupTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriGroupTriConstraintStream.java index 56da9854d0..cfb364ce07 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriGroupTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriGroupTriConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.tri; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetTriGroupTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriMapTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriMapTriConstraintStream.java index afb311b0d8..8a1cf0f41c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriMapTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetTriMapTriConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.tri.MapTriToTriNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetTriMapTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -40,7 +40,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniConcatTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniConcatTriConstraintStream.java index eea67135ba..4ef03653ab 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniConcatTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniConcatTriConstraintStream.java @@ -7,15 +7,15 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.AbstractConcatNode; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.tri.ConcatTriUniNode; import ai.timefold.solver.core.impl.bavet.tri.ConcatUniTriNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public final class BavetUniConcatTriConstraintStream extends BavetAbstractTriConstraintStream @@ -73,7 +73,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); var leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); var rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniGroupTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniGroupTriConstraintStream.java index cbe3250b8a..b76def730a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniGroupTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniGroupTriConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.tri; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetUniGroupTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -37,8 +35,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniMapTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniMapTriConstraintStream.java index c71e97394b..e8b1aabf38 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniMapTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetUniMapTriConstraintStream.java @@ -4,10 +4,10 @@ import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.tri.MapTriToUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetUniMapTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -35,7 +35,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetAbstractUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetAbstractUniConstraintStream.java index b05dedaf6a..d3d5a2b64e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetAbstractUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetAbstractUniConstraintStream.java @@ -27,16 +27,7 @@ import ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream; import ai.timefold.solver.core.impl.bavet.bi.joiner.BiJoinerComber; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeBiConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeTriConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; @@ -59,6 +50,15 @@ import ai.timefold.solver.core.impl.score.stream.bavet.bi.BavetAbstractBiConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.bi.BavetJoinBiConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.bi.BavetUniConcatBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeBiConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeQuadConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeTriConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetAbstractQuadConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.quad.BavetUniConcatQuadConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.tri.BavetAbstractTriConstraintStream; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetBiGroupUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetBiGroupUniConstraintStream.java index 4d98b1ed50..18dd310b72 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetBiGroupUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetBiGroupUniConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.uni; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetBiGroupUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetBiMapUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetBiMapUniConstraintStream.java index 440776bdb4..b193374b7a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetBiMapUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetBiMapUniConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeBiConstraintStream; import ai.timefold.solver.core.impl.bavet.uni.MapUniToBiNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeBiConstraintStream; final class BavetBiMapUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -41,7 +41,7 @@ public void setAftBridge(BavetAftBridgeBiConstraintStream // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFilterUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFilterUniConstraintStream.java index 834d2e2afd..56a65849ce 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFilterUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFilterUniConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.Predicate; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; final class BavetFilterUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -28,7 +28,7 @@ public BavetFilterUniConstraintStream(BavetConstraintFactory constrai // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { buildHelper.> putInsertUpdateRetract(this, childStreamList, tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFlattenLastUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFlattenLastUniConstraintStream.java index b677271bca..0f21ee1acd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFlattenLastUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFlattenLastUniConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.uni.FlattenLastUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetFlattenLastUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -36,7 +36,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(flattenLastStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetForEachUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetForEachUniConstraintStream.java index 7c2ba5327a..1d8a299b6c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetForEachUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetForEachUniConstraintStream.java @@ -6,13 +6,13 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.bavet.common.TupleSource; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.bavet.uni.ForEachExcludingUnassignedUniNode; -import ai.timefold.solver.core.impl.bavet.uni.ForEachIncludingUnassignedUniNode; +import ai.timefold.solver.core.impl.bavet.uni.ForEachUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.common.RetrievalSemantics; public final class BavetForEachUniConstraintStream @@ -47,10 +47,10 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> tupleLifecycle = buildHelper.getAggregatedTupleLifecycle(childStreamList); int outputStoreSize = buildHelper.extractTupleStoreSize(this); - var node = filter == null ? new ForEachIncludingUnassignedUniNode<>(forEachClass, tupleLifecycle, outputStoreSize) + var node = filter == null ? new ForEachUniNode<>(forEachClass, tupleLifecycle, outputStoreSize) : new ForEachExcludingUnassignedUniNode<>(forEachClass, filter, tupleLifecycle, outputStoreSize); buildHelper.addNode(node, this, null); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetIfExistsUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetIfExistsUniConstraintStream.java index 3a6b35e4a6..f74a234858 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetIfExistsUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetIfExistsUniConstraintStream.java @@ -7,15 +7,15 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.bi.joiner.DefaultBiJoiner; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetIfExistsConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.index.IndexerFactory; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.bavet.uni.IndexedIfExistsUniNode; import ai.timefold.solver.core.impl.bavet.uni.UnindexedIfExistsUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetIfExistsConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; final class BavetIfExistsUniConstraintStream extends BavetAbstractUniConstraintStream @@ -62,7 +62,7 @@ public BavetAbstractConstraintStream getTupleSource() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); IndexerFactory indexerFactory = new IndexerFactory<>(joiner); var node = indexerFactory.hasJoiners() diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetQuadGroupUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetQuadGroupUniConstraintStream.java index 93f0963572..837b44638a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetQuadGroupUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetQuadGroupUniConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.uni; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetQuadGroupUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetQuadMapUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetQuadMapUniConstraintStream.java index 8726b864ec..1efdbab456 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetQuadMapUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetQuadMapUniConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; import ai.timefold.solver.core.impl.bavet.uni.MapUniToQuadNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeQuadConstraintStream; final class BavetQuadMapUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -46,7 +46,7 @@ public void setAftBridge(BavetAftBridgeQuadConstraintStream> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetScoringUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetScoringUniConstraintStream.java index d0c2a86520..0dd7ca1ba0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetScoringUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetScoringUniConstraintStream.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.impl.score.stream.bavet.uni; -import static ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream.impactWithConstraintMatchNoJustifications; +import static ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream.impactWithConstraintMatchNoJustifications; import java.math.BigDecimal; import java.util.function.BiFunction; @@ -9,11 +9,11 @@ import java.util.function.ToLongFunction; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.BavetScoringConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetScoringConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.common.inliner.ConstraintMatchSupplier; import ai.timefold.solver.core.impl.score.stream.common.inliner.UndoScoreImpacter; import ai.timefold.solver.core.impl.score.stream.common.inliner.WeightedScoreImpacter; @@ -70,7 +70,7 @@ public void setConstraint(BavetConstraint constraint) { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); var scoreImpacter = buildScoreImpacter(buildHelper.getScoreInliner().getConstraintMatchPolicy()); var weightedScoreImpacter = buildHelper.getScoreInliner().buildWeightedScoreImpacter(constraint); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetTriGroupUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetTriGroupUniConstraintStream.java index 949d7cd26f..fc1bbac126 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetTriGroupUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetTriGroupUniConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.uni; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.TriTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetTriGroupUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -38,8 +36,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetTriMapUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetTriMapUniConstraintStream.java index 6391e1c04a..b8d9968aeb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetTriMapUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetTriMapUniConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeTriConstraintStream; import ai.timefold.solver.core.impl.bavet.uni.MapUniToTriNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeTriConstraintStream; final class BavetTriMapUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -43,7 +43,7 @@ public void setAftBridge(BavetAftBridgeTriConstraintStream> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniConcatUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniConcatUniConstraintStream.java index fadc20a97a..60d81e57a3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniConcatUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniConcatUniConstraintStream.java @@ -5,13 +5,13 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.bavet.common.BavetAbstractConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.BavetConcatConstraintStream; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetForeBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.bavet.uni.ConcatUniUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetConcatConstraintStream; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetForeBridgeUniConstraintStream; public final class BavetUniConcatUniConstraintStream extends BavetAbstractUniConstraintStream @@ -45,7 +45,7 @@ public void collectActiveConstraintStreams(Set> void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { TupleLifecycle> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList); int leftCloneStoreIndex = buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()); int rightCloneStoreIndex = buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniGroupUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniGroupUniConstraintStream.java index 4ba2b8c15d..b20263b07e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniGroupUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniGroupUniConstraintStream.java @@ -1,15 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.uni; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.stream.ConstraintStream; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetUniGroupUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -37,8 +35,8 @@ public boolean guaranteesDistinct() { // ************************************************************************ @Override - public > void buildNode(NodeBuildHelper buildHelper) { - List aftStreamChildList = aftStream.getChildStreamList(); + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { + var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, childStreamList, constraintFactory.getEnvironmentMode()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniMapUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniMapUniConstraintStream.java index d1d1a43925..52baa8639b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniMapUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetUniMapUniConstraintStream.java @@ -4,10 +4,10 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.impl.bavet.common.NodeBuildHelper; -import ai.timefold.solver.core.impl.bavet.common.bridge.BavetAftBridgeUniConstraintStream; import ai.timefold.solver.core.impl.bavet.uni.MapUniToUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.bavet.common.ConstraintNodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.bridge.BavetAftBridgeUniConstraintStream; final class BavetUniMapUniConstraintStream extends BavetAbstractUniConstraintStream { @@ -35,7 +35,7 @@ public boolean guaranteesDistinct() { } @Override - public > void buildNode(NodeBuildHelper buildHelper) { + public > void buildNode(ConstraintNodeBuildHelper buildHelper) { assertEmptyChildStreamList(); int inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); int outputStoreSize = buildHelper.extractTupleStoreSize(aftStream); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java index d646828c36..36ab8752ef 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java @@ -65,8 +65,8 @@ private Result_ callScoreDirector(Solution_ solution, ConstraintMatchPolicy constraintMatchPolicy, boolean cloneSolution) { var isShadowVariableUpdateEnabled = solutionUpdatePolicy.isShadowVariableUpdateEnabled(); var nonNullSolution = Objects.requireNonNull(solution); - try (var scoreDirector = getScoreDirectorFactory().buildScoreDirector(cloneSolution, constraintMatchPolicy, - !isShadowVariableUpdateEnabled)) { + try (var scoreDirector = getScoreDirectorFactory().buildScoreDirector(null, cloneSolution, + constraintMatchPolicy, !isShadowVariableUpdateEnabled)) { nonNullSolution = cloneSolution ? scoreDirector.cloneSolution(nonNullSolution) : nonNullSolution; scoreDirector.setWorkingSolution(nonNullSolution); if (constraintMatchPolicy.isEnabled() && !scoreDirector.getConstraintMatchPolicy().isEnabled()) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java index 2334461448..a8eeaa3580 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java @@ -107,7 +107,8 @@ public > InnerScoreDirectorFactory(castScoreDirector)); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/util/ElementAwareList.java b/core/src/main/java/ai/timefold/solver/core/impl/util/ElementAwareList.java index 84f47552d6..f3672d5d06 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/util/ElementAwareList.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/util/ElementAwareList.java @@ -1,8 +1,13 @@ package ai.timefold.solver.core.impl.util; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Random; import java.util.function.Consumer; /** @@ -16,8 +21,12 @@ public final class ElementAwareList implements Iterable { private int size = 0; private ElementAwareListEntry first = null; private ElementAwareListEntry last = null; + private List copy = null; // Cached copy for randomized iterator. public ElementAwareListEntry add(T tuple) { + if (copy != null) { + copy.add(tuple); + } ElementAwareListEntry entry = new ElementAwareListEntry<>(this, tuple, last); if (first == null) { first = entry; @@ -31,6 +40,7 @@ public ElementAwareListEntry add(T tuple) { public ElementAwareListEntry addFirst(T tuple) { if (first != null) { + copy = null; ElementAwareListEntry entry = new ElementAwareListEntry<>(this, tuple, null); first.previous = entry; entry.next = first; @@ -47,6 +57,7 @@ public ElementAwareListEntry addAfter(T tuple, ElementAwareListEntry previ if (first == null || previous == last) { return add(tuple); } else { + copy = null; ElementAwareListEntry entry = new ElementAwareListEntry<>(this, tuple, previous); ElementAwareListEntry currentNext = previous.next; if (currentNext != null) { @@ -62,6 +73,14 @@ public ElementAwareListEntry addAfter(T tuple, ElementAwareListEntry previ } public void remove(ElementAwareListEntry entry) { + if (copy != null) { + if (entry == last) { + copy.remove(size - 1); + } else { + copy = null; + } + } + if (first == entry) { first = entry.next; } else { @@ -154,6 +173,25 @@ public Iterator iterator() { return new ElementAwareListIterator<>(first); } + public Iterator randomizedIterator(Random random) { + return switch (size) { + case 0 -> Collections.emptyIterator(); + case 1 -> Collections.singleton(first.getElement()).iterator(); + case 2 -> { + var list = random.nextBoolean() ? List.of(first.getElement(), last.getElement()) + : List.of(last.getElement(), first.getElement()); + yield list.iterator(); + } + default -> { + if (copy == null) { // Only copy the list if the existing copy is no longer up to date. + copy = new ArrayList<>(size + 1); // Avoid resizing. + forEach(copy::add); + } + yield new RandomElementAwareListIterator<>(copy, random); + } + }; + } + @Override public String toString() { switch (size) { @@ -198,4 +236,47 @@ public T next() { } } + + /** + * The idea of this iterator is that the list will rarely ever be iterated over in its entirety. + * In fact, move streams are likely to only use the first few elements. + * Therefore, shuffling the entire list would be a waste of time. + * Instead, we pick random index every time and keep a bitset of used elements. + * + * @param The element type. Often a tuple. + */ + private static final class RandomElementAwareListIterator implements Iterator { + + private final List list; + private final Random random; + + private final BitSet usedElements; + private int unusedElements; + + public RandomElementAwareListIterator(List copiedList, Random random) { + this.random = random; + this.list = copiedList; + this.unusedElements = copiedList.size(); + this.usedElements = new BitSet(unusedElements); + } + + @Override + public boolean hasNext() { + return unusedElements > 0; + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + var randomIndex = random.nextInt(unusedElements); + var availableIndex = usedElements.nextClearBit(randomIndex); + usedElements.set(availableIndex); + unusedElements--; + return list.get(availableIndex); + } + + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningEntityMetaModel.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningEntityMetaModel.java index 964c89f25b..6f760612c0 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningEntityMetaModel.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningEntityMetaModel.java @@ -63,6 +63,24 @@ public interface PlanningEntityMetaModel { .toList(); } + /** + * Returns a {@link PlanningVariableMetaModel} for a variable with the given name. + * + * @return A genuine variable declared by the entity. + * @throws IllegalArgumentException if the variable does not exist on the entity, or is not genuine + */ + @SuppressWarnings("unchecked") + default @NonNull GenuineVariableMetaModel + genuineVariable(@NonNull String variableName) { + var variable = variable(variableName); + if (!variable.isGenuine()) { + throw new IllegalArgumentException( + "The variableName (%s) exists among variables (%s) but is not genuine.".formatted(variableName, + variables())); + } + return (GenuineVariableMetaModel) variable; + } + /** * Returns a {@link VariableMetaModel} for a variable with the given name. * diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningVariableMetaModel.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningVariableMetaModel.java index 7ed3f60361..16c74e2891 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningVariableMetaModel.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningVariableMetaModel.java @@ -24,6 +24,11 @@ public non-sealed interface PlanningVariableMetaModel extends GenuineVariableMetaModel { + @Override + default boolean isList() { + return false; + } + /** * Returns whether the planning variable allows null values. * diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/ShadowVariableMetaModel.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/ShadowVariableMetaModel.java index 95119d912b..936b8bb2cc 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/ShadowVariableMetaModel.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/ShadowVariableMetaModel.java @@ -27,4 +27,14 @@ public non-sealed interface ShadowVariableMetaModel extends VariableMetaModel { + @Override + default boolean isList() { + return false; + } + + @Override + default boolean isGenuine() { + return false; + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/VariableMetaModel.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/VariableMetaModel.java index 0460e78d47..bebc1114c0 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/VariableMetaModel.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/VariableMetaModel.java @@ -58,9 +58,7 @@ public sealed interface VariableMetaModel * * @return true if this variable is a genuine @{@link PlanningListVariable}, false otherwise */ - default boolean isList() { - return false; - } + boolean isList(); /** * Whether this variable is a genuine variable. @@ -70,8 +68,6 @@ default boolean isList() { * * @return true if this variable is genuine, false otherwise */ - default boolean isGenuine() { - return false; - } + boolean isGenuine(); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/BiMoveConstructor.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/BiMoveConstructor.java new file mode 100644 index 0000000000..88c1270ef6 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/BiMoveConstructor.java @@ -0,0 +1,9 @@ +package ai.timefold.solver.core.preview.api.move; + +@FunctionalInterface +public non-sealed interface BiMoveConstructor + extends MoveConstructor { + + Move apply(Solution_ solution, A a, B b); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/DataStream.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/DataStream.java new file mode 100644 index 0000000000..6252f863ce --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/DataStream.java @@ -0,0 +1,5 @@ +package ai.timefold.solver.core.preview.api.move; + +public interface DataStream { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/DatasetFactory.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/DatasetFactory.java new file mode 100644 index 0000000000..1e91262553 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/DatasetFactory.java @@ -0,0 +1,40 @@ +package ai.timefold.solver.core.preview.api.move; + +import java.util.Collection; + +import org.jspecify.annotations.NonNull; + +public interface DatasetFactory { + + /** + * Create a cached stream of all possible values of a class. + * Read from the planning solution. + * + * @param clz + * @return + * @param + */ + @NonNull UniDataStream forEach(@NonNull Class clz); + + /** + * Create a cached stream of arbitrary values. + * Read from the planning solution. + * + * @param clz + * @param extractor + * @param + * @return + */ + @NonNull UniDataStream forEach(@NonNull Class clz, @NonNull SolutionExtractor extractor); + + /** + * Create a cached stream of arbitrary values. + * + * @param clz + * @param collection + * @param + * @return + */ + @NonNull UniDataStream forEach(@NonNull Class clz, @NonNull Collection collection); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java index eee2b9cb27..ede3baa626 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java @@ -136,7 +136,7 @@ public interface Move { * * @return A description of the move, ideally including the state of the planning entities being changed. */ - @NonNull + @Override String toString(); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/MoveConstructor.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/MoveConstructor.java new file mode 100644 index 0000000000..728e5b5754 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/MoveConstructor.java @@ -0,0 +1,5 @@ +package ai.timefold.solver.core.preview.api.move; + +public sealed interface MoveConstructor permits BiMoveConstructor { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java index 0282a4556b..0ef2b7aeae 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java @@ -29,6 +29,77 @@ */ public interface MutableSolutionView extends SolutionView { + /** + * Puts a given value at a particular index in a given entity's {@link PlanningListVariable planning list variable}. + * Moves all values at or after the index to the right. + * + * @param variableMetaModel Describes the variable to be changed. + * @param value The value to be assigned to a list variable. + * @param destinationEntity The entity whose list variable is to be changed. + * @param destinationIndex The index at which the value is to be assigned. + * @param + * @param + */ + void assignValue(@NonNull PlanningListVariableMetaModel variableMetaModel, + @NonNull Value_ value, @NonNull Entity_ destinationEntity, int destinationIndex); + + /** + * Removes a given value from the {@link PlanningListVariable planning list variable} that it's part of. + * Shifts any later values to the left. + * + * @param variableMetaModel Describes the variable to be changed. + * @param value The value to be removed from a list variable. + * @param + * @param + * @throws IllegalStateException if the value is not assigned to a list variable + */ + default void unassignValue( + @NonNull PlanningListVariableMetaModel variableMetaModel, + @NonNull Value_ value) { + var locationInList = getPositionOf(variableMetaModel, value) + .ensureAssigned(() -> """ + The value (%s) is not assigned to a list variable. + This may indicate score corruption or a problem with the move's implementation.""" + .formatted(value)); + unassignValue(variableMetaModel, value, locationInList.entity(), locationInList.index()); + } + + /** + * Removes a value from a given entity's {@link PlanningListVariable planning list variable} at a given index. + * Shifts any later values to the left. + * + * @param variableMetaModel Describes the variable to be changed. + * @param entity The entity whose element is to be removed from a list variable. + * @param index >= 0 + * @param + * @param + * @return the removed value + * @throws IllegalArgumentException if the index is out of bounds + */ + default @NonNull Value_ unassignValue( + @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Entity_ entity, + int index) { + var value = getValueAtIndex(variableMetaModel, entity, index); + unassignValue(variableMetaModel, value, entity, index); + return value; + } + + /** + * Removes a given value from a given entity's {@link PlanningListVariable planning list variable} at a given index. + * Shifts any later values to the left. + * + * @param variableMetaModel Describes the variable to be changed. + * @param value The value to be unassigned from a list variable. + * @param entity The entity whose value is to be unassigned from a list variable. + * @param index >= 0 + * @param + * @param + * @throws IllegalArgumentException if the index is out of bounds + * @throws IllegalStateException if the actual value at the given index is not the given value + */ + void unassignValue(@NonNull PlanningListVariableMetaModel variableMetaModel, + @NonNull Value_ value, @NonNull Entity_ entity, int index); + /** * Reads the value of a @{@link PlanningVariable basic planning variable} of a given entity. * @@ -47,9 +118,10 @@ void changeVariable(@NonNull PlanningVariableMetaModel= 0 * @param destinationEntity The second entity whose variable value is to be changed. * @param destinationIndex >= 0 + * @return the value that was moved; null if nothing was moved * @throws IndexOutOfBoundsException if the index is out of bounds */ - void moveValueBetweenLists( + @Nullable Value_ moveValueBetweenLists( @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Entity_ sourceEntity, int sourceIndex, @NonNull Entity_ destinationEntity, int destinationIndex); @@ -60,9 +132,11 @@ void moveValueBetweenLists( * @param entity The entity whose variable value is to be changed. * @param sourceIndex >= 0 * @param destinationIndex >= 0 + * @return the value that was moved; null if nothing was moved * @throws IndexOutOfBoundsException if the index is out of bounds */ - void moveValueInList(@NonNull PlanningListVariableMetaModel variableMetaModel, + @Nullable Value_ moveValueInList( + @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Entity_ entity, int sourceIndex, int destinationIndex); /** diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Picker.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Picker.java new file mode 100644 index 0000000000..cd54a77c82 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Picker.java @@ -0,0 +1,5 @@ +package ai.timefold.solver.core.preview.api.move; + +public interface Picker { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java index f6bdf0d3d9..e73b74593a 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java @@ -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; /** @@ -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 */ - @Nullable T rebase(@NonNull T problemFactOrPlanningEntity); + @Nullable T rebase(@Nullable T problemFactOrPlanningEntity); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionExtractor.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionExtractor.java new file mode 100644 index 0000000000..523f825b43 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionExtractor.java @@ -0,0 +1,9 @@ +package ai.timefold.solver.core.preview.api.move; + +import java.util.function.BiFunction; +import java.util.stream.Stream; + +@FunctionalInterface +public interface SolutionExtractor + extends BiFunction, Solution_, Stream> { +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionView.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionView.java index 83a375a9dc..2a500f9dad 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionView.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionView.java @@ -43,11 +43,11 @@ public interface SolutionView { * @param variableMetaModel Describes the variable whose value is to be read. * @param entity The entity whose variable is to be read. * @param index >= 0 - * @return maybe null; the value of the variable on the entity at the index + * @return The value at the given index in the list variable. * @throws NullPointerException if the value of the list variable is null * @throws IndexOutOfBoundsException if the index is out of bounds */ - @Nullable Value_ getValueAtIndex( + @NonNull Value_ getValueAtIndex( @NonNull PlanningListVariableMetaModel variableMetaModel, @NonNull Entity_ entity, int index); diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/UniDataStream.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/UniDataStream.java new file mode 100644 index 0000000000..24e7315bdc --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/UniDataStream.java @@ -0,0 +1,171 @@ +package ai.timefold.solver.core.preview.api.move; + +import static ai.timefold.solver.core.impl.util.ConstantLambdaUtils.notEquals; + +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.score.stream.Joiners; +import ai.timefold.solver.core.api.score.stream.bi.BiJoiner; + +import org.jspecify.annotations.NonNull; + +public interface UniDataStream extends DataStream { + + /** + * Exhaustively test each fact against the {@link Predicate} + * and match if {@link Predicate#test(Object)} returns true. + */ + @NonNull + UniDataStream filter(@NonNull Predicate predicate); + + /** + * Create a new {@link UniDataStream} for every A where B exists for which all {@link BiJoiner}s are true + * (for the properties they extract from both facts). + * + * @param the type of the second matched fact + * @return a stream that matches every A where B exists for which the {@link BiJoiner}s are true + */ + @SuppressWarnings("unchecked") + @NonNull UniDataStream ifExists(@NonNull Class otherClass, @NonNull BiJoiner... joiners); + + /** + * Create a new {@link UniDataStream} for every A where B exists for which all {@link BiJoiner}s are true + * (for the properties it extracts from both facts). + * + * @param the type of the second matched fact + * @return a stream that matches every A where B exists for which the {@link BiJoiner}s are true + */ + @SuppressWarnings("unchecked") + @NonNull UniDataStream ifExists(@NonNull UniDataStream otherStream, + @NonNull BiJoiner... joiners); + + /** + * Create a new {@link UniDataStream} for every A where B exists for which the {@link BiJoiner}s are true + * (for the properties they extract from both facts). + * For classes annotated with {@link PlanningEntity}, + * this method also includes entities with null variables, + * or entities that are not assigned to any list variable. + * + * @param the type of the second matched fact + * @return a stream that matches every A where B exists for which the {@link BiJoiner}s are true + */ + @SuppressWarnings("unchecked") + @NonNull UniDataStream ifExistsIncludingUnassigned(@NonNull Class otherClass, + @NonNull BiJoiner... joiners); + + /** + * Create a new {@link UniDataStream} for every A, if another A exists that does not {@link Object#equals(Object)} + * the first, and for which the {@link BiJoiner}s are true (for the properties they extract from both facts). + * + * @return a stream that matches every A where a different A exists for which the {@link BiJoiner}s are true + */ + @SuppressWarnings("unchecked") + default @NonNull UniDataStream ifExistsOther(@NonNull Class otherClass, + @NonNull BiJoiner... joiners) { + BiJoiner otherness = Joiners.filtering(notEquals()); + + @SuppressWarnings("unchecked") + BiJoiner[] allJoiners = Stream.concat(Arrays.stream(joiners), Stream.of(otherness)) + .toArray(BiJoiner[]::new); + return ifExists(otherClass, allJoiners); + } + + /** + * Create a new {@link UniDataStream} for every A, + * if another A exists that does not {@link Object#equals(Object)} the first, + * and for which the {@link BiJoiner}s are true (for the properties they extract from both facts). + * For classes annotated with {@link PlanningEntity}, + * this method also includes entities with null variables, + * or entities that are not assigned to any list variable. + * + * @return a stream that matches every A where a different A exists for which the {@link BiJoiner}s are true + */ + @SuppressWarnings("unchecked") + default @NonNull UniDataStream ifExistsOtherIncludingUnassigned(@NonNull Class otherClass, + @NonNull BiJoiner... joiners) { + BiJoiner otherness = Joiners.filtering(notEquals()); + + @SuppressWarnings("unchecked") + BiJoiner[] allJoiners = Stream.concat(Arrays.stream(joiners), Stream.of(otherness)) + .toArray(BiJoiner[]::new); + return ifExistsIncludingUnassigned(otherClass, allJoiners); + } + + /** + * Create a new {@link UniDataStream} for every A where B does not exist for which the {@link BiJoiner}s are true + * (for the properties they extract from both facts). + * + * @param the type of the second matched fact + * @return a stream that matches every A where B does not exist for which the {@link BiJoiner}s are true + */ + @SuppressWarnings("unchecked") + @NonNull UniDataStream ifNotExists(@NonNull Class otherClass, @NonNull BiJoiner... joiners); + + /** + * Create a new {@link UniDataStream} for every A where B does not exist for which the {@link BiJoiner}s are true + * (for the properties they extract from both facts). + * + * @param the type of the second matched fact + * @return a stream that matches every A where B does not exist for which the {@link BiJoiner}s are true + */ + @SuppressWarnings("unchecked") + @NonNull UniDataStream ifNotExists(@NonNull UniDataStream otherStream, + @NonNull BiJoiner... joiners); + + /** + * Create a new {@link UniDataStream} for every A where B does not exist for which the {@link BiJoiner}s are true + * (for the properties they extract from both facts). + * For classes annotated with {@link PlanningEntity}, + * this method also includes entities with null variables, + * or entities that are not assigned to any list variable. + * + * @param the type of the second matched fact + * @return a stream that matches every A where B does not exist for which the {@link BiJoiner}s are true + */ + @SuppressWarnings("unchecked") + @NonNull UniDataStream ifNotExistsIncludingUnassigned(@NonNull Class otherClass, + @NonNull BiJoiner... joiners); + + /** + * Create a new {@link UniDataStream} for every A, if no other A exists that does not {@link Object#equals(Object)} + * the first, + * for which the {@link BiJoiner}s are true (for the properties they extract from both facts). + * + * @return a stream that matches every A where a different A does not exist + */ + @SuppressWarnings("unchecked") + default @NonNull UniDataStream ifNotExistsOther(@NonNull Class otherClass, + @NonNull BiJoiner... joiners) { + BiJoiner otherness = Joiners.filtering(notEquals()); + + @SuppressWarnings("unchecked") + BiJoiner[] allJoiners = Stream.concat(Arrays.stream(joiners), Stream.of(otherness)) + .toArray(BiJoiner[]::new); + return ifNotExists(otherClass, allJoiners); + } + + /** + * Create a new {@link UniDataStream} for every A, + * if no other A exists that does not {@link Object#equals(Object)} the first, + * for which the {@link BiJoiner}s are true (for the properties they extract from both facts). + * For classes annotated with {@link PlanningEntity}, + * this method also includes entities with null variables, + * or entities that are not assigned to any list variable. + * + * @return a stream that matches every A where a different A does not exist + */ + @SuppressWarnings("unchecked") + default @NonNull UniDataStream ifNotExistsOtherIncludingUnassigned(@NonNull Class otherClass, + @NonNull BiJoiner... joiners) { + BiJoiner otherness = Joiners.filtering(notEquals()); + + @SuppressWarnings("unchecked") + BiJoiner[] allJoiners = Stream.concat(Arrays.stream(joiners), Stream.of(otherness)) + .toArray(BiJoiner[]::new); + return ifNotExistsIncludingUnassigned(otherClass, allJoiners); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/provider/ChangeMoveProvider.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/provider/ChangeMoveProvider.java new file mode 100644 index 0000000000..7f779b8123 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/provider/ChangeMoveProvider.java @@ -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 + implements MoveProvider { + + private final PlanningVariableMetaModel variableMetaModel; + + public ChangeMoveProvider(PlanningVariableMetaModel variableMetaModel) { + this.variableMetaModel = Objects.requireNonNull(variableMetaModel); + } + + @Override + public MoveConstructor apply(MoveStreams 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; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/CachedMoveUniStream.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/CachedMoveUniStream.java new file mode 100644 index 0000000000..0a6ed76da7 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/CachedMoveUniStream.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.preview.api.move.stream; + +import java.util.function.Function; + +public interface CachedMoveUniStream { + + CachedMoveUniStream filter(Function filter); + + CachedMoveUniStream addNull(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveBiStream.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveBiStream.java new file mode 100644 index 0000000000..b918897083 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveBiStream.java @@ -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 extends JitMoveStream { + + BiMoveConstructor asMove(TriFunction> moveFactory); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveStream.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveStream.java new file mode 100644 index 0000000000..2becb12716 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveStream.java @@ -0,0 +1,7 @@ +package ai.timefold.solver.core.preview.api.move.stream; + +public interface JitMoveStream { + + MoveStreams getMoveFactory(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveUniStream.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveUniStream.java new file mode 100644 index 0000000000..0f630bebb8 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/JitMoveUniStream.java @@ -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 extends JitMoveStream { + + default JitMoveBiStream pick(Class clz) { + return pick(getMoveFactory().enumerate(clz)); + } + + default JitMoveBiStream pick(Class clz, BiPredicate filter) { + return pick(getMoveFactory().enumerate(clz), filter); + } + + JitMoveBiStream pick(CachedMoveUniStream cachedMoveUniStream); + + JitMoveBiStream pick(CachedMoveUniStream cachedMoveUniStream, BiPredicate filter); + + JitMoveBiStream pick(Function> cachedMoveUniStreamFunction); + + default JitMoveBiStream pickOther(Class clz) { + return pickOther(getMoveFactory().enumerate(clz)); + } + + default JitMoveBiStream pickOther(Class clz, BiPredicate filter) { + return pickOther(getMoveFactory().enumerate(clz), filter); + } + + // TODO identity or equality? + default JitMoveBiStream pickOther(CachedMoveUniStream cachedMoveUniStream) { + return pick(cachedMoveUniStream, (a, b) -> !a.equals(b)); + } + + // TODO identity or equality? + default JitMoveBiStream pickOther(CachedMoveUniStream cachedMoveUniStream, + BiPredicate filter) { + return pick(cachedMoveUniStream, filter); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/MoveProvider.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/MoveProvider.java new file mode 100644 index 0000000000..2e257d8556 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/MoveProvider.java @@ -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 the solution type, the class with the {@link PlanningSolution} annotation + */ +@FunctionalInterface +public interface MoveProvider + extends Function, MoveConstructor> { +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/MoveStreams.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/MoveStreams.java new file mode 100644 index 0000000000..c74b2a932e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/stream/MoveStreams.java @@ -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 { + + CachedMoveUniStream enumerate(Class clz); + + default CachedMoveUniStream + enumerateEntities(PlanningEntityMetaModel entityMetaModel) { + return enumerate(entityMetaModel.type()); + } + + @SuppressWarnings("unchecked") + default CachedMoveUniStream + enumeratePossibleValues(PlanningVariableMetaModel variableMetaModel) { + var variableDescriptor = + ((DefaultPlanningVariableMetaModel) variableMetaModel).variableDescriptor(); + var valueRangeDescriptor = variableDescriptor.getValueRangeDescriptor(); + if (variableDescriptor.isValueRangeEntityIndependent()) { + return enumerate(solution -> (Collection) valueRangeDescriptor.extractValueRange(solution, null)); + } else { + return enumerateFromEntity(variableMetaModel.entity(), + (solution, entity) -> (Collection) valueRangeDescriptor.extractValueRange(solution, entity)); + } + } + + CachedMoveUniStream enumerate(Function> collectionFunction); + + CachedMoveUniStream enumerateFromEntity( + PlanningEntityMetaModel entityMetaModel, + BiFunction> collectionFunction); + + CachedMoveUniStream enumerate(Collection collection); + + default JitMoveUniStream pick(Class clz) { + return pick(enumerate(clz)); + } + + JitMoveUniStream pick(CachedMoveUniStream cachedMoveUniStream); + +} diff --git a/core/src/test/java/ai/timefold/solver/core/api/solver/SolverFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/api/solver/SolverFactoryTest.java index b92eea4e16..7c70bf1d9a 100644 --- a/core/src/test/java/ai/timefold/solver/core/api/solver/SolverFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/api/solver/SolverFactoryTest.java @@ -164,7 +164,7 @@ void getScoreDirectorFactory() { var solution = new TestdataSolution("s1"); solution.setEntityList(Arrays.asList(new TestdataEntity("e1"), new TestdataEntity("e2"), new TestdataEntity("e3"))); solution.setValueList(Arrays.asList(new TestdataValue("v1"), new TestdataValue("v2"))); - try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED)) { + try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { scoreDirector.setWorkingSolution(solution); var score = scoreDirector.calculateScore(); assertThat(score).isNotNull(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/ConstraintWeightOverridesTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/ConstraintWeightOverridesTest.java index 1a302f747f..d747fbbfeb 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/ConstraintWeightOverridesTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/ConstraintWeightOverridesTest.java @@ -80,8 +80,7 @@ void appliesOverridesToConstraintProvider() { try (var scoreDirector = BavetConstraintStreamScoreDirectorFactory.buildScoreDirectorFactory(solutionDescriptor, new ScoreDirectorFactoryConfig() .withConstraintProviderClass(TestdataConstraintWeightOverridesConstraintProvider.class), - EnvironmentMode.REPRODUCIBLE) - .buildScoreDirector(false, ConstraintMatchPolicy.DISABLED)) { + EnvironmentMode.REPRODUCIBLE).buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { // Default weights scoreDirector.setWorkingSolution(solution); scoreDirector.triggerVariableListeners(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java index cddb9d0751..7f8be7738d 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveTest.java @@ -62,7 +62,7 @@ void doMove() { new EasyScoreDirectorFactory<>(TestdataEntityProvidingSolution.buildSolutionDescriptor(), solution -> SimpleScore.ZERO); ScoreDirector scoreDirector = - scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); + scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED); GenuineVariableDescriptor variableDescriptor = TestdataEntityProvidingEntity.buildVariableDescriptorForValue(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java index 12acf7f61a..bc49da907b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveTest.java @@ -77,7 +77,7 @@ void doMove() { new EasyScoreDirectorFactory<>(TestdataEntityProvidingSolution.buildSolutionDescriptor(), solution -> SimpleScore.ZERO); ScoreDirector scoreDirector = - scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); + scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED); GenuineVariableDescriptor variableDescriptor = TestdataEntityProvidingEntity .buildVariableDescriptorForValue(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java index 9f55af1523..6f8e742a47 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarSwapMoveTest.java @@ -130,7 +130,7 @@ void doMove() { new EasyScoreDirectorFactory<>(TestdataEntityProvidingSolution.buildSolutionDescriptor(), solution -> SimpleScore.ZERO); ScoreDirector scoreDirector = - scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); + scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED); List> variableDescriptorList = TestdataEntityProvidingEntity .buildEntityDescriptor().getGenuineVariableDescriptorList(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java index da06267e49..e91d0085c4 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveTest.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.mock; import java.util.Arrays; +import java.util.Collections; import java.util.List; import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; @@ -105,7 +106,7 @@ void doMove() { new EasyScoreDirectorFactory<>(TestdataEntityProvidingSolution.buildSolutionDescriptor(), solution -> SimpleScore.ZERO); ScoreDirector scoreDirector = - scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); + scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED); EntityDescriptor entityDescriptor = TestdataEntityProvidingEntity .buildEntityDescriptor(); @@ -228,7 +229,7 @@ void getters() { .buildVariableDescriptorForPrimaryValue(); GenuineVariableDescriptor secondaryDescriptor = TestdataMultiVarEntity .buildVariableDescriptorForSecondaryValue(); - SwapMove move = new SwapMove<>(Arrays.asList(primaryDescriptor), + SwapMove move = new SwapMove<>(Collections.singletonList(primaryDescriptor), new TestdataMultiVarEntity("a"), new TestdataMultiVarEntity("b")); assertCode("a", move.getLeftEntity()); assertCode("b", move.getRightEntity()); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/director/MoveDirectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/move/director/MoveDirectorTest.java index 90d89e82ef..0546d160e5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/director/MoveDirectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/move/director/MoveDirectorTest.java @@ -211,10 +211,12 @@ void rebase() { @Test void updateShadowVariables() { var mockScoreDirector = mock(InnerScoreDirector.class); - var moveDirector = new MoveDirector(mockScoreDirector); + var mockMoveStreamSession = mock(MoveStreamSession.class); + var moveDirector = new MoveDirector(mockScoreDirector, mockMoveStreamSession); moveDirector.updateShadowVariables(); verify(mockScoreDirector).triggerVariableListeners(); + verify(mockMoveStreamSession).settle(); } @Test @@ -267,7 +269,7 @@ void undoNestedPhaseMove() { move.doMoveOnly(scoreDirector); var undoMove = (RecordedUndoMove) ephemeralMoveDirector.createUndoMove(); // e1 must be analyzed at the beginning of the move execution - assertThat(undoMove.getVariableChangeActionList().stream().anyMatch(action -> { + assertThat(undoMove.variableChangeActionList().stream().anyMatch(action -> { if (action instanceof ListVariableBeforeChangeAction beforeChangeAction) { return beforeChangeAction.entity() == e1 && beforeChangeAction.fromIndex() == 0 && beforeChangeAction.toIndex() == 1 && beforeChangeAction.oldValue().size() == 1 @@ -277,7 +279,7 @@ void undoNestedPhaseMove() { })).isTrue(); // e2 is not analyzed at the beginning of move execution, // but it must have a before list change event to restore the original elements. - assertThat(undoMove.getVariableChangeActionList().stream().anyMatch(action -> { + assertThat(undoMove.variableChangeActionList().stream().anyMatch(action -> { if (action instanceof ListVariableBeforeChangeAction beforeChangeAction) { return beforeChangeAction.entity() == e2 && beforeChangeAction.fromIndex() == 0 && beforeChangeAction.toIndex() == 1 && beforeChangeAction.oldValue().size() == 1 diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorSemanticsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorSemanticsTest.java index 76fe476be5..4cc22cf84a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorSemanticsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorSemanticsTest.java @@ -2,17 +2,24 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import java.util.List; import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.impl.move.director.MoveStreamSession; +import ai.timefold.solver.core.impl.move.generic.ChangeMove; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.testdata.domain.TestdataEntity; -import ai.timefold.solver.core.impl.testdata.domain.constraintconfiguration.TestdataConstraintConfiguration; +import ai.timefold.solver.core.impl.testdata.domain.TestdataSolution; import ai.timefold.solver.core.impl.testdata.domain.constraintconfiguration.TestdataConstraintConfigurationSolution; import ai.timefold.solver.core.impl.testdata.domain.list.pinned.TestdataPinnedListSolution; import ai.timefold.solver.core.impl.testdata.domain.list.pinned.index.TestdataPinnedWithIndexListSolution; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; @@ -30,6 +37,9 @@ public abstract class AbstractScoreDirectorSemanticsTest { buildInnerScoreDirectorFactoryWithConstraintConfiguration( SolutionDescriptor solutionDescriptor); + protected abstract InnerScoreDirectorFactory + buildInnerScoreDirectorFactory(SolutionDescriptor solutionDescriptor); + protected abstract InnerScoreDirectorFactory buildInnerScoreDirectorFactoryWithListVariableEntityPin( SolutionDescriptor solutionDescriptor); @@ -40,100 +50,96 @@ public abstract class AbstractScoreDirectorSemanticsTest { @Test void independentScoreDirectors() { - InnerScoreDirectorFactory scoreDirectorFactory = + var scoreDirectorFactory = buildInnerScoreDirectorFactoryWithConstraintConfiguration(constraintConfigurationSolutionDescriptor); // Create first score director, calculate score. - TestdataConstraintConfigurationSolution solution1 = - TestdataConstraintConfigurationSolution.generateSolution(1, 1); - InnerScoreDirector scoreDirector1 = - scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); - scoreDirector1.setWorkingSolution(solution1); - SimpleScore score1 = scoreDirector1.calculateScore(); - assertThat(score1).isEqualTo(SimpleScore.of(1)); - - // Create second score director, calculate score. - TestdataConstraintConfigurationSolution solution2 = - TestdataConstraintConfigurationSolution.generateSolution(2, 2); - InnerScoreDirector scoreDirector2 = - scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); - scoreDirector2.setWorkingSolution(solution2); - SimpleScore score2 = scoreDirector2.calculateScore(); - assertThat(score2).isEqualTo(SimpleScore.of(2)); - - // Ensure that the second score director did not influence the first. - assertThat(scoreDirector1.calculateScore()).isEqualTo(SimpleScore.of(1)); - - // Make a change on the second score director, ensure it did not affect the first. - TestdataEntity entity = solution2.getEntityList().get(1); - scoreDirector2.beforeEntityRemoved(entity); - solution2.getEntityList().remove(entity); - scoreDirector2.afterEntityRemoved(entity); - scoreDirector2.triggerVariableListeners(); - assertThat(scoreDirector2.calculateScore()).isEqualTo(SimpleScore.of(1)); - assertThat(scoreDirector1.calculateScore()).isEqualTo(SimpleScore.of(1)); - - // Add the same entity to the first score director, ensure it did not affect the second. - scoreDirector1.beforeEntityAdded(entity); - solution1.getEntityList().add(entity); - scoreDirector1.afterEntityAdded(entity); - scoreDirector1.triggerVariableListeners(); - assertThat(scoreDirector1.calculateScore()).isEqualTo(SimpleScore.of(2)); - assertThat(scoreDirector2.calculateScore()).isEqualTo(SimpleScore.of(1)); + var solution1 = TestdataConstraintConfigurationSolution.generateSolution(1, 1); + try (var scoreDirector1 = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { + scoreDirector1.setWorkingSolution(solution1); + var score1 = scoreDirector1.calculateScore(); + assertThat(score1).isEqualTo(SimpleScore.of(1)); + + // Create second score director, calculate score. + var solution2 = TestdataConstraintConfigurationSolution.generateSolution(2, 2); + try (var scoreDirector2 = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { + scoreDirector2.setWorkingSolution(solution2); + var score2 = scoreDirector2.calculateScore(); + assertThat(score2).isEqualTo(SimpleScore.of(2)); + + // Ensure that the second score director did not influence the first. + assertThat(scoreDirector1.calculateScore()).isEqualTo(SimpleScore.of(1)); + + // Make a change on the second score director, ensure it did not affect the first. + var entity = solution2.getEntityList().get(1); + scoreDirector2.beforeEntityRemoved(entity); + solution2.getEntityList().remove(entity); + scoreDirector2.afterEntityRemoved(entity); + scoreDirector2.triggerVariableListeners(); + assertThat(scoreDirector2.calculateScore()).isEqualTo(SimpleScore.of(1)); + assertThat(scoreDirector1.calculateScore()).isEqualTo(SimpleScore.of(1)); + + // Add the same entity to the first score director, ensure it did not affect the second. + scoreDirector1.beforeEntityAdded(entity); + solution1.getEntityList().add(entity); + scoreDirector1.afterEntityAdded(entity); + scoreDirector1.triggerVariableListeners(); + assertThat(scoreDirector1.calculateScore()).isEqualTo(SimpleScore.of(2)); + assertThat(scoreDirector2.calculateScore()).isEqualTo(SimpleScore.of(1)); + } + } } @Test void solutionBasedScoreWeights() { - InnerScoreDirectorFactory scoreDirectorFactory = + var scoreDirectorFactory = buildInnerScoreDirectorFactoryWithConstraintConfiguration(constraintConfigurationSolutionDescriptor); // Create score director, calculate score. - TestdataConstraintConfigurationSolution solution1 = - TestdataConstraintConfigurationSolution.generateSolution(1, 1); - InnerScoreDirector scoreDirector = - scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); - scoreDirector.setWorkingSolution(solution1); - SimpleScore score1 = scoreDirector.calculateScore(); - assertThat(score1).isEqualTo(SimpleScore.of(1)); - - // Set new solution with a different constraint weight, calculate score. - TestdataConstraintConfigurationSolution solution2 = - TestdataConstraintConfigurationSolution.generateSolution(1, 1); - TestdataConstraintConfiguration constraintConfiguration = solution2.getConstraintConfiguration(); - constraintConfiguration.setFirstWeight(SimpleScore.of(2)); - scoreDirector.setWorkingSolution(solution2); - SimpleScore score2 = scoreDirector.calculateScore(); - assertThat(score2).isEqualTo(SimpleScore.of(2)); - - // Set new solution with a disabled constraint, calculate score. - constraintConfiguration.setFirstWeight(SimpleScore.ZERO); - scoreDirector.setWorkingSolution(solution2); - SimpleScore score3 = scoreDirector.calculateScore(); - assertThat(score3).isEqualTo(SimpleScore.ZERO); + var solution1 = TestdataConstraintConfigurationSolution.generateSolution(1, 1); + try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { + scoreDirector.setWorkingSolution(solution1); + var score1 = scoreDirector.calculateScore(); + assertThat(score1).isEqualTo(SimpleScore.of(1)); + + // Set new solution with a different constraint weight, calculate score. + var solution2 = + TestdataConstraintConfigurationSolution.generateSolution(1, 1); + var constraintConfiguration = solution2.getConstraintConfiguration(); + constraintConfiguration.setFirstWeight(SimpleScore.of(2)); + scoreDirector.setWorkingSolution(solution2); + var score2 = scoreDirector.calculateScore(); + assertThat(score2).isEqualTo(SimpleScore.of(2)); + + // Set new solution with a disabled constraint, calculate score. + constraintConfiguration.setFirstWeight(SimpleScore.ZERO); + scoreDirector.setWorkingSolution(solution2); + var score3 = scoreDirector.calculateScore(); + assertThat(score3).isEqualTo(SimpleScore.ZERO); + } } @Test void mutableConstraintConfiguration() { - InnerScoreDirectorFactory scoreDirectorFactory = + var scoreDirectorFactory = buildInnerScoreDirectorFactoryWithConstraintConfiguration(constraintConfigurationSolutionDescriptor); // Create score director, calculate score with a given constraint configuration. - TestdataConstraintConfigurationSolution solution = - TestdataConstraintConfigurationSolution.generateSolution(1, 1); - InnerScoreDirector scoreDirector = - scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); - scoreDirector.setWorkingSolution(solution); - SimpleScore score1 = scoreDirector.calculateScore(); - assertThat(score1).isEqualTo(SimpleScore.of(1)); - - // Change constraint configuration on the current working solution. - TestdataConstraintConfiguration constraintConfiguration = solution.getConstraintConfiguration(); - scoreDirector.beforeProblemPropertyChanged(constraintConfiguration); - constraintConfiguration.setFirstWeight(SimpleScore.of(2)); - scoreDirector.afterProblemPropertyChanged(constraintConfiguration); - SimpleScore score2 = scoreDirector.calculateScore(); - assertThat(score2).isEqualTo(SimpleScore.of(2)); + var solution = TestdataConstraintConfigurationSolution.generateSolution(1, 1); + try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { + scoreDirector.setWorkingSolution(solution); + var score1 = scoreDirector.calculateScore(); + assertThat(score1).isEqualTo(SimpleScore.of(1)); + + // Change constraint configuration on the current working solution. + var constraintConfiguration = solution.getConstraintConfiguration(); + scoreDirector.beforeProblemPropertyChanged(constraintConfiguration); + constraintConfiguration.setFirstWeight(SimpleScore.of(2)); + scoreDirector.afterProblemPropertyChanged(constraintConfiguration); + var score2 = scoreDirector.calculateScore(); + assertThat(score2).isEqualTo(SimpleScore.of(2)); + } } @Test @@ -145,7 +151,7 @@ void constraintPresentEvenIfNoMatches() { // Create score director, calculate score with a given constraint configuration. var solution = TestdataConstraintConfigurationSolution.generateSolution(1, 1); - try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.ENABLED)) { + try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.ENABLED)) { scoreDirector.setWorkingSolution(solution); var score1 = scoreDirector.calculateScore(); assertSoftly(softly -> { @@ -178,7 +184,7 @@ void listVariableEntityPinningSupported() { firstEntity.setValueList(List.of(solution.getValueList().get(0))); firstEntity.setPinned(true); - try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED)) { + try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { scoreDirector.setWorkingSolution(solution); var score1 = scoreDirector.calculateScore(); assertThat(score1).isEqualTo(SimpleScore.ofUninitialized(-1, -2)); @@ -206,7 +212,7 @@ void listVariableIndexPinningSupported() { secondEntity.setValueList(List.of(solution.getValueList().get(1))); secondEntity.setPlanningPinToIndex(1); - try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED)) { + try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { scoreDirector.setWorkingSolution(solution); var score1 = scoreDirector.calculateScore(); assertThat(score1).isEqualTo(SimpleScore.ofUninitialized(-1, -3)); @@ -222,4 +228,41 @@ void listVariableIndexPinningSupported() { } } + @Test + void ephemeralMovesDoNotTriggerMoveStreams() { + var solutionDescriptor = TestdataSolution.buildSolutionDescriptor(); + var variableMetaModel = solutionDescriptor.getMetaModel() + .entity(TestdataEntity.class) + .genuineVariable("value"); + + var moveStreamSession = (MoveStreamSession) mock(MoveStreamSession.class); + var solution1 = TestdataSolution.generateSolution(1, 1); + var entity = solution1.getEntityList().get(0); + + var scoreDirectorFactory = buildInnerScoreDirectorFactory(solutionDescriptor); + try (var scoreDirector = + scoreDirectorFactory.buildScoreDirector(moveStreamSession, false, ConstraintMatchPolicy.DISABLED)) { + scoreDirector.setWorkingSolution(solution1); + verify(moveStreamSession).resetWorkingSolution(same(scoreDirector.getWorkingSolution())); + reset(moveStreamSession); + + // Create a move that does not trigger move streams, because it is immediately undone. + var move = new ChangeMove<>((PlanningVariableMetaModel) variableMetaModel, + entity, null); + var score = scoreDirector.doAndProcessMove(move, false); + assertThat(score).isNotNull(); + verify(moveStreamSession).settle(); // Nothing will be done here, because the move was undone. + reset(moveStreamSession); + + // When executed directly, the move does trigger move streams. + move.execute(scoreDirector.getMoveDirector()); + verify(moveStreamSession).update(same(entity)); + + // Variable listeners need to trigger settling of the move stream session, + // to keep the Bavet nodes in sync with the working solution. + scoreDirector.triggerVariableListeners(); + verify(moveStreamSession).settle(); + } + } + } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactoryTest.java index 3c3e35c079..e335d8473a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactoryTest.java @@ -36,7 +36,7 @@ void incrementalScoreCalculatorWithCustomProperties() { ScoreDirectorFactory scoreDirectorFactory = buildTestdataScoreDirectoryFactory(config); IncrementalScoreDirector scoreDirector = - (IncrementalScoreDirector) scoreDirectorFactory.buildScoreDirector(false, + (IncrementalScoreDirector) scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED); TestCustomPropertiesIncrementalScoreCalculator scoreCalculator = (TestCustomPropertiesIncrementalScoreCalculator) scoreDirector @@ -60,7 +60,7 @@ void buildWithAssertionScoreDirectorFactory() { ScoreDirectorFactory assertionScoreDirectorFactory = scoreDirectorFactory.getAssertionScoreDirectorFactory(); IncrementalScoreDirector assertionScoreDirector = - (IncrementalScoreDirector) assertionScoreDirectorFactory.buildScoreDirector(false, + (IncrementalScoreDirector) assertionScoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED); IncrementalScoreCalculator assertionScoreCalculator = assertionScoreDirector.getIncrementalScoreCalculator(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactoryTest.java index 3055a0744a..9c91c1c7bc 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactoryTest.java @@ -26,7 +26,7 @@ void buildScoreDirector() { EasyScoreDirectorFactory directorFactory = new EasyScoreDirectorFactory<>( solutionDescriptor, scoreCalculator); - try (var director = directorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED)) { + try (var director = directorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { TestdataSolution solution = new TestdataSolution(); solution.setValueList(Collections.emptyList()); solution.setEntityList(Collections.emptyList()); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorSemanticsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorSemanticsTest.java index 8391916772..70bc912a1b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorSemanticsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorSemanticsTest.java @@ -15,6 +15,7 @@ import ai.timefold.solver.core.impl.score.director.InnerScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactoryFactory; +import ai.timefold.solver.core.impl.testdata.domain.TestdataEasyScoreCalculator; import ai.timefold.solver.core.impl.testdata.domain.TestdataSolution; import ai.timefold.solver.core.impl.testdata.domain.constraintconfiguration.TestdataConstraintConfigurationSolution; import ai.timefold.solver.core.impl.testdata.domain.constraintconfiguration.TestdataConstraintWeightEasyScoreCalculator; @@ -32,10 +33,20 @@ final class EasyScoreDirectorSemanticsTest extends AbstractScoreDirectorSemantic protected InnerScoreDirectorFactory buildInnerScoreDirectorFactoryWithConstraintConfiguration( SolutionDescriptor solutionDescriptor) { - ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig() + var scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig() .withEasyScoreCalculatorClass(TestdataConstraintWeightEasyScoreCalculator.class); - ScoreDirectorFactoryFactory scoreDirectorFactoryFactory = - new ScoreDirectorFactoryFactory<>(scoreDirectorFactoryConfig); + var scoreDirectorFactoryFactory = new ScoreDirectorFactoryFactory( + scoreDirectorFactoryConfig); + return scoreDirectorFactoryFactory.buildScoreDirectorFactory(EnvironmentMode.REPRODUCIBLE, solutionDescriptor); + } + + @Override + protected InnerScoreDirectorFactory + buildInnerScoreDirectorFactory(SolutionDescriptor solutionDescriptor) { + var scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig() + .withEasyScoreCalculatorClass(TestdataEasyScoreCalculator.class); + var scoreDirectorFactoryFactory = + new ScoreDirectorFactoryFactory(scoreDirectorFactoryConfig); return scoreDirectorFactoryFactory.buildScoreDirectorFactory(EnvironmentMode.REPRODUCIBLE, solutionDescriptor); } @@ -63,17 +74,17 @@ final class EasyScoreDirectorSemanticsTest extends AbstractScoreDirectorSemantic @Test void easyScoreCalculatorWithCustomProperties() { - ScoreDirectorFactoryConfig config = new ScoreDirectorFactoryConfig(); + var config = new ScoreDirectorFactoryConfig(); config.setEasyScoreCalculatorClass(TestCustomPropertiesEasyScoreCalculator.class); - HashMap customProperties = new HashMap<>(); + var customProperties = new HashMap(); customProperties.put("stringProperty", "string 1"); customProperties.put("intProperty", "7"); config.setEasyScoreCalculatorCustomProperties(customProperties); - EasyScoreDirector scoreDirector = + var scoreDirector = (EasyScoreDirector) buildTestdataScoreDirectoryFactory(config) - .buildScoreDirector(false, ConstraintMatchPolicy.DISABLED); - TestCustomPropertiesEasyScoreCalculator scoreCalculator = + .buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED); + var scoreCalculator = (TestCustomPropertiesEasyScoreCalculator) scoreDirector .getEasyScoreCalculator(); assertThat(scoreCalculator.getStringProperty()).isEqualTo("string 1"); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorTest.java index ca24dc5d89..2175430739 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorTest.java @@ -22,7 +22,7 @@ void shadowVariableCorruption() { (solution_) -> SimpleScore.of(0)); scoreDirectorFactory .setInitializingScoreTrend(InitializingScoreTrend.buildUniformTrend(InitializingScoreTrendLevel.ONLY_DOWN, 1)); - try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED)) { + try (var scoreDirector = scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED)) { var solution = new TestdataCorruptedShadowedSolution("s1"); var v1 = new TestdataValue("v1"); var v2 = new TestdataValue("v2"); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorSemanticsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorSemanticsTest.java index 9dec5de324..f36edb150b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorSemanticsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorSemanticsTest.java @@ -7,6 +7,8 @@ import ai.timefold.solver.core.impl.score.director.AbstractScoreDirectorSemanticsTest; import ai.timefold.solver.core.impl.score.director.InnerScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactoryFactory; +import ai.timefold.solver.core.impl.testdata.domain.TestdataIncrementalScoreCalculator; +import ai.timefold.solver.core.impl.testdata.domain.TestdataSolution; import ai.timefold.solver.core.impl.testdata.domain.constraintconfiguration.TestdataConstraintConfigurationSolution; import ai.timefold.solver.core.impl.testdata.domain.constraintconfiguration.TestdataConstraintWeighIncrementalScoreCalculator; import ai.timefold.solver.core.impl.testdata.domain.list.pinned.TestdataPinnedListIncrementalScoreCalculator; @@ -27,6 +29,16 @@ final class IncrementalScoreDirectorSemanticsTest extends AbstractScoreDirectorS return scoreDirectorFactoryFactory.buildScoreDirectorFactory(EnvironmentMode.REPRODUCIBLE, solutionDescriptor); } + @Override + protected InnerScoreDirectorFactory + buildInnerScoreDirectorFactory(SolutionDescriptor solutionDescriptor) { + var scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig() + .withIncrementalScoreCalculatorClass(TestdataIncrementalScoreCalculator.class); + var scoreDirectorFactoryFactory = + new ScoreDirectorFactoryFactory(scoreDirectorFactoryConfig); + return scoreDirectorFactoryFactory.buildScoreDirectorFactory(EnvironmentMode.REPRODUCIBLE, solutionDescriptor); + } + @Override protected InnerScoreDirectorFactory buildInnerScoreDirectorFactoryWithListVariableEntityPin( diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorTest.java index a564444482..58b173a7e6 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirectorTest.java @@ -56,7 +56,7 @@ void variableListener() { mock(IncrementalScoreCalculator.class); when(incrementalScoreCalculator.calculateScore()).thenReturn(SimpleScore.of(100)); try (var scoreDirector = - new IncrementalScoreDirector<>(scoreDirectorFactory, false, ConstraintMatchPolicy.DISABLED, true, + new IncrementalScoreDirector<>(scoreDirectorFactory, null, false, ConstraintMatchPolicy.DISABLED, true, incrementalScoreCalculator)) { scoreDirector.setWorkingSolution(solution); reset(incrementalScoreCalculator); @@ -80,7 +80,7 @@ void variableListener() { @Test void illegalStateExceptionThrownWhenConstraintMatchNotEnabled() { - try (var director = new IncrementalScoreDirector<>(mockIncrementalScoreDirectorFactory(), false, + try (var director = new IncrementalScoreDirector<>(mockIncrementalScoreDirectorFactory(), null, false, ConstraintMatchPolicy.DISABLED, true, mockIncrementalScoreCalculator(false))) { director.setWorkingSolution(new Object()); @@ -92,7 +92,7 @@ void illegalStateExceptionThrownWhenConstraintMatchNotEnabled() { @Test void constraintMatchTotalsNeverNull() { - try (var director = new IncrementalScoreDirector<>(mockIncrementalScoreDirectorFactory(), false, + try (var director = new IncrementalScoreDirector<>(mockIncrementalScoreDirectorFactory(), null, false, ConstraintMatchPolicy.ENABLED, true, mockIncrementalScoreCalculator(true))) { director.setWorkingSolution(new Object()); @@ -102,7 +102,7 @@ void constraintMatchTotalsNeverNull() { @Test void constraintMatchIsNotEnabledWhenScoreCalculatorNotConstraintMatchAware() { - try (var director = new IncrementalScoreDirector<>(mockIncrementalScoreDirectorFactory(), false, + try (var director = new IncrementalScoreDirector<>(mockIncrementalScoreDirectorFactory(), null, false, ConstraintMatchPolicy.ENABLED, true, mockIncrementalScoreCalculator(false))) { assertThat(director.getConstraintMatchPolicy()).isEqualTo(ConstraintMatchPolicy.DISABLED); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/director/stream/ConstraintStreamsBavetScoreDirectorSemanticsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/director/stream/ConstraintStreamsBavetScoreDirectorSemanticsTest.java index 90aa25284b..8246f71a55 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/director/stream/ConstraintStreamsBavetScoreDirectorSemanticsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/director/stream/ConstraintStreamsBavetScoreDirectorSemanticsTest.java @@ -7,6 +7,8 @@ import ai.timefold.solver.core.impl.score.director.AbstractScoreDirectorSemanticsTest; import ai.timefold.solver.core.impl.score.director.InnerScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactoryFactory; +import ai.timefold.solver.core.impl.testdata.domain.TestdataConstraintProvider; +import ai.timefold.solver.core.impl.testdata.domain.TestdataSolution; import ai.timefold.solver.core.impl.testdata.domain.constraintconfiguration.TestdataConstraintConfigurationSolution; import ai.timefold.solver.core.impl.testdata.domain.constraintconfiguration.TestdataConstraintWeightConstraintProvider; import ai.timefold.solver.core.impl.testdata.domain.list.pinned.TestdataPinnedListConstraintProvider; @@ -20,10 +22,20 @@ final class ConstraintStreamsBavetScoreDirectorSemanticsTest extends AbstractSco protected InnerScoreDirectorFactory buildInnerScoreDirectorFactoryWithConstraintConfiguration( SolutionDescriptor solutionDescriptor) { - ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig() + var scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig() .withConstraintProviderClass(TestdataConstraintWeightConstraintProvider.class); - ScoreDirectorFactoryFactory scoreDirectorFactoryFactory = - new ScoreDirectorFactoryFactory<>(scoreDirectorFactoryConfig); + var scoreDirectorFactoryFactory = new ScoreDirectorFactoryFactory( + scoreDirectorFactoryConfig); + return scoreDirectorFactoryFactory.buildScoreDirectorFactory(EnvironmentMode.REPRODUCIBLE, solutionDescriptor); + } + + @Override + protected InnerScoreDirectorFactory + buildInnerScoreDirectorFactory(SolutionDescriptor solutionDescriptor) { + var scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig() + .withConstraintProviderClass(TestdataConstraintProvider.class); + var scoreDirectorFactoryFactory = + new ScoreDirectorFactoryFactory(scoreDirectorFactoryConfig); return scoreDirectorFactoryFactory.buildScoreDirectorFactory(EnvironmentMode.REPRODUCIBLE, solutionDescriptor); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintStreamImplSupport.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintStreamImplSupport.java index 796ed1119f..a50fe8ae90 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintStreamImplSupport.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/bavet/BavetConstraintStreamImplSupport.java @@ -14,12 +14,13 @@ public record BavetConstraintStreamImplSupport(ConstraintMatchPolicy constraintM implements ConstraintStreamImplSupport { + @SuppressWarnings("unchecked") @Override public , Solution_> InnerScoreDirector buildScoreDirector( SolutionDescriptor solutionDescriptorSupplier, ConstraintProvider constraintProvider) { return (InnerScoreDirector) new BavetConstraintStreamScoreDirectorFactory<>( solutionDescriptorSupplier, constraintProvider, EnvironmentMode.REPRODUCIBLE) - .buildScoreDirector(false, constraintMatchPolicy); + .buildScoreDirector(null, false, constraintMatchPolicy); } @Override diff --git a/core/src/test/java/ai/timefold/solver/core/impl/testdata/util/PlannerTestUtils.java b/core/src/test/java/ai/timefold/solver/core/impl/testdata/util/PlannerTestUtils.java index 3fc872b3fb..4dc43a9536 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/testdata/util/PlannerTestUtils.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/testdata/util/PlannerTestUtils.java @@ -118,7 +118,8 @@ public static TestdataSolution generateTestdataSolution(String code, int entityA scoreDirectorFactory.setInitializingScoreTrend( InitializingScoreTrend.buildUniformTrend(InitializingScoreTrendLevel.ONLY_DOWN, 1)); return mock(InnerScoreDirector.class, - AdditionalAnswers.delegatesTo(scoreDirectorFactory.buildScoreDirector(false, ConstraintMatchPolicy.DISABLED))); + AdditionalAnswers + .delegatesTo(scoreDirectorFactory.buildScoreDirector(null, false, ConstraintMatchPolicy.DISABLED))); } public static > InnerScoreDirector