diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd index 545d35ed6a..57f465034a 100644 --- a/benchmark/src/main/resources/benchmark.xsd +++ b/benchmark/src/main/resources/benchmark.xsd @@ -2387,7 +2387,7 @@ - + @@ -2615,7 +2615,7 @@ - + diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json index a6883eb676..f122c49e3e 100644 --- a/core/src/build/revapi-differences.json +++ b/core/src/build/revapi-differences.json @@ -149,8 +149,8 @@ "annotationType": "jakarta.xml.bind.annotation.XmlType", "attribute": "propOrder", "oldValue": "{\"localSearchType\", \"moveSelectorConfig\", \"acceptorConfig\", \"foragerConfig\"}", - "newValue": "{\"localSearchType\", \"moveSelectorConfig\", \"moveProviderClass\", \"acceptorConfig\", \"foragerConfig\"}", - "justification": "Move Streams arrives as a preview feature." + "newValue": "{\"localSearchType\", \"moveSelectorConfig\", \"neighborhoodProviderClass\", \"acceptorConfig\", \"foragerConfig\"}", + "justification": "Neighborhoods API arrives as a preview feature." }, { "ignore": true, diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java index 95cc0ebc15..501ab44064 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java +++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java @@ -19,6 +19,9 @@ * The property must be an object type. Primitive types (such as int, double, long) are not allowed. *

* It is specified on a getter of a java bean property (or directly on a field) of a {@link PlanningEntity} class. + *

+ * It is sometimes referred to as the "basic" planning variable, + * to distinguish it from a {@link PlanningListVariable list variable}. */ @Target({ METHOD, FIELD }) @Retention(RUNTIME) diff --git a/core/src/main/java/ai/timefold/solver/core/config/localsearch/LocalSearchPhaseConfig.java b/core/src/main/java/ai/timefold/solver/core/config/localsearch/LocalSearchPhaseConfig.java index 60574dc6d5..8b49de6633 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/localsearch/LocalSearchPhaseConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/localsearch/LocalSearchPhaseConfig.java @@ -29,7 +29,7 @@ import ai.timefold.solver.core.config.phase.PhaseConfig; import ai.timefold.solver.core.config.solver.PreviewFeature; import ai.timefold.solver.core.config.util.ConfigUtils; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProvider; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodProvider; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -37,7 +37,7 @@ @XmlType(propOrder = { "localSearchType", "moveSelectorConfig", - "moveProviderClass", + "neighborhoodProviderClass", "acceptorConfig", "foragerConfig" }) @@ -77,7 +77,7 @@ public class LocalSearchPhaseConfig extends PhaseConfig @XmlElement(name = UnionMoveSelectorConfig.XML_ELEMENT_NAME, type = UnionMoveSelectorConfig.class) }) private MoveSelectorConfig moveSelectorConfig = null; - private Class moveProviderClass = null; + private Class neighborhoodProviderClass = null; @XmlElement(name = "acceptor") private LocalSearchAcceptorConfig acceptorConfig = null; @XmlElement(name = "forager") @@ -104,19 +104,19 @@ public void setMoveSelectorConfig(@Nullable MoveSelectorConfig moveSelectorConfi } /** - * Part of {@link PreviewFeature#MOVE_STREAMS}. + * Part of {@link PreviewFeature#NEIGHBORHOODS}. */ @SuppressWarnings("unchecked") - public @Nullable Class> getMoveProviderClass() { - return (Class>) moveProviderClass; + public @Nullable Class> getNeighborhoodProviderClass() { + return (Class>) neighborhoodProviderClass; } /** - * Part of {@link PreviewFeature#MOVE_STREAMS}. + * Part of {@link PreviewFeature#NEIGHBORHOODS}. */ @SuppressWarnings("rawtypes") - public void setMoveProviderClass(@Nullable Class moveProviderClass) { - this.moveProviderClass = moveProviderClass; + public void setNeighborhoodProviderClass(@Nullable Class neighborhoodProviderClass) { + this.neighborhoodProviderClass = neighborhoodProviderClass; } public @Nullable LocalSearchAcceptorConfig getAcceptorConfig() { @@ -150,10 +150,11 @@ public void setForagerConfig(@Nullable LocalSearchForagerConfig foragerConfig) { } /** - * Part of {@link PreviewFeature#MOVE_STREAMS}. + * Part of {@link PreviewFeature#NEIGHBORHOODS}. */ - public @NonNull LocalSearchPhaseConfig withMoveProviderClass(@NonNull Class> moveProviderClass) { - this.moveProviderClass = moveProviderClass; + public @NonNull LocalSearchPhaseConfig + withMoveProviderClass(@NonNull Class> moveProviderClass) { + this.neighborhoodProviderClass = moveProviderClass; return this; } @@ -174,8 +175,8 @@ public void setForagerConfig(@Nullable LocalSearchForagerConfig foragerConfig) { inheritedConfig.getLocalSearchType()); setMoveSelectorConfig(ConfigUtils.inheritOverwritableProperty( getMoveSelectorConfig(), inheritedConfig.getMoveSelectorConfig())); - setMoveProviderClass(ConfigUtils.inheritOverwritableProperty(getMoveProviderClass(), - inheritedConfig.getMoveProviderClass())); + setNeighborhoodProviderClass(ConfigUtils.inheritOverwritableProperty(getNeighborhoodProviderClass(), + inheritedConfig.getNeighborhoodProviderClass())); acceptorConfig = ConfigUtils.inheritConfig(acceptorConfig, inheritedConfig.getAcceptorConfig()); foragerConfig = ConfigUtils.inheritConfig(foragerConfig, inheritedConfig.getForagerConfig()); return this; @@ -194,8 +195,8 @@ public void visitReferencedClasses(@NonNull Consumer> classVisitor) { if (moveSelectorConfig != null) { moveSelectorConfig.visitReferencedClasses(classVisitor); } - if (moveProviderClass != null) { - classVisitor.accept(moveProviderClass); + if (neighborhoodProviderClass != null) { + classVisitor.accept(neighborhoodProviderClass); } if (acceptorConfig != null) { acceptorConfig.visitReferencedClasses(classVisitor); diff --git a/core/src/main/java/ai/timefold/solver/core/config/solver/PreviewFeature.java b/core/src/main/java/ai/timefold/solver/core/config/solver/PreviewFeature.java index 37c895f8b0..03632d91bc 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/solver/PreviewFeature.java +++ b/core/src/main/java/ai/timefold/solver/core/config/solver/PreviewFeature.java @@ -7,31 +7,31 @@ * Any class, method, or field related to these features may change or be removed without prior notice, * although we will strive to avoid this as much as possible. * We encourage you to try these preview features and give us feedback on your experience with them. - * Please direct your feedback to our Github Discussions. + * Please direct your feedback to + * Timefold Solver Github + * or to Timefold Discord. * *

* This list is not constant and is evolving over time, * with items being added and removed without warning. * It should not be treated as part of our public API, * just like the preview features themselves. - * - * @see Timefold Solver Github Discussions */ public enum PreviewFeature { DIVERSIFIED_LATE_ACCEPTANCE, PLANNING_SOLUTION_DIFF, /** - * Unlike other preview features, Move Streams are an active research project. + * Unlike other preview features, Neighborhoods are an active research project. * It is intended to simplify the creation of custom moves, eventually replacing move selectors. * The component is under heavy development, entirely undocumented, and many key features are yet to be delivered. * Neither the API nor the feature set are complete, and any part can change or be removed at any time. * - * Move Streams will eventually stabilize and be promoted from a research project to a true preview feature. + * Neighborhoods will eventually stabilize and be promoted from a research project to a true preview feature. * We only expose it now to be able to use it for experimentation and testing. * As such, it is an exception to the rule; * this preview feature is not finished, and it is not yet ready for feedback. */ - MOVE_STREAMS + NEIGHBORHOODS } diff --git a/core/src/main/java/ai/timefold/solver/core/enterprise/TimefoldSolverEnterpriseService.java b/core/src/main/java/ai/timefold/solver/core/enterprise/TimefoldSolverEnterpriseService.java index b2c0789c3b..13489dc0c5 100644 --- a/core/src/main/java/ai/timefold/solver/core/enterprise/TimefoldSolverEnterpriseService.java +++ b/core/src/main/java/ai/timefold/solver/core/enterprise/TimefoldSolverEnterpriseService.java @@ -31,7 +31,7 @@ import ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider; import ai.timefold.solver.core.impl.localsearch.decider.acceptor.Acceptor; import ai.timefold.solver.core.impl.localsearch.decider.forager.LocalSearchForager; -import ai.timefold.solver.core.impl.move.MoveRepository; +import ai.timefold.solver.core.impl.neighborhood.MoveRepository; import ai.timefold.solver.core.impl.partitionedsearch.PartitionedSearchPhase; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; import ai.timefold.solver.core.impl.solver.termination.SolverTermination; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java index 07f4877fb8..9085c5638e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.constructionheuristic.placer.EntityPlacer; import ai.timefold.solver.core.impl.constructionheuristic.scope.ConstructionHeuristicPhaseScope; import ai.timefold.solver.core.impl.constructionheuristic.scope.ConstructionHeuristicStepScope; -import ai.timefold.solver.core.impl.move.PlacerBasedMoveRepository; +import ai.timefold.solver.core.impl.neighborhood.PlacerBasedMoveRepository; import ai.timefold.solver.core.impl.phase.AbstractPossiblyInitializingPhase; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java index 790c0b8984..b49e018d32 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java @@ -67,7 +67,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import ai.timefold.solver.core.impl.move.director.MoveDirector; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.impl.util.MutableInt; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningEntityMetaModel; @@ -133,7 +133,7 @@ public class EntityDescriptor { private List> effectiveGenuineVariableDescriptorList; private List> effectiveGenuineListVariableDescriptorList; - private final UniDataFilter entityMovablePredicate = + private final UniEnumeratingFilter entityMovablePredicate = (solutionView, entity) -> { var moveDirector = (MoveDirector) solutionView; return !moveDirector.isPinned(this, entity); @@ -639,8 +639,8 @@ public BiPredicate getEffectiveMovableEntityFilter() { } @SuppressWarnings("unchecked") - public UniDataFilter getEntityMovablePredicate() { - return (UniDataFilter) entityMovablePredicate; + public UniEnumeratingFilter getEntityMovablePredicate() { + return (UniEnumeratingFilter) entityMovablePredicate; } public SelectionSorter getDecreasingDifficultySorter() { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningEntityDiff.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningEntityDiff.java index 8767c47c44..2aa850f878 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningEntityDiff.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningEntityDiff.java @@ -7,6 +7,7 @@ import java.util.Objects; import java.util.stream.Collectors; +import ai.timefold.solver.core.preview.api.domain.metamodel.GenuineVariableMetaModel; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningEntityMetaModel; import ai.timefold.solver.core.preview.api.domain.solution.diff.PlanningEntityDiff; import ai.timefold.solver.core.preview.api.domain.solution.diff.PlanningSolutionDiff; @@ -75,7 +76,8 @@ private static String entityDiffToString(PlanningEntityDiff entityDiff) { } return variableDiffs.stream() .map(diff -> " %s (%s): %s -> %s".formatted(diff.variableMetaModel().name(), - diff.variableMetaModel().isGenuine() ? "genuine" : "shadow", diff.oldValue(), diff.newValue())) + diff.variableMetaModel() instanceof GenuineVariableMetaModel ? "genuine" : "shadow", + diff.oldValue(), diff.newValue())) .collect(Collectors.joining(System.lineSeparator())); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningSolutionDiff.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningSolutionDiff.java index 121ba3e38a..a7f268880c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningSolutionDiff.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningSolutionDiff.java @@ -9,6 +9,7 @@ import java.util.Set; import java.util.stream.Collectors; +import ai.timefold.solver.core.preview.api.domain.metamodel.GenuineVariableMetaModel; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; import ai.timefold.solver.core.preview.api.domain.solution.diff.PlanningEntityDiff; import ai.timefold.solver.core.preview.api.domain.solution.diff.PlanningSolutionDiff; @@ -152,7 +153,8 @@ static String entityDiffToString(PlanningEntityDiff entityDiff) { } var variableDiffString = variableDiffs.stream() .map(diff -> " %s (%s): %s -> %s".formatted(diff.variableMetaModel().name(), - diff.variableMetaModel().isGenuine() ? "genuine" : "shadow", diff.oldValue(), diff.newValue())) + diff.variableMetaModel() instanceof GenuineVariableMetaModel ? "genuine" : "shadow", + diff.oldValue(), diff.newValue())) .collect(Collectors.joining(System.lineSeparator())); return """ %s: diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningVariableDiff.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningVariableDiff.java index 1f694bf0a9..ca56356d2a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningVariableDiff.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/DefaultPlanningVariableDiff.java @@ -2,6 +2,7 @@ import java.util.Objects; +import ai.timefold.solver.core.preview.api.domain.metamodel.GenuineVariableMetaModel; import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel; import ai.timefold.solver.core.preview.api.domain.solution.diff.PlanningEntityDiff; import ai.timefold.solver.core.preview.api.domain.solution.diff.PlanningVariableDiff; @@ -27,7 +28,10 @@ public String toString() { Old value: %s New value: %s """ - .formatted(variableMetaModel.isGenuine() ? "genuine" : "shadow", variableMetaModel.name(), + .formatted( + variableMetaModel instanceof GenuineVariableMetaModel ? "genuine" + : "shadow", + variableMetaModel.name(), entityDiff.entity(), entityDiff.entityMetaModel().type(), oldValue, newValue); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableStateSupply.java index 6d83a32923..421d89f23c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableStateSupply.java @@ -38,10 +38,8 @@ * which doesn't care whether the variable is internal or externalized. */ @NullMarked -public interface ListVariableStateSupply extends - SourcedListVariableListener, - SingletonInverseVariableSupply, - IndexVariableSupply { +public interface ListVariableStateSupply + extends SourcedListVariableListener, SingletonInverseVariableSupply, IndexVariableSupply { void externalize(IndexShadowVariableDescriptor shadowVariableDescriptor); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java index a217466c96..19aff357f0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java @@ -15,7 +15,7 @@ import ai.timefold.solver.core.impl.domain.variable.ListVariableStateDemand; import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.move.director.MoveDirector; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; public final class ListVariableDescriptor extends GenuineVariableDescriptor { @@ -24,7 +24,7 @@ public final class ListVariableDescriptor extends GenuineVariableDesc var list = getValue(entity); return list.contains(element); }; - private final BiDataFilter entityContainsPinnedValuePredicate = + private final BiEnumeratingFilter entityContainsPinnedValuePredicate = (solutionView, value, entity) -> { var moveDirector = (MoveDirector) solutionView; return moveDirector.isPinned(this, value); @@ -47,8 +47,8 @@ public BiPredicate getInListPredicate() { } @SuppressWarnings("unchecked") - public BiDataFilter getEntityContainsPinnedValuePredicate() { - return (BiDataFilter) entityContainsPinnedValuePredicate; + public BiEnumeratingFilter getEntityContainsPinnedValuePredicate() { + return (BiEnumeratingFilter) entityContainsPinnedValuePredicate; } public boolean allowsUnassignedValues() { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhase.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhase.java index 1a7eb8152c..eed7cf89bf 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhase.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhase.java @@ -14,7 +14,7 @@ import ai.timefold.solver.core.impl.exhaustivesearch.scope.ExhaustiveSearchPhaseScope; import ai.timefold.solver.core.impl.exhaustivesearch.scope.ExhaustiveSearchStepScope; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.Moves; import ai.timefold.solver.core.impl.phase.AbstractPhase; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java index 9ab8668822..925340e6de 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java @@ -32,7 +32,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.ManualEntityMimicRecorder; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelectorFactory; -import ai.timefold.solver.core.impl.move.MoveSelectorBasedMoveRepository; +import ai.timefold.solver.core.impl.neighborhood.MoveSelectorBasedMoveRepository; import ai.timefold.solver.core.impl.phase.AbstractPhaseFactory; import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/decider/ExhaustiveSearchDecider.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/decider/ExhaustiveSearchDecider.java index 044f76eabb..e9388dac9d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/decider/ExhaustiveSearchDecider.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/decider/ExhaustiveSearchDecider.java @@ -7,7 +7,7 @@ import ai.timefold.solver.core.impl.exhaustivesearch.scope.ExhaustiveSearchPhaseScope; import ai.timefold.solver.core.impl.exhaustivesearch.scope.ExhaustiveSearchStepScope; import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.ManualEntityMimicRecorder; -import ai.timefold.solver.core.impl.move.MoveRepository; +import ai.timefold.solver.core.impl.neighborhood.MoveRepository; import ai.timefold.solver.core.impl.phase.scope.SolverLifecyclePoint; import ai.timefold.solver.core.impl.score.director.InnerScore; import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java index 6d54affdb6..f020be068a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java @@ -39,12 +39,14 @@ import ai.timefold.solver.core.impl.localsearch.decider.acceptor.AcceptorFactory; import ai.timefold.solver.core.impl.localsearch.decider.forager.LocalSearchForager; import ai.timefold.solver.core.impl.localsearch.decider.forager.LocalSearchForagerFactory; -import ai.timefold.solver.core.impl.move.MoveRepository; -import ai.timefold.solver.core.impl.move.MoveSelectorBasedMoveRepository; -import ai.timefold.solver.core.impl.move.MoveStreamsBasedMoveRepository; -import ai.timefold.solver.core.impl.move.streams.DefaultMoveStreamFactory; -import ai.timefold.solver.core.impl.move.streams.InnerMoveProducer; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProvider; +import ai.timefold.solver.core.impl.neighborhood.MoveRepository; +import ai.timefold.solver.core.impl.neighborhood.MoveSelectorBasedMoveRepository; +import ai.timefold.solver.core.impl.neighborhood.NeighborhoodsBasedMoveRepository; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodProvider; +import ai.timefold.solver.core.impl.neighborhood.move.InnerMoveStream; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultMoveStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultNeighborhood; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultNeighborhoodBuilder; import ai.timefold.solver.core.impl.phase.AbstractPhaseFactory; import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; @@ -61,19 +63,19 @@ public DefaultLocalSearchPhaseFactory(LocalSearchPhaseConfig phaseConfig) { public LocalSearchPhase buildPhase(int phaseIndex, boolean lastInitializingPhase, HeuristicConfigPolicy solverConfigPolicy, BestSolutionRecaller bestSolutionRecaller, SolverTermination solverTermination) { - var moveProviderClass = phaseConfig. getMoveProviderClass(); - var moveStreamsEnabled = moveProviderClass != null; + var neighborhoodProviderClass = phaseConfig. getNeighborhoodProviderClass(); + var neighborhoodsEnabled = neighborhoodProviderClass != null; var moveSelectorConfig = phaseConfig.getMoveSelectorConfig(); var moveSelectorsEnabled = moveSelectorConfig != null; - if (moveSelectorsEnabled && moveStreamsEnabled) { + if (moveSelectorsEnabled && neighborhoodsEnabled) { throw new UnsupportedOperationException(""" - The solver configuration enabled both move selectors and Move Streams. + The solver configuration enabled both move selectors and the Neighborhoods API. These are mutually exclusive features, please pick one or the other."""); } var phaseConfigPolicy = solverConfigPolicy.createPhaseConfigPolicy(); var phaseTermination = buildPhaseTermination(phaseConfigPolicy, solverTermination); - var decider = moveStreamsEnabled - ? buildMoveStreamsBasedDecider(phaseConfigPolicy, phaseTermination, moveProviderClass) + var decider = neighborhoodsEnabled + ? buildNeighborhoodsBasedDecider(phaseConfigPolicy, phaseTermination, neighborhoodProviderClass) : buildMoveSelectorBasedDecider(phaseConfigPolicy, phaseTermination); return new DefaultLocalSearchPhase.Builder<>(phaseIndex, solverConfigPolicy.getLogIndentation(), phaseTermination, decider) @@ -87,26 +89,27 @@ private LocalSearchDecider buildMoveSelectorBasedDecider(HeuristicCon return buildDecider(moveRepository, configPolicy, termination); } - private LocalSearchDecider buildMoveStreamsBasedDecider(HeuristicConfigPolicy configPolicy, - PhaseTermination termination, Class> moveProviderClass) { - configPolicy.ensurePreviewFeature(PreviewFeature.MOVE_STREAMS); + private LocalSearchDecider buildNeighborhoodsBasedDecider(HeuristicConfigPolicy configPolicy, + PhaseTermination termination, + Class> neighborhoodProviderClass) { + configPolicy.ensurePreviewFeature(PreviewFeature.NEIGHBORHOODS); var solutionDescriptor = configPolicy.getSolutionDescriptor(); var solutionMetaModel = solutionDescriptor.getMetaModel(); if (solutionMetaModel.genuineEntities().size() > 1) { throw new UnsupportedOperationException( - "Move Streams currently only support solutions with a single entity class, not multiple."); + "Neighborhoods API currently only supports solutions with a single entity class, not multiple."); } var entityMetaModel = solutionMetaModel.genuineEntities().get(0); if (entityMetaModel.genuineVariables().size() > 1) { throw new UnsupportedOperationException( - "Move Streams currently only support solutions with a single variable class, not multiple."); + "Neighborhoods API currently only supports solutions with a single variable class, not multiple."); } var variableMetaModel = entityMetaModel.genuineVariables().get(0); if (variableMetaModel instanceof PlanningVariableMetaModel planningVariableMetaModel && planningVariableMetaModel.isChained()) { throw new UnsupportedOperationException(""" - Move Streams don't support solutions with chained variables. + Neighborhoods API doesn't support solutions with chained variables. Convert your model to use @%s instead.""" .formatted(PlanningListVariable.class.getSimpleName())); } @@ -115,29 +118,32 @@ private LocalSearchDecider buildMoveStreamsBasedDecider(HeuristicConf .toList(); if (!entitiesWithPinningFilters.isEmpty()) { throw new UnsupportedOperationException(""" - %s is deprecated and Move Streams do not support it. + %s is deprecated and Neighborhoods API does not support it. Convert your entities (%s) to use @%s instead.""" .formatted(PinningFilter.class.getSimpleName(), entitiesWithPinningFilters, PlanningPin.class.getSimpleName())); } - if (!MoveProvider.class.isAssignableFrom(moveProviderClass)) { + if (!NeighborhoodProvider.class.isAssignableFrom(neighborhoodProviderClass)) { throw new IllegalArgumentException( - "The moveProviderClass (%s) does not implement %s." - .formatted(moveProviderClass, MoveProvider.class.getSimpleName())); + "The neighborhoodProviderClass (%s) does not implement %s." + .formatted(neighborhoodProviderClass, NeighborhoodProvider.class.getSimpleName())); } - var moveProvider = - ConfigUtils.newInstance(LocalSearchPhaseConfig.class::getSimpleName, "moveProviderClass", moveProviderClass); - var moveDefinitionList = moveProvider.defineMoves(solutionMetaModel); + var neighborhoodProvider = + ConfigUtils.newInstance(LocalSearchPhaseConfig.class::getSimpleName, "neighborhoodProviderClass", + neighborhoodProviderClass); + var neighborhoodBuilder = new DefaultNeighborhoodBuilder<>(solutionMetaModel); + var moveDefinitionList = ((DefaultNeighborhood) neighborhoodProvider.defineNeighborhood(neighborhoodBuilder)) + .getMoveDefinitionList(); if (moveDefinitionList.size() != 1) { throw new IllegalArgumentException( - "The moveProviderClass (%s) must define exactly one MoveDefinition, not %s." - .formatted(moveProviderClass, moveDefinitionList.size())); + "The neighborhoodProviderClass (%s) must define exactly one MoveDefinition, not %s." + .formatted(neighborhoodProviderClass, moveDefinitionList.size())); } var moveDefinition = moveDefinitionList.get(0); var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, configPolicy.getEnvironmentMode()); - var moveProducer = (InnerMoveProducer) moveDefinition.build(moveStreamFactory); - var moveRepository = new MoveStreamsBasedMoveRepository<>(moveStreamFactory, moveProducer, + var moveStream = (InnerMoveStream) moveDefinition.build(moveStreamFactory); + var moveRepository = new NeighborhoodsBasedMoveRepository<>(moveStreamFactory, moveStream, pickSelectionOrder() == SelectionOrder.RANDOM); return buildDecider(moveRepository, configPolicy, termination); @@ -145,7 +151,7 @@ Convert your entities (%s) to use @%s instead.""" private LocalSearchDecider buildDecider(MoveRepository moveRepository, HeuristicConfigPolicy configPolicy, PhaseTermination termination) { - var acceptor = buildAcceptor(configPolicy, moveRepository instanceof MoveStreamsBasedMoveRepository); + var acceptor = buildAcceptor(configPolicy, moveRepository instanceof NeighborhoodsBasedMoveRepository); var forager = buildForager(configPolicy); if (moveRepository.isNeverEnding() && !forager.supportsNeverEndingMoveSelector()) { throw new IllegalStateException(""" @@ -164,7 +170,7 @@ The move repository (%s) is neverEnding (%s), but the forager (%s) does not supp return decider; } - protected Acceptor buildAcceptor(HeuristicConfigPolicy configPolicy, boolean moveStreamsEnabled) { + protected Acceptor buildAcceptor(HeuristicConfigPolicy configPolicy, boolean neighborhoodsEnabled) { var acceptorConfig = phaseConfig.getAcceptorConfig(); var localSearchType = phaseConfig.getLocalSearchType(); if (acceptorConfig != null) { @@ -177,10 +183,10 @@ protected Acceptor buildAcceptor(HeuristicConfigPolicy con } else { var localSearchType_ = Objects.requireNonNullElse(localSearchType, LocalSearchType.LATE_ACCEPTANCE); var acceptorConfig_ = new LocalSearchAcceptorConfig(); - if (moveStreamsEnabled && localSearchType_ == LocalSearchType.VARIABLE_NEIGHBORHOOD_DESCENT) { + if (neighborhoodsEnabled && localSearchType_ == LocalSearchType.VARIABLE_NEIGHBORHOOD_DESCENT) { // Maybe works, but never tested. throw new UnsupportedOperationException( - "Variable Neighborhood descent is not yet supported with Move Streams."); + "Variable Neighborhood descent is not yet supported with the Neighborhoods API."); } var acceptorType = switch (localSearchType_) { case HILL_CLIMBING, VARIABLE_NEIGHBORHOOD_DESCENT -> AcceptorType.HILL_CLIMBING; @@ -190,8 +196,8 @@ protected Acceptor buildAcceptor(HeuristicConfigPolicy con case DIVERSIFIED_LATE_ACCEPTANCE -> AcceptorType.DIVERSIFIED_LATE_ACCEPTANCE; case GREAT_DELUGE -> AcceptorType.GREAT_DELUGE; }; - if (moveStreamsEnabled && acceptorType.isTabu()) { - throw new UnsupportedOperationException("Tabu search is not yet supported with Move Streams."); + if (neighborhoodsEnabled && acceptorType.isTabu()) { + throw new UnsupportedOperationException("Tabu search is not yet supported with the Neighborhoods API."); } acceptorConfig_.setAcceptorTypeList(Collections.singletonList(acceptorType)); return buildAcceptor(acceptorConfig_, configPolicy); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/LocalSearchDecider.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/LocalSearchDecider.java index 80215d7996..b5a5801bba 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/LocalSearchDecider.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/LocalSearchDecider.java @@ -9,7 +9,7 @@ import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchMoveScope; import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchPhaseScope; import ai.timefold.solver.core.impl.localsearch.scope.LocalSearchStepScope; -import ai.timefold.solver.core.impl.move.MoveRepository; +import ai.timefold.solver.core.impl.neighborhood.MoveRepository; import ai.timefold.solver.core.impl.phase.scope.SolverLifecyclePoint; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultBiFromBiMoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultBiFromBiMoveStream.java deleted file mode 100644 index b4e65aef88..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultBiFromBiMoveStream.java +++ /dev/null @@ -1,26 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams; - -import java.util.Objects; - -import ai.timefold.solver.core.impl.move.streams.dataset.bi.BiDataset; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveConstructor; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class DefaultBiFromBiMoveStream implements BiMoveStream { - - private final BiDataset dataset; - - public DefaultBiFromBiMoveStream(BiDataset dataset) { - this.dataset = Objects.requireNonNull(dataset); - } - - @Override - public MoveProducer asMove(BiMoveConstructor moveConstructor) { - return new FromBiUniMoveProducer<>(dataset, Objects.requireNonNull(moveConstructor)); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultBiFromUnisMoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultBiFromUnisMoveStream.java deleted file mode 100644 index 7347f6d44b..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultBiFromUnisMoveStream.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams; - -import java.util.Objects; -import java.util.function.BiPredicate; - -import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDataset; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveConstructor; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class DefaultBiFromUnisMoveStream implements BiMoveStream { - - private final UniDataset leftDataset; - private final UniDataset rightDataset; - private final BiPredicate filter; - - public DefaultBiFromUnisMoveStream(UniDataset leftDataset, UniDataset rightDataset, - BiPredicate filter) { - this.leftDataset = Objects.requireNonNull(leftDataset); - this.rightDataset = Objects.requireNonNull(rightDataset); - this.filter = Objects.requireNonNull(filter); - } - - @Override - public MoveProducer asMove(BiMoveConstructor moveConstructor) { - return new FromUniBiMoveProducer<>(leftDataset, rightDataset, filter, Objects.requireNonNull(moveConstructor)); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultMoveStreamFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultMoveStreamFactory.java deleted file mode 100644 index 7c8e9a84d0..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultMoveStreamFactory.java +++ /dev/null @@ -1,97 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams; - -import ai.timefold.solver.core.config.solver.EnvironmentMode; -import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.DatasetSessionFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.bi.AbstractBiDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.AbstractUniDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.DataJoiners; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamFactory; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniMoveStream; -import ai.timefold.solver.core.impl.score.director.SessionContext; -import ai.timefold.solver.core.preview.api.domain.metamodel.GenuineVariableMetaModel; -import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; -import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class DefaultMoveStreamFactory - implements MoveStreamFactory { - - private final DataStreamFactory dataStreamFactory; - private final DatasetSessionFactory datasetSessionFactory; - - public DefaultMoveStreamFactory(SolutionDescriptor solutionDescriptor, EnvironmentMode environmentMode) { - this.dataStreamFactory = new DataStreamFactory<>(solutionDescriptor, environmentMode); - this.datasetSessionFactory = new DatasetSessionFactory<>(dataStreamFactory); - } - - public DefaultMoveStreamSession createSession(SessionContext context) { - var session = datasetSessionFactory.buildSession(context); - return new DefaultMoveStreamSession<>(session, context.solutionView()); - } - - @Override - public UniDataStream forEach(Class sourceClass, boolean includeNull) { - var entityDescriptor = getSolutionDescriptor().findEntityDescriptor(sourceClass); - if (entityDescriptor == null) { // Not an entity, can't be pinned. - return dataStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); - } - if (entityDescriptor.isGenuine()) { // Genuine entity can be pinned. - return dataStreamFactory.forEachExcludingPinned(sourceClass, includeNull); - } - // From now on, we are testing a shadow entity. - var listVariableDescriptor = getSolutionDescriptor().getListVariableDescriptor(); - if (listVariableDescriptor == null) { // Can't be pinned when there are only basic variables. - return dataStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); - } - if (!listVariableDescriptor.supportsPinning()) { // The genuine entity does not support pinning. - return dataStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); - } - if (!listVariableDescriptor.acceptsValueType(sourceClass)) { // Can't be used as an element. - return dataStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); - } - // Finally a valid pin-supporting type. - return dataStreamFactory.forEachExcludingPinned(sourceClass, includeNull); - } - - @Override - public UniDataStream forEachUnfiltered(Class sourceClass, boolean includeNull) { - return dataStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); - } - - @Override - public BiDataStream forEachEntityValuePair( - GenuineVariableMetaModel variableMetaModel, - UniDataStream entityDataStream) { - var includeNull = - variableMetaModel instanceof PlanningVariableMetaModel planningVariableMetaModel - ? planningVariableMetaModel.allowsUnassigned() - : variableMetaModel instanceof PlanningListVariableMetaModel planningListVariableMetaModel - && planningListVariableMetaModel.allowsUnassignedValues(); - var stream = dataStreamFactory.forEachExcludingPinned(variableMetaModel.type(), includeNull); - return entityDataStream.join(stream, DataJoiners. filtering( - (solutionView, entity, value) -> solutionView.isValueInRange(variableMetaModel, entity, value))); - } - - @Override - public UniMoveStream pick(UniDataStream dataStream) { - return new DefaultUniMoveStream<>(this, - ((AbstractUniDataStream) dataStream).createDataset()); - } - - @Override - public BiMoveStream pick(BiDataStream dataStream) { - return new DefaultBiFromBiMoveStream<>(((AbstractBiDataStream) dataStream).createDataset()); - } - - public SolutionDescriptor getSolutionDescriptor() { - return dataStreamFactory.getSolutionDescriptor(); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultUniMoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultUniMoveStream.java deleted file mode 100644 index d976eb609d..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultUniMoveStream.java +++ /dev/null @@ -1,35 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams; - -import java.util.Objects; -import java.util.function.BiPredicate; - -import ai.timefold.solver.core.impl.move.streams.dataset.uni.AbstractUniDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDataset; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class DefaultUniMoveStream implements InnerUniMoveStream { - - private final DefaultMoveStreamFactory moveStreamFactory; - private final UniDataset dataset; - - public DefaultUniMoveStream(DefaultMoveStreamFactory moveStreamFactory, UniDataset dataset) { - this.moveStreamFactory = Objects.requireNonNull(moveStreamFactory); - this.dataset = Objects.requireNonNull(dataset); - } - - @Override - public BiMoveStream pick(UniDataStream uniDataStream, BiPredicate filter) { - return new DefaultBiFromUnisMoveStream<>(dataset, ((AbstractUniDataStream) uniDataStream).createDataset(), - filter); - } - - @Override - public UniDataset getDataset() { - return dataset; - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerMoveProducer.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerMoveProducer.java deleted file mode 100644 index cf17553ad1..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerMoveProducer.java +++ /dev/null @@ -1,19 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams; - -import java.util.Set; - -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface InnerMoveProducer extends MoveProducer { - - @Override - MoveIterable getMoveIterable(MoveStreamSession moveStreamSession); - - void collectActiveDataStreams(Set> activeDataStreamSet); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerMoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerMoveStream.java deleted file mode 100644 index ff8cc117e6..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerMoveStream.java +++ /dev/null @@ -1,14 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams; - -import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface InnerMoveStream extends MoveStream { - - AbstractDataset getDataset(); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerUniMoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerUniMoveStream.java deleted file mode 100644 index e842401dd4..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/InnerUniMoveStream.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams; - -import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDataset; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniMoveStream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface InnerUniMoveStream - extends InnerMoveStream>, UniMoveStream { - - @Override - UniDataset getDataset(); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/AbstractBiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/AbstractBiDataStream.java deleted file mode 100644 index 04edf5f270..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/AbstractBiDataStream.java +++ /dev/null @@ -1,78 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; - -import java.util.function.BiFunction; - -import ai.timefold.solver.core.impl.bavet.bi.Group2Mapping0CollectorBiNode; -import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeBiDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeUniDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataMapper; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; -import ai.timefold.solver.core.impl.util.ConstantLambdaUtils; - -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@NullMarked -public abstract class AbstractBiDataStream extends AbstractDataStream - implements BiDataStream { - - protected AbstractBiDataStream(DataStreamFactory dataStreamFactory) { - super(dataStreamFactory, null); - } - - protected AbstractBiDataStream(DataStreamFactory dataStreamFactory, - @Nullable AbstractDataStream parent) { - super(dataStreamFactory, parent); - } - - @Override - public final BiDataStream filter(BiDataFilter filter) { - return shareAndAddChild(new FilterBiDataStream<>(dataStreamFactory, this, filter)); - } - - protected AbstractBiDataStream - groupBy(BiFunction groupKeyAMapping, BiFunction groupKeyBMapping) { - GroupNodeConstructor> nodeConstructor = - GroupNodeConstructor.twoKeysGroupBy(groupKeyAMapping, groupKeyBMapping, Group2Mapping0CollectorBiNode::new); - return buildBiGroupBy(nodeConstructor); - } - - private AbstractBiDataStream - buildBiGroupBy(GroupNodeConstructor> nodeConstructor) { - var stream = shareAndAddChild(new BiGroupBiDataStream<>(dataStreamFactory, this, nodeConstructor)); - return dataStreamFactory.share(new AftBridgeBiDataStream<>(dataStreamFactory, stream), stream::setAftBridge); - } - - @Override - public UniDataStream map(BiDataMapper mapping) { - var stream = shareAndAddChild(new UniMapBiDataStream<>(dataStreamFactory, this, mapping)); - return dataStreamFactory.share(new AftBridgeUniDataStream<>(dataStreamFactory, stream), stream::setAftBridge); - } - - @Override - public BiDataStream - map(BiDataMapper mappingA, BiDataMapper mappingB) { - var stream = shareAndAddChild(new BiMapBiDataStream<>(dataStreamFactory, this, mappingA, mappingB)); - return dataStreamFactory.share(new AftBridgeBiDataStream<>(dataStreamFactory, stream), stream::setAftBridge); - } - - @Override - public AbstractBiDataStream distinct() { - if (guaranteesDistinct()) { - return this; // Already distinct, no need to create a new stream. - } - return groupBy(ConstantLambdaUtils.biPickFirst(), ConstantLambdaUtils.biPickSecond()); - } - - public BiDataset createDataset() { - var stream = shareAndAddChild(new TerminalBiDataStream<>(dataStreamFactory, this)); - return stream.getDataset(); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiDataset.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiDataset.java deleted file mode 100644 index 72a3667dbe..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiDataset.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; - -import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class BiDataset extends AbstractDataset> { - - public BiDataset(DataStreamFactory dataStreamFactory, AbstractBiDataStream parent) { - super(dataStreamFactory, parent); - } - - @Override - public BiDatasetInstance instantiate(int storeIndex) { - return new BiDatasetInstance<>(this, storeIndex); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/TerminalBiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/TerminalBiDataStream.java deleted file mode 100644 index 55c305dfa2..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/TerminalBiDataStream.java +++ /dev/null @@ -1,39 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; - -import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.TerminalDataStream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -final class TerminalBiDataStream - extends AbstractBiDataStream - implements TerminalDataStream, BiDataset> { - - private final BiDataset dataset; - - public TerminalBiDataStream(DataStreamFactory dataStreamFactory, AbstractBiDataStream parent) { - super(dataStreamFactory, parent); - this.dataset = new BiDataset<>(dataStreamFactory, this); - } - - @Override - public void buildNode(DataNodeBuildHelper buildHelper) { - assertEmptyChildStreamList(); - var inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); - buildHelper.putInsertUpdateRetract(this, dataset.instantiate(inputStoreIndex)); - } - - @Override - public BiDataset getDataset() { - return dataset; - } - - @Override - public String toString() { - return "Terminal node"; - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDataset.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDataset.java deleted file mode 100644 index 99fb5a8bfc..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDataset.java +++ /dev/null @@ -1,49 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common; - -import java.util.Objects; -import java.util.Set; - -import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public abstract class AbstractDataset { - - private final DataStreamFactory dataStreamFactory; - private final AbstractDataStream parent; - - protected AbstractDataset(DataStreamFactory dataStreamFactory, AbstractDataStream parent) { - this.dataStreamFactory = Objects.requireNonNull(dataStreamFactory); - this.parent = Objects.requireNonNull(parent); - } - - public void collectActiveDataStreams(Set> dataStreamSet) { - parent.collectActiveDataStreams(dataStreamSet); - } - - public abstract AbstractDatasetInstance instantiate(int storeIndex); - - public DataStreamFactory getDataStreamFactory() { - return dataStreamFactory; - } - - @Override - public boolean equals(Object entity) { - if (!(entity instanceof AbstractDataset dataset)) { - return false; - } - return Objects.equals(dataStreamFactory, dataset.dataStreamFactory) && Objects.equals(parent, dataset.parent); - } - - @Override - public int hashCode() { - return Objects.hash(dataStreamFactory, parent); - } - - @Override - public String toString() { - return "%s for %s".formatted(getClass().getSimpleName(), parent); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/DataStreamBinaryOperation.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/DataStreamBinaryOperation.java deleted file mode 100644 index afc1e298c7..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/DataStreamBinaryOperation.java +++ /dev/null @@ -1,12 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common; - -import ai.timefold.solver.core.impl.bavet.common.BavetStreamBinaryOperation; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public sealed interface DataStreamBinaryOperation - extends BavetStreamBinaryOperation> - permits IfExistsDataStream, JoinDataStream { - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/IfExistsDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/IfExistsDataStream.java deleted file mode 100644 index d1dc71b8ee..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/IfExistsDataStream.java +++ /dev/null @@ -1,8 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public non-sealed interface IfExistsDataStream extends DataStreamBinaryOperation { - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/JoinDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/JoinDataStream.java deleted file mode 100644 index ce69fbd739..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/JoinDataStream.java +++ /dev/null @@ -1,11 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common; - -import ai.timefold.solver.core.impl.bavet.common.TupleSource; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public non-sealed interface JoinDataStream - extends DataStreamBinaryOperation, TupleSource { - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/TerminalDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/TerminalDataStream.java deleted file mode 100644 index ad31999df4..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/TerminalDataStream.java +++ /dev/null @@ -1,12 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common; - -import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface TerminalDataStream> { - - Dataset_ getDataset(); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/AftBridgeBiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/AftBridgeBiDataStream.java deleted file mode 100644 index 343624b86c..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/AftBridgeBiDataStream.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common.bridge; - -import java.util.Objects; - -import ai.timefold.solver.core.impl.bavet.common.TupleSource; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.bi.AbstractBiDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class AftBridgeBiDataStream - extends AbstractBiDataStream - implements TupleSource { - - public AftBridgeBiDataStream(DataStreamFactory dataStreamFactory, AbstractDataStream parent) { - super(dataStreamFactory, parent); - } - - @Override - public void buildNode(DataNodeBuildHelper buildHelper) { - // Do nothing. The parent stream builds everything. - } - - @Override - public boolean equals(Object o) { - return o instanceof AftBridgeBiDataStream that && Objects.equals(parent, that.parent); - } - - @Override - public int hashCode() { - return Objects.requireNonNull(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/streams/dataset/common/bridge/AftBridgeUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/AftBridgeUniDataStream.java deleted file mode 100644 index ed50b5119f..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/AftBridgeUniDataStream.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common.bridge; - -import java.util.Objects; - -import ai.timefold.solver.core.impl.bavet.common.TupleSource; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.AbstractUniDataStream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class AftBridgeUniDataStream - extends AbstractUniDataStream - implements TupleSource { - - public AftBridgeUniDataStream(DataStreamFactory dataStreamFactory, AbstractDataStream parent) { - super(dataStreamFactory, parent); - } - - @Override - public void buildNode(DataNodeBuildHelper buildHelper) { - // Do nothing. The parent stream builds everything. - } - - @Override - public boolean equals(Object o) { - return o instanceof AftBridgeUniDataStream that && Objects.equals(parent, that.parent); - } - - @Override - public int hashCode() { - return Objects.requireNonNull(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/streams/dataset/common/bridge/ForeBridgeUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/ForeBridgeUniDataStream.java deleted file mode 100644 index 8a4c42e801..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/ForeBridgeUniDataStream.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common.bridge; - -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.AbstractUniDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class ForeBridgeUniDataStream - extends AbstractUniDataStream - implements UniDataStream { - - public ForeBridgeUniDataStream(DataStreamFactory dataStreamFactory, AbstractDataStream parent) { - super(dataStreamFactory, 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/streams/dataset/joiner/FilteringBiDataJoiner.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/FilteringBiDataJoiner.java deleted file mode 100644 index ff2d157381..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/FilteringBiDataJoiner.java +++ /dev/null @@ -1,17 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.joiner; - -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataJoiner; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public record FilteringBiDataJoiner(BiDataFilter filter) implements BiDataJoiner { - - @Override - public FilteringBiDataJoiner and(BiDataJoiner otherJoiner) { - FilteringBiDataJoiner castJoiner = (FilteringBiDataJoiner) otherJoiner; - return new FilteringBiDataJoiner<>(filter.and(castJoiner.filter())); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/AbstractUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/AbstractUniDataStream.java deleted file mode 100644 index 430fda1502..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/AbstractUniDataStream.java +++ /dev/null @@ -1,152 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; - -import static ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor.oneKeyGroupBy; - -import java.util.Objects; -import java.util.function.Function; - -import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; -import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; -import ai.timefold.solver.core.impl.bavet.uni.Group1Mapping0CollectorUniNode; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.bi.JoinBiDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeBiDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeUniDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.ForeBridgeUniDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.joiner.BiDataJoinerComber; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataJoiner; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataFilter; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataMapper; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; -import ai.timefold.solver.core.impl.util.ConstantLambdaUtils; - -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@NullMarked -public abstract class AbstractUniDataStream extends AbstractDataStream - implements UniDataStream { - - protected AbstractUniDataStream(DataStreamFactory dataStreamFactory) { - super(dataStreamFactory, null); - } - - protected AbstractUniDataStream(DataStreamFactory dataStreamFactory, - @Nullable AbstractDataStream parent) { - super(dataStreamFactory, parent); - } - - @Override - public final UniDataStream filter(UniDataFilter filter) { - return shareAndAddChild(new FilterUniDataStream<>(dataStreamFactory, this, filter)); - } - - @Override - public BiDataStream join(UniDataStream otherStream, BiDataJoiner... joiners) { - var other = (AbstractUniDataStream) otherStream; - var leftBridge = new ForeBridgeUniDataStream(dataStreamFactory, this); - var rightBridge = new ForeBridgeUniDataStream(dataStreamFactory, other); - var joinerComber = BiDataJoinerComber. comb(joiners); - var joinStream = new JoinBiDataStream<>(dataStreamFactory, leftBridge, rightBridge, - joinerComber.mergedJoiner(), joinerComber.mergedFiltering()); - return dataStreamFactory.share(joinStream, joinStream_ -> { - // Connect the bridges upstream, as it is an actual new join. - getChildStreamList().add(leftBridge); - other.getChildStreamList().add(rightBridge); - }); - } - - @Override - public BiDataStream join(Class otherClass, BiDataJoiner... joiners) { - return join(dataStreamFactory.forEachNonDiscriminating(otherClass, false), joiners); - } - - @SafeVarargs - @Override - public final UniDataStream ifExists(Class otherClass, BiDataJoiner... joiners) { - return ifExists(dataStreamFactory.forEachNonDiscriminating(otherClass, false), joiners); - } - - @SafeVarargs - @Override - public final UniDataStream ifExists(UniDataStream otherStream, - BiDataJoiner... joiners) { - return ifExistsOrNot(true, otherStream, joiners); - } - - @SafeVarargs - @Override - public final UniDataStream ifNotExists(Class otherClass, BiDataJoiner... joiners) { - return ifExistsOrNot(false, dataStreamFactory.forEachNonDiscriminating(otherClass, false), joiners); - } - - @SafeVarargs - @Override - public final UniDataStream ifNotExists(UniDataStream otherStream, - BiDataJoiner... joiners) { - return ifExistsOrNot(false, otherStream, joiners); - } - - private UniDataStream ifExistsOrNot(boolean shouldExist, UniDataStream otherStream, - BiDataJoiner[] joiners) { - var other = (AbstractUniDataStream) otherStream; - var joinerComber = BiDataJoinerComber. comb(joiners); - var parentBridgeB = other.shareAndAddChild(new ForeBridgeUniDataStream(dataStreamFactory, other)); - return dataStreamFactory.share(new IfExistsUniDataStream<>(dataStreamFactory, this, parentBridgeB, shouldExist, - joinerComber.mergedJoiner(), joinerComber.mergedFiltering()), childStreamList::add); - } - - /** - * Convert the {@link UniDataStream} to a different {@link UniDataStream}, - * containing the set of tuples resulting from applying the group key mapping function - * on all tuples of the original stream. - * Neither tuple of the new stream {@link Objects#equals(Object, Object)} any other. - * - * @param groupKeyMapping mapping function to convert each element in the stream to a different element - * @param the type of a fact in the destination {@link UniDataStream}'s tuple; - * must honor {@link Object#hashCode() the general contract of hashCode}. - */ - protected AbstractUniDataStream groupBy(Function groupKeyMapping) { - // We do not expose this on the API, as this operation is not yet needed in any of the moves. - // The groupBy API will need revisiting if exposed as a feature of Move Streams, do not expose as is. - GroupNodeConstructor> nodeConstructor = - oneKeyGroupBy(groupKeyMapping, Group1Mapping0CollectorUniNode::new); - return buildUniGroupBy(nodeConstructor); - } - - private AbstractUniDataStream - buildUniGroupBy(GroupNodeConstructor> nodeConstructor) { - var stream = shareAndAddChild(new UniGroupUniDataStream<>(dataStreamFactory, this, nodeConstructor)); - return dataStreamFactory.share(new AftBridgeUniDataStream<>(dataStreamFactory, stream), - stream::setAftBridge); - } - - @Override - public UniDataStream map(UniDataMapper mapping) { - var stream = shareAndAddChild(new UniMapUniDataStream<>(dataStreamFactory, this, mapping)); - return dataStreamFactory.share(new AftBridgeUniDataStream<>(dataStreamFactory, stream), stream::setAftBridge); - } - - @Override - public BiDataStream map(UniDataMapper mappingA, - UniDataMapper mappingB) { - var stream = shareAndAddChild(new BiMapUniDataStream<>(dataStreamFactory, this, mappingA, mappingB)); - return dataStreamFactory.share(new AftBridgeBiDataStream<>(dataStreamFactory, stream), stream::setAftBridge); - } - - @Override - public AbstractUniDataStream distinct() { - if (guaranteesDistinct()) { - return this; // Already distinct, no need to create a new stream. - } - return groupBy(ConstantLambdaUtils.identity()); - } - - public UniDataset createDataset() { - var stream = shareAndAddChild(new TerminalUniDataStream<>(dataStreamFactory, this)); - return stream.getDataset(); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/TerminalUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/TerminalUniDataStream.java deleted file mode 100644 index c4e3421a53..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/TerminalUniDataStream.java +++ /dev/null @@ -1,39 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; - -import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.TerminalDataStream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -final class TerminalUniDataStream - extends AbstractUniDataStream - implements TerminalDataStream, UniDataset> { - - private final UniDataset dataset; - - public TerminalUniDataStream(DataStreamFactory dataStreamFactory, AbstractUniDataStream parent) { - super(dataStreamFactory, parent); - this.dataset = new UniDataset<>(dataStreamFactory, this); - } - - @Override - public void buildNode(DataNodeBuildHelper buildHelper) { - assertEmptyChildStreamList(); - var inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); - buildHelper.putInsertUpdateRetract(this, dataset.instantiate(inputStoreIndex)); - } - - @Override - public UniDataset getDataset() { - return dataset; - } - - @Override - public String toString() { - return "Terminal node"; - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniDataset.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniDataset.java deleted file mode 100644 index 11225e0cf4..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniDataset.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; - -import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public final class UniDataset extends AbstractDataset> { - - public UniDataset(DataStreamFactory dataStreamFactory, AbstractUniDataStream parent) { - super(dataStreamFactory, parent); - } - - @Override - public UniDatasetInstance instantiate(int storeIndex) { - return new UniDatasetInstance<>(this, storeIndex); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataJoiner.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataJoiner.java deleted file mode 100644 index 80c105ab94..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataJoiner.java +++ /dev/null @@ -1,19 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi; - -import ai.timefold.solver.core.api.score.stream.Joiners; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; - -import org.jspecify.annotations.NullMarked; - -/** - * Created with {@link Joiners}. - * Used by {@link UniDataStream#join(Class, BiDataJoiner[])}, ... - * - * @see Joiners - */ -@NullMarked -public interface BiDataJoiner { - - BiDataJoiner and(BiDataJoiner otherJoiner); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ChangeMoveDefinition.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ChangeMoveDefinition.java deleted file mode 100644 index 6dd65af2a5..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ChangeMoveDefinition.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions; - -import java.util.Objects; - -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamFactory; -import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public class ChangeMoveDefinition - implements MoveDefinition { - - private final PlanningVariableMetaModel variableMetaModel; - - public ChangeMoveDefinition(PlanningVariableMetaModel variableMetaModel) { - this.variableMetaModel = Objects.requireNonNull(variableMetaModel); - } - - @Override - public MoveProducer build(MoveStreamFactory moveStreamFactory) { - var dataStream = moveStreamFactory.forEachEntityValuePair(variableMetaModel) - .filter((solutionView, entity, value) -> { - Value_ currentValue = solutionView.getValue(variableMetaModel, Objects.requireNonNull(entity)); - return !Objects.equals(currentValue, value); - }); - return moveStreamFactory.pick(dataStream) - .asMove((solution, entity, value) -> Moves.change(Objects.requireNonNull(entity), value, variableMetaModel)); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiDataStream.java deleted file mode 100644 index ace6d7515f..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiDataStream.java +++ /dev/null @@ -1,51 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataMapper; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataMapper; -import ai.timefold.solver.core.preview.api.move.SolutionView; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface BiDataStream extends DataStream { - - /** - * Exhaustively test each fact against the {@link BiDataFilter} - * and match if {@link BiDataFilter#test(SolutionView, Object, Object)} returns true. - */ - BiDataStream filter(BiDataFilter filter); - - // ************************************************************************ - // Operations with duplicate tuple possibility - // ************************************************************************ - - /** - * As defined by {@link UniDataStream#map(UniDataMapper)}. - * - *

- * Use with caution, - * as the increased memory allocation rates coming from tuple creation may negatively affect performance. - * - * @param mapping function to convert the original tuple into the new tuple - * @param the type of the only fact in the resulting {@link UniDataStream}'s tuple - */ - UniDataStream map(BiDataMapper mapping); - - /** - * As defined by {@link #map(BiDataMapper)}, only resulting in {@link BiDataStream}. - * - * @param mappingA function to convert the original tuple into the first fact of a new tuple - * @param mappingB function to convert the original tuple into the second fact of a new tuple - * @param the type of the first fact in the resulting {@link BiDataStream}'s tuple - * @param the type of the first fact in the resulting {@link BiDataStream}'s tuple - */ - BiDataStream map(BiDataMapper mappingA, - BiDataMapper mappingB); - - /** - * As defined by {@link UniDataStream#distinct()}. - */ - BiDataStream distinct(); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiMoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiMoveStream.java deleted file mode 100644 index f2c30d7009..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiMoveStream.java +++ /dev/null @@ -1,10 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface BiMoveStream extends MoveStream { - - MoveProducer asMove(BiMoveConstructor moveConstructor); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/DataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/DataStream.java deleted file mode 100644 index 43a3df9fe1..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/DataStream.java +++ /dev/null @@ -1,8 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface DataStream { - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveConstructor.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveConstructor.java deleted file mode 100644 index bc1f505897..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveConstructor.java +++ /dev/null @@ -1,8 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public sealed interface MoveConstructor permits BiMoveConstructor { - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveProducer.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveProducer.java deleted file mode 100644 index 4928d12cbb..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveProducer.java +++ /dev/null @@ -1,12 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import ai.timefold.solver.core.preview.api.move.Move; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface MoveProducer { - - Iterable> getMoveIterable(MoveStreamSession moveStreamSession); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveProvider.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveProvider.java deleted file mode 100644 index 1aa8d7b84c..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveProvider.java +++ /dev/null @@ -1,14 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import java.util.List; - -import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface MoveProvider { - - List> defineMoves(PlanningSolutionMetaModel solutionMetaModel); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStream.java deleted file mode 100644 index 17490150fa..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStream.java +++ /dev/null @@ -1,8 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface MoveStream { - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStreamSession.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStreamSession.java deleted file mode 100644 index 44bc4db146..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStreamSession.java +++ /dev/null @@ -1,7 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface MoveStreamSession { -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/UniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/UniDataStream.java deleted file mode 100644 index 48cbc56d2f..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/UniDataStream.java +++ /dev/null @@ -1,419 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataJoiner; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataFilter; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataMapper; -import ai.timefold.solver.core.preview.api.move.SolutionView; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface UniDataStream extends DataStream { - - /** - * Exhaustively test each fact against the {@link UniDataFilter} - * and match if {@link UniDataFilter#test(SolutionView, Object)} returns true. - */ - UniDataStream filter(UniDataFilter filter); - - /** - * As defined by {@link #join(UniDataStream, BiDataJoiner[])}, with the array being empty. - */ - @SuppressWarnings("unchecked") - default BiDataStream join(UniDataStream otherStream) { - return join(otherStream, new BiDataJoiner[0]); - } - - /** - * As defined by {@link #join(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default BiDataStream join(UniDataStream otherStream, BiDataJoiner joiner) { - return join(otherStream, new BiDataJoiner[] { joiner }); - } - - /** - * As defined by {@link #join(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default BiDataStream join(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2) { - return join(otherStream, new BiDataJoiner[] { joiner1, joiner2 }); - } - - /** - * As defined by {@link #join(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default BiDataStream join(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3) { - return join(otherStream, new BiDataJoiner[] { joiner1, joiner2, joiner3 }); - } - - /** - * As defined by {@link #join(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default BiDataStream join(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3, BiDataJoiner joiner4) { - return join(otherStream, new BiDataJoiner[] { joiner1, joiner2, joiner3, joiner4 }); - } - - /** - * Create a new {@link BiDataStream} for every combination of A and B for which the {@link BiDataJoiner} - * is true (for the properties it extracts from both facts). - *

- * Important: Joining is faster and more scalable than a {@link BiDataStream#filter(BiDataFilter) filter}, - * because it applies hashing and/or indexing on the properties, - * so it doesn't create nor checks every combination of A and B. - * - * @param the type of the second matched fact - * @return a stream that matches every combination of A and B for which the {@link BiDataJoiner} is true - */ - BiDataStream join(UniDataStream otherStream, BiDataJoiner... joiners); - - /** - * As defined by {@link #join(Class, BiDataJoiner[])}, with the array being empty. - */ - @SuppressWarnings("unchecked") - default BiDataStream join(Class otherClass) { - return join(otherClass, new BiDataJoiner[0]); - } - - /** - * As defined by {@link #join(Class, BiDataJoiner[])} - */ - @SuppressWarnings("unchecked") - default BiDataStream join(Class otherClass, BiDataJoiner joiner) { - return join(otherClass, new BiDataJoiner[] { joiner }); - } - - /** - * As defined by {@link #join(Class, BiDataJoiner[])} - */ - @SuppressWarnings("unchecked") - default BiDataStream join(Class otherClass, BiDataJoiner joiner1, - BiDataJoiner joiner2) { - return join(otherClass, new BiDataJoiner[] { joiner1, joiner2 }); - } - - /** - * As defined by {@link #join(Class, BiDataJoiner[])} - */ - @SuppressWarnings("unchecked") - default BiDataStream join(Class otherClass, BiDataJoiner joiner1, BiDataJoiner joiner2, - BiDataJoiner joiner3) { - return join(otherClass, new BiDataJoiner[] { joiner1, joiner2, joiner3 }); - } - - /** - * As defined by {@link #join(Class, BiDataJoiner[])} - */ - @SuppressWarnings("unchecked") - default BiDataStream join(Class otherClass, BiDataJoiner joiner1, BiDataJoiner joiner2, - BiDataJoiner joiner3, BiDataJoiner joiner4) { - return join(otherClass, new BiDataJoiner[] { joiner1, joiner2, joiner3, joiner4 }); - } - - /** - * Create a new {@link BiDataStream} for every combination of A and B - * for which the {@link BiDataJoiner} is true (for the properties it extracts from both facts). - * The stream will include all facts or entities of the given class, - * regardless of their pinning status. - *

- * Important: Joining is faster and more scalable than a {@link BiDataStream#filter(BiDataFilter) filter}, - * because it applies hashing and/or indexing on the properties, - * so it doesn't create nor checks every combination of A and B. - * - * @param the type of the second matched fact - * @return a stream that matches every combination of A and B for which the {@link BiDataJoiner} is true - */ - BiDataStream join(Class otherClass, BiDataJoiner... joiners); - - /** - * As defined by {@link #ifExists(UniDataStream, BiDataJoiner[])}, with the array being empty. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(UniDataStream otherStream) { - return ifExists(otherStream, new BiDataJoiner[0]); - } - - /** - * As defined by {@link #ifExists(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(UniDataStream otherStream, BiDataJoiner joiner) { - return ifExists(otherStream, new BiDataJoiner[] { joiner }); - } - - /** - * As defined by {@link #ifExists(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2) { - return ifExists(otherStream, new BiDataJoiner[] { joiner1, joiner2 }); - } - - /** - * As defined by {@link #ifExists(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3) { - return ifExists(otherStream, new BiDataJoiner[] { joiner1, joiner2, joiner3 }); - } - - /** - * As defined by {@link #ifExists(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3, BiDataJoiner joiner4) { - return ifExists(otherStream, new BiDataJoiner[] { joiner1, joiner2, joiner3, joiner4 }); - } - - /** - * Create a new {@link UniDataStream} for every A where B exists for which all {@link BiDataJoiner}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 BiDataJoiner}s are true - */ - @SuppressWarnings("unchecked") - UniDataStream ifExists(UniDataStream otherStream, BiDataJoiner... joiners); - - /** - * As defined by {@link #ifExists(Class, BiDataJoiner[])}, with the array being empty. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(Class otherClass) { - return ifExists(otherClass, new BiDataJoiner[0]); - } - - /** - * As defined by {@link #ifExists(Class, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(Class otherClass, BiDataJoiner joiner) { - return ifExists(otherClass, new BiDataJoiner[] { joiner }); - } - - /** - * As defined by {@link #ifExists(Class, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(Class otherClass, BiDataJoiner joiner1, - BiDataJoiner joiner2) { - return ifExists(otherClass, new BiDataJoiner[] { joiner1, joiner2 }); - } - - /** - * As defined by {@link #ifExists(Class, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(Class otherClass, BiDataJoiner joiner1, - BiDataJoiner joiner2, - BiDataJoiner joiner3) { - return ifExists(otherClass, new BiDataJoiner[] { joiner1, joiner2, joiner3 }); - } - - /** - * As defined by {@link #ifExists(Class, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifExists(Class otherClass, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3, BiDataJoiner joiner4) { - return ifExists(otherClass, new BiDataJoiner[] { joiner1, joiner2, joiner3, joiner4 }); - } - - /** - * Create a new {@link UniDataStream} for every A where B exists for which all {@link BiDataJoiner}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 BiDataJoiner}s are true - */ - @SuppressWarnings("unchecked") - UniDataStream ifExists(Class otherClass, BiDataJoiner... joiners); - - /** - * As defined by {@link #ifNotExists(UniDataStream, BiDataJoiner[])}, with the array being empty. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(UniDataStream otherStream) { - return ifNotExists(otherStream, new BiDataJoiner[0]); - } - - /** - * As defined by {@link #ifNotExists(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(UniDataStream otherStream, BiDataJoiner joiner) { - return ifNotExists(otherStream, new BiDataJoiner[] { joiner }); - } - - /** - * As defined by {@link #ifNotExists(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2) { - return ifNotExists(otherStream, new BiDataJoiner[] { joiner1, joiner2 }); - } - - /** - * As defined by {@link #ifNotExists(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3) { - return ifNotExists(otherStream, new BiDataJoiner[] { joiner1, joiner2, joiner3 }); - } - - /** - * As defined by {@link #ifNotExists(UniDataStream, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(UniDataStream otherStream, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3, BiDataJoiner joiner4) { - return ifNotExists(otherStream, new BiDataJoiner[] { joiner1, joiner2, joiner3, joiner4 }); - } - - /** - * Create a new {@link UniDataStream} for every A where B does not exist for which the {@link BiDataJoiner}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 BiDataJoiner}s are true - */ - @SuppressWarnings("unchecked") - UniDataStream ifNotExists(UniDataStream otherStream, BiDataJoiner... joiners); - - /** - * As defined by {@link #ifNotExists(Class, BiDataJoiner[])}, with the array being empty. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(Class otherClass) { - return ifNotExists(otherClass, new BiDataJoiner[0]); - } - - /** - * As defined by {@link #ifNotExists(Class, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(Class otherClass, BiDataJoiner joiner) { - return ifNotExists(otherClass, new BiDataJoiner[] { joiner }); - } - - /** - * As defined by {@link #ifNotExists(Class, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(Class otherClass, BiDataJoiner joiner1, - BiDataJoiner joiner2) { - return ifNotExists(otherClass, new BiDataJoiner[] { joiner1, joiner2 }); - } - - /** - * As defined by {@link #ifNotExists(Class, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(Class otherClass, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3) { - return ifNotExists(otherClass, new BiDataJoiner[] { joiner1, joiner2, joiner3 }); - } - - /** - * As defined by {@link #ifNotExists(Class, BiDataJoiner[])}. - */ - @SuppressWarnings("unchecked") - default UniDataStream ifNotExists(Class otherClass, BiDataJoiner joiner1, - BiDataJoiner joiner2, BiDataJoiner joiner3, BiDataJoiner joiner4) { - return ifNotExists(otherClass, new BiDataJoiner[] { joiner1, joiner2, joiner3, joiner4 }); - } - - /** - * Create a new {@link UniDataStream} for every A where B does not exist for which the {@link BiDataJoiner}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 BiDataJoiner}s are true - */ - @SuppressWarnings("unchecked") - UniDataStream ifNotExists(Class otherClass, BiDataJoiner... joiners); - - // ************************************************************************ - // Operations with duplicate tuple possibility - // ************************************************************************ - - /** - * Transforms the stream in such a way that tuples are remapped using the given function. - * This may produce a stream with duplicate tuples. - * See {@link #distinct()} for details. - *

- * There are several recommendations for implementing the mapping function: - * - *

- * - *

- * Simple example: assuming a data stream of tuples of {@code Person}s - * {@code [Ann(age = 20), Beth(age = 25), Cathy(age = 30)]}, - * calling {@code map(Person::getAge)} on such stream will produce a stream of {@link Integer}s - * {@code [20, 25, 30]}, - * - *

- * Example with a non-bijective mapping function: assuming a data stream of tuples of {@code Person}s - * {@code [Ann(age = 20), Beth(age = 25), Cathy(age = 30), David(age = 30), Eric(age = 20)]}, - * calling {@code map(Person::getAge)} on such stream will produce a stream of {@link Integer}s - * {@code [20, 25, 30, 30, 20]}. - * - *

- * Use with caution, - * as the increased memory allocation rates coming from tuple creation may negatively affect performance. - * - * @param mapping function to convert the original tuple into the new tuple - * @param the type of the only fact in the resulting {@link UniDataStream}'s tuple - */ - UniDataStream map(UniDataMapper mapping); - - /** - * As defined by {@link #map(UniDataMapper)}, only resulting in {@link BiDataStream}. - * - * @param mappingA function to convert the original tuple into the first fact of a new tuple - * @param mappingB function to convert the original tuple into the second fact of a new tuple - * @param the type of the first fact in the resulting {@link BiDataStream}'s tuple - * @param the type of the first fact in the resulting {@link BiDataStream}'s tuple - */ - BiDataStream map(UniDataMapper mappingA, - UniDataMapper mappingB); - - /** - * Transforms the stream in such a way that all the tuples going through it are distinct. - * (No two tuples will {@link Object#equals(Object) equal}.) - * - *

- * By default, tuples going through a data stream are distinct. - * However, operations such as {@link #map(UniDataMapper)} may create a stream which breaks that promise. - * By calling this method on such a stream, - * duplicate copies of the same tuple will be omitted at a performance cost. - */ - UniDataStream distinct(); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/UniMoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/UniMoveStream.java deleted file mode 100644 index 453c1bd508..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/UniMoveStream.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; - -import java.util.function.BiPredicate; - -import org.jspecify.annotations.NullMarked; - -@NullMarked -public interface UniMoveStream extends MoveStream { - - default BiMoveStream pick(UniDataStream uniDataStream) { - return pick(uniDataStream, (a, b) -> true); - } - - BiMoveStream pick(UniDataStream uniDataStream, BiPredicate filter); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveRepository.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveRepository.java similarity index 67% rename from core/src/main/java/ai/timefold/solver/core/impl/move/MoveRepository.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveRepository.java index dd8216c89c..5be89acbb2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveRepository.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveRepository.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move; +package ai.timefold.solver.core.impl.neighborhood; import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListener; import ai.timefold.solver.core.impl.score.director.SessionContext; @@ -12,21 +12,21 @@ *

    *
  • {@link MoveSelectorBasedMoveRepository} for local search and exhaustive search.
  • *
  • {@link PlacerBasedMoveRepository} for construction heuristics.
  • - *
  • {@link MoveStreamsBasedMoveRepository} for move streams.
  • + *
  • {@link NeighborhoodsBasedMoveRepository} for the Neighborhoods API.
  • *
* - * As move streams become gradually more capable, + * As the Neighborhoods API becomes gradually more capable, * these extra implementations will be removed - * until only {@link MoveStreamsBasedMoveRepository} remains in use. + * until only {@link NeighborhoodsBasedMoveRepository} remains in use. * At this point, this entire abstraction will be removed, - * and all code will work with move streams directly. + * and all code will work with Neighborhoods directly. * * @param */ @NullMarked public sealed interface MoveRepository extends Iterable>, PhaseLifecycleListener - permits MoveSelectorBasedMoveRepository, MoveStreamsBasedMoveRepository, PlacerBasedMoveRepository { + permits MoveSelectorBasedMoveRepository, NeighborhoodsBasedMoveRepository, PlacerBasedMoveRepository { boolean isNeverEnding(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveSelectorBasedMoveRepository.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveSelectorBasedMoveRepository.java similarity index 98% rename from core/src/main/java/ai/timefold/solver/core/impl/move/MoveSelectorBasedMoveRepository.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveSelectorBasedMoveRepository.java index e74866ccdb..3c0e7e37be 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveSelectorBasedMoveRepository.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/MoveSelectorBasedMoveRepository.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move; +package ai.timefold.solver.core.impl.neighborhood; import java.util.Iterator; import java.util.Objects; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveStreamsBasedMoveRepository.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsBasedMoveRepository.java similarity index 71% rename from core/src/main/java/ai/timefold/solver/core/impl/move/MoveStreamsBasedMoveRepository.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsBasedMoveRepository.java index f1e3245877..c50eae10f3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveStreamsBasedMoveRepository.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsBasedMoveRepository.java @@ -1,14 +1,14 @@ -package ai.timefold.solver.core.impl.move; +package ai.timefold.solver.core.impl.neighborhood; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Random; -import ai.timefold.solver.core.impl.move.streams.DefaultMoveStreamFactory; -import ai.timefold.solver.core.impl.move.streams.DefaultMoveStreamSession; -import ai.timefold.solver.core.impl.move.streams.InnerMoveProducer; -import ai.timefold.solver.core.impl.move.streams.MoveIterable; +import ai.timefold.solver.core.impl.neighborhood.move.InnerMoveStream; +import ai.timefold.solver.core.impl.neighborhood.move.MoveIterable; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultMoveStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultNeighborhoodSession; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.score.director.SessionContext; @@ -19,21 +19,21 @@ import org.jspecify.annotations.Nullable; @NullMarked -public final class MoveStreamsBasedMoveRepository +public final class NeighborhoodsBasedMoveRepository implements MoveRepository { private final DefaultMoveStreamFactory moveStreamFactory; - private final InnerMoveProducer moveProducer; + private final InnerMoveStream moveStream; private final boolean random; - private @Nullable DefaultMoveStreamSession moveStreamSession; + private @Nullable DefaultNeighborhoodSession neighborhoodSession; private @Nullable MoveIterable moveIterable; private @Nullable Random workingRandom; - public MoveStreamsBasedMoveRepository(DefaultMoveStreamFactory moveStreamFactory, - InnerMoveProducer moveProducer, boolean random) { + public NeighborhoodsBasedMoveRepository(DefaultMoveStreamFactory moveStreamFactory, + InnerMoveStream moveStream, boolean random) { this.moveStreamFactory = Objects.requireNonNull(moveStreamFactory); - this.moveProducer = Objects.requireNonNull(moveProducer); + this.moveStream = Objects.requireNonNull(moveStream); this.random = random; } @@ -44,25 +44,25 @@ public boolean isNeverEnding() { @Override public void initialize(SessionContext context) { - if (moveStreamSession != null) { + if (neighborhoodSession != null) { throw new IllegalStateException("Impossible state: move repository initialized twice."); } - moveStreamSession = moveStreamFactory.createSession(context); - moveStreamFactory.getSolutionDescriptor().visitAll(context.workingSolution(), moveStreamSession::insert); - moveStreamSession.settle(); - moveIterable = moveProducer.getMoveIterable(moveStreamSession); + neighborhoodSession = moveStreamFactory.createSession(context); + moveStreamFactory.getSolutionDescriptor().visitAll(context.workingSolution(), neighborhoodSession::insert); + neighborhoodSession.settle(); + moveIterable = moveStream.getMoveIterable(neighborhoodSession); } public void insert(Object planningEntityOrProblemFact) { - Objects.requireNonNull(moveStreamSession).insert(planningEntityOrProblemFact); + Objects.requireNonNull(neighborhoodSession).insert(planningEntityOrProblemFact); } public void update(Object planningEntityOrProblemFact) { - Objects.requireNonNull(moveStreamSession).update(planningEntityOrProblemFact); + Objects.requireNonNull(neighborhoodSession).update(planningEntityOrProblemFact); } public void retract(Object planningEntityOrProblemFact) { - Objects.requireNonNull(moveStreamSession).retract(planningEntityOrProblemFact); + Objects.requireNonNull(neighborhoodSession).retract(planningEntityOrProblemFact); } @Override @@ -83,13 +83,13 @@ public void stepStarted(AbstractStepScope stepScope) { @Override public void stepEnded(AbstractStepScope stepScope) { - Objects.requireNonNull(moveStreamSession).settle(); + Objects.requireNonNull(neighborhoodSession).settle(); } @Override public void phaseEnded(AbstractPhaseScope phaseScope) { - if (moveStreamSession != null) { - moveStreamSession = null; + if (neighborhoodSession != null) { + neighborhoodSession = null; } phaseScope.getScoreDirector().setMoveRepository(null); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/PlacerBasedMoveRepository.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/PlacerBasedMoveRepository.java similarity index 98% rename from core/src/main/java/ai/timefold/solver/core/impl/move/PlacerBasedMoveRepository.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/PlacerBasedMoveRepository.java index 255039d9c2..0173b3b73d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/PlacerBasedMoveRepository.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/PlacerBasedMoveRepository.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move; +package ai.timefold.solver.core.impl.neighborhood; import java.util.Iterator; import java.util.Objects; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveDefinition.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveDefinition.java similarity index 69% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveDefinition.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveDefinition.java index 95422b1bf3..0780489b1a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveDefinition.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveDefinition.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; +package ai.timefold.solver.core.impl.neighborhood.maybeapi; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -12,6 +12,6 @@ @NullMarked public interface MoveDefinition { - MoveProducer build(MoveStreamFactory moveStreamFactory); + MoveStream build(MoveStreamFactory moveStreamFactory); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveStream.java new file mode 100644 index 0000000000..873ed68d43 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveStream.java @@ -0,0 +1,12 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi; + +import ai.timefold.solver.core.preview.api.move.Move; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface MoveStream { + + Iterable> getMoveIterable(NeighborhoodSession neighborhoodSession); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStreamFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveStreamFactory.java similarity index 60% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStreamFactory.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveStreamFactory.java index b5b63f5981..8f3c92390d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/MoveStreamFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/MoveStreamFactory.java @@ -1,25 +1,33 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; +package ai.timefold.solver.core.impl.neighborhood.maybeapi; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.entity.PlanningPin; import ai.timefold.solver.core.api.domain.entity.PlanningPinToIndex; import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.BiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.EnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.BiSamplingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.UniSamplingStream; import ai.timefold.solver.core.preview.api.domain.metamodel.GenuineVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; import org.jspecify.annotations.NullMarked; @NullMarked public interface MoveStreamFactory { + PlanningSolutionMetaModel getSolutionMetaModel(); + /** - * Start a {@link DataStream} of all instances of the sourceClass + * Start a {@link EnumeratingStream} of all instances of the sourceClass * that are known as {@link ProblemFactCollectionProperty problem facts} * or {@link PlanningEntity planning entities}. *

* If the sourceClass is a {@link PlanningEntity}, then it is automatically - * {@link UniDataStream#filter(UniDataFilter) filtered} to only contain entities + * {@link UniEnumeratingStream#filter(UniEnumeratingFilter) filtered} to only contain entities * which are not pinned. *

* If the sourceClass is a shadow entity (an entity without any genuine planning variables), @@ -30,7 +38,7 @@ public interface MoveStreamFactory { *

* This stream returns genuine entities regardless of whether they have any null genuine planning variables. * This stream returns shadow entities regardless of whether they are assigned to any genuine entity. - * They can easily be {@link UniDataStream#filter(UniDataFilter) filtered out}. + * They can easily be {@link UniEnumeratingStream#filter(UniEnumeratingFilter) filtered out}. * * @return A stream containing a tuple for each of the entities as described above. * @see PlanningPin An annotation to mark the entire entity as pinned. @@ -38,17 +46,17 @@ public interface MoveStreamFactory { * @see #forEachUnfiltered(Class, boolean) Specialized method exists to automatically include pinned entities as * well. */ - UniDataStream forEach(Class sourceClass, boolean includeNull); + UniEnumeratingStream forEach(Class sourceClass, boolean includeNull); /** - * Start a {@link DataStream} of all instances of the sourceClass + * Start a {@link EnumeratingStream} of all instances of the sourceClass * that are known as {@link ProblemFactCollectionProperty problem facts} * or {@link PlanningEntity planning entities}. * If the sourceClass is a genuine or shadow entity, * it returns instances regardless of their pinning status. * Otherwise as defined by {@link #forEach(Class, boolean)}. */ - UniDataStream forEachUnfiltered(Class sourceClass, boolean includeNull); + UniEnumeratingStream forEachUnfiltered(Class sourceClass, boolean includeNull); /** * Enumerate possible values for any given entity, @@ -57,9 +65,9 @@ public interface MoveStreamFactory { * If the variable allows unassigned values, the resulting stream will include a null value. * * @param variableMetaModel the meta model of the variable to enumerate - * @return data stream with all possible values of a given variable + * @return enumerating stream with all possible values of a given variable */ - default BiDataStream + default BiEnumeratingStream forEachEntityValuePair(GenuineVariableMetaModel variableMetaModel) { return forEachEntityValuePair(variableMetaModel, forEach(variableMetaModel.entity().type(), false)); } @@ -69,15 +77,15 @@ public interface MoveStreamFactory { * If the variable allows unassigned values, the resulting stream will include a null value. * * @param variableMetaModel the meta model of the variable to enumerate - * @param entityDataStream the data stream of entities to enumerate values for - * @return data stream with all possible values of a given variable + * @param entityEnumeratingStream the enumerating stream of entities to enumerate values for + * @return enumerating stream with all possible values of a given variable */ - BiDataStream forEachEntityValuePair( + BiEnumeratingStream forEachEntityValuePair( GenuineVariableMetaModel variableMetaModel, - UniDataStream entityDataStream); + UniEnumeratingStream entityEnumeratingStream); - UniMoveStream pick(UniDataStream dataStream); + UniSamplingStream pick(UniEnumeratingStream enumeratingStream); - BiMoveStream pick(BiDataStream dataStream); + BiSamplingStream pick(BiEnumeratingStream enumeratingStream); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/Neighborhood.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/Neighborhood.java new file mode 100644 index 0000000000..0c3e171f35 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/Neighborhood.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface Neighborhood { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodBuilder.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodBuilder.java new file mode 100644 index 0000000000..e0c906064a --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodBuilder.java @@ -0,0 +1,16 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi; + +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface NeighborhoodBuilder { + + PlanningSolutionMetaModel getSolutionMetaModel(); + + NeighborhoodBuilder add(MoveDefinition moveDefinition); + + Neighborhood build(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodProvider.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodProvider.java new file mode 100644 index 0000000000..0d1702f6d8 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodProvider.java @@ -0,0 +1,10 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface NeighborhoodProvider { + + Neighborhood defineNeighborhood(NeighborhoodBuilder builder); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodSession.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodSession.java new file mode 100644 index 0000000000..421aa752b8 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/NeighborhoodSession.java @@ -0,0 +1,7 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface NeighborhoodSession { +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/AbstractMove.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/AbstractMove.java similarity index 97% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/AbstractMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/AbstractMove.java index acc60ff016..82fde00559 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/AbstractMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/AbstractMove.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.List; @@ -22,6 +22,7 @@ abstract class AbstractMove implements Move { private static final char OPENING_PARENTHESES = '('; private static final char CLOSING_PARENTHESES = ')'; + @Override public final String describe() { var metaModels = variableMetaModels(); var substring = switch (metaModels.size()) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiMoveConstructor.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/BiMoveConstructor.java similarity index 78% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiMoveConstructor.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/BiMoveConstructor.java index 500aee5c17..123f7e9288 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/stream/BiMoveConstructor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/BiMoveConstructor.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.stream; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.preview.api.move.SolutionView; @@ -9,7 +9,7 @@ @NullMarked @FunctionalInterface public non-sealed interface BiMoveConstructor - extends MoveConstructor { + extends MoveConstructor { Move apply(SolutionView solutionView, @Nullable A a, @Nullable B b); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMove.java similarity index 97% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ChangeMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMove.java index 49d7292bb8..67beed6522 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMove.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Collection; import java.util.Collections; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinition.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinition.java new file mode 100644 index 0000000000..457405272e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinition.java @@ -0,0 +1,34 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; + +import java.util.Objects; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStreamFactory; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class ChangeMoveDefinition + implements MoveDefinition { + + private final PlanningVariableMetaModel variableMetaModel; + + public ChangeMoveDefinition(PlanningVariableMetaModel variableMetaModel) { + this.variableMetaModel = Objects.requireNonNull(variableMetaModel); + } + + @Override + public MoveStream build(MoveStreamFactory moveStreamFactory) { + var enumeratingStream = + moveStreamFactory.forEachEntityValuePair(variableMetaModel) + .filter((solutionView, entity, value) -> { + Value_ currentValue = solutionView.getValue(variableMetaModel, Objects.requireNonNull(entity)); + return !Objects.equals(currentValue, value); + }); + return moveStreamFactory.pick(enumeratingStream) + .asMove((solution, entity, value) -> Moves.change(Objects.requireNonNull(entity), value, variableMetaModel)); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/CompositeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/CompositeMove.java similarity index 94% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/CompositeMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/CompositeMove.java index 87eba91413..a005f995dc 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/CompositeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/CompositeMove.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Arrays; import java.util.Collection; @@ -13,7 +13,6 @@ import ai.timefold.solver.core.preview.api.move.Rebaser; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; /** * A CompositeMove is composed out of multiple other moves. @@ -73,7 +72,7 @@ public Collection extractPlanningEntities() { } @Override - public Collection<@Nullable Object> extractPlanningValues() { + public Collection extractPlanningValues() { Set values = CollectionUtils.newLinkedHashSet(moves.length * 2); for (Move move : moves) { values.addAll(move.extractPlanningValues()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListAssignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListAssignMove.java similarity index 97% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListAssignMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListAssignMove.java index 9d9dc21f4d..f31ac90282 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListAssignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListAssignMove.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Collection; import java.util.List; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMove.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListChangeMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMove.java index cec5628a44..dccb63691e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMove.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Collection; import java.util.Collections; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ListChangeMoveDefinition.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinition.java similarity index 87% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ListChangeMoveDefinition.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinition.java index 193b759925..dbe80affac 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ListChangeMoveDefinition.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinition.java @@ -1,13 +1,12 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Objects; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; -import ai.timefold.solver.core.impl.move.streams.maybeapi.DataJoiners; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.EnumeratingJoiners; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; import ai.timefold.solver.core.preview.api.domain.metamodel.PositionInList; @@ -40,7 +39,7 @@ public class ListChangeMoveDefinition implements MoveDefinition { private final PlanningListVariableMetaModel variableMetaModel; - private final BiDataFilter isValueInListFilter; + private final BiEnumeratingFilter isValueInListFilter; public ListChangeMoveDefinition(PlanningListVariableMetaModel variableMetaModel) { this.variableMetaModel = Objects.requireNonNull(variableMetaModel); @@ -56,7 +55,7 @@ public ListChangeMoveDefinition(PlanningListVariableMetaModel build(MoveStreamFactory moveStreamFactory) { + public MoveStream build(MoveStreamFactory moveStreamFactory) { // Stream with unpinned entities; // includes null if the variable allows unassigned values. var unpinnedEntities = @@ -70,7 +69,7 @@ public MoveProducer build(MoveStreamFactory moveStreamFact // Joins the two previous streams to create pairs of (entity, value), // eliminating values which do not match that entity's value range. // It maps these pairs to expected target positions in that entity's list variable. - var entityValuePairs = unpinnedEntities.join(unpinnedValues, DataJoiners.filtering(isValueInListFilter)) + var entityValuePairs = unpinnedEntities.join(unpinnedValues, EnumeratingJoiners.filtering(isValueInListFilter)) .map((solutionView, entity, value) -> { if (entity == null) { // Null entity means we need to unassign the value. return ElementPosition.unassigned(); @@ -85,11 +84,11 @@ public MoveProducer build(MoveStreamFactory moveStreamFact .distinct(); // Finally the stream of these positions is joined with the stream of all existing values, // filtering out those which would not result in a valid move. - var dataStream = moveStreamFactory.forEach(variableMetaModel.type(), false) - .join(entityValuePairs, DataJoiners.filtering(this::isValidChange)); + var enumeratingStream = moveStreamFactory.forEach(variableMetaModel.type(), false) + .join(entityValuePairs, EnumeratingJoiners.filtering(this::isValidChange)); // When picking from this stream, we decide what kind of move we need to create, // based on whether the value is assigned or unassigned. - return moveStreamFactory.pick(dataStream) + return moveStreamFactory.pick(enumeratingStream) .asMove((solutionView, value, targetPosition) -> { var currentPosition = solutionView.getPositionOf(variableMetaModel, Objects.requireNonNull(value)); if (targetPosition instanceof UnassignedElement) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListSwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMove.java similarity index 97% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListSwapMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMove.java index 90015eed67..1e37140337 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListSwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMove.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Arrays; import java.util.Collection; @@ -30,7 +30,7 @@ * @param the variable type, the type of the property with the {@link PlanningVariable} annotation */ @NullMarked -public final class ListSwapMove extends AbstractMove { +public class ListSwapMove extends AbstractMove { private final PlanningListVariableMetaModel variableMetaModel; private final Entity_ leftEntity; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ListSwapMoveDefinition.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinition.java similarity index 83% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ListSwapMoveDefinition.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinition.java index 8ab8ca194e..6c68011299 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/ListSwapMoveDefinition.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinition.java @@ -1,12 +1,11 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Objects; -import ai.timefold.solver.core.impl.move.streams.maybeapi.DataJoiners; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.EnumeratingJoiners; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; import ai.timefold.solver.core.preview.api.domain.metamodel.PositionInList; import ai.timefold.solver.core.preview.api.move.SolutionView; @@ -24,12 +23,12 @@ public ListSwapMoveDefinition(PlanningListVariableMetaModel build(MoveStreamFactory moveStreamFactory) { + public MoveStream build(MoveStreamFactory moveStreamFactory) { var assignedValueStream = moveStreamFactory.forEach(variableMetaModel.type(), false) .filter((solutionView, value) -> solutionView.getPositionOf(variableMetaModel, value) instanceof PositionInList); var validAssignedValuePairStream = assignedValueStream.join(assignedValueStream, - DataJoiners.filtering((SolutionView solutionView, Value_ leftValue, + EnumeratingJoiners.filtering((SolutionView solutionView, Value_ leftValue, Value_ rightValue) -> !Objects.equals(leftValue, rightValue))); // Ensure unique pairs; without demanding PlanningId, this becomes tricky. // Convert values to their locations in list. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListUnassignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListUnassignMove.java similarity index 97% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListUnassignMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListUnassignMove.java index d7bf4e1c90..40935b6a22 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/ListUnassignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListUnassignMove.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Collection; import java.util.Collections; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/MoveConstructor.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/MoveConstructor.java new file mode 100644 index 0000000000..07912fb629 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/MoveConstructor.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public sealed interface MoveConstructor permits BiMoveConstructor { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/Moves.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/Moves.java similarity index 98% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/Moves.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/Moves.java index 03d6b3fa6e..6eafe9264d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/Moves.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/Moves.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Arrays; import java.util.Collections; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/SwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMove.java similarity index 98% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/SwapMove.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMove.java index 1495826976..374e45de0f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/SwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMove.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.ArrayList; import java.util.Collection; @@ -120,7 +120,7 @@ public Collection extractPlanningEntities() { } @Override - public Collection<@Nullable Object> extractPlanningValues() { + public Collection extractPlanningValues() { return new LinkedHashSet<>(getCachedValues()); // Not using Set.of(), as values may be null. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/SwapMoveDefinition.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinition.java similarity index 87% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/SwapMoveDefinition.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinition.java index 533b8316fb..82d04747a5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/SwapMoveDefinition.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinition.java @@ -1,16 +1,15 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; -import static ai.timefold.solver.core.impl.move.streams.maybeapi.DataJoiners.filtering; +import static ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.EnumeratingJoiners.filtering; import java.util.List; import java.util.Objects; import java.util.stream.Stream; import ai.timefold.solver.core.impl.domain.solution.descriptor.DefaultPlanningVariableMetaModel; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProducer; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStreamFactory; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningEntityMetaModel; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel; @@ -59,9 +58,9 @@ public SwapMoveDefinition(List build(MoveStreamFactory moveStreamFactory) { + public MoveStream build(MoveStreamFactory moveStreamFactory) { var entityType = entityMetaModel.type(); - var dataStream = moveStreamFactory.forEach(entityType, false) + var enumeratingStream = moveStreamFactory.forEach(entityType, false) .join(entityType, filtering((SolutionView solutionView, Entity_ leftEntity, Entity_ rightEntity) -> { if (leftEntity == rightEntity) { @@ -91,7 +90,7 @@ public MoveProducer build(MoveStreamFactory moveStreamFact .map((solutionView, leftEntity, rightEntity) -> new UniquePair<>(leftEntity, rightEntity)) .distinct() .map((solutionView, pair) -> pair.first(), (solutionView, pair) -> pair.second()); - return moveStreamFactory.pick(dataStream) + return moveStreamFactory.pick(enumeratingStream) .asMove((solutionView, leftEntity, rightEntity) -> Moves.swap(leftEntity, rightEntity, variableMetaModelList.toArray(new PlanningVariableMetaModel[0]))); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/UniquePair.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/UniquePair.java similarity index 91% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/UniquePair.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/UniquePair.java index 1feac5c9d8..ef19a1cb83 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/definitions/UniquePair.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/UniquePair.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import java.util.Objects; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/package-info.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/package-info.java similarity index 75% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/package-info.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/package-info.java index a499da6977..9bf344530e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/package-info.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/package-info.java @@ -2,4 +2,4 @@ * Should eventually become {@code ai.timefold.solver.core.preview.api.move.streams}. * TODO move this to the preview package, when we have the basic generic moves working */ -package ai.timefold.solver.core.impl.move.streams.maybeapi; \ No newline at end of file +package ai.timefold.solver.core.impl.neighborhood.maybeapi; \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/BiEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/BiEnumeratingStream.java new file mode 100644 index 0000000000..795210e356 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/BiEnumeratingStream.java @@ -0,0 +1,52 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingMapper; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingMapper; +import ai.timefold.solver.core.preview.api.move.SolutionView; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface BiEnumeratingStream extends EnumeratingStream { + + /** + * Exhaustively test each fact against the {@link BiEnumeratingFilter} + * and match if {@link BiEnumeratingFilter#test(SolutionView, Object, Object)} returns true. + */ + BiEnumeratingStream filter(BiEnumeratingFilter filter); + + // ************************************************************************ + // Operations with duplicate tuple possibility + // ************************************************************************ + + /** + * As defined by {@link UniEnumeratingStream#map(UniEnumeratingMapper)}. + * + *

+ * Use with caution, + * as the increased memory allocation rates coming from tuple creation may negatively affect performance. + * + * @param mapping function to convert the original tuple into the new tuple + * @param the type of the only fact in the resulting {@link UniEnumeratingStream}'s tuple + */ + UniEnumeratingStream map(BiEnumeratingMapper mapping); + + /** + * As defined by {@link #map(BiEnumeratingMapper)}, only resulting in {@link BiEnumeratingStream}. + * + * @param mappingA function to convert the original tuple into the first fact of a new tuple + * @param mappingB function to convert the original tuple into the second fact of a new tuple + * @param the type of the first fact in the resulting {@link BiEnumeratingStream}'s tuple + * @param the type of the first fact in the resulting {@link BiEnumeratingStream}'s tuple + */ + BiEnumeratingStream map( + BiEnumeratingMapper mappingA, + BiEnumeratingMapper mappingB); + + /** + * As defined by {@link UniEnumeratingStream#distinct()}. + */ + BiEnumeratingStream distinct(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/DataJoiners.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/EnumeratingJoiners.java similarity index 81% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/DataJoiners.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/EnumeratingJoiners.java index aee0735404..30f4323891 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/DataJoiners.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/EnumeratingJoiners.java @@ -1,30 +1,31 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating; import java.util.function.BiPredicate; import java.util.function.Function; import ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream; import ai.timefold.solver.core.impl.bavet.common.joiner.JoinerType; -import ai.timefold.solver.core.impl.move.streams.dataset.joiner.DefaultBiDataJoiner; -import ai.timefold.solver.core.impl.move.streams.dataset.joiner.FilteringBiDataJoiner; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingJoiner; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner.DefaultBiEnumeratingJoiner; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner.FilteringBiEnumeratingJoiner; import ai.timefold.solver.core.impl.util.ConstantLambdaUtils; import org.jspecify.annotations.NullMarked; /** - * Creates an {@link BiDataJoiner}, ... instance - * for use in {@link UniDataStream#join(Class, BiDataJoiner[])}, ... + * Creates an {@link BiEnumeratingJoiner}, ... instance + * for use in {@link UniEnumeratingStream#join(Class, BiEnumeratingJoiner[])}, ... */ @NullMarked -public final class DataJoiners { +public final class EnumeratingJoiners { /** * As defined by {@link #equal(Function)} with {@link Function#identity()} as the argument. * * @param the type of both objects */ - public static BiDataJoiner equal() { + public static BiEnumeratingJoiner equal() { return equal(ConstantLambdaUtils.identity()); } @@ -35,7 +36,7 @@ public static BiDataJoiner equal() { * @param the type of the property to compare * @param mapping mapping function to apply to both A and B */ - public static BiDataJoiner equal(Function mapping) { + public static BiEnumeratingJoiner equal(Function mapping) { return equal(mapping, mapping); } @@ -51,9 +52,9 @@ public static BiDataJoiner equal(Function map * @param leftMapping mapping function to apply to A * @param rightMapping mapping function to apply to B */ - public static BiDataJoiner equal(Function leftMapping, + public static BiEnumeratingJoiner equal(Function leftMapping, Function rightMapping) { - return new DefaultBiDataJoiner<>(leftMapping, JoinerType.EQUAL, rightMapping); + return new DefaultBiEnumeratingJoiner<>(leftMapping, JoinerType.EQUAL, rightMapping); } /** @@ -63,7 +64,7 @@ public static BiDataJoiner equal(Function * @param the type of both objects * @param the type of the property to compare */ - public static > BiDataJoiner + public static > BiEnumeratingJoiner lessThan(Function mapping) { return lessThan(mapping, mapping); } @@ -82,9 +83,9 @@ public static BiDataJoiner equal(Function * @param the type of object on the right * @param the type of the property to compare */ - public static > BiDataJoiner lessThan( + public static > BiEnumeratingJoiner lessThan( Function leftMapping, Function rightMapping) { - return new DefaultBiDataJoiner<>(leftMapping, JoinerType.LESS_THAN, rightMapping); + return new DefaultBiEnumeratingJoiner<>(leftMapping, JoinerType.LESS_THAN, rightMapping); } /** @@ -94,7 +95,7 @@ public static > BiDataJoiner * @param the type of both objects * @param the type of the property to compare */ - public static > BiDataJoiner lessThanOrEqual( + public static > BiEnumeratingJoiner lessThanOrEqual( Function mapping) { return lessThanOrEqual(mapping, mapping); } @@ -114,9 +115,9 @@ public static > BiDataJoiner le * @param the type of object on the right * @param the type of the property to compare */ - public static > BiDataJoiner lessThanOrEqual( + public static > BiEnumeratingJoiner lessThanOrEqual( Function leftMapping, Function rightMapping) { - return new DefaultBiDataJoiner<>(leftMapping, JoinerType.LESS_THAN_OR_EQUAL, rightMapping); + return new DefaultBiEnumeratingJoiner<>(leftMapping, JoinerType.LESS_THAN_OR_EQUAL, rightMapping); } /** @@ -126,7 +127,7 @@ public static > BiDataJoiner * @param the type of both objects * @param the type of the property to compare */ - public static > BiDataJoiner greaterThan( + public static > BiEnumeratingJoiner greaterThan( Function mapping) { return greaterThan(mapping, mapping); } @@ -145,9 +146,9 @@ public static > BiDataJoiner gr * @param the type of object on the right * @param the type of the property to compare */ - public static > BiDataJoiner greaterThan( + public static > BiEnumeratingJoiner greaterThan( Function leftMapping, Function rightMapping) { - return new DefaultBiDataJoiner<>(leftMapping, JoinerType.GREATER_THAN, rightMapping); + return new DefaultBiEnumeratingJoiner<>(leftMapping, JoinerType.GREATER_THAN, rightMapping); } /** @@ -157,7 +158,7 @@ public static > BiDataJoiner * @param the type of both objects * @param the type of the property to compare */ - public static > BiDataJoiner greaterThanOrEqual( + public static > BiEnumeratingJoiner greaterThanOrEqual( Function mapping) { return greaterThanOrEqual(mapping, mapping); } @@ -177,9 +178,9 @@ public static > BiDataJoiner gr * @param the type of object on the right * @param the type of the property to compare */ - public static > BiDataJoiner greaterThanOrEqual( + public static > BiEnumeratingJoiner greaterThanOrEqual( Function leftMapping, Function rightMapping) { - return new DefaultBiDataJoiner<>(leftMapping, JoinerType.GREATER_THAN_OR_EQUAL, rightMapping); + return new DefaultBiEnumeratingJoiner<>(leftMapping, JoinerType.GREATER_THAN_OR_EQUAL, rightMapping); } /** @@ -193,8 +194,8 @@ public static > BiDataJoiner * @param type of the first fact in the tuple * @param type of the second fact in the tuple */ - public static BiDataJoiner filtering(BiDataFilter filter) { - return new FilteringBiDataJoiner<>(filter); + public static BiEnumeratingJoiner filtering(BiEnumeratingFilter filter) { + return new FilteringBiEnumeratingJoiner<>(filter); } /** @@ -212,7 +213,7 @@ public static BiDataJoiner filtering(BiDataFilter the type of both the first and second argument * @param the type used to define the interval, comparable */ - public static > BiDataJoiner overlapping( + public static > BiEnumeratingJoiner overlapping( Function startMapping, Function endMapping) { return overlapping(startMapping, endMapping, startMapping, endMapping); } @@ -228,14 +229,14 @@ public static > BiDataJoiner ov * @param the type of the second argument * @param the type used to define the interval, comparable */ - public static > BiDataJoiner overlapping( + public static > BiEnumeratingJoiner overlapping( Function leftStartMapping, Function leftEndMapping, Function rightStartMapping, Function rightEndMapping) { - return DataJoiners.lessThan(leftStartMapping, rightEndMapping) - .and(DataJoiners.greaterThan(leftEndMapping, rightStartMapping)); + return EnumeratingJoiners.lessThan(leftStartMapping, rightEndMapping) + .and(EnumeratingJoiners.greaterThan(leftEndMapping, rightStartMapping)); } - private DataJoiners() { + private EnumeratingJoiners() { } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/EnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/EnumeratingStream.java new file mode 100644 index 0000000000..cc99a06f8e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/EnumeratingStream.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface EnumeratingStream { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/UniEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/UniEnumeratingStream.java new file mode 100644 index 0000000000..3d24af4365 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/UniEnumeratingStream.java @@ -0,0 +1,439 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingJoiner; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingMapper; +import ai.timefold.solver.core.preview.api.move.SolutionView; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface UniEnumeratingStream extends EnumeratingStream { + + /** + * Exhaustively test each fact against the {@link UniEnumeratingFilter} + * and match if {@link UniEnumeratingFilter#test(SolutionView, Object)} returns true. + */ + UniEnumeratingStream filter(UniEnumeratingFilter filter); + + /** + * As defined by {@link #join(UniEnumeratingStream, BiEnumeratingJoiner[])}, with the array being empty. + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(UniEnumeratingStream otherStream) { + return join(otherStream, new BiEnumeratingJoiner[0]); + } + + /** + * As defined by {@link #join(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner) { + return join(otherStream, new BiEnumeratingJoiner[] { joiner }); + } + + /** + * As defined by {@link #join(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2) { + return join(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2 }); + } + + /** + * As defined by {@link #join(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3) { + return join(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3 }); + } + + /** + * As defined by {@link #join(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3, BiEnumeratingJoiner joiner4) { + return join(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3, joiner4 }); + } + + /** + * Create a new {@link BiEnumeratingStream} for every combination of A and B for which the {@link BiEnumeratingJoiner} + * is true (for the properties it extracts from both facts). + *

+ * Important: Joining is faster and more scalable than a {@link BiEnumeratingStream#filter(BiEnumeratingFilter) filter}, + * because it applies hashing and/or indexing on the properties, + * so it doesn't create nor checks every combination of A and B. + * + * @param the type of the second matched fact + * @return a stream that matches every combination of A and B for which the {@link BiEnumeratingJoiner} is true + */ + BiEnumeratingStream join(UniEnumeratingStream otherStream, + BiEnumeratingJoiner... joiners); + + /** + * As defined by {@link #join(Class, BiEnumeratingJoiner[])}, with the array being empty. + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(Class otherClass) { + return join(otherClass, new BiEnumeratingJoiner[0]); + } + + /** + * As defined by {@link #join(Class, BiEnumeratingJoiner[])} + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(Class otherClass, BiEnumeratingJoiner joiner) { + return join(otherClass, new BiEnumeratingJoiner[] { joiner }); + } + + /** + * As defined by {@link #join(Class, BiEnumeratingJoiner[])} + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2) { + return join(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2 }); + } + + /** + * As defined by {@link #join(Class, BiEnumeratingJoiner[])} + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, + BiEnumeratingJoiner joiner3) { + return join(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3 }); + } + + /** + * As defined by {@link #join(Class, BiEnumeratingJoiner[])} + */ + @SuppressWarnings("unchecked") + default BiEnumeratingStream join(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, + BiEnumeratingJoiner joiner3, BiEnumeratingJoiner joiner4) { + return join(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3, joiner4 }); + } + + /** + * Create a new {@link BiEnumeratingStream} for every combination of A and B + * for which the {@link BiEnumeratingJoiner} is true (for the properties it extracts from both facts). + * The stream will include all facts or entities of the given class, + * regardless of their pinning status. + *

+ * Important: Joining is faster and more scalable than a {@link BiEnumeratingStream#filter(BiEnumeratingFilter) filter}, + * because it applies hashing and/or indexing on the properties, + * so it doesn't create nor checks every combination of A and B. + * + * @param the type of the second matched fact + * @return a stream that matches every combination of A and B for which the {@link BiEnumeratingJoiner} is true + */ + BiEnumeratingStream join(Class otherClass, BiEnumeratingJoiner... joiners); + + /** + * As defined by {@link #ifExists(UniEnumeratingStream, BiEnumeratingJoiner[])}, with the array being empty. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(UniEnumeratingStream otherStream) { + return ifExists(otherStream, new BiEnumeratingJoiner[0]); + } + + /** + * As defined by {@link #ifExists(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner) { + return ifExists(otherStream, new BiEnumeratingJoiner[] { joiner }); + } + + /** + * As defined by {@link #ifExists(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2) { + return ifExists(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2 }); + } + + /** + * As defined by {@link #ifExists(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3) { + return ifExists(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3 }); + } + + /** + * As defined by {@link #ifExists(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3, BiEnumeratingJoiner joiner4) { + return ifExists(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3, joiner4 }); + } + + /** + * Create a new {@link UniEnumeratingStream} for every A where B exists for which all {@link BiEnumeratingJoiner}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 BiEnumeratingJoiner}s are true + */ + @SuppressWarnings("unchecked") + UniEnumeratingStream ifExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner... joiners); + + /** + * As defined by {@link #ifExists(Class, BiEnumeratingJoiner[])}, with the array being empty. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(Class otherClass) { + return ifExists(otherClass, new BiEnumeratingJoiner[0]); + } + + /** + * As defined by {@link #ifExists(Class, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(Class otherClass, BiEnumeratingJoiner joiner) { + return ifExists(otherClass, new BiEnumeratingJoiner[] { joiner }); + } + + /** + * As defined by {@link #ifExists(Class, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2) { + return ifExists(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2 }); + } + + /** + * As defined by {@link #ifExists(Class, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, + BiEnumeratingJoiner joiner3) { + return ifExists(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3 }); + } + + /** + * As defined by {@link #ifExists(Class, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifExists(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3, BiEnumeratingJoiner joiner4) { + return ifExists(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3, joiner4 }); + } + + /** + * Create a new {@link UniEnumeratingStream} for every A where B exists for which all {@link BiEnumeratingJoiner}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 BiEnumeratingJoiner}s are true + */ + @SuppressWarnings("unchecked") + UniEnumeratingStream ifExists(Class otherClass, BiEnumeratingJoiner... joiners); + + /** + * As defined by {@link #ifNotExists(UniEnumeratingStream, BiEnumeratingJoiner[])}, with the array being empty. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(UniEnumeratingStream otherStream) { + return ifNotExists(otherStream, new BiEnumeratingJoiner[0]); + } + + /** + * As defined by {@link #ifNotExists(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner) { + return ifNotExists(otherStream, new BiEnumeratingJoiner[] { joiner }); + } + + /** + * As defined by {@link #ifNotExists(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2) { + return ifNotExists(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2 }); + } + + /** + * As defined by {@link #ifNotExists(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3) { + return ifNotExists(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3 }); + } + + /** + * As defined by {@link #ifNotExists(UniEnumeratingStream, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3, BiEnumeratingJoiner joiner4) { + return ifNotExists(otherStream, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3, joiner4 }); + } + + /** + * Create a new {@link UniEnumeratingStream} for every A where B does not exist for which the {@link BiEnumeratingJoiner}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 BiEnumeratingJoiner}s are true + */ + @SuppressWarnings("unchecked") + UniEnumeratingStream ifNotExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner... joiners); + + /** + * As defined by {@link #ifNotExists(Class, BiEnumeratingJoiner[])}, with the array being empty. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(Class otherClass) { + return ifNotExists(otherClass, new BiEnumeratingJoiner[0]); + } + + /** + * As defined by {@link #ifNotExists(Class, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(Class otherClass, BiEnumeratingJoiner joiner) { + return ifNotExists(otherClass, new BiEnumeratingJoiner[] { joiner }); + } + + /** + * As defined by {@link #ifNotExists(Class, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2) { + return ifNotExists(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2 }); + } + + /** + * As defined by {@link #ifNotExists(Class, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3) { + return ifNotExists(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3 }); + } + + /** + * As defined by {@link #ifNotExists(Class, BiEnumeratingJoiner[])}. + */ + @SuppressWarnings("unchecked") + default UniEnumeratingStream ifNotExists(Class otherClass, BiEnumeratingJoiner joiner1, + BiEnumeratingJoiner joiner2, BiEnumeratingJoiner joiner3, BiEnumeratingJoiner joiner4) { + return ifNotExists(otherClass, new BiEnumeratingJoiner[] { joiner1, joiner2, joiner3, joiner4 }); + } + + /** + * Create a new {@link UniEnumeratingStream} for every A where B does not exist for which the {@link BiEnumeratingJoiner}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 BiEnumeratingJoiner}s are true + */ + @SuppressWarnings("unchecked") + UniEnumeratingStream ifNotExists(Class otherClass, BiEnumeratingJoiner... joiners); + + // ************************************************************************ + // Operations with duplicate tuple possibility + // ************************************************************************ + + /** + * Transforms the stream in such a way that tuples are remapped using the given function. + * This may produce a stream with duplicate tuples. + * See {@link #distinct()} for details. + *

+ * There are several recommendations for implementing the mapping function: + * + *

+ * + *

+ * Simple example: assuming a enumerating stream of tuples of {@code Person}s + * {@code [Ann(age = 20), Beth(age = 25), Cathy(age = 30)]}, + * calling {@code map(Person::getAge)} on such stream will produce a stream of {@link Integer}s + * {@code [20, 25, 30]}, + * + *

+ * Example with a non-bijective mapping function: assuming a enumerating stream of tuples of {@code Person}s + * {@code [Ann(age = 20), Beth(age = 25), Cathy(age = 30), David(age = 30), Eric(age = 20)]}, + * calling {@code map(Person::getAge)} on such stream will produce a stream of {@link Integer}s + * {@code [20, 25, 30, 30, 20]}. + * + *

+ * Use with caution, + * as the increased memory allocation rates coming from tuple creation may negatively affect performance. + * + * @param mapping function to convert the original tuple into the new tuple + * @param the type of the only fact in the resulting {@link UniEnumeratingStream}'s tuple + */ + UniEnumeratingStream map(UniEnumeratingMapper mapping); + + /** + * As defined by {@link #map(UniEnumeratingMapper)}, only resulting in {@link BiEnumeratingStream}. + * + * @param mappingA function to convert the original tuple into the first fact of a new tuple + * @param mappingB function to convert the original tuple into the second fact of a new tuple + * @param the type of the first fact in the resulting {@link BiEnumeratingStream}'s tuple + * @param the type of the first fact in the resulting {@link BiEnumeratingStream}'s tuple + */ + BiEnumeratingStream map( + UniEnumeratingMapper mappingA, + UniEnumeratingMapper mappingB); + + /** + * Transforms the stream in such a way that all the tuples going through it are distinct. + * (No two tuples will {@link Object#equals(Object) equal}.) + * + *

+ * By default, tuples going through an enumerating stream are distinct. + * However, operations such as {@link #map(UniEnumeratingMapper)} may create a stream which breaks that promise. + * By calling this method on such a stream, + * duplicate copies of the same tuple will be omitted at a performance cost. + */ + UniEnumeratingStream distinct(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataFilter.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingFilter.java similarity index 54% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataFilter.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingFilter.java index 50401ea661..2f2ee0fb3a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataFilter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingFilter.java @@ -1,16 +1,16 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function; import java.util.function.BiPredicate; import ai.timefold.solver.core.api.function.TriPredicate; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiDataStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.BiEnumeratingStream; import ai.timefold.solver.core.preview.api.move.SolutionView; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; /** - * A filter that can be applied to a {@link BiDataStream} to filter out pairs of data, + * A filter that can be applied to a {@link BiEnumeratingStream} to filter out pairs of data, * optionally using {@link SolutionView} to query for solution state. * * @param the type of the solution @@ -18,14 +18,15 @@ * @param the type of the second parameter */ @NullMarked -public interface BiDataFilter extends TriPredicate, A, B> { +public interface BiEnumeratingFilter extends TriPredicate, A, B> { @Override boolean test(SolutionView solutionView, @Nullable A a, @Nullable B b); @Override - default BiDataFilter and(TriPredicate, ? super A, ? super B> other) { - return (BiDataFilter) TriPredicate.super.and(other); + default BiEnumeratingFilter + and(TriPredicate, ? super A, ? super B> other) { + return (BiEnumeratingFilter) TriPredicate.super.and(other); } default BiPredicate toBiPredicate(SolutionView solutionView) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingJoiner.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingJoiner.java new file mode 100644 index 0000000000..e4f5375a57 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingJoiner.java @@ -0,0 +1,19 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function; + +import ai.timefold.solver.core.api.score.stream.Joiners; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; + +import org.jspecify.annotations.NullMarked; + +/** + * Created with {@link Joiners}. + * Used by {@link UniEnumeratingStream#join(Class, BiEnumeratingJoiner[])}, ... + * + * @see Joiners + */ +@NullMarked +public interface BiEnumeratingJoiner { + + BiEnumeratingJoiner and(BiEnumeratingJoiner otherJoiner); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataMapper.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingMapper.java similarity index 64% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataMapper.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingMapper.java index 7d8ddc1346..2a906defe9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/BiDataMapper.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/BiEnumeratingMapper.java @@ -1,16 +1,16 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function; import java.util.function.BiFunction; import ai.timefold.solver.core.api.function.TriFunction; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiDataStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.BiEnumeratingStream; import ai.timefold.solver.core.preview.api.move.SolutionView; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; /** - * A mapping function that can be applied to {@link BiDataStream} to transform data, + * A mapping function that can be applied to {@link BiEnumeratingStream} to transform data, * optionally using {@link SolutionView} to query for solution state. * * @param the type of the solution @@ -18,7 +18,7 @@ * @param the type of the second parameter */ @NullMarked -public interface BiDataMapper extends TriFunction, A, B, Result_> { +public interface BiEnumeratingMapper extends TriFunction, A, B, Result_> { @Override Result_ apply(SolutionView solutionSolutionView, @Nullable A a, @Nullable B b); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/UniDataFilter.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/UniEnumeratingFilter.java similarity index 52% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/UniDataFilter.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/UniEnumeratingFilter.java index 2cda9b79aa..058e094d2a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/UniDataFilter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/UniEnumeratingFilter.java @@ -1,30 +1,30 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function; import java.util.function.BiPredicate; import java.util.function.Predicate; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; import ai.timefold.solver.core.preview.api.move.SolutionView; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; /** - * A filter that can be applied to a {@link UniDataStream} to filter out pairs of data, + * A filter that can be applied to a {@link UniEnumeratingStream} to filter out pairs of data, * optionally using {@link SolutionView} to query for solution state. * * @param the type of the solution * @param the type of the first parameter */ @NullMarked -public interface UniDataFilter extends BiPredicate, A> { +public interface UniEnumeratingFilter extends BiPredicate, A> { @Override boolean test(SolutionView solutionView, @Nullable A a); @Override - default UniDataFilter and(BiPredicate, ? super A> other) { - return (UniDataFilter) BiPredicate.super.and(other); + default UniEnumeratingFilter and(BiPredicate, ? super A> other) { + return (UniEnumeratingFilter) BiPredicate.super.and(other); } default Predicate toPredicate(SolutionView solutionView) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/UniDataMapper.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/UniEnumeratingMapper.java similarity index 61% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/UniDataMapper.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/UniEnumeratingMapper.java index 81358cad1c..eaa6d0ba8d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/UniDataMapper.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/function/UniEnumeratingMapper.java @@ -1,23 +1,23 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function; import java.util.function.BiFunction; import java.util.function.Function; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; import ai.timefold.solver.core.preview.api.move.SolutionView; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; /** - * A mapping function that can be applied to {@link UniDataStream} to transform data, + * A mapping function that can be applied to {@link UniEnumeratingStream} to transform data, * optionally using {@link SolutionView} to query for solution state. * * @param the type of the solution * @param the type of the first parameter */ @NullMarked -public interface UniDataMapper extends BiFunction, A, Result_> { +public interface UniEnumeratingMapper extends BiFunction, A, Result_> { @Override Result_ apply(SolutionView solutionSolutionView, @Nullable A a); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/BiSamplingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/BiSamplingStream.java new file mode 100644 index 0000000000..8b366c8a96 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/BiSamplingStream.java @@ -0,0 +1,13 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.BiMoveConstructor; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface BiSamplingStream extends SamplingStream { + + MoveStream asMove(BiMoveConstructor moveConstructor); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/SamplingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/SamplingStream.java new file mode 100644 index 0000000000..91c3868439 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/SamplingStream.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface SamplingStream { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/UniSamplingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/UniSamplingStream.java new file mode 100644 index 0000000000..5eef8c1c0e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/sampling/UniSamplingStream.java @@ -0,0 +1,19 @@ +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling; + +import java.util.function.BiPredicate; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface UniSamplingStream extends SamplingStream { + + default BiSamplingStream pick(UniEnumeratingStream uniEnumeratingStream) { + return pick(uniEnumeratingStream, (a, b) -> true); + } + + BiSamplingStream pick(UniEnumeratingStream uniEnumeratingStream, + BiPredicate filter); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/FromBiUniMoveProducer.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/FromBiUniMoveStream.java similarity index 57% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/FromBiUniMoveProducer.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/FromBiUniMoveStream.java index 9906fe055e..08f9416b23 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/FromBiUniMoveProducer.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/FromBiUniMoveStream.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams; +package ai.timefold.solver.core.impl.neighborhood.move; import java.util.Iterator; import java.util.NoSuchElementException; @@ -8,10 +8,11 @@ import java.util.function.Supplier; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.bi.BiDataset; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveConstructor; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.BiMoveConstructor; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultNeighborhoodSession; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi.BiDataset; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.preview.api.move.SolutionView; @@ -19,24 +20,24 @@ import org.jspecify.annotations.Nullable; @NullMarked -public final class FromBiUniMoveProducer implements InnerMoveProducer { +public final class FromBiUniMoveStream implements InnerMoveStream { private final BiDataset aDataset; private final BiMoveConstructor moveConstructor; - public FromBiUniMoveProducer(BiDataset aDataset, BiMoveConstructor moveConstructor) { + public FromBiUniMoveStream(BiDataset aDataset, BiMoveConstructor moveConstructor) { this.aDataset = Objects.requireNonNull(aDataset); this.moveConstructor = Objects.requireNonNull(moveConstructor); } @Override - public MoveIterable getMoveIterable(MoveStreamSession moveStreamSession) { - return new InnerMoveIterable((DefaultMoveStreamSession) moveStreamSession); + public MoveIterable getMoveIterable(NeighborhoodSession neighborhoodSession) { + return new InnerMoveIterable((DefaultNeighborhoodSession) neighborhoodSession); } @Override - public void collectActiveDataStreams(Set> activeDataStreamSet) { - aDataset.collectActiveDataStreams(activeDataStreamSet); + public void collectActiveEnumeratingStreams(Set> enumeratingStreamSet) { + aDataset.collectActiveEnumeratingStreams(enumeratingStreamSet); } @NullMarked @@ -49,16 +50,16 @@ private final class InnerMoveIterator implements Iterator> { private @Nullable Move nextMove; private @Nullable Iterator> iterator; - public InnerMoveIterator(DefaultMoveStreamSession moveStreamSession) { - var aInstance = moveStreamSession.getDatasetInstance(aDataset); + public InnerMoveIterator(DefaultNeighborhoodSession neighborhoodSession) { + var aInstance = neighborhoodSession.getDatasetInstance(aDataset); this.iteratorSupplier = aInstance::iterator; - this.solutionView = moveStreamSession.getSolutionView(); + this.solutionView = neighborhoodSession.getSolutionView(); } - public InnerMoveIterator(DefaultMoveStreamSession moveStreamSession, Random random) { - var aInstance = moveStreamSession.getDatasetInstance(aDataset); + public InnerMoveIterator(DefaultNeighborhoodSession neighborhoodSession, Random random) { + var aInstance = neighborhoodSession.getDatasetInstance(aDataset); this.iteratorSupplier = () -> aInstance.iterator(random); - this.solutionView = moveStreamSession.getSolutionView(); + this.solutionView = neighborhoodSession.getSolutionView(); } @Override @@ -102,20 +103,20 @@ private interface IteratorSupplier extends Supplier @NullMarked private final class InnerMoveIterable implements MoveIterable { - private final DefaultMoveStreamSession moveStreamSession; + private final DefaultNeighborhoodSession neighborhoodSession; - public InnerMoveIterable(DefaultMoveStreamSession moveStreamSession) { - this.moveStreamSession = Objects.requireNonNull(moveStreamSession); + public InnerMoveIterable(DefaultNeighborhoodSession neighborhoodSession) { + this.neighborhoodSession = Objects.requireNonNull(neighborhoodSession); } @Override public Iterator> iterator() { - return new InnerMoveIterator(moveStreamSession); + return new InnerMoveIterator(neighborhoodSession); } @Override public Iterator> iterator(Random random) { - return new InnerMoveIterator(moveStreamSession, random); + return new InnerMoveIterator(neighborhoodSession, random); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/FromUniBiMoveProducer.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/FromUniBiMoveStream.java similarity index 65% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/FromUniBiMoveProducer.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/FromUniBiMoveStream.java index 470ac68838..55457ffc3f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/FromUniBiMoveProducer.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/FromUniBiMoveStream.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams; +package ai.timefold.solver.core.impl.neighborhood.move; import java.util.Iterator; import java.util.NoSuchElementException; @@ -9,10 +9,11 @@ import java.util.function.Supplier; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDataset; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.BiMoveConstructor; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.BiMoveConstructor; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultNeighborhoodSession; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.UniDataset; import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.preview.api.move.SolutionView; @@ -20,14 +21,14 @@ import org.jspecify.annotations.Nullable; @NullMarked -public final class FromUniBiMoveProducer implements InnerMoveProducer { +public final class FromUniBiMoveStream implements InnerMoveStream { private final UniDataset aDataset; private final UniDataset bDataset; private final BiMoveConstructor moveConstructor; private final BiPredicate filter; - public FromUniBiMoveProducer(UniDataset aDataset, UniDataset bDataset, BiPredicate filter, + public FromUniBiMoveStream(UniDataset aDataset, UniDataset bDataset, BiPredicate filter, BiMoveConstructor moveConstructor) { this.aDataset = Objects.requireNonNull(aDataset); this.bDataset = Objects.requireNonNull(bDataset); @@ -36,14 +37,14 @@ public FromUniBiMoveProducer(UniDataset aDataset, UniDataset getMoveIterable(MoveStreamSession moveStreamSession) { - return new BiMoveIterable((DefaultMoveStreamSession) moveStreamSession); + public MoveIterable getMoveIterable(NeighborhoodSession neighborhoodSession) { + return new BiMoveIterable((DefaultNeighborhoodSession) neighborhoodSession); } @Override - public void collectActiveDataStreams(Set> activeDataStreamSet) { - aDataset.collectActiveDataStreams(activeDataStreamSet); - bDataset.collectActiveDataStreams(activeDataStreamSet); + public void collectActiveEnumeratingStreams(Set> enumeratingStreamSet) { + aDataset.collectActiveEnumeratingStreams(enumeratingStreamSet); + bDataset.collectActiveEnumeratingStreams(enumeratingStreamSet); } private final class BiMoveIterator implements Iterator> { @@ -58,20 +59,20 @@ private final class BiMoveIterator implements Iterator> { private @Nullable Iterator> bIterator; private @Nullable A currentA; - public BiMoveIterator(DefaultMoveStreamSession moveStreamSession) { - var aInstance = moveStreamSession.getDatasetInstance(aDataset); + public BiMoveIterator(DefaultNeighborhoodSession neighborhoodSession) { + var aInstance = neighborhoodSession.getDatasetInstance(aDataset); this.aIteratorSupplier = aInstance::iterator; - var bInstance = moveStreamSession.getDatasetInstance(bDataset); + var bInstance = neighborhoodSession.getDatasetInstance(bDataset); this.bIteratorSupplier = bInstance::iterator; - this.solutionView = moveStreamSession.getSolutionView(); + this.solutionView = neighborhoodSession.getSolutionView(); } - public BiMoveIterator(DefaultMoveStreamSession moveStreamSession, Random random) { - var aInstance = moveStreamSession.getDatasetInstance(aDataset); + public BiMoveIterator(DefaultNeighborhoodSession neighborhoodSession, Random random) { + var aInstance = neighborhoodSession.getDatasetInstance(aDataset); this.aIteratorSupplier = () -> aInstance.iterator(random); - var bInstance = moveStreamSession.getDatasetInstance(bDataset); + var bInstance = neighborhoodSession.getDatasetInstance(bDataset); this.bIteratorSupplier = () -> bInstance.iterator(random); - this.solutionView = moveStreamSession.getSolutionView(); + this.solutionView = neighborhoodSession.getSolutionView(); } @Override @@ -137,20 +138,20 @@ private interface IteratorSupplier extends Supplier>> { private final class BiMoveIterable implements MoveIterable { - private final DefaultMoveStreamSession moveStreamSession; + private final DefaultNeighborhoodSession neighborhoodSession; - public BiMoveIterable(DefaultMoveStreamSession moveStreamSession) { - this.moveStreamSession = Objects.requireNonNull(moveStreamSession); + public BiMoveIterable(DefaultNeighborhoodSession neighborhoodSession) { + this.neighborhoodSession = Objects.requireNonNull(neighborhoodSession); } @Override public Iterator> iterator() { - return new BiMoveIterator(moveStreamSession); + return new BiMoveIterator(neighborhoodSession); } @Override public Iterator> iterator(Random random) { - return new BiMoveIterator(moveStreamSession, random); + return new BiMoveIterator(neighborhoodSession, random); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/InnerMoveStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/InnerMoveStream.java new file mode 100644 index 0000000000..c9f17301d8 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/InnerMoveStream.java @@ -0,0 +1,19 @@ +package ai.timefold.solver.core.impl.neighborhood.move; + +import java.util.Set; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodSession; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface InnerMoveStream extends MoveStream { + + @Override + MoveIterable getMoveIterable(NeighborhoodSession neighborhoodSession); + + void collectActiveEnumeratingStreams(Set> enumeratingStreamSet); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/MoveIterable.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/MoveIterable.java similarity index 84% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/MoveIterable.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/MoveIterable.java index 5bcd70dc4c..9f95ce7936 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/MoveIterable.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/move/MoveIterable.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams; +package ai.timefold.solver.core.impl.neighborhood.move; import java.util.Iterator; import java.util.Random; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultMoveStreamFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultMoveStreamFactory.java new file mode 100644 index 0000000000..ca628e372f --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultMoveStreamFactory.java @@ -0,0 +1,105 @@ +package ai.timefold.solver.core.impl.neighborhood.stream; + +import ai.timefold.solver.core.config.solver.EnvironmentMode; +import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.BiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.EnumeratingJoiners; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.BiSamplingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.UniSamplingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.DatasetSessionFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi.AbstractBiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.AbstractUniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.sampling.DefaultBiFromBiSamplingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.sampling.DefaultUniSamplingStream; +import ai.timefold.solver.core.impl.score.director.SessionContext; +import ai.timefold.solver.core.preview.api.domain.metamodel.GenuineVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class DefaultMoveStreamFactory + implements MoveStreamFactory { + + private final EnumeratingStreamFactory enumeratingStreamFactory; + private final DatasetSessionFactory datasetSessionFactory; + + public DefaultMoveStreamFactory(SolutionDescriptor solutionDescriptor, EnvironmentMode environmentMode) { + this.enumeratingStreamFactory = new EnumeratingStreamFactory<>(solutionDescriptor, environmentMode); + this.datasetSessionFactory = new DatasetSessionFactory<>(enumeratingStreamFactory); + } + + public DefaultNeighborhoodSession createSession(SessionContext context) { + var session = datasetSessionFactory.buildSession(context); + return new DefaultNeighborhoodSession<>(session, context.solutionView()); + } + + @Override + public PlanningSolutionMetaModel getSolutionMetaModel() { + return enumeratingStreamFactory.getSolutionDescriptor().getMetaModel(); + } + + @Override + public UniEnumeratingStream forEach(Class sourceClass, boolean includeNull) { + var entityDescriptor = getSolutionDescriptor().findEntityDescriptor(sourceClass); + if (entityDescriptor == null) { // Not an entity, can't be pinned. + return enumeratingStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); + } + if (entityDescriptor.isGenuine()) { // Genuine entity can be pinned. + return enumeratingStreamFactory.forEachExcludingPinned(sourceClass, includeNull); + } + // From now on, we are testing a shadow entity. + var listVariableDescriptor = getSolutionDescriptor().getListVariableDescriptor(); + if (listVariableDescriptor == null) { // Can't be pinned when there are only basic variables. + return enumeratingStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); + } + if (!listVariableDescriptor.supportsPinning()) { // The genuine entity does not support pinning. + return enumeratingStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); + } + if (!listVariableDescriptor.acceptsValueType(sourceClass)) { // Can't be used as an element. + return enumeratingStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); + } + // Finally a valid pin-supporting type. + return enumeratingStreamFactory.forEachExcludingPinned(sourceClass, includeNull); + } + + @Override + public UniEnumeratingStream forEachUnfiltered(Class sourceClass, boolean includeNull) { + return enumeratingStreamFactory.forEachNonDiscriminating(sourceClass, includeNull); + } + + @Override + public BiEnumeratingStream forEachEntityValuePair( + GenuineVariableMetaModel variableMetaModel, + UniEnumeratingStream entityEnumeratingStream) { + var includeNull = + variableMetaModel instanceof PlanningVariableMetaModel planningVariableMetaModel + ? planningVariableMetaModel.allowsUnassigned() + : variableMetaModel instanceof PlanningListVariableMetaModel planningListVariableMetaModel + && planningListVariableMetaModel.allowsUnassignedValues(); + var stream = enumeratingStreamFactory.forEachExcludingPinned(variableMetaModel.type(), includeNull); + return entityEnumeratingStream.join(stream, EnumeratingJoiners. filtering( + (solutionView, entity, value) -> solutionView.isValueInRange(variableMetaModel, entity, value))); + } + + @Override + public UniSamplingStream pick(UniEnumeratingStream enumeratingStream) { + return new DefaultUniSamplingStream<>(((AbstractUniEnumeratingStream) enumeratingStream).createDataset()); + } + + @Override + public BiSamplingStream pick(BiEnumeratingStream enumeratingStream) { + return new DefaultBiFromBiSamplingStream<>( + ((AbstractBiEnumeratingStream) enumeratingStream).createDataset()); + } + + public SolutionDescriptor getSolutionDescriptor() { + return enumeratingStreamFactory.getSolutionDescriptor(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhood.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhood.java new file mode 100644 index 0000000000..3817c3949d --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhood.java @@ -0,0 +1,24 @@ +package ai.timefold.solver.core.impl.neighborhood.stream; + +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.Neighborhood; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class DefaultNeighborhood implements Neighborhood { + + private final List> moveDefinitionList; + + public DefaultNeighborhood(List> moveDefinitions) { + this.moveDefinitionList = Objects.requireNonNull(moveDefinitions); + } + + public List> getMoveDefinitionList() { + return moveDefinitionList; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhoodBuilder.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhoodBuilder.java new file mode 100644 index 0000000000..ffd4563d95 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhoodBuilder.java @@ -0,0 +1,40 @@ +package ai.timefold.solver.core.impl.neighborhood.stream; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodBuilder; +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class DefaultNeighborhoodBuilder implements NeighborhoodBuilder { + + private final PlanningSolutionMetaModel solutionMetaModel; + private final Set> moveDefinitionSet = new LinkedHashSet<>(); + + public DefaultNeighborhoodBuilder(PlanningSolutionMetaModel solutionMetaModel) { + this.solutionMetaModel = Objects.requireNonNull(solutionMetaModel); + } + + @Override + public PlanningSolutionMetaModel getSolutionMetaModel() { + return solutionMetaModel; + } + + @Override + public NeighborhoodBuilder add(MoveDefinition moveDefinition) { + moveDefinitionSet.add(moveDefinition); + return this; + } + + @Override + public DefaultNeighborhood build() { + return new DefaultNeighborhood<>(List.copyOf(moveDefinitionSet)); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultMoveStreamSession.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhoodSession.java similarity index 60% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultMoveStreamSession.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhoodSession.java index 321ac4d6cb..682d14d786 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/DefaultMoveStreamSession.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultNeighborhoodSession.java @@ -1,25 +1,25 @@ -package ai.timefold.solver.core.impl.move.streams; +package ai.timefold.solver.core.impl.neighborhood.stream; import java.util.Objects; -import ai.timefold.solver.core.impl.move.streams.dataset.DatasetSession; -import ai.timefold.solver.core.impl.move.streams.dataset.bi.BiDataset; -import ai.timefold.solver.core.impl.move.streams.dataset.bi.BiDatasetInstance; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDataset; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.UniDatasetInstance; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodSession; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.DatasetSession; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi.BiDataset; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi.BiDatasetInstance; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.UniDataset; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.UniDatasetInstance; import ai.timefold.solver.core.preview.api.move.SolutionView; import org.jspecify.annotations.NullMarked; @NullMarked -public final class DefaultMoveStreamSession - implements MoveStreamSession { +public final class DefaultNeighborhoodSession + implements NeighborhoodSession { private final DatasetSession datasetSession; private final SolutionView solutionView; - public DefaultMoveStreamSession(DatasetSession datasetSession, SolutionView solutionView) { + public DefaultNeighborhoodSession(DatasetSession datasetSession, SolutionView solutionView) { this.datasetSession = Objects.requireNonNull(datasetSession); this.solutionView = Objects.requireNonNull(solutionView); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DatasetSession.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSession.java similarity index 83% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DatasetSession.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSession.java index 1a196cf448..0ddd44add4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DatasetSession.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSession.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating; import java.util.IdentityHashMap; import java.util.Map; @@ -7,8 +7,8 @@ import ai.timefold.solver.core.impl.bavet.AbstractSession; import ai.timefold.solver.core.impl.bavet.NodeNetwork; import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDatasetInstance; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDataset; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDatasetInstance; public final class DatasetSession extends AbstractSession { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DatasetSessionFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSessionFactory.java similarity index 69% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DatasetSessionFactory.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSessionFactory.java index 33f8f92b0b..6d0a4f103d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DatasetSessionFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/DatasetSessionFactory.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -10,37 +10,37 @@ import ai.timefold.solver.core.impl.bavet.NodeNetwork; import ai.timefold.solver.core.impl.bavet.common.AbstractNodeBuildHelper; import ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; import ai.timefold.solver.core.impl.score.director.SessionContext; public final class DatasetSessionFactory { - private final DataStreamFactory dataStreamFactory; + private final EnumeratingStreamFactory enumeratingStreamFactory; - public DatasetSessionFactory(DataStreamFactory dataStreamFactory) { - this.dataStreamFactory = dataStreamFactory; + public DatasetSessionFactory(EnumeratingStreamFactory enumeratingStreamFactory) { + this.enumeratingStreamFactory = enumeratingStreamFactory; } public DatasetSession buildSession(SessionContext context) { - var activeDataStreamSet = new LinkedHashSet>(); - var datasets = dataStreamFactory.getDatasets(); + var activeEnumeratingStreamSet = new LinkedHashSet>(); + var datasets = enumeratingStreamFactory.getDatasets(); for (var dataset : datasets) { - dataset.collectActiveDataStreams(activeDataStreamSet); + dataset.collectActiveEnumeratingStreams(activeEnumeratingStreamSet); } - var buildHelper = new DataNodeBuildHelper<>(context, activeDataStreamSet); - var session = new DatasetSession(buildNodeNetwork(activeDataStreamSet, buildHelper, null)); + var buildHelper = new DataNodeBuildHelper<>(context, activeEnumeratingStreamSet); + var session = new DatasetSession(buildNodeNetwork(activeEnumeratingStreamSet, buildHelper, null)); for (var datasetInstance : buildHelper.getDatasetInstanceList()) { session.registerDatasetInstance(datasetInstance.getParent(), datasetInstance); } return session; } - private NodeNetwork buildNodeNetwork(Set> dataStreamSet, + private NodeNetwork buildNodeNetwork(Set> enumeratingStreamSet, DataNodeBuildHelper buildHelper, Consumer nodeNetworkVisualizationConsumer) { var declaredClassToNodeMap = new LinkedHashMap, List>>(); - var nodeList = buildHelper.buildNodeList(dataStreamSet, buildHelper, - AbstractDataStream::buildNode, node -> { + var nodeList = buildHelper.buildNodeList(enumeratingStreamSet, buildHelper, + AbstractEnumeratingStream::buildNode, node -> { if (!(node instanceof AbstractForEachUniNode forEachUniNode)) { return; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DataStreamFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/EnumeratingStreamFactory.java similarity index 70% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DataStreamFactory.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/EnumeratingStreamFactory.java index 1b9e32c594..5acb3de1c3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/DataStreamFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/EnumeratingStreamFactory.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating; import java.util.HashMap; import java.util.List; @@ -10,36 +10,37 @@ import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset; -import ai.timefold.solver.core.impl.move.streams.dataset.common.TerminalDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.AbstractUniDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.ForEachIncludingPinnedDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.DataJoiners; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.UniDataStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.EnumeratingJoiners; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDataset; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.TerminalEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.AbstractUniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.ForEachIncludingPinnedEnumeratingStream; import ai.timefold.solver.core.impl.score.director.SessionContext; import org.jspecify.annotations.NullMarked; @NullMarked -public final class DataStreamFactory { +public final class EnumeratingStreamFactory { private final SolutionDescriptor solutionDescriptor; private final EnvironmentMode environmentMode; - private final Map, AbstractDataStream> sharingStreamMap = new HashMap<>(256); + private final Map, AbstractEnumeratingStream> sharingStreamMap = + new HashMap<>(256); - public DataStreamFactory(SolutionDescriptor solutionDescriptor, EnvironmentMode environmentMode) { + public EnumeratingStreamFactory(SolutionDescriptor solutionDescriptor, EnvironmentMode environmentMode) { this.solutionDescriptor = Objects.requireNonNull(solutionDescriptor); this.environmentMode = Objects.requireNonNull(environmentMode); } - public UniDataStream forEachNonDiscriminating(Class sourceClass, boolean includeNull) { + public UniEnumeratingStream forEachNonDiscriminating(Class sourceClass, boolean includeNull) { assertValidForEachType(sourceClass); - return share(new ForEachIncludingPinnedDataStream<>(this, sourceClass, includeNull)); + return share(new ForEachIncludingPinnedEnumeratingStream<>(this, sourceClass, includeNull)); } @SuppressWarnings("unchecked") - public UniDataStream forEachExcludingPinned(Class sourceClass, boolean includeNull) { + public UniEnumeratingStream forEachExcludingPinned(Class sourceClass, boolean includeNull) { assertValidForEachType(sourceClass); if (!solutionDescriptor.getMetaModel().hasEntity(sourceClass)) { // The sourceClass is not a planning entity, therefore it cannot be pinned. @@ -51,7 +52,7 @@ public UniDataStream forEachExcludingPinned(Class sourceCla if (listVariableDescriptor == null || !listVariableDescriptor.acceptsValueType(sourceClass)) { var entityDescriptor = solutionDescriptor.findEntityDescriptorOrFail(sourceClass); // The predicate is cached to allow for node-sharing, which expects identical lambdas. - return share((AbstractUniDataStream) forEachNonDiscriminating(sourceClass, includeNull) + return share((AbstractUniEnumeratingStream) forEachNonDiscriminating(sourceClass, includeNull) .filter(entityDescriptor.getEntityMovablePredicate())); } // The sourceClass is a list variable value, therefore we need to specialize the exclusion logic. @@ -63,8 +64,8 @@ public UniDataStream forEachExcludingPinned(Class sourceCla // The predicate is cached to allow for node-sharing, which expects identical lambdas. var stream = forEachNonDiscriminating(sourceClass, includeNull) .ifNotExists(parentEntityDescriptor.getEntityClass(), - DataJoiners.filtering(listVariableDescriptor.getEntityContainsPinnedValuePredicate())); - return share((AbstractUniDataStream) stream); + EnumeratingJoiners.filtering(listVariableDescriptor.getEntityContainsPinnedValuePredicate())); + return share((AbstractUniEnumeratingStream) stream); } public void assertValidForEachType(Class fromType) { @@ -83,7 +84,7 @@ public void assertValidForEachType(Class fromType) { .sorted() .toList(); throw new IllegalArgumentException(""" - Cannot use class (%s) in a data stream as it is neither the same as, \ + Cannot use class (%s) in an enumerating stream as it is neither the same as, \ nor a superclass or superinterface of one of planning entities or problem facts. Ensure that all forEach(), join(), ifExists() and ifNotExists() building blocks only reference classes \ assignable from planning entities or problem facts (%s) annotated on the planning solution (%s).""" @@ -92,7 +93,7 @@ assignable from planning entities or problem facts (%s) annotated on the plannin } } - public > Stream_ share(Stream_ stream) { + public > Stream_ share(Stream_ stream) { return share(stream, t -> { }); } @@ -100,7 +101,7 @@ public > Stream_ share(Stream_ str /** * 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 AbstractEnumeratingStream} implement equals/hashcode ignoring child streams. *

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

@@ -108,11 +109,11 @@ public > Stream_ share(Stream_ str * * @param stream never null * @param consumer never null - * @param the {@link AbstractDataStream} subclass + * @param the {@link AbstractEnumeratingStream} subclass * @return never null */ @SuppressWarnings("unchecked") - public > Stream_ share(Stream_ stream, Consumer consumer) { + public > Stream_ share(Stream_ stream, Consumer consumer) { return (Stream_) sharingStreamMap.computeIfAbsent(stream, k -> { consumer.accept(stream); return stream; @@ -131,7 +132,7 @@ public EnvironmentMode getEnvironmentMode() { public List> getDatasets() { return sharingStreamMap.values().stream() .flatMap(s -> { - if (s instanceof TerminalDataStream terminalStream) { + if (s instanceof TerminalEnumeratingStream terminalStream) { return Stream.of((AbstractDataset) terminalStream.getDataset()); } return Stream.empty(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/AbstractBiEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/AbstractBiEnumeratingStream.java new file mode 100644 index 0000000000..a6f1d61afc --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/AbstractBiEnumeratingStream.java @@ -0,0 +1,82 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; + +import java.util.function.BiFunction; + +import ai.timefold.solver.core.impl.bavet.bi.Group2Mapping0CollectorBiNode; +import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; +import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.BiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingMapper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeBiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeUniEnumeratingStream; +import ai.timefold.solver.core.impl.util.ConstantLambdaUtils; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public abstract class AbstractBiEnumeratingStream extends AbstractEnumeratingStream + implements BiEnumeratingStream { + + protected AbstractBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory) { + super(enumeratingStreamFactory, null); + } + + protected AbstractBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + @Nullable AbstractEnumeratingStream parent) { + super(enumeratingStreamFactory, parent); + } + + @Override + public final BiEnumeratingStream filter(BiEnumeratingFilter filter) { + return shareAndAddChild(new FilterBiEnumeratingStream<>(enumeratingStreamFactory, this, filter)); + } + + protected AbstractBiEnumeratingStream + groupBy(BiFunction groupKeyAMapping, BiFunction groupKeyBMapping) { + GroupNodeConstructor> nodeConstructor = + GroupNodeConstructor.twoKeysGroupBy(groupKeyAMapping, groupKeyBMapping, Group2Mapping0CollectorBiNode::new); + return buildBiGroupBy(nodeConstructor); + } + + private AbstractBiEnumeratingStream + buildBiGroupBy(GroupNodeConstructor> nodeConstructor) { + var stream = shareAndAddChild(new BiGroupBiEnumeratingStream<>(enumeratingStreamFactory, this, nodeConstructor)); + return enumeratingStreamFactory.share(new AftBridgeBiEnumeratingStream<>(enumeratingStreamFactory, stream), + stream::setAftBridge); + } + + @Override + public UniEnumeratingStream map(BiEnumeratingMapper mapping) { + var stream = shareAndAddChild(new UniMapBiEnumeratingStream<>(enumeratingStreamFactory, this, mapping)); + return enumeratingStreamFactory.share(new AftBridgeUniEnumeratingStream<>(enumeratingStreamFactory, stream), + stream::setAftBridge); + } + + @Override + public BiEnumeratingStream + map(BiEnumeratingMapper mappingA, + BiEnumeratingMapper mappingB) { + var stream = shareAndAddChild(new BiMapBiEnumeratingStream<>(enumeratingStreamFactory, this, mappingA, mappingB)); + return enumeratingStreamFactory.share(new AftBridgeBiEnumeratingStream<>(enumeratingStreamFactory, stream), + stream::setAftBridge); + } + + @Override + public AbstractBiEnumeratingStream distinct() { + if (guaranteesDistinct()) { + return this; // Already distinct, no need to create a new stream. + } + return groupBy(ConstantLambdaUtils.biPickFirst(), ConstantLambdaUtils.biPickSecond()); + } + + public BiDataset createDataset() { + var stream = shareAndAddChild(new TerminalBiEnumeratingStream<>(enumeratingStreamFactory, this)); + return stream.getDataset(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiDataset.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiDataset.java new file mode 100644 index 0000000000..e146037c9c --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiDataset.java @@ -0,0 +1,22 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; + +import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDataset; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class BiDataset extends AbstractDataset> { + + public BiDataset(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractBiEnumeratingStream parent) { + super(enumeratingStreamFactory, parent); + } + + @Override + public BiDatasetInstance instantiate(int storeIndex) { + return new BiDatasetInstance<>(this, storeIndex); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiDatasetInstance.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiDatasetInstance.java similarity index 95% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiDatasetInstance.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiDatasetInstance.java index 2f7c2e9dfd..2cc7f1a135 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiDatasetInstance.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiDatasetInstance.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; import java.util.ArrayList; import java.util.Iterator; @@ -9,8 +9,8 @@ import java.util.Random; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDatasetInstance; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDataset; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDatasetInstance; import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.impl.util.ElementAwareList; import ai.timefold.solver.core.impl.util.ElementAwareListEntry; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiGroupBiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiGroupBiEnumeratingStream.java similarity index 55% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiGroupBiDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiGroupBiEnumeratingStream.java index 9a06430107..11d61291e6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiGroupBiDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiGroupBiEnumeratingStream.java @@ -1,30 +1,31 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; import java.util.Objects; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeBiDataStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeBiEnumeratingStream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -final class BiGroupBiDataStream - extends AbstractBiDataStream { +final class BiGroupBiEnumeratingStream + extends AbstractBiEnumeratingStream { private final GroupNodeConstructor> nodeConstructor; - private @Nullable AftBridgeBiDataStream aftStream; + private @Nullable AftBridgeBiEnumeratingStream aftStream; - public BiGroupBiDataStream(DataStreamFactory dataStreamFactory, AbstractBiDataStream parent, + public BiGroupBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractBiEnumeratingStream parent, GroupNodeConstructor> nodeConstructor) { - super(dataStreamFactory, parent); + super(enumeratingStreamFactory, parent); this.nodeConstructor = nodeConstructor; } - public void setAftBridge(AftBridgeBiDataStream aftStream) { + public void setAftBridge(AftBridgeBiEnumeratingStream aftStream) { this.aftStream = aftStream; } @@ -32,7 +33,7 @@ public void setAftBridge(AftBridgeBiDataStream aftStream) public void buildNode(DataNodeBuildHelper buildHelper) { var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, - dataStreamFactory.getEnvironmentMode()); + enumeratingStreamFactory.getEnvironmentMode()); } @Override @@ -41,7 +42,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - var that = (BiGroupBiDataStream) object; + var that = (BiGroupBiEnumeratingStream) object; return Objects.equals(parent, that.parent) && Objects.equals(nodeConstructor, that.nodeConstructor); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiMapBiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiMapBiEnumeratingStream.java similarity index 54% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiMapBiDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiMapBiEnumeratingStream.java index 07cd840e8a..6be88d96a9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiMapBiDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/BiMapBiEnumeratingStream.java @@ -1,32 +1,34 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; import java.util.Objects; import ai.timefold.solver.core.impl.bavet.bi.MapBiToBiNode; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeBiDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataMapper; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingMapper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeBiEnumeratingStream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -final class BiMapBiDataStream - extends AbstractBiDataStream { +final class BiMapBiEnumeratingStream + extends AbstractBiEnumeratingStream { - private final BiDataMapper mappingFunctionA; - private final BiDataMapper mappingFunctionB; - private @Nullable AftBridgeBiDataStream aftStream; + private final BiEnumeratingMapper mappingFunctionA; + private final BiEnumeratingMapper mappingFunctionB; + private @Nullable AftBridgeBiEnumeratingStream aftStream; - public BiMapBiDataStream(DataStreamFactory dataStreamFactory, AbstractBiDataStream parent, - BiDataMapper mappingFunctionA, BiDataMapper mappingFunctionB) { - super(dataStreamFactory, parent); + public BiMapBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractBiEnumeratingStream parent, + BiEnumeratingMapper mappingFunctionA, + BiEnumeratingMapper mappingFunctionB) { + super(enumeratingStreamFactory, parent); this.mappingFunctionA = mappingFunctionA; this.mappingFunctionB = mappingFunctionB; } - public void setAftBridge(AftBridgeBiDataStream aftStream) { + public void setAftBridge(AftBridgeBiEnumeratingStream aftStream) { this.aftStream = aftStream; } @@ -53,7 +55,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - BiMapBiDataStream that = (BiMapBiDataStream) object; + BiMapBiEnumeratingStream that = (BiMapBiEnumeratingStream) object; return Objects.equals(parent, that.parent) && Objects.equals(mappingFunctionA, that.mappingFunctionA) && Objects.equals(mappingFunctionB, that.mappingFunctionB); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/FilterBiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/FilterBiEnumeratingStream.java similarity index 53% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/FilterBiDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/FilterBiEnumeratingStream.java index 2cc285e295..d19fa6da82 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/FilterBiDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/FilterBiEnumeratingStream.java @@ -1,24 +1,25 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; import java.util.Objects; 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.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; import org.jspecify.annotations.NullMarked; @NullMarked -final class FilterBiDataStream - extends AbstractBiDataStream { +final class FilterBiEnumeratingStream + extends AbstractBiEnumeratingStream { - private final BiDataFilter filter; + private final BiEnumeratingFilter filter; - public FilterBiDataStream(DataStreamFactory dataStreamFactory, AbstractBiDataStream parent, - BiDataFilter filter) { - super(dataStreamFactory, parent); + public FilterBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractBiEnumeratingStream parent, + BiEnumeratingFilter filter) { + super(enumeratingStreamFactory, parent); this.filter = Objects.requireNonNull(filter, "The filter cannot be null."); } @@ -36,7 +37,7 @@ public int hashCode() { @Override public boolean equals(Object o) { - return o instanceof FilterBiDataStream other + return o instanceof FilterBiEnumeratingStream other && parent == other.parent && filter == other.filter; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/JoinBiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/JoinBiEnumeratingStream.java similarity index 63% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/JoinBiDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/JoinBiEnumeratingStream.java index a1997fcaf1..e70b1afdc2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/JoinBiDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/JoinBiEnumeratingStream.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; import java.util.Objects; import java.util.Set; @@ -8,26 +8,26 @@ 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.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.JoinDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.ForeBridgeUniDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.joiner.DefaultBiDataJoiner; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.JoinEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.ForeBridgeUniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner.DefaultBiEnumeratingJoiner; -public final class JoinBiDataStream extends AbstractBiDataStream - implements JoinDataStream { +public final class JoinBiEnumeratingStream extends AbstractBiEnumeratingStream + implements JoinEnumeratingStream { - private final ForeBridgeUniDataStream leftParent; - private final ForeBridgeUniDataStream rightParent; - private final DefaultBiDataJoiner joiner; - private final BiDataFilter filtering; + private final ForeBridgeUniEnumeratingStream leftParent; + private final ForeBridgeUniEnumeratingStream rightParent; + private final DefaultBiEnumeratingJoiner joiner; + private final BiEnumeratingFilter filtering; - public JoinBiDataStream(DataStreamFactory dataStreamFactory, - ForeBridgeUniDataStream leftParent, ForeBridgeUniDataStream rightParent, - DefaultBiDataJoiner joiner, BiDataFilter filtering) { - super(dataStreamFactory); + public JoinBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + ForeBridgeUniEnumeratingStream leftParent, ForeBridgeUniEnumeratingStream rightParent, + DefaultBiEnumeratingJoiner joiner, BiEnumeratingFilter filtering) { + super(enumeratingStreamFactory); this.leftParent = leftParent; this.rightParent = rightParent; this.joiner = joiner; @@ -35,10 +35,10 @@ public JoinBiDataStream(DataStreamFactory dataStreamFactory, } @Override - public void collectActiveDataStreams(Set> dataStreamSet) { - leftParent.collectActiveDataStreams(dataStreamSet); - rightParent.collectActiveDataStreams(dataStreamSet); - dataStreamSet.add(this); + public void collectActiveEnumeratingStreams(Set> enumeratingStreamSet) { + leftParent.collectActiveEnumeratingStreams(enumeratingStreamSet); + rightParent.collectActiveEnumeratingStreams(enumeratingStreamSet); + enumeratingStreamSet.add(this); } @Override @@ -75,7 +75,7 @@ public boolean equals(Object o) { * resulting in StackOverflowError. * Therefore we need to check bridge parents to see where this join node comes from. */ - return o instanceof JoinBiDataStream other + return o instanceof JoinBiEnumeratingStream other && Objects.equals(leftParent.getParent(), other.leftParent.getParent()) && Objects.equals(rightParent.getParent(), other.rightParent.getParent()) && Objects.equals(joiner, other.joiner) @@ -97,12 +97,12 @@ public String toString() { // ************************************************************************ @Override - public AbstractDataStream getLeftParent() { + public AbstractEnumeratingStream getLeftParent() { return leftParent; } @Override - public AbstractDataStream getRightParent() { + public AbstractEnumeratingStream getRightParent() { return rightParent; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/TerminalBiEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/TerminalBiEnumeratingStream.java new file mode 100644 index 0000000000..4b47059d32 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/TerminalBiEnumeratingStream.java @@ -0,0 +1,40 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; + +import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.TerminalEnumeratingStream; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +final class TerminalBiEnumeratingStream + extends AbstractBiEnumeratingStream + implements TerminalEnumeratingStream, BiDataset> { + + private final BiDataset dataset; + + public TerminalBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractBiEnumeratingStream parent) { + super(enumeratingStreamFactory, parent); + this.dataset = new BiDataset<>(enumeratingStreamFactory, this); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + assertEmptyChildStreamList(); + var inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); + buildHelper.putInsertUpdateRetract(this, dataset.instantiate(inputStoreIndex)); + } + + @Override + public BiDataset getDataset() { + return dataset; + } + + @Override + public String toString() { + return "Terminal node"; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/UniMapBiDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/UniMapBiEnumeratingStream.java similarity index 52% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/UniMapBiDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/UniMapBiEnumeratingStream.java index 6ca4ae629a..15ec7b7cf1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/UniMapBiDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/bi/UniMapBiEnumeratingStream.java @@ -1,31 +1,32 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.bi; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi; import java.util.Objects; import ai.timefold.solver.core.impl.bavet.bi.MapBiToUniNode; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeUniDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.AbstractUniDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataMapper; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingMapper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeUniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.AbstractUniEnumeratingStream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -final class UniMapBiDataStream - extends AbstractUniDataStream { +final class UniMapBiEnumeratingStream + extends AbstractUniEnumeratingStream { - private final BiDataMapper mappingFunction; - private @Nullable AftBridgeUniDataStream aftStream; + private final BiEnumeratingMapper mappingFunction; + private @Nullable AftBridgeUniEnumeratingStream aftStream; - public UniMapBiDataStream(DataStreamFactory dataStreamFactory, AbstractBiDataStream parent, - BiDataMapper mappingFunction) { - super(dataStreamFactory, parent); + public UniMapBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractBiEnumeratingStream parent, + BiEnumeratingMapper mappingFunction) { + super(enumeratingStreamFactory, parent); this.mappingFunction = mappingFunction; } - public void setAftBridge(AftBridgeUniDataStream aftStream) { + public void setAftBridge(AftBridgeUniEnumeratingStream aftStream) { this.aftStream = aftStream; } @@ -51,7 +52,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - UniMapBiDataStream that = (UniMapBiDataStream) object; + UniMapBiEnumeratingStream that = (UniMapBiEnumeratingStream) object; return Objects.equals(parent, that.parent) && Objects.equals(mappingFunction, that.mappingFunction); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractDataset.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractDataset.java new file mode 100644 index 0000000000..5b1b2eaf7f --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractDataset.java @@ -0,0 +1,47 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common; + +import java.util.Objects; +import java.util.Set; + +import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public abstract class AbstractDataset { + + private final EnumeratingStreamFactory enumeratingStreamFactory; + private final AbstractEnumeratingStream parent; + + protected AbstractDataset(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractEnumeratingStream parent) { + this.enumeratingStreamFactory = Objects.requireNonNull(enumeratingStreamFactory); + this.parent = Objects.requireNonNull(parent); + } + + public void collectActiveEnumeratingStreams(Set> enumeratingStreamSet) { + parent.collectActiveEnumeratingStreams(enumeratingStreamSet); + } + + public abstract AbstractDatasetInstance instantiate(int storeIndex); + + @Override + public boolean equals(Object entity) { + if (!(entity instanceof AbstractDataset dataset)) { + return false; + } + return Objects.equals(enumeratingStreamFactory, dataset.enumeratingStreamFactory) + && Objects.equals(parent, dataset.parent); + } + + @Override + public int hashCode() { + return Objects.hash(enumeratingStreamFactory, parent); + } + + @Override + public String toString() { + return "%s for %s".formatted(getClass().getSimpleName(), parent); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDatasetInstance.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractDatasetInstance.java similarity index 82% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDatasetInstance.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractDatasetInstance.java index 28d9744760..577b5b2087 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDatasetInstance.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractDatasetInstance.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common; import java.util.Iterator; import java.util.Objects; @@ -16,7 +16,7 @@ public abstract class AbstractDatasetInstance parent; protected final int inputStoreIndex; - public AbstractDatasetInstance(AbstractDataset parent, int inputStoreIndex) { + protected AbstractDatasetInstance(AbstractDataset parent, int inputStoreIndex) { this.parent = Objects.requireNonNull(parent); this.inputStoreIndex = inputStoreIndex; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractEnumeratingStream.java similarity index 62% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractEnumeratingStream.java index 4451a183eb..f4d483ba3b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractEnumeratingStream.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common; import java.util.ArrayList; import java.util.List; @@ -6,27 +6,27 @@ 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.streams.dataset.DataStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -public abstract class AbstractDataStream +public abstract class AbstractEnumeratingStream implements BavetStream { - protected final DataStreamFactory dataStreamFactory; - protected final @Nullable AbstractDataStream parent; - protected final List> childStreamList = new ArrayList<>(2); + protected final EnumeratingStreamFactory enumeratingStreamFactory; + protected final @Nullable AbstractEnumeratingStream parent; + protected final List> childStreamList = new ArrayList<>(2); - protected AbstractDataStream(DataStreamFactory dataStreamFactory, - @Nullable AbstractDataStream parent) { - this.dataStreamFactory = dataStreamFactory; + protected AbstractEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + @Nullable AbstractEnumeratingStream parent) { + this.enumeratingStreamFactory = enumeratingStreamFactory; this.parent = parent; } - public final > Stream_ shareAndAddChild(Stream_ stream) { - return dataStreamFactory.share(stream, childStreamList::add); + public final > Stream_ shareAndAddChild(Stream_ stream) { + return enumeratingStreamFactory.share(stream, childStreamList::add); } protected boolean guaranteesDistinct() { @@ -42,13 +42,13 @@ protected boolean guaranteesDistinct() { // Node creation // ************************************************************************ - public void collectActiveDataStreams(Set> dataStreamSet) { + public void collectActiveEnumeratingStreams(Set> enumeratingStreamSet) { if (parent == null) { // Maybe a join/ifExists/forEach forgot to override this? throw new IllegalStateException("Impossible state: the stream (%s) does not have a parent." .formatted(this)); } - parent.collectActiveDataStreams(dataStreamSet); - dataStreamSet.add(this); + parent.collectActiveEnumeratingStreams(enumeratingStreamSet); + enumeratingStreamSet.add(this); } /** @@ -57,7 +57,7 @@ public void collectActiveDataStreams(Set> dataStre * * @return this if {@link TupleSource}, otherwise parent's tuple source. */ - public AbstractDataStream getTupleSource() { + public AbstractEnumeratingStream getTupleSource() { if (this instanceof TupleSource) { return this; } else if (parent == null) { // Maybe some stream forgot to override this? @@ -76,11 +76,11 @@ protected void assertEmptyChildStreamList() { } @Override - public final @Nullable AbstractDataStream getParent() { + public final @Nullable AbstractEnumeratingStream getParent() { return parent; } - public final List> getChildStreamList() { + public final List> getChildStreamList() { return childStreamList; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/DataNodeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/DataNodeBuildHelper.java similarity index 85% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/DataNodeBuildHelper.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/DataNodeBuildHelper.java index 5de65cfc4c..9e2c02f9fb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/DataNodeBuildHelper.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/DataNodeBuildHelper.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.common; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common; import java.util.ArrayList; import java.util.Collections; @@ -14,19 +14,20 @@ import org.jspecify.annotations.NullMarked; @NullMarked -public final class DataNodeBuildHelper extends AbstractNodeBuildHelper> { +public final class DataNodeBuildHelper extends AbstractNodeBuildHelper> { private final SessionContext sessionContext; private final List> datasetInstanceList = new ArrayList<>(); - public DataNodeBuildHelper(SessionContext sessionContext, Set> activeStreamSet) { + public DataNodeBuildHelper(SessionContext sessionContext, + Set> activeStreamSet) { super(activeStreamSet); this.sessionContext = Objects.requireNonNull(sessionContext); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public void putInsertUpdateRetract(AbstractDataStream stream, + public void putInsertUpdateRetract(AbstractEnumeratingStream stream, TupleLifecycle tupleLifecycle) { super.putInsertUpdateRetract(stream, tupleLifecycle); if (tupleLifecycle instanceof AbstractDatasetInstance datasetInstance) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/EnumeratingStreamBinaryOperation.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/EnumeratingStreamBinaryOperation.java new file mode 100644 index 0000000000..89a1a3ef3e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/EnumeratingStreamBinaryOperation.java @@ -0,0 +1,12 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common; + +import ai.timefold.solver.core.impl.bavet.common.BavetStreamBinaryOperation; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public sealed interface EnumeratingStreamBinaryOperation + extends BavetStreamBinaryOperation> + permits IfExistsEnumeratingStream, JoinEnumeratingStream { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/IfExistsEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/IfExistsEnumeratingStream.java new file mode 100644 index 0000000000..eb2f3d0bd3 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/IfExistsEnumeratingStream.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public non-sealed interface IfExistsEnumeratingStream extends EnumeratingStreamBinaryOperation { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/JoinEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/JoinEnumeratingStream.java new file mode 100644 index 0000000000..389b99b102 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/JoinEnumeratingStream.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common; + +import ai.timefold.solver.core.impl.bavet.common.TupleSource; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public non-sealed interface JoinEnumeratingStream + extends EnumeratingStreamBinaryOperation, TupleSource { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/TerminalEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/TerminalEnumeratingStream.java new file mode 100644 index 0000000000..ba7f057062 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/TerminalEnumeratingStream.java @@ -0,0 +1,12 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common; + +import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface TerminalEnumeratingStream> { + + Dataset_ getDataset(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/AftBridgeBiEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/AftBridgeBiEnumeratingStream.java new file mode 100644 index 0000000000..c563709542 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/AftBridgeBiEnumeratingStream.java @@ -0,0 +1,43 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge; + +import java.util.Objects; + +import ai.timefold.solver.core.impl.bavet.common.TupleSource; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi.AbstractBiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class AftBridgeBiEnumeratingStream + extends AbstractBiEnumeratingStream + implements TupleSource { + + public AftBridgeBiEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractEnumeratingStream parent) { + super(enumeratingStreamFactory, parent); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + // Do nothing. The parent stream builds everything. + } + + @Override + public boolean equals(Object o) { + return o instanceof AftBridgeBiEnumeratingStream that && Objects.equals(parent, that.parent); + } + + @Override + public int hashCode() { + return Objects.requireNonNull(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/neighborhood/stream/enumerating/common/bridge/AftBridgeUniEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/AftBridgeUniEnumeratingStream.java new file mode 100644 index 0000000000..c39ee25e70 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/AftBridgeUniEnumeratingStream.java @@ -0,0 +1,43 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge; + +import java.util.Objects; + +import ai.timefold.solver.core.impl.bavet.common.TupleSource; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.AbstractUniEnumeratingStream; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class AftBridgeUniEnumeratingStream + extends AbstractUniEnumeratingStream + implements TupleSource { + + public AftBridgeUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractEnumeratingStream parent) { + super(enumeratingStreamFactory, parent); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + // Do nothing. The parent stream builds everything. + } + + @Override + public boolean equals(Object o) { + return o instanceof AftBridgeUniEnumeratingStream that && Objects.equals(parent, that.parent); + } + + @Override + public int hashCode() { + return Objects.requireNonNull(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/neighborhood/stream/enumerating/common/bridge/ForeBridgeUniEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/ForeBridgeUniEnumeratingStream.java new file mode 100644 index 0000000000..179455b1fc --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/ForeBridgeUniEnumeratingStream.java @@ -0,0 +1,31 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.AbstractUniEnumeratingStream; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class ForeBridgeUniEnumeratingStream + extends AbstractUniEnumeratingStream + implements UniEnumeratingStream { + + public ForeBridgeUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractEnumeratingStream parent) { + super(enumeratingStreamFactory, 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/streams/dataset/common/bridge/package-info.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/package-info.java similarity index 78% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/package-info.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/package-info.java index abe836f4a5..7001946042 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/bridge/package-info.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/bridge/package-info.java @@ -1,5 +1,5 @@ /** - * Data streams that serve as bridges. + * Enumerating streams that serve as bridges. * Fore bridges go before the stream they bridge, * while aft bridges go after. *

@@ -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.move.streams.dataset.common.bridge; \ No newline at end of file +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge; \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/AbstractDataJoiner.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/AbstractDataJoiner.java similarity index 93% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/AbstractDataJoiner.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/AbstractDataJoiner.java index b3416cf612..d516311876 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/AbstractDataJoiner.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/AbstractDataJoiner.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.joiner; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner; import java.util.Objects; import java.util.function.Function; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/BiDataJoinerComber.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/BiDataJoinerComber.java similarity index 54% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/BiDataJoinerComber.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/BiDataJoinerComber.java index 04af468f48..d52f7c0262 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/BiDataJoinerComber.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/BiDataJoinerComber.java @@ -1,57 +1,57 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.joiner; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner; import java.util.ArrayList; import java.util.List; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataJoiner; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingJoiner; import ai.timefold.solver.core.preview.api.move.SolutionView; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; /** - * Combs an array of {@link BiDataJoiner} instances into a mergedJoiner and a mergedFiltering. + * Combs an array of {@link BiEnumeratingJoiner} instances into a mergedJoiner and a mergedFiltering. * * @param * @param */ @NullMarked -public record BiDataJoinerComber(DefaultBiDataJoiner mergedJoiner, - @Nullable BiDataFilter mergedFiltering) { +public record BiDataJoinerComber(DefaultBiEnumeratingJoiner mergedJoiner, + @Nullable BiEnumeratingFilter mergedFiltering) { - public static BiDataJoinerComber comb(BiDataJoiner[] joiners) { - List> defaultJoinerList = new ArrayList<>(joiners.length); - List> filteringList = new ArrayList<>(joiners.length); + public static BiDataJoinerComber comb(BiEnumeratingJoiner[] joiners) { + List> defaultJoinerList = new ArrayList<>(joiners.length); + List> filteringList = new ArrayList<>(joiners.length); int indexOfFirstFilter = -1; // Make sure all indexing joiners, if any, come before filtering joiners. This is necessary for performance. for (int i = 0; i < joiners.length; i++) { - BiDataJoiner joiner = joiners[i]; - if (joiner instanceof FilteringBiDataJoiner) { + BiEnumeratingJoiner joiner = joiners[i]; + if (joiner instanceof FilteringBiEnumeratingJoiner) { // From now on, only allow filtering joiners. indexOfFirstFilter = i; - filteringList.add(((FilteringBiDataJoiner) joiner).filter()); - } else if (joiner instanceof DefaultBiDataJoiner) { + filteringList.add(((FilteringBiEnumeratingJoiner) joiner).filter()); + } else if (joiner instanceof DefaultBiEnumeratingJoiner) { if (indexOfFirstFilter >= 0) { throw new IllegalStateException(""" Indexing joiner (%s) must not follow a filtering joiner (%s). Maybe reorder the joiners such that filtering() joiners are later in the parameter list.""" .formatted(joiner, joiners[indexOfFirstFilter])); } - defaultJoinerList.add((DefaultBiDataJoiner) joiner); + defaultJoinerList.add((DefaultBiEnumeratingJoiner) joiner); } else { throw new IllegalArgumentException( "The joiner class (%s) is not supported.".formatted(joiner.getClass().getSimpleName())); } } - DefaultBiDataJoiner mergedJoiner = DefaultBiDataJoiner.merge(defaultJoinerList); - BiDataFilter mergedFiltering = mergeFiltering(filteringList); + DefaultBiEnumeratingJoiner mergedJoiner = DefaultBiEnumeratingJoiner.merge(defaultJoinerList); + BiEnumeratingFilter mergedFiltering = mergeFiltering(filteringList); return new BiDataJoinerComber<>(mergedJoiner, mergedFiltering); } - private static @Nullable BiDataFilter - mergeFiltering(List> filteringList) { + private static @Nullable BiEnumeratingFilter + mergeFiltering(List> filteringList) { if (filteringList.isEmpty()) { return null; } @@ -61,7 +61,7 @@ Maybe reorder the joiners such that filtering() joiners are later in the paramet default -> // Avoid predicate.and() when more than 2 predicates for debugging and potentially performance (SolutionView solutionView, A a, B b) -> { - for (BiDataFilter predicate : filteringList) { + for (BiEnumeratingFilter predicate : filteringList) { if (!predicate.test(solutionView, a, b)) { return false; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/DefaultBiDataJoiner.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/DefaultBiEnumeratingJoiner.java similarity index 69% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/DefaultBiDataJoiner.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/DefaultBiEnumeratingJoiner.java index f933c90934..44b40debb6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/joiner/DefaultBiDataJoiner.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/DefaultBiEnumeratingJoiner.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.joiner; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner; import java.util.Arrays; import java.util.List; @@ -8,36 +8,36 @@ import ai.timefold.solver.core.impl.bavet.bi.joiner.DefaultBiJoiner; import ai.timefold.solver.core.impl.bavet.common.joiner.AbstractJoiner; import ai.timefold.solver.core.impl.bavet.common.joiner.JoinerType; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataJoiner; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingJoiner; import org.jspecify.annotations.NullMarked; @SuppressWarnings({ "unchecked", "rawtypes" }) @NullMarked -public final class DefaultBiDataJoiner extends AbstractDataJoiner implements BiDataJoiner { +public final class DefaultBiEnumeratingJoiner extends AbstractDataJoiner implements BiEnumeratingJoiner { - private static final DefaultBiDataJoiner NONE = - new DefaultBiDataJoiner(new Function[0], new JoinerType[0], new Function[0]); + private static final DefaultBiEnumeratingJoiner NONE = + new DefaultBiEnumeratingJoiner(new Function[0], new JoinerType[0], new Function[0]); private final Function[] leftMappings; - public DefaultBiDataJoiner(Function leftMapping, JoinerType joinerType, + public DefaultBiEnumeratingJoiner(Function leftMapping, JoinerType joinerType, Function rightMapping) { super(rightMapping, joinerType); this.leftMappings = new Function[] { leftMapping }; } - private DefaultBiDataJoiner(Function[] leftMappings, JoinerType[] joinerTypes, + private DefaultBiEnumeratingJoiner(Function[] leftMappings, JoinerType[] joinerTypes, Function[] rightMappings) { super(rightMappings, joinerTypes); this.leftMappings = (Function[]) leftMappings; } - public static DefaultBiDataJoiner merge(List> joinerList) { + public static DefaultBiEnumeratingJoiner merge(List> joinerList) { if (joinerList.size() == 1) { return joinerList.get(0); } - return joinerList.stream().reduce(NONE, DefaultBiDataJoiner::and); + return joinerList.stream().reduce(NONE, DefaultBiEnumeratingJoiner::and); } public AbstractJoiner toBiJoiner() { @@ -45,8 +45,8 @@ public AbstractJoiner toBiJoiner() { } @Override - public DefaultBiDataJoiner and(BiDataJoiner otherJoiner) { - var castJoiner = (DefaultBiDataJoiner) otherJoiner; + public DefaultBiEnumeratingJoiner and(BiEnumeratingJoiner otherJoiner) { + var castJoiner = (DefaultBiEnumeratingJoiner) otherJoiner; var joinerCount = getJoinerCount(); var castJoinerCount = castJoiner.getJoinerCount(); var newJoinerCount = joinerCount + castJoinerCount; @@ -59,7 +59,7 @@ public DefaultBiDataJoiner and(BiDataJoiner otherJoiner) { newLeftMappings[newJoinerIndex] = castJoiner.getLeftMapping(i); newRightMappings[newJoinerIndex] = castJoiner.getRightMapping(i); } - return new DefaultBiDataJoiner<>(newLeftMappings, newJoinerTypes, newRightMappings); + return new DefaultBiEnumeratingJoiner<>(newLeftMappings, newJoinerTypes, newRightMappings); } public Function getLeftMapping(int index) { @@ -81,7 +81,7 @@ public boolean matches(A a, B b) { @Override public boolean equals(Object o) { - return o instanceof DefaultBiDataJoiner other + return o instanceof DefaultBiEnumeratingJoiner other && Arrays.equals(joinerTypes, other.joinerTypes) && Arrays.equals(leftMappings, other.leftMappings) && Arrays.equals(rightMappings, other.rightMappings); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/FilteringBiEnumeratingJoiner.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/FilteringBiEnumeratingJoiner.java new file mode 100644 index 0000000000..15a34297c7 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/joiner/FilteringBiEnumeratingJoiner.java @@ -0,0 +1,18 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingJoiner; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record FilteringBiEnumeratingJoiner( + BiEnumeratingFilter filter) implements BiEnumeratingJoiner { + + @Override + public FilteringBiEnumeratingJoiner and(BiEnumeratingJoiner otherJoiner) { + FilteringBiEnumeratingJoiner castJoiner = (FilteringBiEnumeratingJoiner) otherJoiner; + return new FilteringBiEnumeratingJoiner<>(filter.and(castJoiner.filter())); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/AbstractForEachDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/AbstractForEachEnumeratingStream.java similarity index 62% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/AbstractForEachDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/AbstractForEachEnumeratingStream.java index 1b30fc6bcd..3b64467bca 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/AbstractForEachDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/AbstractForEachEnumeratingStream.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; import static ai.timefold.solver.core.impl.bavet.uni.AbstractForEachUniNode.LifecycleOperation; @@ -9,31 +9,32 @@ 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.ForEachUnfilteredUniNode; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; import org.jspecify.annotations.NullMarked; @NullMarked -abstract sealed class AbstractForEachDataStream - extends AbstractUniDataStream +abstract sealed class AbstractForEachEnumeratingStream + extends AbstractUniEnumeratingStream implements TupleSource - permits ForEachIncludingPinnedDataStream { + permits ForEachIncludingPinnedEnumeratingStream { protected final Class forEachClass; final boolean shouldIncludeNull; - protected AbstractForEachDataStream(DataStreamFactory dataStreamFactory, Class forEachClass, + protected AbstractForEachEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + Class forEachClass, boolean includeNull) { - super(dataStreamFactory, null); + super(enumeratingStreamFactory, null); this.forEachClass = Objects.requireNonNull(forEachClass); this.shouldIncludeNull = includeNull; } @Override - public final void collectActiveDataStreams(Set> dataStreamSet) { - dataStreamSet.add(this); + public final void collectActiveEnumeratingStreams(Set> enumeratingStreamSet) { + enumeratingStreamSet.add(this); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/AbstractUniEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/AbstractUniEnumeratingStream.java new file mode 100644 index 0000000000..22db40d4cc --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/AbstractUniEnumeratingStream.java @@ -0,0 +1,159 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; + +import static ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor.oneKeyGroupBy; + +import java.util.Objects; +import java.util.function.Function; + +import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.bavet.uni.Group1Mapping0CollectorUniNode; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.BiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingJoiner; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingMapper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi.JoinBiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeBiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeUniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.ForeBridgeUniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner.BiDataJoinerComber; +import ai.timefold.solver.core.impl.util.ConstantLambdaUtils; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public abstract class AbstractUniEnumeratingStream extends AbstractEnumeratingStream + implements UniEnumeratingStream { + + protected AbstractUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory) { + super(enumeratingStreamFactory, null); + } + + protected AbstractUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + @Nullable AbstractEnumeratingStream parent) { + super(enumeratingStreamFactory, parent); + } + + @Override + public final UniEnumeratingStream filter(UniEnumeratingFilter filter) { + return shareAndAddChild(new FilterUniEnumeratingStream<>(enumeratingStreamFactory, this, filter)); + } + + @Override + public BiEnumeratingStream join(UniEnumeratingStream otherStream, + BiEnumeratingJoiner... joiners) { + var other = (AbstractUniEnumeratingStream) otherStream; + var leftBridge = new ForeBridgeUniEnumeratingStream(enumeratingStreamFactory, this); + var rightBridge = new ForeBridgeUniEnumeratingStream(enumeratingStreamFactory, other); + var joinerComber = BiDataJoinerComber. comb(joiners); + var joinStream = new JoinBiEnumeratingStream<>(enumeratingStreamFactory, leftBridge, rightBridge, + joinerComber.mergedJoiner(), joinerComber.mergedFiltering()); + return enumeratingStreamFactory.share(joinStream, joinStream_ -> { + // Connect the bridges upstream, as it is an actual new join. + getChildStreamList().add(leftBridge); + other.getChildStreamList().add(rightBridge); + }); + } + + @Override + public BiEnumeratingStream join(Class otherClass, BiEnumeratingJoiner... joiners) { + return join(enumeratingStreamFactory.forEachNonDiscriminating(otherClass, false), joiners); + } + + @SafeVarargs + @Override + public final UniEnumeratingStream ifExists(Class otherClass, BiEnumeratingJoiner... joiners) { + return ifExists(enumeratingStreamFactory.forEachNonDiscriminating(otherClass, false), joiners); + } + + @SafeVarargs + @Override + public final UniEnumeratingStream ifExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner... joiners) { + return ifExistsOrNot(true, otherStream, joiners); + } + + @SafeVarargs + @Override + public final UniEnumeratingStream ifNotExists(Class otherClass, BiEnumeratingJoiner... joiners) { + return ifExistsOrNot(false, enumeratingStreamFactory.forEachNonDiscriminating(otherClass, false), joiners); + } + + @SafeVarargs + @Override + public final UniEnumeratingStream ifNotExists(UniEnumeratingStream otherStream, + BiEnumeratingJoiner... joiners) { + return ifExistsOrNot(false, otherStream, joiners); + } + + private UniEnumeratingStream ifExistsOrNot(boolean shouldExist, + UniEnumeratingStream otherStream, + BiEnumeratingJoiner[] joiners) { + var other = (AbstractUniEnumeratingStream) otherStream; + var joinerComber = BiDataJoinerComber. comb(joiners); + var parentBridgeB = + other.shareAndAddChild(new ForeBridgeUniEnumeratingStream(enumeratingStreamFactory, other)); + return enumeratingStreamFactory + .share(new IfExistsUniEnumeratingStream<>(enumeratingStreamFactory, this, parentBridgeB, shouldExist, + joinerComber.mergedJoiner(), joinerComber.mergedFiltering()), childStreamList::add); + } + + /** + * Convert the {@link UniEnumeratingStream} to a different {@link UniEnumeratingStream}, + * containing the set of tuples resulting from applying the group key mapping function + * on all tuples of the original stream. + * Neither tuple of the new stream {@link Objects#equals(Object, Object)} any other. + * + * @param groupKeyMapping mapping function to convert each element in the stream to a different element + * @param the type of a fact in the destination {@link UniEnumeratingStream}'s tuple; + * must honor {@link Object#hashCode() the general contract of hashCode}. + */ + protected AbstractUniEnumeratingStream groupBy(Function groupKeyMapping) { + // We do not expose this on the API, as this operation is not yet needed in any of the moves. + // The groupBy API will need revisiting if exposed as a feature of Neighborhoods API, do not expose as is. + GroupNodeConstructor> nodeConstructor = + oneKeyGroupBy(groupKeyMapping, Group1Mapping0CollectorUniNode::new); + return buildUniGroupBy(nodeConstructor); + } + + private AbstractUniEnumeratingStream + buildUniGroupBy(GroupNodeConstructor> nodeConstructor) { + var stream = shareAndAddChild(new UniGroupUniEnumeratingStream<>(enumeratingStreamFactory, this, nodeConstructor)); + return enumeratingStreamFactory.share(new AftBridgeUniEnumeratingStream<>(enumeratingStreamFactory, stream), + stream::setAftBridge); + } + + @Override + public UniEnumeratingStream map(UniEnumeratingMapper mapping) { + var stream = shareAndAddChild(new UniMapUniEnumeratingStream<>(enumeratingStreamFactory, this, mapping)); + return enumeratingStreamFactory.share(new AftBridgeUniEnumeratingStream<>(enumeratingStreamFactory, stream), + stream::setAftBridge); + } + + @Override + public BiEnumeratingStream map( + UniEnumeratingMapper mappingA, + UniEnumeratingMapper mappingB) { + var stream = shareAndAddChild(new BiMapUniEnumeratingStream<>(enumeratingStreamFactory, this, mappingA, mappingB)); + return enumeratingStreamFactory.share(new AftBridgeBiEnumeratingStream<>(enumeratingStreamFactory, stream), + stream::setAftBridge); + } + + @Override + public AbstractUniEnumeratingStream distinct() { + if (guaranteesDistinct()) { + return this; // Already distinct, no need to create a new stream. + } + return groupBy(ConstantLambdaUtils.identity()); + } + + public UniDataset createDataset() { + var stream = shareAndAddChild(new TerminalUniEnumeratingStream<>(enumeratingStreamFactory, this)); + return stream.getDataset(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/BiMapUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/BiMapUniEnumeratingStream.java similarity index 53% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/BiMapUniDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/BiMapUniEnumeratingStream.java index a56a3d9871..e897fc036b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/BiMapUniDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/BiMapUniEnumeratingStream.java @@ -1,33 +1,35 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; import java.util.Objects; import ai.timefold.solver.core.impl.bavet.uni.MapUniToBiNode; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.bi.AbstractBiDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeBiDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataMapper; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingMapper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi.AbstractBiEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeBiEnumeratingStream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -final class BiMapUniDataStream - extends AbstractBiDataStream { +final class BiMapUniEnumeratingStream + extends AbstractBiEnumeratingStream { - private final UniDataMapper mappingFunctionA; - private final UniDataMapper mappingFunctionB; - private @Nullable AftBridgeBiDataStream aftStream; + private final UniEnumeratingMapper mappingFunctionA; + private final UniEnumeratingMapper mappingFunctionB; + private @Nullable AftBridgeBiEnumeratingStream aftStream; - public BiMapUniDataStream(DataStreamFactory dataStreamFactory, AbstractUniDataStream parent, - UniDataMapper mappingFunctionA, UniDataMapper mappingFunctionB) { - super(dataStreamFactory, parent); + public BiMapUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractUniEnumeratingStream parent, + UniEnumeratingMapper mappingFunctionA, + UniEnumeratingMapper mappingFunctionB) { + super(enumeratingStreamFactory, parent); this.mappingFunctionA = mappingFunctionA; this.mappingFunctionB = mappingFunctionB; } - public void setAftBridge(AftBridgeBiDataStream aftStream) { + public void setAftBridge(AftBridgeBiEnumeratingStream aftStream) { this.aftStream = aftStream; } @@ -54,7 +56,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - BiMapUniDataStream that = (BiMapUniDataStream) object; + BiMapUniEnumeratingStream that = (BiMapUniEnumeratingStream) object; return Objects.equals(parent, that.parent) && Objects.equals(mappingFunctionA, that.mappingFunctionA) && Objects.equals(mappingFunctionB, that.mappingFunctionB); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/FilterUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/FilterUniEnumeratingStream.java similarity index 56% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/FilterUniDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/FilterUniEnumeratingStream.java index 5c03988605..265f292dae 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/FilterUniDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/FilterUniEnumeratingStream.java @@ -1,24 +1,25 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; 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.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; import org.jspecify.annotations.NullMarked; @NullMarked -final class FilterUniDataStream - extends AbstractUniDataStream { +final class FilterUniEnumeratingStream + extends AbstractUniEnumeratingStream { - private final UniDataFilter filter; + private final UniEnumeratingFilter filter; - public FilterUniDataStream(DataStreamFactory dataStreamFactory, AbstractUniDataStream parent, - UniDataFilter filter) { - super(dataStreamFactory, parent); + public FilterUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractUniEnumeratingStream parent, + UniEnumeratingFilter filter) { + super(enumeratingStreamFactory, parent); this.filter = Objects.requireNonNull(filter, "The filter cannot be null."); } @@ -38,7 +39,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) { return true; - } else if (o instanceof FilterUniDataStream other) { + } else if (o instanceof FilterUniEnumeratingStream other) { return parent == other.parent && filter == other.filter; } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/ForEachIncludingPinnedDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/ForEachIncludingPinnedEnumeratingStream.java similarity index 53% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/ForEachIncludingPinnedDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/ForEachIncludingPinnedEnumeratingStream.java index de66d7e164..fa4d2c8b44 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/ForEachIncludingPinnedDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/ForEachIncludingPinnedEnumeratingStream.java @@ -1,25 +1,26 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; import java.util.Objects; import ai.timefold.solver.core.impl.bavet.common.TupleSource; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; import org.jspecify.annotations.NullMarked; @NullMarked -public final class ForEachIncludingPinnedDataStream - extends AbstractForEachDataStream +public final class ForEachIncludingPinnedEnumeratingStream + extends AbstractForEachEnumeratingStream implements TupleSource { - public ForEachIncludingPinnedDataStream(DataStreamFactory dataStreamFactory, Class forEachClass, + public ForEachIncludingPinnedEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + Class forEachClass, boolean includeNull) { - super(dataStreamFactory, forEachClass, includeNull); + super(enumeratingStreamFactory, forEachClass, includeNull); } @Override public boolean equals(Object o) { - return o instanceof ForEachIncludingPinnedDataStream that && + return o instanceof ForEachIncludingPinnedEnumeratingStream that && Objects.equals(shouldIncludeNull, that.shouldIncludeNull) && Objects.equals(forEachClass, that.forEachClass); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/IfExistsUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/IfExistsUniEnumeratingStream.java similarity index 66% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/IfExistsUniDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/IfExistsUniEnumeratingStream.java index f9a93b8fb9..0664f4523c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/IfExistsUniDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/IfExistsUniEnumeratingStream.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; import java.util.Objects; import java.util.Set; @@ -9,32 +9,34 @@ 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.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.IfExistsDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.ForeBridgeUniDataStream; -import ai.timefold.solver.core.impl.move.streams.dataset.joiner.DefaultBiDataJoiner; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.BiEnumeratingFilter; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.IfExistsEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.ForeBridgeUniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.joiner.DefaultBiEnumeratingJoiner; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -final class IfExistsUniDataStream - extends AbstractUniDataStream - implements IfExistsDataStream { +final class IfExistsUniEnumeratingStream + extends AbstractUniEnumeratingStream + implements IfExistsEnumeratingStream { - private final AbstractUniDataStream parentA; - private final ForeBridgeUniDataStream parentBridgeB; + private final AbstractUniEnumeratingStream parentA; + private final ForeBridgeUniEnumeratingStream parentBridgeB; private final boolean shouldExist; - private final DefaultBiDataJoiner joiner; - private final @Nullable BiDataFilter filtering; + private final DefaultBiEnumeratingJoiner joiner; + private final @Nullable BiEnumeratingFilter filtering; - public IfExistsUniDataStream(DataStreamFactory dataStreamFactory, AbstractUniDataStream parentA, - ForeBridgeUniDataStream parentBridgeB, boolean shouldExist, DefaultBiDataJoiner joiner, - @Nullable BiDataFilter filtering) { - super(dataStreamFactory); + public IfExistsUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractUniEnumeratingStream parentA, + ForeBridgeUniEnumeratingStream parentBridgeB, boolean shouldExist, + DefaultBiEnumeratingJoiner joiner, + @Nullable BiEnumeratingFilter filtering) { + super(enumeratingStreamFactory); this.parentA = parentA; this.parentBridgeB = parentBridgeB; this.shouldExist = shouldExist; @@ -43,10 +45,10 @@ public IfExistsUniDataStream(DataStreamFactory dataStreamFactory, Abs } @Override - public void collectActiveDataStreams(Set> dataStreamSet) { - parentA.collectActiveDataStreams(dataStreamSet); - parentBridgeB.collectActiveDataStreams(dataStreamSet); - dataStreamSet.add(this); + public void collectActiveEnumeratingStreams(Set> enumeratingStreamSet) { + parentA.collectActiveEnumeratingStreams(enumeratingStreamSet); + parentBridgeB.collectActiveEnumeratingStreams(enumeratingStreamSet); + enumeratingStreamSet.add(this); } @Override @@ -95,7 +97,7 @@ private AbstractIfExistsNode, B> getNode(IndexerFactory indexerFa @Override public boolean equals(Object o) { - return o instanceof IfExistsUniDataStream that + return o instanceof IfExistsUniEnumeratingStream that && shouldExist == that.shouldExist && Objects.equals(parentA, that.parentA) && Objects.equals(parentBridgeB, that.parentBridgeB) @@ -114,17 +116,17 @@ public String toString() { } @Override - public AbstractDataStream getTupleSource() { + public AbstractEnumeratingStream getTupleSource() { return parentA.getTupleSource(); } @Override - public AbstractDataStream getLeftParent() { + public AbstractEnumeratingStream getLeftParent() { return parentA; } @Override - public AbstractDataStream getRightParent() { + public AbstractEnumeratingStream getRightParent() { return parentBridgeB; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/TerminalUniEnumeratingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/TerminalUniEnumeratingStream.java new file mode 100644 index 0000000000..0c3ee32915 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/TerminalUniEnumeratingStream.java @@ -0,0 +1,40 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; + +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.TerminalEnumeratingStream; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +final class TerminalUniEnumeratingStream + extends AbstractUniEnumeratingStream + implements TerminalEnumeratingStream, UniDataset> { + + private final UniDataset dataset; + + public TerminalUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractUniEnumeratingStream parent) { + super(enumeratingStreamFactory, parent); + this.dataset = new UniDataset<>(enumeratingStreamFactory, this); + } + + @Override + public void buildNode(DataNodeBuildHelper buildHelper) { + assertEmptyChildStreamList(); + var inputStoreIndex = buildHelper.reserveTupleStoreIndex(parent.getTupleSource()); + buildHelper.putInsertUpdateRetract(this, dataset.instantiate(inputStoreIndex)); + } + + @Override + public UniDataset getDataset() { + return dataset; + } + + @Override + public String toString() { + return "Terminal node"; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniDataset.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniDataset.java new file mode 100644 index 0000000000..1094060a7e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniDataset.java @@ -0,0 +1,22 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; + +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDataset; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class UniDataset extends AbstractDataset> { + + public UniDataset(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractUniEnumeratingStream parent) { + super(enumeratingStreamFactory, parent); + } + + @Override + public UniDatasetInstance instantiate(int storeIndex) { + return new UniDatasetInstance<>(this, storeIndex); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniDatasetInstance.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniDatasetInstance.java similarity index 83% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniDatasetInstance.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniDatasetInstance.java index b0aa6205ba..6dabba658a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniDatasetInstance.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniDatasetInstance.java @@ -1,11 +1,11 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; import java.util.Iterator; import java.util.Random; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset; -import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDatasetInstance; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDataset; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDatasetInstance; import ai.timefold.solver.core.impl.util.ElementAwareList; import ai.timefold.solver.core.impl.util.ElementAwareListEntry; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniGroupUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniGroupUniEnumeratingStream.java similarity index 61% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniGroupUniDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniGroupUniEnumeratingStream.java index f68580ac0e..b5dd284b63 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniGroupUniDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniGroupUniEnumeratingStream.java @@ -1,30 +1,31 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; import java.util.Objects; import ai.timefold.solver.core.impl.bavet.common.GroupNodeConstructor; import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeUniDataStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeUniEnumeratingStream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -final class UniGroupUniDataStream - extends AbstractUniDataStream { +final class UniGroupUniEnumeratingStream + extends AbstractUniEnumeratingStream { private final GroupNodeConstructor> nodeConstructor; - private @Nullable AftBridgeUniDataStream aftStream; + private @Nullable AftBridgeUniEnumeratingStream aftStream; - public UniGroupUniDataStream(DataStreamFactory dataStreamFactory, AbstractUniDataStream parent, + public UniGroupUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractUniEnumeratingStream parent, GroupNodeConstructor> nodeConstructor) { - super(dataStreamFactory, parent); + super(enumeratingStreamFactory, parent); this.nodeConstructor = nodeConstructor; } - public void setAftBridge(AftBridgeUniDataStream aftStream) { + public void setAftBridge(AftBridgeUniEnumeratingStream aftStream) { this.aftStream = aftStream; } @@ -36,7 +37,7 @@ public void setAftBridge(AftBridgeUniDataStream aftStream) { public void buildNode(DataNodeBuildHelper buildHelper) { var aftStreamChildList = aftStream.getChildStreamList(); nodeConstructor.build(buildHelper, parent.getTupleSource(), aftStream, aftStreamChildList, this, - dataStreamFactory.getEnvironmentMode()); + enumeratingStreamFactory.getEnvironmentMode()); } // ************************************************************************ @@ -49,7 +50,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - var that = (UniGroupUniDataStream) object; + var that = (UniGroupUniEnumeratingStream) object; return Objects.equals(parent, that.parent) && Objects.equals(nodeConstructor, that.nodeConstructor); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniMapUniDataStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniMapUniEnumeratingStream.java similarity index 62% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniMapUniDataStream.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniMapUniEnumeratingStream.java index 9eb97a6ff2..029718216c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniMapUniDataStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniMapUniEnumeratingStream.java @@ -1,30 +1,31 @@ -package ai.timefold.solver.core.impl.move.streams.dataset.uni; +package ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni; import java.util.Objects; import ai.timefold.solver.core.impl.bavet.uni.MapUniToUniNode; -import ai.timefold.solver.core.impl.move.streams.dataset.DataStreamFactory; -import ai.timefold.solver.core.impl.move.streams.dataset.common.DataNodeBuildHelper; -import ai.timefold.solver.core.impl.move.streams.dataset.common.bridge.AftBridgeUniDataStream; -import ai.timefold.solver.core.impl.move.streams.maybeapi.UniDataMapper; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingMapper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.DataNodeBuildHelper; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.bridge.AftBridgeUniEnumeratingStream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked -final class UniMapUniDataStream - extends AbstractUniDataStream { +final class UniMapUniEnumeratingStream + extends AbstractUniEnumeratingStream { - private final UniDataMapper mappingFunction; - private @Nullable AftBridgeUniDataStream aftStream; + private final UniEnumeratingMapper mappingFunction; + private @Nullable AftBridgeUniEnumeratingStream aftStream; - public UniMapUniDataStream(DataStreamFactory dataStreamFactory, AbstractUniDataStream parent, - UniDataMapper mappingFunction) { - super(dataStreamFactory, parent); + public UniMapUniEnumeratingStream(EnumeratingStreamFactory enumeratingStreamFactory, + AbstractUniEnumeratingStream parent, + UniEnumeratingMapper mappingFunction) { + super(enumeratingStreamFactory, parent); this.mappingFunction = mappingFunction; } - public void setAftBridge(AftBridgeUniDataStream aftStream) { + public void setAftBridge(AftBridgeUniEnumeratingStream aftStream) { this.aftStream = aftStream; } @@ -58,7 +59,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - UniMapUniDataStream that = (UniMapUniDataStream) object; + UniMapUniEnumeratingStream that = (UniMapUniEnumeratingStream) object; return Objects.equals(parent, that.parent) && Objects.equals(mappingFunction, that.mappingFunction); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/package-info.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/package-info.java similarity index 90% rename from core/src/main/java/ai/timefold/solver/core/impl/move/streams/package-info.java rename to core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/package-info.java index c04734fe5a..5bff18dad3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/streams/package-info.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/package-info.java @@ -1,5 +1,5 @@ /** - * This package contains a work-in-progress implementation of a major new feature, codenamed "Move Streams". + * This package contains a work-in-progress implementation of a major new feature, codenamed "Neighborhoods". * Nothing in this package is considered stable, and it will change on a regular basis. * As the feature matures, the package structure will be refactored, * classes and interfaces moved to other packages, @@ -17,4 +17,4 @@ * Therefore we are doing this work in the main development branch, * but entirely separately from the stable solver. */ -package ai.timefold.solver.core.impl.move.streams; \ No newline at end of file +package ai.timefold.solver.core.impl.neighborhood.stream; \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultBiFromBiSamplingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultBiFromBiSamplingStream.java new file mode 100644 index 0000000000..a77dd116b8 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultBiFromBiSamplingStream.java @@ -0,0 +1,27 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.sampling; + +import java.util.Objects; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.BiMoveConstructor; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.BiSamplingStream; +import ai.timefold.solver.core.impl.neighborhood.move.FromBiUniMoveStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.bi.BiDataset; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class DefaultBiFromBiSamplingStream implements BiSamplingStream { + + private final BiDataset dataset; + + public DefaultBiFromBiSamplingStream(BiDataset dataset) { + this.dataset = Objects.requireNonNull(dataset); + } + + @Override + public MoveStream asMove(BiMoveConstructor moveConstructor) { + return new FromBiUniMoveStream<>(dataset, Objects.requireNonNull(moveConstructor)); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultBiFromUnisSamplingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultBiFromUnisSamplingStream.java new file mode 100644 index 0000000000..a072a8799b --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultBiFromUnisSamplingStream.java @@ -0,0 +1,33 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.sampling; + +import java.util.Objects; +import java.util.function.BiPredicate; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.BiMoveConstructor; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.BiSamplingStream; +import ai.timefold.solver.core.impl.neighborhood.move.FromUniBiMoveStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.UniDataset; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class DefaultBiFromUnisSamplingStream implements BiSamplingStream { + + private final UniDataset leftDataset; + private final UniDataset rightDataset; + private final BiPredicate filter; + + public DefaultBiFromUnisSamplingStream(UniDataset leftDataset, UniDataset rightDataset, + BiPredicate filter) { + this.leftDataset = Objects.requireNonNull(leftDataset); + this.rightDataset = Objects.requireNonNull(rightDataset); + this.filter = Objects.requireNonNull(filter); + } + + @Override + public MoveStream asMove(BiMoveConstructor moveConstructor) { + return new FromUniBiMoveStream<>(leftDataset, rightDataset, filter, Objects.requireNonNull(moveConstructor)); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultUniSamplingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultUniSamplingStream.java new file mode 100644 index 0000000000..237c19b1b6 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/DefaultUniSamplingStream.java @@ -0,0 +1,35 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.sampling; + +import java.util.Objects; +import java.util.function.BiPredicate; + +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.UniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.BiSamplingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.AbstractUniEnumeratingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.UniDataset; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class DefaultUniSamplingStream implements InnerUniSamplingStream { + + private final UniDataset dataset; + + public DefaultUniSamplingStream(UniDataset dataset) { + this.dataset = Objects.requireNonNull(dataset); + } + + @Override + public BiSamplingStream pick(UniEnumeratingStream uniEnumeratingStream, + BiPredicate filter) { + return new DefaultBiFromUnisSamplingStream<>(dataset, + ((AbstractUniEnumeratingStream) uniEnumeratingStream).createDataset(), + filter); + } + + @Override + public UniDataset getDataset() { + return dataset; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/InnerSamplingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/InnerSamplingStream.java new file mode 100644 index 0000000000..5b96dccf81 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/InnerSamplingStream.java @@ -0,0 +1,14 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.sampling; + +import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.SamplingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.common.AbstractDataset; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface InnerSamplingStream extends SamplingStream { + + AbstractDataset getDataset(); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/InnerUniSamplingStream.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/InnerUniSamplingStream.java new file mode 100644 index 0000000000..7cb11e552c --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/sampling/InnerUniSamplingStream.java @@ -0,0 +1,16 @@ +package ai.timefold.solver.core.impl.neighborhood.stream.sampling; + +import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.sampling.UniSamplingStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.UniDataset; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface InnerUniSamplingStream + extends InnerSamplingStream>, UniSamplingStream { + + @Override + UniDataset getDataset(); + +} 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 12a7b4938b..ed0d823f7a 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 @@ -31,9 +31,9 @@ import ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport; 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.MoveRepository; -import ai.timefold.solver.core.impl.move.MoveStreamsBasedMoveRepository; import ai.timefold.solver.core.impl.move.director.MoveDirector; +import ai.timefold.solver.core.impl.neighborhood.MoveRepository; +import ai.timefold.solver.core.impl.neighborhood.NeighborhoodsBasedMoveRepository; 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; @@ -308,7 +308,7 @@ public void executeMove(Move move) { @Override public InnerScore executeTemporaryMove(Move move, boolean assertMoveScoreFromScratch) { - // This change and resulting before/after events will not be propagated to move stream session, + // This change and resulting before/after events will not be propagated to a neighborhood session, // as they will be immediately undone. // Moves will only be re-generated once the solution has actually changed, // which will happen at the end of the step, after executeMove(...) was called. @@ -459,8 +459,8 @@ public void afterEntityAdded(EntityDescriptor entityDescriptor, Objec lookUpManager.addWorkingObject(entity); } if (!allChangesWillBeUndoneBeforeStepEnds) { - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.insert(entity); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.insert(entity); } // Some selectors depend on this revision value to detect changes in entity value ranges. // Therefore, we need to reset the value range state, but the working solution does not change. @@ -482,8 +482,8 @@ public void afterVariableChanged(VariableDescriptor variableDescripto if (variableDescriptor.isGenuineAndUninitialized(entity)) { workingInitScore--; } - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.update(entity); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.update(entity); } variableListenerSupport.afterVariableChanged(variableDescriptor, entity); } @@ -498,8 +498,8 @@ public void afterListVariableElementAssigned(ListVariableDescriptor v workingInitScore++; assertInitScoreZeroOrLess(); } - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.update(element); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.update(element); } } @@ -514,8 +514,8 @@ public void afterListVariableElementUnassigned(ListVariableDescriptor workingInitScore--; } variableListenerSupport.afterElementUnassigned(variableDescriptor, element); - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.update(element); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.update(element); } } @@ -539,8 +539,8 @@ Attempting to change list variable (%s) on an entity (%s) in range [%d, %d), whi public void afterListVariableChanged(ListVariableDescriptor variableDescriptor, Object entity, int fromIndex, int toIndex) { variableListenerSupport.afterListVariableChanged(variableDescriptor, entity, fromIndex, toIndex); - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.update(entity); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.update(entity); } } @@ -557,8 +557,8 @@ public void afterEntityRemoved(EntityDescriptor entityDescriptor, Obj lookUpManager.removeWorkingObject(entity); } if (!allChangesWillBeUndoneBeforeStepEnds) { - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.retract(entity); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.retract(entity); } // Some selectors depend on this revision value to detect changes in entity value ranges. // Therefore, we need to reset the value range state, but the working solution does not change. @@ -581,8 +581,8 @@ public void afterProblemFactAdded(Object problemFact) { lookUpManager.addWorkingObject(problemFact); } variableListenerSupport.resetWorkingSolution(); // TODO do not nuke the variable listeners - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.insert(problemFact); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.insert(problemFact); } } @@ -597,8 +597,8 @@ public void afterProblemPropertyChanged(Object problemFactOrEntity) { setWorkingSolution(workingSolution); // Nuke everything and recalculate, constraint weights have changed. } else { variableListenerSupport.resetWorkingSolution(); // TODO do not nuke the variable listeners - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.update(problemFactOrEntity); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.update(problemFactOrEntity); } } } @@ -618,8 +618,8 @@ public void afterProblemFactRemoved(Object problemFact) { lookUpManager.removeWorkingObject(problemFact); } variableListenerSupport.resetWorkingSolution(); // TODO do not nuke the variable listeners - if (moveRepository instanceof MoveStreamsBasedMoveRepository moveStreamsBasedMoveRepository) { - moveStreamsBasedMoveRepository.retract(problemFact); + if (moveRepository instanceof NeighborhoodsBasedMoveRepository neighborhoodsBasedMoveRepository) { + neighborhoodsBasedMoveRepository.retract(problemFact); } } @@ -1039,7 +1039,7 @@ public Builder_ withExpectShadowVariablesInCorrectState(boolean expectShadowVari * Optionally makes the score director a derived one; most score directors do not require this. * Derived score directors may make choices which the main score director cannot 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. + * Derived score directors do not support Neighborhoods API, as they are only used to calculate the score. * * @return this */ diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java index 46fb032734..738f4f1a2c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java @@ -30,8 +30,8 @@ import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; -import ai.timefold.solver.core.impl.move.MoveRepository; import ai.timefold.solver.core.impl.move.director.MoveDirector; +import ai.timefold.solver.core.impl.neighborhood.MoveRepository; 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; @@ -105,7 +105,7 @@ static > ConstraintAnalysis getConstraintAn * Therefore move repository cannot be injected at score director construction time. * * A phase may not need a move repository at all, - * such as construction heuristics, which currently does not support Move Streams. + * such as construction heuristics, which currently does not support Neighborhoods API. * * Each phase is responsible for calling this method to set its repository, * and also calling it again at the end to null it out. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/util/ConstantLambdaUtils.java b/core/src/main/java/ai/timefold/solver/core/impl/util/ConstantLambdaUtils.java index 4e6f5860d1..e07a49c636 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/util/ConstantLambdaUtils.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/util/ConstantLambdaUtils.java @@ -16,7 +16,6 @@ import ai.timefold.solver.core.api.function.ToLongQuadFunction; import ai.timefold.solver.core.api.function.ToLongTriFunction; import ai.timefold.solver.core.api.function.TriFunction; -import ai.timefold.solver.core.impl.move.streams.maybeapi.BiDataFilter; /** * A class that holds common lambdas that are guaranteed to be the same across method calls. @@ -37,10 +36,6 @@ public final class ConstantLambdaUtils { @SuppressWarnings("rawtypes") private static final BiPredicate NOT_EQUALS = (a, b) -> !Objects.equals(a, b); - @SuppressWarnings("rawtypes") - private static final BiDataFilter NOT_EQUALS_FOR_DATA_STREAMS = - (BiDataFilter) (solutionView, a, b) -> !Objects.equals(a, b); - @SuppressWarnings("rawtypes") private static final BiFunction BI_PICK_FIRST = (a, b) -> a; @@ -155,17 +150,6 @@ public static BiPredicate notEquals() { return NOT_EQUALS; } - /** - * Returns a {@link BiDataFilter} that return true if and only if its inputs are not equal according to - * {@link Objects#equals(Object, Object)}. - * - * @return never null - */ - @SuppressWarnings("unchecked") - public static BiDataFilter notEqualsForDataStreams() { - return NOT_EQUALS_FOR_DATA_STREAMS; - } - /** * Returns a {@link BiFunction} that returns its first input. * 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 b5b0c16f04..0c23315575 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 @@ -244,7 +244,7 @@ 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. + * In fact, Neighborhoods API is 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 list of unused indexes. * diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/ElementPosition.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/ElementPosition.java index 08ebe95131..93692bab91 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/ElementPosition.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/ElementPosition.java @@ -16,7 +16,7 @@ * This prevents accidental use of {@link UnassignedElement} in places where {@link PositionInList} is expected, * catching this error as early as possible. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -25,7 +25,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. */ @NullMarked public sealed interface ElementPosition permits PositionInList, UnassignedElement { diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/GenuineVariableMetaModel.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/GenuineVariableMetaModel.java index 7bcf276346..792ba026c2 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/GenuineVariableMetaModel.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/GenuineVariableMetaModel.java @@ -8,7 +8,7 @@ * A {@link VariableMetaModel} that represents a @{@link PlanningVariable basic planning variable}. * *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -17,7 +17,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param the solution type * @param the entity type @@ -28,9 +29,4 @@ public sealed interface GenuineVariableMetaModel extends VariableMetaModel permits PlanningVariableMetaModel, PlanningListVariableMetaModel { - @Override - default boolean isGenuine() { - return true; - } - } 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 67c554174e..c8b4de2cfc 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 @@ -12,7 +12,7 @@ * Represents the meta-model of an entity. * Gives access to the entity's variable meta-models. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -21,7 +21,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param The solution type. * @param The entity type. @@ -58,7 +59,7 @@ public interface PlanningEntityMetaModel { @SuppressWarnings({ "unchecked", "rawtypes" }) default List> genuineVariables() { return (List) variables().stream() - .filter(VariableMetaModel::isGenuine) + .filter(v -> v instanceof GenuineVariableMetaModel) .map(v -> (GenuineVariableMetaModel) v) .toList(); } @@ -91,12 +92,30 @@ default GenuineVariableMetaModel genuineVar @SuppressWarnings("unchecked") default GenuineVariableMetaModel genuineVariable(String variableName) { var variable = variable(variableName); - if (!variable.isGenuine()) { + if (!(variable instanceof GenuineVariableMetaModel genuineVariable)) { throw new IllegalArgumentException( "The variableName (%s) exists among variables (%s) but is not genuine.".formatted(variableName, variables())); } - return (GenuineVariableMetaModel) variable; + return (GenuineVariableMetaModel) genuineVariable; + } + + /** + * 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 + * @throws IllegalArgumentException if the variable is not of the given type. + */ + default GenuineVariableMetaModel genuineVariable(String variableName, + Class variableClass) { + var variable = variable(variableName, variableClass); + if (!(variable instanceof GenuineVariableMetaModel genuineVariable)) { + throw new IllegalArgumentException( + "The variableName (%s) exists among variables (%s) but is not genuine.".formatted(variableName, + variables())); + } + return genuineVariable; } /** @@ -116,6 +135,30 @@ default VariableMetaModel variable(String v "The variableName (%s) does not exist in the variables (%s).".formatted(variableName, variables())); } + /** + * As defined by {@link #variable(String)}, + * but only succeeds if the variable is of a given type. + * + * @return A variable declared by the entity. + * @throws IllegalArgumentException where {@link #hasVariable(String)} would have returned false. + * @throws IllegalArgumentException if the variable is not of the given type. + */ + @SuppressWarnings("unchecked") + default VariableMetaModel variable(String variableName, Class variableClass) { + for (var variableMetaModel : variables()) { + if (variableMetaModel.name().equals(variableName)) { + if (!variableClass.isAssignableFrom(variableMetaModel.type())) { + throw new IllegalArgumentException( + "The variableName (%s) exists among variables (%s) but is not of type (%s).".formatted(variableName, + variables(), variableClass.getCanonicalName())); + } + return (VariableMetaModel) variableMetaModel; + } + } + throw new IllegalArgumentException( + "The variableName (%s) does not exist in the variables (%s).".formatted(variableName, variables())); + } + /** * Checks whether a variable is present on the entity. * @@ -132,11 +175,11 @@ default boolean hasVariable(String variableName) { } /** - * As defined by {@link #genuineVariable()} ()}, + * As defined by {@link #genuineVariable()}, * but only succeeds if the variable is a {@link PlanningVariable basic planning variable}. */ @SuppressWarnings("unchecked") - default PlanningVariableMetaModel planningVariable() { + default PlanningVariableMetaModel basicVariable() { return (PlanningVariableMetaModel) genuineVariable(); } @@ -145,16 +188,27 @@ default PlanningVariableMetaModel planningV * but only succeeds if the variable is a {@link PlanningVariable basic planning variable}. */ @SuppressWarnings("unchecked") - default PlanningVariableMetaModel planningVariable(String variableName) { + default PlanningVariableMetaModel basicVariable(String variableName) { return (PlanningVariableMetaModel) variable(variableName); } + /** + * As defined by {@link #basicVariable(String)}, + * but only succeeds if the variable is of a given type. + * + * @throws IllegalArgumentException if the variable is not of the given type. + */ + default PlanningVariableMetaModel basicVariable(String variableName, + Class variableClass) { + return (PlanningVariableMetaModel) variable(variableName, variableClass); + } + /** * As defined by {@link #genuineVariable()}, * but only succeeds if the variable is a {@link PlanningListVariable planning list variable}. */ @SuppressWarnings("unchecked") - default PlanningListVariableMetaModel planningListVariable() { + default PlanningListVariableMetaModel listVariable() { return (PlanningListVariableMetaModel) genuineVariable(); } @@ -163,19 +217,41 @@ default PlanningListVariableMetaModel plann * but only succeeds if the variable is a {@link PlanningListVariable planning list variable}. */ @SuppressWarnings("unchecked") - default PlanningListVariableMetaModel planningListVariable(String variableName) { + default PlanningListVariableMetaModel listVariable(String variableName) { return (PlanningListVariableMetaModel) variable(variableName); } + /** + * As defined by {@link #listVariable(String)}, + * but only succeeds if the variable is of a given type. + * + * @throws IllegalArgumentException if the variable is not of the given type. + */ + default PlanningListVariableMetaModel listVariable(String variableName, + Class variableClass) { + return (PlanningListVariableMetaModel) variable(variableName, variableClass); + } + /** * As defined by {@link #variable(String)}, - * but only succeeds if the variable is a shadow variable}. + * but only succeeds if the variable is a shadow variable. */ @SuppressWarnings("unchecked") default ShadowVariableMetaModel shadowVariable(String variableName) { return (ShadowVariableMetaModel) variable(variableName); } + /** + * As defined by {@link #shadowVariable(String)}, + * but only succeeds if the variable is of a given type. + * + * @throws IllegalArgumentException if the variable is not of the given type. + */ + default ShadowVariableMetaModel shadowVariable(String variableName, + Class variableClass) { + return (ShadowVariableMetaModel) variable(variableName, variableClass); + } + /** * Returns whether the entity declares any genuine variables. * @@ -183,7 +259,7 @@ default ShadowVariableMetaModel shadowVaria */ default boolean isGenuine() { for (var variableMetaModel : variables()) { - if (variableMetaModel.isGenuine()) { + if (variableMetaModel instanceof GenuineVariableMetaModel) { return true; } } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningListVariableMetaModel.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningListVariableMetaModel.java index 0dc0b12e2f..ee92bf18ff 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningListVariableMetaModel.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningListVariableMetaModel.java @@ -7,7 +7,7 @@ /** * A {@link VariableMetaModel} that represents a @{@link PlanningVariable list planning variable}. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -16,7 +16,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param the solution type * @param the entity type @@ -26,11 +27,6 @@ public non-sealed interface PlanningListVariableMetaModel extends GenuineVariableMetaModel { - @Override - default boolean isList() { - return true; - } - /** * Returns whether the planning variable allows values not to be assigned to any entity's list variable. * diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningSolutionMetaModel.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningSolutionMetaModel.java index 8de29a2d9e..aad851cd2c 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningSolutionMetaModel.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PlanningSolutionMetaModel.java @@ -11,7 +11,7 @@ * Represents the meta-model of a {@link PlanningSolution}. * Allows access to information about all the entities and variables. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -20,7 +20,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param the type of the solution */ 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 b70eacff23..54b1225624 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 @@ -8,7 +8,7 @@ * A {@link VariableMetaModel} that represents a @{@link PlanningVariable basic planning variable}. * *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -17,7 +17,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param the solution type * @param the entity type @@ -27,11 +28,6 @@ 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/PositionInList.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PositionInList.java index 41531b66dd..0f469e5a15 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PositionInList.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/PositionInList.java @@ -9,7 +9,7 @@ * Within that one list, the index is unique for each value and therefore the instances are comparable. * Comparing them between different lists has no meaning. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -18,7 +18,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * */ @NullMarked 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 8352e88d10..c07c41d13d 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 @@ -11,7 +11,7 @@ * (see {@link PlanningVariableMetaModel} and {@link PlanningListVariableMetaModel}) * using a {@link ShadowVariable} provided either internally or by the user. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -20,7 +20,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param the solution type * @param the entity type @@ -30,14 +31,4 @@ 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/UnassignedElement.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/UnassignedElement.java index 180d878529..2a0dcb1d23 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/UnassignedElement.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/UnassignedElement.java @@ -8,7 +8,7 @@ * Identifies that a given value was not found in any {@link PlanningEntity}'s list variables. * Singleton instance can be accessed by {@link ElementPosition#unassigned()}. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -17,7 +17,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. */ @NullMarked public sealed interface UnassignedElement 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 b966bc22b0..5076c2640a 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 @@ -1,15 +1,12 @@ package ai.timefold.solver.core.preview.api.domain.metamodel; -import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; -import ai.timefold.solver.core.api.domain.variable.PlanningVariable; - import org.jspecify.annotations.NullMarked; /** * Describes a variable in the domain model. * See extending interfaces for more specific types of variables. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -18,7 +15,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param * @param @@ -49,23 +47,4 @@ public sealed interface VariableMetaModel */ String name(); - /** - * Whether this variable is a @{@link PlanningListVariable} or a {@link PlanningVariable}. - * If list, this is guaranteed to extend {@link PlanningListVariableMetaModel}. - * Otherwise it is guaranteed to extend either {@link PlanningVariableMetaModel} or {@link ShadowVariableMetaModel}. - * - * @return true if this variable is a genuine @{@link PlanningListVariable}, false otherwise - */ - boolean isList(); - - /** - * Whether this variable is a genuine variable. - * If genuine, this is guaranteed to extend either {@link PlanningVariableMetaModel} or - * {@link PlanningListVariableMetaModel}. - * Otherwise it is guaranteed to extend {@link ShadowVariableMetaModel}. - * - * @return true if this variable is genuine, false otherwise - */ - boolean isGenuine(); - } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/package-info.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/package-info.java index 0b07976bf5..71ae909a50 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/package-info.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/package-info.java @@ -4,7 +4,7 @@ * typically for use within {@link ai.timefold.solver.core.preview.api.move.Move}s * *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -13,6 +13,7 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. */ package ai.timefold.solver.core.preview.api.domain.metamodel; \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/solution/diff/package-info.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/solution/diff/package-info.java index 42f132476b..763139369c 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/solution/diff/package-info.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/solution/diff/package-info.java @@ -12,6 +12,7 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize it. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. */ package ai.timefold.solver.core.preview.api.domain.solution.diff; \ No newline at end of file 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 478e2d55f1..20a1b48a78 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 @@ -28,7 +28,7 @@ * For tabu search, a Move should implement {@link Object#equals(Object)} and {@link Object#hashCode()}, * {@link #extractPlanningEntities()} and {@link #extractPlanningValues()}. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -37,7 +37,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param */ 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 4da454699a..dec4b6f2fe 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 @@ -13,7 +13,7 @@ * Contains all reading and mutating methods available to a {@link Move} * in order to change the state of a {@link PlanningSolution planning solution}. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -22,7 +22,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param */ 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 e5f19f1d3c..5eabf49778 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 @@ -12,7 +12,7 @@ * Allows to transfer an entity or fact instance (often from another {@link Thread}) * to another {@link ScoreDirector}'s internal working instance. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -21,7 +21,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. */ public interface Rebaser { 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 1d9029fd50..07385d5e14 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 @@ -17,7 +17,7 @@ /** * Allows read-only access to the state of the solution that is being operated on by the {@link Move}. *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -26,7 +26,8 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. * * @param */ diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/package-info.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/package-info.java index b9835a65f3..3b317b6b47 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/package-info.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/package-info.java @@ -4,7 +4,7 @@ * It will eventually replace the move selector framework. * *

- * This package and all of its contents are part of the Move Streams API, + * This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. * There are no guarantees for backward compatibility; * any class, method, or field may change or be removed without prior notice, @@ -13,6 +13,7 @@ * We encourage you to try the API and give us feedback on your experience with it, * before we finalize the API. * Please direct your feedback to - * Timefold Solver Github. + * Timefold Solver Github + * or to Timefold Discord. */ package ai.timefold.solver.core.preview.api.move; \ No newline at end of file diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index 14821ae11b..9ebbdbd4c6 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -1397,7 +1397,7 @@ - + @@ -1595,7 +1595,7 @@ - + diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/descriptor/PlanningSolutionMetaModelTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/descriptor/PlanningSolutionMetaModelTest.java index 9ff94dfed5..3846b630e2 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/descriptor/PlanningSolutionMetaModelTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/descriptor/PlanningSolutionMetaModelTest.java @@ -120,18 +120,6 @@ void hasProperType() { .isEqualTo(TestdataValue.class); } - @Test - void isGenuine() { - assertThat(variableMetaModel.isGenuine()) - .isTrue(); - } - - @Test - void isNotList() { - assertThat(variableMetaModel.isList()) - .isFalse(); - } - } } @@ -240,18 +228,6 @@ void hasProperType() { .isEqualTo(TestdataListValue.class); } - @Test - void isGenuine() { - assertThat(variableMetaModel.isGenuine()) - .isTrue(); - } - - @Test - void isList() { - assertThat(variableMetaModel.isList()) - .isTrue(); - } - } } @@ -319,18 +295,6 @@ void hasProperType() { .isEqualTo(TestdataListEntity.class); } - @Test - void isNotGenuine() { - assertThat(variableMetaModel.isGenuine()) - .isFalse(); - } - - @Test - void isList() { - assertThat(variableMetaModel.isList()) - .isFalse(); - } - } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSourceTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSourceTest.java index b3667973ef..065a115b8d 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSourceTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSourceTest.java @@ -43,11 +43,11 @@ class RootVariableSourceTest { private final PlanningEntityMetaModel shadowEntityMetaModel = planningSolutionMetaModel.entity(TestdataInvalidDeclarativeValue.class); private final ShadowVariableMetaModel previousElementMetaModel = - shadowEntityMetaModel.shadowVariable("previous"); + shadowEntityMetaModel.shadowVariable("previous", TestdataInvalidDeclarativeValue.class); private final ShadowVariableMetaModel shadowVariableMetaModel = - shadowEntityMetaModel.shadowVariable("shadow"); + shadowEntityMetaModel.shadowVariable("shadow", TestdataInvalidDeclarativeValue.class); private final ShadowVariableMetaModel dependencyMetaModel = - shadowEntityMetaModel.shadowVariable("dependency"); + shadowEntityMetaModel.shadowVariable("dependency", TestdataInvalidDeclarativeValue.class); private void assertChainToVariableEntity(VariableSourceReference variableSourceReference, String... expectedNames) { var chain = variableSourceReference.chainFromRootEntityToVariableEntity(); 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 024586599d..111e7721bf 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 @@ -55,7 +55,7 @@ void readBasicVariable() { var solutionMetaModel = TestdataSolution.buildSolutionDescriptor() .getMetaModel(); var variableMetaModel = solutionMetaModel.entity(TestdataEntity.class) - . planningVariable("value"); + .basicVariable("value", TestdataValue.class); var mockScoreDirector = mock(InnerScoreDirector.class); var moveDirector = new MoveDirector(mockScoreDirector); @@ -69,7 +69,7 @@ void changeVariable() { var solutionMetaModel = TestdataSolution.buildSolutionDescriptor() .getMetaModel(); var variableMetaModel = solutionMetaModel.entity(TestdataEntity.class) - . planningVariable("value"); + .basicVariable("value", TestdataValue.class); var variableDescriptor = ((DefaultPlanningVariableMetaModel) variableMetaModel) .variableDescriptor(); @@ -98,7 +98,7 @@ void readListVariable() { var solutionMetaModel = TestdataListSolution.buildSolutionDescriptor() .getMetaModel(); var variableMetaModel = solutionMetaModel.entity(TestdataListEntity.class) - . planningListVariable("valueList"); + .listVariable("valueList", TestdataListValue.class); var mockScoreDirector = (InnerScoreDirector) mock(InnerScoreDirector.class); var moveDirector = new MoveDirector<>(mockScoreDirector); @@ -124,7 +124,7 @@ void moveValueInList() { var solutionMetaModel = TestdataListSolution.buildSolutionDescriptor() .getMetaModel(); var variableMetaModel = solutionMetaModel.entity(TestdataListEntity.class) - . planningListVariable("valueList"); + .listVariable("valueList", TestdataListValue.class); var variableDescriptor = ((DefaultPlanningListVariableMetaModel) variableMetaModel) .variableDescriptor(); @@ -170,7 +170,7 @@ void moveValueBetweenLists() { var solutionMetaModel = TestdataListSolution.buildSolutionDescriptor() .getMetaModel(); var variableMetaModel = solutionMetaModel.entity(TestdataListEntity.class) - . planningListVariable("valueList"); + .listVariable("valueList", TestdataListValue.class); var variableDescriptor = ((DefaultPlanningListVariableMetaModel) variableMetaModel) .variableDescriptor(); @@ -212,7 +212,7 @@ void swapValuesInList() { var solutionMetaModel = TestdataListSolution.buildSolutionDescriptor() .getMetaModel(); var variableMetaModel = solutionMetaModel.entity(TestdataListEntity.class) - . planningListVariable("valueList"); + .listVariable("valueList", TestdataListValue.class); var variableDescriptor = ((DefaultPlanningListVariableMetaModel) variableMetaModel) .variableDescriptor(); @@ -257,7 +257,7 @@ void swapValuesBetweenLists() { var solutionMetaModel = TestdataListSolution.buildSolutionDescriptor() .getMetaModel(); var variableMetaModel = solutionMetaModel.entity(TestdataListEntity.class) - . planningListVariable("valueList"); + .listVariable("valueList", TestdataListValue.class); var variableDescriptor = ((DefaultPlanningListVariableMetaModel) variableMetaModel) .variableDescriptor(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/MoveStreamsBasedLocalSearchTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsBasedLocalSearchTest.java similarity index 89% rename from core/src/test/java/ai/timefold/solver/core/impl/move/MoveStreamsBasedLocalSearchTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsBasedLocalSearchTest.java index 6376700d18..96992aed71 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/MoveStreamsBasedLocalSearchTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/NeighborhoodsBasedLocalSearchTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move; +package ai.timefold.solver.core.impl.neighborhood; import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.Mockito.doReturn; @@ -19,9 +19,9 @@ import ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider; import ai.timefold.solver.core.impl.localsearch.decider.acceptor.AcceptorFactory; import ai.timefold.solver.core.impl.localsearch.decider.forager.LocalSearchForagerFactory; -import ai.timefold.solver.core.impl.move.streams.DefaultMoveStreamFactory; -import ai.timefold.solver.core.impl.move.streams.InnerMoveProducer; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions.ChangeMoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.ChangeMoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.move.InnerMoveStream; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultMoveStreamFactory; import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory; import ai.timefold.solver.core.impl.solver.AbstractSolver; import ai.timefold.solver.core.impl.solver.event.SolverEventSupport; @@ -36,7 +36,7 @@ import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; -class MoveStreamsBasedLocalSearchTest { +class NeighborhoodsBasedLocalSearchTest { @Test void changeMoveBasedLocalSearch() { @@ -90,16 +90,15 @@ void changeMoveBasedLocalSearch() { .doesNotThrowAnyException(); } - private static MoveStreamsBasedMoveRepository + private static NeighborhoodsBasedMoveRepository getMoveRepository(SolutionDescriptor solutionDescriptor) { var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataEntity.class) - .planningVariable(); - var moveDefinition = new ChangeMoveDefinition<>(variableMetaModel); + .basicVariable(); var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveProducer = moveDefinition.build(moveStreamFactory); + var moveStream = new ChangeMoveDefinition<>(variableMetaModel).build(moveStreamFactory); // Random selection otherwise LS gets stuck in an endless loop. - return new MoveStreamsBasedMoveRepository<>(moveStreamFactory, (InnerMoveProducer) moveProducer, + return new NeighborhoodsBasedMoveRepository<>(moveStreamFactory, (InnerMoveStream) moveStream, true); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ChangeMoveDefinitionTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinitionTest.java similarity index 80% rename from core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ChangeMoveDefinitionTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinitionTest.java index 2489a852be..cc47071583 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ChangeMoveDefinitionTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ChangeMoveDefinitionTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.provider; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -12,13 +12,12 @@ import ai.timefold.solver.core.api.score.stream.ConstraintProvider; import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.impl.move.streams.DefaultMoveStreamFactory; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.ChangeMove; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions.ChangeMoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultMoveStreamFactory; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.SessionContext; import ai.timefold.solver.core.impl.score.director.stream.BavetConstraintStreamScoreDirectorFactory; +import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; @@ -42,7 +41,7 @@ void fromSolution() { var solutionDescriptor = TestdataSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataEntity.class) - .planningVariable(); + .basicVariable(); var solution = TestdataSolution.generateSolution(2, 2); var firstEntity = solution.getEntityList().get(0); @@ -51,14 +50,8 @@ void fromSolution() { secondEntity.setValue(null); var firstValue = solution.getValueList().get(0); var secondValue = solution.getValueList().get(1); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(4); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -104,7 +97,7 @@ void fromSolutionIncompleteValueRange() { var solutionDescriptor = TestdataIncompleteValueRangeSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataIncompleteValueRangeEntity.class) - .planningVariable(); + .basicVariable(); // The point of this test is to ensure that the move provider skips values that are not in the value range. var solution = TestdataIncompleteValueRangeSolution.generateSolution(2, 2); @@ -117,15 +110,8 @@ void fromSolutionIncompleteValueRange() { secondEntity.setValue(null); var firstValue = solution.getValueList().get(0); var secondValue = solution.getValueList().get(1); - var scoreDirector = - createScoreDirector(solutionDescriptor, solution); - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); - - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(4); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -171,24 +157,18 @@ void fromEntity() { var solutionDescriptor = TestdataEntityProvidingSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataEntityProvidingEntity.class) - .planningVariable(); + .basicVariable(); var solution = TestdataEntityProvidingSolution.generateSolution(2, 2); var firstEntity = solution.getEntityList().get(0); var secondEntity = solution.getEntityList().get(1); var firstValue = firstEntity.getValueRange().get(0); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); // One move is expected: // - firstEntity is already assigned to firstValue, the only possible value; skip. // - Assign secondEntity to firstValue, // as it is currently assigned to secondValue, and the value range only contains firstValue. - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(1); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -211,19 +191,12 @@ void fromEntityAllowsUnassigned() { var solutionDescriptor = TestdataAllowsUnassignedEntityProvidingSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataAllowsUnassignedEntityProvidingEntity.class) - .planningVariable(); + .basicVariable(); var solution = TestdataAllowsUnassignedEntityProvidingSolution.generateSolution(2, 2); var firstEntity = solution.getEntityList().get(0); var secondEntity = solution.getEntityList().get(1); var firstValue = firstEntity.getValueRange().get(0); - var scoreDirector = createScoreDirector(solutionDescriptor, - solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); // Three moves are expected: // - Assign firstEntity to null, @@ -232,7 +205,7 @@ void fromEntityAllowsUnassigned() { // as it is currently assigned to secondValue, and the value range only contains firstValue. // Null is not in the value range, but as documented, // null is added automatically to value ranges when allowsUnassigned is true. - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(3); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -272,23 +245,17 @@ void fromSolutionAllowsUnassigned() { var solutionDescriptor = TestdataAllowsUnassignedSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataAllowsUnassignedEntity.class) - .planningVariable(); + .basicVariable(); var solution = TestdataAllowsUnassignedSolution.generateSolution(2, 2); var firstEntity = solution.getEntityList().get(0); // Assigned to null. var secondEntity = solution.getEntityList().get(1); // Assigned to secondValue. var firstValue = solution.getValueList().get(0); // Not assigned to any entity. var secondValue = solution.getValueList().get(1); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); // Filters out moves that would change the value to the value the entity already has. // Therefore this will have 4 moves (2 entities * 2 values) as opposed to 6 (2 entities * 3 values). - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(4); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -356,27 +323,28 @@ void fromSolutionAllowsUnassigned() { } + private Iterable> createMoveIterable(MoveDefinition moveDefinition, + SolutionDescriptor solutionDescriptor, Solution_ solution) { + var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.TRACKED_FULL_ASSERT); + var moveStream = moveDefinition.build(moveStreamFactory); + var scoreDirector = createScoreDirector(solutionDescriptor, solution); + var neighborhoodSession = moveStreamFactory.createSession(new SessionContext<>(scoreDirector)); + solutionDescriptor.visitAll(scoreDirector.getWorkingSolution(), neighborhoodSession::insert); + neighborhoodSession.settle(); + return moveStream.getMoveIterable(neighborhoodSession); + } + private InnerScoreDirector createScoreDirector(SolutionDescriptor solutionDescriptor, Solution_ solution) { - var constraintProvider = new TestingConstraintProvider(solutionDescriptor.getMetaModel().genuineEntities() - .get(0).type()); - var scoreDirectorFactory = - new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, - EnvironmentMode.TRACKED_FULL_ASSERT); + var firstEntityClass = solutionDescriptor.getMetaModel().genuineEntities().get(0).type(); + var constraintProvider = new TestingConstraintProvider(firstEntityClass); + var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, + EnvironmentMode.TRACKED_FULL_ASSERT); var scoreDirector = scoreDirectorFactory.buildScoreDirector(); scoreDirector.setWorkingSolution(solution); return scoreDirector; } - private MoveStreamSession createSession(DefaultMoveStreamFactory moveStreamFactory, - InnerScoreDirector scoreDirector) { - var solution = scoreDirector.getWorkingSolution(); - var moveStreamSession = moveStreamFactory.createSession(new SessionContext<>(scoreDirector)); - scoreDirector.getSolutionDescriptor().visitAll(solution, moveStreamSession::insert); - moveStreamSession.settle(); - return moveStreamSession; - } - // The specifics of the constraint provider are not important for this test, // as the score will never be calculated. private record TestingConstraintProvider(Class entityClass) implements ConstraintProvider { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ListChangeMoveDefinitionTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinitionTest.java similarity index 83% rename from core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ListChangeMoveDefinitionTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinitionTest.java index 31fba15d63..2fb657a726 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ListChangeMoveDefinitionTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListChangeMoveDefinitionTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.provider; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -12,12 +12,8 @@ import ai.timefold.solver.core.api.score.stream.ConstraintProvider; import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.impl.move.streams.DefaultMoveStreamFactory; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.ListAssignMove; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.ListChangeMove; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.ListUnassignMove; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions.ListChangeMoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultMoveStreamFactory; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.SessionContext; import ai.timefold.solver.core.impl.score.director.stream.BavetConstraintStreamScoreDirectorFactory; @@ -42,7 +38,7 @@ void fromSolution() { var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataListEntity.class) - .planningListVariable(); + .listVariable(); var solution = TestdataListSolution.generateUninitializedSolution(2, 2); var e1 = solution.getEntityList().get(0); @@ -52,14 +48,7 @@ void fromSolution() { e2.getValueList().add(initiallyAssignedValue); solution.getEntityList().forEach(TestdataListEntity::setUpShadowVariables); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ListChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); - - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ListChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(4); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -118,7 +107,7 @@ void fromEntity() { var solutionDescriptor = TestdataListEntityProvidingSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataListEntityProvidingEntity.class) - .planningListVariable(); + .listVariable(); var solution = TestdataListEntityProvidingSolution.generateSolution(); var e1 = solution.getEntityList().get(0); @@ -131,15 +120,7 @@ void fromEntity() { e2.getValueList().add(initiallyAssignedValue); solution.getEntityList().forEach(TestdataListEntityProvidingEntity::setUpShadowVariables); - var scoreDirector = - createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ListChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); - - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ListChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(4); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -199,7 +180,7 @@ void fromEntityAllowsUnassigned() { var solutionDescriptor = TestdataListUnassignedEntityProvidingSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataListUnassignedEntityProvidingEntity.class) - .planningListVariable(); + .listVariable(); var solution = TestdataListUnassignedEntityProvidingSolution.generateSolution(); var e1 = solution.getEntityList().get(0); @@ -209,15 +190,7 @@ void fromEntityAllowsUnassigned() { var v3 = solution.getValueList().get(2); e2.getValueList().add(v1); - var scoreDirector = createScoreDirector(solutionDescriptor, - solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ListChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); - - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ListChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(5); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -288,7 +261,7 @@ void fromSolutionAllowsUnassigned() { var solutionDescriptor = TestdataAllowsUnassignedValuesListSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataAllowsUnassignedValuesListEntity.class) - .planningListVariable(); + .listVariable(); var solution = TestdataAllowsUnassignedValuesListSolution.generateUninitializedSolution(2, 2); var e1 = solution.getEntityList().get(0); var e2 = solution.getEntityList().get(1); @@ -297,14 +270,7 @@ void fromSolutionAllowsUnassigned() { e2.getValueList().add(v1); solution.getEntityList().forEach(TestdataAllowsUnassignedValuesListEntity::setUpShadowVariables); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ListChangeMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); - - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ListChangeMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); assertThat(moveIterable).hasSize(5); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) @@ -384,13 +350,23 @@ void fromSolutionAllowsUnassigned() { return (ListAssignMove) moveList.get(index); } + private Iterable> createMoveIterable(MoveDefinition moveDefinition, + SolutionDescriptor solutionDescriptor, Solution_ solution) { + var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.TRACKED_FULL_ASSERT); + var moveStream = moveDefinition.build(moveStreamFactory); + var scoreDirector = createScoreDirector(solutionDescriptor, solution); + var neighborhoodSession = moveStreamFactory.createSession(new SessionContext<>(scoreDirector)); + solutionDescriptor.visitAll(scoreDirector.getWorkingSolution(), neighborhoodSession::insert); + neighborhoodSession.settle(); + return moveStream.getMoveIterable(neighborhoodSession); + } + private InnerScoreDirector createScoreDirector(SolutionDescriptor solutionDescriptor, Solution_ solution) { - var constraintProvider = new TestingConstraintProvider( - solutionDescriptor.getListVariableDescriptor().getEntityDescriptor().getEntityClass()); - var scoreDirectorFactory = - new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, - EnvironmentMode.TRACKED_FULL_ASSERT); + var firstEntityClass = solutionDescriptor.getMetaModel().genuineEntities().get(0).type(); + var constraintProvider = new TestingConstraintProvider(firstEntityClass); + var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, + EnvironmentMode.TRACKED_FULL_ASSERT); var scoreDirector = scoreDirectorFactory.buildScoreDirector(); scoreDirector.setWorkingSolution(solution); return scoreDirector; @@ -413,13 +389,4 @@ private Constraint alwaysPenalizingConstraint(ConstraintFactory constraintFactor } - private MoveStreamSession createSession(DefaultMoveStreamFactory moveStreamFactory, - InnerScoreDirector scoreDirector) { - var solution = scoreDirector.getWorkingSolution(); - var moveStreamSession = moveStreamFactory.createSession(new SessionContext<>(scoreDirector)); - scoreDirector.getSolutionDescriptor().visitAll(solution, moveStreamSession::insert); - moveStreamSession.settle(); - return moveStreamSession; - } - } \ No newline at end of file diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ListSwapMoveDefinitionTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinitionTest.java similarity index 72% rename from core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ListSwapMoveDefinitionTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinitionTest.java index 71082da4b2..6d4b1fb617 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/ListSwapMoveDefinitionTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/ListSwapMoveDefinitionTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.provider; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -11,13 +11,12 @@ import ai.timefold.solver.core.api.score.stream.ConstraintProvider; import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.impl.move.streams.DefaultMoveStreamFactory; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.ListSwapMove; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions.ListSwapMoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultMoveStreamFactory; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.SessionContext; import ai.timefold.solver.core.impl.score.director.stream.BavetConstraintStreamScoreDirectorFactory; +import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; import ai.timefold.solver.core.testdomain.list.TestdataListValue; @@ -35,12 +34,11 @@ void fromSolution() { var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataListEntity.class) - .planningListVariable(); + .listVariable(); var solution = TestdataListSolution.generateUninitializedSolution(4, 2); var e1 = solution.getEntityList().get(0); var e2 = solution.getEntityList().get(1); - var unassignedValue = solution.getValueList().get(0); var assignedValue1 = solution.getValueList().get(1); var assignedValue2 = solution.getValueList().get(2); var assignedValue3 = solution.getValueList().get(3); @@ -49,14 +47,7 @@ void fromSolution() { e2.getValueList().add(assignedValue3); solution.getEntityList().forEach(TestdataListEntity::setUpShadowVariables); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ListSwapMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); - - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ListSwapMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) .toList(); assertThat(moveList).hasSize(3); @@ -99,7 +90,7 @@ void fromEntity() { var solutionDescriptor = TestdataListEntityProvidingSolution.buildSolutionDescriptor(); var variableMetaModel = solutionDescriptor.getMetaModel() .entity(TestdataListEntityProvidingEntity.class) - .planningListVariable(); + .listVariable(); var solution = TestdataListEntityProvidingSolution.generateSolution(); var e1 = solution.getEntityList().get(0); @@ -109,15 +100,7 @@ void fromEntity() { e2.getValueList().add(initiallyAssignedValue); solution.getEntityList().forEach(TestdataListEntityProvidingEntity::setUpShadowVariables); - var scoreDirector = - createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new ListSwapMoveDefinition<>(variableMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); - - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new ListSwapMoveDefinition<>(variableMetaModel), solutionDescriptor, solution); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) .toList(); @@ -126,13 +109,23 @@ void fromEntity() { assertThat(moveList).isEmpty(); } + private Iterable> createMoveIterable(MoveDefinition moveDefinition, + SolutionDescriptor solutionDescriptor, Solution_ solution) { + var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.TRACKED_FULL_ASSERT); + var moveStream = moveDefinition.build(moveStreamFactory); + var scoreDirector = createScoreDirector(solutionDescriptor, solution); + var neighborhoodSession = moveStreamFactory.createSession(new SessionContext<>(scoreDirector)); + solutionDescriptor.visitAll(scoreDirector.getWorkingSolution(), neighborhoodSession::insert); + neighborhoodSession.settle(); + return moveStream.getMoveIterable(neighborhoodSession); + } + private InnerScoreDirector createScoreDirector(SolutionDescriptor solutionDescriptor, Solution_ solution) { - var constraintProvider = new TestingConstraintProvider( - solutionDescriptor.getListVariableDescriptor().getEntityDescriptor().getEntityClass()); - var scoreDirectorFactory = - new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, - EnvironmentMode.TRACKED_FULL_ASSERT); + var firstEntityClass = solutionDescriptor.getMetaModel().genuineEntities().get(0).type(); + var constraintProvider = new TestingConstraintProvider(firstEntityClass); + var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, + EnvironmentMode.TRACKED_FULL_ASSERT); var scoreDirector = scoreDirectorFactory.buildScoreDirector(); scoreDirector.setWorkingSolution(solution); return scoreDirector; @@ -155,13 +148,4 @@ private Constraint alwaysPenalizingConstraint(ConstraintFactory constraintFactor } - private MoveStreamSession createSession(DefaultMoveStreamFactory moveStreamFactory, - InnerScoreDirector scoreDirector) { - var solution = scoreDirector.getWorkingSolution(); - var moveStreamSession = moveStreamFactory.createSession(new SessionContext<>(scoreDirector)); - scoreDirector.getSolutionDescriptor().visitAll(solution, moveStreamSession::insert); - moveStreamSession.settle(); - return moveStreamSession; - } - } \ No newline at end of file diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/SwapMoveDefinitionTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinitionTest.java similarity index 73% rename from core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/SwapMoveDefinitionTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinitionTest.java index 5f3d6feb3d..e4478d8a7b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/maybeapi/provider/SwapMoveDefinitionTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/move/SwapMoveDefinitionTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.maybeapi.provider; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.move; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -11,14 +11,13 @@ import ai.timefold.solver.core.api.score.stream.ConstraintProvider; import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.impl.move.streams.DefaultMoveStreamFactory; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.SwapMove; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions.SwapMoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamSession; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.MoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.stream.DefaultMoveStreamFactory; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.SessionContext; import ai.timefold.solver.core.impl.score.director.stream.BavetConstraintStreamScoreDirectorFactory; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; +import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.multivar.TestdataMultiVarEntity; @@ -42,17 +41,11 @@ void univariate() { var e3 = solution.getEntityList().get(2); var v1 = solution.getValueList().get(0); var v2 = solution.getValueList().get(1); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new SwapMoveDefinition<>(entityMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); // With 3 entities, only 3 swap moves are possible: e1 <-> e2, e1 <-> e3, e2 <-> e3. // But we only have 2 values, guaranteeing that two entities will share a value. // Therefore there will only be 2 unique swap moves. - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new SwapMoveDefinition<>(entityMetaModel), solutionDescriptor, solution); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) .map(m -> (SwapMove) m) .toList(); @@ -89,17 +82,11 @@ void multivariate() { var v2 = solution.getValueList().get(1); var otherV1 = solution.getOtherValueList().get(0); var otherV2 = solution.getOtherValueList().get(1); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new SwapMoveDefinition<>(entityMetaModel); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); // With 3 entities, only 3 swap moves are possible: e1 <-> e2, e1 <-> e3, e2 <-> e3. // But we only have 2 unique combinations of values, guaranteeing that two entities will share values. // Therefore there will only be 2 unique swap moves. - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = createMoveIterable(new SwapMoveDefinition<>(entityMetaModel), solutionDescriptor, solution); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) .map(m -> (SwapMove) m) .toList(); @@ -131,46 +118,41 @@ void multivariateWithExclusions() { .filter(v -> !v.name().contains("tertiary")) .map(v -> (PlanningVariableMetaModel) v) .toList(); - var solution = TestdataMultiVarSolution.generateSolution(3, 1, 2); - var scoreDirector = createScoreDirector(solutionDescriptor, solution); - - var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); - var moveDefinition = new SwapMoveDefinition<>(allowedVariableMetaModels); - var moveProducer = moveDefinition.build(moveStreamFactory); - var moveStreamSession = createSession(moveStreamFactory, scoreDirector); // With 3 entities, only 3 swap moves are possible: e1 <-> e2, e1 <-> e3, e2 <-> e3. // We only have 1 value for primary and secondary variables, // therefore with the tertiary variable excluded, there will be no swap moves. - var moveIterable = moveProducer.getMoveIterable(moveStreamSession); + var moveIterable = + createMoveIterable(new SwapMoveDefinition<>(allowedVariableMetaModels), solutionDescriptor, solution); var moveList = StreamSupport.stream(moveIterable.spliterator(), false) .map(m -> (SwapMove) m) .toList(); assertThat(moveList).isEmpty(); } + private Iterable> createMoveIterable(MoveDefinition moveDefinition, + SolutionDescriptor solutionDescriptor, Solution_ solution) { + var moveStreamFactory = new DefaultMoveStreamFactory<>(solutionDescriptor, EnvironmentMode.TRACKED_FULL_ASSERT); + var moveStream = moveDefinition.build(moveStreamFactory); + var scoreDirector = createScoreDirector(solutionDescriptor, solution); + var neighborhoodSession = moveStreamFactory.createSession(new SessionContext<>(scoreDirector)); + solutionDescriptor.visitAll(scoreDirector.getWorkingSolution(), neighborhoodSession::insert); + neighborhoodSession.settle(); + return moveStream.getMoveIterable(neighborhoodSession); + } + private InnerScoreDirector createScoreDirector(SolutionDescriptor solutionDescriptor, Solution_ solution) { - var constraintProvider = new TestingConstraintProvider(solutionDescriptor.getMetaModel().genuineEntities() - .get(0).type()); - var scoreDirectorFactory = - new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, - EnvironmentMode.TRACKED_FULL_ASSERT); + var firstEntityClass = solutionDescriptor.getMetaModel().genuineEntities().get(0).type(); + var constraintProvider = new TestingConstraintProvider(firstEntityClass); + var scoreDirectorFactory = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, + EnvironmentMode.TRACKED_FULL_ASSERT); var scoreDirector = scoreDirectorFactory.buildScoreDirector(); scoreDirector.setWorkingSolution(solution); return scoreDirector; } - private MoveStreamSession createSession(DefaultMoveStreamFactory moveStreamFactory, - InnerScoreDirector scoreDirector) { - var solution = scoreDirector.getWorkingSolution(); - var moveStreamSession = moveStreamFactory.createSession(new SessionContext<>(scoreDirector)); - scoreDirector.getSolutionDescriptor().visitAll(solution, moveStreamSession::insert); - moveStreamSession.settle(); - return moveStreamSession; - } - // The specifics of the constraint provider are not important for this test, // as the score will never be calculated. private record TestingConstraintProvider(Class entityClass) implements ConstraintProvider { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/dataset/UniDatasetStreamTest.java b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/UniEnumeratingStreamTest.java similarity index 75% rename from core/src/test/java/ai/timefold/solver/core/impl/move/streams/dataset/UniDatasetStreamTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/UniEnumeratingStreamTest.java index 3504d050ec..8cc9c89e2b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/streams/dataset/UniDatasetStreamTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/neighborhood/maybeapi/stream/enumerating/UniEnumeratingStreamTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.move.streams.dataset; +package ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating; import static org.assertj.core.api.Assertions.assertThat; @@ -6,7 +6,10 @@ import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; import ai.timefold.solver.core.config.solver.EnvironmentMode; -import ai.timefold.solver.core.impl.move.streams.dataset.uni.AbstractUniDataStream; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.DatasetSession; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.DatasetSessionFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.EnumeratingStreamFactory; +import ai.timefold.solver.core.impl.neighborhood.stream.enumerating.uni.AbstractUniEnumeratingStream; import ai.timefold.solver.core.impl.score.director.SessionContext; import ai.timefold.solver.core.impl.score.director.easy.EasyScoreDirectorFactory; import ai.timefold.solver.core.testdomain.TestdataEntity; @@ -17,26 +20,27 @@ import ai.timefold.solver.core.testdomain.list.pinned.index.TestdataPinnedWithIndexListSolution; import ai.timefold.solver.core.testdomain.list.pinned.index.TestdataPinnedWithIndexListValue; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -class UniDatasetStreamTest { +class UniEnumeratingStreamTest { @Test void forEachBasicVariable() { - var dataStreamFactory = - new DataStreamFactory<>(TestdataSolution.buildSolutionDescriptor(), EnvironmentMode.PHASE_ASSERT); - var uniDataset = ((AbstractUniDataStream) dataStreamFactory + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataSolution.buildSolutionDescriptor(), EnvironmentMode.PHASE_ASSERT); + var uniDataset = ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachNonDiscriminating(TestdataEntity.class, false)) .createDataset(); var solution = TestdataSolution.generateSolution(2, 2); - var datasetSession = UniDatasetStreamTest.createSession(dataStreamFactory, solution); + var datasetSession = UniEnumeratingStreamTest.createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); var entity1 = solution.getEntityList().get(0); var entity2 = solution.getEntityList().get(1); - assertThat(uniDatasetInstance.iterator()) + Assertions.assertThat(uniDatasetInstance.iterator()) .toIterable() .map(t -> t.factA) .containsExactly(entity1, entity2); @@ -47,7 +51,7 @@ void forEachBasicVariable() { datasetSession.retract(entity2); datasetSession.settle(); - assertThat(uniDatasetInstance.iterator()) + Assertions.assertThat(uniDatasetInstance.iterator()) .toIterable() .map(t -> t.factA) .containsExactly(entity1, entity3); @@ -55,20 +59,20 @@ void forEachBasicVariable() { @Test void forEachBasicVariableIncludingNull() { - var dataStreamFactory = - new DataStreamFactory<>(TestdataSolution.buildSolutionDescriptor(), EnvironmentMode.PHASE_ASSERT); - var uniDataset = ((AbstractUniDataStream) dataStreamFactory + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataSolution.buildSolutionDescriptor(), EnvironmentMode.PHASE_ASSERT); + var uniDataset = ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachNonDiscriminating(TestdataEntity.class, true)) .createDataset(); var solution = TestdataSolution.generateSolution(2, 2); - var datasetSession = UniDatasetStreamTest.createSession(dataStreamFactory, solution); + var datasetSession = UniEnumeratingStreamTest.createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); var entity1 = solution.getEntityList().get(0); var entity2 = solution.getEntityList().get(1); - assertThat(uniDatasetInstance.iterator()) + Assertions.assertThat(uniDatasetInstance.iterator()) .toIterable() .map(t -> t.factA) .containsExactly(null, entity1, entity2); @@ -79,7 +83,7 @@ void forEachBasicVariableIncludingNull() { datasetSession.retract(entity2); datasetSession.settle(); - assertThat(uniDatasetInstance.iterator()) + Assertions.assertThat(uniDatasetInstance.iterator()) .toIterable() .map(t -> t.factA) .containsExactly(null, entity1, entity3); @@ -87,20 +91,20 @@ void forEachBasicVariableIncludingNull() { @Test void forEachListVariable() { - var dataStreamFactory = - new DataStreamFactory<>(TestdataListSolution.buildSolutionDescriptor(), EnvironmentMode.PHASE_ASSERT); - var uniDataset = ((AbstractUniDataStream) dataStreamFactory + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataListSolution.buildSolutionDescriptor(), EnvironmentMode.PHASE_ASSERT); + var uniDataset = ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachNonDiscriminating(TestdataListEntity.class, false)) .createDataset(); var solution = TestdataListSolution.generateInitializedSolution(2, 2); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); var entity1 = solution.getEntityList().get(0); var entity2 = solution.getEntityList().get(1); - assertThat(uniDatasetInstance.iterator()) + Assertions.assertThat(uniDatasetInstance.iterator()) .toIterable() .map(t -> t.factA) .containsExactly(entity1, entity2); @@ -111,7 +115,7 @@ void forEachListVariable() { datasetSession.retract(entity2); datasetSession.settle(); - assertThat(uniDatasetInstance.iterator()) + Assertions.assertThat(uniDatasetInstance.iterator()) .toIterable() .map(t -> t.factA) .containsExactly(entity1, entity3); @@ -119,20 +123,20 @@ void forEachListVariable() { @Test void forEachListVariableIncludingNull() { - var dataStreamFactory = - new DataStreamFactory<>(TestdataListSolution.buildSolutionDescriptor(), EnvironmentMode.PHASE_ASSERT); - var uniDataset = ((AbstractUniDataStream) dataStreamFactory + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataListSolution.buildSolutionDescriptor(), EnvironmentMode.PHASE_ASSERT); + var uniDataset = ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachNonDiscriminating(TestdataListEntity.class, true)) .createDataset(); var solution = TestdataListSolution.generateInitializedSolution(2, 2); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); var entity1 = solution.getEntityList().get(0); var entity2 = solution.getEntityList().get(1); - assertThat(uniDatasetInstance.iterator()) + Assertions.assertThat(uniDatasetInstance.iterator()) .toIterable() .map(t -> t.factA) .containsExactly(null, entity1, entity2); @@ -143,21 +147,23 @@ void forEachListVariableIncludingNull() { datasetSession.retract(entity2); datasetSession.settle(); - assertThat(uniDatasetInstance.iterator()) + Assertions.assertThat(uniDatasetInstance.iterator()) .toIterable() .map(t -> t.factA) .containsExactly(null, entity1, entity3); } - private static DatasetSession createSession(DataStreamFactory dataStreamFactory, + private static DatasetSession createSession( + EnumeratingStreamFactory enumeratingStreamFactory, Solution_ solution) { - var scoreDirector = new EasyScoreDirectorFactory<>(dataStreamFactory.getSolutionDescriptor(), s -> SimpleScore.ZERO) - .buildScoreDirector(); + var scoreDirector = + new EasyScoreDirectorFactory<>(enumeratingStreamFactory.getSolutionDescriptor(), s -> SimpleScore.ZERO) + .buildScoreDirector(); scoreDirector.setWorkingSolution(solution); var sessionContext = new SessionContext<>(scoreDirector); - var datasetSessionFactory = new DatasetSessionFactory<>(dataStreamFactory); + var datasetSessionFactory = new DatasetSessionFactory<>(enumeratingStreamFactory); var datasetSession = datasetSessionFactory.buildSession(sessionContext); - var solutionDescriptor = dataStreamFactory.getSolutionDescriptor(); + var solutionDescriptor = enumeratingStreamFactory.getSolutionDescriptor(); solutionDescriptor.visitAll(solution, datasetSession::insert); @@ -167,10 +173,11 @@ private static DatasetSession createSession(DataStreamFac @Test void forEachListVariableIncludingPinned() { - var dataStreamFactory = new DataStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), - EnvironmentMode.PHASE_ASSERT); + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), + EnvironmentMode.PHASE_ASSERT); var uniDataset = - ((AbstractUniDataStream) dataStreamFactory + ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachNonDiscriminating(TestdataPinnedWithIndexListEntity.class, false)) .createDataset(); @@ -187,7 +194,7 @@ void forEachListVariableIncludingPinned() { unpinnedEntity.setPinned(false); unpinnedEntity.setPlanningPinToIndex(0); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); assertThat(uniDatasetInstance.iterator()) @@ -210,10 +217,11 @@ void forEachListVariableIncludingPinned() { @Test void forEachListVariableIncludingPinnedAndNull() { - var dataStreamFactory = new DataStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), - EnvironmentMode.PHASE_ASSERT); + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), + EnvironmentMode.PHASE_ASSERT); var uniDataset = - ((AbstractUniDataStream) dataStreamFactory + ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachNonDiscriminating(TestdataPinnedWithIndexListEntity.class, true)) .createDataset(); @@ -230,7 +238,7 @@ void forEachListVariableIncludingPinnedAndNull() { unpinnedEntity.setPinned(false); unpinnedEntity.setPlanningPinToIndex(0); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); assertThat(uniDatasetInstance.iterator()) @@ -253,10 +261,11 @@ void forEachListVariableIncludingPinnedAndNull() { @Test void forEachListVariableExcludingPinned() { // Entities with planningPin true will be skipped. - var dataStreamFactory = new DataStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), - EnvironmentMode.PHASE_ASSERT); + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), + EnvironmentMode.PHASE_ASSERT); var uniDataset = - ((AbstractUniDataStream) dataStreamFactory + ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachExcludingPinned(TestdataPinnedWithIndexListEntity.class, false)) .createDataset(); @@ -274,7 +283,7 @@ void forEachListVariableExcludingPinned() { // Entities with planningPin true wi unpinnedEntity.setPinned(false); unpinnedEntity.setPlanningPinToIndex(0); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); assertThat(uniDatasetInstance.iterator()) @@ -297,10 +306,11 @@ void forEachListVariableExcludingPinned() { // Entities with planningPin true wi @Test void forEachListVariableExcludingPinnedIncludingNull() { // Entities with planningPin true will be skipped. - var dataStreamFactory = new DataStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), - EnvironmentMode.PHASE_ASSERT); + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), + EnvironmentMode.PHASE_ASSERT); var uniDataset = - ((AbstractUniDataStream) dataStreamFactory + ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachExcludingPinned(TestdataPinnedWithIndexListEntity.class, true)) .createDataset(); @@ -318,7 +328,7 @@ void forEachListVariableExcludingPinnedIncludingNull() { // Entities with planni unpinnedEntity.setPinned(false); unpinnedEntity.setPlanningPinToIndex(0); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); assertThat(uniDatasetInstance.iterator()) @@ -341,10 +351,11 @@ void forEachListVariableExcludingPinnedIncludingNull() { // Entities with planni @Test void forEachListVariableIncludingPinnedValues() { - var dataStreamFactory = new DataStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), - EnvironmentMode.PHASE_ASSERT); + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), + EnvironmentMode.PHASE_ASSERT); var uniDataset = - ((AbstractUniDataStream) dataStreamFactory + ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachNonDiscriminating(TestdataPinnedWithIndexListValue.class, false)) .createDataset(); @@ -371,7 +382,7 @@ void forEachListVariableIncludingPinnedValues() { // Properly set shadow variables based on the changes above. solution.getEntityList().forEach(TestdataPinnedWithIndexListEntity::setUpShadowVariables); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); assertThat(uniDatasetInstance.iterator()) @@ -382,10 +393,11 @@ void forEachListVariableIncludingPinnedValues() { @Test void forEachListVariableIncludingPinnedValuesAndNull() { - var dataStreamFactory = new DataStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), - EnvironmentMode.PHASE_ASSERT); + var enumeratingStreamFactory = + new EnumeratingStreamFactory<>(TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(), + EnvironmentMode.PHASE_ASSERT); var uniDataset = - ((AbstractUniDataStream) dataStreamFactory + ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachNonDiscriminating(TestdataPinnedWithIndexListValue.class, true)) .createDataset(); @@ -412,7 +424,7 @@ void forEachListVariableIncludingPinnedValuesAndNull() { // Properly set shadow variables based on the changes above. solution.getEntityList().forEach(TestdataPinnedWithIndexListEntity::setUpShadowVariables); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); assertThat(uniDatasetInstance.iterator()) @@ -424,9 +436,9 @@ void forEachListVariableIncludingPinnedValuesAndNull() { @Test void forEachListVariableExcludingPinnedValues() { var solutionDescriptor = TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(); - var dataStreamFactory = new DataStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); + var enumeratingStreamFactory = new EnumeratingStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); var uniDataset = - ((AbstractUniDataStream) dataStreamFactory + ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachExcludingPinned(TestdataPinnedWithIndexListValue.class, false)) .createDataset(); @@ -456,7 +468,7 @@ void forEachListVariableExcludingPinnedValues() { // Properly set shadow variables based on the changes above. solution.getEntityList().forEach(TestdataPinnedWithIndexListEntity::setUpShadowVariables); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); assertThat(uniDatasetInstance.iterator()) @@ -468,9 +480,9 @@ void forEachListVariableExcludingPinnedValues() { @Test void forEachListVariableExcludingPinnedValuesIncludingNull() { var solutionDescriptor = TestdataPinnedWithIndexListSolution.buildSolutionDescriptor(); - var dataStreamFactory = new DataStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); + var enumeratingStreamFactory = new EnumeratingStreamFactory<>(solutionDescriptor, EnvironmentMode.PHASE_ASSERT); var uniDataset = - ((AbstractUniDataStream) dataStreamFactory + ((AbstractUniEnumeratingStream) enumeratingStreamFactory .forEachExcludingPinned(TestdataPinnedWithIndexListValue.class, true)) .createDataset(); @@ -500,7 +512,7 @@ void forEachListVariableExcludingPinnedValuesIncludingNull() { // Properly set shadow variables based on the changes above. solution.getEntityList().forEach(TestdataPinnedWithIndexListEntity::setUpShadowVariables); - var datasetSession = createSession(dataStreamFactory, solution); + var datasetSession = createSession(enumeratingStreamFactory, solution); var uniDatasetInstance = datasetSession.getInstance(uniDataset); assertThat(uniDatasetInstance.iterator()) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java index 30df797834..54cb166a9b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java @@ -81,17 +81,17 @@ import ai.timefold.solver.core.config.solver.termination.TerminationConfig; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.ChangeMove; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions.ChangeMoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.definitions.ListChangeMoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveDefinition; -import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveProvider; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.Neighborhood; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodBuilder; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.NeighborhoodProvider; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.ChangeMoveDefinition; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.ListChangeMoveDefinition; import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.score.DummySimpleScoreEasyScoreCalculator; import ai.timefold.solver.core.impl.score.constraint.DefaultConstraintMatchTotal; import ai.timefold.solver.core.impl.score.constraint.DefaultIndictment; import ai.timefold.solver.core.impl.util.Pair; -import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; @@ -190,16 +190,16 @@ void solve() { } @Test - void solveWithMoveStreams() { + void solveWithNeighborhoods() { var solverConfig = new SolverConfig() - .withPreviewFeature(PreviewFeature.MOVE_STREAMS) + .withPreviewFeature(PreviewFeature.NEIGHBORHOODS) .withSolutionClass(TestdataSolution.class) .withEntityClasses(TestdataEntity.class) .withEasyScoreCalculatorClass(TestingEasyScoreCalculator.class) .withTerminationConfig(new TerminationConfig() .withBestScoreLimit("0")) // Should get there quickly. .withPhases(new LocalSearchPhaseConfig() - .withMoveProviderClass(TestingMoveProvider.class)); + .withMoveProviderClass(TestingNeighborhoodProvider.class)); var solution = TestdataSolution.generateSolution(3, 2); @@ -210,16 +210,16 @@ void solveWithMoveStreams() { } @Test - void solveWithMoveStreamsListVar() { + void solveWithNeighborhoodsListVar() { var solverConfig = new SolverConfig() - .withPreviewFeature(PreviewFeature.MOVE_STREAMS) + .withPreviewFeature(PreviewFeature.NEIGHBORHOODS) .withSolutionClass(TestdataListSolution.class) .withEntityClasses(TestdataListEntity.class, TestdataListValue.class) .withEasyScoreCalculatorClass(TestingListEasyScoreCalculator.class) .withTerminationConfig(new TerminationConfig() .withBestScoreLimit("0")) // Should get there quickly. .withPhases(new LocalSearchPhaseConfig() - .withMoveProviderClass(TestingListMoveProvider.class)); + .withMoveProviderClass(TestingListNeighborhoodProvider.class)); // Both values are on the same entity; the goal of the solver is to move one of them to the other entity. var solution = TestdataListSolution.generateUninitializedSolution(2, 2); @@ -235,7 +235,7 @@ void solveWithMoveStreamsListVar() { } @Test - void solveWithMoveStreamsNotEnabled() { + void solveWithNeighborhoodsNotEnabled() { var solverConfig = new SolverConfig() // Preview feature not enabled. .withSolutionClass(TestdataSolution.class) .withEntityClasses(TestdataEntity.class) @@ -243,18 +243,18 @@ void solveWithMoveStreamsNotEnabled() { .withTerminationConfig(new TerminationConfig() .withBestScoreLimit("0")) // Should get there quickly. .withPhases(new LocalSearchPhaseConfig() - .withMoveProviderClass(TestingMoveProvider.class)); + .withMoveProviderClass(TestingNeighborhoodProvider.class)); var solution = TestdataSolution.generateSolution(3, 2); Assertions.assertThatThrownBy(() -> PlannerTestUtils.solve(solverConfig, solution)) .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("MOVE_STREAMS"); + .hasMessageContaining("NEIGHBORHOODS"); } @Test - void solveWithMoveStreamsAndMoveSelectorsFails() { + void solveWithNeighborhoodsAndMoveSelectorsFails() { var solverConfig = new SolverConfig() - .withPreviewFeature(PreviewFeature.MOVE_STREAMS) + .withPreviewFeature(PreviewFeature.NEIGHBORHOODS) .withSolutionClass(TestdataSolution.class) .withEntityClasses(TestdataEntity.class) .withEasyScoreCalculatorClass(TestingEasyScoreCalculator.class) @@ -262,13 +262,13 @@ void solveWithMoveStreamsAndMoveSelectorsFails() { .withBestScoreLimit("0")) // Should get there quickly. .withPhases(new LocalSearchPhaseConfig() .withMoveSelectorConfig(new ChangeMoveSelectorConfig()) - .withMoveProviderClass(TestingMoveProvider.class)); + .withMoveProviderClass(TestingNeighborhoodProvider.class)); var solution = TestdataSolution.generateSolution(3, 2); Assertions.assertThatThrownBy(() -> PlannerTestUtils.solve(solverConfig, solution)) .isInstanceOf(UnsupportedOperationException.class) .hasMessageContaining("move selectors") - .hasMessageContaining("Move Streams"); + .hasMessageContaining("Neighborhoods API"); } @Test @@ -2548,14 +2548,15 @@ public void afterEntityRemoved(@NonNull Object entity) { } @NullMarked - public static final class TestingMoveProvider implements MoveProvider { + public static final class TestingNeighborhoodProvider implements NeighborhoodProvider { @Override - public List> - defineMoves(PlanningSolutionMetaModel solutionMetaModel) { - var variableMetamodel = solutionMetaModel.entity(TestdataEntity.class) - . planningVariable(); - return List.of(new ChangeMoveDefinition<>(variableMetamodel)); + public Neighborhood defineNeighborhood(NeighborhoodBuilder builder) { + var variableMetamodel = builder.getSolutionMetaModel() + .entity(TestdataEntity.class) + . basicVariable(); + return builder.add(new ChangeMoveDefinition<>(variableMetamodel)) + .build(); } } @@ -2581,14 +2582,15 @@ public static final class TestingEasyScoreCalculator implements EasyScoreCalcula } @NullMarked - public static final class TestingListMoveProvider implements MoveProvider { + public static final class TestingListNeighborhoodProvider implements NeighborhoodProvider { @Override - public List> - defineMoves(PlanningSolutionMetaModel solutionMetaModel) { - var variableMetamodel = solutionMetaModel.entity(TestdataListEntity.class) - .planningListVariable(); - return List.of(new ListChangeMoveDefinition<>(variableMetamodel)); + public Neighborhood defineNeighborhood(NeighborhoodBuilder builder) { + var variableMetamodel = builder.getSolutionMetaModel() + .entity(TestdataListEntity.class) + .listVariable(); + return builder.add(new ListChangeMoveDefinition<>(variableMetamodel)) + .build(); } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/dependency/DependencyValuesShadowVariableTest.java b/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/dependency/DependencyValuesShadowVariableTest.java index 3ef68be2f3..f203ab7938 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/dependency/DependencyValuesShadowVariableTest.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/dependency/DependencyValuesShadowVariableTest.java @@ -13,7 +13,7 @@ import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.Moves; import ai.timefold.solver.core.impl.solver.MoveAsserter; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/dynamic_follower/DynamicFollowerValuesShadowVariableTest.java b/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/dynamic_follower/DynamicFollowerValuesShadowVariableTest.java index 39819720ef..45da15eb3c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/dynamic_follower/DynamicFollowerValuesShadowVariableTest.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/dynamic_follower/DynamicFollowerValuesShadowVariableTest.java @@ -8,7 +8,7 @@ import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.Moves; import ai.timefold.solver.core.impl.solver.MoveAsserter; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; import ai.timefold.solver.core.testdomain.TestdataValue; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/follower/FollowerValuesShadowVariableTest.java b/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/follower/FollowerValuesShadowVariableTest.java index 1a63633422..724671d659 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/follower/FollowerValuesShadowVariableTest.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/follower/FollowerValuesShadowVariableTest.java @@ -8,7 +8,7 @@ import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.Moves; import ai.timefold.solver.core.impl.solver.MoveAsserter; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; import ai.timefold.solver.core.testdomain.TestdataValue; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/follower_set/FollowerValuesShadowVariableTest.java b/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/follower_set/FollowerValuesShadowVariableTest.java index 623151d4be..3405f51767 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/follower_set/FollowerValuesShadowVariableTest.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/shadow/follower_set/FollowerValuesShadowVariableTest.java @@ -8,7 +8,7 @@ import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; -import ai.timefold.solver.core.impl.move.streams.maybeapi.generic.Moves; +import ai.timefold.solver.core.impl.neighborhood.maybeapi.move.Moves; import ai.timefold.solver.core.impl.solver.MoveAsserter; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; import ai.timefold.solver.core.testdomain.TestdataValue; diff --git a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc index 9a044419f8..92f0ba1d0f 100644 --- a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc +++ b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc @@ -51,16 +51,17 @@ and in the `SolutionManager` We encourage you to try these preview features and give us feedback on your experience with them. Please direct your feedback to -https://github.com/TimefoldAI/timefold-solver/discussions[Timefold Solver Github]. +https://github.com/TimefoldAI/timefold-solver/discussions[Timefold Solver Github] +or to https://discord.com/channels/1413420192213631086/1414521616955605003[Timefold Discord] -=== Experimental status of Move Streams +=== Experimental status of Neighborhoods -Move Streams are an active research project. +Neighborhoods are an active research project. It intends to simplify the creation of custom moves, eventually replacing move selectors. The component is under heavy development, entirely undocumented, and many key features are yet to be delivered. Neither the API nor the feature set are complete, and any part can change or be removed at any time. -Move Streams will eventually stabilize and be promoted from a research project to a true preview feature. +The Neighborhoods API will eventually stabilize and be promoted from a research project to a true preview feature. We only expose it now to be able to use it for internal experimentation and testing. As such, it is an exception to the rule — this preview feature is not finished, and it is not yet ready for feedback. \ No newline at end of file