From 70ee4a9656dbf5333f1c2d8e0c64147a2f4335a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Thu, 14 Nov 2024 09:55:34 +0100 Subject: [PATCH 01/17] Move inverse fully into the state supply --- .../ExternalizedListVariableStateSupply.java | 45 +++++-- ...letonListListInverseVariableProcessor.java | 40 +++++++ ...letonListListInverseVariableProcessor.java | 69 +++++++++++ .../variable/ListVariableStateSupply.java | 3 + ...SingletonListInverseVariableProcessor.java | 17 +++ ...verseRelationShadowVariableDescriptor.java | 12 +- .../SingletonListInverseVariableDemand.java | 23 ---- .../SingletonListInverseVariableListener.java | 111 ------------------ .../support/VariableListenerSupport.java | 19 +++ ...ternalizedListVariableStateSupplyTest.java | 5 +- ...gletonListInverseVariableListenerTest.java | 95 --------------- 11 files changed, 191 insertions(+), 248 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableDemand.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableListener.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableListenerTest.java diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index e85296773f..05e7d9f60e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -6,6 +6,8 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; @@ -15,6 +17,14 @@ final class ExternalizedListVariableStateSupply implements ListVariableStateSupply { private final ListVariableDescriptor sourceVariableDescriptor; + private SingletonListInverseVariableProcessor inverseProcessor = + new ExternalizedSingletonListListInverseVariableProcessor<>(planningValue -> { + var elementLocation = getElementLocationMap().get(Objects.requireNonNull(planningValue)); + if (elementLocation == null) { + return null; + } + return elementLocation.entity(); + }); private Map elementLocationMap; private int unassignedCount; @@ -22,6 +32,16 @@ public ExternalizedListVariableStateSupply(ListVariableDescriptor sou this.sourceVariableDescriptor = sourceVariableDescriptor; } + public void externalizeSingletonListInverseVariable( + InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { + this.inverseProcessor = + new InternalSingletonListListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + } + + private Map getElementLocationMap() { + return elementLocationMap; + } + @Override public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector) { var workingSolution = scoreDirector.getWorkingSolution(); @@ -33,10 +53,11 @@ public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector // Start with everything unassigned. unassignedCount = (int) sourceVariableDescriptor.getValueRangeSize(workingSolution, null); // Will run over all entities and unmark all present elements as unassigned. - sourceVariableDescriptor.getEntityDescriptor().visitAllEntities(workingSolution, this::insert); + sourceVariableDescriptor.getEntityDescriptor() + .visitAllEntities(workingSolution, o -> insert(scoreDirector, o)); } - private void insert(Object entity) { + private void insert(ScoreDirector scoreDirector, Object entity) { var assignedElements = sourceVariableDescriptor.getValue(entity); var index = 0; for (var element : assignedElements) { @@ -46,6 +67,7 @@ private void insert(Object entity) { "The supply (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)." .formatted(this, element, index, oldLocation)); } + inverseProcessor.addElement((InnerScoreDirector) scoreDirector, entity, element); index++; unassignedCount--; } @@ -63,7 +85,7 @@ public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @ @Override public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { - insert(o); + insert(scoreDirector, o); } @Override @@ -75,10 +97,10 @@ public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { // When the entity is removed, its values become unassigned. // An unassigned value has no inverse entity and no index. - retract(o); + retract(scoreDirector, o); } - private void retract(Object entity) { + private void retract(ScoreDirector scoreDirector, Object entity) { var assignedElements = sourceVariableDescriptor.getValue(entity); for (var index = 0; index < assignedElements.size(); index++) { var element = assignedElements.get(index); @@ -94,6 +116,7 @@ private void retract(Object entity) { "The supply (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)." .formatted(this, element, index, oldIndex, index)); } + inverseProcessor.removeElement((InnerScoreDirector) scoreDirector, entity, element); unassignedCount++; } } @@ -106,6 +129,7 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector "The supply (%s) is corrupted, because the element (%s) did not exist before unassigning." .formatted(this, element)); } + inverseProcessor.unassignElement((InnerScoreDirector) scoreDirector, element); unassignedCount++; } @@ -118,15 +142,16 @@ public void beforeListVariableChanged(@NonNull ScoreDirector scoreDir @Override public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object o, int fromIndex, int toIndex) { - updateIndexes(o, fromIndex, toIndex); + updateIndexes(scoreDirector, o, fromIndex, toIndex); } - private void updateIndexes(Object entity, int startIndex, int toIndex) { + private void updateIndexes(ScoreDirector scoreDirector, Object entity, int startIndex, int toIndex) { var assignedElements = sourceVariableDescriptor.getValue(entity); for (var index = startIndex; index < assignedElements.size(); index++) { var element = assignedElements.get(index); var newLocation = ElementLocation.of(entity, index); var oldLocation = elementLocationMap.put(element, newLocation); + inverseProcessor.changeElement((InnerScoreDirector) scoreDirector, entity, element); if (oldLocation == null) { unassignedCount--; } else if (index >= toIndex && newLocation.equals(oldLocation)) { @@ -155,11 +180,7 @@ public Integer getIndex(Object planningValue) { @Override public Object getInverseSingleton(Object planningValue) { - var elementLocation = elementLocationMap.get(Objects.requireNonNull(planningValue)); - if (elementLocation == null) { - return null; - } - return elementLocation.entity(); + return inverseProcessor.getInverseSingleton(planningValue); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java new file mode 100644 index 0000000000..cc84cd4d1b --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java @@ -0,0 +1,40 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import java.util.function.Function; + +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; + +public final class ExternalizedSingletonListListInverseVariableProcessor + implements SingletonListInverseVariableProcessor { + + private final Function inverseSingletonFunction; + + public ExternalizedSingletonListListInverseVariableProcessor(Function inverseSingletonFunction) { + this.inverseSingletonFunction = inverseSingletonFunction; + } + + @Override + public void addElement(InnerScoreDirector scoreDirector, Object entity, Object element) { + // Do nothing. + } + + @Override + public void removeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { + // Do nothing. + } + + @Override + public void unassignElement(InnerScoreDirector scoreDirector, Object element) { + // Do nothing. + } + + @Override + public void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { + // Do nothing. + } + + @Override + public Object getInverseSingleton(Object planningValue) { + return inverseSingletonFunction.apply(planningValue); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java new file mode 100644 index 0000000000..1de7c3fe16 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java @@ -0,0 +1,69 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; + +public final class InternalSingletonListListInverseVariableProcessor + implements SingletonListInverseVariableProcessor { + + private final InverseRelationShadowVariableDescriptor shadowVariableDescriptor; + private final ListVariableDescriptor sourceVariableDescriptor; + + public InternalSingletonListListInverseVariableProcessor( + InverseRelationShadowVariableDescriptor shadowVariableDescriptor, + ListVariableDescriptor sourceVariableDescriptor) { + this.shadowVariableDescriptor = shadowVariableDescriptor; + this.sourceVariableDescriptor = sourceVariableDescriptor; + } + + @Override + public void addElement(InnerScoreDirector scoreDirector, Object entity, Object element) { + setInverse(scoreDirector, element, entity, null); + } + + @Override + public void removeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { + setInverse(scoreDirector, element, null, entity); + } + + @Override + public void unassignElement(InnerScoreDirector scoreDirector, Object element) { + updateInverse(scoreDirector, null, element); + } + + private void updateInverse(InnerScoreDirector scoreDirector, Object entity, Object element) { + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, entity); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + } + + @Override + public void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { + if (getInverseSingleton(element) != entity) { + updateInverse(scoreDirector, entity, element); + } + } + + private void setInverse(InnerScoreDirector scoreDirector, + Object element, Object inverseEntity, Object expectedOldInverseEntity) { + var oldInverseEntity = getInverseSingleton(element); + if (oldInverseEntity == inverseEntity) { + return; + } + if (scoreDirector.expectShadowVariablesInCorrectState() && oldInverseEntity != expectedOldInverseEntity) { + throw new IllegalStateException("The entity (" + inverseEntity + + ") has a list variable (" + sourceVariableDescriptor.getVariableName() + + ") and one of its elements (" + element + + ") which has a shadow variable (" + shadowVariableDescriptor.getVariableName() + + ") has an oldInverseEntity (" + oldInverseEntity + ") which is not that entity.\n" + + "Verify the consistency of your input problem for that shadow variable."); + } + updateInverse(scoreDirector, inverseEntity, element); + } + + @Override + public Object getInverseSingleton(Object planningValue) { + return shadowVariableDescriptor.getValue(planningValue); + } +} 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 b673df378b..fbf0435222 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 @@ -3,6 +3,7 @@ import ai.timefold.solver.core.api.domain.variable.ListVariableListener; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.index.IndexVariableSupply; +import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.inverserelation.SingletonInverseVariableSupply; import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; @@ -13,6 +14,8 @@ public interface ListVariableStateSupply extends IndexVariableSupply, ListVariableElementStateSupply { + void externalizeSingletonListInverseVariable(InverseRelationShadowVariableDescriptor shadowVariableDescriptor); + @Override ListVariableDescriptor getSourceVariableDescriptor(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java new file mode 100644 index 0000000000..96ff2db90d --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java @@ -0,0 +1,17 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import ai.timefold.solver.core.impl.domain.variable.inverserelation.SingletonInverseVariableSupply; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; + +public sealed interface SingletonListInverseVariableProcessor extends SingletonInverseVariableSupply + permits InternalSingletonListListInverseVariableProcessor, ExternalizedSingletonListListInverseVariableProcessor { + + void addElement(InnerScoreDirector scoreDirector, Object entity, Object element); + + void removeElement(InnerScoreDirector scoreDirector, Object entity, Object element); + + void unassignElement(InnerScoreDirector scoreDirector, Object element); + + void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/InverseRelationShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/InverseRelationShadowVariableDescriptor.java index 401d91bece..aa58a5e6d3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/InverseRelationShadowVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/InverseRelationShadowVariableDescriptor.java @@ -15,8 +15,8 @@ import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy; +import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.descriptor.BasicVariableDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.listener.VariableListenerWithSources; @@ -138,7 +138,8 @@ public Collection> getVariableListener if (chained) { return Collections.singleton(SingletonInverseVariableListener.class); } else { - return Collections.singleton(SingletonListInverseVariableListener.class); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } } else { return Collections.singleton(CollectionInverseVariableListener.class); @@ -155,7 +156,8 @@ public Demand getProvidedDemand() { if (chained) { return new SingletonInverseVariableDemand<>(sourceVariableDescriptor); } else { - return new SingletonListInverseVariableDemand<>((ListVariableDescriptor) sourceVariableDescriptor); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } } else { return new CollectionInverseVariableDemand<>(sourceVariableDescriptor); @@ -172,8 +174,8 @@ private AbstractVariableListener buildVariableListener() { if (chained) { return new SingletonInverseVariableListener<>(this, sourceVariableDescriptor); } else { - return new SingletonListInverseVariableListener<>( - this, (ListVariableDescriptor) sourceVariableDescriptor); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } } else { return new CollectionInverseVariableListener<>(this, sourceVariableDescriptor); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableDemand.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableDemand.java deleted file mode 100644 index b3c788987d..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableDemand.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable.inverserelation; - -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.domain.variable.supply.AbstractVariableDescriptorBasedDemand; -import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; - -public final class SingletonListInverseVariableDemand - extends AbstractVariableDescriptorBasedDemand { - - public SingletonListInverseVariableDemand(ListVariableDescriptor sourceVariableDescriptor) { - super(sourceVariableDescriptor); - } - - // ************************************************************************ - // Creation method - // ************************************************************************ - - @Override - public SingletonInverseVariableSupply createExternalizedSupply(SupplyManager supplyManager) { - return supplyManager.demand(((ListVariableDescriptor) variableDescriptor).getStateDemand()); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableListener.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableListener.java deleted file mode 100644 index 9169256d56..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableListener.java +++ /dev/null @@ -1,111 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable.inverserelation; - -import ai.timefold.solver.core.api.domain.variable.ListVariableListener; -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -public class SingletonListInverseVariableListener - implements ListVariableListener, SingletonInverseVariableSupply { - - protected final InverseRelationShadowVariableDescriptor shadowVariableDescriptor; - protected final ListVariableDescriptor sourceVariableDescriptor; - - public SingletonListInverseVariableListener( - InverseRelationShadowVariableDescriptor shadowVariableDescriptor, - ListVariableDescriptor sourceVariableDescriptor) { - this.shadowVariableDescriptor = shadowVariableDescriptor; - this.sourceVariableDescriptor = sourceVariableDescriptor; - } - - @Override - public void resetWorkingSolution(ScoreDirector scoreDirector) { - if (sourceVariableDescriptor.supportsPinning()) { - // Required for variable pinning, otherwise pinned values have their inverse set to null. - var entityDescriptor = sourceVariableDescriptor.getEntityDescriptor(); - entityDescriptor.getSolutionDescriptor() - .visitEntitiesByEntityClass(scoreDirector.getWorkingSolution(), entityDescriptor.getEntityClass(), - entity -> { - beforeEntityAdded(scoreDirector, entity); - afterEntityAdded(scoreDirector, entity); - return false; - }); - } - } - - @Override - public void beforeEntityAdded(ScoreDirector scoreDirector, Object entity) { - // Do nothing - } - - @Override - public void afterEntityAdded(ScoreDirector scoreDirector, Object entity) { - for (var element : sourceVariableDescriptor.getValue(entity)) { - setInverse((InnerScoreDirector) scoreDirector, element, entity, null); - } - } - - @Override - public void beforeEntityRemoved(ScoreDirector scoreDirector, Object entity) { - // Do nothing - } - - @Override - public void afterEntityRemoved(ScoreDirector scoreDirector, Object entity) { - var castScoreDirector = (InnerScoreDirector) scoreDirector; - for (var element : sourceVariableDescriptor.getValue(entity)) { - setInverse(castScoreDirector, element, null, entity); - } - } - - @Override - public void afterListVariableElementUnassigned(ScoreDirector scoreDirector, Object element) { - var castScoreDirector = (InnerScoreDirector) scoreDirector; - castScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); - castScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - - @Override - public void beforeListVariableChanged(ScoreDirector scoreDirector, Object entity, int fromIndex, int toIndex) { - // Do nothing - } - - @Override - public void afterListVariableChanged(ScoreDirector scoreDirector, Object entity, int fromIndex, int toIndex) { - var castScoreDirector = (InnerScoreDirector) scoreDirector; - var listVariable = sourceVariableDescriptor.getValue(entity); - for (var i = fromIndex; i < toIndex; i++) { - var element = listVariable.get(i); - if (getInverseSingleton(element) != entity) { - castScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, entity); - castScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - } - } - - private void setInverse(InnerScoreDirector scoreDirector, - Object element, Object inverseEntity, Object expectedOldInverseEntity) { - var oldInverseEntity = getInverseSingleton(element); - if (oldInverseEntity == inverseEntity) { - return; - } - if (scoreDirector.expectShadowVariablesInCorrectState() && oldInverseEntity != expectedOldInverseEntity) { - throw new IllegalStateException("The entity (" + inverseEntity - + ") has a list variable (" + sourceVariableDescriptor.getVariableName() - + ") and one of its elements (" + element - + ") which has a shadow variable (" + shadowVariableDescriptor.getVariableName() - + ") has an oldInverseEntity (" + oldInverseEntity + ") which is not that entity.\n" - + "Verify the consistency of your input problem for that shadow variable."); - } - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, inverseEntity); - scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - - @Override - public Object getInverseSingleton(Object planningValue) { - return shadowVariableDescriptor.getValue(planningValue); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java index 8a785be4c2..af78b44220 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java @@ -14,6 +14,7 @@ import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; import ai.timefold.solver.core.impl.domain.variable.listener.support.violation.ShadowVariablesAssert; import ai.timefold.solver.core.impl.domain.variable.supply.Demand; @@ -64,6 +65,24 @@ public void linkVariableListeners() { } private void processShadowVariableDescriptor(ShadowVariableDescriptor shadowVariableDescriptor) { + if (listVariableDescriptor == null) { + processShadowVariableDescriptorWithoutListVariable(shadowVariableDescriptor); + } else { + processShadowVariableDescriptorWithListVariable(shadowVariableDescriptor); + } + } + + private void processShadowVariableDescriptorWithListVariable(ShadowVariableDescriptor shadowVariableDescriptor) { + if (shadowVariableDescriptor instanceof InverseRelationShadowVariableDescriptor inverseRelationShadowVariableDescriptor) { + demand(listVariableDescriptor.getStateDemand()) + .externalizeSingletonListInverseVariable(inverseRelationShadowVariableDescriptor); + } else { // These are shadow variables not supported by the list variable supply; we use the standard mechanism. + processShadowVariableDescriptorWithoutListVariable(shadowVariableDescriptor); + } + } + + private void + processShadowVariableDescriptorWithoutListVariable(ShadowVariableDescriptor shadowVariableDescriptor) { for (var listenerWithSources : shadowVariableDescriptor.buildVariableListeners(this)) { var variableListener = listenerWithSources.getVariableListener(); if (variableListener instanceof Supply supply) { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupplyTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupplyTest.java index 84af3fcccc..8f12b9372b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupplyTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupplyTest.java @@ -9,6 +9,7 @@ import java.util.Arrays; import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.testdata.domain.list.allows_unassigned.TestdataAllowsUnassignedValuesListEntity; import ai.timefold.solver.core.impl.testdata.domain.list.allows_unassigned.TestdataAllowsUnassignedValuesListSolution; import ai.timefold.solver.core.impl.testdata.domain.list.allows_unassigned.TestdataAllowsUnassignedValuesListValue; @@ -21,7 +22,7 @@ class ExternalizedListVariableStateSupplyTest { @Test void initializeRoundTrip() { var variableDescriptor = TestdataAllowsUnassignedValuesListEntity.buildVariableDescriptorForValueList(); - var scoreDirector = (ScoreDirector) mock(ScoreDirector.class); + var scoreDirector = (ScoreDirector) mock(InnerScoreDirector.class); try (var supply = new ExternalizedListVariableStateSupply<>(variableDescriptor)) { var v1 = new TestdataAllowsUnassignedValuesListValue("1"); @@ -49,7 +50,7 @@ void initializeRoundTrip() { @Test void assignRoundTrip() { var variableDescriptor = TestdataAllowsUnassignedValuesListEntity.buildVariableDescriptorForValueList(); - var scoreDirector = (ScoreDirector) mock(ScoreDirector.class); + var scoreDirector = (ScoreDirector) mock(InnerScoreDirector.class); try (var supply = new ExternalizedListVariableStateSupply<>(variableDescriptor)) { var v1 = new TestdataAllowsUnassignedValuesListValue("1"); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableListenerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableListenerTest.java deleted file mode 100644 index 12de174d1e..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonListInverseVariableListenerTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable.inverserelation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListEntity; -import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListSolution; -import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListValue; - -import org.junit.jupiter.api.Test; - -class SingletonListInverseVariableListenerTest { - - private final ScoreDirector scoreDirector = mock(InnerScoreDirector.class); - - private final SingletonListInverseVariableListener inverseVariableListener = - new SingletonListInverseVariableListener<>( - TestdataListValue.buildVariableDescriptorForEntity(), - TestdataListEntity.buildVariableDescriptorForValueList()); - - @Test - void inverseRelation() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListEntity e1 = new TestdataListEntity("a", v1, v2); - TestdataListEntity e2 = new TestdataListEntity("b", v3); - - assertThat(v1.getEntity()).isNull(); - assertThat(v2.getEntity()).isNull(); - assertThat(v3.getEntity()).isNull(); - - inverseVariableListener.beforeEntityAdded(scoreDirector, e1); - inverseVariableListener.afterEntityAdded(scoreDirector, e1); - inverseVariableListener.beforeEntityAdded(scoreDirector, e2); - inverseVariableListener.afterEntityAdded(scoreDirector, e2); - - assertInverseEntity(v1, e1); - assertInverseEntity(v2, e1); - assertInverseEntity(v3, e2); - - // Move v1 from e1 to e2. - inverseVariableListener.beforeListVariableChanged(scoreDirector, e1, 0, 1); - e1.getValueList().remove(v1); - inverseVariableListener.afterListVariableChanged(scoreDirector, e1, 0, 0); - inverseVariableListener.beforeListVariableChanged(scoreDirector, e2, 1, 1); - e2.getValueList().add(v1); - inverseVariableListener.afterListVariableChanged(scoreDirector, e2, 1, 2); - - assertInverseEntity(v1, e2); - - // Assign v4 to e2[0]. - inverseVariableListener.beforeListVariableChanged(scoreDirector, e2, 0, 0); - e2.getValueList().add(0, v4); - inverseVariableListener.afterListVariableChanged(scoreDirector, e2, 0, 1); - - assertInverseEntity(v4, e2); - - // Unassign v2 from e1. - inverseVariableListener.beforeListVariableChanged(scoreDirector, e1, 0, 1); - e1.getValueList().remove(0); - inverseVariableListener.afterListVariableElementUnassigned(scoreDirector, v2); - inverseVariableListener.afterListVariableChanged(scoreDirector, e1, 0, 0); - - assertInverseEntity(v2, null); - } - - @Test - void removeEntity() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListEntity e1 = TestdataListEntity.createWithValues("a", v1, v2); - TestdataListEntity e2 = TestdataListEntity.createWithValues("b", v3); - - assertThat(v1.getEntity()).isEqualTo(e1); - assertThat(v2.getEntity()).isEqualTo(e1); - assertThat(v3.getEntity()).isEqualTo(e2); - - inverseVariableListener.beforeEntityRemoved(scoreDirector, e1); - inverseVariableListener.afterEntityRemoved(scoreDirector, e1); - - assertInverseEntity(v1, null); - assertInverseEntity(v2, null); - assertInverseEntity(v3, e2); - } - - void assertInverseEntity(TestdataListValue element, Object entity) { - assertThat(element.getEntity()).isEqualTo(entity); - assertThat(inverseVariableListener.getInverseSingleton(element)).isEqualTo(entity); - } -} From be5c63b26d925826609ff4953aa76729dfa538d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Thu, 14 Nov 2024 11:19:27 +0100 Subject: [PATCH 02/17] Move next fully into the state supply --- ...tractNextPrevElementVariableProcessor.java | 18 ++++++ .../ExternalizedListVariableStateSupply.java | 46 +++++++++++---- .../variable/ListVariableStateSupply.java | 3 + .../NextElementVariableProcessor.java | 56 +++++++++++++++++++ ... => PreviousElementVariableProcessor.java} | 30 +++++----- .../support/VariableListenerSupport.java | 4 ++ .../NextElementShadowVariableDescriptor.java | 9 +-- .../variable/ListVariableListenerTest.java | 12 +++- 8 files changed, 147 insertions(+), 31 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java rename core/src/main/java/ai/timefold/solver/core/impl/domain/variable/{nextprev/NextElementVariableListener.java => PreviousElementVariableProcessor.java} (76%) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java new file mode 100644 index 0000000000..094b9a8435 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java @@ -0,0 +1,18 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import java.util.List; + +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; + +abstract class AbstractNextPrevElementVariableProcessor { + + public abstract void addElement(InnerScoreDirector scoreDirector, List listVariable, + Object originalElement, LocationInList originalElementLocation); + + public abstract void removeElement(InnerScoreDirector scoreDirector, Object element); + + public abstract void changeElement(InnerScoreDirector scoreDirector, List listVariable, + Object originalElement, LocationInList originalElementLocation); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 05e7d9f60e..66c1f39c2d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -7,6 +7,7 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; @@ -25,6 +26,7 @@ final class ExternalizedListVariableStateSupply } return elementLocation.entity(); }); + private NextElementVariableProcessor nextElementProcessor; private Map elementLocationMap; private int unassignedCount; @@ -38,6 +40,10 @@ public void externalizeSingletonListInverseVariable( new InternalSingletonListListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); } + public void enableNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { + this.nextElementProcessor = new NextElementVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + } + private Map getElementLocationMap() { return elementLocationMap; } @@ -61,13 +67,18 @@ private void insert(ScoreDirector scoreDirector, Object entity) { var assignedElements = sourceVariableDescriptor.getValue(entity); var index = 0; for (var element : assignedElements) { - var oldLocation = elementLocationMap.put(element, ElementLocation.of(entity, index)); + var location = ElementLocation.of(entity, index); + var oldLocation = elementLocationMap.put(element, location); if (oldLocation != null) { throw new IllegalStateException( "The supply (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)." .formatted(this, element, index, oldLocation)); } inverseProcessor.addElement((InnerScoreDirector) scoreDirector, entity, element); + if (nextElementProcessor != null) { + nextElementProcessor.addElement((InnerScoreDirector) scoreDirector, assignedElements, element, + location); + } index++; unassignedCount--; } @@ -117,6 +128,9 @@ private void retract(ScoreDirector scoreDirector, Object entity) { .formatted(this, element, index, oldIndex, index)); } inverseProcessor.removeElement((InnerScoreDirector) scoreDirector, entity, element); + if (nextElementProcessor != null) { + nextElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); + } unassignedCount++; } } @@ -130,6 +144,9 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector .formatted(this, element)); } inverseProcessor.unassignElement((InnerScoreDirector) scoreDirector, element); + if (nextElementProcessor != null) { + nextElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); + } unassignedCount++; } @@ -142,20 +159,29 @@ public void beforeListVariableChanged(@NonNull ScoreDirector scoreDir @Override public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object o, int fromIndex, int toIndex) { - updateIndexes(scoreDirector, o, fromIndex, toIndex); - } - - private void updateIndexes(ScoreDirector scoreDirector, Object entity, int startIndex, int toIndex) { - var assignedElements = sourceVariableDescriptor.getValue(entity); - for (var index = startIndex; index < assignedElements.size(); index++) { + var assignedElements = sourceVariableDescriptor.getValue(o); + if (nextElementProcessor != null && fromIndex > 0) { + // If we need to process next elements, include the last element of the previous part of the list too. + // Otherwise the last element would point to the wrong next element. + nextElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, + assignedElements.get(fromIndex - 1), + ElementLocation.of(o, fromIndex - 1)); + } + for (var index = fromIndex; index < assignedElements.size(); index++) { var element = assignedElements.get(index); - var newLocation = ElementLocation.of(entity, index); + var newLocation = ElementLocation.of(o, index); var oldLocation = elementLocationMap.put(element, newLocation); - inverseProcessor.changeElement((InnerScoreDirector) scoreDirector, entity, element); + inverseProcessor.changeElement((InnerScoreDirector) scoreDirector, o, element); + if (nextElementProcessor != null) { + nextElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, element, + newLocation); + } if (oldLocation == null) { unassignedCount--; - } else if (index >= toIndex && newLocation.equals(oldLocation)) { + } + if (index >= toIndex && newLocation.equals(oldLocation)) { // Location is unchanged and we are past the part of the list that changed. + // Also, we don't need to update the next element shadows. return; } else { // Continue to the next element. 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 fbf0435222..6943a62512 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 @@ -6,6 +6,7 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.inverserelation.SingletonInverseVariableSupply; import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; +import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; public interface ListVariableStateSupply extends SourcedVariableListener, @@ -16,6 +17,8 @@ public interface ListVariableStateSupply extends void externalizeSingletonListInverseVariable(InverseRelationShadowVariableDescriptor shadowVariableDescriptor); + void enableNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor); + @Override ListVariableDescriptor getSourceVariableDescriptor(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java new file mode 100644 index 0000000000..27499ac547 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java @@ -0,0 +1,56 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import java.util.List; + +import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; + +public class NextElementVariableProcessor + extends AbstractNextPrevElementVariableProcessor { + + protected final NextElementShadowVariableDescriptor shadowVariableDescriptor; + protected final ListVariableDescriptor sourceVariableDescriptor; + + public NextElementVariableProcessor( + NextElementShadowVariableDescriptor shadowVariableDescriptor, + ListVariableDescriptor sourceVariableDescriptor) { + this.shadowVariableDescriptor = shadowVariableDescriptor; + this.sourceVariableDescriptor = sourceVariableDescriptor; + } + + @Override + public void addElement(InnerScoreDirector scoreDirector, List listVariable, + Object originalElement, LocationInList originalElementLocation) { + System.out.println("Add " + originalElement + " " + originalElementLocation); + var index = originalElementLocation.index(); + var next = index == listVariable.size() - 1 ? null : listVariable.get(index + 1); + if (shadowVariableDescriptor.getValue(originalElement) != next) { + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, originalElement); + shadowVariableDescriptor.setValue(originalElement, next); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, originalElement); + } + } + + @Override + public void removeElement(InnerScoreDirector scoreDirector, Object element) { + System.out.println("Rem " + element); + if (shadowVariableDescriptor.getValue(element) != null) { + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, null); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + } + } + + @Override + public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object originalElement, + LocationInList originalElementLocation) { + var index = originalElementLocation.index(); + if (index == listVariable.size() - 1) { + removeElement(scoreDirector, originalElement); + } else { + addElement(scoreDirector, listVariable, originalElement, originalElementLocation); + } + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/NextElementVariableListener.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java similarity index 76% rename from core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/NextElementVariableListener.java rename to core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java index 51da259ee5..a60419e208 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/NextElementVariableListener.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java @@ -1,22 +1,22 @@ -package ai.timefold.solver.core.impl.domain.variable.nextprev; +package ai.timefold.solver.core.impl.domain.variable; import java.util.List; import ai.timefold.solver.core.api.domain.variable.ListVariableListener; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import org.jspecify.annotations.NonNull; -public class NextElementVariableListener - implements ListVariableListener { +public class PreviousElementVariableProcessor implements ListVariableListener { - protected final NextElementShadowVariableDescriptor shadowVariableDescriptor; + protected final PreviousElementShadowVariableDescriptor shadowVariableDescriptor; protected final ListVariableDescriptor sourceVariableDescriptor; - public NextElementVariableListener( - NextElementShadowVariableDescriptor shadowVariableDescriptor, + public PreviousElementVariableProcessor( + PreviousElementShadowVariableDescriptor shadowVariableDescriptor, ListVariableDescriptor sourceVariableDescriptor) { this.shadowVariableDescriptor = shadowVariableDescriptor; this.sourceVariableDescriptor = sourceVariableDescriptor; @@ -31,11 +31,11 @@ public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @ public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; List listVariable = sourceVariableDescriptor.getValue(entity); - for (int i = 0; i < listVariable.size() - 1; i++) { + for (int i = 1; i < listVariable.size(); i++) { Object element = listVariable.get(i); - Object next = listVariable.get(i + 1); + Object previous = listVariable.get(i - 1); innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, next); + shadowVariableDescriptor.setValue(element, previous); innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); } } @@ -49,7 +49,7 @@ public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; List listVariable = sourceVariableDescriptor.getValue(entity); - for (int i = 0; i < listVariable.size() - 1; i++) { + for (int i = 1; i < listVariable.size(); i++) { Object element = listVariable.get(i); innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); shadowVariableDescriptor.setValue(element, null); @@ -78,15 +78,15 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire int toIndex) { InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; List listVariable = sourceVariableDescriptor.getValue(entity); - Object next = toIndex < listVariable.size() ? listVariable.get(toIndex) : null; - for (int i = toIndex - 1; i >= fromIndex - 1 && i >= 0; i--) { + Object previous = fromIndex > 0 ? listVariable.get(fromIndex - 1) : null; + for (int i = fromIndex; i <= toIndex && i < listVariable.size(); i++) { Object element = listVariable.get(i); - if (next != shadowVariableDescriptor.getValue(element)) { + if (previous != shadowVariableDescriptor.getValue(element)) { innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, next); + shadowVariableDescriptor.setValue(element, previous); innerScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); } - next = element; + previous = element; } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java index af78b44220..7d041de2d2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java @@ -17,6 +17,7 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; import ai.timefold.solver.core.impl.domain.variable.listener.support.violation.ShadowVariablesAssert; +import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.supply.Demand; import ai.timefold.solver.core.impl.domain.variable.supply.Supply; import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; @@ -76,6 +77,9 @@ private void processShadowVariableDescriptorWithListVariable(ShadowVariableDescr if (shadowVariableDescriptor instanceof InverseRelationShadowVariableDescriptor inverseRelationShadowVariableDescriptor) { demand(listVariableDescriptor.getStateDemand()) .externalizeSingletonListInverseVariable(inverseRelationShadowVariableDescriptor); + } else if (shadowVariableDescriptor instanceof NextElementShadowVariableDescriptor nextElementShadowVariableDescriptor) { + demand(listVariableDescriptor.getStateDemand()) + .enableNextElementShadowVariable(nextElementShadowVariableDescriptor); } else { // These are shadow variables not supported by the list variable supply; we use the standard mechanism. processShadowVariableDescriptorWithoutListVariable(shadowVariableDescriptor); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/NextElementShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/NextElementShadowVariableDescriptor.java index 9892ff4ecf..0af1d2cf32 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/NextElementShadowVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/NextElementShadowVariableDescriptor.java @@ -1,12 +1,12 @@ package ai.timefold.solver.core.impl.domain.variable.nextprev; import java.util.Collection; -import java.util.Collections; import ai.timefold.solver.core.api.domain.variable.AbstractVariableListener; import ai.timefold.solver.core.api.domain.variable.NextElementShadowVariable; import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; +import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.listener.VariableListenerWithSources; import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; @@ -30,12 +30,13 @@ String getAnnotationName() { @Override public Collection> getVariableListenerClasses() { - return Collections.singleton(NextElementVariableListener.class); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } @Override public Iterable> buildVariableListeners(SupplyManager supplyManager) { - return new VariableListenerWithSources<>(new NextElementVariableListener<>(this, sourceVariableDescriptor), - sourceVariableDescriptor).toCollection(); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java index 702a950417..8279b76b33 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java @@ -69,8 +69,12 @@ static void assertEmptyNextHistory(TestdataListValueWithShadowHistory element) { static void assertNextHistory(TestdataListValueWithShadowHistory element, TestdataListValueWithShadowHistory... nextHistory) { - assertThat(element.getNext()).isEqualTo(nextHistory[nextHistory.length - 1]); - assertThat(element.getNextHistory()).containsExactly(nextHistory); + assertThat(element.getNext()) + .as("Next is incorrect") + .isEqualTo(nextHistory[nextHistory.length - 1]); + assertThat(element.getNextHistory()) + .as("History is incorrect") + .containsExactly(nextHistory); } void doChangeMove( @@ -197,6 +201,10 @@ void addAndRemoveElement() { assertPreviousHistory(x, b); assertPreviousHistory(c, b, x); + System.out.println("Next of A: " + a.getNext() + " " + a.getNextHistory()); + System.out.println("Next of B: " + b.getNext() + " " + b.getNextHistory()); + System.out.println("Next of X: " + x.getNext() + " " + x.getNextHistory()); + System.out.println("Next of C: " + c.getNext() + " " + c.getNextHistory()); assertNextHistory(a, b); assertNextHistory(b, c, x); assertNextHistory(x, c); From 857a2fb1bd60f5fa3cb5f1c2ea62e343bf4a5c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Thu, 14 Nov 2024 12:00:51 +0100 Subject: [PATCH 03/17] Move previous fully into the state supply --- ...tractNextPrevElementVariableProcessor.java | 3 +- .../ExternalizedListVariableStateSupply.java | 36 ++++++- .../variable/ListVariableStateSupply.java | 3 + .../NextElementVariableProcessor.java | 35 +++---- .../PreviousElementVariableProcessor.java | 95 +++++-------------- .../support/VariableListenerSupport.java | 4 + ...eviousElementShadowVariableDescriptor.java | 9 +- .../PreviousElementVariableListener.java | 91 ------------------ 8 files changed, 88 insertions(+), 188 deletions(-) delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/PreviousElementVariableListener.java diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java index 094b9a8435..70a6964546 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java @@ -5,7 +5,8 @@ import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; -abstract class AbstractNextPrevElementVariableProcessor { +abstract sealed class AbstractNextPrevElementVariableProcessor + permits NextElementVariableProcessor, PreviousElementVariableProcessor { public abstract void addElement(InnerScoreDirector scoreDirector, List listVariable, Object originalElement, LocationInList originalElementLocation); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 66c1f39c2d..76d59d99e1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -8,6 +8,7 @@ import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; @@ -26,6 +27,7 @@ final class ExternalizedListVariableStateSupply } return elementLocation.entity(); }); + private PreviousElementVariableProcessor previousElementProcessor; private NextElementVariableProcessor nextElementProcessor; private Map elementLocationMap; private int unassignedCount; @@ -34,14 +36,23 @@ public ExternalizedListVariableStateSupply(ListVariableDescriptor sou this.sourceVariableDescriptor = sourceVariableDescriptor; } + @Override public void externalizeSingletonListInverseVariable( InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { this.inverseProcessor = new InternalSingletonListListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); } + @Override + public void + enablePreviousElementShadowVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { + this.previousElementProcessor = + new PreviousElementVariableProcessor<>(shadowVariableDescriptor); + } + + @Override public void enableNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { - this.nextElementProcessor = new NextElementVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + this.nextElementProcessor = new NextElementVariableProcessor<>(shadowVariableDescriptor); } private Map getElementLocationMap() { @@ -79,6 +90,10 @@ private void insert(ScoreDirector scoreDirector, Object entity) { nextElementProcessor.addElement((InnerScoreDirector) scoreDirector, assignedElements, element, location); } + if (previousElementProcessor != null) { + previousElementProcessor.addElement((InnerScoreDirector) scoreDirector, assignedElements, element, + location); + } index++; unassignedCount--; } @@ -131,6 +146,9 @@ private void retract(ScoreDirector scoreDirector, Object entity) { if (nextElementProcessor != null) { nextElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); } + if (previousElementProcessor != null) { + previousElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); + } unassignedCount++; } } @@ -147,6 +165,9 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector if (nextElementProcessor != null) { nextElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); } + if (previousElementProcessor != null) { + previousElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); + } unassignedCount++; } @@ -176,12 +197,23 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire nextElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, element, newLocation); } + if (previousElementProcessor != null) { + previousElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, + element, + newLocation); + } if (oldLocation == null) { unassignedCount--; } if (index >= toIndex && newLocation.equals(oldLocation)) { // Location is unchanged and we are past the part of the list that changed. - // Also, we don't need to update the next element shadows. + // If we need to process previous elements, include the last element of the previous part of the list too. + // Otherwise the last element would point to the wrong next element. + if (previousElementProcessor != null && index < assignedElements.size() - 1) { + previousElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, + assignedElements.get(index + 1), ElementLocation.of(o, index + 1)); + } + // Finally, we can terminate the loop prematurely. return; } else { // Continue to the next element. 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 6943a62512..e6e1ae4d55 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 @@ -7,6 +7,7 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.SingletonInverseVariableSupply; import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; public interface ListVariableStateSupply extends SourcedVariableListener, @@ -17,6 +18,8 @@ public interface ListVariableStateSupply extends void externalizeSingletonListInverseVariable(InverseRelationShadowVariableDescriptor shadowVariableDescriptor); + void enablePreviousElementShadowVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor); + void enableNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor); @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java index 27499ac547..1f3318d048 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java @@ -2,55 +2,48 @@ import java.util.List; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; -public class NextElementVariableProcessor +final class NextElementVariableProcessor extends AbstractNextPrevElementVariableProcessor { - protected final NextElementShadowVariableDescriptor shadowVariableDescriptor; - protected final ListVariableDescriptor sourceVariableDescriptor; + private final NextElementShadowVariableDescriptor shadowVariableDescriptor; - public NextElementVariableProcessor( - NextElementShadowVariableDescriptor shadowVariableDescriptor, - ListVariableDescriptor sourceVariableDescriptor) { + public NextElementVariableProcessor(NextElementShadowVariableDescriptor shadowVariableDescriptor) { this.shadowVariableDescriptor = shadowVariableDescriptor; - this.sourceVariableDescriptor = sourceVariableDescriptor; } @Override public void addElement(InnerScoreDirector scoreDirector, List listVariable, Object originalElement, LocationInList originalElementLocation) { - System.out.println("Add " + originalElement + " " + originalElementLocation); var index = originalElementLocation.index(); var next = index == listVariable.size() - 1 ? null : listVariable.get(index + 1); - if (shadowVariableDescriptor.getValue(originalElement) != next) { - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, originalElement); - shadowVariableDescriptor.setValue(originalElement, next); - scoreDirector.afterVariableChanged(shadowVariableDescriptor, originalElement); - } + setValue(scoreDirector, originalElement, next); } - @Override - public void removeElement(InnerScoreDirector scoreDirector, Object element) { - System.out.println("Rem " + element); - if (shadowVariableDescriptor.getValue(element) != null) { + void setValue(InnerScoreDirector scoreDirector, Object element, Object value) { + if (shadowVariableDescriptor.getValue(element) != value) { scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); + shadowVariableDescriptor.setValue(element, value); scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); } } + @Override + public void removeElement(InnerScoreDirector scoreDirector, Object element) { + setValue(scoreDirector, element, null); + } + @Override public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object originalElement, LocationInList originalElementLocation) { var index = originalElementLocation.index(); if (index == listVariable.size() - 1) { - removeElement(scoreDirector, originalElement); + setValue(scoreDirector, originalElement, null); } else { - addElement(scoreDirector, listVariable, originalElement, originalElementLocation); + setValue(scoreDirector, originalElement, listVariable.get(index + 1)); } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java index a60419e208..66eef3bedf 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java @@ -2,91 +2,48 @@ import java.util.List; -import ai.timefold.solver.core.api.domain.variable.ListVariableListener; -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; -import org.jspecify.annotations.NonNull; +final class PreviousElementVariableProcessor + extends AbstractNextPrevElementVariableProcessor { -public class PreviousElementVariableProcessor implements ListVariableListener { + private final PreviousElementShadowVariableDescriptor shadowVariableDescriptor; - protected final PreviousElementShadowVariableDescriptor shadowVariableDescriptor; - protected final ListVariableDescriptor sourceVariableDescriptor; - - public PreviousElementVariableProcessor( - PreviousElementShadowVariableDescriptor shadowVariableDescriptor, - ListVariableDescriptor sourceVariableDescriptor) { + public PreviousElementVariableProcessor(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { this.shadowVariableDescriptor = shadowVariableDescriptor; - this.sourceVariableDescriptor = sourceVariableDescriptor; - } - - @Override - public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - // Do nothing - } - - @Override - public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - List listVariable = sourceVariableDescriptor.getValue(entity); - for (int i = 1; i < listVariable.size(); i++) { - Object element = listVariable.get(i); - Object previous = listVariable.get(i - 1); - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, previous); - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - } - } - - @Override - public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - // Do nothing - } - - @Override - public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - List listVariable = sourceVariableDescriptor.getValue(entity); - for (int i = 1; i < listVariable.size(); i++) { - Object element = listVariable.get(i); - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - } } @Override - public void afterListVariableElementUnassigned(@NonNull ScoreDirector scoreDirector, @NonNull Object element) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - if (shadowVariableDescriptor.getValue(element) != null) { - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); - innerScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + public void addElement(InnerScoreDirector scoreDirector, List listVariable, + Object originalElement, LocationInList originalElementLocation) { + var index = originalElementLocation.index(); + var previous = index == 0 ? null : listVariable.get(originalElementLocation.index() - 1); + setValue(scoreDirector, originalElement, previous); + } + + void setValue(InnerScoreDirector scoreDirector, Object element, Object value) { + if (shadowVariableDescriptor.getValue(element) != value) { + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, value); + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); } } @Override - public void beforeListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, - int fromIndex, int toIndex) { - // Do nothing + public void removeElement(InnerScoreDirector scoreDirector, Object element) { + setValue(scoreDirector, element, null); } @Override - public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, int fromIndex, - int toIndex) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - List listVariable = sourceVariableDescriptor.getValue(entity); - Object previous = fromIndex > 0 ? listVariable.get(fromIndex - 1) : null; - for (int i = fromIndex; i <= toIndex && i < listVariable.size(); i++) { - Object element = listVariable.get(i); - if (previous != shadowVariableDescriptor.getValue(element)) { - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, previous); - innerScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - previous = element; + public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object originalElement, + LocationInList originalElementLocation) { + var index = originalElementLocation.index(); + if (index == 0) { + setValue(scoreDirector, originalElement, null); + } else { + setValue(scoreDirector, originalElement, listVariable.get(index - 1)); } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java index 7d041de2d2..f442b7ef7f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java @@ -18,6 +18,7 @@ import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; import ai.timefold.solver.core.impl.domain.variable.listener.support.violation.ShadowVariablesAssert; import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.supply.Demand; import ai.timefold.solver.core.impl.domain.variable.supply.Supply; import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; @@ -77,6 +78,9 @@ private void processShadowVariableDescriptorWithListVariable(ShadowVariableDescr if (shadowVariableDescriptor instanceof InverseRelationShadowVariableDescriptor inverseRelationShadowVariableDescriptor) { demand(listVariableDescriptor.getStateDemand()) .externalizeSingletonListInverseVariable(inverseRelationShadowVariableDescriptor); + } else if (shadowVariableDescriptor instanceof PreviousElementShadowVariableDescriptor previousElementShadowVariableDescriptor) { + demand(listVariableDescriptor.getStateDemand()) + .enablePreviousElementShadowVariable(previousElementShadowVariableDescriptor); } else if (shadowVariableDescriptor instanceof NextElementShadowVariableDescriptor nextElementShadowVariableDescriptor) { demand(listVariableDescriptor.getStateDemand()) .enableNextElementShadowVariable(nextElementShadowVariableDescriptor); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/PreviousElementShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/PreviousElementShadowVariableDescriptor.java index 93a0acf89a..c46fdc8f6c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/PreviousElementShadowVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/PreviousElementShadowVariableDescriptor.java @@ -1,12 +1,12 @@ package ai.timefold.solver.core.impl.domain.variable.nextprev; import java.util.Collection; -import java.util.Collections; import ai.timefold.solver.core.api.domain.variable.AbstractVariableListener; import ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable; import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; +import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.listener.VariableListenerWithSources; import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; @@ -30,12 +30,13 @@ String getAnnotationName() { @Override public Collection> getVariableListenerClasses() { - return Collections.singleton(PreviousElementVariableListener.class); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } @Override public Iterable> buildVariableListeners(SupplyManager supplyManager) { - return new VariableListenerWithSources<>(new PreviousElementVariableListener<>(this, sourceVariableDescriptor), - sourceVariableDescriptor).toCollection(); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/PreviousElementVariableListener.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/PreviousElementVariableListener.java deleted file mode 100644 index bc8e9ae9a6..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/PreviousElementVariableListener.java +++ /dev/null @@ -1,91 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable.nextprev; - -import java.util.List; - -import ai.timefold.solver.core.api.domain.variable.ListVariableListener; -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -import org.jspecify.annotations.NonNull; - -public class PreviousElementVariableListener implements ListVariableListener { - - protected final PreviousElementShadowVariableDescriptor shadowVariableDescriptor; - protected final ListVariableDescriptor sourceVariableDescriptor; - - public PreviousElementVariableListener( - PreviousElementShadowVariableDescriptor shadowVariableDescriptor, - ListVariableDescriptor sourceVariableDescriptor) { - this.shadowVariableDescriptor = shadowVariableDescriptor; - this.sourceVariableDescriptor = sourceVariableDescriptor; - } - - @Override - public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - // Do nothing - } - - @Override - public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - List listVariable = sourceVariableDescriptor.getValue(entity); - for (int i = 1; i < listVariable.size(); i++) { - Object element = listVariable.get(i); - Object previous = listVariable.get(i - 1); - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, previous); - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - } - } - - @Override - public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - // Do nothing - } - - @Override - public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - List listVariable = sourceVariableDescriptor.getValue(entity); - for (int i = 1; i < listVariable.size(); i++) { - Object element = listVariable.get(i); - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - } - } - - @Override - public void afterListVariableElementUnassigned(@NonNull ScoreDirector scoreDirector, @NonNull Object element) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - if (shadowVariableDescriptor.getValue(element) != null) { - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); - innerScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - } - - @Override - public void beforeListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, - int fromIndex, int toIndex) { - // Do nothing - } - - @Override - public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, int fromIndex, - int toIndex) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - List listVariable = sourceVariableDescriptor.getValue(entity); - Object previous = fromIndex > 0 ? listVariable.get(fromIndex - 1) : null; - for (int i = fromIndex; i <= toIndex && i < listVariable.size(); i++) { - Object element = listVariable.get(i); - if (previous != shadowVariableDescriptor.getValue(element)) { - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, previous); - innerScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - previous = element; - } - } -} From 5739e330def674380e435fef5621b102d830158b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Thu, 14 Nov 2024 13:44:38 +0100 Subject: [PATCH 04/17] Move index fully into the state supply --- .../ExternalizedIndexVariableProcessor.java | 40 ++++++++ .../ExternalizedListVariableStateSupply.java | 29 ++++-- .../variable/IndexVariableProcessor.java | 17 ++++ .../InternalIndexVariableProcessor.java | 51 ++++++++++ .../variable/ListVariableStateSupply.java | 3 + .../index/IndexShadowVariableDescriptor.java | 14 ++- .../variable/index/IndexVariableDemand.java | 23 ----- .../variable/index/IndexVariableListener.java | 98 ------------------- .../variable/index/IndexVariableSupply.java | 3 - .../SingletonInverseVariableSupply.java | 4 - .../support/VariableListenerSupport.java | 6 +- ...xtPrevElementShadowVariableDescriptor.java | 5 +- .../generic/list/kopt/KOptDescriptor.java | 3 +- .../variable/ListVariableListenerTest.java | 12 +-- .../index/IndexVariableListenerTest.java | 97 ------------------ 15 files changed, 156 insertions(+), 249 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableDemand.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableListener.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableListenerTest.java diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java new file mode 100644 index 0000000000..bce954bf7b --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java @@ -0,0 +1,40 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import java.util.function.Function; + +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; + +public final class ExternalizedIndexVariableProcessor + implements IndexVariableProcessor { + + private final Function indexFunction; + + public ExternalizedIndexVariableProcessor(Function indexFunction) { + this.indexFunction = indexFunction; + } + + @Override + public void addElement(InnerScoreDirector scoreDirector, Object element, Integer index) { + // Do nothing. + } + + @Override + public void removeElement(InnerScoreDirector scoreDirector, Object element) { + // Do nothing. + } + + @Override + public void unassignElement(InnerScoreDirector scoreDirector, Object element) { + // Do nothing. + } + + @Override + public void changeElement(InnerScoreDirector scoreDirector, Object element, Integer index) { + // Do nothing. + } + + @Override + public Integer getIndex(Object planningValue) { + return indexFunction.apply(planningValue); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 76d59d99e1..15fc354037 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -6,6 +6,7 @@ import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; @@ -19,9 +20,16 @@ final class ExternalizedListVariableStateSupply implements ListVariableStateSupply { private final ListVariableDescriptor sourceVariableDescriptor; + private IndexVariableProcessor indexProcessor = new ExternalizedIndexVariableProcessor<>(planningValue -> { + var elementLocation = getElementLocation(planningValue); + if (elementLocation == null) { + return null; + } + return elementLocation.index(); + }); private SingletonListInverseVariableProcessor inverseProcessor = new ExternalizedSingletonListListInverseVariableProcessor<>(planningValue -> { - var elementLocation = getElementLocationMap().get(Objects.requireNonNull(planningValue)); + var elementLocation = getElementLocation(planningValue); if (elementLocation == null) { return null; } @@ -36,6 +44,11 @@ public ExternalizedListVariableStateSupply(ListVariableDescriptor sou this.sourceVariableDescriptor = sourceVariableDescriptor; } + @Override + public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { + this.indexProcessor = new InternalIndexVariableProcessor<>(shadowVariableDescriptor); + } + @Override public void externalizeSingletonListInverseVariable( InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { @@ -55,8 +68,8 @@ public void enableNextElementShadowVariable(NextElementShadowVariableDescriptor< this.nextElementProcessor = new NextElementVariableProcessor<>(shadowVariableDescriptor); } - private Map getElementLocationMap() { - return elementLocationMap; + private LocationInList getElementLocation(Object planningValue) { + return elementLocationMap.get(Objects.requireNonNull(planningValue)); } @Override @@ -85,6 +98,7 @@ private void insert(ScoreDirector scoreDirector, Object entity) { "The supply (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)." .formatted(this, element, index, oldLocation)); } + indexProcessor.addElement((InnerScoreDirector) scoreDirector, element, index); inverseProcessor.addElement((InnerScoreDirector) scoreDirector, entity, element); if (nextElementProcessor != null) { nextElementProcessor.addElement((InnerScoreDirector) scoreDirector, assignedElements, element, @@ -142,6 +156,7 @@ private void retract(ScoreDirector scoreDirector, Object entity) { "The supply (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)." .formatted(this, element, index, oldIndex, index)); } + indexProcessor.removeElement((InnerScoreDirector) scoreDirector, element); inverseProcessor.removeElement((InnerScoreDirector) scoreDirector, entity, element); if (nextElementProcessor != null) { nextElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); @@ -161,6 +176,7 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector "The supply (%s) is corrupted, because the element (%s) did not exist before unassigning." .formatted(this, element)); } + indexProcessor.unassignElement((InnerScoreDirector) scoreDirector, element); inverseProcessor.unassignElement((InnerScoreDirector) scoreDirector, element); if (nextElementProcessor != null) { nextElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); @@ -192,6 +208,7 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire var element = assignedElements.get(index); var newLocation = ElementLocation.of(o, index); var oldLocation = elementLocationMap.put(element, newLocation); + indexProcessor.changeElement((InnerScoreDirector) scoreDirector, element, index); inverseProcessor.changeElement((InnerScoreDirector) scoreDirector, o, element); if (nextElementProcessor != null) { nextElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, element, @@ -229,11 +246,7 @@ public ElementLocation getLocationInList(Object planningValue) { @Override public Integer getIndex(Object planningValue) { - var elementLocation = elementLocationMap.get(Objects.requireNonNull(planningValue)); - if (elementLocation == null) { - return null; - } - return elementLocation.index(); + return indexProcessor.getIndex(planningValue); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java new file mode 100644 index 0000000000..80c5bf376e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java @@ -0,0 +1,17 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import ai.timefold.solver.core.impl.domain.variable.index.IndexVariableSupply; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; + +public sealed interface IndexVariableProcessor extends IndexVariableSupply + permits InternalIndexVariableProcessor, ExternalizedIndexVariableProcessor { + + void addElement(InnerScoreDirector scoreDirector, Object element, Integer index); + + void removeElement(InnerScoreDirector scoreDirector, Object element); + + void unassignElement(InnerScoreDirector scoreDirector, Object element); + + void changeElement(InnerScoreDirector scoreDirector, Object element, Integer index); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java new file mode 100644 index 0000000000..9137746296 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java @@ -0,0 +1,51 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import java.util.Objects; + +import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; + +public final class InternalIndexVariableProcessor + implements IndexVariableProcessor { + + private final IndexShadowVariableDescriptor shadowVariableDescriptor; + + public InternalIndexVariableProcessor(IndexShadowVariableDescriptor shadowVariableDescriptor) { + this.shadowVariableDescriptor = shadowVariableDescriptor; + } + + @Override + public void addElement(InnerScoreDirector scoreDirector, Object element, Integer index) { + updateIndex(scoreDirector, element, index); + } + + @Override + public void removeElement(InnerScoreDirector scoreDirector, Object element) { + updateIndex(scoreDirector, element, null); + } + + @Override + public void unassignElement(InnerScoreDirector scoreDirector, Object element) { + updateIndex(scoreDirector, element, null); + } + + @Override + public void changeElement(InnerScoreDirector scoreDirector, Object element, Integer index) { + updateIndex(scoreDirector, element, index); + } + + private void updateIndex(InnerScoreDirector scoreDirector, Object element, Integer index) { + var oldIndex = shadowVariableDescriptor.getValue(element); + if (!Objects.equals(oldIndex, index)) { + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, index); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + } + } + + @Override + public Integer getIndex(Object planningValue) { + return shadowVariableDescriptor.getValue(planningValue); + } + +} 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 e6e1ae4d55..4b66af93f7 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 @@ -2,6 +2,7 @@ import ai.timefold.solver.core.api.domain.variable.ListVariableListener; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.index.IndexVariableSupply; import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.inverserelation.SingletonInverseVariableSupply; @@ -16,6 +17,8 @@ public interface ListVariableStateSupply extends IndexVariableSupply, ListVariableElementStateSupply { + void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor); + void externalizeSingletonListInverseVariable(InverseRelationShadowVariableDescriptor shadowVariableDescriptor); void enablePreviousElementShadowVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexShadowVariableDescriptor.java index 6f216df7aa..3cf942c09a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexShadowVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexShadowVariableDescriptor.java @@ -11,10 +11,12 @@ import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy; +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.descriptor.ShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.listener.VariableListenerWithSources; +import ai.timefold.solver.core.impl.domain.variable.supply.Demand; import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; public final class IndexShadowVariableDescriptor extends ShadowVariableDescriptor { @@ -94,18 +96,20 @@ public List> getSourceVariableDescriptorList() { @Override public Collection> getVariableListenerClasses() { - return Collections.singleton(IndexVariableListener.class); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } @Override - public IndexVariableDemand getProvidedDemand() { - return new IndexVariableDemand<>(sourceVariableDescriptor); + public Demand getProvidedDemand() { + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } @Override public Iterable> buildVariableListeners(SupplyManager supplyManager) { - return new VariableListenerWithSources<>(new IndexVariableListener<>(this, sourceVariableDescriptor), - sourceVariableDescriptor).toCollection(); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableDemand.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableDemand.java deleted file mode 100644 index 46a4e27419..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableDemand.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable.index; - -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.domain.variable.supply.AbstractVariableDescriptorBasedDemand; -import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager; - -public final class IndexVariableDemand - extends AbstractVariableDescriptorBasedDemand { - - public IndexVariableDemand(ListVariableDescriptor sourceVariableDescriptor) { - super(sourceVariableDescriptor); - } - - // ************************************************************************ - // Creation method - // ************************************************************************ - - @Override - public IndexVariableSupply createExternalizedSupply(SupplyManager supplyManager) { - return supplyManager.demand(((ListVariableDescriptor) variableDescriptor).getStateDemand()); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableListener.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableListener.java deleted file mode 100644 index 6e7a7df48f..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableListener.java +++ /dev/null @@ -1,98 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable.index; - -import java.util.List; -import java.util.Objects; - -import ai.timefold.solver.core.api.domain.variable.ListVariableListener; -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -import org.jspecify.annotations.NonNull; - -public class IndexVariableListener implements ListVariableListener, IndexVariableSupply { - - protected final IndexShadowVariableDescriptor shadowVariableDescriptor; - protected final ListVariableDescriptor sourceVariableDescriptor; - - private static final int NEVER_QUIT_EARLY = Integer.MAX_VALUE; - - public IndexVariableListener( - IndexShadowVariableDescriptor shadowVariableDescriptor, - ListVariableDescriptor sourceVariableDescriptor) { - this.shadowVariableDescriptor = shadowVariableDescriptor; - this.sourceVariableDescriptor = sourceVariableDescriptor; - } - - @Override - public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - // Do nothing - } - - @Override - public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - updateIndexes((InnerScoreDirector) scoreDirector, entity, 0, NEVER_QUIT_EARLY); - } - - @Override - public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - // Do nothing - } - - @Override - public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - List listVariable = sourceVariableDescriptor.getValue(entity); - for (Object element : listVariable) { - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); - innerScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - } - - @Override - public void afterListVariableElementUnassigned(@NonNull ScoreDirector scoreDirector, @NonNull Object element) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - innerScoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); - innerScoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - - @Override - public void beforeListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, - int fromIndex, int toIndex) { - // Do nothing - } - - @Override - public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, int fromIndex, - int toIndex) { - InnerScoreDirector innerScoreDirector = (InnerScoreDirector) scoreDirector; - updateIndexes(innerScoreDirector, entity, fromIndex, toIndex); - } - - private void updateIndexes(InnerScoreDirector scoreDirector, Object entity, int fromIndex, int toIndex) { - List listVariable = sourceVariableDescriptor.getValue(entity); - for (int i = fromIndex; i < listVariable.size(); i++) { - Object element = listVariable.get(i); - Integer oldIndex = shadowVariableDescriptor.getValue(element); - if (!Objects.equals(oldIndex, i)) { - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, i); - scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } else if (i >= toIndex) { - // Do not quit early while inside the affected subList range. - // Example 1. When X is moved from Ann[3] to Beth[3], we need to start updating Beth's elements at index 3 - // where X already has the expected index, but quitting there would be incorrect because all the elements - // above X need their indexes incremented. - // Example 2. After ListSwapMove(Ann, 5, 9), the listener must not quit at index 6, but it can quit at index 10. - return; - } - } - } - - @Override - public Integer getIndex(Object planningValue) { - return shadowVariableDescriptor.getValue(planningValue); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableSupply.java index 5c3561c570..6cfc2e0607 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableSupply.java @@ -2,12 +2,9 @@ import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.impl.domain.variable.supply.Supply; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; /** * Only supported for {@link PlanningListVariable list variables}. - *

- * To get an instance, demand an {@link IndexVariableDemand} from {@link InnerScoreDirector#getSupplyManager()}. */ public interface IndexVariableSupply extends Supply { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonInverseVariableSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonInverseVariableSupply.java index adc0d6ed20..6c3f888dc1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonInverseVariableSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/SingletonInverseVariableSupply.java @@ -2,14 +2,10 @@ import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.impl.domain.variable.supply.Supply; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; /** * Currently only supported for chained variables and {@link PlanningListVariable list variables}, * which guarantee that no 2 entities use the same planningValue. - *

- * To get an instance, demand a {@link SingletonInverseVariableDemand} (for a chained variable) - * or a {@link SingletonListInverseVariableDemand} (for a list variable) from {@link InnerScoreDirector#getSupplyManager()}. */ public interface SingletonInverseVariableSupply extends Supply { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java index f442b7ef7f..3584360820 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java @@ -14,6 +14,7 @@ import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; import ai.timefold.solver.core.impl.domain.variable.listener.support.violation.ShadowVariablesAssert; @@ -75,7 +76,10 @@ private void processShadowVariableDescriptor(ShadowVariableDescriptor } private void processShadowVariableDescriptorWithListVariable(ShadowVariableDescriptor shadowVariableDescriptor) { - if (shadowVariableDescriptor instanceof InverseRelationShadowVariableDescriptor inverseRelationShadowVariableDescriptor) { + if (shadowVariableDescriptor instanceof IndexShadowVariableDescriptor indexShadowVariableDescriptor) { + demand(listVariableDescriptor.getStateDemand()) + .externalizeIndexVariable(indexShadowVariableDescriptor); + } else if (shadowVariableDescriptor instanceof InverseRelationShadowVariableDescriptor inverseRelationShadowVariableDescriptor) { demand(listVariableDescriptor.getStateDemand()) .externalizeSingletonListInverseVariable(inverseRelationShadowVariableDescriptor); } else if (shadowVariableDescriptor instanceof PreviousElementShadowVariableDescriptor previousElementShadowVariableDescriptor) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/AbstractNextPrevElementShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/AbstractNextPrevElementShadowVariableDescriptor.java index fa3600fa89..fb7efe3816 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/AbstractNextPrevElementShadowVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/nextprev/AbstractNextPrevElementShadowVariableDescriptor.java @@ -8,6 +8,7 @@ import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy; +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.descriptor.ShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; @@ -93,7 +94,7 @@ public List> getSourceVariableDescriptorList() { @Override public Demand getProvidedDemand() { - throw new UnsupportedOperationException( - "Not implemented because no subsystems demand previous or next shadow variables."); + throw new UnsupportedOperationException("Impossible state: Handled by %s." + .formatted(ListVariableStateSupply.class.getSimpleName())); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptDescriptor.java index 82c148ce1f..ba049268e1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/KOptDescriptor.java @@ -165,8 +165,7 @@ public KOptListMove getKOptListMove(ListVariableStateSupp } var combinedList = computeCombinedList(listVariableDescriptor, listVariableStateSupply); - IndexVariableSupply indexVariableSupply = - node -> combinedList.getIndexOfValue(listVariableStateSupply, node); + IndexVariableSupply indexVariableSupply = node -> combinedList.getIndexOfValue(listVariableStateSupply, node); var entityListSize = combinedList.size(); List out = new ArrayList<>(); var originalToCurrentIndexList = new int[entityListSize]; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java index 8279b76b33..17fe1a8d3f 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/ListVariableListenerTest.java @@ -58,8 +58,12 @@ static void assertEmptyPreviousHistory(TestdataListValueWithShadowHistory elemen static void assertPreviousHistory(TestdataListValueWithShadowHistory element, TestdataListValueWithShadowHistory... previousHistory) { - assertThat(element.getPrevious()).isEqualTo(previousHistory[previousHistory.length - 1]); - assertThat(element.getPreviousHistory()).containsExactly(previousHistory); + assertThat(element.getPrevious()) + .as("Previous is incorrect") + .isEqualTo(previousHistory[previousHistory.length - 1]); + assertThat(element.getPreviousHistory()) + .as("History is incorrect") + .containsExactly(previousHistory); } static void assertEmptyNextHistory(TestdataListValueWithShadowHistory element) { @@ -201,10 +205,6 @@ void addAndRemoveElement() { assertPreviousHistory(x, b); assertPreviousHistory(c, b, x); - System.out.println("Next of A: " + a.getNext() + " " + a.getNextHistory()); - System.out.println("Next of B: " + b.getNext() + " " + b.getNextHistory()); - System.out.println("Next of X: " + x.getNext() + " " + x.getNextHistory()); - System.out.println("Next of C: " + c.getNext() + " " + c.getNextHistory()); assertNextHistory(a, b); assertNextHistory(b, c, x); assertNextHistory(x, c); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableListenerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableListenerTest.java deleted file mode 100644 index 86c730760d..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/index/IndexVariableListenerTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable.index; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListEntity; -import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListSolution; -import ai.timefold.solver.core.impl.testdata.domain.list.TestdataListValue; - -import org.junit.jupiter.api.Test; - -class IndexVariableListenerTest { - - private final ScoreDirector scoreDirector = mock(InnerScoreDirector.class); - - private final IndexVariableListener indexVariableListener = new IndexVariableListener<>( - TestdataListValue.buildVariableDescriptorForIndex(), - TestdataListEntity.buildVariableDescriptorForValueList()); - - @Test - void index() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListValue v4 = new TestdataListValue("4"); - TestdataListEntity entity = new TestdataListEntity("a", v1, v2, v3); - - assertIndex(v1, null); - assertIndex(v2, null); - assertIndex(v3, null); - assertIndex(v4, null); - - indexVariableListener.beforeEntityAdded(scoreDirector, entity); - indexVariableListener.afterEntityAdded(scoreDirector, entity); - - assertIndex(v1, 0); - assertIndex(v2, 1); - assertIndex(v3, 2); - - // Assign v4. - indexVariableListener.beforeListVariableChanged(scoreDirector, entity, 2, 2); - entity.getValueList().add(2, v4); - indexVariableListener.afterListVariableChanged(scoreDirector, entity, 2, 3); - - assertIndex(v1, 0); - assertIndex(v2, 1); - assertIndex(v4, 2); - assertIndex(v3, 3); - - // Unassign v1. - indexVariableListener.beforeListVariableChanged(scoreDirector, entity, 0, 1); - entity.getValueList().remove(v1); - indexVariableListener.afterListVariableElementUnassigned(scoreDirector, v1); - indexVariableListener.afterListVariableChanged(scoreDirector, entity, 0, 0); - - assertIndex(v1, null); - assertIndex(v2, 0); - assertIndex(v4, 1); - assertIndex(v3, 2); - - // Move v4 from entity[1] to entity[2]. - indexVariableListener.beforeListVariableChanged(scoreDirector, entity, 1, 3); - entity.getValueList().remove(v4); - entity.getValueList().add(2, v4); - indexVariableListener.afterListVariableChanged(scoreDirector, entity, 1, 3); - - assertIndex(v2, 0); - assertIndex(v3, 1); - assertIndex(v4, 2); - } - - @Test - void removeEntity() { - TestdataListValue v1 = new TestdataListValue("1"); - TestdataListValue v2 = new TestdataListValue("2"); - TestdataListValue v3 = new TestdataListValue("3"); - TestdataListEntity entity = TestdataListEntity.createWithValues("a", v1, v2, v3); - - assertIndex(v1, 0); - assertIndex(v2, 1); - assertIndex(v3, 2); - - indexVariableListener.beforeEntityRemoved(scoreDirector, entity); - indexVariableListener.afterEntityRemoved(scoreDirector, entity); - - assertIndex(v1, null); - assertIndex(v2, null); - assertIndex(v3, null); - } - - void assertIndex(TestdataListValue element, Integer index) { - assertThat(element.getIndex()).isEqualTo(index); - assertThat(indexVariableListener.getIndex(element)).isEqualTo(index); - } -} From fb68f6e0e172b547a1025ca4eba5a6052a9dd0c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Thu, 14 Nov 2024 13:50:04 +0100 Subject: [PATCH 05/17] Clean up --- ...tractNextPrevElementVariableProcessor.java | 7 +- .../ExternalizedIndexVariableProcessor.java | 33 ++-- .../ExternalizedListVariableStateSupply.java | 143 ++++++++++-------- ...letonListListInverseVariableProcessor.java | 51 +++++-- .../variable/IndexVariableProcessor.java | 14 +- .../InternalIndexVariableProcessor.java | 31 ++-- ...letonListListInverseVariableProcessor.java | 51 ++----- .../variable/ListVariableStateSupply.java | 10 ++ .../NextElementVariableProcessor.java | 17 +-- .../PreviousElementVariableProcessor.java | 19 +-- ...SingletonListInverseVariableProcessor.java | 16 +- .../support/VariableListenerSupport.java | 3 + 12 files changed, 222 insertions(+), 173 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java index 70a6964546..e03235b452 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java @@ -3,17 +3,16 @@ import java.util.List; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; abstract sealed class AbstractNextPrevElementVariableProcessor permits NextElementVariableProcessor, PreviousElementVariableProcessor { - public abstract void addElement(InnerScoreDirector scoreDirector, List listVariable, - Object originalElement, LocationInList originalElementLocation); + public abstract void addElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + int index); public abstract void removeElement(InnerScoreDirector scoreDirector, Object element); public abstract void changeElement(InnerScoreDirector scoreDirector, List listVariable, - Object originalElement, LocationInList originalElementLocation); + Object element, int index); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java index bce954bf7b..a30c584660 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java @@ -1,40 +1,53 @@ package ai.timefold.solver.core.impl.domain.variable; -import java.util.function.Function; +import java.util.Objects; +import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -public final class ExternalizedIndexVariableProcessor +final class ExternalizedIndexVariableProcessor implements IndexVariableProcessor { - private final Function indexFunction; + private final IndexShadowVariableDescriptor shadowVariableDescriptor; - public ExternalizedIndexVariableProcessor(Function indexFunction) { - this.indexFunction = indexFunction; + public ExternalizedIndexVariableProcessor(IndexShadowVariableDescriptor shadowVariableDescriptor) { + this.shadowVariableDescriptor = shadowVariableDescriptor; } @Override public void addElement(InnerScoreDirector scoreDirector, Object element, Integer index) { - // Do nothing. + updateIndex(scoreDirector, element, index); } @Override public void removeElement(InnerScoreDirector scoreDirector, Object element) { - // Do nothing. + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, null); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); } @Override public void unassignElement(InnerScoreDirector scoreDirector, Object element) { - // Do nothing. + removeElement(scoreDirector, element); } @Override public void changeElement(InnerScoreDirector scoreDirector, Object element, Integer index) { - // Do nothing. + updateIndex(scoreDirector, element, index); + } + + private void updateIndex(InnerScoreDirector scoreDirector, Object element, Integer index) { + var oldIndex = shadowVariableDescriptor.getValue(element); + if (!Objects.equals(oldIndex, index)) { + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, index); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + } } @Override public Integer getIndex(Object planningValue) { - return indexFunction.apply(planningValue); + return shadowVariableDescriptor.getValue(planningValue); } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 15fc354037..761c74a515 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -20,21 +20,10 @@ final class ExternalizedListVariableStateSupply implements ListVariableStateSupply { private final ListVariableDescriptor sourceVariableDescriptor; - private IndexVariableProcessor indexProcessor = new ExternalizedIndexVariableProcessor<>(planningValue -> { - var elementLocation = getElementLocation(planningValue); - if (elementLocation == null) { - return null; - } - return elementLocation.index(); - }); + private IndexVariableProcessor indexProcessor = + new InternalIndexVariableProcessor<>(this::getIndexFromElementLocationMap); private SingletonListInverseVariableProcessor inverseProcessor = - new ExternalizedSingletonListListInverseVariableProcessor<>(planningValue -> { - var elementLocation = getElementLocation(planningValue); - if (elementLocation == null) { - return null; - } - return elementLocation.entity(); - }); + new InternalSingletonListListInverseVariableProcessor<>(this::getInverseFromElementLocationMap); private PreviousElementVariableProcessor previousElementProcessor; private NextElementVariableProcessor nextElementProcessor; private Map elementLocationMap; @@ -44,16 +33,36 @@ public ExternalizedListVariableStateSupply(ListVariableDescriptor sou this.sourceVariableDescriptor = sourceVariableDescriptor; } + private Integer getIndexFromElementLocationMap(Object planningValue) { + var elementLocation = getElementLocation(planningValue); + if (elementLocation == null) { + return null; + } + return elementLocation.index(); + } + + private LocationInList getElementLocation(Object planningValue) { + return elementLocationMap.get(Objects.requireNonNull(planningValue)); + } + + private Object getInverseFromElementLocationMap(Object planningValue) { + var elementLocation = getElementLocation(planningValue); + if (elementLocation == null) { + return null; + } + return elementLocation.entity(); + } + @Override public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { - this.indexProcessor = new InternalIndexVariableProcessor<>(shadowVariableDescriptor); + this.indexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); } @Override public void externalizeSingletonListInverseVariable( InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { this.inverseProcessor = - new InternalSingletonListListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + new ExternalizedSingletonListListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); } @Override @@ -63,15 +72,19 @@ public void externalizeSingletonListInverseVariable( new PreviousElementVariableProcessor<>(shadowVariableDescriptor); } + private boolean isPreviousElementShadowVariableEnabled() { + return previousElementProcessor != null; + } + + private boolean isNextElementShadowVariableEnabled() { + return nextElementProcessor != null; + } + @Override public void enableNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { this.nextElementProcessor = new NextElementVariableProcessor<>(shadowVariableDescriptor); } - private LocationInList getElementLocation(Object planningValue) { - return elementLocationMap.get(Objects.requireNonNull(planningValue)); - } - @Override public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector) { var workingSolution = scoreDirector.getWorkingSolution(); @@ -84,10 +97,12 @@ public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector unassignedCount = (int) sourceVariableDescriptor.getValueRangeSize(workingSolution, null); // Will run over all entities and unmark all present elements as unassigned. sourceVariableDescriptor.getEntityDescriptor() - .visitAllEntities(workingSolution, o -> insert(scoreDirector, o)); + .visitAllEntities(workingSolution, o -> insert(scoreDirector, o, isPreviousElementShadowVariableEnabled(), + isNextElementShadowVariableEnabled())); } - private void insert(ScoreDirector scoreDirector, Object entity) { + private void insert(ScoreDirector scoreDirector, Object entity, boolean previousElementProcessingEnabled, + boolean nextElementProcessingEnabled) { var assignedElements = sourceVariableDescriptor.getValue(entity); var index = 0; for (var element : assignedElements) { @@ -98,15 +113,14 @@ private void insert(ScoreDirector scoreDirector, Object entity) { "The supply (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)." .formatted(this, element, index, oldLocation)); } - indexProcessor.addElement((InnerScoreDirector) scoreDirector, element, index); - inverseProcessor.addElement((InnerScoreDirector) scoreDirector, entity, element); - if (nextElementProcessor != null) { - nextElementProcessor.addElement((InnerScoreDirector) scoreDirector, assignedElements, element, - location); + var castScoreDirector = (InnerScoreDirector) scoreDirector; + indexProcessor.addElement(castScoreDirector, element, index); + inverseProcessor.addElement(castScoreDirector, entity, element); + if (previousElementProcessingEnabled) { + previousElementProcessor.addElement(castScoreDirector, assignedElements, element, index); } - if (previousElementProcessor != null) { - previousElementProcessor.addElement((InnerScoreDirector) scoreDirector, assignedElements, element, - location); + if (nextElementProcessingEnabled) { + nextElementProcessor.addElement(castScoreDirector, assignedElements, element, index); } index++; unassignedCount--; @@ -125,7 +139,7 @@ public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @ @Override public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { - insert(scoreDirector, o); + insert(scoreDirector, o, previousElementProcessor != null, nextElementProcessor != null); } @Override @@ -137,10 +151,11 @@ public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { // When the entity is removed, its values become unassigned. // An unassigned value has no inverse entity and no index. - retract(scoreDirector, o); + retract(scoreDirector, o, isPreviousElementShadowVariableEnabled(), isNextElementShadowVariableEnabled()); } - private void retract(ScoreDirector scoreDirector, Object entity) { + private void retract(ScoreDirector scoreDirector, Object entity, boolean previousElementProcessingEnabled, + boolean nextElementProcessingEnabled) { var assignedElements = sourceVariableDescriptor.getValue(entity); for (var index = 0; index < assignedElements.size(); index++) { var element = assignedElements.get(index); @@ -156,13 +171,14 @@ private void retract(ScoreDirector scoreDirector, Object entity) { "The supply (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)." .formatted(this, element, index, oldIndex, index)); } - indexProcessor.removeElement((InnerScoreDirector) scoreDirector, element); - inverseProcessor.removeElement((InnerScoreDirector) scoreDirector, entity, element); - if (nextElementProcessor != null) { - nextElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); + var castScoreDirector = (InnerScoreDirector) scoreDirector; + indexProcessor.removeElement(castScoreDirector, element); + inverseProcessor.removeElement(castScoreDirector, entity, element); + if (previousElementProcessingEnabled) { + previousElementProcessor.removeElement(castScoreDirector, element); } - if (previousElementProcessor != null) { - previousElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); + if (nextElementProcessingEnabled) { + nextElementProcessor.removeElement(castScoreDirector, element); } unassignedCount++; } @@ -176,13 +192,14 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector "The supply (%s) is corrupted, because the element (%s) did not exist before unassigning." .formatted(this, element)); } - indexProcessor.unassignElement((InnerScoreDirector) scoreDirector, element); - inverseProcessor.unassignElement((InnerScoreDirector) scoreDirector, element); - if (nextElementProcessor != null) { - nextElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); + var castScoreDirector = (InnerScoreDirector) scoreDirector; + indexProcessor.unassignElement(castScoreDirector, element); + inverseProcessor.unassignElement(castScoreDirector, element); + if (isPreviousElementShadowVariableEnabled()) { + previousElementProcessor.removeElement(castScoreDirector, element); } - if (previousElementProcessor != null) { - previousElementProcessor.removeElement((InnerScoreDirector) scoreDirector, element); + if (isNextElementShadowVariableEnabled()) { + nextElementProcessor.removeElement(castScoreDirector, element); } unassignedCount++; } @@ -196,28 +213,29 @@ public void beforeListVariableChanged(@NonNull ScoreDirector scoreDir @Override public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object o, int fromIndex, int toIndex) { + var castScoreDirector = (InnerScoreDirector) scoreDirector; var assignedElements = sourceVariableDescriptor.getValue(o); - if (nextElementProcessor != null && fromIndex > 0) { + var previousElementProcessingEnabled = isPreviousElementShadowVariableEnabled(); + var nextElementProcessingEnabled = isNextElementShadowVariableEnabled(); + if (nextElementProcessingEnabled && fromIndex > 0) { // If we need to process next elements, include the last element of the previous part of the list too. // Otherwise the last element would point to the wrong next element. - nextElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, - assignedElements.get(fromIndex - 1), - ElementLocation.of(o, fromIndex - 1)); + var previousIndex = fromIndex - 1; + nextElementProcessor.changeElement(castScoreDirector, assignedElements, assignedElements.get(previousIndex), + previousIndex); } - for (var index = fromIndex; index < assignedElements.size(); index++) { + var elementCount = assignedElements.size(); + for (var index = fromIndex; index < elementCount; index++) { var element = assignedElements.get(index); var newLocation = ElementLocation.of(o, index); var oldLocation = elementLocationMap.put(element, newLocation); - indexProcessor.changeElement((InnerScoreDirector) scoreDirector, element, index); - inverseProcessor.changeElement((InnerScoreDirector) scoreDirector, o, element); - if (nextElementProcessor != null) { - nextElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, element, - newLocation); + indexProcessor.changeElement(castScoreDirector, element, index); + inverseProcessor.changeElement(castScoreDirector, o, element); + if (previousElementProcessingEnabled) { + previousElementProcessor.changeElement(castScoreDirector, assignedElements, element, index); } - if (previousElementProcessor != null) { - previousElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, - element, - newLocation); + if (nextElementProcessingEnabled) { + nextElementProcessor.changeElement(castScoreDirector, assignedElements, element, index); } if (oldLocation == null) { unassignedCount--; @@ -226,9 +244,10 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire // Location is unchanged and we are past the part of the list that changed. // If we need to process previous elements, include the last element of the previous part of the list too. // Otherwise the last element would point to the wrong next element. - if (previousElementProcessor != null && index < assignedElements.size() - 1) { - previousElementProcessor.changeElement((InnerScoreDirector) scoreDirector, assignedElements, - assignedElements.get(index + 1), ElementLocation.of(o, index + 1)); + if (previousElementProcessingEnabled && index < elementCount - 1) { + var nextIndex = index + 1; + previousElementProcessor.changeElement(castScoreDirector, assignedElements, assignedElements.get(nextIndex), + nextIndex); } // Finally, we can terminate the loop prematurely. return; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java index cc84cd4d1b..a532dcc0f9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java @@ -1,40 +1,69 @@ package ai.timefold.solver.core.impl.domain.variable; -import java.util.function.Function; - +import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -public final class ExternalizedSingletonListListInverseVariableProcessor +final class ExternalizedSingletonListListInverseVariableProcessor implements SingletonListInverseVariableProcessor { - private final Function inverseSingletonFunction; + private final InverseRelationShadowVariableDescriptor shadowVariableDescriptor; + private final ListVariableDescriptor sourceVariableDescriptor; - public ExternalizedSingletonListListInverseVariableProcessor(Function inverseSingletonFunction) { - this.inverseSingletonFunction = inverseSingletonFunction; + public ExternalizedSingletonListListInverseVariableProcessor( + InverseRelationShadowVariableDescriptor shadowVariableDescriptor, + ListVariableDescriptor sourceVariableDescriptor) { + this.shadowVariableDescriptor = shadowVariableDescriptor; + this.sourceVariableDescriptor = sourceVariableDescriptor; } @Override public void addElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - // Do nothing. + setInverseAsserted(scoreDirector, element, entity, null); + } + + private void setInverseAsserted(InnerScoreDirector scoreDirector, Object element, Object inverseEntity, + Object expectedOldInverseEntity) { + var oldInverseEntity = getInverseSingleton(element); + if (oldInverseEntity == inverseEntity) { + return; + } + if (scoreDirector.expectShadowVariablesInCorrectState() && oldInverseEntity != expectedOldInverseEntity) { + throw new IllegalStateException("The entity (" + inverseEntity + + ") has a list variable (" + sourceVariableDescriptor.getVariableName() + + ") and one of its elements (" + element + + ") which has a shadow variable (" + shadowVariableDescriptor.getVariableName() + + ") has an oldInverseEntity (" + oldInverseEntity + ") which is not that entity.\n" + + "Verify the consistency of your input problem for that shadow variable."); + } + setInverse(scoreDirector, inverseEntity, element); + } + + private void setInverse(InnerScoreDirector scoreDirector, Object entity, Object element) { + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, entity); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); } @Override public void removeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - // Do nothing. + setInverseAsserted(scoreDirector, element, null, entity); } @Override public void unassignElement(InnerScoreDirector scoreDirector, Object element) { - // Do nothing. + setInverse(scoreDirector, null, element); } @Override public void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - // Do nothing. + if (getInverseSingleton(element) != entity) { + setInverse(scoreDirector, entity, element); + } } @Override public Object getInverseSingleton(Object planningValue) { - return inverseSingletonFunction.apply(planningValue); + return shadowVariableDescriptor.getValue(planningValue); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java index 80c5bf376e..d5b1a253aa 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java @@ -3,8 +3,18 @@ import ai.timefold.solver.core.impl.domain.variable.index.IndexVariableSupply; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -public sealed interface IndexVariableProcessor extends IndexVariableSupply - permits InternalIndexVariableProcessor, ExternalizedIndexVariableProcessor { +/** + * Has two implementations: + * + *
    + *
  • {@link ExternalizedIndexVariableProcessor} uses the shadow variable declared in user's data model.
  • + *
  • {@link InternalIndexVariableProcessor} uses our internal tracker when the shadow variable isn't present.
  • + *
+ * + * @param + */ +sealed interface IndexVariableProcessor extends IndexVariableSupply + permits ExternalizedIndexVariableProcessor, InternalIndexVariableProcessor { void addElement(InnerScoreDirector scoreDirector, Object element, Integer index); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java index 9137746296..b732b7e3d9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java @@ -1,51 +1,40 @@ package ai.timefold.solver.core.impl.domain.variable; -import java.util.Objects; +import java.util.function.Function; -import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -public final class InternalIndexVariableProcessor +final class InternalIndexVariableProcessor implements IndexVariableProcessor { - private final IndexShadowVariableDescriptor shadowVariableDescriptor; + private final Function indexFunction; - public InternalIndexVariableProcessor(IndexShadowVariableDescriptor shadowVariableDescriptor) { - this.shadowVariableDescriptor = shadowVariableDescriptor; + public InternalIndexVariableProcessor(Function indexFunction) { + this.indexFunction = indexFunction; } @Override public void addElement(InnerScoreDirector scoreDirector, Object element, Integer index) { - updateIndex(scoreDirector, element, index); + // Do nothing. } @Override public void removeElement(InnerScoreDirector scoreDirector, Object element) { - updateIndex(scoreDirector, element, null); + // Do nothing. } @Override public void unassignElement(InnerScoreDirector scoreDirector, Object element) { - updateIndex(scoreDirector, element, null); + // Do nothing. } @Override public void changeElement(InnerScoreDirector scoreDirector, Object element, Integer index) { - updateIndex(scoreDirector, element, index); - } - - private void updateIndex(InnerScoreDirector scoreDirector, Object element, Integer index) { - var oldIndex = shadowVariableDescriptor.getValue(element); - if (!Objects.equals(oldIndex, index)) { - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, index); - scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } + // Do nothing. } @Override public Integer getIndex(Object planningValue) { - return shadowVariableDescriptor.getValue(planningValue); + return indexFunction.apply(planningValue); } - } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java index 1de7c3fe16..405adac5a9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java @@ -1,69 +1,40 @@ package ai.timefold.solver.core.impl.domain.variable; -import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; -import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; +import java.util.function.Function; + import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -public final class InternalSingletonListListInverseVariableProcessor +final class InternalSingletonListListInverseVariableProcessor implements SingletonListInverseVariableProcessor { - private final InverseRelationShadowVariableDescriptor shadowVariableDescriptor; - private final ListVariableDescriptor sourceVariableDescriptor; + private final Function inverseSingletonFunction; - public InternalSingletonListListInverseVariableProcessor( - InverseRelationShadowVariableDescriptor shadowVariableDescriptor, - ListVariableDescriptor sourceVariableDescriptor) { - this.shadowVariableDescriptor = shadowVariableDescriptor; - this.sourceVariableDescriptor = sourceVariableDescriptor; + public InternalSingletonListListInverseVariableProcessor(Function inverseSingletonFunction) { + this.inverseSingletonFunction = inverseSingletonFunction; } @Override public void addElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - setInverse(scoreDirector, element, entity, null); + // Do nothing. } @Override public void removeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - setInverse(scoreDirector, element, null, entity); + // Do nothing. } @Override public void unassignElement(InnerScoreDirector scoreDirector, Object element) { - updateInverse(scoreDirector, null, element); - } - - private void updateInverse(InnerScoreDirector scoreDirector, Object entity, Object element) { - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, entity); - scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + // Do nothing. } @Override public void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - if (getInverseSingleton(element) != entity) { - updateInverse(scoreDirector, entity, element); - } - } - - private void setInverse(InnerScoreDirector scoreDirector, - Object element, Object inverseEntity, Object expectedOldInverseEntity) { - var oldInverseEntity = getInverseSingleton(element); - if (oldInverseEntity == inverseEntity) { - return; - } - if (scoreDirector.expectShadowVariablesInCorrectState() && oldInverseEntity != expectedOldInverseEntity) { - throw new IllegalStateException("The entity (" + inverseEntity - + ") has a list variable (" + sourceVariableDescriptor.getVariableName() - + ") and one of its elements (" + element - + ") which has a shadow variable (" + shadowVariableDescriptor.getVariableName() - + ") has an oldInverseEntity (" + oldInverseEntity + ") which is not that entity.\n" - + "Verify the consistency of your input problem for that shadow variable."); - } - updateInverse(scoreDirector, inverseEntity, element); + // Do nothing. } @Override public Object getInverseSingleton(Object planningValue) { - return shadowVariableDescriptor.getValue(planningValue); + return inverseSingletonFunction.apply(planningValue); } } 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 4b66af93f7..50c21f6233 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 @@ -10,6 +10,15 @@ import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; +/** + * Single source of truth for all information about elements inside list variables. + * Shadow variables can be connected to this class to save on iteration costs + * that would've been incurred otherwise if using variable listeners for each of them independently. + * This way, there is only one variable listener for all such shadow variables, + * and therefore only a single iteration to update all the information. + * + * @param + */ public interface ListVariableStateSupply extends SourcedVariableListener, ListVariableListener, @@ -27,4 +36,5 @@ public interface ListVariableStateSupply extends @Override ListVariableDescriptor getSourceVariableDescriptor(); + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java index 1f3318d048..045bade309 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java @@ -4,7 +4,6 @@ import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; final class NextElementVariableProcessor extends AbstractNextPrevElementVariableProcessor { @@ -16,11 +15,10 @@ public NextElementVariableProcessor(NextElementShadowVariableDescriptor scoreDirector, List listVariable, - Object originalElement, LocationInList originalElementLocation) { - var index = originalElementLocation.index(); + public void addElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + int index) { var next = index == listVariable.size() - 1 ? null : listVariable.get(index + 1); - setValue(scoreDirector, originalElement, next); + setValue(scoreDirector, element, next); } void setValue(InnerScoreDirector scoreDirector, Object element, Object value) { @@ -37,13 +35,12 @@ public void removeElement(InnerScoreDirector scoreDirector, Object } @Override - public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object originalElement, - LocationInList originalElementLocation) { - var index = originalElementLocation.index(); + public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + int index) { if (index == listVariable.size() - 1) { - setValue(scoreDirector, originalElement, null); + setValue(scoreDirector, element, null); } else { - setValue(scoreDirector, originalElement, listVariable.get(index + 1)); + setValue(scoreDirector, element, listVariable.get(index + 1)); } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java index 66eef3bedf..6600224a9a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java @@ -4,7 +4,6 @@ import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; final class PreviousElementVariableProcessor extends AbstractNextPrevElementVariableProcessor { @@ -16,11 +15,10 @@ public PreviousElementVariableProcessor(PreviousElementShadowVariableDescriptor< } @Override - public void addElement(InnerScoreDirector scoreDirector, List listVariable, - Object originalElement, LocationInList originalElementLocation) { - var index = originalElementLocation.index(); - var previous = index == 0 ? null : listVariable.get(originalElementLocation.index() - 1); - setValue(scoreDirector, originalElement, previous); + public void addElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + int index) { + var previous = index == 0 ? null : listVariable.get(index - 1); + setValue(scoreDirector, element, previous); } void setValue(InnerScoreDirector scoreDirector, Object element, Object value) { @@ -37,13 +35,12 @@ public void removeElement(InnerScoreDirector scoreDirector, Object } @Override - public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object originalElement, - LocationInList originalElementLocation) { - var index = originalElementLocation.index(); + public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + int index) { if (index == 0) { - setValue(scoreDirector, originalElement, null); + setValue(scoreDirector, element, null); } else { - setValue(scoreDirector, originalElement, listVariable.get(index - 1)); + setValue(scoreDirector, element, listVariable.get(index - 1)); } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java index 96ff2db90d..655aec35c4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java @@ -3,8 +3,20 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.SingletonInverseVariableSupply; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -public sealed interface SingletonListInverseVariableProcessor extends SingletonInverseVariableSupply - permits InternalSingletonListListInverseVariableProcessor, ExternalizedSingletonListListInverseVariableProcessor { +/** + * Has two implementations: + * + *
    + *
  • {@link ExternalizedSingletonListListInverseVariableProcessor} uses the shadow variable declared in user's data + * model.
  • + *
  • {@link InternalSingletonListListInverseVariableProcessor} uses our internal tracker when the shadow variable isn't + * present.
  • + *
+ * + * @param + */ +sealed interface SingletonListInverseVariableProcessor extends SingletonInverseVariableSupply + permits ExternalizedSingletonListListInverseVariableProcessor, InternalSingletonListListInverseVariableProcessor { void addElement(InnerScoreDirector scoreDirector, Object entity, Object element); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java index 3584360820..bdc775a105 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java @@ -71,6 +71,9 @@ private void processShadowVariableDescriptor(ShadowVariableDescriptor if (listVariableDescriptor == null) { processShadowVariableDescriptorWithoutListVariable(shadowVariableDescriptor); } else { + // All information about elements in all shadow variables is tracked in a centralized place. + // Therefore all list-related shadow variables need to be connected to that centralized place. + // Shadow variables which are not related to a list variable are processed normally. processShadowVariableDescriptorWithListVariable(shadowVariableDescriptor); } } From a24499d0d52d1d23255ac1403580cca41d720eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Fri, 15 Nov 2024 08:14:44 +0100 Subject: [PATCH 06/17] Optionally disable the map at all --- .../ExternalizedListVariableStateSupply.java | 104 +++++++++++------- .../metamodel/DefaultLocationInList.java | 16 +++ 2 files changed, 82 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 761c74a515..2b062e0503 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -1,6 +1,5 @@ package ai.timefold.solver.core.impl.domain.variable; -import java.util.IdentityHashMap; import java.util.Map; import java.util.Objects; @@ -11,8 +10,10 @@ import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; +import ai.timefold.solver.core.preview.api.domain.metamodel.UnassignedLocation; import org.jspecify.annotations.NonNull; @@ -26,6 +27,7 @@ final class ExternalizedListVariableStateSupply new InternalSingletonListListInverseVariableProcessor<>(this::getInverseFromElementLocationMap); private PreviousElementVariableProcessor previousElementProcessor; private NextElementVariableProcessor nextElementProcessor; + private boolean requiresLocationTracking = true; private Map elementLocationMap; private int unassignedCount; @@ -56,6 +58,7 @@ private Object getInverseFromElementLocationMap(Object planningValue) { @Override public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { this.indexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); + this.requiresLocationTracking = inverseProcessor instanceof InternalSingletonListListInverseVariableProcessor; } @Override @@ -63,6 +66,7 @@ public void externalizeSingletonListInverseVariable( InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { this.inverseProcessor = new ExternalizedSingletonListListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + this.requiresLocationTracking = indexProcessor instanceof InternalIndexVariableProcessor; } @Override @@ -88,13 +92,17 @@ public void enableNextElementShadowVariable(NextElementShadowVariableDescriptor< @Override public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector) { var workingSolution = scoreDirector.getWorkingSolution(); - if (elementLocationMap == null) { - elementLocationMap = new IdentityHashMap<>((int) sourceVariableDescriptor.getValueRangeSize(workingSolution, null)); + // Start with everything unassigned. + this.unassignedCount = (int) sourceVariableDescriptor.getValueRangeSize(workingSolution, null); + if (requiresLocationTracking) { + if (elementLocationMap == null) { + elementLocationMap = CollectionUtils.newIdentityHashMap(unassignedCount); + } else { + elementLocationMap.clear(); + } } else { - elementLocationMap.clear(); + elementLocationMap = null; } - // Start with everything unassigned. - unassignedCount = (int) sourceVariableDescriptor.getValueRangeSize(workingSolution, null); // Will run over all entities and unmark all present elements as unassigned. sourceVariableDescriptor.getEntityDescriptor() .visitAllEntities(workingSolution, o -> insert(scoreDirector, o, isPreviousElementShadowVariableEnabled(), @@ -104,14 +112,17 @@ public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector private void insert(ScoreDirector scoreDirector, Object entity, boolean previousElementProcessingEnabled, boolean nextElementProcessingEnabled) { var assignedElements = sourceVariableDescriptor.getValue(entity); + var trackLocation = requiresLocationTracking; var index = 0; for (var element : assignedElements) { - var location = ElementLocation.of(entity, index); - var oldLocation = elementLocationMap.put(element, location); - if (oldLocation != null) { - throw new IllegalStateException( - "The supply (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)." - .formatted(this, element, index, oldLocation)); + if (trackLocation) { + var location = ElementLocation.of(entity, index); + var oldLocation = elementLocationMap.put(element, location); + if (oldLocation != null) { + throw new IllegalStateException( + "The supply (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)." + .formatted(this, element, index, oldLocation)); + } } var castScoreDirector = (InnerScoreDirector) scoreDirector; indexProcessor.addElement(castScoreDirector, element, index); @@ -139,7 +150,7 @@ public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @ @Override public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { - insert(scoreDirector, o, previousElementProcessor != null, nextElementProcessor != null); + insert(scoreDirector, o, isPreviousElementShadowVariableEnabled(), isNextElementShadowVariableEnabled()); } @Override @@ -157,19 +168,22 @@ public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, private void retract(ScoreDirector scoreDirector, Object entity, boolean previousElementProcessingEnabled, boolean nextElementProcessingEnabled) { var assignedElements = sourceVariableDescriptor.getValue(entity); + var trackLocation = requiresLocationTracking; for (var index = 0; index < assignedElements.size(); index++) { var element = assignedElements.get(index); - var oldElementLocation = elementLocationMap.remove(element); - if (oldElementLocation == null) { - throw new IllegalStateException( - "The supply (%s) is corrupted, because the element (%s) at index (%d) was already unassigned (%s)." - .formatted(this, element, index, oldElementLocation)); - } - var oldIndex = oldElementLocation.index(); - if (oldIndex != index) { - throw new IllegalStateException( - "The supply (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)." - .formatted(this, element, index, oldIndex, index)); + if (trackLocation) { + var oldElementLocation = elementLocationMap.remove(element); + if (oldElementLocation == null) { + throw new IllegalStateException( + "The supply (%s) is corrupted, because the element (%s) at index (%d) was already unassigned (%s)." + .formatted(this, element, index, oldElementLocation)); + } + var oldIndex = oldElementLocation.index(); + if (oldIndex != index) { + throw new IllegalStateException( + "The supply (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)." + .formatted(this, element, index, oldIndex, index)); + } } var castScoreDirector = (InnerScoreDirector) scoreDirector; indexProcessor.removeElement(castScoreDirector, element); @@ -186,11 +200,13 @@ private void retract(ScoreDirector scoreDirector, Object entity, bool @Override public void afterListVariableElementUnassigned(@NonNull ScoreDirector scoreDirector, @NonNull Object element) { - var oldLocation = elementLocationMap.remove(element); - if (oldLocation == null) { - throw new IllegalStateException( - "The supply (%s) is corrupted, because the element (%s) did not exist before unassigning." - .formatted(this, element)); + if (requiresLocationTracking) { + var oldLocation = elementLocationMap.remove(element); + if (oldLocation == null) { + throw new IllegalStateException( + "The supply (%s) is corrupted, because the element (%s) did not exist before unassigning." + .formatted(this, element)); + } } var castScoreDirector = (InnerScoreDirector) scoreDirector; indexProcessor.unassignElement(castScoreDirector, element); @@ -225,10 +241,10 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire previousIndex); } var elementCount = assignedElements.size(); + var trackLocation = requiresLocationTracking; for (var index = fromIndex; index < elementCount; index++) { var element = assignedElements.get(index); - var newLocation = ElementLocation.of(o, index); - var oldLocation = elementLocationMap.put(element, newLocation); + var locationsDiffer = changeLocation(o, element, index, trackLocation); indexProcessor.changeElement(castScoreDirector, element, index); inverseProcessor.changeElement(castScoreDirector, o, element); if (previousElementProcessingEnabled) { @@ -237,10 +253,7 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire if (nextElementProcessingEnabled) { nextElementProcessor.changeElement(castScoreDirector, assignedElements, element, index); } - if (oldLocation == null) { - unassignedCount--; - } - if (index >= toIndex && newLocation.equals(oldLocation)) { + if (index >= toIndex && !locationsDiffer) { // Location is unchanged and we are past the part of the list that changed. // If we need to process previous elements, include the last element of the previous part of the list too. // Otherwise the last element would point to the wrong next element. @@ -257,10 +270,25 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire } } + private boolean changeLocation(Object entity, Object element, int index, boolean trackLocation) { + var newLocation = ElementLocation.of(entity, index); + var oldLocation = trackLocation ? elementLocationMap.put(element, newLocation) + : getLocationInList(element); + if (oldLocation == null || oldLocation instanceof UnassignedLocation) { + unassignedCount--; + return true; + } else { + return !oldLocation.equals(newLocation); + } + } + @Override public ElementLocation getLocationInList(Object planningValue) { - return Objects.requireNonNullElse(elementLocationMap.get(Objects.requireNonNull(planningValue)), - ElementLocation.unassigned()); + var inverse = getInverseSingleton(planningValue); + if (inverse == null) { + return ElementLocation.unassigned(); + } + return ElementLocation.of(inverse, getIndex(planningValue)); } @Override @@ -275,7 +303,7 @@ public Object getInverseSingleton(Object planningValue) { @Override public boolean isAssigned(Object element) { - return getLocationInList(element) instanceof LocationInList; + return getInverseSingleton(element) != null; } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/DefaultLocationInList.java b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/DefaultLocationInList.java index e5ad52887c..4af729184f 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/DefaultLocationInList.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/domain/metamodel/DefaultLocationInList.java @@ -23,6 +23,22 @@ record DefaultLocationInList(@NonNull Object entity, int index) implements Locat return this; } + @Override + public boolean equals(Object element) { + if (!(element instanceof DefaultLocationInList that)) { + return false; + } + return index == that.index && entity == that.entity; + } + + @Override + public int hashCode() { + var result = 1; + result = 31 * result + (System.identityHashCode(entity)); + result = 31 * result + (Integer.hashCode(index)); + return result; + } + @Override public String toString() { return entity + "[" + index + "]"; From f6f997d1299e1c60b7e95811350604d48debe18a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Sun, 24 Nov 2024 09:09:29 +0100 Subject: [PATCH 07/17] Simplify and reuse --- ...tractNextPrevElementVariableProcessor.java | 23 ++++++++++++--- .../ExternalizedIndexVariableProcessor.java | 10 ++++--- .../ExternalizedListVariableStateSupply.java | 20 ++++++------- .../NextElementVariableProcessor.java | 28 ++----------------- .../PreviousElementVariableProcessor.java | 28 ++----------------- 5 files changed, 39 insertions(+), 70 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java index e03235b452..45126e02af 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java @@ -1,18 +1,33 @@ package ai.timefold.solver.core.impl.domain.variable; import java.util.List; +import java.util.Objects; +import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; abstract sealed class AbstractNextPrevElementVariableProcessor permits NextElementVariableProcessor, PreviousElementVariableProcessor { - public abstract void addElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + private final ShadowVariableDescriptor shadowVariableDescriptor; + + protected AbstractNextPrevElementVariableProcessor(ShadowVariableDescriptor shadowVariableDescriptor) { + this.shadowVariableDescriptor = Objects.requireNonNull(shadowVariableDescriptor); + } + + public abstract void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, int index); - public abstract void removeElement(InnerScoreDirector scoreDirector, Object element); + public void unsetElement(InnerScoreDirector scoreDirector, Object element) { + setValue(scoreDirector, element, null); + } - public abstract void changeElement(InnerScoreDirector scoreDirector, List listVariable, - Object element, int index); + protected void setValue(InnerScoreDirector scoreDirector, Object element, Object value) { + if (shadowVariableDescriptor.getValue(element) != value) { + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, value); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + } + } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java index a30c584660..32ecc958ef 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java @@ -21,8 +21,12 @@ public void addElement(InnerScoreDirector scoreDirector, Object el @Override public void removeElement(InnerScoreDirector scoreDirector, Object element) { + setIndex(scoreDirector, element, null); + } + + private void setIndex(InnerScoreDirector scoreDirector, Object element, Object value) { scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, null); + shadowVariableDescriptor.setValue(element, value); scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); } @@ -39,9 +43,7 @@ public void changeElement(InnerScoreDirector scoreDirector, Object private void updateIndex(InnerScoreDirector scoreDirector, Object element, Integer index) { var oldIndex = shadowVariableDescriptor.getValue(element); if (!Objects.equals(oldIndex, index)) { - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, index); - scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + setIndex(scoreDirector, element, index); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 2b062e0503..3ec368976c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -128,10 +128,10 @@ private void insert(ScoreDirector scoreDirector, Object entity, boole indexProcessor.addElement(castScoreDirector, element, index); inverseProcessor.addElement(castScoreDirector, entity, element); if (previousElementProcessingEnabled) { - previousElementProcessor.addElement(castScoreDirector, assignedElements, element, index); + previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); } if (nextElementProcessingEnabled) { - nextElementProcessor.addElement(castScoreDirector, assignedElements, element, index); + nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); } index++; unassignedCount--; @@ -189,10 +189,10 @@ private void retract(ScoreDirector scoreDirector, Object entity, bool indexProcessor.removeElement(castScoreDirector, element); inverseProcessor.removeElement(castScoreDirector, entity, element); if (previousElementProcessingEnabled) { - previousElementProcessor.removeElement(castScoreDirector, element); + previousElementProcessor.unsetElement(castScoreDirector, element); } if (nextElementProcessingEnabled) { - nextElementProcessor.removeElement(castScoreDirector, element); + nextElementProcessor.unsetElement(castScoreDirector, element); } unassignedCount++; } @@ -212,10 +212,10 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector indexProcessor.unassignElement(castScoreDirector, element); inverseProcessor.unassignElement(castScoreDirector, element); if (isPreviousElementShadowVariableEnabled()) { - previousElementProcessor.removeElement(castScoreDirector, element); + previousElementProcessor.unsetElement(castScoreDirector, element); } if (isNextElementShadowVariableEnabled()) { - nextElementProcessor.removeElement(castScoreDirector, element); + nextElementProcessor.unsetElement(castScoreDirector, element); } unassignedCount++; } @@ -237,7 +237,7 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire // If we need to process next elements, include the last element of the previous part of the list too. // Otherwise the last element would point to the wrong next element. var previousIndex = fromIndex - 1; - nextElementProcessor.changeElement(castScoreDirector, assignedElements, assignedElements.get(previousIndex), + nextElementProcessor.setElement(castScoreDirector, assignedElements, assignedElements.get(previousIndex), previousIndex); } var elementCount = assignedElements.size(); @@ -248,10 +248,10 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire indexProcessor.changeElement(castScoreDirector, element, index); inverseProcessor.changeElement(castScoreDirector, o, element); if (previousElementProcessingEnabled) { - previousElementProcessor.changeElement(castScoreDirector, assignedElements, element, index); + previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); } if (nextElementProcessingEnabled) { - nextElementProcessor.changeElement(castScoreDirector, assignedElements, element, index); + nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); } if (index >= toIndex && !locationsDiffer) { // Location is unchanged and we are past the part of the list that changed. @@ -259,7 +259,7 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire // Otherwise the last element would point to the wrong next element. if (previousElementProcessingEnabled && index < elementCount - 1) { var nextIndex = index + 1; - previousElementProcessor.changeElement(castScoreDirector, assignedElements, assignedElements.get(nextIndex), + previousElementProcessor.setElement(castScoreDirector, assignedElements, assignedElements.get(nextIndex), nextIndex); } // Finally, we can terminate the loop prematurely. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java index 045bade309..5422758985 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java @@ -8,39 +8,15 @@ final class NextElementVariableProcessor extends AbstractNextPrevElementVariableProcessor { - private final NextElementShadowVariableDescriptor shadowVariableDescriptor; - public NextElementVariableProcessor(NextElementShadowVariableDescriptor shadowVariableDescriptor) { - this.shadowVariableDescriptor = shadowVariableDescriptor; + super(shadowVariableDescriptor); } @Override - public void addElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, int index) { var next = index == listVariable.size() - 1 ? null : listVariable.get(index + 1); setValue(scoreDirector, element, next); } - void setValue(InnerScoreDirector scoreDirector, Object element, Object value) { - if (shadowVariableDescriptor.getValue(element) != value) { - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, value); - scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); - } - } - - @Override - public void removeElement(InnerScoreDirector scoreDirector, Object element) { - setValue(scoreDirector, element, null); - } - - @Override - public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - int index) { - if (index == listVariable.size() - 1) { - setValue(scoreDirector, element, null); - } else { - setValue(scoreDirector, element, listVariable.get(index + 1)); - } - } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java index 6600224a9a..d31a2c451d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/PreviousElementVariableProcessor.java @@ -8,39 +8,15 @@ final class PreviousElementVariableProcessor extends AbstractNextPrevElementVariableProcessor { - private final PreviousElementShadowVariableDescriptor shadowVariableDescriptor; - public PreviousElementVariableProcessor(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { - this.shadowVariableDescriptor = shadowVariableDescriptor; + super(shadowVariableDescriptor); } @Override - public void addElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, int index) { var previous = index == 0 ? null : listVariable.get(index - 1); setValue(scoreDirector, element, previous); } - void setValue(InnerScoreDirector scoreDirector, Object element, Object value) { - if (shadowVariableDescriptor.getValue(element) != value) { - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, value); - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - } - } - - @Override - public void removeElement(InnerScoreDirector scoreDirector, Object element) { - setValue(scoreDirector, element, null); - } - - @Override - public void changeElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - int index) { - if (index == 0) { - setValue(scoreDirector, element, null); - } else { - setValue(scoreDirector, element, listVariable.get(index - 1)); - } - } } From a018e9ddda7d3a4bdedf9efb35e81727efd0b214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Sun, 24 Nov 2024 09:37:52 +0100 Subject: [PATCH 08/17] Add previous and next too --- ...izedNextPrevElementVariableProcessor.java} | 15 +-- .../ExternalizedListVariableStateSupply.java | 124 ++++++++++-------- ...rnalizedNextElementVariableProcessor.java} | 10 +- ...izedPreviousElementVariableProcessor.java} | 11 +- .../InternalNextPrevVariableProcessor.java | 32 +++++ .../ListVariableElementStateSupply.java | 33 ----- .../variable/ListVariableStateSupply.java | 44 ++++++- .../NextPrevElementVariableProcessor.java | 16 +++ .../support/VariableListenerSupport.java | 4 +- 9 files changed, 183 insertions(+), 106 deletions(-) rename core/src/main/java/ai/timefold/solver/core/impl/domain/variable/{AbstractNextPrevElementVariableProcessor.java => AbstractExternalizedNextPrevElementVariableProcessor.java} (63%) rename core/src/main/java/ai/timefold/solver/core/impl/domain/variable/{NextElementVariableProcessor.java => ExternalizedNextElementVariableProcessor.java} (60%) rename core/src/main/java/ai/timefold/solver/core/impl/domain/variable/{PreviousElementVariableProcessor.java => ExternalizedPreviousElementVariableProcessor.java} (59%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableElementStateSupply.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java similarity index 63% rename from core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java rename to core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java index 45126e02af..f61f0c6c1a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractNextPrevElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java @@ -1,23 +1,22 @@ package ai.timefold.solver.core.impl.domain.variable; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -abstract sealed class AbstractNextPrevElementVariableProcessor - permits NextElementVariableProcessor, PreviousElementVariableProcessor { +abstract sealed class AbstractExternalizedNextPrevElementVariableProcessor + implements NextPrevElementVariableProcessor + permits ExternalizedNextElementVariableProcessor, ExternalizedPreviousElementVariableProcessor { - private final ShadowVariableDescriptor shadowVariableDescriptor; + protected final ShadowVariableDescriptor shadowVariableDescriptor; - protected AbstractNextPrevElementVariableProcessor(ShadowVariableDescriptor shadowVariableDescriptor) { + protected AbstractExternalizedNextPrevElementVariableProcessor( + ShadowVariableDescriptor shadowVariableDescriptor) { this.shadowVariableDescriptor = Objects.requireNonNull(shadowVariableDescriptor); } - public abstract void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - int index); - + @Override public void unsetElement(InnerScoreDirector scoreDirector, Object element) { setValue(scoreDirector, element, null); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 3ec368976c..49fc8ce3c1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -25,8 +25,10 @@ final class ExternalizedListVariableStateSupply new InternalIndexVariableProcessor<>(this::getIndexFromElementLocationMap); private SingletonListInverseVariableProcessor inverseProcessor = new InternalSingletonListListInverseVariableProcessor<>(this::getInverseFromElementLocationMap); - private PreviousElementVariableProcessor previousElementProcessor; - private NextElementVariableProcessor nextElementProcessor; + private NextPrevElementVariableProcessor previousElementProcessor = + new InternalNextPrevVariableProcessor<>(this::getPreviousElementFromElementLocationMap); + private NextPrevElementVariableProcessor nextElementProcessor = + new InternalNextPrevVariableProcessor<>(this::getNextElementFromElementLocationMap); private boolean requiresLocationTracking = true; private Map elementLocationMap; private int unassignedCount; @@ -55,10 +57,37 @@ private Object getInverseFromElementLocationMap(Object planningValue) { return elementLocation.entity(); } + private Object getPreviousElementFromElementLocationMap(Object planningValue) { + var elementLocation = getElementLocation(planningValue); + if (elementLocation == null) { + return null; + } + var index = elementLocation.index(); + if (index == 0) { + return null; + } + return sourceVariableDescriptor.getValue(elementLocation.entity()).get(index - 1); + } + + private Object getNextElementFromElementLocationMap(Object planningValue) { + var elementLocation = getElementLocation(planningValue); + if (elementLocation == null) { + return null; + } + var list = sourceVariableDescriptor.getValue(elementLocation.entity()); + var index = elementLocation.index(); + if (index == list.size() - 1) { + return null; + } + return list.get(index + 1); + } + @Override public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { this.indexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); - this.requiresLocationTracking = inverseProcessor instanceof InternalSingletonListListInverseVariableProcessor; + this.requiresLocationTracking = inverseProcessor instanceof InternalSingletonListListInverseVariableProcessor + || previousElementProcessor instanceof InternalNextPrevVariableProcessor + || nextElementProcessor instanceof InternalNextPrevVariableProcessor; } @Override @@ -66,27 +95,27 @@ public void externalizeSingletonListInverseVariable( InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { this.inverseProcessor = new ExternalizedSingletonListListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); - this.requiresLocationTracking = indexProcessor instanceof InternalIndexVariableProcessor; + this.requiresLocationTracking = indexProcessor instanceof InternalIndexVariableProcessor + || previousElementProcessor instanceof InternalNextPrevVariableProcessor + || nextElementProcessor instanceof InternalNextPrevVariableProcessor; } @Override - public void - enablePreviousElementShadowVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { + public void externalizePreviousElementShadowVariable( + PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { this.previousElementProcessor = - new PreviousElementVariableProcessor<>(shadowVariableDescriptor); - } - - private boolean isPreviousElementShadowVariableEnabled() { - return previousElementProcessor != null; - } - - private boolean isNextElementShadowVariableEnabled() { - return nextElementProcessor != null; + new ExternalizedPreviousElementVariableProcessor<>(shadowVariableDescriptor); + this.requiresLocationTracking = indexProcessor instanceof InternalIndexVariableProcessor + || inverseProcessor instanceof InternalSingletonListListInverseVariableProcessor + || nextElementProcessor instanceof InternalNextPrevVariableProcessor; } @Override - public void enableNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { - this.nextElementProcessor = new NextElementVariableProcessor<>(shadowVariableDescriptor); + public void externalizeNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { + this.nextElementProcessor = new ExternalizedNextElementVariableProcessor<>(shadowVariableDescriptor); + this.requiresLocationTracking = indexProcessor instanceof InternalIndexVariableProcessor + || inverseProcessor instanceof InternalSingletonListListInverseVariableProcessor + || previousElementProcessor instanceof InternalNextPrevVariableProcessor; } @Override @@ -105,12 +134,10 @@ public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector } // Will run over all entities and unmark all present elements as unassigned. sourceVariableDescriptor.getEntityDescriptor() - .visitAllEntities(workingSolution, o -> insert(scoreDirector, o, isPreviousElementShadowVariableEnabled(), - isNextElementShadowVariableEnabled())); + .visitAllEntities(workingSolution, o -> insert(scoreDirector, o)); } - private void insert(ScoreDirector scoreDirector, Object entity, boolean previousElementProcessingEnabled, - boolean nextElementProcessingEnabled) { + private void insert(ScoreDirector scoreDirector, Object entity) { var assignedElements = sourceVariableDescriptor.getValue(entity); var trackLocation = requiresLocationTracking; var index = 0; @@ -127,12 +154,8 @@ private void insert(ScoreDirector scoreDirector, Object entity, boole var castScoreDirector = (InnerScoreDirector) scoreDirector; indexProcessor.addElement(castScoreDirector, element, index); inverseProcessor.addElement(castScoreDirector, entity, element); - if (previousElementProcessingEnabled) { - previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); - } - if (nextElementProcessingEnabled) { - nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); - } + previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); + nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); index++; unassignedCount--; } @@ -150,7 +173,7 @@ public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @ @Override public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { - insert(scoreDirector, o, isPreviousElementShadowVariableEnabled(), isNextElementShadowVariableEnabled()); + insert(scoreDirector, o); } @Override @@ -162,11 +185,10 @@ public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { // When the entity is removed, its values become unassigned. // An unassigned value has no inverse entity and no index. - retract(scoreDirector, o, isPreviousElementShadowVariableEnabled(), isNextElementShadowVariableEnabled()); + retract(scoreDirector, o); } - private void retract(ScoreDirector scoreDirector, Object entity, boolean previousElementProcessingEnabled, - boolean nextElementProcessingEnabled) { + private void retract(ScoreDirector scoreDirector, Object entity) { var assignedElements = sourceVariableDescriptor.getValue(entity); var trackLocation = requiresLocationTracking; for (var index = 0; index < assignedElements.size(); index++) { @@ -188,12 +210,8 @@ private void retract(ScoreDirector scoreDirector, Object entity, bool var castScoreDirector = (InnerScoreDirector) scoreDirector; indexProcessor.removeElement(castScoreDirector, element); inverseProcessor.removeElement(castScoreDirector, entity, element); - if (previousElementProcessingEnabled) { - previousElementProcessor.unsetElement(castScoreDirector, element); - } - if (nextElementProcessingEnabled) { - nextElementProcessor.unsetElement(castScoreDirector, element); - } + previousElementProcessor.unsetElement(castScoreDirector, element); + nextElementProcessor.unsetElement(castScoreDirector, element); unassignedCount++; } } @@ -211,12 +229,8 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector var castScoreDirector = (InnerScoreDirector) scoreDirector; indexProcessor.unassignElement(castScoreDirector, element); inverseProcessor.unassignElement(castScoreDirector, element); - if (isPreviousElementShadowVariableEnabled()) { - previousElementProcessor.unsetElement(castScoreDirector, element); - } - if (isNextElementShadowVariableEnabled()) { - nextElementProcessor.unsetElement(castScoreDirector, element); - } + previousElementProcessor.unsetElement(castScoreDirector, element); + nextElementProcessor.unsetElement(castScoreDirector, element); unassignedCount++; } @@ -231,9 +245,7 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire int toIndex) { var castScoreDirector = (InnerScoreDirector) scoreDirector; var assignedElements = sourceVariableDescriptor.getValue(o); - var previousElementProcessingEnabled = isPreviousElementShadowVariableEnabled(); - var nextElementProcessingEnabled = isNextElementShadowVariableEnabled(); - if (nextElementProcessingEnabled && fromIndex > 0) { + if (fromIndex > 0) { // If we need to process next elements, include the last element of the previous part of the list too. // Otherwise the last element would point to the wrong next element. var previousIndex = fromIndex - 1; @@ -247,17 +259,13 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire var locationsDiffer = changeLocation(o, element, index, trackLocation); indexProcessor.changeElement(castScoreDirector, element, index); inverseProcessor.changeElement(castScoreDirector, o, element); - if (previousElementProcessingEnabled) { - previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); - } - if (nextElementProcessingEnabled) { - nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); - } + previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); + nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); if (index >= toIndex && !locationsDiffer) { // Location is unchanged and we are past the part of the list that changed. // If we need to process previous elements, include the last element of the previous part of the list too. // Otherwise the last element would point to the wrong next element. - if (previousElementProcessingEnabled && index < elementCount - 1) { + if (index < elementCount - 1) { var nextIndex = index + 1; previousElementProcessor.setElement(castScoreDirector, assignedElements, assignedElements.get(nextIndex), nextIndex); @@ -311,6 +319,16 @@ public int getUnassignedCount() { return unassignedCount; } + @Override + public Object getPreviousElement(Object element) { + return previousElementProcessor.getElement(element); + } + + @Override + public Object getNextElement(Object element) { + return nextElementProcessor.getElement(element); + } + @Override public ListVariableDescriptor getSourceVariableDescriptor() { return sourceVariableDescriptor; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java similarity index 60% rename from core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java rename to core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java index 5422758985..7855980f7d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java @@ -5,10 +5,10 @@ import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -final class NextElementVariableProcessor - extends AbstractNextPrevElementVariableProcessor { +final class ExternalizedNextElementVariableProcessor + extends AbstractExternalizedNextPrevElementVariableProcessor { - public NextElementVariableProcessor(NextElementShadowVariableDescriptor shadowVariableDescriptor) { + public ExternalizedNextElementVariableProcessor(NextElementShadowVariableDescriptor shadowVariableDescriptor) { super(shadowVariableDescriptor); } @@ -19,4 +19,8 @@ public void setElement(InnerScoreDirector scoreDirector, List - extends AbstractNextPrevElementVariableProcessor { +final class ExternalizedPreviousElementVariableProcessor + extends AbstractExternalizedNextPrevElementVariableProcessor { - public PreviousElementVariableProcessor(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { + public ExternalizedPreviousElementVariableProcessor( + PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { super(shadowVariableDescriptor); } @@ -19,4 +20,8 @@ public void setElement(InnerScoreDirector scoreDirector, List + implements NextPrevElementVariableProcessor { + + private final Function elementFunction; + + public InternalNextPrevVariableProcessor(Function elementFunction) { + this.elementFunction = elementFunction; + } + + @Override + public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + int index) { + // Do nothing. + } + + @Override + public void unsetElement(InnerScoreDirector scoreDirector, Object element) { + // Do nothing. + } + + @Override + public Object getElement(Object element) { + return elementFunction.apply(element); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableElementStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableElementStateSupply.java deleted file mode 100644 index 1d27c5cd47..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableElementStateSupply.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable; - -import ai.timefold.solver.core.api.domain.variable.ListVariableListener; -import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; -import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; - -public interface ListVariableElementStateSupply extends - SourcedVariableListener, - ListVariableListener { - - /** - * - * @param element never null - * @return true if the element is contained in a list variable of any entity. - */ - boolean isAssigned(Object element); - - /** - * - * @param value never null - * @return never null - */ - ElementLocation getLocationInList(Object value); - - /** - * Consider colling this before {@link #isAssigned(Object)} to eliminate some map accesses. - * If unassigned count is 0, {@link #isAssigned(Object)} is guaranteed to return true. - * - * @return number of elements for which {@link #isAssigned(Object)} would return false. - */ - int getUnassignedCount(); - -} 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 50c21f6233..657ebc5819 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 @@ -9,6 +9,7 @@ import ai.timefold.solver.core.impl.domain.variable.listener.SourcedVariableListener; import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; +import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; /** * Single source of truth for all information about elements inside list variables. @@ -23,18 +24,53 @@ public interface ListVariableStateSupply extends SourcedVariableListener, ListVariableListener, SingletonInverseVariableSupply, - IndexVariableSupply, - ListVariableElementStateSupply { + IndexVariableSupply { void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor); void externalizeSingletonListInverseVariable(InverseRelationShadowVariableDescriptor shadowVariableDescriptor); - void enablePreviousElementShadowVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor); + void externalizePreviousElementShadowVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor); - void enableNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor); + void externalizeNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor); @Override ListVariableDescriptor getSourceVariableDescriptor(); + /** + * + * @param element never null + * @return true if the element is contained in a list variable of any entity. + */ + boolean isAssigned(Object element); + + /** + * + * @param value never null + * @return never null + */ + ElementLocation getLocationInList(Object value); + + /** + * Consider calling this before {@link #isAssigned(Object)} to eliminate some map accesses. + * If unassigned count is 0, {@link #isAssigned(Object)} is guaranteed to return true. + * + * @return number of elements for which {@link #isAssigned(Object)} would return false. + */ + int getUnassignedCount(); + + /** + * + * @param element never null + * @return null if the element is the first element in the list + */ + Object getPreviousElement(Object element); + + /** + * + * @param element never null + * @return null if the element is the last element in the list + */ + Object getNextElement(Object element); + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java new file mode 100644 index 0000000000..60b2458339 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java @@ -0,0 +1,16 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import java.util.List; + +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; + +sealed interface NextPrevElementVariableProcessor + permits AbstractExternalizedNextPrevElementVariableProcessor, InternalNextPrevVariableProcessor { + + void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, int index); + + void unsetElement(InnerScoreDirector scoreDirector, Object element); + + Object getElement(Object element); + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java index bdc775a105..9956ae5a55 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java @@ -87,10 +87,10 @@ private void processShadowVariableDescriptorWithListVariable(ShadowVariableDescr .externalizeSingletonListInverseVariable(inverseRelationShadowVariableDescriptor); } else if (shadowVariableDescriptor instanceof PreviousElementShadowVariableDescriptor previousElementShadowVariableDescriptor) { demand(listVariableDescriptor.getStateDemand()) - .enablePreviousElementShadowVariable(previousElementShadowVariableDescriptor); + .externalizePreviousElementShadowVariable(previousElementShadowVariableDescriptor); } else if (shadowVariableDescriptor instanceof NextElementShadowVariableDescriptor nextElementShadowVariableDescriptor) { demand(listVariableDescriptor.getStateDemand()) - .enableNextElementShadowVariable(nextElementShadowVariableDescriptor); + .externalizeNextElementShadowVariable(nextElementShadowVariableDescriptor); } else { // These are shadow variables not supported by the list variable supply; we use the standard mechanism. processShadowVariableDescriptorWithoutListVariable(shadowVariableDescriptor); } From 46ce0b6cf3f799a6d84a13bd3af88164551ff9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Sun, 24 Nov 2024 10:55:52 +0100 Subject: [PATCH 09/17] Simplify --- .../ExternalizedListVariableStateSupply.java | 84 +++++++++++-------- ...ingletonListInverseVariableProcessor.java} | 4 +- ...ingletonListInverseVariableProcessor.java} | 4 +- .../variable/ListVariableStateSupply.java | 7 ++ ...SingletonListInverseVariableProcessor.java | 6 +- 5 files changed, 61 insertions(+), 44 deletions(-) rename core/src/main/java/ai/timefold/solver/core/impl/domain/variable/{ExternalizedSingletonListListInverseVariableProcessor.java => ExternalizedSingletonListInverseVariableProcessor.java} (95%) rename core/src/main/java/ai/timefold/solver/core/impl/domain/variable/{InternalSingletonListListInverseVariableProcessor.java => InternalSingletonListInverseVariableProcessor.java} (85%) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 49fc8ce3c1..075498e3d6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -13,7 +13,6 @@ import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; -import ai.timefold.solver.core.preview.api.domain.metamodel.UnassignedLocation; import org.jspecify.annotations.NonNull; @@ -24,12 +23,13 @@ final class ExternalizedListVariableStateSupply private IndexVariableProcessor indexProcessor = new InternalIndexVariableProcessor<>(this::getIndexFromElementLocationMap); private SingletonListInverseVariableProcessor inverseProcessor = - new InternalSingletonListListInverseVariableProcessor<>(this::getInverseFromElementLocationMap); + new InternalSingletonListInverseVariableProcessor<>(this::getInverseFromElementLocationMap); private NextPrevElementVariableProcessor previousElementProcessor = new InternalNextPrevVariableProcessor<>(this::getPreviousElementFromElementLocationMap); private NextPrevElementVariableProcessor nextElementProcessor = new InternalNextPrevVariableProcessor<>(this::getNextElementFromElementLocationMap); - private boolean requiresLocationTracking = true; + private boolean readLocationFromMap = true; + private boolean requiresLocationMap = true; private Map elementLocationMap; private int unassignedCount; @@ -85,7 +85,8 @@ private Object getNextElementFromElementLocationMap(Object planningValue) { @Override public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { this.indexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); - this.requiresLocationTracking = inverseProcessor instanceof InternalSingletonListListInverseVariableProcessor + this.readLocationFromMap = inverseProcessor instanceof InternalSingletonListInverseVariableProcessor; + this.requiresLocationMap = readLocationFromMap || previousElementProcessor instanceof InternalNextPrevVariableProcessor || nextElementProcessor instanceof InternalNextPrevVariableProcessor; } @@ -94,8 +95,9 @@ public void externalizeIndexVariable(IndexShadowVariableDescriptor sh public void externalizeSingletonListInverseVariable( InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { this.inverseProcessor = - new ExternalizedSingletonListListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); - this.requiresLocationTracking = indexProcessor instanceof InternalIndexVariableProcessor + new ExternalizedSingletonListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + this.readLocationFromMap = indexProcessor instanceof InternalIndexVariableProcessor; + this.requiresLocationMap = readLocationFromMap || previousElementProcessor instanceof InternalNextPrevVariableProcessor || nextElementProcessor instanceof InternalNextPrevVariableProcessor; } @@ -105,17 +107,15 @@ public void externalizePreviousElementShadowVariable( PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { this.previousElementProcessor = new ExternalizedPreviousElementVariableProcessor<>(shadowVariableDescriptor); - this.requiresLocationTracking = indexProcessor instanceof InternalIndexVariableProcessor - || inverseProcessor instanceof InternalSingletonListListInverseVariableProcessor - || nextElementProcessor instanceof InternalNextPrevVariableProcessor; + this.requiresLocationMap = + readLocationFromMap || nextElementProcessor instanceof InternalNextPrevVariableProcessor; } @Override public void externalizeNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { this.nextElementProcessor = new ExternalizedNextElementVariableProcessor<>(shadowVariableDescriptor); - this.requiresLocationTracking = indexProcessor instanceof InternalIndexVariableProcessor - || inverseProcessor instanceof InternalSingletonListListInverseVariableProcessor - || previousElementProcessor instanceof InternalNextPrevVariableProcessor; + this.requiresLocationMap = + readLocationFromMap || previousElementProcessor instanceof InternalNextPrevVariableProcessor; } @Override @@ -123,7 +123,7 @@ public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector var workingSolution = scoreDirector.getWorkingSolution(); // Start with everything unassigned. this.unassignedCount = (int) sourceVariableDescriptor.getValueRangeSize(workingSolution, null); - if (requiresLocationTracking) { + if (requiresLocationMap) { if (elementLocationMap == null) { elementLocationMap = CollectionUtils.newIdentityHashMap(unassignedCount); } else { @@ -139,7 +139,7 @@ public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector private void insert(ScoreDirector scoreDirector, Object entity) { var assignedElements = sourceVariableDescriptor.getValue(entity); - var trackLocation = requiresLocationTracking; + var trackLocation = requiresLocationMap; var index = 0; for (var element : assignedElements) { if (trackLocation) { @@ -190,7 +190,7 @@ public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, private void retract(ScoreDirector scoreDirector, Object entity) { var assignedElements = sourceVariableDescriptor.getValue(entity); - var trackLocation = requiresLocationTracking; + var trackLocation = requiresLocationMap; for (var index = 0; index < assignedElements.size(); index++) { var element = assignedElements.get(index); if (trackLocation) { @@ -218,7 +218,7 @@ private void retract(ScoreDirector scoreDirector, Object entity) { @Override public void afterListVariableElementUnassigned(@NonNull ScoreDirector scoreDirector, @NonNull Object element) { - if (requiresLocationTracking) { + if (requiresLocationMap) { var oldLocation = elementLocationMap.remove(element); if (oldLocation == null) { throw new IllegalStateException( @@ -246,26 +246,44 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire var castScoreDirector = (InnerScoreDirector) scoreDirector; var assignedElements = sourceVariableDescriptor.getValue(o); if (fromIndex > 0) { - // If we need to process next elements, include the last element of the previous part of the list too. + // Include the last element of the previous part of the list too. // Otherwise the last element would point to the wrong next element. var previousIndex = fromIndex - 1; nextElementProcessor.setElement(castScoreDirector, assignedElements, assignedElements.get(previousIndex), previousIndex); } var elementCount = assignedElements.size(); - var trackLocation = requiresLocationTracking; + var trackLocation = requiresLocationMap; for (var index = fromIndex; index < elementCount; index++) { var element = assignedElements.get(index); - var locationsDiffer = changeLocation(o, element, index, trackLocation); + + boolean locationsDiffer; + if (trackLocation) { // Update the location and figure out if it is different from previous. + var newLocation = ElementLocation.of(o, index); + var oldLocation = elementLocationMap.put(element, newLocation); + if (oldLocation == null) { + unassignedCount--; + } + locationsDiffer = !newLocation.equals(oldLocation); + } else { // Read the location and figure out if it is different from previous. + var previousEntity = getInverseSingleton(element); + if (previousEntity == null) { + unassignedCount--; + } + locationsDiffer = previousEntity != o || getIndex(element) != index; + } + // Update location; no-op if the map is used. indexProcessor.changeElement(castScoreDirector, element, index); inverseProcessor.changeElement(castScoreDirector, o, element); + // Update previous and next elements; no-op if the map is used. previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); + if (index >= toIndex && !locationsDiffer) { // Location is unchanged and we are past the part of the list that changed. - // If we need to process previous elements, include the last element of the previous part of the list too. - // Otherwise the last element would point to the wrong next element. if (index < elementCount - 1) { + // Include the last element of the previous part of the list too. + // Otherwise the last element would point to the wrong next element. var nextIndex = index + 1; previousElementProcessor.setElement(castScoreDirector, assignedElements, assignedElements.get(nextIndex), nextIndex); @@ -278,25 +296,17 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire } } - private boolean changeLocation(Object entity, Object element, int index, boolean trackLocation) { - var newLocation = ElementLocation.of(entity, index); - var oldLocation = trackLocation ? elementLocationMap.put(element, newLocation) - : getLocationInList(element); - if (oldLocation == null || oldLocation instanceof UnassignedLocation) { - unassignedCount--; - return true; - } else { - return !oldLocation.equals(newLocation); - } - } - @Override public ElementLocation getLocationInList(Object planningValue) { - var inverse = getInverseSingleton(planningValue); - if (inverse == null) { - return ElementLocation.unassigned(); + if (readLocationFromMap) { + return Objects.requireNonNullElse(getElementLocation(planningValue), ElementLocation.unassigned()); + } else { + var inverse = getInverseSingleton(planningValue); + if (inverse == null) { + return ElementLocation.unassigned(); + } + return ElementLocation.of(inverse, getIndex(planningValue)); } - return ElementLocation.of(inverse, getIndex(planningValue)); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java similarity index 95% rename from core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java rename to core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java index a532dcc0f9..b24321eed8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java @@ -4,13 +4,13 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -final class ExternalizedSingletonListListInverseVariableProcessor +final class ExternalizedSingletonListInverseVariableProcessor implements SingletonListInverseVariableProcessor { private final InverseRelationShadowVariableDescriptor shadowVariableDescriptor; private final ListVariableDescriptor sourceVariableDescriptor; - public ExternalizedSingletonListListInverseVariableProcessor( + public ExternalizedSingletonListInverseVariableProcessor( InverseRelationShadowVariableDescriptor shadowVariableDescriptor, ListVariableDescriptor sourceVariableDescriptor) { this.shadowVariableDescriptor = shadowVariableDescriptor; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListInverseVariableProcessor.java similarity index 85% rename from core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java rename to core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListInverseVariableProcessor.java index 405adac5a9..385384b17a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListInverseVariableProcessor.java @@ -4,12 +4,12 @@ import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -final class InternalSingletonListListInverseVariableProcessor +final class InternalSingletonListInverseVariableProcessor implements SingletonListInverseVariableProcessor { private final Function inverseSingletonFunction; - public InternalSingletonListListInverseVariableProcessor(Function inverseSingletonFunction) { + public InternalSingletonListInverseVariableProcessor(Function inverseSingletonFunction) { this.inverseSingletonFunction = inverseSingletonFunction; } 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 657ebc5819..f12b566ad5 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 @@ -17,6 +17,13 @@ * that would've been incurred otherwise if using variable listeners for each of them independently. * This way, there is only one variable listener for all such shadow variables, * and therefore only a single iteration to update all the information. + * + *

+ * If a particular shadow variable is externalized, + * it means that there is a field on an entity holding the value of the shadow variable. + * In this case, we will attempt to use that value. + * Otherwise, we will keep an internal track of all the possible shadow variables (index, inverse, previous, next, ...) + * and use their values from this internal representation. * * @param */ diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java index 655aec35c4..ebe1c67516 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java @@ -7,16 +7,16 @@ * Has two implementations: * *

    - *
  • {@link ExternalizedSingletonListListInverseVariableProcessor} uses the shadow variable declared in user's data + *
  • {@link ExternalizedSingletonListInverseVariableProcessor} uses the shadow variable declared in user's data * model.
  • - *
  • {@link InternalSingletonListListInverseVariableProcessor} uses our internal tracker when the shadow variable isn't + *
  • {@link InternalSingletonListInverseVariableProcessor} uses our internal tracker when the shadow variable isn't * present.
  • *
* * @param */ sealed interface SingletonListInverseVariableProcessor extends SingletonInverseVariableSupply - permits ExternalizedSingletonListListInverseVariableProcessor, InternalSingletonListListInverseVariableProcessor { + permits ExternalizedSingletonListInverseVariableProcessor, InternalSingletonListInverseVariableProcessor { void addElement(InnerScoreDirector scoreDirector, Object entity, Object element); From c59005413da597bd0f1558c640c5bb60bd5a7b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Sun, 24 Nov 2024 16:48:38 +0100 Subject: [PATCH 10/17] Further simplification --- .../ExternalizedListVariableStateSupply.java | 109 ++++++++---------- ...ernalizedNextElementVariableProcessor.java | 2 +- ...lizedPreviousElementVariableProcessor.java | 2 +- .../InternalNextPrevVariableProcessor.java | 2 +- .../NextPrevElementVariableProcessor.java | 2 +- 5 files changed, 53 insertions(+), 64 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 075498e3d6..9388d48fb6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -138,11 +138,11 @@ public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector } private void insert(ScoreDirector scoreDirector, Object entity) { + var castScoreDirector = (InnerScoreDirector) scoreDirector; var assignedElements = sourceVariableDescriptor.getValue(entity); - var trackLocation = requiresLocationMap; var index = 0; for (var element : assignedElements) { - if (trackLocation) { + if (requiresLocationMap) { var location = ElementLocation.of(entity, index); var oldLocation = elementLocationMap.put(element, location); if (oldLocation != null) { @@ -151,11 +151,11 @@ private void insert(ScoreDirector scoreDirector, Object entity) { .formatted(this, element, index, oldLocation)); } } - var castScoreDirector = (InnerScoreDirector) scoreDirector; - indexProcessor.addElement(castScoreDirector, element, index); + var castIndex = Integer.valueOf(index); // Avoid multiple auto-boxing. + indexProcessor.addElement(castScoreDirector, element, castIndex); inverseProcessor.addElement(castScoreDirector, entity, element); - previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); - nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); + previousElementProcessor.setElement(castScoreDirector, assignedElements, element, castIndex); + nextElementProcessor.setElement(castScoreDirector, assignedElements, element, castIndex); index++; unassignedCount--; } @@ -167,33 +167,33 @@ public void close() { } @Override - public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { + public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { // No need to do anything. } @Override - public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { - insert(scoreDirector, o); + public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { + insert(scoreDirector, entity); } @Override - public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { + public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { // No need to do anything. } @Override - public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object o) { + public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { // When the entity is removed, its values become unassigned. // An unassigned value has no inverse entity and no index. - retract(scoreDirector, o); + retract(scoreDirector, entity); } private void retract(ScoreDirector scoreDirector, Object entity) { + var castScoreDirector = (InnerScoreDirector) scoreDirector; var assignedElements = sourceVariableDescriptor.getValue(entity); - var trackLocation = requiresLocationMap; for (var index = 0; index < assignedElements.size(); index++) { var element = assignedElements.get(index); - if (trackLocation) { + if (requiresLocationMap) { var oldElementLocation = elementLocationMap.remove(element); if (oldElementLocation == null) { throw new IllegalStateException( @@ -207,7 +207,6 @@ private void retract(ScoreDirector scoreDirector, Object entity) { .formatted(this, element, index, oldIndex, index)); } } - var castScoreDirector = (InnerScoreDirector) scoreDirector; indexProcessor.removeElement(castScoreDirector, element); inverseProcessor.removeElement(castScoreDirector, entity, element); previousElementProcessor.unsetElement(castScoreDirector, element); @@ -235,67 +234,57 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector } @Override - public void beforeListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object o, int fromIndex, - int toIndex) { + public void beforeListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, + int fromIndex, int toIndex) { // No need to do anything. } @Override - public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object o, int fromIndex, + public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, int fromIndex, int toIndex) { var castScoreDirector = (InnerScoreDirector) scoreDirector; - var assignedElements = sourceVariableDescriptor.getValue(o); - if (fromIndex > 0) { - // Include the last element of the previous part of the list too. - // Otherwise the last element would point to the wrong next element. - var previousIndex = fromIndex - 1; - nextElementProcessor.setElement(castScoreDirector, assignedElements, assignedElements.get(previousIndex), - previousIndex); - } + var assignedElements = sourceVariableDescriptor.getValue(entity); var elementCount = assignedElements.size(); - var trackLocation = requiresLocationMap; - for (var index = fromIndex; index < elementCount; index++) { - var element = assignedElements.get(index); - - boolean locationsDiffer; - if (trackLocation) { // Update the location and figure out if it is different from previous. - var newLocation = ElementLocation.of(o, index); - var oldLocation = elementLocationMap.put(element, newLocation); - if (oldLocation == null) { - unassignedCount--; - } - locationsDiffer = !newLocation.equals(oldLocation); - } else { // Read the location and figure out if it is different from previous. - var previousEntity = getInverseSingleton(element); - if (previousEntity == null) { - unassignedCount--; - } - locationsDiffer = previousEntity != o || getIndex(element) != index; - } + // Include the last element of the previous part of the list, if any, for the next element shadow var. + var firstChangeIndex = Math.max(0, fromIndex - 1); + // Include the first element of the next part of the list, if any, for the previous element shadow var. + var lastChangeIndex = Math.min(toIndex + 1, elementCount); + for (var index = firstChangeIndex; index < elementCount; index++) { + var boxedIndex = Integer.valueOf(index); // Avoid many counts of auto-boxing. + var element = assignedElements.get(boxedIndex); + var locationsDiffer = processElementLocation(entity, element, boxedIndex); // Update location; no-op if the map is used. - indexProcessor.changeElement(castScoreDirector, element, index); - inverseProcessor.changeElement(castScoreDirector, o, element); + indexProcessor.changeElement(castScoreDirector, element, boxedIndex); + inverseProcessor.changeElement(castScoreDirector, entity, element); // Update previous and next elements; no-op if the map is used. - previousElementProcessor.setElement(castScoreDirector, assignedElements, element, index); - nextElementProcessor.setElement(castScoreDirector, assignedElements, element, index); + previousElementProcessor.setElement(castScoreDirector, assignedElements, element, boxedIndex); + nextElementProcessor.setElement(castScoreDirector, assignedElements, element, boxedIndex); - if (index >= toIndex && !locationsDiffer) { + if (!locationsDiffer && index > lastChangeIndex) { // Location is unchanged and we are past the part of the list that changed. - if (index < elementCount - 1) { - // Include the last element of the previous part of the list too. - // Otherwise the last element would point to the wrong next element. - var nextIndex = index + 1; - previousElementProcessor.setElement(castScoreDirector, assignedElements, assignedElements.get(nextIndex), - nextIndex); - } - // Finally, we can terminate the loop prematurely. + // We can terminate the loop prematurely. return; - } else { - // Continue to the next element. } } } + private boolean processElementLocation(Object entity, Object element, Integer index) { + if (requiresLocationMap) { // Update the location and figure out if it is different from previous. + var newLocation = ElementLocation.of(entity, index); + var oldLocation = elementLocationMap.put(element, newLocation); + if (oldLocation == null) { + unassignedCount--; + } + return !newLocation.equals(oldLocation); + } else { // Read the location and figure out if it is different from previous. + var previousEntity = getInverseSingleton(element); + if (previousEntity == null) { + unassignedCount--; + } + return previousEntity != entity || !Objects.equals(getIndex(element), index); + } + } + @Override public ElementLocation getLocationInList(Object planningValue) { if (readLocationFromMap) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java index 7855980f7d..d9e96c5706 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java @@ -14,7 +14,7 @@ public ExternalizedNextElementVariableProcessor(NextElementShadowVariableDescrip @Override public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - int index) { + Integer index) { var next = index == listVariable.size() - 1 ? null : listVariable.get(index + 1); setValue(scoreDirector, element, next); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedPreviousElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedPreviousElementVariableProcessor.java index 5c2f309ef9..31a894c252 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedPreviousElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedPreviousElementVariableProcessor.java @@ -15,7 +15,7 @@ public ExternalizedPreviousElementVariableProcessor( @Override public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - int index) { + Integer index) { var previous = index == 0 ? null : listVariable.get(index - 1); setValue(scoreDirector, element, previous); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java index dfa294eebd..ada9bb3264 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java @@ -16,7 +16,7 @@ public InternalNextPrevVariableProcessor(Function elementFunctio @Override public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - int index) { + Integer index) { // Do nothing. } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java index 60b2458339..c8b85398c4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java @@ -7,7 +7,7 @@ sealed interface NextPrevElementVariableProcessor permits AbstractExternalizedNextPrevElementVariableProcessor, InternalNextPrevVariableProcessor { - void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, int index); + void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, Integer index); void unsetElement(InnerScoreDirector scoreDirector, Object element); From 1f6a74d66cee8ef32a75f4cf43d58fc60d8c6715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Mon, 25 Nov 2024 09:11:38 +0100 Subject: [PATCH 11/17] Simplify more --- ...lizedNextPrevElementVariableProcessor.java | 8 +- .../ExternalizedIndexVariableProcessor.java | 8 +- .../ExternalizedListVariableStateSupply.java | 340 +++++++++++++----- ...SingletonListInverseVariableProcessor.java | 8 +- .../variable/IndexVariableProcessor.java | 27 -- .../InternalIndexVariableProcessor.java | 40 --- .../InternalNextPrevVariableProcessor.java | 32 -- ...SingletonListInverseVariableProcessor.java | 40 --- .../NextPrevElementVariableProcessor.java | 16 - ...SingletonListInverseVariableProcessor.java | 29 -- 10 files changed, 251 insertions(+), 297 deletions(-) delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListInverseVariableProcessor.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java index f61f0c6c1a..8a5ef8d495 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java @@ -1,12 +1,12 @@ package ai.timefold.solver.core.impl.domain.variable; +import java.util.List; import java.util.Objects; import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; abstract sealed class AbstractExternalizedNextPrevElementVariableProcessor - implements NextPrevElementVariableProcessor permits ExternalizedNextElementVariableProcessor, ExternalizedPreviousElementVariableProcessor { protected final ShadowVariableDescriptor shadowVariableDescriptor; @@ -16,7 +16,11 @@ protected AbstractExternalizedNextPrevElementVariableProcessor( this.shadowVariableDescriptor = Objects.requireNonNull(shadowVariableDescriptor); } - @Override + public abstract void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, + Integer index); + + public abstract Object getElement(Object element); + public void unsetElement(InnerScoreDirector scoreDirector, Object element) { setValue(scoreDirector, element, null); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java index 32ecc958ef..aa490c2a88 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java @@ -5,8 +5,7 @@ import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -final class ExternalizedIndexVariableProcessor - implements IndexVariableProcessor { +final class ExternalizedIndexVariableProcessor { private final IndexShadowVariableDescriptor shadowVariableDescriptor; @@ -14,12 +13,10 @@ public ExternalizedIndexVariableProcessor(IndexShadowVariableDescriptor scoreDirector, Object element, Integer index) { updateIndex(scoreDirector, element, index); } - @Override public void removeElement(InnerScoreDirector scoreDirector, Object element) { setIndex(scoreDirector, element, null); } @@ -30,12 +27,10 @@ private void setIndex(InnerScoreDirector scoreDirector, Object ele scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); } - @Override public void unassignElement(InnerScoreDirector scoreDirector, Object element) { removeElement(scoreDirector, element); } - @Override public void changeElement(InnerScoreDirector scoreDirector, Object element, Integer index) { updateIndex(scoreDirector, element, index); } @@ -47,7 +42,6 @@ private void updateIndex(InnerScoreDirector scoreDirector, Object } } - @Override public Integer getIndex(Object planningValue) { return shadowVariableDescriptor.getValue(planningValue); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 9388d48fb6..906fcbd380 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -1,5 +1,7 @@ package ai.timefold.solver.core.impl.domain.variable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -20,15 +22,16 @@ final class ExternalizedListVariableStateSupply implements ListVariableStateSupply { private final ListVariableDescriptor sourceVariableDescriptor; - private IndexVariableProcessor indexProcessor = - new InternalIndexVariableProcessor<>(this::getIndexFromElementLocationMap); - private SingletonListInverseVariableProcessor inverseProcessor = - new InternalSingletonListInverseVariableProcessor<>(this::getInverseFromElementLocationMap); - private NextPrevElementVariableProcessor previousElementProcessor = - new InternalNextPrevVariableProcessor<>(this::getPreviousElementFromElementLocationMap); - private NextPrevElementVariableProcessor nextElementProcessor = - new InternalNextPrevVariableProcessor<>(this::getNextElementFromElementLocationMap); - private boolean readLocationFromMap = true; + + private ExternalizedIndexVariableProcessor externalizedIndexProcessor = null; + private ExternalizedSingletonListInverseVariableProcessor externalizedInverseProcessor = null; + private AbstractExternalizedNextPrevElementVariableProcessor externalizedPreviousElementProcessor = null; + private AbstractExternalizedNextPrevElementVariableProcessor externalizedNextElementProcessor = null; + private ElementAdder elementAdder = createElementAdder(); + private ElementRemover elementRemover = createElementRemover(); + private ElementUnassigner elementUnassigner = createElementUnassigner(); + private ElementChanger elementChanger = createElementChanger(); + private boolean canUseExternalizedLocation = false; private boolean requiresLocationMap = true; private Map elementLocationMap; private int unassignedCount; @@ -37,85 +40,206 @@ public ExternalizedListVariableStateSupply(ListVariableDescriptor sou this.sourceVariableDescriptor = sourceVariableDescriptor; } - private Integer getIndexFromElementLocationMap(Object planningValue) { - var elementLocation = getElementLocation(planningValue); - if (elementLocation == null) { - return null; - } - return elementLocation.index(); + @Override + public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { + this.externalizedIndexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); + reinitializeAccessors(); } - private LocationInList getElementLocation(Object planningValue) { - return elementLocationMap.get(Objects.requireNonNull(planningValue)); + private void reinitializeAccessors() { + this.canUseExternalizedLocation = externalizedIndexProcessor != null && externalizedInverseProcessor != null; + this.requiresLocationMap = !canUseExternalizedLocation + || externalizedPreviousElementProcessor == null || externalizedNextElementProcessor == null; + this.elementAdder = createElementAdder(); + this.elementRemover = createElementRemover(); + this.elementUnassigner = createElementUnassigner(); + this.elementChanger = createElementChanger(); } - private Object getInverseFromElementLocationMap(Object planningValue) { - var elementLocation = getElementLocation(planningValue); - if (elementLocation == null) { - return null; - } - return elementLocation.entity(); + @Override + public void externalizeSingletonListInverseVariable( + InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { + this.externalizedInverseProcessor = + new ExternalizedSingletonListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + reinitializeAccessors(); + } + + @Override + public void externalizePreviousElementShadowVariable( + PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { + this.externalizedPreviousElementProcessor = + new ExternalizedPreviousElementVariableProcessor<>(shadowVariableDescriptor); + reinitializeAccessors(); + } + + @Override + public void externalizeNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { + this.externalizedNextElementProcessor = new ExternalizedNextElementVariableProcessor<>(shadowVariableDescriptor); + reinitializeAccessors(); } - private Object getPreviousElementFromElementLocationMap(Object planningValue) { - var elementLocation = getElementLocation(planningValue); - if (elementLocation == null) { - return null; + @FunctionalInterface + private interface ElementAdder { + + void apply(InnerScoreDirector scoreDirector, Object entity, List elements, Object element, + Integer index); + + } + + private ElementAdder createElementAdder() { + var list = new ArrayList>(4); + if (externalizedIndexProcessor != null) { + list.add((scoreDirector, entity, elements, element, index) -> externalizedIndexProcessor.addElement(scoreDirector, + element, index)); + } + if (externalizedInverseProcessor != null) { + list.add((scoreDirector, entity, elements, element, index) -> externalizedInverseProcessor.addElement(scoreDirector, + entity, element)); } - var index = elementLocation.index(); - if (index == 0) { - return null; + if (externalizedPreviousElementProcessor != null) { + list.add((scoreDirector, entity, elements, element, index) -> externalizedPreviousElementProcessor + .setElement(scoreDirector, elements, element, index)); } - return sourceVariableDescriptor.getValue(elementLocation.entity()).get(index - 1); + if (externalizedNextElementProcessor != null) { + list.add((scoreDirector, entity, elements, element, index) -> externalizedNextElementProcessor + .setElement(scoreDirector, elements, element, index)); + } + + return switch (list.size()) { + case 0 -> (scoreDirector, entity, elements, element, index) -> { + // Do nothing + }; + case 1 -> list.get(0); + default -> { + var array = list.toArray(ElementAdder[]::new); + yield (scoreDirector, entity, elements, element, index) -> { + for (var adder : array) { + adder.apply(scoreDirector, entity, elements, element, index); + } + }; + } + }; } - private Object getNextElementFromElementLocationMap(Object planningValue) { - var elementLocation = getElementLocation(planningValue); - if (elementLocation == null) { - return null; + @FunctionalInterface + private interface ElementRemover { + + void apply(InnerScoreDirector scoreDirector, Object entity, Object element); + + } + + private ElementRemover createElementRemover() { + var list = new ArrayList>(4); + if (externalizedIndexProcessor != null) { + list.add((scoreDirector, entity, element) -> externalizedIndexProcessor.removeElement(scoreDirector, element)); + } + if (externalizedInverseProcessor != null) { + list.add((scoreDirector, entity, element) -> externalizedInverseProcessor.removeElement(scoreDirector, entity, + element)); } - var list = sourceVariableDescriptor.getValue(elementLocation.entity()); - var index = elementLocation.index(); - if (index == list.size() - 1) { - return null; + if (externalizedPreviousElementProcessor != null) { + list.add((scoreDirector, entity, element) -> externalizedPreviousElementProcessor.unsetElement(scoreDirector, + element)); } - return list.get(index + 1); + if (externalizedNextElementProcessor != null) { + list.add((scoreDirector, entity, element) -> externalizedNextElementProcessor.unsetElement(scoreDirector, element)); + } + + return switch (list.size()) { + case 0 -> (scoreDirector, entity, element) -> { + // Do nothing + }; + case 1 -> list.get(0); + default -> { + var array = list.toArray(ElementRemover[]::new); + yield (scoreDirector, entity, element) -> { + for (var remover : array) { + remover.apply(scoreDirector, entity, element); + } + }; + } + }; } - @Override - public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { - this.indexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); - this.readLocationFromMap = inverseProcessor instanceof InternalSingletonListInverseVariableProcessor; - this.requiresLocationMap = readLocationFromMap - || previousElementProcessor instanceof InternalNextPrevVariableProcessor - || nextElementProcessor instanceof InternalNextPrevVariableProcessor; + @FunctionalInterface + private interface ElementUnassigner { + + void apply(InnerScoreDirector scoreDirector, Object element); + } - @Override - public void externalizeSingletonListInverseVariable( - InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { - this.inverseProcessor = - new ExternalizedSingletonListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); - this.readLocationFromMap = indexProcessor instanceof InternalIndexVariableProcessor; - this.requiresLocationMap = readLocationFromMap - || previousElementProcessor instanceof InternalNextPrevVariableProcessor - || nextElementProcessor instanceof InternalNextPrevVariableProcessor; + private ElementUnassigner createElementUnassigner() { + var list = new ArrayList>(4); + if (externalizedIndexProcessor != null) { + list.add((scoreDirector, element) -> externalizedIndexProcessor.unassignElement(scoreDirector, element)); + } + if (externalizedInverseProcessor != null) { + list.add((scoreDirector, element) -> externalizedInverseProcessor.unassignElement(scoreDirector, element)); + } + if (externalizedPreviousElementProcessor != null) { + list.add((scoreDirector, element) -> externalizedPreviousElementProcessor.unsetElement(scoreDirector, element)); + } + if (externalizedNextElementProcessor != null) { + list.add((scoreDirector, element) -> externalizedNextElementProcessor.unsetElement(scoreDirector, element)); + } + + return switch (list.size()) { + case 0 -> (scoreDirector, element) -> { + // Do nothing + }; + case 1 -> list.get(0); + default -> { + var array = list.toArray(ElementUnassigner[]::new); + yield (scoreDirector, element) -> { + for (var unassigner : array) { + unassigner.apply(scoreDirector, element); + } + }; + } + }; } - @Override - public void externalizePreviousElementShadowVariable( - PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { - this.previousElementProcessor = - new ExternalizedPreviousElementVariableProcessor<>(shadowVariableDescriptor); - this.requiresLocationMap = - readLocationFromMap || nextElementProcessor instanceof InternalNextPrevVariableProcessor; + @FunctionalInterface + private interface ElementChanger { + + void apply(InnerScoreDirector scoreDirector, Object entity, List elements, Object element, + Integer index); + } - @Override - public void externalizeNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { - this.nextElementProcessor = new ExternalizedNextElementVariableProcessor<>(shadowVariableDescriptor); - this.requiresLocationMap = - readLocationFromMap || previousElementProcessor instanceof InternalNextPrevVariableProcessor; + private ElementChanger createElementChanger() { + var list = new ArrayList>(4); + if (externalizedIndexProcessor != null) { + list.add((scoreDirector, entity, elements, element, index) -> externalizedIndexProcessor + .changeElement(scoreDirector, element, index)); + } + if (externalizedInverseProcessor != null) { + list.add((scoreDirector, entity, elements, element, index) -> externalizedInverseProcessor + .changeElement(scoreDirector, entity, element)); + } + if (externalizedPreviousElementProcessor != null) { + list.add((scoreDirector, entity, elements, element, index) -> externalizedPreviousElementProcessor + .setElement(scoreDirector, elements, element, index)); + } + if (externalizedNextElementProcessor != null) { + list.add((scoreDirector, entity, elements, element, index) -> externalizedNextElementProcessor + .setElement(scoreDirector, elements, element, index)); + } + + return switch (list.size()) { + case 0 -> (scoreDirector, entity, elements, element, index) -> { + // Do nothing + }; + case 1 -> list.get(0); + default -> { + var array = list.toArray(ElementChanger[]::new); + yield (scoreDirector, entity, elements, element, index) -> { + for (var changer : array) { + changer.apply(scoreDirector, entity, elements, element, index); + } + }; + } + }; } @Override @@ -151,11 +275,7 @@ private void insert(ScoreDirector scoreDirector, Object entity) { .formatted(this, element, index, oldLocation)); } } - var castIndex = Integer.valueOf(index); // Avoid multiple auto-boxing. - indexProcessor.addElement(castScoreDirector, element, castIndex); - inverseProcessor.addElement(castScoreDirector, entity, element); - previousElementProcessor.setElement(castScoreDirector, assignedElements, element, castIndex); - nextElementProcessor.setElement(castScoreDirector, assignedElements, element, castIndex); + elementAdder.apply(castScoreDirector, entity, assignedElements, element, index); index++; unassignedCount--; } @@ -207,10 +327,7 @@ private void retract(ScoreDirector scoreDirector, Object entity) { .formatted(this, element, index, oldIndex, index)); } } - indexProcessor.removeElement(castScoreDirector, element); - inverseProcessor.removeElement(castScoreDirector, entity, element); - previousElementProcessor.unsetElement(castScoreDirector, element); - nextElementProcessor.unsetElement(castScoreDirector, element); + elementRemover.apply(castScoreDirector, entity, element); unassignedCount++; } } @@ -225,11 +342,7 @@ public void afterListVariableElementUnassigned(@NonNull ScoreDirector .formatted(this, element)); } } - var castScoreDirector = (InnerScoreDirector) scoreDirector; - indexProcessor.unassignElement(castScoreDirector, element); - inverseProcessor.unassignElement(castScoreDirector, element); - previousElementProcessor.unsetElement(castScoreDirector, element); - nextElementProcessor.unsetElement(castScoreDirector, element); + elementUnassigner.apply((InnerScoreDirector) scoreDirector, element); unassignedCount++; } @@ -250,17 +363,11 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire // Include the first element of the next part of the list, if any, for the previous element shadow var. var lastChangeIndex = Math.min(toIndex + 1, elementCount); for (var index = firstChangeIndex; index < elementCount; index++) { + var element = assignedElements.get(index); var boxedIndex = Integer.valueOf(index); // Avoid many counts of auto-boxing. - var element = assignedElements.get(boxedIndex); var locationsDiffer = processElementLocation(entity, element, boxedIndex); - // Update location; no-op if the map is used. - indexProcessor.changeElement(castScoreDirector, element, boxedIndex); - inverseProcessor.changeElement(castScoreDirector, entity, element); - // Update previous and next elements; no-op if the map is used. - previousElementProcessor.setElement(castScoreDirector, assignedElements, element, boxedIndex); - nextElementProcessor.setElement(castScoreDirector, assignedElements, element, boxedIndex); - - if (!locationsDiffer && index > lastChangeIndex) { + elementChanger.apply(castScoreDirector, entity, assignedElements, element, boxedIndex); + if (!locationsDiffer && index >= lastChangeIndex) { // Location is unchanged and we are past the part of the list that changed. // We can terminate the loop prematurely. return; @@ -274,12 +381,14 @@ private boolean processElementLocation(Object entity, Object element, Integer in var oldLocation = elementLocationMap.put(element, newLocation); if (oldLocation == null) { unassignedCount--; + return true; } return !newLocation.equals(oldLocation); } else { // Read the location and figure out if it is different from previous. var previousEntity = getInverseSingleton(element); if (previousEntity == null) { unassignedCount--; + return true; } return previousEntity != entity || !Objects.equals(getIndex(element), index); } @@ -287,8 +396,8 @@ private boolean processElementLocation(Object entity, Object element, Integer in @Override public ElementLocation getLocationInList(Object planningValue) { - if (readLocationFromMap) { - return Objects.requireNonNullElse(getElementLocation(planningValue), ElementLocation.unassigned()); + if (!canUseExternalizedLocation) { + return Objects.requireNonNullElse(elementLocationMap.get(planningValue), ElementLocation.unassigned()); } else { var inverse = getInverseSingleton(planningValue); if (inverse == null) { @@ -300,12 +409,26 @@ public ElementLocation getLocationInList(Object planningValue) { @Override public Integer getIndex(Object planningValue) { - return indexProcessor.getIndex(planningValue); + if (externalizedIndexProcessor == null) { + var elementLocation = elementLocationMap.get(planningValue); + if (elementLocation == null) { + return null; + } + return elementLocation.index(); + } + return externalizedIndexProcessor.getIndex(planningValue); } @Override public Object getInverseSingleton(Object planningValue) { - return inverseProcessor.getInverseSingleton(planningValue); + if (externalizedInverseProcessor == null) { + var elementLocation = elementLocationMap.get(planningValue); + if (elementLocation == null) { + return null; + } + return elementLocation.entity(); + } + return externalizedInverseProcessor.getInverseSingleton(planningValue); } @Override @@ -320,12 +443,35 @@ public int getUnassignedCount() { @Override public Object getPreviousElement(Object element) { - return previousElementProcessor.getElement(element); + if (externalizedPreviousElementProcessor == null) { + var elementLocation = getLocationInList(element); + if (!(elementLocation instanceof LocationInList locationInList)) { + return null; + } + var index = locationInList.index(); + if (index == 0) { + return null; + } + return sourceVariableDescriptor.getValue(locationInList.entity()).get(index - 1); + } + return externalizedPreviousElementProcessor.getElement(element); } @Override public Object getNextElement(Object element) { - return nextElementProcessor.getElement(element); + if (externalizedNextElementProcessor == null) { + var elementLocation = getLocationInList(element); + if (!(elementLocation instanceof LocationInList locationInList)) { + return null; + } + var list = sourceVariableDescriptor.getValue(locationInList.entity()); + var index = locationInList.index(); + if (index == list.size() - 1) { + return null; + } + return list.get(index + 1); + } + return externalizedNextElementProcessor.getElement(element); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java index b24321eed8..235365ca92 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java @@ -4,8 +4,7 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -final class ExternalizedSingletonListInverseVariableProcessor - implements SingletonListInverseVariableProcessor { +final class ExternalizedSingletonListInverseVariableProcessor { private final InverseRelationShadowVariableDescriptor shadowVariableDescriptor; private final ListVariableDescriptor sourceVariableDescriptor; @@ -17,7 +16,6 @@ public ExternalizedSingletonListInverseVariableProcessor( this.sourceVariableDescriptor = sourceVariableDescriptor; } - @Override public void addElement(InnerScoreDirector scoreDirector, Object entity, Object element) { setInverseAsserted(scoreDirector, element, entity, null); } @@ -45,24 +43,20 @@ private void setInverse(InnerScoreDirector scoreDirector, Object e scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); } - @Override public void removeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { setInverseAsserted(scoreDirector, element, null, entity); } - @Override public void unassignElement(InnerScoreDirector scoreDirector, Object element) { setInverse(scoreDirector, null, element); } - @Override public void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { if (getInverseSingleton(element) != entity) { setInverse(scoreDirector, entity, element); } } - @Override public Object getInverseSingleton(Object planningValue) { return shadowVariableDescriptor.getValue(planningValue); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java deleted file mode 100644 index d5b1a253aa..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/IndexVariableProcessor.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable; - -import ai.timefold.solver.core.impl.domain.variable.index.IndexVariableSupply; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -/** - * Has two implementations: - * - *
    - *
  • {@link ExternalizedIndexVariableProcessor} uses the shadow variable declared in user's data model.
  • - *
  • {@link InternalIndexVariableProcessor} uses our internal tracker when the shadow variable isn't present.
  • - *
- * - * @param - */ -sealed interface IndexVariableProcessor extends IndexVariableSupply - permits ExternalizedIndexVariableProcessor, InternalIndexVariableProcessor { - - void addElement(InnerScoreDirector scoreDirector, Object element, Integer index); - - void removeElement(InnerScoreDirector scoreDirector, Object element); - - void unassignElement(InnerScoreDirector scoreDirector, Object element); - - void changeElement(InnerScoreDirector scoreDirector, Object element, Integer index); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java deleted file mode 100644 index b732b7e3d9..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalIndexVariableProcessor.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable; - -import java.util.function.Function; - -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -final class InternalIndexVariableProcessor - implements IndexVariableProcessor { - - private final Function indexFunction; - - public InternalIndexVariableProcessor(Function indexFunction) { - this.indexFunction = indexFunction; - } - - @Override - public void addElement(InnerScoreDirector scoreDirector, Object element, Integer index) { - // Do nothing. - } - - @Override - public void removeElement(InnerScoreDirector scoreDirector, Object element) { - // Do nothing. - } - - @Override - public void unassignElement(InnerScoreDirector scoreDirector, Object element) { - // Do nothing. - } - - @Override - public void changeElement(InnerScoreDirector scoreDirector, Object element, Integer index) { - // Do nothing. - } - - @Override - public Integer getIndex(Object planningValue) { - return indexFunction.apply(planningValue); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java deleted file mode 100644 index ada9bb3264..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalNextPrevVariableProcessor.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable; - -import java.util.List; -import java.util.function.Function; - -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -final class InternalNextPrevVariableProcessor - implements NextPrevElementVariableProcessor { - - private final Function elementFunction; - - public InternalNextPrevVariableProcessor(Function elementFunction) { - this.elementFunction = elementFunction; - } - - @Override - public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - Integer index) { - // Do nothing. - } - - @Override - public void unsetElement(InnerScoreDirector scoreDirector, Object element) { - // Do nothing. - } - - @Override - public Object getElement(Object element) { - return elementFunction.apply(element); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListInverseVariableProcessor.java deleted file mode 100644 index 385384b17a..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/InternalSingletonListInverseVariableProcessor.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable; - -import java.util.function.Function; - -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -final class InternalSingletonListInverseVariableProcessor - implements SingletonListInverseVariableProcessor { - - private final Function inverseSingletonFunction; - - public InternalSingletonListInverseVariableProcessor(Function inverseSingletonFunction) { - this.inverseSingletonFunction = inverseSingletonFunction; - } - - @Override - public void addElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - // Do nothing. - } - - @Override - public void removeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - // Do nothing. - } - - @Override - public void unassignElement(InnerScoreDirector scoreDirector, Object element) { - // Do nothing. - } - - @Override - public void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element) { - // Do nothing. - } - - @Override - public Object getInverseSingleton(Object planningValue) { - return inverseSingletonFunction.apply(planningValue); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java deleted file mode 100644 index c8b85398c4..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/NextPrevElementVariableProcessor.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable; - -import java.util.List; - -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -sealed interface NextPrevElementVariableProcessor - permits AbstractExternalizedNextPrevElementVariableProcessor, InternalNextPrevVariableProcessor { - - void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, Integer index); - - void unsetElement(InnerScoreDirector scoreDirector, Object element); - - Object getElement(Object element); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java deleted file mode 100644 index ebe1c67516..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/SingletonListInverseVariableProcessor.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.timefold.solver.core.impl.domain.variable; - -import ai.timefold.solver.core.impl.domain.variable.inverserelation.SingletonInverseVariableSupply; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; - -/** - * Has two implementations: - * - *
    - *
  • {@link ExternalizedSingletonListInverseVariableProcessor} uses the shadow variable declared in user's data - * model.
  • - *
  • {@link InternalSingletonListInverseVariableProcessor} uses our internal tracker when the shadow variable isn't - * present.
  • - *
- * - * @param - */ -sealed interface SingletonListInverseVariableProcessor extends SingletonInverseVariableSupply - permits ExternalizedSingletonListInverseVariableProcessor, InternalSingletonListInverseVariableProcessor { - - void addElement(InnerScoreDirector scoreDirector, Object entity, Object element); - - void removeElement(InnerScoreDirector scoreDirector, Object entity, Object element); - - void unassignElement(InnerScoreDirector scoreDirector, Object element); - - void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element); - -} From e73ab6a527ac4e4015f15cda58d3ecb7b02bb3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 26 Nov 2024 10:32:33 +0100 Subject: [PATCH 12/17] Separate the logic --- ...lizedNextPrevElementVariableProcessor.java | 2 +- .../ExternalizedListVariableStateSupply.java | 368 ++---------------- ...ernalizedNextElementVariableProcessor.java | 2 +- ...lizedPreviousElementVariableProcessor.java | 2 +- ...SingletonListInverseVariableProcessor.java | 12 +- .../domain/variable/ListVariableState.java | 262 +++++++++++++ .../variable/ListVariableStateSupply.java | 14 +- 7 files changed, 308 insertions(+), 354 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java index 8a5ef8d495..199dd9fdfc 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java @@ -17,7 +17,7 @@ protected AbstractExternalizedNextPrevElementVariableProcessor( } public abstract void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - Integer index); + int index); public abstract Object getElement(Object element); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 906fcbd380..449faa1407 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -1,10 +1,5 @@ package ai.timefold.solver.core.impl.domain.variable; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; @@ -12,9 +7,7 @@ import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -import ai.timefold.solver.core.impl.util.CollectionUtils; import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; -import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; import org.jspecify.annotations.NonNull; @@ -22,270 +15,54 @@ final class ExternalizedListVariableStateSupply implements ListVariableStateSupply { private final ListVariableDescriptor sourceVariableDescriptor; - - private ExternalizedIndexVariableProcessor externalizedIndexProcessor = null; - private ExternalizedSingletonListInverseVariableProcessor externalizedInverseProcessor = null; - private AbstractExternalizedNextPrevElementVariableProcessor externalizedPreviousElementProcessor = null; - private AbstractExternalizedNextPrevElementVariableProcessor externalizedNextElementProcessor = null; - private ElementAdder elementAdder = createElementAdder(); - private ElementRemover elementRemover = createElementRemover(); - private ElementUnassigner elementUnassigner = createElementUnassigner(); - private ElementChanger elementChanger = createElementChanger(); - private boolean canUseExternalizedLocation = false; - private boolean requiresLocationMap = true; - private Map elementLocationMap; - private int unassignedCount; + private final ListVariableState listVariableState; public ExternalizedListVariableStateSupply(ListVariableDescriptor sourceVariableDescriptor) { this.sourceVariableDescriptor = sourceVariableDescriptor; + this.listVariableState = new ListVariableState<>(sourceVariableDescriptor); } @Override public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { - this.externalizedIndexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); - reinitializeAccessors(); - } - - private void reinitializeAccessors() { - this.canUseExternalizedLocation = externalizedIndexProcessor != null && externalizedInverseProcessor != null; - this.requiresLocationMap = !canUseExternalizedLocation - || externalizedPreviousElementProcessor == null || externalizedNextElementProcessor == null; - this.elementAdder = createElementAdder(); - this.elementRemover = createElementRemover(); - this.elementUnassigner = createElementUnassigner(); - this.elementChanger = createElementChanger(); + listVariableState.linkIndexVariable(shadowVariableDescriptor); } @Override public void externalizeSingletonListInverseVariable( InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { - this.externalizedInverseProcessor = - new ExternalizedSingletonListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); - reinitializeAccessors(); + listVariableState.linkInverseVariable(shadowVariableDescriptor); } @Override public void externalizePreviousElementShadowVariable( PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { - this.externalizedPreviousElementProcessor = - new ExternalizedPreviousElementVariableProcessor<>(shadowVariableDescriptor); - reinitializeAccessors(); + listVariableState.linkPreviousElementVariable(shadowVariableDescriptor); } @Override public void externalizeNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { - this.externalizedNextElementProcessor = new ExternalizedNextElementVariableProcessor<>(shadowVariableDescriptor); - reinitializeAccessors(); - } - - @FunctionalInterface - private interface ElementAdder { - - void apply(InnerScoreDirector scoreDirector, Object entity, List elements, Object element, - Integer index); - - } - - private ElementAdder createElementAdder() { - var list = new ArrayList>(4); - if (externalizedIndexProcessor != null) { - list.add((scoreDirector, entity, elements, element, index) -> externalizedIndexProcessor.addElement(scoreDirector, - element, index)); - } - if (externalizedInverseProcessor != null) { - list.add((scoreDirector, entity, elements, element, index) -> externalizedInverseProcessor.addElement(scoreDirector, - entity, element)); - } - if (externalizedPreviousElementProcessor != null) { - list.add((scoreDirector, entity, elements, element, index) -> externalizedPreviousElementProcessor - .setElement(scoreDirector, elements, element, index)); - } - if (externalizedNextElementProcessor != null) { - list.add((scoreDirector, entity, elements, element, index) -> externalizedNextElementProcessor - .setElement(scoreDirector, elements, element, index)); - } - - return switch (list.size()) { - case 0 -> (scoreDirector, entity, elements, element, index) -> { - // Do nothing - }; - case 1 -> list.get(0); - default -> { - var array = list.toArray(ElementAdder[]::new); - yield (scoreDirector, entity, elements, element, index) -> { - for (var adder : array) { - adder.apply(scoreDirector, entity, elements, element, index); - } - }; - } - }; - } - - @FunctionalInterface - private interface ElementRemover { - - void apply(InnerScoreDirector scoreDirector, Object entity, Object element); - - } - - private ElementRemover createElementRemover() { - var list = new ArrayList>(4); - if (externalizedIndexProcessor != null) { - list.add((scoreDirector, entity, element) -> externalizedIndexProcessor.removeElement(scoreDirector, element)); - } - if (externalizedInverseProcessor != null) { - list.add((scoreDirector, entity, element) -> externalizedInverseProcessor.removeElement(scoreDirector, entity, - element)); - } - if (externalizedPreviousElementProcessor != null) { - list.add((scoreDirector, entity, element) -> externalizedPreviousElementProcessor.unsetElement(scoreDirector, - element)); - } - if (externalizedNextElementProcessor != null) { - list.add((scoreDirector, entity, element) -> externalizedNextElementProcessor.unsetElement(scoreDirector, element)); - } - - return switch (list.size()) { - case 0 -> (scoreDirector, entity, element) -> { - // Do nothing - }; - case 1 -> list.get(0); - default -> { - var array = list.toArray(ElementRemover[]::new); - yield (scoreDirector, entity, element) -> { - for (var remover : array) { - remover.apply(scoreDirector, entity, element); - } - }; - } - }; - } - - @FunctionalInterface - private interface ElementUnassigner { - - void apply(InnerScoreDirector scoreDirector, Object element); - - } - - private ElementUnassigner createElementUnassigner() { - var list = new ArrayList>(4); - if (externalizedIndexProcessor != null) { - list.add((scoreDirector, element) -> externalizedIndexProcessor.unassignElement(scoreDirector, element)); - } - if (externalizedInverseProcessor != null) { - list.add((scoreDirector, element) -> externalizedInverseProcessor.unassignElement(scoreDirector, element)); - } - if (externalizedPreviousElementProcessor != null) { - list.add((scoreDirector, element) -> externalizedPreviousElementProcessor.unsetElement(scoreDirector, element)); - } - if (externalizedNextElementProcessor != null) { - list.add((scoreDirector, element) -> externalizedNextElementProcessor.unsetElement(scoreDirector, element)); - } - - return switch (list.size()) { - case 0 -> (scoreDirector, element) -> { - // Do nothing - }; - case 1 -> list.get(0); - default -> { - var array = list.toArray(ElementUnassigner[]::new); - yield (scoreDirector, element) -> { - for (var unassigner : array) { - unassigner.apply(scoreDirector, element); - } - }; - } - }; - } - - @FunctionalInterface - private interface ElementChanger { - - void apply(InnerScoreDirector scoreDirector, Object entity, List elements, Object element, - Integer index); - - } - - private ElementChanger createElementChanger() { - var list = new ArrayList>(4); - if (externalizedIndexProcessor != null) { - list.add((scoreDirector, entity, elements, element, index) -> externalizedIndexProcessor - .changeElement(scoreDirector, element, index)); - } - if (externalizedInverseProcessor != null) { - list.add((scoreDirector, entity, elements, element, index) -> externalizedInverseProcessor - .changeElement(scoreDirector, entity, element)); - } - if (externalizedPreviousElementProcessor != null) { - list.add((scoreDirector, entity, elements, element, index) -> externalizedPreviousElementProcessor - .setElement(scoreDirector, elements, element, index)); - } - if (externalizedNextElementProcessor != null) { - list.add((scoreDirector, entity, elements, element, index) -> externalizedNextElementProcessor - .setElement(scoreDirector, elements, element, index)); - } - - return switch (list.size()) { - case 0 -> (scoreDirector, entity, elements, element, index) -> { - // Do nothing - }; - case 1 -> list.get(0); - default -> { - var array = list.toArray(ElementChanger[]::new); - yield (scoreDirector, entity, elements, element, index) -> { - for (var changer : array) { - changer.apply(scoreDirector, entity, elements, element, index); - } - }; - } - }; + listVariableState.linkNextElementVariable(shadowVariableDescriptor); } @Override public void resetWorkingSolution(@NonNull ScoreDirector scoreDirector) { + listVariableState.initialize((InnerScoreDirector) scoreDirector, + (int) sourceVariableDescriptor.getValueRangeSize(scoreDirector.getWorkingSolution(), null)); var workingSolution = scoreDirector.getWorkingSolution(); - // Start with everything unassigned. - this.unassignedCount = (int) sourceVariableDescriptor.getValueRangeSize(workingSolution, null); - if (requiresLocationMap) { - if (elementLocationMap == null) { - elementLocationMap = CollectionUtils.newIdentityHashMap(unassignedCount); - } else { - elementLocationMap.clear(); - } - } else { - elementLocationMap = null; - } // Will run over all entities and unmark all present elements as unassigned. sourceVariableDescriptor.getEntityDescriptor() - .visitAllEntities(workingSolution, o -> insert(scoreDirector, o)); + .visitAllEntities(workingSolution, this::insert); } - private void insert(ScoreDirector scoreDirector, Object entity) { - var castScoreDirector = (InnerScoreDirector) scoreDirector; + private void insert(Object entity) { var assignedElements = sourceVariableDescriptor.getValue(entity); var index = 0; for (var element : assignedElements) { - if (requiresLocationMap) { - var location = ElementLocation.of(entity, index); - var oldLocation = elementLocationMap.put(element, location); - if (oldLocation != null) { - throw new IllegalStateException( - "The supply (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)." - .formatted(this, element, index, oldLocation)); - } - } - elementAdder.apply(castScoreDirector, entity, assignedElements, element, index); + listVariableState.addElement(entity, assignedElements, element, index); index++; - unassignedCount--; } } - @Override - public void close() { - elementLocationMap = null; - } - @Override public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { // No need to do anything. @@ -293,7 +70,7 @@ public void beforeEntityAdded(@NonNull ScoreDirector scoreDirector, @ @Override public void afterEntityAdded(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { - insert(scoreDirector, entity); + insert(entity); } @Override @@ -305,45 +82,15 @@ public void beforeEntityRemoved(@NonNull ScoreDirector scoreDirector, public void afterEntityRemoved(@NonNull ScoreDirector scoreDirector, @NonNull Object entity) { // When the entity is removed, its values become unassigned. // An unassigned value has no inverse entity and no index. - retract(scoreDirector, entity); - } - - private void retract(ScoreDirector scoreDirector, Object entity) { - var castScoreDirector = (InnerScoreDirector) scoreDirector; var assignedElements = sourceVariableDescriptor.getValue(entity); for (var index = 0; index < assignedElements.size(); index++) { - var element = assignedElements.get(index); - if (requiresLocationMap) { - var oldElementLocation = elementLocationMap.remove(element); - if (oldElementLocation == null) { - throw new IllegalStateException( - "The supply (%s) is corrupted, because the element (%s) at index (%d) was already unassigned (%s)." - .formatted(this, element, index, oldElementLocation)); - } - var oldIndex = oldElementLocation.index(); - if (oldIndex != index) { - throw new IllegalStateException( - "The supply (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)." - .formatted(this, element, index, oldIndex, index)); - } - } - elementRemover.apply(castScoreDirector, entity, element); - unassignedCount++; + listVariableState.removeElement(entity, assignedElements.get(index), index); } } @Override public void afterListVariableElementUnassigned(@NonNull ScoreDirector scoreDirector, @NonNull Object element) { - if (requiresLocationMap) { - var oldLocation = elementLocationMap.remove(element); - if (oldLocation == null) { - throw new IllegalStateException( - "The supply (%s) is corrupted, because the element (%s) did not exist before unassigning." - .formatted(this, element)); - } - } - elementUnassigner.apply((InnerScoreDirector) scoreDirector, element); - unassignedCount++; + listVariableState.unassignElement(element); } @Override @@ -355,7 +102,6 @@ public void beforeListVariableChanged(@NonNull ScoreDirector scoreDir @Override public void afterListVariableChanged(@NonNull ScoreDirector scoreDirector, @NonNull Object entity, int fromIndex, int toIndex) { - var castScoreDirector = (InnerScoreDirector) scoreDirector; var assignedElements = sourceVariableDescriptor.getValue(entity); var elementCount = assignedElements.size(); // Include the last element of the previous part of the list, if any, for the next element shadow var. @@ -363,10 +109,7 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire // Include the first element of the next part of the list, if any, for the previous element shadow var. var lastChangeIndex = Math.min(toIndex + 1, elementCount); for (var index = firstChangeIndex; index < elementCount; index++) { - var element = assignedElements.get(index); - var boxedIndex = Integer.valueOf(index); // Avoid many counts of auto-boxing. - var locationsDiffer = processElementLocation(entity, element, boxedIndex); - elementChanger.apply(castScoreDirector, entity, assignedElements, element, boxedIndex); + var locationsDiffer = listVariableState.changeElement(entity, assignedElements, index); if (!locationsDiffer && index >= lastChangeIndex) { // Location is unchanged and we are past the part of the list that changed. // We can terminate the loop prematurely. @@ -375,60 +118,19 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire } } - private boolean processElementLocation(Object entity, Object element, Integer index) { - if (requiresLocationMap) { // Update the location and figure out if it is different from previous. - var newLocation = ElementLocation.of(entity, index); - var oldLocation = elementLocationMap.put(element, newLocation); - if (oldLocation == null) { - unassignedCount--; - return true; - } - return !newLocation.equals(oldLocation); - } else { // Read the location and figure out if it is different from previous. - var previousEntity = getInverseSingleton(element); - if (previousEntity == null) { - unassignedCount--; - return true; - } - return previousEntity != entity || !Objects.equals(getIndex(element), index); - } - } - @Override public ElementLocation getLocationInList(Object planningValue) { - if (!canUseExternalizedLocation) { - return Objects.requireNonNullElse(elementLocationMap.get(planningValue), ElementLocation.unassigned()); - } else { - var inverse = getInverseSingleton(planningValue); - if (inverse == null) { - return ElementLocation.unassigned(); - } - return ElementLocation.of(inverse, getIndex(planningValue)); - } + return listVariableState.getLocationInList(planningValue); } @Override public Integer getIndex(Object planningValue) { - if (externalizedIndexProcessor == null) { - var elementLocation = elementLocationMap.get(planningValue); - if (elementLocation == null) { - return null; - } - return elementLocation.index(); - } - return externalizedIndexProcessor.getIndex(planningValue); + return listVariableState.getIndex(planningValue); } @Override public Object getInverseSingleton(Object planningValue) { - if (externalizedInverseProcessor == null) { - var elementLocation = elementLocationMap.get(planningValue); - if (elementLocation == null) { - return null; - } - return elementLocation.entity(); - } - return externalizedInverseProcessor.getInverseSingleton(planningValue); + return listVariableState.getInverseSingleton(planningValue); } @Override @@ -438,40 +140,17 @@ public boolean isAssigned(Object element) { @Override public int getUnassignedCount() { - return unassignedCount; + return listVariableState.getUnassignedCount(); } @Override public Object getPreviousElement(Object element) { - if (externalizedPreviousElementProcessor == null) { - var elementLocation = getLocationInList(element); - if (!(elementLocation instanceof LocationInList locationInList)) { - return null; - } - var index = locationInList.index(); - if (index == 0) { - return null; - } - return sourceVariableDescriptor.getValue(locationInList.entity()).get(index - 1); - } - return externalizedPreviousElementProcessor.getElement(element); + return listVariableState.getPreviousElement(element); } @Override public Object getNextElement(Object element) { - if (externalizedNextElementProcessor == null) { - var elementLocation = getLocationInList(element); - if (!(elementLocation instanceof LocationInList locationInList)) { - return null; - } - var list = sourceVariableDescriptor.getValue(locationInList.entity()); - var index = locationInList.index(); - if (index == list.size() - 1) { - return null; - } - return list.get(index + 1); - } - return externalizedNextElementProcessor.getElement(element); + return listVariableState.getNextElement(element); } @Override @@ -479,6 +158,11 @@ public ListVariableDescriptor getSourceVariableDescriptor() { return sourceVariableDescriptor; } + @Override + public void close() { + listVariableState.close(); + } + @Override public String toString() { return getClass().getSimpleName() + "(" + sourceVariableDescriptor.getVariableName() + ")"; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java index d9e96c5706..7855980f7d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedNextElementVariableProcessor.java @@ -14,7 +14,7 @@ public ExternalizedNextElementVariableProcessor(NextElementShadowVariableDescrip @Override public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - Integer index) { + int index) { var next = index == listVariable.size() - 1 ? null : listVariable.get(index + 1); setValue(scoreDirector, element, next); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedPreviousElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedPreviousElementVariableProcessor.java index 31a894c252..5c2f309ef9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedPreviousElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedPreviousElementVariableProcessor.java @@ -15,7 +15,7 @@ public ExternalizedPreviousElementVariableProcessor( @Override public void setElement(InnerScoreDirector scoreDirector, List listVariable, Object element, - Integer index) { + int index) { var previous = index == 0 ? null : listVariable.get(index - 1); setValue(scoreDirector, element, previous); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java index 235365ca92..6f4ecba289 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java @@ -27,12 +27,12 @@ private void setInverseAsserted(InnerScoreDirector scoreDirector, return; } if (scoreDirector.expectShadowVariablesInCorrectState() && oldInverseEntity != expectedOldInverseEntity) { - throw new IllegalStateException("The entity (" + inverseEntity - + ") has a list variable (" + sourceVariableDescriptor.getVariableName() - + ") and one of its elements (" + element - + ") which has a shadow variable (" + shadowVariableDescriptor.getVariableName() - + ") has an oldInverseEntity (" + oldInverseEntity + ") which is not that entity.\n" - + "Verify the consistency of your input problem for that shadow variable."); + throw new IllegalStateException(""" + The entity (%s) has a list variable (%s) and one of its elements (%s) which has a shadow variable (%s) \ + has an oldInverseEntity (%s) which is not that entity. + Verify the consistency of your input problem for that shadow variable.""" + .formatted(inverseEntity, sourceVariableDescriptor.getVariableName(), element, + shadowVariableDescriptor.getVariableName(), oldInverseEntity)); } setInverse(scoreDirector, inverseEntity, element); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java new file mode 100644 index 0000000000..e4fc647c90 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java @@ -0,0 +1,262 @@ +package ai.timefold.solver.core.impl.domain.variable; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor; +import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor; +import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.impl.util.CollectionUtils; +import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; +import ai.timefold.solver.core.preview.api.domain.metamodel.LocationInList; + +final class ListVariableState { + + private final ListVariableDescriptor sourceVariableDescriptor; + + private ExternalizedIndexVariableProcessor externalizedIndexProcessor = null; + private ExternalizedSingletonListInverseVariableProcessor externalizedInverseProcessor = null; + private AbstractExternalizedNextPrevElementVariableProcessor externalizedPreviousElementProcessor = null; + private AbstractExternalizedNextPrevElementVariableProcessor externalizedNextElementProcessor = null; + + private boolean canUseExternalizedLocation = false; + private boolean requiresLocationMap = true; + private InnerScoreDirector scoreDirector; + private int unassignedCount = 0; + private Map elementLocationMap; + + public ListVariableState(ListVariableDescriptor sourceVariableDescriptor) { + this.sourceVariableDescriptor = sourceVariableDescriptor; + } + + public void linkIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { + this.externalizedIndexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); + } + + public void linkInverseVariable(InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { + this.externalizedInverseProcessor = + new ExternalizedSingletonListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + } + + public void linkPreviousElementVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { + this.externalizedPreviousElementProcessor = + new ExternalizedPreviousElementVariableProcessor<>(shadowVariableDescriptor); + } + + public void linkNextElementVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { + this.externalizedNextElementProcessor = new ExternalizedNextElementVariableProcessor<>(shadowVariableDescriptor); + } + + public void initialize(InnerScoreDirector scoreDirector, int initialUnassignedCount) { + this.scoreDirector = scoreDirector; + this.unassignedCount = initialUnassignedCount; + + this.canUseExternalizedLocation = externalizedIndexProcessor != null && externalizedInverseProcessor != null; + this.requiresLocationMap = !canUseExternalizedLocation + || externalizedPreviousElementProcessor == null || externalizedNextElementProcessor == null; + if (requiresLocationMap) { + if (elementLocationMap == null) { + elementLocationMap = CollectionUtils.newIdentityHashMap(unassignedCount); + } else { + elementLocationMap.clear(); + } + } else { + elementLocationMap = null; + } + } + + public void addElement(Object entity, List elements, Object element, int index) { + if (requiresLocationMap) { + var location = ElementLocation.of(entity, index); + var oldLocation = elementLocationMap.put(element, location); + if (oldLocation != null) { + throw new IllegalStateException( + "The supply for list variable (%s) is corrupted, because the element (%s) at index (%d) already exists (%s)." + .formatted(sourceVariableDescriptor, element, index, oldLocation)); + } + } + if (externalizedIndexProcessor != null) { + externalizedIndexProcessor.addElement(scoreDirector, element, index); + } + if (externalizedInverseProcessor != null) { + externalizedInverseProcessor.addElement(scoreDirector, entity, element); + } + if (externalizedPreviousElementProcessor != null) { + externalizedPreviousElementProcessor.setElement(scoreDirector, elements, element, index); + } + if (externalizedNextElementProcessor != null) { + externalizedNextElementProcessor.setElement(scoreDirector, elements, element, index); + } + unassignedCount--; + } + + public void removeElement(Object entity, Object element, int index) { + if (requiresLocationMap) { + var oldElementLocation = elementLocationMap.remove(element); + if (oldElementLocation == null) { + throw new IllegalStateException( + "The supply for list variable (%s) is corrupted, because the element (%s) at index (%d) was already unassigned (%s)." + .formatted(sourceVariableDescriptor, element, index, oldElementLocation)); + } + var oldIndex = oldElementLocation.index(); + if (oldIndex != index) { + throw new IllegalStateException( + "The supply for list variable (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)." + .formatted(sourceVariableDescriptor, element, index, oldIndex, index)); + } + } + if (externalizedIndexProcessor != null) { + externalizedIndexProcessor.removeElement(scoreDirector, element); + } + if (externalizedInverseProcessor != null) { + externalizedInverseProcessor.removeElement(scoreDirector, entity, element); + } + if (externalizedPreviousElementProcessor != null) { + externalizedPreviousElementProcessor.unsetElement(scoreDirector, element); + } + if (externalizedNextElementProcessor != null) { + externalizedNextElementProcessor.unsetElement(scoreDirector, element); + } + unassignedCount++; + } + + public void unassignElement(Object element) { + if (requiresLocationMap) { + var oldLocation = elementLocationMap.remove(element); + if (oldLocation == null) { + throw new IllegalStateException( + "The supply for list variable (%s) is corrupted, because the element (%s) did not exist before unassigning." + .formatted(sourceVariableDescriptor, element)); + } + } + if (externalizedIndexProcessor != null) { + externalizedIndexProcessor.unassignElement(scoreDirector, element); + } + if (externalizedInverseProcessor != null) { + externalizedInverseProcessor.unassignElement(scoreDirector, element); + } + if (externalizedPreviousElementProcessor != null) { + externalizedPreviousElementProcessor.unsetElement(scoreDirector, element); + } + if (externalizedNextElementProcessor != null) { + externalizedNextElementProcessor.unsetElement(scoreDirector, element); + } + unassignedCount++; + } + + public boolean changeElement(Object entity, List elements, int index) { + var element = elements.get(index); + var boxedIndex = Integer.valueOf(index); + var locationsDiffer = processElementLocation(entity, element, boxedIndex); + if (externalizedIndexProcessor != null) { + externalizedIndexProcessor.changeElement(scoreDirector, element, boxedIndex); + } + if (externalizedInverseProcessor != null) { + externalizedInverseProcessor.changeElement(scoreDirector, entity, element); + } + if (externalizedPreviousElementProcessor != null) { + externalizedPreviousElementProcessor.setElement(scoreDirector, elements, element, index); + } + if (externalizedNextElementProcessor != null) { + externalizedNextElementProcessor.setElement(scoreDirector, elements, element, index); + } + return locationsDiffer; + } + + private boolean processElementLocation(Object entity, Object element, Integer index) { + if (requiresLocationMap) { // Update the location and figure out if it is different from previous. + var newLocation = ElementLocation.of(entity, index); + var oldLocation = elementLocationMap.put(element, newLocation); + if (oldLocation == null) { + unassignedCount--; + return true; + } + return !newLocation.equals(oldLocation); + } else { // Read the location and figure out if it is different from previous. + var previousEntity = getInverseSingleton(element); + if (previousEntity == null) { + unassignedCount--; + return true; + } + return previousEntity != entity || !Objects.equals(getIndex(element), index); + } + } + + public ElementLocation getLocationInList(Object planningValue) { + if (!canUseExternalizedLocation) { + return Objects.requireNonNullElse(elementLocationMap.get(planningValue), ElementLocation.unassigned()); + } else { + var inverse = getInverseSingleton(planningValue); + if (inverse == null) { + return ElementLocation.unassigned(); + } + return ElementLocation.of(inverse, getIndex(planningValue)); + } + } + + public Integer getIndex(Object planningValue) { + if (externalizedIndexProcessor == null) { + var elementLocation = elementLocationMap.get(planningValue); + if (elementLocation == null) { + return null; + } + return elementLocation.index(); + } + return externalizedIndexProcessor.getIndex(planningValue); + } + + public Object getInverseSingleton(Object planningValue) { + if (externalizedInverseProcessor == null) { + var elementLocation = elementLocationMap.get(planningValue); + if (elementLocation == null) { + return null; + } + return elementLocation.entity(); + } + return externalizedInverseProcessor.getInverseSingleton(planningValue); + } + + public Object getPreviousElement(Object element) { + if (externalizedPreviousElementProcessor == null) { + var elementLocation = getLocationInList(element); + if (!(elementLocation instanceof LocationInList locationInList)) { + return null; + } + var index = locationInList.index(); + if (index == 0) { + return null; + } + return sourceVariableDescriptor.getValue(locationInList.entity()).get(index - 1); + } + return externalizedPreviousElementProcessor.getElement(element); + } + + public Object getNextElement(Object element) { + if (externalizedNextElementProcessor == null) { + var elementLocation = getLocationInList(element); + if (!(elementLocation instanceof LocationInList locationInList)) { + return null; + } + var list = sourceVariableDescriptor.getValue(locationInList.entity()); + var index = locationInList.index(); + if (index == list.size() - 1) { + return null; + } + return list.get(index + 1); + } + return externalizedNextElementProcessor.getElement(element); + } + + public int getUnassignedCount() { + return unassignedCount; + } + + void close() { + elementLocationMap = null; + } + +} 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 f12b566ad5..e1956a847f 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 @@ -1,6 +1,7 @@ package ai.timefold.solver.core.impl.domain.variable; import ai.timefold.solver.core.api.domain.variable.ListVariableListener; +import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.index.IndexShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.index.IndexVariableSupply; @@ -12,7 +13,7 @@ import ai.timefold.solver.core.preview.api.domain.metamodel.ElementLocation; /** - * Single source of truth for all information about elements inside list variables. + * Single source of truth for all information about elements inside {@link PlanningListVariable list variables}. * Shadow variables can be connected to this class to save on iteration costs * that would've been incurred otherwise if using variable listeners for each of them independently. * This way, there is only one variable listener for all such shadow variables, @@ -22,10 +23,17 @@ * If a particular shadow variable is externalized, * it means that there is a field on an entity holding the value of the shadow variable. * In this case, we will attempt to use that value. - * Otherwise, we will keep an internal track of all the possible shadow variables (index, inverse, previous, next, ...) - * and use their values from this internal representation. + * Otherwise, we will keep an internal track of all the possible shadow variables + * ({@link ai.timefold.solver.core.api.domain.variable.IndexShadowVariable}, + * {@link ai.timefold.solver.core.api.domain.variable.InverseRelationShadowVariable}, + * {@link ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable}, + * {@link ai.timefold.solver.core.api.domain.variable.NextElementShadowVariable}), + * and use values from this internal representation. * * @param + * @see ListVariableState The logic of switching between internal and externalized shadow variables. + * @see ExternalizedListVariableStateSupply The external representation of these shadow variables, + * which doesn't care whether the variable is internal or externalized. */ public interface ListVariableStateSupply extends SourcedVariableListener, From a47d867698f8f426241800233713cc167e56e699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 26 Nov 2024 12:55:06 +0100 Subject: [PATCH 13/17] Cleanup --- .../ExternalizedListVariableStateSupply.java | 18 ++++---- .../domain/variable/ListVariableState.java | 21 ++++++---- .../variable/ListVariableStateSupply.java | 8 ++-- .../support/VariableListenerSupport.java | 41 +++++++++---------- 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 449faa1407..22b565de06 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -23,25 +23,23 @@ public ExternalizedListVariableStateSupply(ListVariableDescriptor sou } @Override - public void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { - listVariableState.linkIndexVariable(shadowVariableDescriptor); + public void externalize(IndexShadowVariableDescriptor shadowVariableDescriptor) { + listVariableState.linkDescriptor(shadowVariableDescriptor); } @Override - public void externalizeSingletonListInverseVariable( - InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { - listVariableState.linkInverseVariable(shadowVariableDescriptor); + public void externalize(InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { + listVariableState.linkDescriptor(shadowVariableDescriptor); } @Override - public void externalizePreviousElementShadowVariable( - PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { - listVariableState.linkPreviousElementVariable(shadowVariableDescriptor); + public void externalize(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { + listVariableState.linkDescriptor(shadowVariableDescriptor); } @Override - public void externalizeNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { - listVariableState.linkNextElementVariable(shadowVariableDescriptor); + public void externalize(NextElementShadowVariableDescriptor shadowVariableDescriptor) { + listVariableState.linkDescriptor(shadowVariableDescriptor); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java index e4fc647c90..6b144031c6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java @@ -33,21 +33,21 @@ public ListVariableState(ListVariableDescriptor sourceVariableDescrip this.sourceVariableDescriptor = sourceVariableDescriptor; } - public void linkIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor) { + public void linkDescriptor(IndexShadowVariableDescriptor shadowVariableDescriptor) { this.externalizedIndexProcessor = new ExternalizedIndexVariableProcessor<>(shadowVariableDescriptor); } - public void linkInverseVariable(InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { + public void linkDescriptor(InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { this.externalizedInverseProcessor = new ExternalizedSingletonListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); } - public void linkPreviousElementVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { + public void linkDescriptor(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { this.externalizedPreviousElementProcessor = new ExternalizedPreviousElementVariableProcessor<>(shadowVariableDescriptor); } - public void linkNextElementVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor) { + public void linkDescriptor(NextElementShadowVariableDescriptor shadowVariableDescriptor) { this.externalizedNextElementProcessor = new ExternalizedNextElementVariableProcessor<>(shadowVariableDescriptor); } @@ -150,10 +150,9 @@ public void unassignElement(Object element) { public boolean changeElement(Object entity, List elements, int index) { var element = elements.get(index); - var boxedIndex = Integer.valueOf(index); - var locationsDiffer = processElementLocation(entity, element, boxedIndex); + var locationsDiffer = processElementLocation(entity, element, index); if (externalizedIndexProcessor != null) { - externalizedIndexProcessor.changeElement(scoreDirector, element, boxedIndex); + externalizedIndexProcessor.changeElement(scoreDirector, element, index); } if (externalizedInverseProcessor != null) { externalizedInverseProcessor.changeElement(scoreDirector, entity, element); @@ -167,7 +166,7 @@ public boolean changeElement(Object entity, List elements, int index) { return locationsDiffer; } - private boolean processElementLocation(Object entity, Object element, Integer index) { + private boolean processElementLocation(Object entity, Object element, int index) { if (requiresLocationMap) { // Update the location and figure out if it is different from previous. var newLocation = ElementLocation.of(entity, index); var oldLocation = elementLocationMap.put(element, newLocation); @@ -182,10 +181,14 @@ private boolean processElementLocation(Object entity, Object element, Integer in unassignedCount--; return true; } - return previousEntity != entity || !Objects.equals(getIndex(element), index); + return previousEntity != entity || !equalsIntegerAndInt(getIndex(element), index); } } + private static boolean equalsIntegerAndInt(Integer integer, int i) { + return integer != null && integer == i; + } + public ElementLocation getLocationInList(Object planningValue) { if (!canUseExternalizedLocation) { return Objects.requireNonNullElse(elementLocationMap.get(planningValue), ElementLocation.unassigned()); 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 e1956a847f..7292b303ed 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 @@ -41,13 +41,13 @@ public interface ListVariableStateSupply extends SingletonInverseVariableSupply, IndexVariableSupply { - void externalizeIndexVariable(IndexShadowVariableDescriptor shadowVariableDescriptor); + void externalize(IndexShadowVariableDescriptor shadowVariableDescriptor); - void externalizeSingletonListInverseVariable(InverseRelationShadowVariableDescriptor shadowVariableDescriptor); + void externalize(InverseRelationShadowVariableDescriptor shadowVariableDescriptor); - void externalizePreviousElementShadowVariable(PreviousElementShadowVariableDescriptor shadowVariableDescriptor); + void externalize(PreviousElementShadowVariableDescriptor shadowVariableDescriptor); - void externalizeNextElementShadowVariable(NextElementShadowVariableDescriptor shadowVariableDescriptor); + void externalize(NextElementShadowVariableDescriptor shadowVariableDescriptor); @Override ListVariableDescriptor getSourceVariableDescriptor(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java index 9956ae5a55..cb69e86a52 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java @@ -10,6 +10,7 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; +import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.cascade.CascadingUpdateShadowVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor; @@ -59,39 +60,35 @@ public static VariableListenerSupport create(InnerScoreDi } public void linkVariableListeners() { + var listVariableStateSupply = listVariableDescriptor == null ? null : demand(listVariableDescriptor.getStateDemand()); scoreDirector.getSolutionDescriptor().getEntityDescriptors().stream() .map(EntityDescriptor::getDeclaredShadowVariableDescriptors) .flatMap(Collection::stream) .filter(ShadowVariableDescriptor::hasVariableListener) .sorted(Comparator.comparingInt(ShadowVariableDescriptor::getGlobalShadowOrder)) - .forEach(this::processShadowVariableDescriptor); + .forEach(d -> { + // All information about elements in all shadow variables is tracked in a centralized place. + // Therefore all list-related shadow variables need to be connected to that centralized place. + // Shadow variables which are not related to a list variable are processed normally. + if (listVariableStateSupply == null) { + processShadowVariableDescriptorWithoutListVariable(d); + } else { + processShadowVariableDescriptorWithListVariable(d, listVariableStateSupply); + } + }); } - private void processShadowVariableDescriptor(ShadowVariableDescriptor shadowVariableDescriptor) { - if (listVariableDescriptor == null) { - processShadowVariableDescriptorWithoutListVariable(shadowVariableDescriptor); - } else { - // All information about elements in all shadow variables is tracked in a centralized place. - // Therefore all list-related shadow variables need to be connected to that centralized place. - // Shadow variables which are not related to a list variable are processed normally. - processShadowVariableDescriptorWithListVariable(shadowVariableDescriptor); - } - } - - private void processShadowVariableDescriptorWithListVariable(ShadowVariableDescriptor shadowVariableDescriptor) { + private void processShadowVariableDescriptorWithListVariable(ShadowVariableDescriptor shadowVariableDescriptor, + ListVariableStateSupply listVariableStateSupply) { if (shadowVariableDescriptor instanceof IndexShadowVariableDescriptor indexShadowVariableDescriptor) { - demand(listVariableDescriptor.getStateDemand()) - .externalizeIndexVariable(indexShadowVariableDescriptor); + listVariableStateSupply.externalize(indexShadowVariableDescriptor); } else if (shadowVariableDescriptor instanceof InverseRelationShadowVariableDescriptor inverseRelationShadowVariableDescriptor) { - demand(listVariableDescriptor.getStateDemand()) - .externalizeSingletonListInverseVariable(inverseRelationShadowVariableDescriptor); + listVariableStateSupply.externalize(inverseRelationShadowVariableDescriptor); } else if (shadowVariableDescriptor instanceof PreviousElementShadowVariableDescriptor previousElementShadowVariableDescriptor) { - demand(listVariableDescriptor.getStateDemand()) - .externalizePreviousElementShadowVariable(previousElementShadowVariableDescriptor); + listVariableStateSupply.externalize(previousElementShadowVariableDescriptor); } else if (shadowVariableDescriptor instanceof NextElementShadowVariableDescriptor nextElementShadowVariableDescriptor) { - demand(listVariableDescriptor.getStateDemand()) - .externalizeNextElementShadowVariable(nextElementShadowVariableDescriptor); - } else { // These are shadow variables not supported by the list variable supply; we use the standard mechanism. + listVariableStateSupply.externalize(nextElementShadowVariableDescriptor); + } else { // The list variable supply supports no other shadow variables. processShadowVariableDescriptorWithoutListVariable(shadowVariableDescriptor); } } From ad93f8d0ab3d86849cc438fbfd039cee196bda8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 26 Nov 2024 16:16:40 +0100 Subject: [PATCH 14/17] Less iteration --- .../variable/ExternalizedListVariableStateSupply.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index 22b565de06..e43ce274c0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -17,6 +17,9 @@ final class ExternalizedListVariableStateSupply private final ListVariableDescriptor sourceVariableDescriptor; private final ListVariableState listVariableState; + private boolean previousExternalized = false; + private boolean nextExternalized = false; + public ExternalizedListVariableStateSupply(ListVariableDescriptor sourceVariableDescriptor) { this.sourceVariableDescriptor = sourceVariableDescriptor; this.listVariableState = new ListVariableState<>(sourceVariableDescriptor); @@ -35,11 +38,13 @@ public void externalize(InverseRelationShadowVariableDescriptor shado @Override public void externalize(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { listVariableState.linkDescriptor(shadowVariableDescriptor); + previousExternalized = true; } @Override public void externalize(NextElementShadowVariableDescriptor shadowVariableDescriptor) { listVariableState.linkDescriptor(shadowVariableDescriptor); + nextExternalized = true; } @Override @@ -103,9 +108,11 @@ public void afterListVariableChanged(@NonNull ScoreDirector scoreDire var assignedElements = sourceVariableDescriptor.getValue(entity); var elementCount = assignedElements.size(); // Include the last element of the previous part of the list, if any, for the next element shadow var. - var firstChangeIndex = Math.max(0, fromIndex - 1); + // But only if the next element shadow var is externalized; otherwise, there is nothing to update. + var firstChangeIndex = nextExternalized ? Math.max(0, fromIndex - 1) : fromIndex; // Include the first element of the next part of the list, if any, for the previous element shadow var. - var lastChangeIndex = Math.min(toIndex + 1, elementCount); + // But only if the previous element shadow var is externalized; otherwise, there is nothing to update. + var lastChangeIndex = previousExternalized ? Math.min(toIndex + 1, elementCount) : toIndex; for (var index = firstChangeIndex; index < elementCount; index++) { var locationsDiffer = listVariableState.changeElement(entity, assignedElements, index); if (!locationsDiffer && index >= lastChangeIndex) { From 3bcd03885ba032eec93262eb472ae0da0bfe2b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 26 Nov 2024 17:10:53 +0100 Subject: [PATCH 15/17] Naming --- ...java => ExternalizedListInverseVariableProcessor.java} | 4 ++-- .../variable/ExternalizedListVariableStateSupply.java | 5 ----- .../core/impl/domain/variable/ListVariableState.java | 8 ++------ 3 files changed, 4 insertions(+), 13 deletions(-) rename core/src/main/java/ai/timefold/solver/core/impl/domain/variable/{ExternalizedSingletonListInverseVariableProcessor.java => ExternalizedListInverseVariableProcessor.java} (95%) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListInverseVariableProcessor.java similarity index 95% rename from core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java rename to core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListInverseVariableProcessor.java index 6f4ecba289..8632476fbc 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedSingletonListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListInverseVariableProcessor.java @@ -4,12 +4,12 @@ import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; -final class ExternalizedSingletonListInverseVariableProcessor { +final class ExternalizedListInverseVariableProcessor { private final InverseRelationShadowVariableDescriptor shadowVariableDescriptor; private final ListVariableDescriptor sourceVariableDescriptor; - public ExternalizedSingletonListInverseVariableProcessor( + public ExternalizedListInverseVariableProcessor( InverseRelationShadowVariableDescriptor shadowVariableDescriptor, ListVariableDescriptor sourceVariableDescriptor) { this.shadowVariableDescriptor = shadowVariableDescriptor; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java index e43ce274c0..041d44397c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListVariableStateSupply.java @@ -163,11 +163,6 @@ public ListVariableDescriptor getSourceVariableDescriptor() { return sourceVariableDescriptor; } - @Override - public void close() { - listVariableState.close(); - } - @Override public String toString() { return getClass().getSimpleName() + "(" + sourceVariableDescriptor.getVariableName() + ")"; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java index 6b144031c6..ce4629a783 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java @@ -19,7 +19,7 @@ final class ListVariableState { private final ListVariableDescriptor sourceVariableDescriptor; private ExternalizedIndexVariableProcessor externalizedIndexProcessor = null; - private ExternalizedSingletonListInverseVariableProcessor externalizedInverseProcessor = null; + private ExternalizedListInverseVariableProcessor externalizedInverseProcessor = null; private AbstractExternalizedNextPrevElementVariableProcessor externalizedPreviousElementProcessor = null; private AbstractExternalizedNextPrevElementVariableProcessor externalizedNextElementProcessor = null; @@ -39,7 +39,7 @@ public void linkDescriptor(IndexShadowVariableDescriptor shadowVariab public void linkDescriptor(InverseRelationShadowVariableDescriptor shadowVariableDescriptor) { this.externalizedInverseProcessor = - new ExternalizedSingletonListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); + new ExternalizedListInverseVariableProcessor<>(shadowVariableDescriptor, sourceVariableDescriptor); } public void linkDescriptor(PreviousElementShadowVariableDescriptor shadowVariableDescriptor) { @@ -258,8 +258,4 @@ public int getUnassignedCount() { return unassignedCount; } - void close() { - elementLocationMap = null; - } - } From 821944aac046b01ef6d256fc95d5164fb9127935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 26 Nov 2024 20:05:17 +0100 Subject: [PATCH 16/17] Last ditch attempt --- .../domain/variable/ListVariableState.java | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java index ce4629a783..613ee59f2f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ListVariableState.java @@ -150,43 +150,73 @@ public void unassignElement(Object element) { public boolean changeElement(Object entity, List elements, int index) { var element = elements.get(index); - var locationsDiffer = processElementLocation(entity, element, index); - if (externalizedIndexProcessor != null) { + var difference = processElementLocation(entity, element, index); + if (difference.indexChanged && externalizedIndexProcessor != null) { externalizedIndexProcessor.changeElement(scoreDirector, element, index); } - if (externalizedInverseProcessor != null) { + if (difference.entityChanged && externalizedInverseProcessor != null) { externalizedInverseProcessor.changeElement(scoreDirector, entity, element); } + // Next and previous still might have changed, even if the index and entity did not. + // Those are based on what happened elsewhere in the list. if (externalizedPreviousElementProcessor != null) { externalizedPreviousElementProcessor.setElement(scoreDirector, elements, element, index); } if (externalizedNextElementProcessor != null) { externalizedNextElementProcessor.setElement(scoreDirector, elements, element, index); } - return locationsDiffer; + return difference.anythingChanged; } - private boolean processElementLocation(Object entity, Object element, int index) { + private ChangeType processElementLocation(Object entity, Object element, int index) { if (requiresLocationMap) { // Update the location and figure out if it is different from previous. var newLocation = ElementLocation.of(entity, index); var oldLocation = elementLocationMap.put(element, newLocation); if (oldLocation == null) { unassignedCount--; - return true; + return ChangeType.BOTH; } - return !newLocation.equals(oldLocation); + return compareLocations(entity, oldLocation.entity(), index, oldLocation.index()); } else { // Read the location and figure out if it is different from previous. - var previousEntity = getInverseSingleton(element); - if (previousEntity == null) { + var oldEntity = getInverseSingleton(element); + if (oldEntity == null) { unassignedCount--; - return true; + return ChangeType.BOTH; + } + var oldIndex = getIndex(element); + if (oldIndex == null) { // Technically impossible, but we handle it anyway. + return ChangeType.BOTH; } - return previousEntity != entity || !equalsIntegerAndInt(getIndex(element), index); + return compareLocations(entity, oldEntity, index, oldIndex); + } + } + + private static ChangeType compareLocations(Object entity, Object otherEntity, int index, int otherIndex) { + if (entity != otherEntity) { + return ChangeType.BOTH; // Entity changed, so index changed too. + } else if (index != otherIndex) { + return ChangeType.INDEX; + } else { + return ChangeType.NEITHER; } } - private static boolean equalsIntegerAndInt(Integer integer, int i) { - return integer != null && integer == i; + private enum ChangeType { + + BOTH(true, true), + INDEX(false, true), + NEITHER(false, false); + + final boolean anythingChanged; + final boolean entityChanged; + final boolean indexChanged; + + ChangeType(boolean entityChanged, boolean indexChanged) { + this.anythingChanged = entityChanged || indexChanged; + this.entityChanged = entityChanged; + this.indexChanged = indexChanged; + } + } public ElementLocation getLocationInList(Object planningValue) { From 224fc61a7b495e87ee2426ea550ff55371b11cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Wed, 27 Nov 2024 07:27:12 +0100 Subject: [PATCH 17/17] One more --- ...ternalizedNextPrevElementVariableProcessor.java | 2 +- .../ExternalizedIndexVariableProcessor.java | 14 +++++--------- .../ExternalizedListInverseVariableProcessor.java | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java index 199dd9fdfc..304fa1fc93 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/AbstractExternalizedNextPrevElementVariableProcessor.java @@ -26,7 +26,7 @@ public void unsetElement(InnerScoreDirector scoreDirector, Object } protected void setValue(InnerScoreDirector scoreDirector, Object element, Object value) { - if (shadowVariableDescriptor.getValue(element) != value) { + if (getElement(element) != value) { scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); shadowVariableDescriptor.setValue(element, value); scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java index aa490c2a88..f3ece2ef83 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedIndexVariableProcessor.java @@ -18,13 +18,7 @@ public void addElement(InnerScoreDirector scoreDirector, Object el } public void removeElement(InnerScoreDirector scoreDirector, Object element) { - setIndex(scoreDirector, element, null); - } - - private void setIndex(InnerScoreDirector scoreDirector, Object element, Object value) { - scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); - shadowVariableDescriptor.setValue(element, value); - scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); + updateIndex(scoreDirector, element, null); } public void unassignElement(InnerScoreDirector scoreDirector, Object element) { @@ -36,9 +30,11 @@ public void changeElement(InnerScoreDirector scoreDirector, Object } private void updateIndex(InnerScoreDirector scoreDirector, Object element, Integer index) { - var oldIndex = shadowVariableDescriptor.getValue(element); + var oldIndex = getIndex(element); if (!Objects.equals(oldIndex, index)) { - setIndex(scoreDirector, element, index); + scoreDirector.beforeVariableChanged(shadowVariableDescriptor, element); + shadowVariableDescriptor.setValue(element, index); + scoreDirector.afterVariableChanged(shadowVariableDescriptor, element); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListInverseVariableProcessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListInverseVariableProcessor.java index 8632476fbc..889e587d2a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListInverseVariableProcessor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/ExternalizedListInverseVariableProcessor.java @@ -48,7 +48,7 @@ public void removeElement(InnerScoreDirector scoreDirector, Object } public void unassignElement(InnerScoreDirector scoreDirector, Object element) { - setInverse(scoreDirector, null, element); + changeElement(scoreDirector, null, element); } public void changeElement(InnerScoreDirector scoreDirector, Object entity, Object element) {